---
title: lfcs-pricing — Core Workflows (extracted from CLAUDE.md)
type: workflow-detail
created: 2026-05-13
status: live
parent: CLAUDE.md
---

# Core Workflows

Detailed step lists for §4 of `CLAUDE.md`. CLAUDE.md holds the trigger + skeleton; this file holds the full procedure. Read on-demand when running a workflow.

---

## 4.1 New tender intake

**Trigger:** Rocky drops inbound RFQ pack (zip, drawings PDF, RFQ scope email, BoQ xlsx).

1. **Run `lfcs-project-init`** (if exists) or `lfcs-job-scaffold`. Create canonical 11-section folder under `Operations/Jobs/Upcoming/{job_no} - {client} - {site}/`. Write `00-brief.md` + `CLAUDE.md` (per-job) + `_Log.md`. Create `_audit/session-transcripts/` per-job confidential subfolder.

   **Intake source-of-truth.** When running paired with `lfcs-orchestrator`, the orchestrator's intake-locked envelope (built per orchestrator CLAUDE.md §5.1 + §11) is THE authoritative source for the 6 standing intake answers (scope / site exposure / labour rate / margin+contingency / adverse weather / LAFHA) and any bid-specific decisions the orchestrator already auto-decided. Read the assumptions log at `_Internal/assumptions-log-{job-id}.md` before starting any pricing work — every row marked `auto-decided (confidence ≥60%)` or `Rocky-confirmed` is a lock; do NOT re-ask Rocky and do NOT verbal-override mid-session unless Rocky explicitly directs. Rows marked `flagged-for-Rocky` are decision-pending — surface and stop until resolved. When running unpaired (no orchestrator), fall back to standing-context defaults + Phase 0 grill verbal capture.
2. **File source artefacts** per `Operations/lfcs-standards/folder-spec.md`:
   - Drawings → `03 - Plans and Specifications/03a - Issued for Construction/`
   - RFQ scope email + zip → `06 - Correspondence/06a - Emails/`
   - BoQ → `02 - Scope and Pricing/02b - BOQ and Rate Schedules/` (raw HC-supplied xlsx)
   - Specs → `03b - Specifications/`
3. **Verify BOQ download integrity.** If `gws drive files get` returned 0 bytes (the 2026-05-04 2626 RFQ ZIP failure pattern), STOP. Manual web download fallback per `pricing-checklist.md` Common Misses log entry. Never proceed with known data gap.
4. **Run `lfcs-drawing-extract-text`.** Output: `Drawing-Index.md` + Drawing Elements rows in Airtable.
5. **Run `lfcs-boq-parse`.** Output: `boq-normalised.json` + quantities-at-a-glance markdown table appended to `00-brief.md`.
6. **Run `lfcs-bid-prep-doc`.** Reads grill answers, BOQ, drawings; produces `00-Bid-Prep.md` with sections 6–10 (ADDED / cross-refs / Rate Only / OPTION / cranage). v3 chain enforced.
7. **Run `lfcs-pre-grill` (Phase 0 grill).** MANDATORY before pricing. Loop until every Q-category answered (Q-A0 cost basis, Q-A1/A2/A3 supply, Q-B1 access, Q-C QA, Q-D1/D2 programme, Q-E productivity, Q-F1 commercial posture, Q-F4 format, Q-G sanity). Write Pre-Pricing-Grill record signed off by Rocky to `02b - BOQ and Rate Schedules/Pre-Pricing-Grill-{date}.md`. Skill aborts pricing if any answer is TBC.
8. **First-pass pricing.** `lfcs-pricing-estimator` (M5 — NOT YET BUILT). Until M5: manual rates with `lfcs-pricing-history-lookup` informing Rocky. Apply margin + contingency per the locked formula in standing-context (override per intake-gate Q-4):
   - Labour: `direct × 1.10 contingency × 1.15 margin = direct × 1.265`
   - Materials (LFCS-purchased): `direct × 1.15 margin × 1.10 contingency = direct × 1.265`
   - Per-category sequence preserved for traceability; do not collapse to a single 1.265 multiplier
   - Customer-facing BoQ: single rate per line, loadings hidden
   - Internal view: show direct AND loaded side-by-side
