# SKILL — lfcs-drawing-extract-vision

## Purpose

**Vision-pass companion to `lfcs-drawing-extract-text`.** Reads engineering drawings page-by-page via the Read tool (Cowork or Claude Code OAuth on Max subscription — no separate Anthropic API key needed), extracts drawn-only content that text extraction misses (dimensions on the drawing as graphic labels, hatching patterns, section markers, detail bubbles, drawn-only callouts, hand-marked annotations, geometric clashes), augments the existing `Drawing-Index.md` with structured `vision_findings:` blocks per drawing, and upserts Drawing Elements rows in Airtable with Confidence=High where vision validates content.

**Built Phase 8 (cowork-lfcs 2026-05-04 PM late) once it became clear that OAuth Max subscription covers the vision capability — original LFCS Bid System Plan §6 had assumed Anthropic API direct + pdftoppm + Python script, but for typical LFCS pack sizes (1-7 PDFs per bid, every few days) the OAuth path is fine and zero marginal cost.**

Mandatory complement to `lfcs-drawing-extract-text` per Phase 0 of the LFCS Bid System Plan — vision-pass is required before pricing finalisation.

## Trigger

User says any of:
- "Vision-pass [job]" / "Vision-pass on 2602 drawings"
- "Upgrade Drawing-Index.md to High confidence"
- "Run vision extraction on [job]"
- "Confirm drawing dimensions for [job]"

Or invoked programmatically by `lfcs-bid-prep-doc` (v2+) when Drawing-Index.md has `vision_pass_status: pending` in frontmatter AND bid is < 48hr from due.

NOTE: vision-pass is explicitly NOT in `lfcs-project-init` orchestrator — it's a separate Rocky-trigger because it consumes more time + tokens than the text-pass intake. Run when ready (typically after intake + before pricing).

## Inputs

Required:
- `job_folder` — absolute path to the job folder (must contain `03 - Plans and Specifications/03a - Issued for Construction/` with PDF source files AND existing `Drawing-Index.md` from prior text-pass)

Optional:
- `discipline_filter` — `structural-only` / `civil-only` / `all` (default: `all`, but skip drawings tagged `lfcs_scope: reference-only` unless `--include-reference` flag set)
- `pdf_filter` — explicit filename list to vision-pass (default: all PDFs in 03a)
- `page_limit` — for very large PDFs (>20 pages), set max pages per skill invocation (default: 20; recommend running multi-pass on large packs)

## Behaviour

1. **Pre-flight**:
   - Confirm `Drawing-Index.md` exists in `{job_folder}/03 - Plans and Specifications/03a - Issued for Construction/`. Abort if missing — text-pass must run first (`lfcs-drawing-extract-text`).
   - Discover PDFs in 03a. Skip non-PDF files. Apply `discipline_filter` + `pdf_filter` if set.
   - For each PDF, extract page count from existing Drawing-Index.md OR read PDF metadata directly.
2. **Per-PDF vision pass** (loop):
   - For each page in the PDF (up to `page_limit`):
     - Read the PDF page via Read tool (Cowork or CC CLI native PDF support — both render pages as images for vision processing)
     - Apply structured extraction prompt:
       ```
       This is page {N} of {pdf_filename} ({drawing_no} per Drawing-Index.md, sheet_size {A1|A0|other}, discipline {structural|civil|services|other}, lfcs_scope {primary|reference-only|cover-only}).

       Extract the following in JSON, ONLY items visible on the drawing (not inferred):
       - geometry_dimensions: array of {dimension_value_mm, dimension_what, location_on_sheet}
       - materials_called_out: array of {material, what_for, location}
       - section_markers: array of {section_label, references_drawing}
       - detail_bubbles: array of {detail_label, what_in, references_drawing}
       - drawn_clashes_with_text_labels: array of {what, where, observation}
       - hand_marked_annotations: array of {annotation, what, where}
       - construction_notes_visible: array of "verbatim notes from sheet"
       - lfcs_scope_relevant_tags: array of tag strings (use convention from existing Drawing-Index.md: material:X, geometry:X, risk:X, discrepancy:X, existing:X, etc.)
       - drawing_quality_assessment: one of {clean, smudged, partial, illegible}
       - extraction_confidence: one of {High, Medium, Low, NeedsReview} per overall page
       ```
3. **Append to Drawing-Index.md** — for each drawing block in the existing file, add a new `vision_findings:` sub-block (do NOT overwrite the text-pass content; preserve source traceability):
   ```yaml
   vision_findings:
     pass_date: 2026-05-04
     pass_by: cowork-lfcs (lfcs-drawing-extract-vision v1)
     extraction_confidence: High
     per_page:
       - page: 1
         geometry_dimensions:
           - {dimension_value_mm: 34000, dimension_what: "overall bridge length", location_on_sheet: "elevation view top dimension line"}
           - ...
         materials_called_out: [...]
         construction_notes_visible: [...]
         lfcs_scope_relevant_tags: [...]
       - page: 2
         ...
     summary_per_drawing: "1-paragraph summary of vision-only findings for this drawing"
   ```
