On this page (14)

Contract Derivation Rules

In short
Technical doc for the generator script (the program that will produce all 37 individual contracts from the templates). Defines which template each person gets, which addendums attach, and how to handle edge cases. Mostly for the developer — you can skim or skip.

Purpose: rules the generator (when built) uses to decide, for each employee in Employee Contract Info - Wild.csv:

  1. Which template (A / B / C)?
  2. Which notice tier (Standard / Senior / Director)?
  3. Which addendums (1 Drivers / 2 Bakers / 3 Young Workers)?
  4. Whether the Director's Service Letter attaches.
  5. What merge field values populate from the CSV.

Reproducibility: any future regeneration should produce the same template/addendum assignments unless the CSV changes or rules are updated here.


1. Template assignment

Apply rules in order. First match wins.

Rule Condition Result
1 Job Title contains "Consultant" Template C — Consultancy
2 Job Title contains "Evening Packer", "Weekend Cleaner", "Visiting", "Stage" Template B — Casual
3 Team(s) contains "Evening Packing" OR "Weekend Cleaning" Template B — Casual
4 Hours Per Week = 0 AND Salary is blank Flag as incomplete — do not generate (Harry Reith case)
5 Otherwise Template A — Employment

Manual overrides currently applied (cases where the data is ambiguous):

  • Caledonia Jeffrey: kept on Template-A with 9 guaranteed hours/week. The Drive analysis (2026-05-17) showed her current "casual" contract is structurally a full employment contract with guaranteed hours; moving her to Template-B would be a downward variation of existing rights. Overridden in overrides.yaml.
  • Cameron Simpson, Rebekah France: PT cleaners on Weekend Cleaning team → B (confirm intent — flip to A if they want guaranteed hours)
  • David Johnson: 0h, "Consultant" title → C

2. Notice tier

Determines the post-probation notice period in clause 11.2 of Template A.

Tier Notice (either side) Trigger condition
Director 6 months Job Title = "Director" OR person is named on Companies House as a current director
Senior 2 months Job Title is one of: "Head Baker", "Senior Baker", "Head Driver", "Assistant Operations Manager", "Operations Manager", "Shop Manager"
Standard 1 month All other Template A staff
N/A (no notice required) Template B (casual) and Template C (consultancy — uses contract-specific notice)

Statutory minimum floor applies for staff with 2+ years' continuous service (employer side only): 1 week per complete year, capped at 12 weeks.


3. Addendum assignment

A single employee can receive multiple addendums.

Addendum 1 — Drivers

Attaches when any of these is true:

  • Job Title contains "Driver"
  • Team(s) contains "Drivers"
  • Manual flag (set per employee for borderline cases — e.g. Sean Mackenzie may need this depending on whether he drives in practice)

Addendum 2 — Bakers (Senior/Head)

Attaches when Job Title is one of:

  • "Head Baker"
  • "Senior Baker"

Does not attach to "Baker" or "Trainee Baker" — those roles don't have recipe-level access.

Addendum 3 — Young Workers

Attaches when, at the start date of the contract being issued:

  • Age in years (current_dateDate of Birth) is < 18

Detaches automatically when the worker turns 18 (and main contract terms become fully effective).

Director's Service Letter

Attaches when person is a statutory director (verified against Companies House register).


4. Pay phrasing

BrightHR "Rate" Contract phrasing
Hourly "£{{rate}} per hour, paid monthly by BACS in arrears"
Monthly "£{{rate × 12}} per annum, paid in 12 equal monthly instalments"
Annually "£{{rate}} per annum, paid in 12 equal monthly instalments"

For casual workers (Template B), always "£{{rate}} per hour, plus accrued holiday pay at 12.07% of hours worked, shown on each payslip" regardless of CSV basis.

For consultants (Template C), CSV basis is treated as the payment cadence of the retainer/fee, not annualised.


5. Merge field sources