9. **Generate Bid + Bid Line Items rows in Airtable** via `lfcs-airtable-sync`. Status = Draft. Pricing Basis from grill Q-A0. `_internal_first_pass_total_ex_gst` populated.
10. **Run `lfcs-incl-excl-draft@v3`.** Canonical exclusions structure = 2629 Erskine Park three-tier (Liam-authored submitted PDF, ~43 items): tier 1 STANDARD (1–10 always-out), tier 2 PRELIMINARIES (11–28 HC supply boundary), tier 3 DETAILED-Excl-Assump-Incl (29+ job-specific). Match this shape on all submissions unless Rocky overrides per-bid. Exclusion rows pulled from `_methodology_Exclusion_Library` matching `applicable_job_categories`. See `Codebase/lfcs-orchestrator/standards/Standard-LFCS-Exclusions-Library.md`.
11. **Sanity-check session** — handoff to §4.2.
12. **External submission generation** — handoff to §4.3 after sanity-check complete.

---

## 4.2 Sanity-check capture

**Trigger:** post-first-pass review session with Rocky + Liam (or +Hasibul).

1. **Open session** via `OverrideCaptureSession` context manager (`lib/override_capture.py`). Inputs: `bid_record_id`, `agent_id`, `participants`, `capture_mode = "typed"` (voice spike pending). Session opens `_Internal_Session_Logs` row with UUID `session_ref`.
2. **Display the bid line items table side-by-side.** Per-line visible: original_rate_ex_gst, qty, unit, original_qty, complexity_factors, source_methodology_template. Confidential fields (`_internal_first_pass_confidence`) visible to Rocky; never exported.
3. **For each verbal override, call `session.capture_override(...)`.** Required inputs:
   - `bid_line_item_id` (the line being overridden)
   - `original_rate_ex_gst`, `override_rate_ex_gst`
   - `reason_codes` — multi-tag from 9 domains (`SCOPE-SHIFT` / `RATE-BASIS` / `PRELIM-VISIBILITY` / `STRUCTURAL-ADD` / `STRUCTURAL-DEDUCT` / `POSTURE-MISMATCH` / `BOILERPLATE-CARRY` / `PRODUCTIVITY-CALIBRATION` / `LOSS-CATEGORY`)
   - `reviewer_type` — singleSelect (`Trades-Internal-Rocky` / `Trades-Internal-Liam` / `Trades-Internal-Hasibul` / `Commercial-External-Robert` / `Commercial-External-CivilExperts` / `Commercial-External-Other` / `Post-Submission-Outcome`)
   - `reviewer_name` — operator name (free text)
   - `free_text_rationale` — 1–3 sentences (REQUIRED; aborts if blank)
   - Optional: `verbal_capture` checkbox + `verbal_transcript_excerpt` (voice path; deferred)
4. **Each capture writes:**
   - `Bid Line Item Overrides` row in Airtable (`tblPp9hjyWvWdRYIK`)
   - `_Internal_Audit_Trail` row (`tblSjvdXg8imY3rUy`, `event_type=override_captured`)
   - `_Internal/audit-trail/session-{uuid}.md` cross-job source of truth (vault md mirror)
   - Per-job `_audit/overrides-log-{date}.md` filtered view
5. **Offline-queue fallback.** If Airtable unreachable: captures land in `_Internal/audit-trail/pending-overrides-{session_ref}.json`. Skill marks `offline=True`. Downstream sanitised-submission generation REFUSES to proceed until pending queue is flushed.
6. **On session exit:** stamp `ended_at` on `_Internal_Session_Logs` row + write `transcript_summary` (`{N} overrides captured. Net delta_dollars = ${X}`).
7. **Diversity criteria** for downstream feedback aggregator:
   - ≥3 overrides on same Rate Template
   - From at least 2 distinct Jobs
   - Spanning at least 30 days
   - Reason codes spanning at least 2 of the 9 domains

   These criteria fire `lfcs-feedback-aggregator` daily 04:00 AEST (cron registration deferred). Proposals land with `confidence = Needs review`. Rocky must accept/reject/modify before any Rate Template `base_rate_aud` is overwritten. NEVER auto-applied.