4. **Upsert Drawing Elements rows in Airtable**:
   - For each existing Drawing Element row linked to this Drawing (queried via Airtable filter `Drawing = {drawing_rec_id}`), check if vision validates the row:
     - If vision saw the same element + same dimensions/material → upsert Confidence Medium → **High**
     - If vision saw it but with different dimensions/material → upsert with Notes flag "DRAWING SHOWS X but text extract had Y — VERIFY", Confidence = Needs review
     - If vision did NOT see it → leave Confidence as is, append Notes "vision pass did not surface this element on page {N}"
   - For NEW elements vision found that text-pass missed → create NEW Drawing Element rows with Confidence=High + Sheet Reference + Notes "first surfaced via vision pass"
   - All writes routed through `lfcs-airtable-sync` (per Phase 2+ adapter protocol)
5. **Update `Drawing-Index.md` frontmatter** — set `vision_pass_status: complete` + `vision_pass_date: {date}`. If multi-pass needed (e.g. CI251342 large PDF), set `vision_pass_status: partial` + `vision_pass_pages_complete: {n}` and continue on next invocation.
6. **Append project log + daily note + skills/_Log.md** per AI Team Protocol.

## Files to read (in order)

```
HQ-Vault/_System/CLAUDE.md
HQ-Vault/11_OpsMan_LFCS/_Brain/CLAUDE.md
HQ-Vault/11_OpsMan_LFCS/Operations/skills/lfcs-drawing-extract-text/SKILL.md  ← sister skill; vision is the augmenting pass
HQ-Vault/11_OpsMan_LFCS/Operations/lfcs-standards/concepts/TfNSW-bridge-FRP-subcontract-pattern.md  ← if bridge job; calibration of dimensions/materials expected
HQ-Vault/11_OpsMan_LFCS/Operations/lfcs-standards/concepts/TfNSW-R0220-pit-pattern.md  ← if pit job
HQ-Vault/11_OpsMan_LFCS/Operations/Jobs/Upcoming/2602 - Dec Projects - Munmorah Bridge/03 - Plans and Specifications/03a - Issued for Construction/Drawing-Index.md  ← regression fixture / template (Phase 4 text-pass output; this skill upgrades to vision-validated High confidence)
{job_folder}/03 - Plans and Specifications/03a - Issued for Construction/Drawing-Index.md  ← input + output target
{job_folder}/03 - Plans and Specifications/03a - Issued for Construction/*.pdf  ← per-page vision read
```

## Airtable behaviour

- **Reads:** `Drawings` (filter by Job for this job's drawings), `Drawing Elements` (filter by Drawing for each drawing's existing rows).
- **Writes:** `Drawing Elements` upserts (Confidence Medium → High where vision validates, Notes flag where vision differs from text-pass, NEW rows for elements vision found that text-pass missed). `Drawings` upsert AI Summary field with vision-pass summary appended. All routed through `lfcs-airtable-sync`.
- **Idempotency:** Drawing Elements upserted by Element Name primary field. Re-running vision-pass on same PDFs = same Airtable state. Vision findings always APPEND to Drawing-Index.md `vision_findings:` block (Phase 1+) so source-traceability preserved across multiple passes.

## Output format

To user (terminal):

```
✓ Drawing-Index.md vision-pass complete for {job_no}
  PDFs vision-passed: {n}
  Pages vision-extracted: {n}
  Drawing Elements upgraded Medium → High: {n}
  NEW Drawing Elements found via vision: {n}
  Drawing Elements flagged for verify (vision differs from text-pass): {n}

  Drawing-Index.md path: {abs path}
  vision_pass_status frontmatter: complete (or partial — see field)

  Drawings rows updated: {n} | AI Summary appended with vision findings
  Confidence breakdown after pass:
    High: {n} (was {n_before})
    Medium: {n} (was {n_before})
    Low: 0
    Needs review: {n} (vision differs from text-pass — manually verify)

  Next skills:
    - lfcs-bid-prep-doc — refresh 00-Bid-Prep.md with high-confidence quantities
    - lfcs-boq-cross-check (v2) — cross-validate BOQ vs vision-pass elements (now reliable since vision pass = High confidence)
```

## Hard rules

