# SKILL — lfcs-letter-of-offer-draft

**Version:** v1 (2026-05-06)

## Purpose

Generate `Letter-of-Offer-{job}-{date}.docx` — the signed-ready bid cover letter — from the priced BOQ + `Inclusions-Exclusions.md` + `00-Bid-Prep.md` + the final lump-sum price. Output conforms to the seven-section structure codified in `lfcs-standards/templates/letter-of-offer-structure.md`. Closes the bid-submission pipeline: the LoO is the cover document Liam signs and emails to the Head Contractor alongside the priced BOQ + incl-excl pack.

## Trigger

User says any of:
- "Draft the Letter of Offer for [job]"
- "Generate the LoO for [job]"
- "Build the cover letter for [job] at $X"
- "Run letter-of-offer on [job]"

Or invoked at end of the bid-submission skill chain by cowork-lfcs after `lfcs-incl-excl-draft v3` (and after Rocky has finalised pricing).

## Inputs

Required:
- `job_folder` — absolute path
- `final_price_ex_gst` — final lump-sum price as integer or decimal (e.g. `434462.00`)
- `recipient_company` — Head Contractor legal name (e.g. `The Civil Experts Pty Ltd`)
- `recipient_address` — multi-line string (e.g. `Suite 2.01, Building G, 350 Parramatta Rd\nHomebush West, NSW 2140`)
- `recipient_salutation` — name or `Dear,` if unknown (mirrors Liam's HCB LoO)

Optional:
- `validity_days` — default `30`
- `submission_date` — default today (DD MMM YYYY format)
- `package_name` — default pulled from `00-brief.md` (e.g. `FRP Package`, `Stormwater Pit Package`)

Implicit (must exist):
- `{job_folder}/00-brief.md`
- `{job_folder}/00-Bid-Prep.md` (v3 — provides ADDED rows, cross-refs, Rate Only, OPTION, cranage)
- `{job_folder}/02 - Scope and Pricing/02a - Quotes and Estimates/Inclusions-Exclusions.md` (v3 — three-tier structure)
- `{job_folder}/02 - Scope and Pricing/02b - BOQ and Rate Schedules/Pre-Pricing-Grill-*.md`
- `lfcs-standards/templates/letter-of-offer-template.docx` (regression fixture)
- `lfcs-standards/templates/letter-of-offer-structure.md` (mandatory read)
- `lfcs-standards/templates/standard-exclusions-boilerplate.md` (mandatory read)
- `lfcs-standards/company-info.md` (signature block source)

## Behaviour

1. **Pre-flight** inputs. Abort if any missing. Specifically check:
   - Inclusions-Exclusions.md status frontmatter is NOT `draft-pre-call` — must be `submitted` or `final` before LoO generation. Rocky should have refined post-Tuesday call.
   - `00-Bid-Prep.md` v3 sections 6–10 (ADDED / cross-ref / Rate Only / OPTION / cranage) must all be populated (or marked "N/A — none identified").
   - `final_price_ex_gst` is a positive number, not zero, not "TBC".

2. **Read the spec files first** — `letter-of-offer-structure.md` + `standard-exclusions-boilerplate.md` + `letter-of-offer-template.docx` (regression fixture). Output structure is dictated by the spec.

3. **Read company-info.md** for the signature block (Liam Fitzgerald, MD, ABN, address, phone, email). NEVER hardcode — pull from source.

4. **Convert price to words.** `434462.00` → "Four Hundred Thirty-Four Thousand Four Hundred Sixty-Two Dollars and Zero cents". Use Python `num2words` or equivalent. Both words AND figures appear in the LoO; figures formatted with thousands separator and `ex GST` suffix.

5. **Build LoO body — Section 1: Header block.**
   - Date (default today, DD MMM YYYY)
   - Recipient legal name
   - Recipient address (multi-line)
   - "RE:  {Project name from 00-brief} - {package_name}"
   - Salutation (`Dear,` if no name supplied, else `Dear {name},`)

6. **Build LoO body — Section 2: Submission paragraph.** Verbatim:
   > LF Construction Services is pleased to submit our tender offer of the above-mentioned project. For a detailed breakdown of our offer please see the submission schedule attached.

7. **Build LoO body — Section 3: Lump sum statement.** Format:
   > Our Lump Sum pricing for the scope is:
   > {WORDS} excluding GST (${FIGURES} ex GST)

8. **Build LoO body — Section 4: Standard Exclusions.** Heading: `STANDARD EXCLUSIONS`. Pull verbatim from `standard-exclusions-boilerplate.md` Section "The list". Bullet list. **No paraphrasing.**

9. **Build LoO body — Section 5: Preliminaries.** Heading: `PRELIMINARIES`. Pull verbatim from `standard-exclusions-boilerplate.md` Section "Standard Preliminaries Assumptions". Bullet list. **No paraphrasing.**

10. **Build LoO body — Section 6: Detailed Exclusions & Assumptions.** Heading: `DETAILED EXCLUSIONS & ASSUMPTIONS`. Three sub-sections, in this order:
    - **Exclusions** — pull from `Inclusions-Exclusions.md` Tier 3 Exclusions sub-section. Bullet list.
    - **Assumptions** — pull from `Inclusions-Exclusions.md` Tier 3 Assumptions sub-section. Bullet list. **All five mandatory clauses (where applicable per 00-Bid-Prep v3 sections 6–10) must appear here**:
      - Discrepancy callout (if take-off differs from HC's BOQ)
      - Scope-gap callout (if any ADDED rows in BOQ)
      - Cranage delineation (if job has lifts)
      - Hire-duration assumption (if hire items in scope)
      - Optional-rate flag (if OPTION rows in BOQ)
    - **Inclusions** — pull from `Inclusions-Exclusions.md` Tier 3 Inclusions sub-section. Bullet list. Class 2 finish (or domain equivalent) explicit. Architectural finishes excluded.

11. **Build LoO body — Section 7: Validity + sign-off.** Verbatim closing:
    > This quotation is valid for {validity_days} days from the date of submission. If you have any further queries relating to this quotation, please call to discuss. We trust that this meets with your approval, and we would welcome the opportunity to discuss the project further.
    > 
    > Yours sincerely,
    > 
    > {Signature block from company-info.md — Liam Fitzgerald / Managing Director / LF Construction Services Pty Ltd / Phone / Email}

12. **Render to docx** via the `docx` skill (matches `letter-of-offer-template.docx` formatting — Calibri 11pt, LFCS letterhead, page margins, bullet-point styling). The template is the exact look Liam signs.

13. **Write `{job_folder}/02 - Scope and Pricing/02a - Quotes and Estimates/Letter-of-Offer-{job_no}-{submission_date}.docx`**.

14. **Also write `{job_folder}/02 - Scope and Pricing/02a - Quotes and Estimates/Letter-of-Offer-{job_no}-{submission_date}.md`** — markdown shadow copy for vault search/diff. Frontmatter: `title`, `job`, `submission_date`, `final_price_ex_gst`, `recipient`, `created_by: cowork-lfcs via lfcs-letter-of-offer-draft v1`, `status: draft-pending-rocky-review`, `related: [00-Bid-Prep, Inclusions-Exclusions, Pre-Pricing-Grill, letter-of-offer-structure]`.

15. **Append project log + daily note + job _Log.md.**

16. **After vault writes commit, invoke `lfcs-airtable-sync target_table=Bids mode=upsert`** — set `Bids.Status = Submitted-Draft`, `Bids.Total Ex GST = final_price_ex_gst`, `Bids.LoO Generated = today`. Idempotent via `Job` link + `Bid Number`.

## Files to read (in order)

```
HQ-Vault/_System/CLAUDE.md
HQ-Vault/11_OpsMan_LFCS/_Brain/CLAUDE.md
HQ-Vault/11_OpsMan_LFCS/Operations/lfcs-standards/templates/letter-of-offer-structure.md  ← mandatory
HQ-Vault/11_OpsMan_LFCS/Operations/lfcs-standards/templates/standard-exclusions-boilerplate.md  ← mandatory
HQ-Vault/11_OpsMan_LFCS/Operations/lfcs-standards/templates/letter-of-offer-template.docx  ← regression fixture
HQ-Vault/11_OpsMan_LFCS/Operations/lfcs-standards/company-info.md
{job_folder}/00-brief.md
{job_folder}/00-Bid-Prep.md  ← v3, sections 6-10 must be populated
{job_folder}/02 - Scope and Pricing/02a - Quotes and Estimates/Inclusions-Exclusions.md  ← status must NOT be draft-pre-call
{job_folder}/02 - Scope and Pricing/02b - BOQ and Rate Schedules/Pre-Pricing-Grill-*.md
```

## Airtable behaviour

- **Reads:** `Jobs` (for Active Bid pointer), `Bids` (current draft for this job, if any).
- **Writes:** `Bids` table — sets `Status = Submitted-Draft`, `Total Ex GST`, `LoO Generated` date, links `LoO Document` field to vault file path. Calls `lfcs-airtable-sync target_table=Bids mode=upsert`. Idempotent via `Job` link + `Bid Number`.
- **Idempotency:** Re-run with same `final_price_ex_gst` is a no-op. Re-run with different price creates a new revision (`Bids.Revision = N+1`); old draft moves to `Status = Superseded` (per hard rule: never delete Airtable records).

## Output format

To user:

```
✓ Letter-of-Offer-{job_no}-{date}.docx generated
  Recipient: {recipient_company}
  Lump sum: {WORDS} ex GST (${FIGURES} ex GST)
  Validity: {N} days

  Mandatory clauses applied:
    Discrepancy callout: {YES / N/A}
    Scope-gap callout: {YES / N/A}
    Cranage delineation: {YES / N/A}
    Hire-duration: {YES / N/A}
    Optional rate: {YES / N/A}

  Files:
    docx: {abs path}
    md shadow: {abs path}

  Status: draft-pending-rocky-review

  Next steps:
    1. Open docx in Word, review for tone + project-specifics
    2. Liam signs (digital or print-sign-scan)
    3. Email to {recipient_company} with priced BOQ + Inclusions-Exclusions attached
    4. Rocky changes status frontmatter to "submitted" + appends submission timestamp + recipient email to job _Log.md
```

## Hard rules

- **Boilerplate is verbatim.** Standard Exclusions and Preliminaries sections must match `standard-exclusions-boilerplate.md` word-for-word. No paraphrasing.
- **Seven-section structure is non-negotiable.** Output must have exactly the seven sections from `letter-of-offer-structure.md` in order.
- **Lump sum spelled out in words AND figures.** No "approximately", no ranges, no GST inclusive — submission price always ex GST.
- **All five mandatory clauses fire when their trigger condition is met.** No silent omissions. Output flags each clause as YES or N/A with reasoning visible in the markdown shadow's frontmatter.
- **Signature block pulled from company-info.md.** Never hardcode Liam's contact details.
- **Status on first write is `draft-pending-rocky-review`.** Rocky changes to `submitted` after sending.
- **Append-only.** If `Letter-of-Offer-{job}-{date}.docx` exists, abort + ask user (re-run with new date if a revision is needed).
- **Regression fixture:** running this skill against the Hammond Canal Bridge inputs (BOQ + bid-prep + incl-excl + price=434462) must reproduce or improve the existing `letter-of-offer-template.docx`. Drift in any of: section order, clause wording, signature block, lump-sum format, exclusion list — blocks deployment.
- **Vault markdown shadow is source of truth.** docx is the polished output, but the .md shadow with frontmatter is what Airtable links + `_Log.md` references.
- **Never overwrite the canonical template.** `templates/letter-of-offer-template.docx` is read-only fixture. Job outputs go in the job folder, never in templates.

## Dependencies

- `lfcs-bid-prep-doc v3` (provides ADDED / cross-ref / Rate Only / OPTION / cranage feeders)
- `lfcs-incl-excl-draft v3` (provides three-tier exclusion structure)
- `lfcs-pre-grill` (provides assumption inputs)
- `templates/letter-of-offer-structure.md`
- `templates/standard-exclusions-boilerplate.md`
- `templates/letter-of-offer-template.docx` (regression fixture)
- `lfcs-standards/company-info.md` (signature block)
- `docx` skill (rendering)
- `lfcs-airtable-sync` (final write to Bids)

## Failure modes

| Failure | Detection | Recovery |
|---|---|---|
| Missing required input | Pre-flight | Abort + name the missing skill to run first |
| `Inclusions-Exclusions.md` status is `draft-pre-call` | Frontmatter check | Abort + tell Rocky to refine post-Tuesday-call first |
| `00-Bid-Prep.md` v3 sections 6-10 missing | Section presence check | Abort + tell Rocky to re-run lfcs-bid-prep-doc v3 |
| `final_price_ex_gst` is zero / TBC / non-numeric | Type/value check | Abort + ask user |
| Number-to-words conversion fails | Library fallback | Use long-form manual conversion + flag for Rocky review |
| `Letter-of-Offer-*.docx` already exists for today | Pre-check | Abort + ask user (rev with timestamp suffix if intentional) |
| Airtable write fails | Per-write check | Log to `_System/Conflicts.md` + continue (vault MD is SoR) |
| Mandatory clause trigger ambiguous | Per-clause check | Mark TBC in markdown shadow + flag in output for Rocky pre-send |

## Test fixture

Hammond Canal Bridge regression run:
- Inputs: HCB BOQ ($434,462 ex GST) + bid-prep + incl-excl + price=434462 + recipient="The Civil Experts Pty Ltd" + address per HCB LoO
- Expected output: docx that matches `letter-of-offer-template.docx` line-for-line on:
  - Lump sum: "Four Hundred Thirty-Four Thousand Four Hundred Sixty-Two Dollars and Zero cents excluding GST ($434,462.00 ex GST)"
  - All 10 standard exclusion items in order
  - All 6 preliminaries assumptions in order
  - Detailed Exclusions: 15 items including earthworks, concrete supply, reo supply, cranage, demolition
  - Assumptions: includes discrepancy callout + scope-gap callout (diaphragm beams) + cranage delineation + hire-duration assumption (6-week scaffolding/walkway) + optional-rate flag (sacrificial ply)
  - Inclusions: formwork, concrete placement, reo install, Class 2 finish, precast parapet install, light broom finish, expansion joints, elastomeric bearings
  - 30-day validity
  - Liam Fitzgerald signature block

Any drift = blocking issue.