---

## 4.3 External submission generation

**Trigger:** sanity-check complete + `final_submitted_total_ex_gst` set + Rocky says "send."

1. **Format selection from grill Q-F4 answer:**
   - `lump-sum-letter` → call `lfcs-loo-builder` (markdown LoO; 7-section structure per `letter-of-offer-structure.md`)
   - `boq-response` → call `lfcs-boq-response-builder` (xlsx; 7-column live-formula layout)
   - `both` → call both builders sequentially
2. **Layer 1 sanitisation (`lfcs-sanitise-for-external@v1`).** Input: source artefact + `operator_of_record` (e.g. `"Liam Fitzgerald"`) + `company` (`"LF Construction Services Pty Ltd"`).
   - Strip every field starting `_internal_` (case-insensitive)
   - Strip every whole table starting `_Internal_`
   - Regex-strip 12 agent-derivation patterns (claude-* / cowork-* / hermes-* / openclaw / agent rate / agent confidence / `[source: friend-vault]` / `[source: ...]` / `_internal_*` mention / `PLACEHOLDER-*` / `(PENDING DERIVE)` / frontmatter `created_by:`)
   - Set xlsx properties: Author = operator, LastModifiedBy = operator, Comments = ""
   - Return `SanitisationResult` with `fields_stripped`, `regex_strips_applied`, `metadata_overrides`, `layer1_pass`
3. **Layer 2 verification (`lfcs-sanitisation-verify@v1`).** INDEPENDENT code path. Inputs: sanitised artefact bytes + expected_author + expected_company.
   - Re-derive 14 forbidden patterns from spec (NOT copied from Layer 1)
   - Scan output bytes for any forbidden pattern
   - Verify file metadata matches expected operator-of-record
   - Return `VerificationResult` with `passed`, `findings`, `metadata_check`
4. **Fail-closed contract.** If `verification.passed is False`:
   - Delete the target artefact (`os.remove`)
   - Write `_Internal_Sanitisation_Log` row with `layer2_pass=False` + `layer2_failures=<finding list>` + `verified_by_human=NEVER`
   - Notify operator. STOP. No retry without root-cause investigation.
5. **Human verification gate.** Even on `verification.passed is True`: Liam (or operator-of-record) opens the Drive preview, visually confirms File → Properties → Author = correct human + cell comments contain no agent terms + body has no "claude" / "agent" mentions. Sets `_Internal_Sanitisation_Log.verified_by_human = true`. If not verified, sanitisation skill BLOCKS send.
6. **Drive upload + email send.** Use `scripts/lfcs/lfcs-drive-upload.sh` wrapper (correct `--json` body pattern for metadata; never `--params`). Auto-share with Rocky's `mcloughlinmichael.r@gmail.com` as Editor, no notification. Update `Bids.final_artefact_drive_id` + `final_artefact_email_thread_id` post-send.
7. **Post-submission Airtable.** Set `Bids.Status = Submitted`, `Submitted_Date`, `final_submitted_total_ex_gst`, `final_authored_by`. Trigger `lfcs-bid-vs-qs-compare` to write per-line pricing-history MDs to `Operations/lfcs-standards/pricing-history/`.

---

## 4.4 Outcome capture

**Trigger:** result lands from QS — won / lost / withdrawn / superseded — OR a variation / EOT / amendment fires post-award.