Variable Template Source
{{first_name}} All CSV: First Name
{{last_name}} All CSV: Last Name
{{address}} All CSV: concat Address Line 1 + Address Line 2 + Address Line 3 + Town/City + County + Postcode (skip blanks)
{{dob}} Addendum 3 CSV: Date of Birth
{{start_date}} All CSV: Start Date
{{job_title}} A, B CSV: Job Title
{{reports_to}} A, B CSV: Reports To (first name only if multiple)
{{location}} A, B CSV: Location
{{hours_per_week}} A CSV: Hours Per Week, with per-employee overrides applied from overrides.yaml
{{pay_basis}} All CSV: Rate (Hourly / Monthly / Annually)
{{rate}} All CSV: Salary (with pay-phrasing rule from §4)
{{holiday_entitlement}} A CSV: Holiday Entitlement
{{notice_period}} A Derived: see §2
{{pension_provider}} A Per-employee variable — to be confirmed. Source TBC (likely BrightHR custom field or overrides.yaml). Until confirmed, generator should fail-fast on missing value rather than default
{{employer_contribution}} A CSV: Employer Contribution. Flag as missing if blank (do not silently default)
{{employee_contribution}} A CSV: Employee Contribution. Flag as missing if blank (do not silently default)
{{annual_salary}} A Computed: if pay_basis = Monthly → rate × 12; if Annually → rate; if Hourly → not used
{{services_description}} C Not in CSV — manual entry
{{trading_name}} C Not in CSV — manual entry
{{rate_basis}} C Not in CSV — manual entry
{{director_appointment_date}} Director's letter Companies House: 25 Aug 2016 (John)
{{annual_hours}} A §4.8 Per-employee — overrides.yaml annual_hours field. Only required when annualised_hours: true. Generator should fail-fast on missing when annualised flag is set, and not emit §4.8 when the flag is unset.
{{notice_tier}} Cover-letter Derived: same as §2 notice tier (Standard / Senior / Director). Used to drive the cover-letter's Senior-asymmetric-notice carve-out paragraph.
{{addendum_drivers}} Cover-letter Boolean. True when Addendum 1 attaches per §3 Drivers rules.
{{addendum_bakers}} Cover-letter Boolean. True when Addendum 2 attaches per §3 Senior/Head Baker rules.
{{addendum_young}} Cover-letter Boolean. True when Addendum 3 attaches per §3 (DOB < 18 at start).
{{directors_letter}} Cover-letter Boolean. True for statutory directors (per §3 Director's Service Letter rule).
{{letter_signer}} Cover-letter Default "John Castley". For John's own letter, override to "Tim Brown" (the Assistant Operations Manager on behalf of the Board — per cover-letter.md "Signer convention" note).
Company constants All Wild Hearth Bakery Ltd, SC543706, 42 Comrie Street registered office, 15A Cultybraggan Camp, Comrie, Crieff PH6 2AB principal place of business

6. Edge cases requiring human decision

Person Issue Default Override note
John Castley Pay vs NMW Hours capped at 19.8/wk via overrides.yaml Office-holder duties beyond 19.8h are unpaid director duties, not employment
Michael Hansen Record incomplete Hold contract until data collected
Harry Reith 0 hours, no pay Hold contract until pattern confirmed (A or B?)
Sean Mackenzie Manages drivers, not on Drivers team Default no Addendum 1 Confirm whether he drives in practice
Bravo Nyamudoka Excluded from this pass Skip generation Add own record to BrightHR before generation
Caledonia Jeffrey PT in CSV but on Evening Packing team Template B Confirm with Caledonia
Cameron Simpson, Rebekah France PT cleaners, regular hours Template B Confirm intent — Template A if they want guaranteed-hours contract

7. Updating these rules

When the rules change:

  1. Update this document first.
  2. Update the generator script to match.
  3. Regenerate and review any contracts whose classification would change.
  4. Note the change with date in the section below.

8. Templating engine

Chosen: Jinja2.

Reasons:

  • Supports conditionals ({% if pay_basis == 'Hourly' %}...{% endif %}) needed in Template A §6.1
  • Supports the section-style blocks used in cover-letter.md
  • Standard Python library, well-documented
  • Easy to add filters (e.g. {{ rate | round(2) }}, {{ pay_basis | lower }})

Generator should:

  1. Load CSV → dict per employee
  2. Apply derivation rules (§1–§3) to choose template + addendums
  3. Compute derived fields (§4 annual_salary; §2 notice_period; etc.)
  4. Apply per-employee overrides from overrides.yaml
  5. Render template using Jinja2
  6. Strip any PLACEHOLDER... markers (see §9)
  7. Emit one Markdown file per employee, plus a manifest

9. Placeholder marker convention

Anything in the form PLACEHOLDER...content... is a note for Zoe or internal review, not contract text. The generator must remove all PLACEHOLDER... blocks before issuing.

This applies across all template files. Examples currently in the pack:

  • Template A §12.3 (training repayment placeholder)

Other in-doc notes that are binding contract text should not use this marker — e.g. Template C §1.5 is a real clause requiring CEST assessment, not a placeholder.

Change log

  • 2026-05-17 — Initial rules drafted. Hours override for John Castley moved to overrides.yaml. Pension provider set as per-employee variable (TBC source). Pension contributions no longer silently defaulted. Template C trigger simplified to Job Title contains "Consultant". Jinja2 chosen as templating engine. PLACEHOLDER... placeholder convention added.
Source: docs/contracts/derivation-rules.md