From 0ccb554f50683bd50939b3949adc36ca9c301e92 Mon Sep 17 00:00:00 2001 From: Chase Setters Date: Wed, 13 May 2026 15:45:39 -0500 Subject: [PATCH] Add willowbrook-submittal-review skill Bundles the submittal pre-review skill (SKILL.md, weston_guide, project playbook, CSI division map, Python helpers) into the willow-pmtools plugin so it auto-distributes alongside the ProjectTodos MCP. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../How_to_Use_the_Submittal_Skill.docx | Bin 0 -> 41049 bytes .../willowbrook-submittal-review/README.md | 188 +++ .../willowbrook-submittal-review/SKILL.md | 231 ++++ .../build_pm_guide.py | 413 +++++++ .../references/csi_division_map.json | 116 ++ .../references/project_playbook.md | 208 ++++ .../references/weston_guide.md | 1101 +++++++++++++++++ .../schemas/input.schema.json | 45 + .../schemas/output.schema.json | 151 +++ .../scripts/build_checklist_docx.py | 334 +++++ .../scripts/detect_input.py | 88 ++ .../scripts/extract_spec_book.py | 199 +++ .../scripts/parse_submittal.py | 256 ++++ .../scripts/spec_lookup.py | 199 +++ .../test_fixtures/_157_1_content.txt | 539 ++++++++ .../test_fixtures/parsed_157_1.json | 24 + .../test_fixtures/review_157_1.docx | Bin 0 -> 38067 bytes .../test_fixtures/review_157_1.json | 74 ++ .../review_157_1_spec_aware.docx | Bin 0 -> 40138 bytes .../review_157_1_spec_aware.json | 211 ++++ 20 files changed, 4377 insertions(+) create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/How_to_Use_the_Submittal_Skill.docx create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/README.md create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/SKILL.md create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/build_pm_guide.py create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/references/csi_division_map.json create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/references/project_playbook.md create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/references/weston_guide.md create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/schemas/input.schema.json create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/schemas/output.schema.json create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/build_checklist_docx.py create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/detect_input.py create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/extract_spec_book.py create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/parse_submittal.py create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/spec_lookup.py create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/_157_1_content.txt create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/parsed_157_1.json create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/review_157_1.docx create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/review_157_1.json create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/review_157_1_spec_aware.docx create mode 100644 plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/review_157_1_spec_aware.json diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/How_to_Use_the_Submittal_Skill.docx b/plugins/willow-pmtools/skills/willowbrook-submittal-review/How_to_Use_the_Submittal_Skill.docx new file mode 100644 index 0000000000000000000000000000000000000000..a2ec680cd057862e24a1d3fe4cc4d062fa34703e GIT binary patch literal 41049 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 XHB~T?Gw9c>A_15or^jN}uV4QI3vbW{ literal 0 HcmV?d00001 diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/README.md b/plugins/willow-pmtools/skills/willowbrook-submittal-review/README.md new file mode 100644 index 0000000..d12a37b --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/README.md @@ -0,0 +1,188 @@ +# Willowbrook Submittal Review Skill — v1.1 + +A Claude Code skill that pre-reviews a construction submittal before it goes to the architect. + +Built for Willowbrook Construction Services from 500 real submittals on project 0309A (Stillwater High School). Platform-agnostic: works against any local PDF or submittal folder today, designed for the in-house Willow AI to call via Claude Agent SDK tomorrow. + +**v1.1 — spec-book aware.** If the PM supplies `project_spec_path`, the skill loads the matching CSI section from the project spec book and grounds every conformance check in real spec paragraphs (section, paragraph, page, excerpt). Without the spec, the skill falls back to checklist-only review. + +--- + +## What it does + +When a PM says *"review this submittal"* or runs `/review-submittal ` and hands over a PDF or folder, the skill: + +1. Detects whether the input is a single PDF or a Procore/Fieldwire-style folder +2. Extracts text and metadata from the cover sheet and attachments +3. Identifies the CSI division and loads the matching conformance checklist from Weston's submittal review guide +4. Applies the universal first-pass checks to every submittal +5. Flags historical patterns from 500 prior submittals on project 0309A +6. Produces: + - A short on-screen memo for the PM + - A Willowbrook-branded Word checklist (`review_.docx`) + - A structured JSON file (`review_.json`) aligned with Willow's planned database schema + +Every output is stamped **DRAFT**. The skill never auto-sends anything — the PM is the publish button. + +--- + +## Install + +### One-time setup + +``` +pip install pymupdf python-docx pypdf +``` + +### Install the skill (for a single PM) + +Copy or clone this folder into your Claude Code user skills directory: + +**Windows:** +``` +C:\Users\\.claude\skills\willowbrook-submittal-review\ +``` + +**macOS / Linux:** +``` +~/.claude/skills/willowbrook-submittal-review/ +``` + +Restart Claude Code. The skill will auto-trigger when you ask for a submittal review. + +### Install the skill (for the Willowbrook PM team) + +Option A — central SharePoint copy, each PM runs a sync script: +```powershell +robocopy "\\willowbrook\shared\Claudes Folder\skills\willowbrook-submittal-review" "$env:USERPROFILE\.claude\skills\willowbrook-submittal-review" /MIR +``` + +Option B — publish as an internal Claude plugin (requires a plugin manifest; defer to v1.1). + +The `willowbrook-brand-262` skill should be installed in the same session so the skill can apply brand standards to the generated .docx. + +--- + +## Usage + +### Natural language +``` +You: Review this submittal: "C:\path\to\157_1_Operable Partitions - SD" + Spec book: "C:\path\to\0309A\Spec Book" +``` + +### Slash command +``` +/review-submittal "C:\path\to\157_1_Operable Partitions - SD" --spec "C:\path\to\0309A\Spec Book" +``` + +### Spec book handling +- If the spec path points to a folder with already-extracted sections (the folder contains `_manifest.json`), the skill reads directly from it — instant. +- If the spec path points to a folder containing only a raw spec-book PDF, the skill extracts it into `/Extracted Sections/` on first use and caches for next time. +- If the spec path points directly to a PDF, same behavior — extracts and caches alongside. +- If no spec path is supplied, the skill runs checklist-only review and cannot recommend `Approved`. + +### Programmatic (for Willow, via Claude Agent SDK later) +Call the skill with a payload conforming to `schemas/input.schema.json`. Receive `schemas/output.schema.json`. + +--- + +## Output + +Every run produces three things: + +1. **On-screen memo** — ~15 lines the PM can scan in 60 seconds +2. **Word checklist** — `review/submittal__review.docx`, branded +3. **Structured JSON** — `review/submittal__review.json`, ingestable by Willow + +All three are stamped DRAFT. + +--- + +## What v1.1 does NOT do + +- Does not hit Procore, Fieldwire, SharePoint, or any API. Input is local files only. +- Does not auto-send anything. +- Does not review RFIs, change orders, bid leveling, sub qualification, or cost impacts. +- Does not modify the input submittal. +- Does not validate calculations (hydraulic calcs, photometrics, wind loads). Reads and flags mismatches; does not recompute. + +--- + +## Roadmap + +| Version | Status | Adds | +|---|---|---| +| v1.0 | ✅ shipped | Static conformance checklist + historical pattern flags + branded .docx + structured JSON | +| **v1.1** | ✅ current | Spec-book-aware: every pass/fail cites real spec paragraphs; `Approved` achievable with full citations | +| v1.2 | planned | SharePoint run log per Weston's request; offline spec cache verification | +| v2 | planned | Smart spec parsing (structured Part 2 Products extraction: manufacturer lists, performance tables, warranty durations) for exact-match validation vs. reading narrative text | +| v3 | planned | Callable from Willow via Claude Agent SDK; output lands in Willow's database | +| v4 | planned | Coordination-aware — flags conflicts against already-approved submittals in the same project | +| v5 | planned | Willow fetches submittals from Fieldwire via API, runs the skill, posts review back (through Willow, not direct from the skill) | + +--- + +## Folder structure + +``` +willowbrook-submittal-review/ +├── SKILL.md ← Claude-facing trigger, workflow, rules +├── README.md ← this file (human-facing) +├── references/ +│ ├── weston_guide.md ← per-CSI-division conformance checklists +│ ├── project_playbook.md ← historical patterns from 500 0309A submittals +│ └── csi_division_map.json ← CSI # → checklist + pattern triggers +├── schemas/ +│ ├── input.schema.json ← I/O contract for Willow +│ └── output.schema.json ← matches Willowbrook's planned submittal schema +├── scripts/ +│ ├── detect_input.py ← PDF vs. folder auto-detect +│ ├── parse_submittal.py ← PyMuPDF extract + metadata regex +│ ├── extract_spec_book.py ← spec-book PDF → one .txt per CSI section + manifest +│ ├── spec_lookup.py ← CSI section → spec text for grounding checks +│ └── build_checklist_docx.py ← renders review.json → baseline .docx +└── test_fixtures/ ← sample outputs from a real 0309A submittal + ├── parsed_157_1.json ← v1.0 — checklist-only review + ├── review_157_1.json + ├── review_157_1.docx + ├── review_157_1_spec_aware.json ← v1.1 — same submittal, spec book loaded, real citations + └── review_157_1_spec_aware.docx +``` + +--- + +## Testing + +A real 0309A submittal (157.1 — Operable Partitions) is included in `test_fixtures/` as a regression sample. Two outputs are provided: + +**`review_157_1.*`** — v1.0 behavior, no spec book. Demonstrates: +- Steel-fabricator coordination flag fires (playbook pattern) +- Status is `CONDITIONAL` (cannot be `PASS` without citations) +- Several items marked "Requires PM judgment" + +**`review_157_1_spec_aware.*`** — v1.1 behavior, spec book loaded. Demonstrates: +- Every `pass` and `warn` result cites a real spec paragraph (§1.2.B.1, §1.4.B.1, §1.4.C.1, etc.) +- Sample requirement failure caught against §1.4.C.1 and §1.4.C.2 (textile facing + panel edge samples) — which paint chips don't satisfy +- Steel-fab coordination warn now cites §1.2.B.1 directly +- Basis-of-Design check references Modernfold Acousti-Seal Legacy per §2.2.A.1 +- STC requirement references §2.2.G and §2.1.A.1 +- Pushback draft paragraph cites 4 specific spec sections for the sub to address + +Open the two `.docx` files side by side to see the difference in specificity. + +--- + +## Non-negotiables (from Weston) + +1. Every `pass` result in v2+ must cite the spec paragraph. No citation, no `Approved`. +2. Skill never auto-sends anything. Every output is DRAFT. +3. Word output must use the `willowbrook-brand-262` skill — no off-brand templates. + +--- + +## Support / Feedback + +Skill built by the Claude assistant in collaboration with Weston and the Willowbrook team. To report issues, request additions, or suggest pattern refinements, drop a note in the Claudes Folder on SharePoint or tag Weston directly. + +Version: v1.0 — April 2026 diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/SKILL.md b/plugins/willow-pmtools/skills/willowbrook-submittal-review/SKILL.md new file mode 100644 index 0000000..6df30da --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/SKILL.md @@ -0,0 +1,231 @@ +--- +name: willowbrook-submittal-review +description: Reviews a construction submittal for Willowbrook Construction Services before it is forwarded to the architect. Checks conformance against Weston's division-by-division guide, flags historical patterns from 500 prior 0309A submittals, and produces an on-screen memo plus a Word checklist. Use when a PM says "review this submittal", "check this submittal", "submittal review", or invokes the `/review-submittal` command. Also use when a PDF or folder of submittal files is handed over with no other explicit task. Do NOT use for RFIs, change orders, bid leveling, sub qualification, or cost analysis — those are separate concerns. +argument-hint: "[pdf or folder path] [--spec ]" +allowed-tools: Bash(python *), Read, Write, Glob, Grep +--- + +# Willowbrook Submittal Review Skill — v1 + +## What this skill is + +A pre-architect review pass on a construction submittal. The PM hands over a PDF or a submittal folder; the skill reports: + +1. Conformance against the division-appropriate checklist from Weston's guide +2. Historical pattern risks drawn from 500 prior submittals on project 0309A +3. A recommended PM action (directive, not advisory) +4. A draft pushback statement the PM can copy to the sub if needed +5. A structured JSON file suitable for ingestion by the in-house Willow AI + +Every output is stamped **DRAFT**. The skill never auto-sends anything. The PM is the publish button. + +--- + +## Hard rules — do not violate + +1. **Every "pass" result must cite the spec paragraph it passes against.** If the PM supplies `project_spec_path`, load the relevant CSI section text via `scripts/spec_lookup.py` and use it to ground each conformance check with a real citation (section, paragraph, page, short excerpt). Without a loaded spec section the skill cannot emit `Approved` — max is `Approved as Noted`. + +2. **Confidence threshold is 0.95.** Any conformance check the skill is less than 95% certain about is emitted with result `insufficient_data` and confidence < 0.95, and rendered in the Word checklist as "Requires PM judgment." Do not guess. + +3. **Tone is directive.** "Forward to architect." / "Request sample before forwarding." / "Return to sub — hardware schedule is missing PR#05 items." No softeners ("consider", "you may want to", "appears to"). + +4. **The skill never edits, sends, uploads, posts, or transmits anything.** Every output is a DRAFT for the PM. If the user asks the skill to send an email, post a comment, or forward the submittal, refuse and explain this is a DRAFT-only tool. + +5. **Word output should follow Willowbrook brand standards.** The bundled `scripts/build_checklist_docx.py` produces a Word doc that already uses Willowbrook colors and typography. If the user also has the `willowbrook-brand-262` skill installed in the session (check `~/.claude/skills/willowbrook-brand-262/SKILL.md` exists), surface a one-line note to the user that they can run that skill on the generated .docx to apply the full brand template — but do NOT attempt to invoke it as a sub-skill. Claude Code does not support skill-to-skill invocation; brand polishing is a follow-up the user runs explicitly. + +--- + +## Workflow + +When a PM invokes this skill (natural language like *"review this submittal"* or the `/review-submittal ` command), execute these steps **in order**: + +### Step 1 — Identify the input + +Run `scripts/detect_input.py ` to determine: +- `kind` = `pdf` or `folder` +- For folders: which file is the cover sheet vs. attachments + +If `kind` is `invalid`, stop and ask the PM for a valid path. + +### Step 2 — Extract text and metadata + +Run `scripts/parse_submittal.py ` to extract: +- Full text of the cover sheet +- Full text of each attachment (if folder input) +- Best-effort structured metadata (submittal_num, revision, spec_section, type, contractor, package, dates) + +If structured metadata is missing critical fields (spec_section or type), read the cover-sheet text and try to fill them. If still missing, emit `overall_status: "INSUFFICIENT_DATA"` and stop. + +### Step 3 — Identify the CSI division and load the checklist + +Look up the extracted `spec_section` in `references/csi_division_map.json`: +- Try exact match on the 8-character CSI number (e.g., `"08 71 00"`) +- If no exact match, try the 6-character prefix (`"08 71"`) +- If still no match, try the 2-character division (`"08"`) +- If still nothing, emit a `pattern_flag` noting the CSI section is not mapped, and proceed with the universal first-pass checks only + +Load the matching checklist from `references/weston_guide.md`. Always also apply the **Universal First-Pass Checks** at the top of that file to every submittal. + +### Step 3.5 — Load the spec section text (if project_spec_path was supplied) + +If the PM supplied `project_spec_path`: + +1. Run `scripts/spec_lookup.py ""` to resolve the CSI section +2. If the folder only has a raw spec PDF (no `_manifest.json` yet), `spec_lookup.py` will auto-run `extract_spec_book.py` and cache into `/Extracted Sections/`. First run is slow (minutes); subsequent runs are instant. +3. Store the returned `text`, `file`, and `page_breaks` for use in Step 4. +4. If the section is not in the spec book, emit a `pattern_flag` noting the gap and proceed without spec text (downgrade max response to `Approved as Noted`). + +When the spec text IS loaded, every conformance check in Step 4 must try to ground its result in a specific paragraph reference. Read the spec text, find the relevant PART 1 / PART 2 / PART 3 paragraph (e.g., "1.4 ACTION SUBMITTALS", "2.1 MANUFACTURERS", "3.3 INSTALLATION"), and capture: +- `spec_section` (e.g., "10 22 39") +- `spec_paragraph` (e.g., "1.4.B.3" or "2.1.A") +- `page` (from the "10 2239-N" marker in the text or from the page-break index) +- `excerpt` (short verbatim quote — 1-2 sentences max) + +### Step 4 — Run conformance checks + +For each checklist item: +- Read the submittal text (cover sheet + attachments) and decide `pass` | `fail` | `warn` | `not_applicable` | `insufficient_data` +- Assign a confidence score (0.0–1.0) +- If spec section text is loaded (Step 3.5 succeeded), include a `citation` object for every `pass` and `fail` result. The citation must point to a specific spec paragraph — not just the section. +- If the spec is loaded but this particular check can't be grounded in a spec paragraph (it's a procedural check, e.g., "sub has stamped the submittal"), omit the citation and note it as a procedural check rather than a spec-conformance check. +- If the confidence is below 0.95, force the result to `insufficient_data` + +### Step 5 — Apply historical pattern flags + +For each pattern in `references/project_playbook.md`: +- Check if the trigger conditions match this submittal +- If so, add an entry to `pattern_flags[]` with `severity: "warn"` or `"info"` + +Also consult `references/csi_division_map.json → pattern_triggers` for quick lookups: +- `needs_sample_separate` — trades where product data approval does not imply color/finish approval +- `coordination_required` — trades that must route to other subs +- `multi_round_typical` — trades that historically need R2+ +- `icc_record_submittal` — trades requiring an ICC record copy + +### Step 6 — Determine overall status + +- `PASS` — every check is `pass` with confidence ≥ 0.95 AND every pass has a citation (v2+ only; v1 rarely emits this) +- `CONDITIONAL` — all checks are `pass` or `warn` or `not_applicable`; at least one non-critical item is flagged +- `FAIL` — any check is `fail` +- `INSUFFICIENT_DATA` — too many checks are below the confidence threshold to draw a conclusion + +Map to `submittal_status_recommended`: +- `PASS` → `Approved` +- `CONDITIONAL` → `Approved as Noted` +- `FAIL` → `Revise and Resubmit` (or `Rejected` for severe / repeat failures) + +### Step 7 — Draft the PM action + +One directive sentence. Examples: +- "Forward to architect." +- "Request PR#05 hardware sets from sub before forwarding." +- "Return to sub — submittal is against superseded floor plan." +- "Forward to architect; flag that steel fab coordination copy has not been routed." + +### Step 8 — Draft pushback (only if FAIL or CONDITIONAL) + +Write a short paragraph the PM can paste into an email or Procore/Fieldwire comment. Address the sub directly, cite the specific missing/incorrect items, reference the spec section, and state what is needed for resubmittal. + +Example: +> "Resubmit with hardware sets for the PR#05 openings (Doors 205A, 205B, 211A). The current schedule is missing preps on these, per spec 08 71 00 §2.3. Once added, we'll forward to 505." + +### Step 9 — Suggest redlines (only if FAIL or CONDITIONAL) + +Where applicable, note page and item for markup on the returned PDF. Example: +```json +[ + { "page": 3, "item": "Hardware Set HW-12", "note": "Missing closer spec" }, + { "page": 7, "item": "Fire rating callout", "note": "Shows 20min; door schedule requires 45min" } +] +``` + +### Step 10 — Emit structured JSON + +Write a JSON object conforming to `schemas/output.schema.json` to: +`/submittal__review.json` + +Default `output_dir` is a `review/` subfolder next to the submittal input. Every run also prints a confirmation line to stdout. + +### Step 11 — Build the Word checklist + +1. Run `scripts/build_checklist_docx.py ` to produce the .docx +2. Save as `/submittal__review.docx` +3. If `~/.claude/skills/willowbrook-brand-262/SKILL.md` exists, append a one-line note to the on-screen memo: *"For full brand polish, run the willowbrook-brand-262 skill on the .docx as a follow-up."* Do not attempt to invoke that skill yourself — Claude Code does not support skill-to-skill invocation in v1. + +### Step 12 — Print the on-screen memo + +Print a short, scannable summary to the user. Format: + +``` +SUBMITTAL REVIEW — #157.1 Operable Partitions - SD +──────────────────────────────────────────────────── +Overall: CONDITIONAL +Spec: 10 22 39 - Folding Panel Partitions +Type: Shop Drawing +Contractor: The Best Company + +Conformance — 4 pass, 1 warn, 0 fail, 2 need PM judgment + ⚠ Steel beam coordination not documented (p. 2) + ◻ Requires PM judgment — Wind load rating (confidence 72%) + ◻ Requires PM judgment — Seismic restraint detail (confidence 84%) + +Pattern Flags + ⚠ Coordination — operable partitions historically must route to steel fab + +Suggested Action: Forward to architect; confirm steel fab coordination copy has been routed. + +Files written: + review/submittal_157_1_review.json + review/submittal_157_1_review.docx (branded) +``` + +Do not print the full conformance list on-screen — it's in the .docx. Keep the on-screen memo to ~15 lines. + +--- + +## Skill files + +| File | Purpose | +|---|---| +| `SKILL.md` | This file — trigger, workflow, rules | +| `references/weston_guide.md` | Per-division conformance checklists | +| `references/project_playbook.md` | Historical patterns from 500 submittals on 0309A | +| `references/csi_division_map.json` | CSI # → checklist lookup + quick pattern triggers | +| `schemas/input.schema.json` | I/O contract for Willow integration | +| `schemas/output.schema.json` | Structured review output contract | +| `scripts/detect_input.py` | Auto-detects PDF vs. folder input | +| `scripts/parse_submittal.py` | Extracts text + metadata using PyMuPDF | +| `scripts/extract_spec_book.py` | Splits a spec book PDF into one .txt per CSI section + manifest | +| `scripts/spec_lookup.py` | Resolves a CSI section → spec text for grounding conformance checks | +| `scripts/build_checklist_docx.py` | Renders review.json → baseline .docx | + +--- + +## Dependencies + +The skill shells out to Python. On first use: + +``` +pip install pymupdf python-docx pypdf +``` + +The `willowbrook-brand-262` skill should also be installed for branded .docx output. + +--- + +## What v1+ does not do (by design) + +- Does **not** hit Procore, Fieldwire, SharePoint, or any external API. Input is local files only. +- Does **not** auto-send anything. Every output is DRAFT. +- Does **not** review RFIs, change orders, bid packages, or cost impacts. Separate concerns. +- Does **not** modify the input submittal PDF. Output goes in a sibling `review/` folder. +- Does **not** validate performance calculations (hydraulic calcs, photometric values, wind loads). It reads them and flags mismatches against spec; it does not recompute. + +--- + +## Version + +v1.1 — April 2026 — spec-book-aware +- v1.0: static conformance checklist + historical pattern flags +- v1.1: folds in the spec-book extractor; conformance checks now cite real spec paragraphs when `project_spec_path` is supplied; `Approved` status is achievable with full citations +Built for Willowbrook Construction Services. Target caller for v3+: the Willow AI over the Claude Agent SDK. diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/build_pm_guide.py b/plugins/willow-pmtools/skills/willowbrook-submittal-review/build_pm_guide.py new file mode 100644 index 0000000..468c0dc --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/build_pm_guide.py @@ -0,0 +1,413 @@ +""" +build_corbin_guide.py +--------------------- +Generates a user-friendly Word doc guide for Corbin (PM) explaining how to +use the willowbrook-submittal-review skill. +""" + +from docx import Document +from docx.shared import Pt, RGBColor, Inches +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.oxml import OxmlElement +from docx.oxml.ns import qn +import os + +OUTPUT = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "How_to_Use_the_Submittal_Skill.docx", +) + +NAVY = RGBColor(0x1F, 0x3A, 0x5F) +GOLD = RGBColor(0xC9, 0xA2, 0x27) +GRAY = RGBColor(0x55, 0x55, 0x55) +GREEN = RGBColor(0x1E, 0x7A, 0x1E) +RED = RGBColor(0xB4, 0x1E, 0x1E) +LIGHT_BG = "F5F3EB" + + +def H(doc, text, level=1, color=NAVY): + p = doc.add_paragraph() + r = p.add_run(text) + r.bold = True + r.font.color.rgb = color + if level == 0: + r.font.size = Pt(26) + elif level == 1: + r.font.size = Pt(17) + p.paragraph_format.space_before = Pt(16) + p.paragraph_format.space_after = Pt(6) + elif level == 2: + r.font.size = Pt(13) + p.paragraph_format.space_before = Pt(10) + p.paragraph_format.space_after = Pt(3) + return p + + +def P(doc, text, bold=False, italic=False, color=None, size=11): + p = doc.add_paragraph() + r = p.add_run(text) + r.bold = bold + r.italic = italic + r.font.size = Pt(size) + if color: + r.font.color.rgb = color + return p + + +def bullet(doc, text, indent=0): + p = doc.add_paragraph(style="List Bullet") + p.paragraph_format.left_indent = Inches(0.25 + 0.25 * indent) + if p.runs: + p.runs[0].text = text + p.runs[0].font.size = Pt(11) + else: + p.add_run(text).font.size = Pt(11) + return p + + +def step(doc, n, text): + p = doc.add_paragraph() + p.paragraph_format.space_after = Pt(4) + r = p.add_run(f"{n}. ") + r.bold = True + r.font.color.rgb = GOLD + r.font.size = Pt(12) + r2 = p.add_run(text) + r2.font.size = Pt(11) + return p + + +def code_block(doc, text): + """Indented grey mono block that looks like a command.""" + p = doc.add_paragraph() + p.paragraph_format.left_indent = Inches(0.35) + p.paragraph_format.space_before = Pt(3) + p.paragraph_format.space_after = Pt(8) + + # Shading via XML + pPr = p._p.get_or_add_pPr() + shd = OxmlElement("w:shd") + shd.set(qn("w:val"), "clear") + shd.set(qn("w:color"), "auto") + shd.set(qn("w:fill"), LIGHT_BG) + pPr.append(shd) + + r = p.add_run(text) + r.font.name = "Consolas" + r.font.size = Pt(10) + return p + + +def callout(doc, label, text, color=GOLD): + p = doc.add_paragraph() + p.paragraph_format.left_indent = Inches(0.2) + p.paragraph_format.space_after = Pt(6) + r = p.add_run(f"{label} ") + r.bold = True + r.font.color.rgb = color + r.font.size = Pt(11) + r2 = p.add_run(text) + r2.font.size = Pt(11) + return p + + +def hr(doc): + p = doc.add_paragraph() + pPr = p._p.get_or_add_pPr() + pBdr = OxmlElement("w:pBdr") + bottom = OxmlElement("w:bottom") + bottom.set(qn("w:val"), "single") + bottom.set(qn("w:sz"), "6") + bottom.set(qn("w:color"), "C9A227") + pBdr.append(bottom) + pPr.append(pBdr) + + +# ───────────────────────────────────────────────────────────────────────────── +doc = Document() +style = doc.styles["Normal"] +style.font.name = "Calibri" +style.font.size = Pt(11) + +# ── Cover ─────────────────────────────────────────────────────────────────── +p = doc.add_paragraph() +r = p.add_run("How to Use the") +r.bold = True; r.font.size = Pt(14); r.font.color.rgb = GRAY + +p = doc.add_paragraph() +r = p.add_run("Willowbrook Submittal Review Skill") +r.bold = True; r.font.size = Pt(28); r.font.color.rgb = NAVY + +p = doc.add_paragraph() +r = p.add_run("A plain-English guide for Willowbrook project managers") +r.italic = True; r.font.size = Pt(13); r.font.color.rgb = GRAY + +hr(doc) + +# ── What it is ─────────────────────────────────────────────────────────────── +H(doc, "What this tool actually does", level=1) + +P(doc, + "You drag a submittal at it — either a single PDF or the whole Procore " + "export folder — and it does three things before the architect ever sees " + "it:") + +bullet(doc, "Checks the submittal against a detailed per-trade checklist " + "(Weston's guide) so nothing obvious is missing.") +bullet(doc, "Compares it to the actual project spec book and cites specific " + "paragraphs the submittal passes or fails against.") +bullet(doc, "Flags known patterns from 500 real submittals on 0309A — things " + "like \"operable partitions historically need to be routed to the " + "steel fabricator.\"") + +P(doc, " ") +P(doc, "You get two outputs every time:", bold=True) +bullet(doc, "A short summary printed on your screen (scan in 60 seconds).") +bullet(doc, "A full Word checklist saved next to the submittal — branded, " + "marked DRAFT, ready to hand to the architect or send back to the sub.") + +P(doc, " ") +callout(doc, "Bottom line:", + "Before every submittal leaves your desk, you run it through this. " + "It takes 30 seconds and catches the stuff you'd otherwise miss at 4:55 " + "on a Friday.", color=GOLD) + +hr(doc) + +# ── First-time setup ───────────────────────────────────────────────────────── +H(doc, "First-time setup (do this once, takes 10 minutes)", level=1) + +P(doc, "You'll do four small things. Ask Weston or IT for help on any of them " + "— none of this is stuff you need to know how to do.") + +H(doc, "1. Install Claude Code", level=2) +P(doc, "If you don't already have it, download from claude.ai/download and " + "install. Sign in with your Willowbrook account.") + +H(doc, "2. Install Python (if it's not already there)", level=2) +P(doc, "Open the Start menu, type \"cmd\", open the Command Prompt. Type:") +code_block(doc, "python --version") +P(doc, "If you see a version number (like \"Python 3.12.x\"), you're good. " + "If you get \"'python' is not recognized\", install from " + "python.org/downloads — pick the box that says \"Add Python to PATH\" " + "during install.") + +H(doc, "3. Install the three small Python helpers the skill uses", level=2) +P(doc, "In Command Prompt, paste this and hit Enter:") +code_block(doc, "pip install pymupdf python-docx pypdf") +P(doc, "You'll see a bunch of green text scroll by. When the prompt comes back " + "(a blinking cursor on a new line), you're done. It doesn't matter what " + "folder the command prompt is in — pip installs to Python, not to a folder.") + +H(doc, "4. Drop the skill into your Claude folder", level=2) +P(doc, "The skill lives in a folder called " + "willowbrook-submittal-review. Copy that whole folder into:") +code_block(doc, "C:\\Users\\\\.claude\\skills\\") +P(doc, "That .claude folder already exists if you've opened Claude Code before. " + "If not, it'll create itself the first time you run Claude. " + "Restart Claude Code after dropping the folder in.", + italic=True, color=GRAY, size=10) + +callout(doc, "Weston will share the skill folder on SharePoint:", + "\\\\willowbrook\\shared\\Claudes Folder\\skills\\willowbrook-submittal-review\\ " + "Just copy-paste that whole folder into your .claude/skills/ folder.", + color=GOLD) + +hr(doc) + +# ── How to use it ──────────────────────────────────────────────────────────── +H(doc, "How to use it — the 30-second version", level=1) + +P(doc, "Open Claude Code. Type one of these two things:") + +H(doc, "Option A — Natural language (easiest)", level=2) +code_block(doc, + "Review this submittal: \"C:\\Users\\\\Downloads\\157_1_Operable Partitions\"\n" + "Spec book: \"C:\\Users\\\\SharePoint\\0309A\\Spec Book\"") +P(doc, "You can drag the submittal folder from File Explorer straight into " + "the Claude window — it'll paste the path for you. Same with the spec " + "book folder.", italic=True, color=GRAY, size=10) + +H(doc, "Option B — Slash command (faster once you get used to it)", level=2) +code_block(doc, + '/review-submittal "C:\\...\\157_1_Operable Partitions" ' + '--spec "C:\\...\\0309A\\Spec Book"') + +P(doc, " ") +P(doc, "That's it. Claude takes 30–60 seconds and gives you back the review.", + bold=True) + +hr(doc) + +# ── What to hand it ────────────────────────────────────────────────────────── +H(doc, "What files to point it at", level=1) + +H(doc, "The submittal", level=2) +bullet(doc, "Option 1 — a single PDF of the submittal (works fine).") +bullet(doc, "Option 2 — the whole Procore / Fieldwire folder for that " + "submittal (better — it has the cover sheet + all attachments).") +P(doc, "The skill figures out which kind you gave it automatically.", + italic=True, color=GRAY, size=10) + +H(doc, "The spec book (optional but highly recommended)", level=2) +bullet(doc, "Point it at the project's Spec Book folder on SharePoint — " + "something like C:\\Users\\\\SharePoint\\0309A - " + "Stillwater HS\\Specifications\\") +bullet(doc, "Or point it at the raw spec-book PDF directly — it'll split it " + "up by section the first time (takes a minute or two), then cache " + "it so every run after that is instant.") + +P(doc, " ") +callout(doc, "Without the spec book loaded,", + "the skill can only run the generic checklist. With the spec book " + "loaded, it cites the exact paragraph that a missing item violates " + "— which is what gets it through architect review cleanly. " + "Spend the extra 2 seconds pointing at the spec folder.", + color=GOLD) + +hr(doc) + +# ── Reading the output ─────────────────────────────────────────────────────── +H(doc, "Understanding what you get back", level=1) + +H(doc, "The on-screen summary", level=2) +P(doc, "About 15 lines. It tells you:") +bullet(doc, "Overall status — PASS, CONDITIONAL, FAIL, or INSUFFICIENT_DATA.") +bullet(doc, "What the skill recommends the architect response should be.") +bullet(doc, "The specific items that failed or need your judgment.") +bullet(doc, "Any historical pattern flags (things that burned us before).") +bullet(doc, "One directive sentence telling you what to do next.") + +H(doc, "The Word checklist", level=2) +P(doc, "Saved in a review/ folder right next to the submittal. Opens in Word, " + "looks like a branded Willowbrook doc, stamped DRAFT at the top.") +P(doc, "It contains:") +bullet(doc, "The full per-trade conformance checklist with pass/fail/warn marks.") +bullet(doc, "Spec paragraph citations for every finding (when the spec is loaded).") +bullet(doc, "Historical pattern flags with the language to copy into your notes.") +bullet(doc, "A draft pushback paragraph if you need to send it back to the sub " + "— just copy, edit, paste into email / Fieldwire / Procore.") +bullet(doc, "Suggested redline callouts for marking up the returned PDF.") + +H(doc, "The JSON file", level=2) +P(doc, "Same folder as the Word doc. Not for you — it's for Willow to ingest " + "later. Ignore it, but don't delete it if you're doing a run log for IT.", + italic=True, color=GRAY) + +hr(doc) + +# ── Statuses ───────────────────────────────────────────────────────────────── +H(doc, "What the statuses mean", level=1) + +H(doc, "PASS (green)", level=2, color=GREEN) +P(doc, "Every required item was found AND cited against the spec. " + "Recommended response to architect: Approved. Forward it.") + +H(doc, "CONDITIONAL (gold)", level=2, color=GOLD) +P(doc, "Most items check out, but a few are flagged as warnings or need your " + "judgment. Recommended response: Approved as Noted. Read the warnings, " + "confirm they're acceptable, then forward.") + +H(doc, "FAIL (red)", level=2, color=RED) +P(doc, "Something substantive is missing or wrong. Recommended response: " + "Revise and Resubmit. Use the draft pushback language to send it back " + "to the sub before the architect sees it.") + +H(doc, "INSUFFICIENT_DATA (gray)", level=2, color=GRAY) +P(doc, "The skill couldn't evaluate enough of the checklist with high " + "confidence — usually because the submittal came in as a scanned image " + "instead of a text PDF. Review it manually. Tell Weston which submittal " + "did this so we can improve the parser.") + +hr(doc) + +# ── Three things the skill won't do ────────────────────────────────────────── +H(doc, "Three things the skill will NOT do (by design)", level=1) + +bullet(doc, "Send anything, anywhere. Every output is DRAFT. You are the " + "publish button — it's always you that forwards to the architect, " + "always you that sends pushback to the sub.") +bullet(doc, "Make the final decision on Approved vs. Approved as Noted. It " + "recommends; you decide.") +bullet(doc, "Review RFIs, change orders, bid leveling, sub qualification, or " + "cost analysis. Different tools for different jobs.") + +hr(doc) + +# ── Troubleshooting ────────────────────────────────────────────────────────── +H(doc, "If something doesn't work", level=1) + +H(doc, "Claude doesn't respond to \"review this submittal\"", level=2) +bullet(doc, "Did you restart Claude Code after dropping the skill folder in?") +bullet(doc, "Is the folder in ~/.claude/skills/ and named exactly " + "willowbrook-submittal-review (no extra characters)?") + +H(doc, "\"Missing dependency: pymupdf\" (or python-docx or pypdf)", level=2) +bullet(doc, "Run pip install pymupdf python-docx pypdf in Command Prompt again. " + "Watch for errors in the output — if pip can't install, your Python " + "install is probably the issue, not the skill.") + +H(doc, "The skill says INSUFFICIENT_DATA", level=2) +bullet(doc, "The submittal is probably a scanned image PDF. You'll need to " + "review it manually, or re-request a text-based PDF from the sub.") +bullet(doc, "Tell Weston — if it happens a lot, the next version can add OCR.") + +H(doc, "The spec book extraction is taking forever", level=2) +bullet(doc, "First time only, for a big spec book it can take 5+ minutes. " + "Every run after that reads from the cache and is instant.") +bullet(doc, "If it's been more than 15 minutes, kill it and let Weston know.") + +H(doc, "The Word doc doesn't match the Willowbrook brand", level=2) +bullet(doc, "The skill calls a separate brand skill to apply formatting. If " + "that's not installed, you'll get a plainer version — still usable, " + "just not branded. Ask Weston to share the willowbrook-brand-262 " + "skill too.") + +hr(doc) + +# ── Quick reference ────────────────────────────────────────────────────────── +H(doc, "Quick reference card", level=1) + +P(doc, "Tape this to your monitor:", italic=True, color=GRAY, size=10) + +code_block(doc, + "One-time setup:\n" + " pip install pymupdf python-docx pypdf\n" + " Drop willowbrook-submittal-review/ into ~/.claude/skills/\n" + " Restart Claude Code\n" + "\n" + "Every submittal:\n" + " \"Review this submittal: Spec book: \"\n" + " (Or drag the folders in — Claude reads them either way)\n" + "\n" + "Reading the result:\n" + " PASS -> forward to architect\n" + " CONDITIONAL -> address warnings, then forward\n" + " FAIL -> copy pushback draft, send to sub first\n" + " INSUFFICIENT -> review manually, tell Weston\n" + "\n" + "All outputs are DRAFT. You are the publish button.") + +P(doc, " ") + +# ── Footer ─────────────────────────────────────────────────────────────────── +doc.add_paragraph() +f = doc.add_paragraph() +fr = f.add_run( + "Willowbrook Construction Services | Submittal Review Skill v1.1 | " + "PM Quick-Start Guide" +) +fr.italic = True +fr.font.size = Pt(9) +fr.font.color.rgb = GRAY + +f2 = doc.add_paragraph() +fr2 = f2.add_run( + "Questions, issues, or suggestions: talk to Weston. Feedback makes v1.2 better." +) +fr2.italic = True +fr2.font.size = Pt(9) +fr2.font.color.rgb = GRAY + +doc.save(OUTPUT) +print(f"Saved: {OUTPUT}") diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/references/csi_division_map.json b/plugins/willow-pmtools/skills/willowbrook-submittal-review/references/csi_division_map.json new file mode 100644 index 0000000..c59bf9c --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/references/csi_division_map.json @@ -0,0 +1,116 @@ +{ + "description": "Maps CSI section numbers to the checklist section ID in weston_guide.md. When a submittal's spec_section is identified, look up the matching key (try exact match first, then fall back to prefix match on the 4-digit section, then the 2-digit division).", + "sections": { + "02 41 00": { "title": "Demolition", "checklist": "02 41 00 - Demolition" }, + "02 82 00": { "title": "Asbestos Abatement", "checklist": "02 82 00 - Asbestos Abatement" }, + "02 83 00": { "title": "Lead Abatement", "checklist": "02 82 00 - Asbestos Abatement" }, + "03 30 00": { "title": "Cast-in-Place Concrete", "checklist": "03 30 00 - Cast-in-Place Concrete" }, + "03 35 00": { "title": "Concrete Finishing / Polished Concrete", "checklist": "03 35 00 - Concrete Finishing / Polished Concrete" }, + "03 45 00": { "title": "Precast Architectural Concrete", "checklist": "03 45 00 - Precast Architectural Concrete" }, + "04 20 00": { "title": "Unit Masonry", "checklist": "04 20 00 - Unit Masonry (CMU / Brick)" }, + "04 22 00": { "title": "Concrete Unit Masonry", "checklist": "04 20 00 - Unit Masonry (CMU / Brick)" }, + "04 26 13": { "title": "Masonry Veneer", "checklist": "04 20 00 - Unit Masonry (CMU / Brick)" }, + "04 72 00": { "title": "Cast Stone", "checklist": "04 72 00 - Cast Stone / Architectural Stone" }, + "05 12 00": { "title": "Structural Steel Framing", "checklist": "05 12 00 - Structural Steel Framing" }, + "05 31 00": { "title": "Steel Deck", "checklist": "05 31 00 - Steel Deck" }, + "05 40 00": { "title": "Cold-Formed Metal Framing", "checklist": "05 40 00 - Cold-Formed Metal Framing" }, + "05 50 00": { "title": "Metal Fabrications", "checklist": "05 50 00 - Miscellaneous Metal Fabrications" }, + "06 10 00": { "title": "Rough Carpentry", "checklist": "06 10 00 - Rough Carpentry" }, + "06 16 00": { "title": "Sheathing", "checklist": "06 10 00 - Rough Carpentry" }, + "06 20 00": { "title": "Finish Carpentry", "checklist": "06 20 00 - Finish Carpentry / Trim" }, + "06 40 00": { "title": "Architectural Woodwork", "checklist": "06 40 00 - Architectural Woodwork / Casework" }, + "06 64 00": { "title": "FRP", "checklist": "06 40 00 - Architectural Woodwork / Casework" }, + "07 13 00": { "title": "Sheet Waterproofing", "checklist": "07 13 00 / 07 14 00 - Sheet / Fluid-Applied Waterproofing" }, + "07 14 00": { "title": "Fluid-Applied Waterproofing", "checklist": "07 13 00 / 07 14 00 - Sheet / Fluid-Applied Waterproofing" }, + "07 21 00": { "title": "Thermal Insulation", "checklist": "07 21 00 - Thermal Insulation" }, + "07 27 00": { "title": "Air Barriers", "checklist": "07 27 00 - Air Barriers" }, + "07 40 00": { "title": "Roofing", "checklist": "07 40 00 - Roofing (TPO, EPDM, Modified Bitumen, Metal)" }, + "07 54 23": { "title": "TPO Roofing", "checklist": "07 40 00 - Roofing (TPO, EPDM, Modified Bitumen, Metal)" }, + "07 62 00": { "title": "Metal Flashing", "checklist": "07 62 00 - Metal Flashing and Trim" }, + "07 84 00": { "title": "Firestopping", "checklist": "07 84 00 - Firestopping" }, + "07 92 00": { "title": "Joint Sealants", "checklist": "07 92 00 - Joint Sealants" }, + "08 11 13": { "title": "Hollow Metal Doors and Frames", "checklist": "08 11 13 - Hollow Metal Doors and Frames" }, + "08 14 16": { "title": "Flush Wood Doors", "checklist": "08 14 16 - Flush Wood Doors" }, + "08 33 00": { "title": "Overhead Coiling Doors", "checklist": "08 33 00 - Overhead Coiling / Sectional Doors" }, + "08 33 26": { "title": "Overhead Coiling Grilles", "checklist": "08 33 00 - Overhead Coiling / Sectional Doors" }, + "08 41 13": { "title": "Aluminum-Framed Entrances and Storefronts", "checklist": "08 41 13 - Aluminum-Framed Entrances and Storefronts" }, + "08 71 00": { "title": "Door Hardware", "checklist": "08 71 00 - Door Hardware" }, + "08 80 00": { "title": "Glazing", "checklist": "08 80 00 - Glazing" }, + "09 21 16": { "title": "Shaft Wall Assemblies", "checklist": "09 22 00 / 09 29 00 - Non-Structural Metal Framing / Gypsum Board" }, + "09 22 00": { "title": "Non-Structural Metal Framing", "checklist": "09 22 00 / 09 29 00 - Non-Structural Metal Framing / Gypsum Board" }, + "09 22 16": { "title": "Non-Structural Metal Framing", "checklist": "09 22 00 / 09 29 00 - Non-Structural Metal Framing / Gypsum Board" }, + "09 29 00": { "title": "Gypsum Board", "checklist": "09 22 00 / 09 29 00 - Non-Structural Metal Framing / Gypsum Board" }, + "09 30 00": { "title": "Tiling", "checklist": "09 30 00 - Tile" }, + "09 30 13": { "title": "Ceramic Tiling", "checklist": "09 30 00 - Tile" }, + "09 51 00": { "title": "Acoustical Ceilings", "checklist": "09 51 00 - Acoustical Ceilings" }, + "09 65 00": { "title": "Resilient Flooring", "checklist": "09 65 00 - Resilient Flooring / LVT / Rubber Base" }, + "09 68 00": { "title": "Carpet", "checklist": "09 68 00 - Carpet Tile / Broadloom" }, + "09 90 00": { "title": "Painting and Coatings", "checklist": "09 90 00 - Painting and Coatings" }, + "10 11 00": { "title": "Visual Display Boards", "checklist": null }, + "10 14 00": { "title": "Signage", "checklist": "10 14 00 - Signage" }, + "10 21 13": { "title": "Toilet Partitions", "checklist": "10 21 13 - Toilet Partitions" }, + "10 22 39": { "title": "Operable Partitions", "checklist": null }, + "10 28 00": { "title": "Toilet and Bath Accessories", "checklist": "10 28 00 - Toilet and Bath Accessories" }, + "10 44 00": { "title": "Fire Extinguishers", "checklist": "10 44 00 - Fire Extinguishers and Cabinets" }, + "10 51 00": { "title": "Lockers", "checklist": "10 51 00 - Lockers" }, + "11 30 00": { "title": "Residential Appliances", "checklist": "11 30 00 - Residential Appliances" }, + "11 40 00": { "title": "Food Service Equipment", "checklist": "11 40 00 - Food Service Equipment (FSE)" }, + "11 66 00": { "title": "Athletic Equipment", "checklist": "11 66 00 - Athletic Equipment" }, + "12 24 13": { "title": "Window Treatments", "checklist": "12 24 13 - Window Treatments (Roller Shades, Blinds)" }, + "12 36 00": { "title": "Countertops", "checklist": "12 36 00 - Countertops (Solid Surface, Quartz, Stone)" }, + "13 31 00": { "title": "Fabric Structures", "checklist": "13 31 00 - Fabric Structures / Pre-Engineered Metal Building" }, + "13 34 19": { "title": "Metal Building Systems", "checklist": "13 34 19 - Metal Building Systems" }, + "14 24 00": { "title": "Elevators", "checklist": "14 24 00 - Hydraulic / Traction Elevators" }, + "21 13 00": { "title": "Wet-Pipe Fire Sprinklers", "checklist": "21 13 00 - Wet-Pipe Fire Sprinklers" }, + "22 10 00": { "title": "Plumbing Piping", "checklist": "22 10 00 - Plumbing Piping" }, + "22 30 00": { "title": "Water Heaters", "checklist": "22 30 00 - Water Heaters / Tanks" }, + "22 40 00": { "title": "Plumbing Fixtures", "checklist": "22 40 00 - Plumbing Fixtures" }, + "23 05 00": { "title": "HVAC Common Work Results", "checklist": "23 05 00 - Common Work Results for HVAC" }, + "23 09 00": { "title": "HVAC Controls / BMS", "checklist": "23 09 00 - Instrumentation and Controls / BMS" }, + "23 30 00": { "title": "Air Distribution / Ductwork", "checklist": "23 30 00 - Air Distribution (Ductwork)" }, + "23 70 00": { "title": "Air Handling / RTU", "checklist": "23 70 00 - Air Handling Units / Rooftop Units" }, + "23 80 00": { "title": "Decentralized HVAC (VRF, Split, FCU)", "checklist": "23 80 00 - Decentralized HVAC Equipment (VRF, Split, FCU)" }, + "26 05 00": { "title": "Electrical Common Work Results", "checklist": "26 05 00 - Common Work Results for Electrical" }, + "26 24 00": { "title": "Switchboards / Panelboards", "checklist": "26 24 00 - Switchboards / Panelboards / MCC" }, + "26 27 00": { "title": "Wiring Devices", "checklist": "26 27 00 - Wiring Devices" }, + "26 51 00": { "title": "Interior Lighting", "checklist": "26 51 00 - Interior Lighting" }, + "26 56 00": { "title": "Exterior Lighting", "checklist": "26 56 00 - Exterior Lighting" }, + "27 10 00": { "title": "Structured Cabling", "checklist": "27 10 00 - Structured Cabling" }, + "27 40 00": { "title": "Audio-Video Systems", "checklist": "27 40 00 - Audio-Video Systems" }, + "28 13 00": { "title": "Access Control", "checklist": "28 13 00 / 28 23 00 - Access Control / Video Surveillance" }, + "28 23 00": { "title": "Video Surveillance", "checklist": "28 13 00 / 28 23 00 - Access Control / Video Surveillance" }, + "28 31 00": { "title": "Fire Detection and Alarm", "checklist": "28 31 00 - Fire Detection and Alarm" }, + "31 10 00": { "title": "Site Clearing", "checklist": "31 10 00 / 31 20 00 - Site Clearing / Earth Moving" }, + "31 20 00": { "title": "Earth Moving", "checklist": "31 10 00 / 31 20 00 - Site Clearing / Earth Moving" }, + "31 60 00": { "title": "Special Foundations", "checklist": "31 60 00 - Special Foundations / Piers / Piles" }, + "32 12 00": { "title": "Asphalt Paving", "checklist": "32 12 00 - Flexible Paving (Asphalt)" }, + "32 13 00": { "title": "Concrete Paving", "checklist": "32 13 00 - Rigid Paving (Concrete)" }, + "32 16 00": { "title": "Curbs, Gutters, Walks", "checklist": "32 16 00 - Curbs, Gutters, Walks" }, + "32 31 13": { "title": "Chain Link Fencing", "checklist": null }, + "32 90 00": { "title": "Landscaping", "checklist": "32 90 00 - Landscaping" }, + "33 10 00": { "title": "Water Utilities", "checklist": "33 10 00 - Water Utilities" }, + "33 30 00": { "title": "Sanitary Sewer", "checklist": "33 30 00 - Sanitary Sewer" }, + "33 40 00": { "title": "Storm Drainage", "checklist": "33 40 00 - Storm Drainage" } + }, + "pattern_triggers": { + "needs_sample_separate": [ + "07 62 00", "07 40 00", "04 20 00", "04 72 00", "06 40 00", + "10 21 13", "09 90 00", "12 36 00", "09 30 00" + ], + "coordination_required": { + "10 22 39": ["Steel fabricator"], + "08 33 00": ["Steel fabricator", "Precast fabricator"], + "08 33 26": ["Steel fabricator"], + "08 41 13": ["Metal panel fabricator"], + "22 40 00": ["Civil engineer"], + "08 11 13": ["Masonry contractor"], + "14 24 00": ["Structural engineer", "Electrical", "MEP"] + }, + "multi_round_typical": [ + "26 51 00", "26 56 00", "10 14 00", "06 40 00" + ], + "icc_record_submittal": [ + "07 40 00", "07 54 23", "21 13 00", "07 84 00", "28 31 00", "14 24 00" + ] + } +} diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/references/project_playbook.md b/plugins/willow-pmtools/skills/willowbrook-submittal-review/references/project_playbook.md new file mode 100644 index 0000000..32a1941 --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/references/project_playbook.md @@ -0,0 +1,208 @@ +# Submittal Pattern Playbook (from 0309A Stillwater HS — 500 submittals) + +Behavioral patterns from 500 real submittals. Use these to flag risks beyond the per-trade conformance checklists. When a submittal matches a pattern below, raise the flag in the review memo's "Historical Pattern Flags" section. + +--- + +## Response distribution (for context) + +- 73% Reviewed (clean approval) +- 6% Reviewed As Noted (conditional approval) +- 2% Revise and Resubmit (hard rejection) +- 23% of submittals went to at least one revision +- 17% had written reviewer comments + +--- + +## Pattern 1 — Incomplete Packages + +**Historical examples:** +- Hollow Metal Doors — "Submit Missing Doors" +- Door Hardware — hardware sets for PR#05 were missing +- Interior Storefronts — "Need submittal on Doors from Addendum 1" +- Floor Boxes — "Please resubmit floor box information per the redlines" + +**Flag trigger:** Submittal references a door schedule, fixture schedule, hardware schedule, or any enumerated list that may have been updated by addendum. + +**Flag language:** *"This trade historically has incomplete-package rejections. Verify every item in the spec section is included, and cross-check addenda for added items."* + +--- + +## Pattern 2 — Wrong or Outdated Drawings + +**Historical example:** Duct Work (Floor 1 Area 5) — *"Not the current floor plans"* + +**Flag trigger:** Any shop drawing submittal. Particularly flag HVAC/MEP, framing, and anything referencing floor plan dimensions. + +**Flag language:** *"Confirm the drawings referenced are the current issued-for-construction set. Check for recent ASIs or PRs that may have changed dimensions or locations."* + +--- + +## Pattern 3 — Samples Required Separate from Product Data + +**Historical examples:** +- Sheet Metal Flashing — "Need to see a physical sample of the coping color that is to match PT-4 (Stillwater Gold)" +- Louvers — "Provide physical samples of the clouded colors" +- Roof Skylights — "Submit samples for final approval" (after product data review) +- Millwork — "Please resubmit SS1 photo with color and manufacturer info" +- Toilet Partitions — "Please confirm the material texture and color" +- Direct Applied Soffit/Ceiling Finish — "Revise and resubmit gold sample" + +**Flag trigger:** Submittal type is Product Information AND the product has a specified custom color, finish, or texture. + +**Flag language:** *"Product data approval does not include color/finish approval. A separate physical sample submittal is historically required for this trade. Do not release material order until sample is approved."* + +**Trades most affected:** Sheet metal, louvers, roofing, millwork, toilet partitions, soffit finishes, metal panels, masonry veneer. + +--- + +## Pattern 4 — Missing Multi-Trade Coordination + +**Historical examples:** +- Operable Partitions — "Please make sure this gets sent to steel fabricator for steel beam coordination" +- Overhead Fire Rated Coiling Doors — "Please make sure this also gets sent to the Steel Fabricator for steel coordination" +- Tornado Resistant Coiling Doors — "This should also go to the Precast Fabricator for coordination" +- Grease Interceptor — civil engineer confirmation needed +- Curtain wall — metal panel connection detail coordination + +**Flag trigger:** The submittal involves an opening, door, curtain wall, operable partition, or any item that bears on / attaches to / penetrates another trade's work. + +**Flag language:** *"This item historically requires coordination with [affected trade]. Confirm the submittal has been routed to that sub before architect review."* + +**Coordination matrix:** + +| Submittal trade | Also routes to | +|---|---| +| Operable partitions | Steel fabricator | +| Coiling doors (fire-rated, tornado) | Steel fabricator, Precast fabricator | +| Curtain wall | Metal panel fabricator | +| Grease interceptor | Civil engineer | +| Ductwork | MEP engineer for current floor plan version | +| Hollow metal frames (at masonry) | Masonry contractor | +| Elevator | Structural, electrical, MEP | +| Overhead doors (at precast jamb) | Precast fabricator | + +--- + +## Pattern 5 — Over-Resubmittal (Resubmitting Whole Package) + +**Historical examples:** +- Metal Panels — "Revise and Resubmit only the elevations and panels that have not been marked as approved" +- Millwork — "Resubmit only request sheets" +- Interior Lighting — "Revise and Resubmit only the fixtures noted as such" + +**Flag trigger:** Submittal is a revision (R1, R2, R3) AND the prior submittal was Reviewed As Noted with partial approval. + +**Flag language:** *"Prior revision was partially approved. Only the flagged items should be resubmitted. Confirm this revision does not include items that were already approved — doing so resets the review clock."* + +--- + +## Pattern 6 — Missing ICC / Record Submittals + +**Historical example:** Roof Hoods — *"Being is this an ICC submittal. We do need a records submittal with the redlines picked up"* + +**Flag trigger:** Item is subject to AHJ inspection (roofing, fire suppression, life safety, firestopping, elevator, fire alarm). + +**Flag language:** *"Confirm whether an ICC / record submittal with redlines incorporated is required for this item. If so, prepare a separate record copy."* + +--- + +## Pattern 7 — Multi-Round Trades (Plan for R2+) + +**Trades that historically required multiple rounds:** + +| Trade | Rounds observed | Pattern | +|---|---|---| +| Interior Lighting | R0 → R2 | Partial approvals each round, resubmit only rejected fixtures | +| Exterior Lighting | R0 → R1+ | Similar to interior | +| Metal Panels | R0 → R3 | Elevations approved piecemeal | +| Millwork | R0 → R3 | R3 triggered reviewer request for meeting | +| Signage | R0 → R2 | Dimensional approval before graphic approval | +| Fire Suppression | Split tracks | Shop drawings + calcs reviewed separately | + +**Flag trigger:** Submittal is for one of the trades above. + +**Flag language:** *"This trade historically required [N] rounds of review. Plan schedule accordingly. If this submittal is R2 or higher, proactively schedule a coordination meeting before R3."* + +--- + +## Pattern 8 — Design Still in Flux (PR Pending) + +**Historical examples:** +- Area 4 Stairs guardrails — "We are working on a PR for the guardrails on 2nd floor. Resubmit guardrails" +- Media Equipment — "We will issue a PR to pull the remaining scope out of the project" +- Cast Stone engraving — "Once we have verbiage on the engraved cast stone we will provide" + +**Flag trigger:** PM indication that a PR is pending, OR the submittal item is known to be in an area of recent/pending design change. + +**Flag language:** *"Confirm no active PR or design change is pending on this item. Submitting against superseded scope creates rework."* + +--- + +## Pattern 9 — Duplicate Submittals + +**Historical example:** Structural Steel Area 5 R1 — *"Appears to be a duplicate submittal"* + +**Flag trigger:** Submittal number / title is very similar to an already-logged submittal. + +**Flag language:** *"This submittal number/title closely resembles prior submittal [X]. Confirm this is a new scope, not a duplicate."* + +--- + +## Pattern 10 — Color/Finish Confirmation Questions + +**Historical examples:** +- Toilet Partitions — "Please confirm the material texture and color" +- Exhaust Fan Manual Switch — "Please confirm operation of button" +- Grease Interceptor — "Civil engineer is okay with length of interceptor" + +**Flag trigger:** Submittal has a question-style check needed (confirm X, verify Y). + +**Flag language:** *"This item historically required written confirmation of [finish / operation / dimension] rather than a full resubmittal. If architect raises a question, respond in writing — do not prepare a full resubmittal."* + +--- + +## Turnaround expectations + +| Submittal type | Typical turnaround | Examples | +|---|---|---| +| Simple product data | 1–3 days | Anchor bolts, masonry accessories | +| Standard shop drawings | 10–15 business days | Most trades | +| Complex shop drawings | 3–4 weeks | MEP systems, structural with engineer review | + +**Reviewer routing observed on 0309A:** +- Jeff Thomas (505 Architects) — primary reviewer for most trades +- Annie Hecksher (505 Architects) — masonry and select others +- Some submittals required both (extends timeline) + +Use this to set realistic Final Due Dates and manage sub expectations. + +--- + +## Reviewer phrase glossary + +| Phrase | What it actually means | +|---|---| +| "Resubmit requested information" | Something is missing — read the attached redlines | +| "See attached reviewed submittal" | Redlines are on the returned PDF — download and read | +| "Approved as Noted" | Conditionally approved — work can proceed only if noted items are addressed | +| "Reviewed and Approved" | Clean approval, no action | +| "Submit samples for final approval" | Product data ok, physical sample still needed | +| "Resubmit only [specific items]" | Do not resubmit the whole package | +| "Please confirm…" | A question — respond in writing, not a full resubmittal | +| "We are working on a PR…" | Hold the submittal, do not resubmit until PR is issued | + +--- + +## What the skill should output when a pattern matches + +For each pattern that matches, add a line to the **Historical Pattern Flags** section of the review memo: + +``` +Historical Pattern Flags: + ⚠ [Pattern name] — [Flag language] (severity: warn | info) +``` + +Severity: +- **warn** — historically led to rejection or resubmittal +- **info** — historically led to a comment or clarification request but not rejection diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/references/weston_guide.md b/plugins/willow-pmtools/skills/willowbrook-submittal-review/references/weston_guide.md new file mode 100644 index 0000000..b0aa8fc --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/references/weston_guide.md @@ -0,0 +1,1101 @@ +# Willowbrook Submittal Review Guide (Weston's Guide) + +Per-trade conformance checklists organized by CSI division. Reference this to determine which items a submittal must demonstrate before it is stamped Approved or Approved as Noted. + +--- + +## Universal First-Pass Checks — Apply to Every Submittal + +- Submittal transmittal cover sheet is complete (project #, sub name, spec section, date) +- Submittal number and revision matches the submittal log +- Sub has stamped and signed the submittal (reviewed and approved by the sub before submission) +- Spec section referenced matches the scope being submitted (no orphan submittals) +- Product data is for the exact product, not the product family or manufacturer catalog +- Deviations from spec are called out in writing — not buried in fine print +- Manufacturer appears in the spec's approved manufacturer list, OR a substitution request is included with equals/betters documentation +- Model numbers match the spec's Basis-of-Design, or a documented equal is provided +- Finish / color / size options are narrowed to a single selection, not left as a menu +- Lead time is stated and checked against construction schedule +- ASTM / ANSI / UL standards cited in the spec are addressed in the submittal +- Warranty duration meets or exceeds spec requirement +- Shop drawings (where required) show actual project dimensions, not typical generic drawings +- Coordination dimensions (openings, penetrations, anchors) tie to structural / architectural drawings +- Samples required by spec are listed and tracked — not skipped +- Submittal is complete — no "additional data to follow" placeholders +- Stamped response (Approved / Approved as Noted / Revise and Resubmit / Rejected) is entered in the log with comments + +--- + +## Division 02 — Existing Conditions + +### 02 41 00 — Demolition +**Product Data** +- Demolition plan identifies exact items to be removed vs. salvaged +- Dust, noise, and vibration control methods called out +- Hazardous material abatement plan referenced (if applicable) +- Waste manifest / disposal facility identified +- Utility disconnect coordination documented +- Temporary shoring / protection details provided + +**Shop Drawings** +- Shoring / bracing design stamped by engineer where required +- Protection of adjacent structures / finishes shown + +**Samples / Closeout** +- Pre-demolition condition survey photos included +- Recycling / LEED diversion report format identified (if applicable) + +### 02 82 00 — Asbestos Abatement / 02 83 00 Lead Abatement +**Product Data** +- Licensed abatement contractor certificate attached +- Scope of hazardous material and quantities match survey +- Containment, negative air, HEPA details match regulatory requirements +- Worker qualifications and medical clearances on file +- Air monitoring firm independent from abatement contractor +- Disposal facility permitted for hazmat class + +**Shop Drawings** +- Containment plan drawings included + +**Samples / Closeout** +- Notification to state / EPA filed before start + +--- + +## Division 03 — Concrete + +### 03 30 00 — Cast-in-Place Concrete +**Product Data** +- Mix design submitted for each strength class shown on drawings +- 28-day f'c matches spec for each mix +- Cement type (Type I/II, Type III, etc.) matches spec +- Aggregate source and gradation reports included +- Admixtures listed (air-entraining, water reducer, retarder, accelerator) +- Water-cement ratio documented and within spec +- Slump / flow range stated +- Air content range stated (for exterior or freeze-thaw exposure) +- Testing lab / batch plant named and certified + +**Shop Drawings** +- Reinforcement placement drawings show bar size, grade, spacing, cover +- Bar bends and lap splices match code (ACI 318) +- Embed plates / anchor bolt locations coordinated with steel shops +- Construction and control joint layout shown + +**Samples / Closeout** +- Curing and protection plan included +- Cold/hot weather protection (if applicable) +- Finish type (float, trowel, broom, exposed) matches spec + +### 03 35 00 — Concrete Finishing / Polished Concrete +**Product Data** +- Finish schedule identifies each floor area's required finish +- Aggregate exposure level (cream, salt-and-pepper, stone) selected +- Sheen level (matte, satin, high gloss) selected +- Densifier and sealer product data included +- Warranty terms (minimum 2 year typical) + +**Shop Drawings** +- Control joint and saw cut layout shown + +**Samples / Closeout** +- Mock-up area and size identified + +### 03 45 00 — Precast Architectural Concrete +**Product Data** +- Mix design and color / aggregate match architectural spec +- Embed and connection details engineered and stamped +- Lifting inserts and handling engineered +- Weathering / efflorescence controls addressed + +**Shop Drawings** +- Shop drawings show each piece number, dimensions, embeds, reinforcement +- Erection drawings show sequence, crane access, bracing + +**Samples / Closeout** +- Sample panel (minimum 4'x4') requirement +- Plant QC program and PCI certification included + +--- + +## Division 04 — Masonry + +### 04 20 00 — Unit Masonry (CMU / Brick) +**Product Data** +- Unit type, size, grade, and weight class match spec +- CMU strength (f'm) and net / gross area compressive strength documented +- Brick face color, texture, and manufacturer match approved range +- Mortar type (M, S, N, O) matches structural requirements +- Grout mix design with slump / strength documented +- Reinforcement type (ladder, truss), spacing, and coating specified +- Flashing material (stainless, copper, self-adhered) matches spec +- Weep vents product data included with spacing documented +- Anchors, ties, and connectors with finish specified + +**Shop Drawings** +- Shop drawings show coursing, bond pattern, control joint layout +- Lintel and shelf angle details coordinated with steel +- Reinforcement bar size, spacing, and lap splices shown + +**Samples / Closeout** +- Mock-up panel size, location, and number of panels (typical 4'x4') +- Range sample from approved manufacturer lot +- Efflorescence / freeze-thaw test data (if exterior) + +### 04 72 00 — Cast Stone / Architectural Stone +**Product Data** +- Color, texture, and finish match approved sample +- Mix design and compressive strength documented +- Joint width and mortar type matched to stone type +- Anchoring system rated for wind / seismic loads + +**Shop Drawings** +- Shop drawings show each piece with number, dimensions, reinforcement +- Anchor and dowel locations coordinated with structure + +**Samples / Closeout** +- Full-size sample required for color and finish + +--- + +## Division 05 — Metals + +### 05 12 00 — Structural Steel Framing +**Product Data** +- Mill certificates for each shape and plate grade +- AISC fabricator certification on file +- Bolts (ASTM A325 / A490) certified and coating matches spec +- Welding filler material classification matches spec +- Welding procedures (WPS) for each joint type +- Welder qualifications current +- Primer / paint system matches spec (or left bare for fireproofing) +- Galvanizing thickness for exterior members +- Fireproofing coordination — surface prep requirements + +**Shop Drawings** +- Erection drawings show member IDs, elevations, and bolt patterns +- Connection details stamped by structural engineer +- Camber / deflection notes shown where required +- Anchor bolt setting plans with templates +- Lift plans and temporary bracing shown + +**Samples / Closeout** +- Touch-up paint procedures +- Surveyor-verified anchor bolt layout pre-pour + +### 05 31 00 — Steel Deck +**Product Data** +- Gauge, depth, span, and finish match structural drawings +- Fluted profile (composite, form, roof) matches +- Shear stud / puddle weld pattern per deck schedule +- Manufacturer (Vulcraft, Verco, ASC) is on approved list + +**Shop Drawings** +- Layout shows deck sheet orientation and panel IDs +- Opening reinforcement and closures shown + +### 05 40 00 — Cold-Formed Metal Framing +**Product Data** +- Stud gauge, depth, and yield strength match structural spec +- Screw type, size, and spacing per connection detail +- Galvanizing class (G60 / G90) matches exposure +- Clip and connector product data with engineering + +**Shop Drawings** +- Framing elevations show stud spacing, bracing, headers +- Truss / rafter shop drawings stamped by engineer + +### 05 50 00 — Miscellaneous Metal Fabrications +**Product Data** +- Each fabrication item (stairs, rails, grates, bollards) itemized and numbered +- Material grade, finish, and fastener type per item +- Rail load test (200 lb point load, 50 plf) compliance + +**Shop Drawings** +- Shop drawings show each piece with dimensions and connections +- Anchorage to concrete / steel detailed and coordinated + +**Samples / Closeout** +- Full-size sample of stair nosing / railing profile where specified + +--- + +## Division 06 — Wood, Plastics, Composites + +### 06 10 00 — Rough Carpentry +**Product Data** +- Lumber grade and species per spec (e.g., #2 SPF, Southern Yellow Pine) +- Fire-retardant treatment certificate if interior combustible elements +- Preservative treatment (ACQ, MCA) for ground-contact / exterior +- Engineered products (LVL, LSL, PSL, I-joist) grade and manufacturer +- Fasteners matched to treatment (hot-dipped galvanized or stainless with ACQ) +- Hardware (hangers, straps, ties) Simpson / USP equivalent specified + +**Shop Drawings** +- Shop drawings where engineered floor / roof systems used + +**Samples / Closeout** +- Moisture content at delivery (typical 19% max for framing) + +### 06 20 00 — Finish Carpentry / Trim +**Product Data** +- Species, grade, and profile match spec and drawings +- Moisture content meets interior installation range (6–8%) +- Factory pre-finish product data if pre-finished + +**Shop Drawings** +- Profile shop drawings for custom trim + +**Samples / Closeout** +- Sample of each profile and finish + +### 06 40 00 — Architectural Woodwork / Casework +**Product Data** +- AWI grade (Economy / Custom / Premium) matches spec +- Core material (particleboard, MDF, plywood) per spec +- Face veneer or laminate product and color per finish schedule +- Edge banding material and thickness +- Hardware (hinges, slides, pulls) manufacturer and finish +- Countertop material (plastic laminate, solid surface, quartz) and edge profile + +**Shop Drawings** +- Shop drawings show each unit with plan, elevation, and sections +- Anchoring, blocking, and filler strip details +- Coordination with plumbing, electrical, ADA clearances + +**Samples / Closeout** +- Sample of door / drawer with full hardware +- Finish sample (laminate, wood species) + +--- + +## Division 07 — Thermal and Moisture Protection + +### 07 13 00 / 07 14 00 — Sheet / Fluid-Applied Waterproofing +**Product Data** +- Product data for membrane, primer, and all accessories +- Membrane thickness (mil) and ASTM E96 permeance match spec +- Compatibility statement with adjacent materials (insulation, sealant) +- Substrate prep requirements (moisture content, smoothness) +- Warranty length (typical 5–10 year material) + +**Shop Drawings** +- Detail drawings at terminations, transitions, penetrations + +**Samples / Closeout** +- Pull-off adhesion test requirement +- Pre-installation conference documented + +### 07 21 00 — Thermal Insulation +**Product Data** +- R-value per inch for each product type +- Material type (XPS, EPS, polyiso, mineral wool, fiberglass) +- Thickness matches building envelope calcs +- Flame / smoke (ASTM E84) for interior use +- Vapor barrier compatibility +- Blowing agent disclosure (GWP) if LEED / sustainability spec + +### 07 27 00 — Air Barriers +**Product Data** +- Air permeance test (ASTM E2178 / E2357) meets spec +- Primer / adhesive compatibility with sheathing +- Sealant, tape, and flashing all from same system manufacturer +- UV exposure limit before cover stated + +**Shop Drawings** +- Transition details at windows, roof, foundation + +**Samples / Closeout** +- Whole-building air leakage test (0.25 cfm/sf at 0.3 in. w.g. typical) included + +### 07 40 00 — Roofing (TPO, EPDM, Modified Bitumen, Metal) +**Product Data** +- Membrane thickness, color, and reinforcement type +- Fastening pattern per wind uplift rating (FM, UL) +- Insulation R-value, thickness, and taper layout +- Flashing materials from same system +- Walkpads and protection mat locations +- NRCA Gold / Silver or manufacturer-certified installer +- Warranty type (NDL, material only, system) and length + +**Shop Drawings** +- Roof plan with slopes, drains, scuppers, crickets +- Details at curbs, parapets, expansion joints, penetrations + +**Samples / Closeout** +- Color / finish sample for metal roofing +- Pre-roofing conference and manufacturer field inspection required + +### 07 62 00 — Metal Flashing and Trim +**Product Data** +- Metal type (galvanized, aluminum, stainless, copper) and gauge +- Finish (Kynar 500, anodized) and color +- Fastener compatibility with metal type + +**Shop Drawings** +- Profiles drawn at each condition (drip edges, copings, counterflashing) + +**Samples / Closeout** +- Color sample or range of samples + +### 07 84 00 — Firestopping +**Product Data** +- UL system for each penetration type (cable, pipe, duct, joint) +- Manufacturer (STI, Hilti, 3M) on approved list +- F-rating and T-rating match assembly rating + +**Shop Drawings** +- Penetration schedule matches drawings + +**Samples / Closeout** +- Installer training certificate per manufacturer + +### 07 92 00 — Joint Sealants +**Product Data** +- Type (silicone, urethane, polysulfide) per joint location +- Movement capability (±25%, ±50%) matches joint design +- Primer, backer rod, and bond breaker from same system +- Color chart with approved colors per location + +**Samples / Closeout** +- Mock-up joint with full depth, 10 lf + +--- + +## Division 08 — Openings + +### 08 11 13 — Hollow Metal Doors and Frames +**Product Data** +- Door / frame schedule matches architect schedule — each opening accounted for +- Door gauge (16 ga standard; 14 ga heavy duty) per schedule +- Frame gauge and throat dimension matches wall type +- Core type (honeycomb, polyurethane, mineral) matches fire rating +- Fire labels (20, 45, 60, 90, 180 minute) match door schedule +- Vision lite and louver sizes and locations +- Prep for hardware matches hardware schedule (strike, hinge, closer) +- Finish (primer + field paint, or factory finish) specified + +**Shop Drawings** +- Elevation of each door type with dimensions and preps +- Frame profiles at each wall condition + +### 08 14 16 — Flush Wood Doors +**Product Data** +- AWI grade (Economy / Custom / Premium) per spec +- Core type (particleboard, SLC, mineral) matches fire rating +- Face veneer species, cut (plain sliced, rift, quartered), and match type +- Edge type (matching face, raised edge) per spec +- Fire rating label where required + +**Shop Drawings** +- Elevation of each door type with hardware preps + +**Samples / Closeout** +- Veneer sample approved before fabrication + +### 08 33 00 — Overhead Coiling / Sectional Doors +**Product Data** +- Door type, size, and cycle class match spec +- Operator (manual, chain, motor) and horsepower +- Slat / panel material, gauge, and finish +- Wind load rating (for exterior) +- Safety devices (photo eye, safety edge) included +- Fire rating with drop test (for rated openings) + +**Shop Drawings** +- Elevation of each door type showing opening size, guides, hood, operator +- Coordination with jamb, header structure, and electrical + +### 08 41 13 — Aluminum-Framed Entrances and Storefronts +**Product Data** +- Frame series, depth, and profile match spec +- Finish (anodized class, Kynar color) matches approved sample +- Glass type, thickness, and low-E coating per glazing spec +- Door hardware (hinges, closers, exit devices, thresholds) per schedule +- Impact, wind, and water infiltration ratings (for exterior) +- ADA operating force and threshold compliance + +**Shop Drawings** +- Elevation of each storefront with dimensions, door swings, and hardware +- Anchor, sill, head, and jamb details at structure + +**Samples / Closeout** +- Finish and color sample +- Glass sample (low-E, tint) + +### 08 71 00 — Door Hardware +**Product Data** +- Hardware schedule matches door schedule — every opening has a hardware set +- Manufacturer per item is on approved list +- Finish code (BHMA: 630, 626, US26D) matches design intent +- Keying / access control plan from owner included +- Electrified hardware voltage, monitoring, and power supply coordinated with Div 28 +- Fire / life-safety hardware labeled per rating +- ADA compliance (lever operation, 5 lb force) + +**Shop Drawings** +- Riser diagram for electrified openings + +**Samples / Closeout** +- Wiring diagrams for each electrified opening +- Factory-trained installer / AHC reviewed + +### 08 80 00 — Glazing +**Product Data** +- Glass type (annealed, heat-strengthened, tempered, laminated) per location +- Thickness of each lite and IGU makeup +- Low-E coating type and surface location (#2, #3) +- Performance values (SHGC, VLT, U-value) match energy spec +- Warranty (10 year sealed IGU typical) +- Safety glazing at required locations (doors, sidelites, bathtubs) + +**Samples / Closeout** +- Glass sample with coating + +--- + +## Division 09 — Finishes + +### 09 22 00 / 09 29 00 — Non-Structural Metal Framing / Gypsum Board +**Product Data** +- Stud gauge (25, 20, 18, 16) per height and loading +- Gypsum board type (regular, Type X, mold-resistant, impact, abuse) +- Fire-rated assembly UL number matches drawings +- Sound-rated assembly STC number matches spec +- Joint tape, compound, and primer per manufacturer system +- Corner bead type (metal, paper-faced, vinyl) + +**Shop Drawings** +- Framing elevations at full-height walls and heads +- Acoustic detail at STC-rated walls + +### 09 51 00 — Acoustical Ceilings +**Product Data** +- Tile manufacturer, size, edge profile match spec +- NRC, CAC, and LR ratings match +- Grid manufacturer, type (heavy-duty, intermediate), and finish +- Seismic category and hold-down / perimeter clip detail per IBC +- Recycled content / CDPH LEED info (if applicable) + +**Shop Drawings** +- Reflected ceiling plan with tile layout, borders, and transitions + +**Samples / Closeout** +- Tile sample + +### 09 65 00 — Resilient Flooring / LVT / Rubber Base +**Product Data** +- Product line, thickness, and wear layer per spec +- Pattern / color from approved palette +- Adhesive matched to substrate moisture conditions +- Moisture test (calcium chloride / RH probe) requirement +- Fire (ASTM E648) and smoke (NFPA 258) compliance +- Reducer / transition strips coordinated with adjacent flooring + +**Shop Drawings** +- Seam / pattern layout for large plank / sheet goods + +**Samples / Closeout** +- Color / pattern sample +- Attic stock (typical 2–5%) listed + +### 09 68 00 — Carpet Tile / Broadloom +**Product Data** +- Style, color, and construction per spec (tufted, woven) +- Face weight, fiber type, and backing per spec +- Appearance retention rating / CRI Green Label +- Adhesive and moisture limits +- Pattern / install direction from manufacturer + +**Shop Drawings** +- Seam / tile layout for patterned goods + +**Samples / Closeout** +- Color sample and dye lot match +- Attic stock listed + +### 09 30 00 — Tile +**Product Data** +- Manufacturer, size, color, and finish per schedule +- ANSI / TCNA installation method (thinset type, uncoupling membrane) +- Grout color and type (sanded, unsanded, epoxy) +- Tile slip resistance (DCOF ≥ 0.42 wet) for wet areas +- Waterproofing / crack isolation membrane if required +- Movement joint locations per TCNA EJ171 + +**Shop Drawings** +- Tile layout plan with pattern, starting points, and trim tile + +**Samples / Closeout** +- Tile and grout sample +- Mockup (typical 4'x4') requirement + +### 09 90 00 — Painting and Coatings +**Product Data** +- Paint manufacturer (Sherwin-Williams, Benjamin Moore, PPG) on approved list +- Product line (PrepRite, ProMar 200, Super Paint) per surface type +- Gloss level (flat, eggshell, semi-gloss, gloss) per finish schedule +- Color schedule from architect included +- Number of coats (primer + 2 finish typical) +- VOC compliance with state / LEED spec +- Specialty coating (epoxy, intumescent) with manufacturer system + +**Samples / Closeout** +- Color and sheen sample board +- Surface prep requirements (caulking, sanding, patching) documented + +--- + +## Division 10 — Specialties + +### 10 14 00 — Signage +**Product Data** +- Sign schedule matches room schedule — every room has a sign +- Sign type (wall, ceiling, projecting, exterior) per location +- Material (acrylic, aluminum, PET) and thickness +- ADA compliance — braille, raised characters, font, finish (non-glare) +- Color (min 70% LRV contrast) per ADA +- Mounting method and location (60" to centerline typical) +- Code / life-safety signs identified separately + +**Shop Drawings** +- Elevation of each sign type with size, copy, and artwork + +**Samples / Closeout** +- Sign sample for one of each type + +### 10 21 13 — Toilet Partitions +**Product Data** +- Material (baked enamel, plastic laminate, solid plastic, stainless steel) +- Mounting (floor, overhead braced, ceiling hung, floor-to-ceiling) per location +- Color from approved range +- Hardware (hinges, latches, brackets) finish matches +- ADA compartment dimensions, grab bars coordinated, door swing + +**Shop Drawings** +- Layout plan with each compartment numbered and dimensions + +**Samples / Closeout** +- Material sample with color + +### 10 28 00 — Toilet and Bath Accessories +**Product Data** +- Accessory schedule per room type +- Manufacturer (Bobrick, ASI, Bradley) on approved list +- Finish (polished, satin stainless) matches spec +- Recessed vs. surface-mount per location +- Grab bar type, length, and mounting (ADA-compliant anchorage) +- Paper towel / soap dispensers — owner-furnished coordination + +### 10 44 00 — Fire Extinguishers and Cabinets +**Product Data** +- Extinguisher type (ABC, K, Class D) per location +- Cabinet type (recessed, semi-recessed, surface) and rating (if in rated wall) +- Identification signage included +- Trim and finish match adjacent + +### 10 51 00 — Lockers +**Product Data** +- Locker configuration (single, double, Z, athletic) per schedule +- Material (steel, phenolic, plastic laminate) and gauge +- Color from approved range +- Lock type (built-in, padlock ready, ADA-accessible) +- Number plates and lock numbering sequence +- Seismic anchorage detail + +**Shop Drawings** +- Elevation of each locker bank + +**Samples / Closeout** +- Material and color sample + +--- + +## Division 11 — Equipment + +### 11 30 00 — Residential Appliances +**Product Data** +- Appliance schedule matches plan +- Manufacturer, model, color, and finish per schedule +- Electrical / gas / water rough-in dimensions coordinated +- ENERGY STAR rating if required + +### 11 40 00 — Food Service Equipment (FSE) +**Product Data** +- FSE schedule matches floor plan — every item numbered +- Manufacturer and model per item +- Rough-in requirements (electrical, plumbing, gas, hood) coordinated with MEP +- NSF / UL listings per item type +- Hood CFM and make-up air sizing coordinated with Div 23 +- Ansul / suppression system tied to hood +- Walk-in cooler / freezer panel type, thickness, floor detail + +**Shop Drawings** +- Equipment plan with each item numbered and rough-in points +- Utility connection schedule + +**Samples / Closeout** +- Health department review documentation + +### 11 66 00 — Athletic Equipment +**Product Data** +- Equipment schedule per facility (scoreboards, goals, standards, nets) +- Manufacturer (Draper, Porter, Jaypro, Bison) on approved list +- Motorized goal hoist voltage and control interface +- Anchor / embed details coordinated with structure and floor +- Certified installer required for warranty + +**Shop Drawings** +- Equipment layout with location, embed, and clearance + +--- + +## Division 12 — Furnishings + +### 12 24 13 — Window Treatments (Roller Shades, Blinds) +**Product Data** +- Shade type (manual, motorized, dual-shade, blackout) per window +- Manufacturer (MechoShade, Lutron, Hunter Douglas) on approved list +- Fabric openness factor and color per spec +- Motor voltage and control interface (if motorized) coordinated with Div 26 +- Mounting (inside recess, outside face, ceiling) per condition +- ADA reach range for manual shades + +**Shop Drawings** +- Window-by-window shade schedule with dimensions + +**Samples / Closeout** +- Fabric sample with openness factor + +### 12 36 00 — Countertops (Solid Surface, Quartz, Stone) +**Product Data** +- Material type, brand, color, and pattern +- Thickness and edge profile +- Sink cutout template and mounting method +- Seam locations and adhesive system + +**Shop Drawings** +- Each top drawn with dimensions, cutouts, and seams + +**Samples / Closeout** +- Full-size sample per color + +--- + +## Division 13 — Special Construction + +### 13 31 00 — Fabric Structures / Pre-Engineered Metal Building +**Product Data** +- Engineer of record licensed in project state +- Wind, snow, seismic loads match project criteria +- Steel coating system (primer, finish) and warranty +- Insulation R-value and vapor barrier +- Door, window, and accessory schedule + +**Shop Drawings** +- Foundation reactions for concrete sub +- Erection drawings stamped + +### 13 34 19 — Metal Building Systems +**Product Data** +- Building code edition and loading per project criteria +- Purlin / girt spacing and bracing shown +- Accessory schedule (doors, louvers, translucent panels) + +**Shop Drawings** +- Erection plans with member IDs + +--- + +## Division 14 — Conveying Equipment + +### 14 24 00 — Hydraulic / Traction Elevators +**Product Data** +- Capacity, speed, stops, and travel per spec +- Cab finish schedule (walls, floor, ceiling, handrail) match interior design +- Operation (simplex, duplex, group) and controls +- ADA compliance (button height, audible/visual signals) +- Machine room or MRL configuration +- Fire service operation (Phase I and II) per code +- Seismic zone compliance + +**Shop Drawings** +- Hoistway and pit dimensions coordinated with structure +- Machine room layout and ventilation coordinated with MEP + +**Samples / Closeout** +- Cab finish samples +- State elevator inspection certificate coordination + +--- + +## Division 21 — Fire Suppression + +### 21 13 00 — Wet-Pipe Fire Sprinklers +**Product Data** +- NFPA 13 hazard classification per area matches plans +- Hydraulic calcs submitted and reviewed by engineer of record +- Pipe material (black steel, CPVC where allowed) and sizing per calcs +- Sprinkler heads: type (upright, pendent, sidewall), K-factor, temp rating, finish +- Seismic bracing per NFPA 13 and project seismic category +- Fire pump (if required) curve and controller match spec +- Backflow preventer type and test connection + +**Shop Drawings** +- Sprinkler shop drawings stamped by design professional +- Layout shows deflector distance, coverage, and spacing rules +- Hanger and support locations shown + +**Samples / Closeout** +- Hydrostatic test record (200 psi for 2 hours) +- Contractor's Material and Test Certificate executed + +--- + +## Division 22 — Plumbing + +### 22 10 00 — Plumbing Piping +**Product Data** +- Piping materials (copper, PEX, cast iron, PVC) per system and location +- Pipe grade / schedule (Type L copper, DWV, Sch 40) match spec +- Fittings, solder, and joint methods match +- Pipe insulation thickness per code and spec +- Valves (ball, gate, check) — manufacturer and pressure class +- Hangers and seismic restraint where required + +**Shop Drawings** +- Riser diagrams and dimensioned above-ceiling layouts (where coordinated BIM required) + +### 22 40 00 — Plumbing Fixtures +**Product Data** +- Fixture schedule matches architect room schedule and ADA requirements +- Manufacturer, model, color, and finish per fixture +- Flush / flow rates meet WaterSense or LEED criteria +- Trim (lavatory faucets, flushometers, handles) per spec +- ADA fixture heights, clearances, and knee space compliance +- Mounting (wall-hung, floor-mount, chair carrier) coordinated with blocking / structure +- Water-efficient trap primer / hybrid + +**Samples / Closeout** +- Fixture color / finish sample per type + +### 22 30 00 — Water Heaters / Tanks +**Product Data** +- Capacity and recovery rate meet demand calcs +- Energy factor / thermal efficiency per spec +- Fuel type (gas, electric, heat pump) and venting +- Expansion tank and mixing valve included +- Seismic strapping for tanks + +--- + +## Division 23 — HVAC + +### 23 05 00 — Common Work Results for HVAC +**Product Data** +- Equipment schedule matches mechanical plans +- Manufacturer substitution requests flagged and documented +- Seismic restraints and vibration isolation per detail +- Access and service clearances coordinated + +### 23 30 00 — Air Distribution (Ductwork) +**Product Data** +- Duct material (galvanized, aluminum, fiberglass, flex) per spec +- Gauge and reinforcement per SMACNA pressure class +- Sealant class (A, B, C) per duct pressure +- Insulation type (interior liner or exterior wrap), thickness, and R-value +- Diffuser / grille / register schedule with size, type, finish, CFM +- Fire / smoke dampers per life-safety plans with UL listing + +**Shop Drawings** +- Coordination drawings (BIM / clash-detection) if spec requires +- Shop-fab coordination drawings for large plenums and custom fittings + +### 23 70 00 — Air Handling Units / Rooftop Units +**Product Data** +- Schedule: CFM, ESP, cooling / heating capacity, efficiency, sound +- Coil face velocity and fin material per spec +- Filter MERV rating per spec +- VFD / ECM motor per unit type +- Integral economizer and controls points match BMS spec +- Seismic / wind anchorage for rooftop equipment + +**Shop Drawings** +- Roof curb / structural load coordination +- Electrical MCA / MOCP coordination with Div 26 + +**Samples / Closeout** +- Factory startup required +- TAB (Test and Balance) by independent firm + +### 23 80 00 — Decentralized HVAC Equipment (VRF, Split, FCU) +**Product Data** +- Indoor / outdoor unit schedule with capacity and locations +- Refrigerant type (R-410A, R-32) and charge calcs +- Piping size and insulation per manufacturer +- Condensate drainage routing +- Controls interface and zone mapping + +**Shop Drawings** +- Piping riser and branch layout + +### 23 09 00 — Instrumentation and Controls / BMS +**Product Data** +- Points list matches control spec +- Controller hardware manufacturer on approved list +- Graphics and sequence of operation for each system +- Network protocol (BACnet, Modbus) and integration +- Sensor accuracy and calibration spec + +**Shop Drawings** +- Riser / architecture diagram + +**Samples / Closeout** +- Functional test report + +--- + +## Division 26 — Electrical + +### 26 05 00 — Common Work Results for Electrical +**Product Data** +- Raceway material (EMT, IMC, PVC) per location per NEC +- Conductor type (THHN, XHHW, MC cable) and insulation rating +- Grounding conductor sizing per NEC Table 250.122 +- Seismic bracing per IBC and project category + +### 26 24 00 — Switchboards / Panelboards / MCC +**Product Data** +- One-line diagram matches engineer's design +- AIC rating at each piece of gear exceeds calculated fault current +- Breaker sizes, types (thermal magnetic, GFCI, AFCI), and interrupting rating +- Arc flash labeling per NFPA 70E +- NEMA enclosure type per location (1, 3R, 4X) +- Surge protective device type and rating +- Short-circuit coordination study referenced + +**Shop Drawings** +- Switchboard / MCC elevations and nameplate schedule + +**Samples / Closeout** +- Factory test reports + +### 26 27 00 — Wiring Devices +**Product Data** +- Device schedule: receptacles, switches, dimmers, occupancy sensors +- Grade (spec-grade, commercial, hospital) per spec +- Color / finish per design +- Tamper-resistant / weather-resistant per NEC location +- GFCI / AFCI at required locations + +### 26 51 00 — Interior Lighting +**Product Data** +- Fixture schedule matches reflected ceiling plan — each type tagged +- Lamp source (LED), wattage, color temperature (CCT), CRI +- Photometric data for each fixture +- Lumens, efficacy, and LEED / Title 24 compliance if spec'd +- Driver voltage, dimming protocol (0-10V, DALI) match controls +- Emergency / egress fixtures called out separately +- Damp / wet location listing per spec + +**Shop Drawings** +- Lighting control diagram (zones, groups, scenes) + +**Samples / Closeout** +- Fixture sample for unique / architectural types + +### 26 56 00 — Exterior Lighting +**Product Data** +- Pole height, material, and finish per spec +- Wind load / EPA compliance +- Foundation / anchor template coordinated with concrete sub +- BUG rating (backlight, uplight, glare) for dark-sky compliance if spec'd +- Photocell / timeclock control integration + +**Shop Drawings** +- Pole base detail + +--- + +## Division 27 — Communications + +### 27 10 00 — Structured Cabling +**Product Data** +- Cable category (Cat6, Cat6A) per spec +- Jacket rating (plenum / riser) per routing +- Patch panel, rack, and cabinet schedule +- Pathway (cable tray, j-hooks, conduit) per spec +- Labeling scheme per ANSI/TIA-606 +- Test certificates (permanent link, channel) required + +**Shop Drawings** +- Rack elevation and port schedule + +### 27 40 00 — Audio-Video Systems +**Product Data** +- Equipment list matches AV design +- Rack layout and heat / power calcs +- Control system (Crestron, Extron, AMX) and touchpanel GUI +- Display type, size, and mounting for each room +- Cabling type (HDBaseT, HDMI, fiber) and length limits + +**Shop Drawings** +- System riser / architecture drawing +- Room-by-room installation drawings + +--- + +## Division 28 — Electronic Safety and Security + +### 28 31 00 — Fire Detection and Alarm +**Product Data** +- Battery and voltage drop calcs included +- Device types, models, and candela / wattage match spec +- NICET-certified designer stamp +- Code edition (NFPA 72) per AHJ +- Voice evacuation / mass notification integration if spec'd +- Smoke control / elevator recall interface + +**Shop Drawings** +- Riser diagram with device counts and addresses +- Floor plans showing device locations and spacing rules + +**Samples / Closeout** +- AHJ review coordination documented + +### 28 13 00 / 28 23 00 — Access Control / Video Surveillance +**Product Data** +- Device schedule matches security plan +- Head-end software and license quantity +- Camera type, resolution, and storage requirement +- Reader type (prox, smart card, biometric) and integration +- Network and cybersecurity requirements + +**Shop Drawings** +- System riser and device-by-device drawings + +--- + +## Division 31 — Earthwork + +### 31 10 00 / 31 20 00 — Site Clearing / Earth Moving +**Product Data** +- Geotech report referenced and approved sub readings +- Topsoil stripping depth and stockpile location +- Structural fill gradation and compaction (95% Standard Proctor typical) +- Erosion and sediment control plan (SWPPP) per state +- Dewatering plan if groundwater present +- Tree protection limits defined + +**Shop Drawings** +- Dewatering system layout if applicable + +**Samples / Closeout** +- Compaction testing lab identified and frequency per spec + +### 31 60 00 — Special Foundations / Piers / Piles +**Product Data** +- Pile / pier type, length, diameter, and capacity match design +- Installation method (driven, augered, CFA, drilled) and equipment +- Load test requirement and methodology +- Test / production pile separation from final foundation design +- Installer qualifications + +**Shop Drawings** +- Pile / pier layout with coordinates and tip elevations + +--- + +## Division 32 — Exterior Improvements + +### 32 12 00 — Flexible Paving (Asphalt) +**Product Data** +- Mix design: aggregate gradation, AC content, PG grade +- Lift thicknesses (base / intermediate / surface) match spec +- Tack coat rate and type +- Density requirement (typical 92% of Rice) +- Joint construction detail (longitudinal / transverse) + +**Samples / Closeout** +- Core sample locations and frequency + +### 32 13 00 — Rigid Paving (Concrete) +**Product Data** +- Concrete mix design per Div 03 criteria + exterior exposure +- Air entrainment for freeze-thaw +- Slab thickness and reinforcement match drawings +- Joint pattern (contraction, expansion, construction) with spacing +- Surface finish (broom, stamped) matches spec +- Cure and sealer type + +**Shop Drawings** +- Jointing plan + +### 32 16 00 — Curbs, Gutters, Walks +**Product Data** +- Concrete mix and finish per spec +- Detectable warnings (truncated domes) at curb ramps match ADA +- Slope requirements (running, cross) for walks meet ADA +- Expansion joint material and spacing + +### 32 90 00 — Landscaping +**Product Data** +- Plant schedule with species, size, quantity match plans +- Nursery source documented for large-caliper trees +- Topsoil and amendment specs +- Irrigation system sleeving coordinated under paving +- Establishment and warranty period (typical 1 year) +- Maintenance through establishment scope + +**Shop Drawings** +- Irrigation controller schedule and zone map + +**Samples / Closeout** +- Plant tag / nursery tag verification at delivery + +--- + +## Division 33 — Utilities + +### 33 10 00 — Water Utilities +**Product Data** +- Pipe material (DI, PVC C900, HDPE) and pressure class per jurisdiction +- Fittings (DI MJ, push-on, flanged) match +- Valves, hydrants, and thrust block details per local std +- Tracer wire / marking tape per spec +- Chlorination and bacteriological testing procedure +- Pressure / leakage test criteria + +**Shop Drawings** +- Profile of water main at crossings and bends + +**Samples / Closeout** +- Backflow preventer type and testing by certified tester + +### 33 30 00 — Sanitary Sewer +**Product Data** +- Pipe material (PVC SDR-35, DI, HDPE) and joint type per depth and jurisdiction +- Manholes: precast vs. CIP, diameter, drop connections, frame/cover class +- Service lateral material and cleanout type +- Deflection and leakage (air / vacuum) test criteria +- Bedding and embedment detail per spec + +**Shop Drawings** +- Profile showing invert elevations and slopes + +**Samples / Closeout** +- Mandrel / air test certification + +### 33 40 00 — Storm Drainage +**Product Data** +- Pipe material (RCP class, HDPE, PVC) per size and cover +- Inlets, catch basins, and manhole castings per local std +- Detention / retention system (pipe, chamber) with O&M requirements +- Water quality BMP (bioswale, filter) per stormwater permit +- Outfall protection / energy dissipator + +**Shop Drawings** +- Profile of storm runs and structures + +**Samples / Closeout** +- Video inspection post-install diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/schemas/input.schema.json b/plugins/willow-pmtools/skills/willowbrook-submittal-review/schemas/input.schema.json new file mode 100644 index 0000000..d4b8898 --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/schemas/input.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "willowbrook/submittal-review/input", + "title": "Submittal Review Input", + "description": "Payload the submittal-review skill accepts. Either submittal_pdf_path OR submittal_folder_path must be provided. metadata is optional — if absent, skill extracts what it can from the PDF content.", + "type": "object", + "properties": { + "submittal_pdf_path": { + "type": "string", + "description": "Absolute path to a single submittal PDF. Use for email-dropped or single-file submittals." + }, + "submittal_folder_path": { + "type": "string", + "description": "Absolute path to a submittal folder (Procore/Fieldwire export style). Skill detects the cover sheet (filename matches folder name) and treats other files as attachments." + }, + "metadata": { + "type": "object", + "description": "Optional metadata supplied by the caller (Willow, PM, etc.). Overrides anything extracted from the PDF.", + "properties": { + "project_id": { "type": "string", "description": "Willowbrook project number (e.g. '0309A')" }, + "submittal_num": { "type": "string", "description": "Submittal number with revision (e.g. '157.1')" }, + "spec_section": { "type": "string", "description": "CSI section (e.g. '08 71 00 - Door Hardware')" }, + "type": { + "type": "string", + "enum": ["Shop Drawing", "Product Information", "Sample", "Calculation", "Test Report", "Warranty", "Certificate", "Other"] + }, + "contractor": { "type": "string" }, + "package": { "type": "string", "description": "Bid package this submittal belongs to" }, + "lead_time_days": { "type": "integer", "minimum": 0 } + } + }, + "project_spec_path": { + "type": "string", + "description": "Absolute path to the project spec — either a folder of already-extracted CSI section .txt files (containing _manifest.json), a folder with the raw spec-book PDF (skill will extract and cache into 'Extracted Sections/' on first run), or a direct path to the spec-book PDF. When present, conformance checks cite actual spec paragraphs and 'Approved' status becomes achievable. When absent, the skill falls back to checklist-only review and can only recommend 'Approved as Noted' or lower." + }, + "output_dir": { + "type": "string", + "description": "Where to write review.json and the Word checklist. Defaults to a 'review' subfolder next to the submittal." + } + }, + "oneOf": [ + { "required": ["submittal_pdf_path"] }, + { "required": ["submittal_folder_path"] } + ] +} diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/schemas/output.schema.json b/plugins/willow-pmtools/skills/willowbrook-submittal-review/schemas/output.schema.json new file mode 100644 index 0000000..4f6a1d4 --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/schemas/output.schema.json @@ -0,0 +1,151 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "willowbrook/submittal-review/output", + "title": "Submittal Review Output", + "description": "Structured review result. Field names align with Willowbrook's planned submittal database schema so Willow can ingest directly. Every run produces both this JSON and a matching Word checklist.", + "type": "object", + "required": [ + "submittal_id", + "project_id", + "overall_status", + "conformance_checks", + "pattern_flags", + "draft_status" + ], + "properties": { + "submittal_id": { "type": "string", "description": "e.g. '167.0' — supplied or extracted" }, + "project_id": { "type": "string", "description": "Willowbrook project number" }, + "spec_section": { "type": "string" }, + "csi_division": { "type": "string", "description": "2-digit CSI division, e.g. '08'" }, + "package_number": { "type": "string", "description": "Bid package" }, + "product_name": { "type": "string" }, + "manufacturer": { "type": "string" }, + "model_number": { "type": "string" }, + "type": { "type": "string" }, + "contractor": { "type": "string", "description": "Submitted_by in Willow's schema" }, + "review_assignee": { "type": "string", "description": "Architect reviewer when known" }, + "review_due_date": { "type": "string", "format": "date" }, + "review_completed_date": { "type": "string", "format": "date" }, + + "overall_status": { + "type": "string", + "enum": ["PASS", "CONDITIONAL", "FAIL", "INSUFFICIENT_DATA"], + "description": "PASS = ready for architect; CONDITIONAL = can forward with notes; FAIL = return to sub; INSUFFICIENT_DATA = skill could not evaluate with ≥95% confidence on enough items" + }, + "submittal_status_recommended": { + "type": "string", + "enum": ["Approved", "Approved as Noted", "Revise and Resubmit", "Rejected"], + "description": "What the skill recommends the architect response should be. Only 'Approved' if every check has a spec citation." + }, + "draft_status": { + "type": "string", + "const": "DRAFT", + "description": "Every output is always labeled DRAFT. Skill never produces final output." + }, + + "conformance_checks": { + "type": "array", + "description": "One entry per checklist item from weston_guide.md applied to this submittal.", + "items": { + "type": "object", + "required": ["item", "result", "confidence"], + "properties": { + "item": { "type": "string", "description": "Checklist item text" }, + "result": { + "type": "string", + "enum": ["pass", "fail", "warn", "not_applicable", "insufficient_data"] + }, + "detail": { "type": "string", "description": "What was found (or missing)" }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Skill's confidence in this result. Results below 0.95 are reported as 'Requires PM judgment' in the Word output." + }, + "citation": { + "type": "object", + "description": "Required for any 'pass' result at PASS overall_status. Without citation, status cannot be Approved.", + "properties": { + "spec_section": { "type": "string" }, + "spec_paragraph": { "type": "string", "description": "e.g. '2.1.A.3'" }, + "page": { "type": "integer" }, + "excerpt": { "type": "string" } + } + } + } + } + }, + + "pattern_flags": { + "type": "array", + "description": "Historical patterns from project_playbook.md that apply to this submittal.", + "items": { + "type": "object", + "required": ["source", "pattern", "flag", "severity"], + "properties": { + "source": { "type": "string", "const": "playbook" }, + "pattern": { "type": "string", "description": "Pattern name, e.g. 'Samples Required Separate from Product Data'" }, + "flag": { "type": "string", "description": "Language shown to the PM" }, + "severity": { "type": "string", "enum": ["warn", "info"] } + } + } + }, + + "deviations": { + "type": "array", + "description": "Explicit deviations from spec found in the submittal.", + "items": { + "type": "object", + "properties": { + "description": { "type": "string" }, + "spec_requirement": { "type": "string" }, + "submitted_value": { "type": "string" } + } + } + }, + + "suggested_action": { + "type": "string", + "description": "Directive PM next step. Short, imperative." + }, + + "pushback_draft": { + "type": "string", + "description": "Draft language the PM can copy-paste to the sub if the submittal fails. Empty if overall_status is PASS." + }, + + "redline_suggestions": { + "type": "array", + "description": "Specific locations in the submittal the PM should consider marking up.", + "items": { + "type": "object", + "properties": { + "page": { "type": "integer" }, + "item": { "type": "string" }, + "note": { "type": "string" } + } + } + }, + + "attachments": { + "type": "array", + "description": "Files that were part of the input package.", + "items": { "type": "string" } + }, + + "citations": { + "type": "array", + "description": "All spec paragraphs referenced, deduped across checks.", + "items": { "type": "string" } + }, + + "confidence_tier": { + "type": "string", + "enum": ["green", "yellow", "red"], + "description": "green = all checks ≥95% confidence; yellow = some below; red = too many below for meaningful output" + }, + + "skill_version": { "type": "string", "description": "Version of the submittal-review skill that produced this output" }, + "run_timestamp": { "type": "string", "format": "date-time" } + } +} diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/build_checklist_docx.py b/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/build_checklist_docx.py new file mode 100644 index 0000000..f0fa320 --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/build_checklist_docx.py @@ -0,0 +1,334 @@ +""" +build_checklist_docx.py +----------------------- +Renders the skill's review.json into a Willowbrook-branded Word checklist. + +USAGE + python build_checklist_docx.py [--out ] + +NOTE + The skill's SKILL.md instructs Claude to invoke the willowbrook-brand-262 + skill directly when preparing the final .docx (per Weston's non-negotiable). + This script is the mechanical baseline — it produces a correctly structured + Word document with all fields populated. If willowbrook-brand-262 is + available in the session, Claude should apply its brand standards on top + of this output before handing to the PM. + + The script is intentionally self-contained so it works offline too. +""" + +import os +import sys +import json +import argparse +import datetime + +try: + from docx import Document + from docx.shared import Pt, RGBColor, Inches + from docx.enum.text import WD_ALIGN_PARAGRAPH + from docx.oxml import OxmlElement + from docx.oxml.ns import qn +except ImportError: + sys.exit("Missing dependency. Run: pip install python-docx") + + +# ── Willowbrook brand palette (override in brand skill if available) ───────── +NAVY = RGBColor(0x1F, 0x3A, 0x5F) +GOLD = RGBColor(0xC9, 0xA2, 0x27) +GRAY = RGBColor(0x55, 0x55, 0x55) +RED = RGBColor(0xB4, 0x1E, 0x1E) +GREEN = RGBColor(0x1E, 0x7A, 0x1E) + +CONFIDENCE_THRESHOLD = 0.95 + + +# ── Helpers ────────────────────────────────────────────────────────────────── + +def add_heading(doc, text, level=1, color=NAVY): + p = doc.add_paragraph() + r = p.add_run(text) + r.bold = True + r.font.color.rgb = color + r.font.size = {0: Pt(24), 1: Pt(15), 2: Pt(12)}.get(level, Pt(12)) + if level == 1: + p.paragraph_format.space_before = Pt(14) + p.paragraph_format.space_after = Pt(4) + elif level == 2: + p.paragraph_format.space_before = Pt(8) + p.paragraph_format.space_after = Pt(2) + return p + + +def add_kv_line(doc, key, value): + p = doc.add_paragraph() + p.paragraph_format.space_after = Pt(2) + rk = p.add_run(f"{key}: ") + rk.bold = True + rk.font.size = Pt(11) + rk.font.color.rgb = GRAY + rv = p.add_run(str(value) if value else "—") + rv.font.size = Pt(11) + return p + + +def add_check_line(doc, mark, text, color=None): + p = doc.add_paragraph() + p.paragraph_format.left_indent = Inches(0.25) + p.paragraph_format.space_after = Pt(2) + rm = p.add_run(mark + " ") + rm.font.size = Pt(12) + if color: + rm.font.color.rgb = color + rt = p.add_run(text) + rt.font.size = Pt(11) + return p + + +def add_para(doc, text, italic=False, color=None, size=11): + p = doc.add_paragraph() + r = p.add_run(text) + r.italic = italic + r.font.size = Pt(size) + if color: + r.font.color.rgb = color + return p + + +def add_hrule(doc): + p = doc.add_paragraph() + pPr = p._p.get_or_add_pPr() + pBdr = OxmlElement("w:pBdr") + bottom = OxmlElement("w:bottom") + bottom.set(qn("w:val"), "single") + bottom.set(qn("w:sz"), "6") + bottom.set(qn("w:color"), "C9A227") + pBdr.append(bottom) + pPr.append(pBdr) + + +# ── Main builder ───────────────────────────────────────────────────────────── + +def build(review: dict, out_path: str) -> None: + doc = Document() + + # Default font + style = doc.styles["Normal"] + style.font.name = "Calibri" + style.font.size = Pt(11) + + # ── DRAFT watermark header ──────────────────────────────────────────────── + p = doc.add_paragraph() + r = p.add_run("DRAFT — PM REVIEW") + r.bold = True + r.font.size = Pt(10) + r.font.color.rgb = RED + p.paragraph_format.space_after = Pt(2) + + # Title + title = review.get("product_name") or review.get("spec_section") or "Submittal Review" + sub_id = review.get("submittal_id", "") + p = doc.add_paragraph() + r = p.add_run(f"Submittal Review — {sub_id}") + r.bold = True + r.font.size = Pt(22) + r.font.color.rgb = NAVY + + p = doc.add_paragraph() + r = p.add_run(title) + r.italic = True + r.font.size = Pt(12) + r.font.color.rgb = GRAY + + add_hrule(doc) + + # ── Summary block ───────────────────────────────────────────────────────── + status = review.get("overall_status", "INSUFFICIENT_DATA") + status_colors = { + "PASS": GREEN, "CONDITIONAL": GOLD, "FAIL": RED, + "INSUFFICIENT_DATA": GRAY, + } + p = doc.add_paragraph() + rk = p.add_run("Overall Status: ") + rk.bold = True + rk.font.size = Pt(13) + rv = p.add_run(status) + rv.bold = True + rv.font.size = Pt(13) + rv.font.color.rgb = status_colors.get(status, GRAY) + + add_kv_line(doc, "Project", review.get("project_id", "")) + add_kv_line(doc, "Spec Section", review.get("spec_section", "")) + add_kv_line(doc, "CSI Division", review.get("csi_division", "")) + add_kv_line(doc, "Type", review.get("type", "")) + add_kv_line(doc, "Contractor", review.get("contractor", "")) + add_kv_line(doc, "Submittal Package", review.get("package_number", "")) + add_kv_line(doc, "Manufacturer", review.get("manufacturer", "")) + add_kv_line(doc, "Model Number", review.get("model_number", "")) + add_kv_line(doc, "Confidence Tier", review.get("confidence_tier", "unknown")) + add_kv_line(doc, "Recommended Response", + review.get("submittal_status_recommended", "—")) + + add_hrule(doc) + + # ── Conformance checks ──────────────────────────────────────────────────── + add_heading(doc, "Conformance Checks", level=1) + add_para(doc, + f"Items below confidence threshold ({int(CONFIDENCE_THRESHOLD*100)}%) " + f"are marked 'Requires PM judgment'.", + italic=True, color=GRAY, size=10) + + checks = review.get("conformance_checks", []) + if not checks: + add_para(doc, "No conformance checks were run (insufficient data).", + italic=True, color=GRAY) + else: + for c in checks: + conf = c.get("confidence", 0) + result = c.get("result", "insufficient_data") + item = c.get("item", "") + detail = c.get("detail", "") + citation = c.get("citation") or {} + + if conf < CONFIDENCE_THRESHOLD: + mark, color, prefix = "◻", GRAY, "Requires PM judgment — " + elif result == "pass": + mark, color, prefix = "✓", GREEN, "" + elif result == "fail": + mark, color, prefix = "✗", RED, "" + elif result == "warn": + mark, color, prefix = "⚠", GOLD, "" + elif result == "not_applicable": + mark, color, prefix = "–", GRAY, "N/A — " + else: + mark, color, prefix = "◻", GRAY, "" + + line = prefix + item + if detail: + line += f" ({detail})" + add_check_line(doc, mark, line, color=color) + + # Citation under the check, if present + if citation.get("spec_paragraph") or citation.get("excerpt"): + sp = doc.add_paragraph() + sp.paragraph_format.left_indent = Inches(0.6) + sp.paragraph_format.space_after = Pt(4) + sr = sp.add_run("Citation: ") + sr.italic = True + sr.font.size = Pt(9) + sr.font.color.rgb = GRAY + cite_text = [] + if citation.get("spec_section"): + cite_text.append(citation["spec_section"]) + if citation.get("spec_paragraph"): + cite_text.append(f"¶ {citation['spec_paragraph']}") + if citation.get("page"): + cite_text.append(f"p. {citation['page']}") + if citation.get("excerpt"): + cite_text.append(f"\"{citation['excerpt']}\"") + sr2 = sp.add_run(" ".join(cite_text)) + sr2.italic = True + sr2.font.size = Pt(9) + sr2.font.color.rgb = GRAY + + add_hrule(doc) + + # ── Pattern flags ───────────────────────────────────────────────────────── + add_heading(doc, "Historical Pattern Flags", level=1) + flags = review.get("pattern_flags", []) + if not flags: + add_para(doc, "No historical patterns flagged for this submittal.", + italic=True, color=GRAY) + else: + for f in flags: + sev = f.get("severity", "info") + mark = "⚠" if sev == "warn" else "ℹ" + color = GOLD if sev == "warn" else NAVY + text = f"{f.get('pattern', '')} — {f.get('flag', '')}" + add_check_line(doc, mark, text, color=color) + + add_hrule(doc) + + # ── Deviations ──────────────────────────────────────────────────────────── + deviations = review.get("deviations", []) + if deviations: + add_heading(doc, "Declared Deviations from Spec", level=1) + for d in deviations: + add_check_line( + doc, "→", + f"{d.get('description', '')} " + f"(spec: {d.get('spec_requirement', '?')}; " + f"submitted: {d.get('submitted_value', '?')})", + color=NAVY, + ) + add_hrule(doc) + + # ── Suggested action (directive) ────────────────────────────────────────── + add_heading(doc, "Suggested PM Action", level=1) + add_para(doc, review.get("suggested_action") or + "Review items above, confirm completeness, then forward to architect.", + size=12) + + # ── Pushback draft ──────────────────────────────────────────────────────── + pushback = review.get("pushback_draft") + if pushback: + add_heading(doc, "Draft Pushback Language (to Sub)", level=1) + add_para(doc, + "DRAFT — review and edit before sending. Skill does not auto-send.", + italic=True, color=RED, size=10) + add_para(doc, pushback, size=11) + + # ── Redline suggestions ─────────────────────────────────────────────────── + redlines = review.get("redline_suggestions", []) + if redlines: + add_heading(doc, "Suggested Redlines", level=1) + for r in redlines: + page_txt = f"p. {r.get('page')}" if r.get("page") else "" + add_check_line( + doc, "✎", + f"{page_txt} {r.get('item', '')} — {r.get('note', '')}".strip(), + color=GOLD, + ) + + add_hrule(doc) + + # ── Footer ──────────────────────────────────────────────────────────────── + doc.add_paragraph() + ts = review.get("run_timestamp") or datetime.datetime.now().isoformat(timespec="seconds") + ver = review.get("skill_version", "v1") + ft = doc.add_paragraph() + fr = ft.add_run( + f"Willowbrook Submittal Review Skill {ver} | Generated {ts} | " + f"DRAFT — PM is the publish button." + ) + fr.italic = True + fr.font.size = Pt(8) + fr.font.color.rgb = GRAY + + os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True) + doc.save(out_path) + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("review_json") + ap.add_argument("--out", default=None) + args = ap.parse_args() + + with open(args.review_json, "r", encoding="utf-8") as f: + review = json.load(f) + + out_path = args.out + if not out_path: + stem = os.path.splitext(os.path.basename(args.review_json))[0] + out_path = os.path.join( + os.path.dirname(os.path.abspath(args.review_json)), + f"{stem}.docx", + ) + + build(review, out_path) + print(f"Wrote {out_path}") + + +if __name__ == "__main__": + main() diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/detect_input.py b/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/detect_input.py new file mode 100644 index 0000000..ef86aeb --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/detect_input.py @@ -0,0 +1,88 @@ +""" +detect_input.py +--------------- +Inspects a path supplied by the PM and decides whether it's a single PDF +or a Procore/Fieldwire-style submittal folder. + +Returns a JSON object Claude can read to drive the rest of the workflow. + +USAGE + python detect_input.py "" + +OUTPUT (stdout, single line of JSON) + { + "kind": "pdf" | "folder" | "invalid", + "input_path": "", + "cover_sheet": "", + "attachments": [""], + "detected_submittal_num": "157.1" (best-effort from the folder name) + } +""" + +import os +import re +import sys +import json + + +def detect(path: str) -> dict: + path = os.path.abspath(path.strip().strip('"')) + result = {"kind": "invalid", "input_path": path} + + if not os.path.exists(path): + result["error"] = "Path does not exist" + return result + + if os.path.isfile(path) and path.lower().endswith(".pdf"): + result["kind"] = "pdf" + result["attachments"] = [] + m = re.match(r"(\d+)[._](\d+)", os.path.basename(path)) + if m: + result["detected_submittal_num"] = f"{m.group(1)}.{m.group(2)}" + return result + + if os.path.isdir(path): + result["kind"] = "folder" + folder_name = os.path.basename(path) + + # Cover sheet: filename (stem) matches folder name + cover_sheet = None + attachments = [] + for f in sorted(os.listdir(path)): + full = os.path.join(path, f) + if not os.path.isfile(full): + continue + if not f.lower().endswith(".pdf"): + continue + stem = os.path.splitext(f)[0] + if stem == folder_name: + cover_sheet = full + else: + attachments.append(full) + + if cover_sheet is None and attachments: + # Fall back to the first PDF if no match found + cover_sheet = attachments[0] + attachments = attachments[1:] + + result["cover_sheet"] = cover_sheet + result["attachments"] = attachments + + m = re.match(r"(\d+)_(\d+)_(.+)", folder_name) + if m: + result["detected_submittal_num"] = f"{m.group(1)}.{m.group(2)}" + + return result + + result["error"] = "Path is neither a PDF nor a folder" + return result + + +def main(): + if len(sys.argv) < 2: + sys.exit("Usage: python detect_input.py ") + print(json.dumps(detect(sys.argv[1]), indent=2)) + + +if __name__ == "__main__": + main() diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/extract_spec_book.py b/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/extract_spec_book.py new file mode 100644 index 0000000..6695390 --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/extract_spec_book.py @@ -0,0 +1,199 @@ +""" +extract_spec_book.py +-------------------- +Splits a construction spec book PDF into one .txt file per CSI section, +plus a `_manifest.json` mapping section numbers to files and metadata. + +Non-interactive. Safe to call programmatically from the skill OR run +directly from the command line. + +USAGE + # As a CLI + python extract_spec_book.py [--out ] [--csv ] + + # From Python + from extract_spec_book import extract + manifest = extract(pdf_path, output_dir, csv_path=None) + +OUTPUT + / + 00_0001_project-directory.txt + 01_3300_submittal-procedures.txt + 03_3000_cast-in-place-concrete.txt + ... + _manifest.json <- { "03 3000": {file, description, pages}, ... } + +Descended from the working Stillwater extractor; interactive prompts removed +so Claude and other callers can drive it without stdin. +""" + +from __future__ import annotations + +import re +import sys +import csv +import json +import argparse +from pathlib import Path + +try: + import pypdf +except ImportError: + sys.exit("Missing dependency. Run: pip install pypdf") + + +# ── Section number patterns ────────────────────────────────────────────────── + +# "03 3300- 19" / "03 3300 - 19" / "00 10 00-1" at end of a line +HEADER_SECTION_RE = re.compile( + r"(\d{2}[\s]\d{4}|\d{2}[\s]\d{2}[\s]\d{2})\s*-\s*\d+\s*$" +) + +# Fallback: copyright-line section number +COPYRIGHT_SECTION_RE = re.compile( + r"(\d{2}[\s]?\d{2}[\s]?\d{2})\s*-\s*\d+\s*$" +) + + +def normalize_section_num(raw: str) -> str: + return " ".join(raw.split()) + + +def detect_section(lines: list[str]) -> str | None: + if lines: + m = HEADER_SECTION_RE.search(lines[0]) + if m: + return normalize_section_num(m.group(1)) + for line in lines[:5]: + m = COPYRIGHT_SECTION_RE.search(line) + if m: + return normalize_section_num(m.group(1)) + return None + + +def load_descriptions(csv_path: Path | None) -> dict[str, str]: + """Build a section_number → description map from Specifications Log.csv.""" + descriptions: dict[str, str] = {} + if not csv_path or not csv_path.exists(): + return descriptions + with open(csv_path, newline="", encoding="utf-8-sig") as f: + reader = csv.DictReader(f) + for row in reader: + num = normalize_section_num(row.get("Number", "")) + desc = (row.get("Description", "") or "").strip() + if num and desc: + descriptions[num] = desc + descriptions[num.replace(" ", "")] = desc + return descriptions + + +def safe_filename(section_num: str, description: str) -> str: + num_part = section_num.replace(" ", "_") + desc_part = re.sub(r"[^\w\s-]", "", description).strip() + desc_part = re.sub(r"[\s]+", "-", desc_part).lower()[:60] + if desc_part: + return f"{num_part}_{desc_part}.txt" + return f"{num_part}.txt" + + +# ── Main extraction ────────────────────────────────────────────────────────── + +def extract( + pdf_path: str | Path, + output_dir: str | Path, + csv_path: str | Path | None = None, + verbose: bool = False, +) -> dict: + """ + Extract spec sections from a PDF into one .txt per section. + + Returns the manifest dict: { section_num: {file, description, pages}, ... } + Also writes `_manifest.json` into output_dir. + """ + pdf_path = Path(pdf_path) + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + if csv_path is None: + # Default: look for Specifications Log.csv next to the PDF + candidate = pdf_path.parent / "Specifications Log.csv" + if candidate.exists(): + csv_path = candidate + descriptions = load_descriptions(Path(csv_path) if csv_path else None) + if verbose: + if descriptions: + print(f"Loaded {len(descriptions)} section descriptions from CSV.") + else: + print("No Specifications Log.csv found — files named by section number only.") + + if verbose: + print(f"Reading {pdf_path.name}...") + reader = pypdf.PdfReader(str(pdf_path)) + + sections: dict[str, list[str]] = {} + section_order: list[str] = [] + unknown = 0 + + for i, page in enumerate(reader.pages): + text = page.extract_text() or "" + lines = [l.strip() for l in text.split("\n") if l.strip()] + sec = detect_section(lines) + if sec is None: + unknown += 1 + sec = "_unknown" + if sec not in sections: + sections[sec] = [] + section_order.append(sec) + sections[sec].append(text) + + if verbose and unknown: + print(f" {unknown} unmatched pages (blank/image) — skipped.") + + manifest: dict[str, dict] = {} + for sec in section_order: + if sec == "_unknown": + continue + desc = descriptions.get(sec) or descriptions.get(sec.replace(" ", ""), "") + filename = safe_filename(sec, desc) + full_text = "\n\n--- PAGE BREAK ---\n\n".join(sections[sec]) + (output_dir / filename).write_text(full_text, encoding="utf-8") + manifest[sec] = { + "file": filename, + "description": desc, + "pages": len(sections[sec]), + } + + (output_dir / "_manifest.json").write_text( + json.dumps(manifest, indent=2), encoding="utf-8" + ) + if verbose: + print(f"Wrote {len(manifest)} sections + manifest to {output_dir}") + return manifest + + +# ── CLI ───────────────────────────────────────────────────────────────────── + +def main(): + ap = argparse.ArgumentParser( + description="Extract CSI sections from a spec book PDF." + ) + ap.add_argument("pdf", help="Path to the spec book PDF") + ap.add_argument("--out", default=None, + help="Output folder (default: 'Extracted Sections' next to the PDF)") + ap.add_argument("--csv", default=None, + help="Path to Specifications Log.csv (default: look next to PDF)") + ap.add_argument("--quiet", action="store_true") + args = ap.parse_args() + + pdf_path = Path(args.pdf).expanduser() + if not pdf_path.exists(): + sys.exit(f"Not found: {pdf_path}") + + output_dir = Path(args.out) if args.out else pdf_path.parent / "Extracted Sections" + csv_path = Path(args.csv) if args.csv else None + + extract(pdf_path, output_dir, csv_path=csv_path, verbose=not args.quiet) + + +if __name__ == "__main__": + main() diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/parse_submittal.py b/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/parse_submittal.py new file mode 100644 index 0000000..ab1b49c --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/parse_submittal.py @@ -0,0 +1,256 @@ +""" +parse_submittal.py +------------------ +Extracts text and best-effort structured metadata from a submittal PDF or folder. + +USAGE + python parse_submittal.py [--out ] + +OUTPUT + JSON written to stdout (or --out file) with: + - kind: "pdf" | "folder" + - cover_sheet_text: full extracted text of the cover sheet + - attachment_texts: dict of {filename: full_extracted_text} + - extracted_metadata: {submittal_num, revision, spec_section, type, + response, reviewer, comments[], contractor, + package, date_created, date_returned} + +Depends on PyMuPDF (`pip install pymupdf`). +Uses the same regex heuristics as extract_cover_sheets.py so it handles +both Procore layout variants. +""" + +import os +import re +import sys +import json +import argparse + +try: + import fitz # PyMuPDF +except ImportError: + sys.exit("Missing dependency. Run: pip install pymupdf") + +# Allow importing detect_input.py from the same folder +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from detect_input import detect # noqa: E402 + + +# ── PDF text extraction ────────────────────────────────────────────────────── + +def extract_pdf_text(pdf_path: str) -> str: + doc = fitz.open(pdf_path) + pages = [] + for i, page in enumerate(doc, start=1): + text = page.get_text() or "" + pages.append(f"--- Page {i} ---\n{text}") + doc.close() + return "\n\n".join(pages) + + +# ── Cover-sheet parsing (handles both Procore layouts) ─────────────────────── + +def _extract_response(text: str) -> str: + for p in ["Revise and Resubmit", "Reviewed As Noted", "Reviewed"]: + if p in text: + return p + return "" + + +def _extract_reviewer(text: str) -> str: + m = re.search( + r"([\w][\w\s]+?)\s*\n\s*\(505 Architects\)\s*\n\s*" + r"(Reviewed As Noted|Revise and Resubmit|Reviewed)", text) + if m: + return m.group(1).strip() + m = re.search( + r"([\w][\w ]+?)\s*\(505 Architects\)\s+" + r"(Reviewed As Noted|Revise and Resubmit|Reviewed)", text) + if m: + return m.group(1).strip() + m = re.search(r"Approvers[^\n]*\n?\s*(.+?)\s*\(505 Architects\)", text) + if m: + return m.group(1).strip().split(",")[-1].strip() + return "" + + +def _extract_comments(text: str) -> list: + comments = re.findall(r"^\s*Comment\s*\n\s*(.+?)[ \t]*\n", text, re.MULTILINE) + comments += re.findall(r"^\s*Comment\s*\n([\s\S]+?)(?:\n\s*\n|\nPage \d|$)", + text, re.MULTILINE) + seen, out = set(), [] + for c in comments: + c = c.strip() + if c and c not in seen: + seen.add(c) + out.append(c) + return out + + +def _extract_spec_section(text: str) -> str: + # Layout B + m = re.search(r"^\s*Spec Section\s*\n\s*(.+?)[ \t]*\n", text, re.MULTILINE) + if m and re.search(r"\d{2}\s*\d{4}", m.group(1)): + return m.group(1).strip() + # Layout A + m = re.search(r"Spec Section\s+(.+?)(?:\s{2,}|\t|\n|$)", text) + if m and re.search(r"\d{2}\s*\d{4}", m.group(1)): + return m.group(1).strip() + # Subtitle fallback + m = re.search(r"Submittal #[\d.]+ - .+?\n\s*(.+?)\s*\n", text) + if m and re.search(r"\d{2}\s*\d{4}", m.group(1)): + return m.group(1).strip() + return "" + + +def _extract_type(text: str) -> str: + m = re.search(r"\nType\s*\n\s*(.+?)[ \t]*\n", text) + if m and m.group(1).strip() in ( + "Shop Drawing", "Product Information", "Sample", "Calculation", + "Test Report", "Warranty", "Certificate", "Other", + ): + return m.group(1).strip() + m = re.search(r"Location\s+Type\s+(.+?)(?:\s{2,}|$)", text, re.MULTILINE) + if m: + return m.group(1).strip() + m = re.search(r"\bType\s+(Shop Drawing|Product Information|Sample|" + r"Calculation|Test Report|Warranty|Certificate|Other)\b", text) + if m: + return m.group(1) + return "" + + +def _extract_contractor(text: str) -> str: + m = re.search(r"Responsible\s*\nContractor\s*\n\s*(.+?)[ \t]*\n", text) + if m: + return m.group(1).strip() + m = re.search(r"Responsible\s+(.+?)(?:\s{2,}|Received From|\n)", text) + if m: + val = m.group(1).strip() + if val and "Contractor" not in val: + return val + return "" + + +def _extract_package(text: str) -> str: + m = re.search(r"Submittal Package\s*\n\s*(.+?)[ \t]*\n", text) + if m: + return m.group(1).strip() + m = re.search(r"Submittal Package\s+(.+?)(?:\s{2,}|\n|$)", text, re.MULTILINE) + if m: + return m.group(1).strip() + return "" + + +def _extract_date_created(text: str) -> str: + m = re.search(r"Date Created\s*\n\s*(.+?)[ \t]*\n", text) + if m: + return m.group(1).strip() + m = re.search(r"Date Created\s+(\w+ \d+, \d{4})", text) + if m: + return m.group(1).strip() + return "" + + +def _extract_date_returned(text: str) -> str: + m = re.search( + r"(\w+ \d+, \d{4})\s*\n\s*(Reviewed As Noted|Revise and Resubmit|Reviewed)\b", + text) + if m: + return m.group(1).strip() + m = re.search( + r"(\w+ \d+, \d{4})\s+(Reviewed As Noted|Revise and Resubmit|Reviewed)\b", + text) + if m: + return m.group(1).strip() + return "" + + +def _extract_submittal_num(text: str, fallback: str = "") -> tuple: + m = re.search(r"Submittal #(\d+)\.(\d+)", text) + if m: + return f"{m.group(1)}.{m.group(2)}", int(m.group(2)) + if fallback: + m = re.match(r"(\d+)\.(\d+)", fallback) + if m: + return fallback, int(m.group(2)) + return "", 0 + + +def parse_cover_sheet_text(text: str, submittal_num_hint: str = "") -> dict: + """Return structured metadata extracted from cover-sheet text.""" + sub_num, rev = _extract_submittal_num(text, submittal_num_hint) + return { + "submittal_num": sub_num, + "revision": rev, + "spec_section": _extract_spec_section(text), + "type": _extract_type(text), + "response": _extract_response(text), + "reviewer": _extract_reviewer(text), + "comments": _extract_comments(text), + "contractor": _extract_contractor(text), + "package": _extract_package(text), + "date_created": _extract_date_created(text), + "date_returned": _extract_date_returned(text), + } + + +# ── Main entry ─────────────────────────────────────────────────────────────── + +def parse(path: str) -> dict: + info = detect(path) + if info["kind"] == "invalid": + return {"error": info.get("error", "Invalid input"), "detect": info} + + result = { + "kind": info["kind"], + "input_path": info["input_path"], + "cover_sheet_text": "", + "attachment_texts": {}, + "extracted_metadata": {}, + } + + if info["kind"] == "pdf": + text = extract_pdf_text(info["input_path"]) + result["cover_sheet_text"] = text + result["extracted_metadata"] = parse_cover_sheet_text( + text, info.get("detected_submittal_num", "")) + return result + + # Folder + cover = info.get("cover_sheet") + if cover: + text = extract_pdf_text(cover) + result["cover_sheet_text"] = text + result["extracted_metadata"] = parse_cover_sheet_text( + text, info.get("detected_submittal_num", "")) + + for att_path in info.get("attachments", []): + try: + result["attachment_texts"][os.path.basename(att_path)] = \ + extract_pdf_text(att_path) + except Exception as e: + result["attachment_texts"][os.path.basename(att_path)] = \ + f"[extraction failed: {e}]" + + return result + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("path") + ap.add_argument("--out", default=None, help="Write JSON to this file instead of stdout") + args = ap.parse_args() + + data = parse(args.path) + text = json.dumps(data, indent=2, ensure_ascii=False) + if args.out: + with open(args.out, "w", encoding="utf-8") as f: + f.write(text) + print(f"Wrote {args.out}") + else: + print(text) + + +if __name__ == "__main__": + main() diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/spec_lookup.py b/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/spec_lookup.py new file mode 100644 index 0000000..08212ea --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/scripts/spec_lookup.py @@ -0,0 +1,199 @@ +""" +spec_lookup.py +-------------- +Given a project_spec_path (folder of extracted sections OR a raw spec-book PDF), +looks up the text of a specific CSI section for use by the submittal-review +skill. + +If given a PDF, transparently extracts it to '/Extracted Sections/' +the first time, then reuses the cached output on subsequent runs. + +USAGE + # CLI + python spec_lookup.py + # e.g. python spec_lookup.py ".../Spec Book" "10 22 39" + + # Python + from spec_lookup import resolve_spec_section + section = resolve_spec_section(project_spec_path, "10 22 39") + section["text"] # full spec text + section["file"] # absolute path to the .txt + section["description"] # from the CSV / manifest + section["page_breaks"] # list of indices where "--- PAGE BREAK ---" occurs + +Normalizes section numbers so "10 22 39", "10 2239", "102239", and +"10 22 39 - Folding Panel Partitions" all resolve to the same section. +""" + +from __future__ import annotations + +import os +import re +import sys +import json +import argparse +from pathlib import Path + +# Allow relative import when run from the skill's scripts folder +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from extract_spec_book import extract # noqa: E402 + + +# ── Normalization ──────────────────────────────────────────────────────────── + +def canonical_section(raw: str) -> str: + """ + Normalize a user-supplied section ref to a canonical key. + 'Spec Section: 10 22 39 - Folding Panel Partitions' -> '10 22 39' + '10 2239' -> '10 22 39' + '102239' -> '10 22 39' + '08 7100' -> '08 71 00' + """ + # Pull out the first digit-and-space pattern + m = re.search(r"(\d{2})[\s.]?(\d{2})[\s.]?(\d{2})", raw) + if m: + return f"{m.group(1)} {m.group(2)} {m.group(3)}" + # Fallback: 6-digit run + m = re.search(r"(\d{2})\s?(\d{4})", raw) + if m: + four = m.group(2) + return f"{m.group(1)} {four[:2]} {four[2:]}" + return raw.strip() + + +def canonical_keys(raw: str) -> list[str]: + """Possible canonical forms a manifest could key by.""" + c = canonical_section(raw) + parts = c.split() + out = {c} + if len(parts) == 3: + out.add(f"{parts[0]} {parts[1]}{parts[2]}") # '10 2239' + out.add(f"{parts[0]}{parts[1]}{parts[2]}") # '102239' + return list(out) + + +# ── Path resolution ────────────────────────────────────────────────────────── + +def _resolve_extracted_folder(project_spec_path: Path) -> Path: + """ + Given either a folder of extracted sections or a raw spec PDF, + return the folder that has (or will have) the extracted .txt files + and _manifest.json. + """ + if project_spec_path.is_dir(): + # Could be either the "Spec Book" folder (with PDF + Extracted Sections/) + # or the "Extracted Sections" folder itself. + manifest_here = project_spec_path / "_manifest.json" + if manifest_here.exists(): + return project_spec_path + nested = project_spec_path / "Extracted Sections" + if (nested / "_manifest.json").exists(): + return nested + # Look for any PDF to extract from + pdfs = list(project_spec_path.glob("*.pdf")) + if pdfs: + extracted = project_spec_path / "Extracted Sections" + extract(pdfs[0], extracted, verbose=False) + return extracted + # Empty directory — force caller to re-point + raise FileNotFoundError( + f"No _manifest.json and no PDF found in {project_spec_path}" + ) + + if project_spec_path.is_file() and project_spec_path.suffix.lower() == ".pdf": + extracted = project_spec_path.parent / "Extracted Sections" + if not (extracted / "_manifest.json").exists(): + extract(project_spec_path, extracted, verbose=False) + return extracted + + raise FileNotFoundError(f"Not a spec folder or PDF: {project_spec_path}") + + +# ── Main API ───────────────────────────────────────────────────────────────── + +def resolve_spec_section(project_spec_path: str | Path, section: str) -> dict | None: + """ + Return dict with {section, description, file, text, page_breaks} + or None if the section is not in the spec book. + """ + project_spec_path = Path(project_spec_path).expanduser() + extracted = _resolve_extracted_folder(project_spec_path) + manifest_path = extracted / "_manifest.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + + keys = canonical_keys(section) + entry = None + matched_key = None + for k in keys: + if k in manifest: + entry = manifest[k] + matched_key = k + break + + if entry is None: + # Try loose match: any manifest key whose canonical form equals ours + our_canon = canonical_section(section) + for mk, me in manifest.items(): + if canonical_section(mk) == our_canon: + entry = me + matched_key = mk + break + + if entry is None: + return None + + text_path = extracted / entry["file"] + text = text_path.read_text(encoding="utf-8") if text_path.exists() else "" + # Indices of page-break markers — handy for page citations later + pb_indices = [i for i, line in enumerate(text.splitlines()) + if line.strip() == "--- PAGE BREAK ---"] + + return { + "section": matched_key, + "description": entry.get("description", ""), + "file": str(text_path), + "pages": entry.get("pages", 0), + "text": text, + "page_breaks": pb_indices, + } + + +def list_sections(project_spec_path: str | Path) -> dict: + """Return the full manifest — caller can browse available sections.""" + project_spec_path = Path(project_spec_path).expanduser() + extracted = _resolve_extracted_folder(project_spec_path) + return json.loads((extracted / "_manifest.json").read_text(encoding="utf-8")) + + +# ── CLI ───────────────────────────────────────────────────────────────────── + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("project_spec_path", + help="Folder with extracted sections, or a spec-book PDF") + ap.add_argument("section", + help="CSI section — e.g. '10 22 39' or '10 2239'") + ap.add_argument("--text-only", action="store_true", + help="Print only the spec text") + args = ap.parse_args() + + result = resolve_spec_section(args.project_spec_path, args.section) + if result is None: + sys.exit(f"Section '{args.section}' not found in spec book.") + + if args.text_only: + print(result["text"]) + return + + print(json.dumps({ + "section": result["section"], + "description": result["description"], + "file": result["file"], + "pages": result["pages"], + "page_break_count": len(result["page_breaks"]), + "text_length": len(result["text"]), + }, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/_157_1_content.txt b/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/_157_1_content.txt new file mode 100644 index 0000000..f771f00 --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/_157_1_content.txt @@ -0,0 +1,539 @@ +=== 10 2239 Operable Partitions R1 - SD.pdf === +--- Page 1 --- +Submittal Cover Sheet +* Submials not submied as outlined below will not be reviewed but marked as revise and resubmit. Submials must be submied with this +cover sheet, per spec, and with types/models outlined in red. Submials are to be divided as separate files or as a single file with each submial +type and spec secon having its own corresponding cover sheet. Submials for each spec secon to be separated and idenfied as Product +Data/Other Info, Licenses, Shop Drawings, and Samples. +Project: +__________________________________ +Company: +__________________________________ +Date: +__________________________________ +Submial/Spec Title: +__________________________________________________________________ +Submial Revision #: +__________________________________________________________________ +Spec Secon # (00 0000.00): __________________________________________________________________ +Subcontractor Comments: __________________________________________________________________ +__________________________________________________________________________________________ + + + + + + +Contractor +Comments +Architect +Comments +Engineer +Comments +Contractor +Stamp +Architect +Stamp +Engineer +Stamp +Submittal Review +This submittal has been reviewed for general compliance with the +construction documents. The submittal review process does not +relieve the subcontractor of the responsibility to verify and provide +the correct: quantities, dimensions, details, layouts, and incidentals +per the construction documents and onsite project conditions. +Reviewed By: Zac Hansen Date: 11/6/2024 + REVIEWED + REVIEWED AS NOTED + REVISE & RESUBMIT + REJECTED +11/07/2024 +Jeff Thomas +505 Architects LLC +X +SHS +The Best Companies +11/6/2024 +Operable Partitions R1 - SD +1 +10 2239 + + +--- Page 2 --- +ELA Classrooms +2183/2195 +REVISED +REVISED +9:10 AM, November 6, 2024 +9:10 AM, November 6, 2024 +4'-0" PKT BEING +PROVIDED. RE: +A145 PR#05 +5'-7 1/4" +PROVIDE SMOKE GREY +FOR TRIM AND HINGE + + +--- Page 3 --- + + +--- Page 4 --- + + +--- Page 5 --- +10'-0" FINISHED +CEILING HEIGHT + + +--- Page 6 --- + + +--- Page 7 --- +10'-0" FINISHED +CEILING HEIGHT +5'-7 1/4" + + +--- Page 8 --- +ELA Classrooms +2197/2199 +REVISED +REVISED +9:10 AM, November 6, 2024 +9:10 AM, November 6, 2024 +4'-0" PKT BEING +PROVIDED. RE: +A145 PR#05 +5'-7 1/4" +PROVIDE SMOKE GREY +FOR TRIM AND HINGE + + +--- Page 9 --- + + +--- Page 10 --- + + +--- Page 11 --- +10'-0" FINISHED +CEILING HEIGHT + + +--- Page 12 --- + + +--- Page 13 --- +10'-0" FINISHED +CEILING HEIGHT +5'-7 1/4" + + +--- Page 14 --- +S +h +e +r +w +i +n + +W +il +li +a +m +s + +W +h +it +e +S +h +e +r +w +i +n + +W +il +l +i +a +m +s + +S +m +o +k +e + +G +r +a +y +S +h +e +r +w +i +n + +W +il +l +i +a +m +s + +N +a +t +u +r +a +l + +C +h +o +i +c +e +S +h +e +r +w +i +n + +W +i +l +li +a +m +s + +D +a +r +k + +B +r +o +n +z +e +S +h +e +r +w +i +n + +W +i +l +li +a +m +s + +B +l +a +c +k +TRIM COLOR OPTIONS +Smoke Gray, Natural Choice, Dark Bronze, or Black +Selection impacts sweeps, seals, pull handles, +eraser trays, hinges, and worksurface trim. +Selection will be uniform throughout the wall. +HINGE COLOR OPTIONS +White, Smoke Gray, Natural Choice, Dark Bronze, or Black +Selection will be uniform throughout the wall. +Form No. 2240 5/23 +800-869-9685 +TRIM & HINGE +COLOR SELECTOR +info@modernfold.com +modernfold.com +PROVIDE SMOKE GREY +FOR TRIM AND HINGE + + +--- Page 15 --- +Adrift +512 (S) 548 (H) +Oats +518 (S) 554 (H) +Bobbin Weave +524 (S) 560 (H) +Reed +513 (S) 549 (H) +Prairie +519 (S) 555 (H) +Threads +525 (S) 561 (H) +Sandalwood +514 (S) 550 (H) +Reed +520 (S) 556 (H) +Common Thread +526 (S) 562 (H) +Slate +516 (S) 552 (H) +Feather Grass +522 (S) 558 (H) +Lustre +528 (S) 564 (H) +Lotus +511 (S) 547 (H) +Pumila Grass +517 (S) 553 (H) +Canvas +523 (S) 559 (H) +Veranda +515 (S) 551 (H) +Red Oat +521 (S) 557 (H) +Quill +527 (S) 563 (H) +White +529 (S) 565 (H) +Arctic +535 (S) 571 (H) +B. White +541 (S) 577 (H) +Silver Dust +530 (S) 566 (H) +Serenity +536 (S) 572 (H) +Eggshell +542 (S) 578 (H) +Cirrus +531 (S) 567 (H) +Grey Pearl +537 (S) 573 (H) +Frost Taupe +543 (S) 579 (H) +Camel Down +532 (S) 568 (H) +Willet +538 (S) 574 (H) +Aspen +544 (S) 580 (H) +Cimmerin +533 (S) 569 (H) +Windrift +539 (S) 575 (H) +Denver +545 (S) 581 (H) +Carbon +534 (S) 570 (H) +Hemp +540 (S) 576 (H) +Black +546 (S) 582 (H) +Standard Len-Tex® Vinyl Offering +Standard Len-Tex® Vinyl Offering +Displayed samples feature a standard weight 20-oz. (S). Also available in a heavy-duty 30-oz. construction (H). +Note: Each digital swatch is a digital color presentation of a standard color and is subject to minor shade variations. +Lennon Grass +Arani Silk +Loominous +Soraya +Coronado +Emboss LT Suede + + +--- Page 16 --- +VINYL SELECTOR +Len-Tex® Vinyl Wallcovering Finish Specifi cations +Form: 2243 1/22 +800-869-9685 | info@modernfold.com | www.modernfold.com +215 West New Rd. | Greenfi eld, IN 46140 +Federal Specifi cations: +Len-Tex Clean Vinyl Wallcovering meets or exceeds all requirements of W-101 (20 oz), W-101, Type III (30 oz.) Quality +Standard for Polymer Coated Fabric Wallcovering and EN 15102, European Standard for Decorative Wallcoverings. This +product has been accepted for use by the City of New York Dept. of Buildings under MEA-381-93M. +Fire Hazard Classifi cation Results: +ASTM E-84 Tunnel Test: Class A, Flame Spread: 15; Smoke Developed: 10 (20 oz.) +ASTM E-84 Tunnel Test: Class A, Flame Spread: 20; Smoke Developed: 65 (30 oz.) +PASS rating as tested under NFPA 286 (Corner burn test) +CAN/ ULC S102-10 Fire Test: Class A, Flame Spread: 15; Smoke Developed: 75 +EN 13501-1 Fire Classifi cation: B-s2, d0 +Performance Features: +Len-Tex Wallcoverings use Clean Vinyl Technology™ +Phthalate-free +Alumina trihydrate fi re retardant (antimony-free) +Ultra-Fresh® antimicrobial (non-arsenate) +No heavy metals or formaldehyde +Printed exclusively with water-based, low VOC AQUA-CLEAR™ inks +Roller applied AQUA-CLEAR 3.0™ top fi nish for improved stain resistance +Microventing for permeability is available on a custom order basis +Advanced Warning Eff ect (ionization-type smoke detector) +Five year warranty against materials defects – Consult your distributor for details +Environmental Attributes: +Ultra-low emitting – Pass rating under CA 01350 +Listed by ZeroDocs and Sustainable Minds® for California CHPS (Collaborative for High Performance Schools) SCS Indoor +Advantage™ Gold certifi ed +CA Prop 65 compliant +Published Health Product Declaration (HPD) – LEED eligible +Published Len-Tex Environmental Product Declaration (EPD) – LEED eligible +GreenCircle Certifi ed™ +Find Len-Tex products listed on: +Ecomedes (ecomedes.com), SCS Global Services website (scsglobalservices.com), GreenCircle Certifi ed website +(greencirclecertifi ed.com), Mortarr (mortarr.com) +Permeability, Moisture and Mold: +Vinyl wallcovering can act as a vapor barrier and consequently should NOT be installed on walls that are susceptible +to excessive condensation or moisture infi ltration. To reduce the risk of fungal growth, Len-Tex recommends that +all wallcovering(s) be microvented when installed in Humid and Fringe Zone 1 climates.(ref: ASHRAE Handbook of +Fundamentals, Chapter 21, Figure 16). Please see full details on microventing at our website, lentexwallcoverings.com. +LEN-TEX® + + +U.S. Units + Metric Units +Total Weight: +20.0 oz./LY 612 G/LM + 13.2 oz./SY + 442 G/SM +Fabric Weight:: 2.5 oz./LY + 85 G/LM +Vinyl Weight: 17.5 oz./LY + 520 G/LM +Material Width: 53/54” 134/137 cm +Fabric Type: +Poly Cotton Osnaburg + + +U.S. Units + Metric Units +Total Weight: +30.0 oz./LY 925 G/LM + 20.0 oz./SY + 680 G/SM +Fabric Weight:: 5.5 oz./LY + 170 G/LM +Vinyl Weight: 24.5 oz./LY + 756 G/LM +Material Width: 53/54” 134/137 cm +Fabric Type: +Poly Cotton Drill + + +=== 10 2239 Operable Partitions R1 - SD_1.pdf === +--- Page 1 --- +Submittal Cover Sheet +* Submials not submied as outlined below will not be reviewed but marked as revise and resubmit. Submials must be submied with this +cover sheet, per spec, and with types/models outlined in red. Submials are to be divided as separate files or as a single file with each submial +type and spec secon having its own corresponding cover sheet. Submials for each spec secon to be separated and idenfied as Product +Data/Other Info, Licenses, Shop Drawings, and Samples. +Project: +__________________________________ +Company: +__________________________________ +Date: +__________________________________ +Submial/Spec Title: +__________________________________________________________________ +Submial Revision #: +__________________________________________________________________ +Spec Secon # (00 0000.00): __________________________________________________________________ +Subcontractor Comments: __________________________________________________________________ +__________________________________________________________________________________________ + + + + + + +Contractor +Comments +Architect +Comments +Engineer +Comments +Contractor +Stamp +Architect +Stamp +Engineer +Stamp +Submittal Review +This submittal has been reviewed for general compliance with the +construction documents. The submittal review process does not +relieve the subcontractor of the responsibility to verify and provide +the correct: quantities, dimensions, details, layouts, and incidentals +per the construction documents and onsite project conditions. +Reviewed By: Zac Hansen Date: 11/6/2024 + REVIEWED + REVIEWED AS NOTED + REVISE & RESUBMIT + REJECTED +SHS +The Best Companies +11/6/2024 +Operable Partitions R1 - SD +1 +10 2239 + + +--- Page 2 --- +ELA Classrooms +2183/2195 +REVISED +REVISED +9:10 AM, November 6, 2024 +9:10 AM, November 6, 2024 + + +--- Page 3 --- + + +--- Page 4 --- + + +--- Page 5 --- + + +--- Page 6 --- + + +--- Page 7 --- + + +--- Page 8 --- +ELA Classrooms +2197/2199 +REVISED +REVISED +9:10 AM, November 6, 2024 +9:10 AM, November 6, 2024 + + +--- Page 9 --- + + +--- Page 10 --- + + +--- Page 11 --- + + +--- Page 12 --- + + +--- Page 13 --- + + diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/parsed_157_1.json b/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/parsed_157_1.json new file mode 100644 index 0000000..7bd66ac --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/parsed_157_1.json @@ -0,0 +1,24 @@ +{ + "kind": "folder", + "input_path": "C:\\Users\\User\\Downloads\\claudie boy\\Procore Submittals\\0309A - Stillwater High School\\2026_04_20_08_26\\Submittals\\157_1_Operable Partitions - SD", + "cover_sheet_text": "--- Page 1 ---\n \nProject: 0309A Stillwater High School\n410 W Franklin Ln\nStillwater, Oklahoma 74075\n \n \nSubmittal #157.1 - Operable Partitions - SD  \n10 2239 - Folding Panel Partitions\n \n \nDistribution Summary\nDistributed by Zac Hansen (Willowbrook) on Nov 19, 2024\n \nTo\nChris Harrell (The Best Company), Shawn Vick (Willowbrook), Trevor Yarborough (Willowbrook), Alexis Perez (Willowbrook),\nScott Trueman (Willowbrook)\nMessage\nNone\nAttachments\n \n \nName\nResponse\nAttachments\nComments\nJeff Thomas (505 Architects)\nReviewed As Noted\n10 2239 Operable Partitions\nR1 - SD.pdf \n \nRevision\n1\nSubmittal Manager\nZac Hansen (Willowbrook)\nStatus\nClosed\nDate Created\nNov 6, 2024\nSpec Section\n10 2239 - Folding Panel Partitions\nResponsible\nContractor\nThe Best Company\nReceived From\nChris Harrell (The Best Company)\nFinal Due Date\nNov 20, 2024\nLead Time\nCost Code\nLocation\nType\nShop Drawing\nSubmittal Package\n#22: BP 24 - Specialties\n \nApprovers\nAnnie Hecksher (505 Architects), Jeff Thomas (505 Architects)\nBall in Court\nDistribution\nAlexis Perez (Willowbrook), Chris Harrell (The Best Company), Scott Trueman (Willowbrook), Shawn Vick (Willowbrook), Trevor\nYarborough (Willowbrook)\nDescription\nOperable Partitions - SD \n \n \nSubmittal Workflow\nName\nSent Date\nDue Date\nReturned Date\nResponse\nAttachments\nGeneral Information\nAttachments\n \n \n \n \n10 2239 Operable Partitions R1 - SD.pdf \nAnnie Hecksher\nNov 6, 2024\nNov 20, 2024\n \nPending\nJeff Thomas\nNov 6, 2024\nNov 20, 2024\nNov 7, 2024\nReviewed As Noted\n10 2239 Operable Partitions R1 -\nSD.pdf (Current) \n \nComment\nPlease make sure this gets sent to steel fabricator for steel beam coordination. \n \n \nPage 1 of 1\nPrinted On: Apr 20, 2026 08:27 AM CDT\n \n \n", + "attachment_texts": { + "10 2239 Operable Partitions R1 - SD.pdf": "--- Page 1 ---\nSubmittal Cover Sheet \n* Submi\u0011als not submi\u0011ed as outlined below will not be reviewed but marked as revise and resubmit. Submi\u0011als must be submi\u0011ed with this \ncover sheet, per spec, and with types/models outlined in red. Submi\u0011als are to be divided as separate files or as a single file with each submi\u0011al \ntype and spec sec\u001fon having its own corresponding cover sheet. Submi\u0011als for each spec sec\u001fon to be separated and iden\u001ffied as Product \nData/Other Info, Licenses, Shop Drawings, and Samples. \nProject: \n__________________________________ \nCompany: \n__________________________________ \nDate: \n__________________________________ \nSubmi\u0011al/Spec Title: \n__________________________________________________________________ \nSubmi\u0011al Revision #: \n__________________________________________________________________ \nSpec Sec\u001fon # (00 0000.00): __________________________________________________________________ \nSubcontractor Comments: __________________________________________________________________ \n__________________________________________________________________________________________ \n \n \n \n \n \n \nContractor\nComments\nArchitect\nComments\nEngineer\nComments\nContractor\nStamp\nArchitect\nStamp\nEngineer\nStamp\nSubmittal Review\nThis submittal has been reviewed for general compliance with the\nconstruction documents. The submittal review process does not\nrelieve the subcontractor of the responsibility to verify and provide\nthe correct: quantities, dimensions, details, layouts, and incidentals\nper the construction documents and onsite project conditions.\nReviewed By: Zac Hansen Date: 11/6/2024\n REVIEWED\n REVIEWED AS NOTED\n REVISE & RESUBMIT\n REJECTED\n11/07/2024\nJeff Thomas\n505 Architects LLC\nX\nSHS\nThe Best Companies\n11/6/2024\nOperable Partitions R1 - SD\n1\n10 2239\n\n\n--- Page 2 ---\nELA Classrooms \n2183/2195\nREVISED\nREVISED\n9:10 AM, November 6, 2024\n9:10 AM, November 6, 2024\n4'-0\" PKT BEING\nPROVIDED. RE:\nA145 PR#05\n5'-7 1/4\"\nPROVIDE SMOKE GREY\nFOR TRIM AND HINGE\n\n\n--- Page 3 ---\n\n\n--- Page 4 ---\n\n\n--- Page 5 ---\n10'-0\" FINISHED\nCEILING HEIGHT\n\n\n--- Page 6 ---\n\n\n--- Page 7 ---\n10'-0\" FINISHED\nCEILING HEIGHT\n5'-7 1/4\"\n\n\n--- Page 8 ---\nELA Classrooms \n2197/2199\nREVISED\nREVISED\n9:10 AM, November 6, 2024\n9:10 AM, November 6, 2024\n4'-0\" PKT BEING\nPROVIDED. RE:\nA145 PR#05\n5'-7 1/4\"\nPROVIDE SMOKE GREY\nFOR TRIM AND HINGE\n\n\n--- Page 9 ---\n\n\n--- Page 10 ---\n\n\n--- Page 11 ---\n10'-0\" FINISHED\nCEILING HEIGHT\n\n\n--- Page 12 ---\n\n\n--- Page 13 ---\n10'-0\" FINISHED\nCEILING HEIGHT\n5'-7 1/4\"\n\n\n--- Page 14 ---\nS\nh\ne\nr\nw\ni\nn\n \nW\nil\nli\na\nm\ns\n \nW\nh\nit\ne\nS\nh\ne\nr\nw\ni\nn\n \nW\nil\nl\ni\na\nm\ns\n \nS\nm\no\nk\ne\n \nG\nr\na\ny\nS\nh\ne\nr\nw\ni\nn\n \nW\nil\nl\ni\na\nm\ns\n \nN\na\nt\nu\nr\na\nl\n \nC\nh\no\ni\nc\ne\nS\nh\ne\nr\nw\ni\nn\n \nW\ni\nl\nli\na\nm\ns\n \nD\na\nr\nk\n \nB\nr\no\nn\nz\ne\nS\nh\ne\nr\nw\ni\nn\n \nW\ni\nl\nli\na\nm\ns\n \nB\nl\na\nc\nk\nTRIM COLOR OPTIONS\nSmoke Gray, Natural Choice, Dark Bronze, or Black\nSelection impacts sweeps, seals, pull handles, \neraser trays, hinges, and worksurface trim.\nSelection will be uniform throughout the wall.\nHINGE COLOR OPTIONS\nWhite, Smoke Gray, Natural Choice, Dark Bronze, or Black\nSelection will be uniform throughout the wall.\nForm No. 2240 5/23\n800-869-9685\nTRIM & HINGE\nCOLOR SELECTOR\ninfo@modernfold.com\nmodernfold.com\nPROVIDE SMOKE GREY\nFOR TRIM AND HINGE\n\n\n--- Page 15 ---\nAdrift\n512 (S) 548 (H)\nOats\n518 (S) 554 (H)\nBobbin Weave\n524 (S) 560 (H)\nReed\n513 (S) 549 (H)\nPrairie\n519 (S) 555 (H)\nThreads\n525 (S) 561 (H)\nSandalwood\n514 (S) 550 (H)\nReed\n520 (S) 556 (H)\nCommon Thread\n526 (S) 562 (H)\nSlate\n516 (S) 552 (H)\nFeather Grass\n522 (S) 558 (H)\nLustre\n528 (S) 564 (H)\nLotus\n511 (S) 547 (H)\nPumila Grass\n517 (S) 553 (H)\nCanvas\n523 (S) 559 (H)\nVeranda\n515 (S) 551 (H)\nRed Oat\n521 (S) 557 (H)\nQuill\n527 (S) 563 (H)\nWhite\n529 (S) 565 (H)\nArctic\n535 (S) 571 (H)\nB. White\n541 (S) 577 (H)\nSilver Dust\n530 (S) 566 (H)\nSerenity\n536 (S) 572 (H)\nEggshell\n542 (S) 578 (H)\nCirrus\n531 (S) 567 (H)\nGrey Pearl\n537 (S) 573 (H)\nFrost Taupe\n543 (S) 579 (H)\nCamel Down\n532 (S) 568 (H)\nWillet\n538 (S) 574 (H)\nAspen\n544 (S) 580 (H)\nCimmerin\n533 (S) 569 (H)\nWindrift\n539 (S) 575 (H)\nDenver\n545 (S) 581 (H)\nCarbon\n534 (S) 570 (H)\nHemp\n540 (S) 576 (H)\nBlack\n546 (S) 582 (H)\nStandard Len-Tex® Vinyl Offering\nStandard Len-Tex® Vinyl Offering\nDisplayed samples feature a standard weight 20-oz. (S). Also available in a heavy-duty 30-oz. construction (H).\nNote: Each digital swatch is a digital color presentation of a standard color and is subject to minor shade variations.\nLennon Grass\nArani Silk\nLoominous\nSoraya\nCoronado\nEmboss LT Suede\n\n\n--- Page 16 ---\nVINYL SELECTOR\nLen-Tex® Vinyl Wallcovering Finish Specifi cations\nForm: 2243 1/22\n800-869-9685 | info@modernfold.com | www.modernfold.com\n215 West New Rd. | Greenfi eld, IN 46140\nFederal Specifi cations: \nLen-Tex Clean Vinyl Wallcovering meets or exceeds all requirements of W-101 (20 oz), W-101, Type III (30 oz.) Quality \nStandard for Polymer Coated Fabric Wallcovering and EN 15102, European Standard for Decorative Wallcoverings. This \nproduct has been accepted for use by the City of New York Dept. of Buildings under MEA-381-93M. \nFire Hazard Classifi cation Results: \nASTM E-84 Tunnel Test: Class A, Flame Spread: 15; Smoke Developed: 10 (20 oz.)\nASTM E-84 Tunnel Test: Class A, Flame Spread: 20; Smoke Developed: 65 (30 oz.)\nPASS rating as tested under NFPA 286 (Corner burn test) \nCAN/ ULC S102-10 Fire Test: Class A, Flame Spread: 15; Smoke Developed: 75 \nEN 13501-1 Fire Classifi cation: B-s2, d0 \nPerformance Features: \nLen-Tex Wallcoverings use Clean Vinyl Technology™\nPhthalate-free \nAlumina trihydrate fi re retardant (antimony-free) \nUltra-Fresh® antimicrobial (non-arsenate) \nNo heavy metals or formaldehyde \nPrinted exclusively with water-based, low VOC AQUA-CLEAR™ inks \nRoller applied AQUA-CLEAR 3.0™ top fi nish for improved stain resistance \nMicroventing for permeability is available on a custom order basis \nAdvanced Warning Eff ect (ionization-type smoke detector) \nFive year warranty against materials defects – Consult your distributor for details \nEnvironmental Attributes: \nUltra-low emitting – Pass rating under CA 01350 \nListed by ZeroDocs and Sustainable Minds® for California CHPS (Collaborative for High Performance Schools) SCS Indoor \nAdvantage™ Gold certifi ed \nCA Prop 65 compliant \nPublished Health Product Declaration (HPD) – LEED eligible \nPublished Len-Tex Environmental Product Declaration (EPD) – LEED eligible \nGreenCircle Certifi ed™ \nFind Len-Tex products listed on: \nEcomedes (ecomedes.com), SCS Global Services website (scsglobalservices.com), GreenCircle Certifi ed website \n(greencirclecertifi ed.com), Mortarr (mortarr.com) \nPermeability, Moisture and Mold: \nVinyl wallcovering can act as a vapor barrier and consequently should NOT be installed on walls that are susceptible \nto excessive condensation or moisture infi ltration. To reduce the risk of fungal growth, Len-Tex recommends that \nall wallcovering(s) be microvented when installed in Humid and Fringe Zone 1 climates.(ref: ASHRAE Handbook of \nFundamentals, Chapter 21, Figure 16). Please see full details on microventing at our website, lentexwallcoverings.com. \nLEN-TEX®\n \n \nU.S. Units \n Metric Units\nTotal Weight: \n20.0 oz./LY 612 G/LM\n 13.2 oz./SY \n 442 G/SM\nFabric Weight:: 2.5 oz./LY \n 85 G/LM\nVinyl Weight: 17.5 oz./LY \n 520 G/LM\nMaterial Width: 53/54” 134/137 cm\nFabric Type: \nPoly Cotton Osnaburg\n \n \nU.S. Units \n Metric Units\nTotal Weight: \n30.0 oz./LY 925 G/LM\n 20.0 oz./SY \n 680 G/SM\nFabric Weight:: 5.5 oz./LY \n 170 G/LM\nVinyl Weight: 24.5 oz./LY \n 756 G/LM\nMaterial Width: 53/54” 134/137 cm\nFabric Type: \nPoly Cotton Drill\n", + "10 2239 Operable Partitions R1 - SD_1.pdf": "--- Page 1 ---\nSubmittal Cover Sheet \n* Submi\u0011als not submi\u0011ed as outlined below will not be reviewed but marked as revise and resubmit. Submi\u0011als must be submi\u0011ed with this \ncover sheet, per spec, and with types/models outlined in red. Submi\u0011als are to be divided as separate files or as a single file with each submi\u0011al \ntype and spec sec\u001fon having its own corresponding cover sheet. Submi\u0011als for each spec sec\u001fon to be separated and iden\u001ffied as Product \nData/Other Info, Licenses, Shop Drawings, and Samples. \nProject: \n__________________________________ \nCompany: \n__________________________________ \nDate: \n__________________________________ \nSubmi\u0011al/Spec Title: \n__________________________________________________________________ \nSubmi\u0011al Revision #: \n__________________________________________________________________ \nSpec Sec\u001fon # (00 0000.00): __________________________________________________________________ \nSubcontractor Comments: __________________________________________________________________ \n__________________________________________________________________________________________ \n \n \n \n \n \n \nContractor\nComments\nArchitect\nComments\nEngineer\nComments\nContractor\nStamp\nArchitect\nStamp\nEngineer\nStamp\nSubmittal Review\nThis submittal has been reviewed for general compliance with the\nconstruction documents. The submittal review process does not\nrelieve the subcontractor of the responsibility to verify and provide\nthe correct: quantities, dimensions, details, layouts, and incidentals\nper the construction documents and onsite project conditions.\nReviewed By: Zac Hansen Date: 11/6/2024\n REVIEWED\n REVIEWED AS NOTED\n REVISE & RESUBMIT\n REJECTED\nSHS\nThe Best Companies\n11/6/2024\nOperable Partitions R1 - SD\n1\n10 2239\n\n\n--- Page 2 ---\nELA Classrooms \n2183/2195\nREVISED\nREVISED\n9:10 AM, November 6, 2024\n9:10 AM, November 6, 2024\n\n\n--- Page 3 ---\n\n\n--- Page 4 ---\n\n\n--- Page 5 ---\n\n\n--- Page 6 ---\n\n\n--- Page 7 ---\n\n\n--- Page 8 ---\nELA Classrooms \n2197/2199\nREVISED\nREVISED\n9:10 AM, November 6, 2024\n9:10 AM, November 6, 2024\n\n\n--- Page 9 ---\n\n\n--- Page 10 ---\n\n\n--- Page 11 ---\n\n\n--- Page 12 ---\n\n\n--- Page 13 ---\n" + }, + "extracted_metadata": { + "submittal_num": "157.1", + "revision": 1, + "spec_section": "", + "type": "Shop Drawing", + "response": "Reviewed", + "reviewer": "", + "comments": [ + "Please make sure this gets sent to steel fabricator for steel beam coordination." + ], + "contractor": "The Best Company", + "package": "", + "date_created": "", + "date_returned": "" + } +} \ No newline at end of file diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/review_157_1.docx b/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/review_157_1.docx new file mode 100644 index 0000000000000000000000000000000000000000..64f9c20be0321ee72253982e434041bf0e1a0dee GIT binary patch literal 38067 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 literal 0 HcmV?d00001 diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/review_157_1.json b/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/review_157_1.json new file mode 100644 index 0000000..77a45d7 --- /dev/null +++ b/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/review_157_1.json @@ -0,0 +1,74 @@ +{ + "submittal_id": "157.1", + "project_id": "0309A", + "spec_section": "10 22 39 - Folding Panel Partitions", + "csi_division": "10", + "package_number": "BP 24 - Specialties", + "product_name": "Operable Partitions - SD", + "manufacturer": "", + "model_number": "", + "type": "Shop Drawing", + "contractor": "The Best Company", + "review_assignee": "Jeff Thomas", + "review_due_date": "2024-11-20", + + "overall_status": "CONDITIONAL", + "submittal_status_recommended": "Approved as Noted", + "draft_status": "DRAFT", + + "conformance_checks": [ + { "item": "Submittal cover sheet is complete (project #, sub name, spec section, date)", + "result": "pass", "confidence": 0.98, + "detail": "Cover sheet contains project 0309A, sub (The Best Company), spec 10 22 39, and dates" }, + { "item": "Submittal number and revision matches the submittal log", + "result": "pass", "confidence": 0.97, "detail": "157.1 (Rev 1)" }, + { "item": "Sub has stamped and signed the submittal", + "result": "insufficient_data", "confidence": 0.60, + "detail": "Stamp presence could not be verified from extracted text" }, + { "item": "Spec section referenced matches the scope being submitted", + "result": "pass", "confidence": 0.97, "detail": "10 22 39 Folding Panel Partitions matches scope" }, + { "item": "Manufacturer appears in the spec's approved manufacturer list", + "result": "insufficient_data", "confidence": 0.50, + "detail": "Spec book not loaded in v1 — PM must verify against Part 2 Products list" }, + { "item": "Shop drawings show actual project dimensions, not generic drawings", + "result": "pass", "confidence": 0.96, + "detail": "Attachments reference project-specific opening locations and dimensions" }, + { "item": "Coordination dimensions tie to structural drawings (steel beam condition)", + "result": "warn", "confidence": 0.97, + "detail": "Drawings show partition head connection to steel beam; no evidence the submittal was routed to the steel fabricator for coordination" }, + { "item": "Submittal is complete — no 'additional data to follow' placeholders", + "result": "pass", "confidence": 0.96, + "detail": "All sheets numbered consecutively; no placeholder language detected" } + ], + + "pattern_flags": [ + { "source": "playbook", + "pattern": "Missing Multi-Trade Coordination", + "flag": "Operable partitions historically require routing to the steel fabricator for beam coordination. Confirm submittal copy has been distributed to the steel sub before architect review.", + "severity": "warn" }, + { "source": "playbook", + "pattern": "Multi-Round Trades (Plan for R2+)", + "flag": "This is already R1; confirm R0 comments have been fully addressed before architect re-review.", + "severity": "info" } + ], + + "deviations": [], + + "suggested_action": "Confirm this submittal has been routed to the steel fabricator for beam coordination, then forward to architect.", + + "pushback_draft": "", + + "redline_suggestions": [], + + "attachments": [ + "10 2239 Operable Partitions R1 - SD.pdf", + "10 2239 Operable Partitions R1 - SD_1.pdf" + ], + + "citations": [], + + "confidence_tier": "yellow", + + "skill_version": "v1.0", + "run_timestamp": "2026-04-24T14:30:00-05:00" +} diff --git a/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/review_157_1_spec_aware.docx b/plugins/willow-pmtools/skills/willowbrook-submittal-review/test_fixtures/review_157_1_spec_aware.docx new file mode 100644 index 0000000000000000000000000000000000000000..844dbbda8f72a2a280b871b3a39d97fca6ea1621 GIT binary patch literal 40138 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