1. **Create `Commercial Events` row** in `tbl6LXA7evO4CzZTD`. Fields:
   - `event_type` — singleSelect: `Tender-Round-Won` / `Tender-Round-Lost` / `Tender-Round-Withdrawn` / `Tender-Round-Superseded` / `Variation` / `EOT` / `Subcontract-Signed` / `Amendment` / `Practical-Completion` / `Final-Account` / `Job-Pulled-Off-Site`
   - `event_date`, `event_value_ex_gst`
   - `linked_bid` (multipleRecordLinks → Bids)
   - `linked_variation` (if Variation event)
   - `tender_outcome` — Won / Lost / Withdrawn / Superseded / Pending / N/A
   - `price_vs_market_signal` — Won-near-budget / Won-under-budget / Won-over-budget / Lost-too-high / Lost-too-low / No-feedback / N/A
   - `price_vs_market_delta_pct` (when known from QS comparison)
   - `execution_outcome` (post-completion only) — Pending / On-Budget / Over-Budget / Under-Budget / Withdrawn-Mid / N/A-not-yet-executed
   - `delay_drivers` (multipleSelects, if execution event)
   - `lessons_learned_text` + `lessons_learned_reason_codes`
2. **For Lost outcomes:** create one or more `Bid Line Item Overrides` rows with `reviewer_type=Post-Submission-Outcome` + `reason_codes=[LOSS-CATEGORY, ...]` + `free_text_rationale` capturing why lost (price too high / price too low / relationship / timing / scope-mismatch). These feed methodology calibration via `lfcs-feedback-aggregator`.
3. **For Won outcomes:** trigger handoff to `lfcs-projects` agent (NOT YET BUILT — see CLAUDE.md §1 boundary). Until lfcs-projects exists, manual handoff: Rocky moves the job folder from `Upcoming/` to `Ongoing Jobs/` per `folder-spec.md`.
4. **For Variation / EOT events:** link to the parent Bid; if reason known, link the override(s) that flagged the underlying scope/productivity issue earlier (`Bid Line Item Overrides.linked_commercial_event` ↔ `Commercial Events.linked_overrides` bidirectional).
5. **Write daily note + `_Brain/_Log.md` one-liner** per AI Team Protocol.

---

## 4.6 Periodic methodology review

**Trigger:** Rocky asks for the override summary, OR `lfcs-feedback-aggregator` cron fires (daily 04:00 AEST, registration deferred), OR a new `_methodology_*` proposal lands.

1. **Surface accumulated overrides** since last review:
   - Count by `reason_codes` domain (top 3 most-frequent)
   - Count by `reviewer_type` (Rocky / Liam / Hasibul / Commercial-External-Robert)
   - Sum `delta_dollars` (signed) — net pricing direction
   - Median `delta_pct` per `linked_rate_template`
2. **Apply diversity criteria** per §4.2 step 7. Any Rate Template meeting all four criteria + ≥3 overrides → proposal generated.
3. **Apply Common Misses diversity criteria.** Different threshold: ≥2 overrides on the same combination of `reason_codes`, ≥2 distinct jobs, ≥30 days span, sum delta_dollars > $5,000 OR median delta_pct > 10%.
4. **Surface proposals** to Rocky via:
   - One-liner per proposal appended to `_Brain/Open-Threads.md`
   - Telegram ping via existing Hermes plumbing (per `_Brain/Cowork-Cheatsheet.md` pattern)
   - Each proposal block: current vs proposed `base_rate_aud`, evidence summary, qualifying override IDs
5. **Methodology rows tagged `confidence = Needs review`:** flag any active in current pricing. Block draft generation until Rocky-confirmed.
6. **Calibration view (Airtable view on Bid Line Item Overrides table):**
   - Group by `linked_bid.job.job_category`
   - Then by `reason_codes` (multi-tag aggregated)
   - Then by rolling-90-day window of `created_at`
   - Aggregations: count, median(delta_pct), mean(delta_pct), p90(delta_pct), sum(delta_dollars)
7. **Calibration view trustworthiness gate.** Calibration view is NOT used as a milestone-acceptance criterion until ≥20 jobs total have full override capture. Until gate crosses, use simple raw error band against Liam's submitted price.
8. **Output session writeback** per Cowork session-writeback skill convention. Append to `01_Daily/{today}.md` + `_Brain/_Log.md` one-liner.
