@structupath/pi-steel 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/DATA_PROVENANCE.json +23 -0
  2. package/DATA_PROVENANCE.md +35 -0
  3. package/PUBLIC_DATA_POLICY.md +52 -0
  4. package/README.md +114 -33
  5. package/docs/assets/pi-steel-demo.gif +0 -0
  6. package/docs/assets/pi-steel-gallery.webp +0 -0
  7. package/package.json +35 -4
  8. package/pyproject.toml +28 -0
  9. package/requirements-dev.txt +5 -0
  10. package/requirements-render.txt +1 -0
  11. package/requirements-tested.txt +9 -0
  12. package/requirements.txt +7 -0
  13. package/scripts/check-data-provenance.py +140 -0
  14. package/scripts/check-public-data.py +401 -0
  15. package/scripts/doctor.py +127 -0
  16. package/skills/_shared/bootstrap.py +32 -0
  17. package/skills/_shared/pi_steel/__init__.py +47 -0
  18. package/skills/_shared/pi_steel/cli.py +167 -0
  19. package/skills/_shared/pi_steel/contracts.py +87 -0
  20. package/skills/_shared/pi_steel/geometry_verify.py +243 -0
  21. package/skills/_shared/pi_steel/parsing.py +205 -0
  22. package/skills/_shared/pi_steel/run_manifest.py +317 -0
  23. package/skills/_shared/pi_steel/validation.py +597 -0
  24. package/skills/_shared/schemas/estimate-package.schema.json +424 -0
  25. package/skills/_shared/schemas/nest-result.schema.json +443 -0
  26. package/skills/_shared/schemas/run-manifest.schema.json +145 -0
  27. package/skills/steel-estimate/SKILL.md +119 -0
  28. package/skills/steel-estimate/references/estimate-package-example.json +91 -0
  29. package/skills/steel-estimate/references/output-contract.md +47 -0
  30. package/skills/steel-estimate/scripts/acknowledge-finding.py +193 -0
  31. package/skills/steel-estimate/scripts/build-estimate-package.py +836 -0
  32. package/skills/steel-nest/SKILL.md +50 -23
  33. package/skills/steel-nest/references/FIXTURE_PROVENANCE.md +12 -0
  34. package/skills/steel-nest/references/example_job.json +21 -25
  35. package/skills/steel-nest/references/job_template.json +11 -9
  36. package/skills/steel-nest/scripts/nest.py +1276 -250
  37. package/skills/steel-rfq/SKILL.md +93 -175
  38. package/skills/steel-rfq/assets/company-profile.example.json +11 -5
  39. package/skills/steel-rfq/references/rfq-input.md +58 -0
  40. package/skills/steel-rfq/scripts/generate-rfq.py +1221 -0
  41. package/skills/steel-rfq/scripts/recalc.py +33 -10
  42. package/skills/steel-takeoff/SKILL.md +12 -8
  43. package/skills/steel-takeoff/assets/bom-template.csv +1 -1
  44. package/skills/steel-takeoff/references/takeoff-procedures.md +3 -1
  45. package/skills/steel-takeoff/scripts/calculate-weight.sh +5 -10
  46. package/skills/steel-takeoff/scripts/lookup-member.sh +2 -2
  47. package/skills/steel-takeoff/scripts/validate-bom.py +151 -196
  48. package/skills/steel-rfq/scripts/__pycache__/recalc.cpython-312.pyc +0 -0
@@ -1,179 +1,97 @@
1
1
  ---
2
2
  name: steel-rfq
3
- description: "Generate standardized Request for Quotation (RFQ) spreadsheets from steel estimate takeoff files. Use this skill whenever someone mentions RFQ, request for quote, vendor quote, material quote, sending a material list to vendors, quoting steel, or getting pricing from a supplier. Also trigger when someone uploads a steel estimate or takeoff spreadsheet and wants to send it out for pricing. Even if they just say 'I need to send this to my vendor' or 'get me prices on this material' — that's an RFQ."
3
+ description: "Compile a deterministic draft steel RFQ workbook from a validated canonical estimate package or an exact-header legacy workbook. Use for material quote lists, RFQ spreadsheets, or versioned nesting references. Produces artifacts only; it never sends, awards, or authorizes purchasing."
4
4
  ---