- **Augment, never replace.** Vision findings ALWAYS append as new `vision_findings:` block in Drawing-Index.md per drawing. NEVER overwrite the text-pass `Shows:` / `Elements drawn:` / `Construction notes:` blocks. Source traceability is non-negotiable — text-pass + vision-pass blocks coexist; future readers can see what each pass contributed.
- **Verbatim construction notes.** Reproduce notes from drawings exactly as they read on the sheet — no paraphrase. Italicise + quote.
- **No invention.** If a dimension or material isn't visible on the drawing, leave it blank or note "not visible on this page". Never extrapolate.
- **Preserve `confidence` flags.** Never silently mark a Drawing Element row High if vision didn't actually validate the dimension. If unsure, mark `Needs review`.
- **Read-before-edit** every PDF + Drawing-Index.md + every regression fixture each invocation.
- **Vault markdown is source of truth.** If `lfcs-airtable-sync` write fails (network / API), the vision-pass run is still considered successful — log the Airtable failure to `_System/Conflicts.md` and continue. Never block on Airtable.
- **Idempotent upserts via primary field.** Drawing Element rows upserted by Element Name. Re-running vision-pass = same Airtable state.
- **Page limit per invocation** (default 20). For large PDFs (>20 pages), run multi-pass with `vision_pass_status: partial` tracking. Avoids timing out on 100+ page packs.
- **Skip lfcs_scope: reference-only drawings** unless explicitly invoked with `--include-reference` flag (saves time; reference-only drawings are not load-bearing for LFCS pricing).

## Dependencies

- Cowork OR Claude Code OAuth Max subscription (vision capability via Read tool — no Anthropic API key required for typical LFCS pack sizes)
- `lfcs-drawing-extract-text` v2+ must have run first (produces the Drawing-Index.md base structure that this skill augments)
- `lfcs-airtable-sync` v1+ (final-step adapter call)
- 2602 Munmorah Bridge `Drawing-Index.md` (regression fixture for Phase 8 build — text-pass output exists from Phase 4; PDFs landed Phase 7; vision-pass run pending)

## Regression test

Running `lfcs-drawing-extract-vision job_folder={2602 path}` should produce (after PDFs land in vault, which they did Phase 7):

1. Drawing-Index.md unchanged in text-pass blocks ✓ (augment-only rule)
2. Drawing-Index.md has new `vision_findings:` sub-block per drawing (S01.00, S01.10, S01.20, C01.00, C01.10, C01.20, C01.21) with per-page extracted dimensions + materials + notes verbatim ✓
3. Drawing-Index.md frontmatter `vision_pass_status: complete` + `vision_pass_date: {date}` ✓
4. Airtable Drawing Element rows for 2602 (currently 20, all Confidence=Medium per Phase 4 text-pass) → upserted, expected ~14-16 to Confidence=High where vision validates the text-extract dimensions/materials, 2-3 NEW rows from vision finds (e.g. drawn dimensions not in text labels), 1-2 flagged Needs review where vision contradicts text ✓
5. Airtable Drawings rows (7 for 2602) → AI Summary field appended with 1-paragraph vision-pass summary per drawing ✓
6. Re-running on same job = same Airtable state (upserts, no duplicates) ✓

## Failure modes

| Failure | Detection | Recovery |
|---|---|---|
| Drawing-Index.md doesn't exist | Pre-flight | Abort, instruct user to run `lfcs-drawing-extract-text` first |
| PDF unreadable / scanned-image-only | Read tool returns empty / fails on a page | Mark drawing's `vision_findings:` `extraction_confidence: NeedsReview` + Notes "page rendered as scan only — manual review required" |
| PDF page count > page_limit | Per-PDF count check | Mark `vision_pass_status: partial` + log progress; user re-invokes to continue from `vision_pass_pages_complete + 1` |
| Vision text differs significantly from text-pass | Per-element comparison after extraction | Mark Drawing Element row Confidence=`Needs review` + Notes "vision dimension {V} differs from text-extract {T} — verify on source PDF" |
| Element vision extracted is NOT in existing Drawing Elements rows | Per-element lookup fails | CREATE new Drawing Element row with Confidence=High + Sheet Reference + Notes "first surfaced via vision pass" |
| Airtable write failure | Per-batch try/except | Per Phase 2 hard rule: NON-BLOCKING. Skill considered successful, failure logged to `_System/Conflicts.md`, continue. |
| Re-run after Drawing-Index.md was hand-edited by Rocky | Drawing-Index.md `vision_pass_status: complete` already + new vision content available | Append NEW `vision_findings:` block dated today (don't overwrite prior pass); Notes "second vision pass — see {date} block above for prior findings" |

## What this skill DOESN'T do (intentional non-scope)

- **Doesn't run text extraction** — that's `lfcs-drawing-extract-text`. Vision is the augmenting pass.
- **Doesn't price** — pricing decisions remain Rocky-interactive (Pre-Pricing-Grill + pricing-checklist Phase 0).
- **Doesn't run on Specifications PDFs** — those go through `lfcs-spec-summarise`, not vision-pass. Specs are text-heavy not drawing-heavy.
- **Doesn't decide LFCS scope** — supply boundaries are confirmed via Pre-Pricing-Grill, not vision pass.
- **Doesn't dispatch to Claude Code CLI automatically** — caller invokes from whichever surface (Cowork or CC CLI). Both have the vision capability.