5
5
 
6
- # Steel RFQ Generator
7
-
8
- ## What This Skill Does
9
-
10
- This skill takes a steel estimate/takeoff spreadsheet (.xlsx) and produces a clean, standardized RFQ spreadsheet that can be sent directly to steel vendors. The output is always an Excel file with a consistent layout so every vendor gets the same professional format regardless of who creates it.
11
-
12
- The RFQ format was designed around how steel vendors actually work materials grouped by type (W-shapes, plates, flat bar) so vendors can quickly identify what they have in stock, quote pricing, and flag what they don't carry.
13
-
14
- ## Company Profile (required setup)
15
-
16
- The RFQ carries the requesting company's identity. Load it from
17
- `assets/company-profile.json` in this skill's directory (copy
18
- `company-profile.example.json` and edit). If the file is missing, ask the
19
- user for their company name, city/state, and payment terms before
20
- generating, and offer to save the answers as `company-profile.json` for
21
- next time.
22
-
23
- Profile fields:
24
- - `company_name` — appears in the header and terms & conditions
25
- - `city_state` appears in the header (e.g., "Denver, CO")
26
- - `payment_terms` — default "Net 30 from date of delivery"
27
- - `quote_validity_days` default 30
28
- - `logo` optional filename in `assets/` to embed top-left
29
-
30
- Never invent company details. If the profile is incomplete, ask.
31
-
32
- ## Input
33
-
34
- Always a steel estimate spreadsheet (.xlsx). These files typically have:
35
- - A "Steel Takeoff" sheet (or similar) with line items for structural members, plates, and connection hardware
36
- - Item numbers (A-01, B-01, C-01, etc.), categories, descriptions, sizes, quantities, stock lengths, and weights
37
- - Items marked "BY OTHERS" (typically purlins) that are NOT part of the fabricator's scope
38
-
39
- The first step is always to read and understand the takeoff data before generating the RFQ.
40
-
41
- ## Reading the Estimate
42
-
43
- 1. Use pandas to read all sheets: `pd.ExcelFile(path)` then inspect sheet names
44
- 2. Find the takeoff sheet (look for "Takeoff", "Steel Takeoff", "Material List", "BOM", or the sheet with item-level steel data)
45
- 3. Read the full sheet with `header=None` to capture all rows including section headers
46
- 4. Identify the data structure:
47
- - Section headers (e.g., "A. MAIN BUILDING — W-SHAPES")
48
- - Column headers (Item, Category, Description, Size, Qty, Length, Weight, Stock Purchase, Purchase Wt, Notes)
49
- - Line items with actual material data
50
- - Items with "BY OTHERS" in the description — these get **excluded**
51
- - Subtotal and total rows
52
-
53
- ## RFQ Output Format
54
-
55
- The output is always a single .xlsx file with this exact structure:
56
-
57
- ### Header Block (Rows 1–3)
58
- - **Row 1**: "REQUEST FOR QUOTATION Structural Steel" dark blue background (#1F3864), white bold Arial 14pt, merged across all columns
59
- - **Row 2**: "[Project Name] | [Company Name] — [City, ST]" — same dark blue, white bold Arial 11pt
60
- - **Row 3**: "Date Issued: [today] | Response Requested By: _______________ | Project Location: [location]" — same dark blue, white Arial 10pt
61
-
62
- ### Vendor Info Block (Rows 4–6)
63
- Fillable fields for the vendor:
64
- - Row 5: Company Name (merged A–B, input in C–E yellow) | Contact Name (merged F–G, input in H–J yellow)
65
- - Row 6: Phone/Email (merged A–B, input in C–E yellow) | Quote Valid Until (merged F–G, input in H–J yellow)
66
-
67
- ### Instructions Row (Row 7)
68
- Single merged row with italic gray text explaining how to fill out the yellow columns. Text:
69
- > "Instructions: Please fill in the YELLOW columns (Unit Price, Total Price, Availability, Lead Time, Alternate Size, Notes). Mark Availability as 'In Stock', 'Lead Time', or 'Unavailable'. If suggesting an alternate size, list it and adjust pricing accordingly."
70
-
71
- ### Column Headers (Row 8)
72
- 14 columns with medium blue background (#2E75B6), white bold Arial 10pt:
73
-
74
- | Column | Header | Width | Purpose |
75
- |--------|--------|-------|---------|
76
- | A | Item | 7 | Item number from estimate (A-01, C-02, E-01, etc.) |
77
- | B | Category | 13 | Columns, Girders, Plate, Flat Bar, etc. |
78
- | C | Description | 38 | Full description including which structure it belongs to |
79
- | D | Size / Designation | 22 | AISC shape or plate dimensions |
80
- | E | Grade | 12 | A992 Gr.50, A572 Gr.50, A36, etc. |
81
- | F | Qty | 6 | Number of pieces or sticks needed |
82
- | G | Stock Length / Size | 20 | How it's being purchased (e.g., "3 sticks × 30′") |
83
- | H | Est. Purchase Wt (lbs) | 20 | Weight from the estimate |
84
- | I | Unit Price ($) | 14 | **VENDOR FILLS** — yellow background |
85
- | J | Total Price ($) | 14 | **VENDOR FILLS** — yellow background |
86
- | K | Availability | 14 | **VENDOR FILLS** — yellow background |
87
- | L | Lead Time (days) | 14 | **VENDOR FILLS** — yellow background |
88
- | M | Alternate Size | 18 | **VENDOR FILLS** — yellow background |
89
- | N | Notes | 30 | **VENDOR FILLS** — yellow background |
90
-
91
- Vendor columns (I–N) get a darker gold header (#BF8F00) to distinguish them from the material data columns.
92
-
93
- ### Material Data Rows
94
-
95
- **Grouping**: Always group by material type, not by structure. The three groups are:
96
- 1. **W-SHAPES [Grade]** (columns and girders from all structures combined)
97
- 2. **PLATE STOCK [Grade]** (all plate material)
98
- 3. **FLAT BAR STOCK — [Grade]** (all flat bar material)
99
-
100
- Each group gets a section header row: merged across all columns, medium blue background, white bold text.
101
-
102
- **Data rows**:
103
- - Alternate row shading: light blue (#D6E4F0) and white
104
- - Vendor columns (I–N) always have yellow background (#FFF2CC) regardless of row
105
- - Weight column (H) formatted as `#,##0`
106
- - Price columns (I–J) formatted as `$#,##0.00`
107
- - All cells have thin borders
108
-
109
- **Filtering rules**:
110
- - EXCLUDE any item where the description contains "BY OTHERS" (purlins, etc.)
111
- - EXCLUDE any item where Qty = 0
112
- - If the estimate has a "Stock Purchase" section (Section E or similar) with consolidated purchase items for plates/flat bar, use those instead of the individual connection plate items. The stock purchase items represent what's actually being ordered (full sheets, full bars), which is what the vendor needs to quote.
113
- - Keep individual W-shape items because each shape/length combination matters for vendor stock
114
-
115
- ### Totals Row
116
- - "TOTAL PURCHASE WEIGHT / PRICE" label merged A–G, right-aligned bold
117
- - Column H: `=SUM(H[first]:H[last])` formula for total weight, green background (#E2EFDA)
118
- - Column J: `=SUM(J[first]:J[last])` formula for total price, green background
119
-
120
- ### Nesting / Drop Reference Table
121
- Below the totals (skip a row), add a reference section:
122
- - Header: "NESTING / DROP REFERENCE (For Fabricator Use)" — dark blue bold text
123
- - Three columns: Material | Nesting Plan | Drop Notes
124
- - Column headers with medium blue background
125
- - One row per material showing the nesting layout and expected drop from the estimate
126
- - This helps cross-check vendor stock lengths against the cutting plan
127
-
128
- ### Standard Terms & Conditions
129
- Below the nesting table (skip a row), add:
130
- - Header: "TERMS & CONDITIONS" — dark blue bold text
131
- - Include these standard terms, each on its own row, substituting the company name and payment terms from the company profile:
132
-
133
- 1. **Delivery**: All material to be delivered FOB jobsite unless otherwise agreed. Vendor to confirm freight costs separately.
134
- 2. **Mill Certifications**: Mill test reports (MTRs) required for all structural steel per AISC/AWS standards. Certs must accompany delivery.
135
- 3. **Payment Terms**: [payment_terms from profile] unless otherwise negotiated in writing.
136
- 4. **Material Standards**: All wide-flange shapes to meet ASTM A992. All plate and bar to meet grade specified on this RFQ (A572 Gr.50 or A36).
137
- 5. **Substitutions**: No substitutions without prior written approval from [Company Name]. If quoting alternate sizes, clearly note in the "Alternate Size" column.
138
- 6. **Quote Validity**: Quoted prices to remain firm for [quote_validity_days] days from date of quote unless otherwise stated.
139
- 7. **Inspection**: [Company Name] reserves the right to inspect material upon delivery and reject material not meeting specifications.
140
- 8. **Cancellation**: Orders may be cancelled without penalty if material has not shipped. Restocking fees, if any, to be stated in quote.
141
-
142
- ### Branding / Logo
143
- If the company profile names a logo file and it exists in the skill's `assets/` directory, insert it in cell A1 area (top-left) and adjust the header text to not overlap. Otherwise use the text header as described above.
144
-
145
- ## Print Setup
146
- - Orientation: Landscape
147
- - Fit to width: 1 page
148
- - Fit to height: 0 (auto)
149
- - Print title rows: Row 8 (column headers repeat on each page)
150
-
151
- ## File Naming
152
- Output file: `[ProjectName]_RFQ_Material_List.xlsx`
153
- - Extract project name from the estimate file (look in "Project Info" sheet or the first rows of the takeoff)
154
- - Replace spaces with underscores
155
- - Example: `Cherokee_Boys_RFQ_Material_List.xlsx`
156
-
157
- ## Step-by-Step Workflow
158
-
159
- 1. Load the company profile (or collect it from the user — see Company Profile above)
160
- 2. Read the uploaded estimate file with pandas to understand its structure
161
- 3. Identify the takeoff sheet and parse all line items
162
- 4. Filter out "BY OTHERS" items and zero-quantity items
163
- 5. Group remaining items by material type (W-shapes → Plates → Flat Bar)
164
- 6. If there's a stock purchase section, use those for plates/flat bar instead of individual pieces
165
- 7. Build the RFQ spreadsheet using openpyxl following the format above
166
- 8. Add formulas for totals and verify the SUM ranges cover exactly the data rows
167
- 9. Add nesting/drop reference from the estimate notes — or, if the `steel-nest` skill has been run for this job, read its `rfq_nesting.json` output straight into the table
168
- 10. Add standard terms & conditions with profile values substituted
169
- 11. Insert logo if configured
170
- 12. Save the file, then run `python3 scripts/recalc.py <output.xlsx>` so formula values are computed (openpyxl writes formulas but never calculates them)
171
- 13. Verify no formula errors and present the file to the user
172
-
173
- ## Common Variations
174
-
175
- **Multiple structures in one project**: Combine all materials into one RFQ, but note which structure each item belongs to in the Description column (e.g., "W12×65 Columns (Bldg A)").
176
-
177
- **Plate stock with nesting**: When plates are purchased as full sheets and then cut, show the full sheet as the line item (that's what the vendor ships) and put the cutting plan in the nesting reference table.
178
-
179
- **Mixed grades**: Group by material type first, then note the grade in each row. If a project has A992, A572, and A36, they all appear in the appropriate material type section with grade clearly marked.
6
+ # Steel RFQ Compiler
7
+
8
+ ## Boundary
9
+
10
+ This skill creates a reviewable draft workbook. It does not contact vendors,
11
+ send RFQs, select a quote, approve substitutions, award work, or authorize a
12
+ purchase. Every workbook and manifest records `DRAFTNOT SENT OR AWARDED`.
13
+
14
+ ## Required inputs
15
+
16
+ - Canonical estimate-package JSON `1.0.0`, or the conservative exact-header
17
+ legacy XLSX contract in `references/rfq-input.md`.
18
+ - Explicit issue date.
19
+ - A runtime company profile with company name, city/state, and an approved,
20
+ hash-bound terms template.
21
+ - Optional versioned nesting handoff `1.0.0`.
22
+
23
+ Resolve the company profile from `PI_STEEL_CONFIG`, project-local ignored
24
+ `.pi-steel/company-profile.json`, then the platform user-config directory.
25
+ Never write runtime identity, terms, or logos into this installed skill.
26
+
27
+ The shipped `assets/company-profile.example.json` is deliberately unapproved.
28
+ Copy it to a runtime config location, supply reviewed terms and approval
29
+ metadata, recompute the exact UTF-8 SHA-256, then change status to `approved`.
30
+
31
+ ## Deterministic scope and purchasing rules
32
+
33
+ - Canonical `intent` controls scope. Description text cannot exclude an item.
34
+ - `allowance`, `exclusion`, and `by_others` never become vendor quantities.
35
+ - Zero quantity in the legacy adapter maps to excluded scope.
36
+ - Consolidated purchased stock replaces fabricated pieces only through explicit
37
+ `dimensions.replaces_item_ids`.
38
+ - Lines remain traceable by source and item ID.
39
+ - Groups and nesting references remain separate by material, grade, thickness,
40
+ size, and stock identity.
41
+ - Missing identity, approved terms, supported versions, or valid input blocks
42
+ workbook generation.
43
+
44
+ ## Workbook contract
45
+
46
+ The compiler owns:
47
+
48
+ - `RFQ Draft` and hidden `RFQ Metadata` sheets.
49
+ - Dark-blue title block, fillable vendor block, fixed A:N headers, grouped
50
+ purchase lines, yellow vendor-response cells, formulas, borders, widths, and
51
+ alternating row fills.
52
+ - Exact total formulas covering the deterministic material range.
53
+ - Versioned nesting/remnant reference rows with visible reference-only labels.
54
+ - Approved terms content and approval lineage.
55
+ - Landscape print setup, one-page width, repeated row-8 headers, and stable
56
+ project-derived filename.
57
+
58
+ A missing optional logo falls back to the text header and does not invent
59
+ branding.
60
+
61
+ ## Run
62
+
63
+ ```bash
64
+ python3 scripts/generate-rfq.py \
65
+ --input <estimate-package.json-or-exact-legacy.xlsx> \
66
+ --nest <optional-rfq_nesting.json> \
67
+ --issued-date <YYYY-MM-DD> \
68
+ --project-location "Example City, ST" \
69
+ --out <publication-root>
70
+ ```
71
+
72
+ Each invocation publishes an isolated run with `run-manifest.json`,
73
+ `qa-report.json`, the draft `.xlsx` when gates pass, and
74
+ `workbook-semantic.json`.
75
+
76
+ Exit codes:
77
+
78
+ - `0`: draft RFQ ready for human review.
79
+ - `2`: draft generated with review-required warnings or reference-only nesting.
80
+ - `3`: blocked; diagnostics only, no workbook.
81
+ - `1`: usage or internal input error.
82
+
83
+ ## Formula calculation status
84
+
85
+ The compiler always requests full recalculation on open. If LibreOffice is
86
+ available and succeeds, QA says `baked_via_libreoffice`. Otherwise QA says
87
+ `deferred_recalculate_on_open`; it never claims cached formula values were
88
+ computed.
89
+
90
+ ## Verification before delivery
91
+
92
+ - Confirm the manifest outcome and artifact allow-list.
93
+ - Confirm workbook metadata remains draft-only.
94
+ - Confirm every purchase line is traceable and typed in scope.
95
+ - Confirm totals formulas span the intended material rows.
96
+ - Confirm nesting rows preserve material/grade/thickness/size boundaries.
97
+ - Report deferred formula caching and reference-only nesting visibly.
@@ -1,7 +1,13 @@
1
1
  {
2
- "company_name": "Acme Steel Fabrication",
3
- "city_state": "Denver, CO",
4
- "payment_terms": "Net 30 from date of delivery",
5
- "quote_validity_days": 30,
6
- "logo": null
2
+ "company_name": "Example Fabricator",
3
+ "city_state": "Example City, ST",
4
+ "logo": null,
5
+ "terms_template": {
6
+ "template_id": "EXAMPLE-TERMS-UNAPPROVED",
7
+ "content": "ENTER SYNTHETICALLY APPROVED TERMS TEMPLATE",
8
+ "content_hash": "607418ebf6466ee2f5f45b7ea63603baba276b5f95860cd8a079dfa1d652fe6b",
9
+ "approver": "ENTER APPROVER",
10
+ "approval_date": null,
11
+ "status": "draft"
12
+ }
7
13
  }
@@ -0,0 +1,58 @@
1
+ # RFQ compiler input contract
2
+
3
+ The deterministic compiler accepts either:
4
+
5
+ 1. A canonical estimate package at schema version `1.0.0`.
6
+ 2. A legacy `.xlsx` workbook with a sheet named exactly `Steel Takeoff` and
7
+ these exact row-1 headers:
8
+
9
+ `Source_ID`, `Item_ID`, `Scope`, `Description`, `Material`, `Grade`,
10
+ `Thickness`, `Size`, `Qty`, `Currency`, `Purchase Weight`.
11
+
12
+ The legacy adapter is intentionally narrow. `Scope` must be exactly `IN SCOPE`,
13
+ `BY OTHERS`, or `EXCLUDED`; descriptions never control scope. Missing stable
14
+ IDs, renamed columns, inferred section headers, and ambiguous quantities are
15
+ rejected for explicit mapping outside the compiler.
16
+
17
+ Canonical filtering uses `intent`. Allowances, exclusions, and by-others items
18
+ never become vendor lines. A purchased-stock or hardware item replaces
19
+ fabricated items only when its `dimensions.replaces_item_ids` explicitly names
20
+ those canonical item IDs.
21
+
22
+ The optional nesting input is the versioned `rfq_nesting.json` object:
23
+
24
+ ```json
25
+ {
26
+ "schema_version": "1.0.0",
27
+ "source_nest_result_version": "1.0.0",
28
+ "geometry_readiness": "geometry_verified",
29
+ "rows": []
30
+ }
31
+ ```
32
+
33
+ Unknown versions are blocked. Rows stay separate by stock identity, material,
34
+ grade, thickness, and sheet size.
35
+
36
+ The company profile is resolved in this order:
37
+
38
+ 1. File named by `PI_STEEL_CONFIG`.
39
+ 2. Ignored project file `.pi-steel/company-profile.json` beside the input.
40
+ 3. Platform user configuration at `pi-steel/company-profile.json`.
41
+
42
+ Runtime profiles must not be saved under the installed package. The profile
43
+ requires company identity and an approved `terms_template` whose SHA-256
44
+ matches its exact UTF-8 content. Editing terms invalidates approval. A missing
45
+ optional logo uses the text header.
46
+
47
+ The command requires an explicit issue date:
48
+
49
+ ```bash
50
+ python3 scripts/generate-rfq.py \
51
+ --input estimate-package.json \
52
+ --nest rfq_nesting.json \
53
+ --issued-date 2026-07-28 \
54
+ --out published/
55
+ ```
56
+
57
+ Every workbook is marked `DRAFT — NOT SENT OR AWARDED`. The compiler contains
58
+ no send, award, vendor-selection, or purchase-authorization action.