@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
@@ -0,0 +1,23 @@
1
+ {
2
+ "schema_version": "1.0.0",
3
+ "datasets": [
4
+ {
5
+ "dataset_id": "structupath-structural-shapes-v1",
6
+ "dataset_name": "StructuPath Structural Shapes Database",
7
+ "shipped_file": "skills/steel-takeoff/assets/aisc-shapes-database.json",
8
+ "source": "Original StructuPath dataset created from StructuPath's internal structural-shape records",
9
+ "copyright_holder": "StructuPath",
10
+ "license": "MIT",
11
+ "authorization": "Authorized by StructuPath for public redistribution in @structupath/pi-steel under the MIT license on 2026-07-28",
12
+ "rows": 477,
13
+ "sha256": "5a7c975c4c290c34df6f7df3b4d0d0d13a00ef7c2b1f45097a49a78d245dcc91",
14
+ "required_fields": [
15
+ "type",
16
+ "designation",
17
+ "weight_per_ft"
18
+ ],
19
+ "redistribution_permission": "authorized",
20
+ "release_readiness": "ready"
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,35 @@
1
+ # Data Provenance
2
+
3
+ This file records the evidence and release decision for datasets shipped by
4
+ pi-steel. [`DATA_PROVENANCE.json`](DATA_PROVENANCE.json) is the machine-readable
5
+ release record checked against the shipped bytes.
6
+
7
+ ## StructuPath Structural Shapes Database
8
+
9
+ | Field | Recorded value |
10
+ | --- | --- |
11
+ | Dataset name | StructuPath Structural Shapes Database |
12
+ | Shipped file | `skills/steel-takeoff/assets/aisc-shapes-database.json` |
13
+ | Source | Original StructuPath dataset created from StructuPath's internal structural-shape records |
14
+ | Rows in shipped JSON | 477 |
15
+ | SHA-256 | `5a7c975c4c290c34df6f7df3b4d0d0d13a00ef7c2b1f45097a49a78d245dcc91` |
16
+ | Repository introduction | Initial repository commit |
17
+ | Copyright holder | StructuPath |
18
+ | Redistribution permission | Authorized |
19
+ | Authorization | Authorized by StructuPath for public redistribution in @structupath/pi-steel under the MIT license on 2026-07-28 |
20
+ | License | MIT |
21
+ | Release gate | Ready |
22
+
23
+ StructuPath confirms that it created this database from its own internal
24
+ structural-shape records and that the dataset is not sourced from AISC. StructuPath owns the
25
+ dataset and has authorized its public redistribution in `@structupath/pi-steel`
26
+ under the MIT license.
27
+
28
+ The dataset uses standard structural-shape designations and property names.
29
+ Those references identify industry-standard members; they do not imply that the
30
+ shipped database was copied, transformed, or licensed from AISC.
31
+
32
+ The release check verifies the recorded path, row count, required fields,
33
+ designation uniqueness, SHA-256 digest, ownership, license, authorization, and
34
+ release decision. Any change to the shipped bytes or provenance record requires
35
+ a new review.
@@ -0,0 +1,52 @@
1
+ # Public Data Policy
2
+
3
+ This repository is public. Source code, documentation, tests, fixtures, generated
4
+ goldens, commit metadata, issue text, and release artifacts must be safe for public
5
+ distribution.
6
+
7
+ ## Allowed
8
+
9
+ - Clearly labeled synthetic projects, customers, vendors, people, and identifiers.
10
+ - Published standards data whose source, edition, license, and redistribution rights
11
+ are documented.
12
+ - Invented dimensions and quantities that do not reproduce a private project.
13
+ - Placeholder configuration such as `Example Fabricator` and `Example City, ST`.
14
+ - Product behavior and generic steel-domain terminology.
15
+ - Public gallery media reviewed before publication and pinned in the scanner by
16
+ exact repository path and SHA-256 digest. Replacing it requires a new review and
17
+ digest.
18
+
19
+ ## Prohibited
20
+
21
+ - Private company names or identifiers other than the public StructuPath product
22
+ identity.
23
+ - Customer, vendor, employee, subcontractor, or project names and contact details.
24
+ - Real job numbers, drawing references, addresses, schedules, quotes, bids, rates,
25
+ margins, payroll, costs, terms, or contract language.
26
+ - Production drawings, takeoffs, BOMs, nests, RFQs, quotes, reports, screenshots, or
27
+ generated artifacts, including "anonymized" copies derived from them.
28
+ - Company profiles, logos, signatures, credentials, tokens, local absolute paths, or
29
+ exported cloud files.
30
+ - Facts that link this product to a private operating company, facility, customer, or
31
+ internal workflow unless separately approved for publication.
32
+
33
+ ## Fixture Rules
34
+
35
+ 1. Create fixtures from scratch; do not sanitize production files for public use.
36
+ 2. Prefix project-like identifiers with `SYNTHETIC-` or `EXAMPLE-`.
37
+ 3. Use `Example Customer`, `Example Vendor`, `Example Fabricator`, and
38
+ `Example City, ST` unless a test requires another obviously fictional value.
39
+ 4. Omit prices and commercial terms unless the test specifically requires them. When
40
+ required, label values as synthetic in the same fixture.
41
+ 5. Keep private inputs and generated outputs outside the repository in ignored
42
+ directories such as `private/`, `local-data/`, `customer-data/`, or `outputs/`.
43
+ 6. Run `python3 scripts/check-public-data.py` before every commit and release. Before
44
+ pushing, also scan the outgoing range with
45
+ `python3 scripts/check-public-data.py --range <remote-ref>..HEAD`.
46
+ 7. Put private names and identifiers, one per line, in the ignored local file
47
+ `.pi-steel/private-terms.txt`; the scanner checks them without committing the
48
+ denylist or echoing matched text.
49
+
50
+ Git history is public too. Removing a value from the current tree does not remove it
51
+ from prior commits. Use `npm run privacy:history` for a redacted audit; history
52
+ cleanup requires an explicit coordinated rewrite.
package/README.md CHANGED
@@ -1,8 +1,23 @@
1
1
  # pi-steel
2
2
 
3
- Structural steel estimating skills for the [Pi coding agent](https://pi.dev) — built by working steel estimators, not by people guessing what a takeoff is.
3
+ [![npm version](https://img.shields.io/npm/v/%40structupath%2Fpi-steel)](https://www.npmjs.com/package/@structupath/pi-steel)
4
+ [![CI](https://github.com/StructuPath/pi-steel/actions/workflows/ci.yml/badge.svg)](https://github.com/StructuPath/pi-steel/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+ [![Pi package](https://img.shields.io/badge/Pi-package-7c3aed)](https://pi.dev/packages?name=pi-steel)
4
7
 
5
- By [StructuPath](https://structupath.ai), from the team behind a production structural-steel fabrication shop in Denver, CO.
8
+ Structural-steel estimating skills for the [Pi coding agent](https://pi.dev):
9
+ validated takeoffs, plate nesting, guarded DXF output, draft vendor RFQs, and a
10
+ review-gated estimate pipeline.
11
+
12
+ Built by [StructuPath](https://structupath.ai).
13
+
14
+ ## Demo
15
+
16
+ ![pi-steel builds a synthetic estimate package](https://raw.githubusercontent.com/StructuPath/pi-steel/main/docs/assets/pi-steel-demo.gif)
17
+
18
+ This demo uses only the repository's synthetic fixture. It runs the complete
19
+ `takeoff → nest → draft RFQ` pipeline and publishes an isolated package with a
20
+ machine-readable `rfq_ready_for_review` outcome.
6
21
 
7
22
  ## Install
8
23
 
@@ -10,56 +25,122 @@ By [StructuPath](https://structupath.ai), from the team behind a production stru
10
25
  pi install npm:@structupath/pi-steel
11
26
  ```
12
27
 
13
- ## What's Inside
28
+ Then ask Pi:
14
29
 
15
- ### `steel-takeoff` — takeoffs, BOMs, tonnage
30
+ ```text
31
+ Do a structural-steel takeoff from these drawings and build a validated BOM.
32
+ Nest these rectangular plate parts on 96 × 48 stock.
33
+ Prepare a draft RFQ from this estimate.
34
+ ```
16
35
 
17
- Structural steel quantity takeoff with a bundled **AISC 16th Edition shapes database (477 shapes)** — W, HSS, angles, channels, pipe — plus scripts the agent runs directly:
36
+ Run the local dependency check when using the source repository:
18
37
 
19
- - `lookup-member.sh` — full property set for any AISC designation (`W14X30` → plf, d, bf, A, Ix, Sx, …)
20
- - `calculate-weight.sh` — BOM totals with connection and misc-steel allowances, tonnage, cost sensitivity
21
- - `validate-bom.py` — catches invalid designations, wrong grades, duplicate marks, unreasonable weights
38
+ ```bash
39
+ npm run doctor
40
+ ```
22
41
 
23
- Also includes reference guides for AISC shape families, takeoff procedures with worked examples, connection types and hardware weights, material grades, and bolt capacities.
42
+ ## What is included
24
43
 
25
- Ask your agent things like:
44
+ | Skill | Purpose | Primary outputs |
45
+ | --- | --- | --- |
46
+ | `steel-takeoff` | Validate member designations and calculate BOM weight and tonnage | Validated BOM and findings |
47
+ | `steel-nest` | Lay out plate parts with kerf, gap, and edge-margin controls | Nest results, cut list, reference drawings, and guarded burn DXFs |
48
+ | `steel-rfq` | Compile estimate data into a reviewable vendor request | Draft `.xlsx` RFQ and semantic workbook record |
49
+ | `steel-estimate` | Orchestrate the complete review-gated workflow | Immutable run directory, QA report, lineage, manifests, nesting, and draft RFQ |
26
50
 
27
- > "Do a takeoff from these drawings and build me a BOM"
28
- > "What's the lightest W-shape with depth ≥ 18" and Ix ≥ 1000?"
29
- > "Total tonnage on this BOM with 12% connections"
51
+ ### Takeoff
30
52
 
31
- ### `steel-nest` plate nesting & burn-table DXF
53
+ `steel-takeoff` provides structural-shape lookup, weight calculation, and BOM
54
+ validation helpers. It checks member designations, grades, duplicate marks, and
55
+ unreasonable weights. It calculates from supplied quantities and allowances; it
56
+ does not invent pricing.
32
57
 
33
- The plate-layout step CAM software does, minus the CAM seat: MaxRects bin-packing of parts onto stock plates with kerf/gap/edge-margin spacing, holes and rectangular cutouts, yield/scrap/reusable-drop numbers, and material cost. Outputs a labeled layout (PDF + PNG per plate), a cut list, and **one DXF per sheet for the burn table** (part outlines on `PROFILE`, holes on `HOLES`, origin at sheet corner — ready for ProNest/FastCAM/SigmaNEST import).
58
+ ### Nesting and DXF safety
34
59
 
35
- Honest about its limits: rectangular parts nest exactly; irregular parts nest by bounding box (flagged, never hidden); it deliberately does **not** emit G-code — kerf comp, lead-ins, and pierce points belong to your table's real post-processor.
60
+ `steel-nest` uses MaxRects bin packing for rectangular parts and reports yield,
61
+ scrap, reusable drops, and unplaced material. Irregular parts are estimated by
62
+ bounding box and are always flagged.
36
63
 
37
- > "How many sheets does this job need?"
38
- > "Nest these parts on 96×48 plate and give me the yield"
39
- > "Lay this out for the burn table"
64
+ Per-sheet `burn_plate_N.dxf` files are emitted only when the full nest is
65
+ complete and every supported hole remains inside its part. Otherwise, pi-steel
66
+ suppresses burn DXFs for the entire run and leaves clearly labeled estimating
67
+ and reference artifacts for review. It does not emit G-code or claim
68
+ machine-specific CAM compatibility.
40
69
 
41
- Requires `ezdxf`, `matplotlib`, `numpy` (`pip install ezdxf matplotlib numpy`).
70
+ ### Draft RFQs
42
71
 
43
- ### `steel-rfq` vendor quote requests
72
+ `steel-rfq` groups material by stock family, carries approved nesting data into
73
+ the workbook, and provides fill-in columns for vendor pricing. Workbooks remain
74
+ drafts until a person reviews and issues them.
44
75
 
45
- Turns a steel estimate/takeoff spreadsheet into a standardized vendor RFQ (.xlsx): materials grouped the way vendors stock them (W-shapes / plate / flat bar), yellow fill-in pricing columns, nesting/drop reference, and terms & conditions — branded with **your** company profile. When `steel-nest` has run for the job, its cutting plan flows straight into the RFQ's nesting table.
76
+ Copy the example company profile to a private project location:
46
77
 
47
- The three skills chain into a full estimating pipeline: **takeoff → nest → RFQ**.
78
+ ```bash
79
+ mkdir -p .pi-steel
80
+ cp skills/steel-rfq/assets/company-profile.example.json \
81
+ .pi-steel/company-profile.json
82
+ ```
48
83
 
49
- One-time setup: copy `skills/steel-rfq/assets/company-profile.example.json` to `company-profile.json` and put in your company name, city, and payment terms. The skill will ask and offer to save it if you skip this.
84
+ Complete that local copy with approved company information. Never commit the
85
+ completed profile.
50
86
 
51
- > "Send this takeoff out for pricing"
52
- > "Generate an RFQ from this estimate"
87
+ ## Workflow and outcomes
53
88
 
54
- ## Requirements
89
+ ```text
90
+ estimate input
91
+
92
+ contract validation → plate nesting → draft RFQ compilation
93
+
94
+ QA findings + lineage + immutable run manifest
95
+
96
+ ready | needs_review | blocked
97
+ ```
55
98
 
56
- - `jq` and `python3` (with `pandas` + `openpyxl` for RFQ generation)
57
- - macOS or Linux
99
+ Each run is published below the requested output root and referenced by
100
+ `latest-run.json`. Exit status `0` means geometry-verified, `2` means human
101
+ review is required, and `3` means the workflow is blocked. A blocked run never
102
+ contains an RFQ workbook.
58
103
 
59
- ## License
104
+ ## Public-repository safety
60
105
 
61
- MIT. AISC shape data derived from the publicly available AISC Shapes Database v16.0.
106
+ This repository and all committed examples are public. Do not add:
62
107
 
63
- ---
108
+ - company profiles or internal business context;
109
+ - customer, vendor, employee, project, bid, or contract data;
110
+ - live pricing, margins, terms, credentials, or private contact information;
111
+ - generated RFQs, takeoffs, PDFs, DXFs, spreadsheets, or output directories.
112
+
113
+ Keep operational inputs and generated artifacts outside the repository. See the
114
+ [public data policy](PUBLIC_DATA_POLICY.md) and
115
+ [data provenance record](DATA_PROVENANCE.md) before contributing.
116
+
117
+ ## Documentation
118
+
119
+ - [GitHub wiki](https://github.com/StructuPath/pi-steel/wiki)
120
+ - [Public data policy](PUBLIC_DATA_POLICY.md)
121
+ - [Data provenance](DATA_PROVENANCE.md)
122
+ - [Pi package catalog](https://pi.dev/packages?name=pi-steel)
123
+ - [npm package](https://www.npmjs.com/package/@structupath/pi-steel)
124
+
125
+ ## Development
126
+
127
+ ```bash
128
+ npm test # base, no-render suite
129
+ npm run test:full # PDF/PNG/DXF/LibreOffice smoke tests
130
+ npm run privacy:check # current public-repository data guard
131
+ npm run privacy:history # redacted audit of reachable history
132
+ npm run pack:check # npm contents and installed-runtime smoke test
133
+ npm run provenance:check # shape-data integrity and recorded decision
134
+ npm run release:check # complete release gate
135
+ ```
136
+
137
+ The current package version is `0.2.3`. `release:check` verifies the test,
138
+ privacy, package-content, shape-data integrity, ownership, license, and
139
+ redistribution contracts before publication.
140
+
141
+ ## License
64
142
 
65
- Building software for steel fabricators? See [structupath.ai](https://structupath.ai).
143
+ StructuPath-authored code, documentation, and the bundled StructuPath Structural
144
+ Shapes Database are distributed under the MIT license. Dataset ownership,
145
+ authorization, and integrity evidence are documented in
146
+ [DATA_PROVENANCE.md](DATA_PROVENANCE.md).
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@structupath/pi-steel",
3
- "version": "0.2.1",
4
- "description": "Structural steel estimating skills for the Pi coding agent AISC takeoffs, bills of material, tonnage calcs, plate nesting with burn-table DXF output, and vendor RFQ generation.",
3
+ "version": "0.2.3",
4
+ "description": "Structural steel estimating for Pi — validated takeoffs, plate nesting, guarded DXF output, and review-ready RFQ packages.",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "pi-package",
@@ -10,7 +10,6 @@
10
10
  "structural-steel",
11
11
  "estimating",
12
12
  "takeoff",
13
- "aisc",
14
13
  "bom",
15
14
  "rfq",
16
15
  "construction",
@@ -30,14 +29,46 @@
30
29
  "engines": {
31
30
  "node": ">=20.6.0"
32
31
  },
32
+ "scripts": {
33
+ "doctor": "python3 scripts/doctor.py",
34
+ "lint": "ruff check skills scripts tests",
35
+ "test": "python3 -m pytest -m 'not full_render'",
36
+ "test:contracts": "python3 -m pytest tests/test_contracts.py tests/test_run_manifests.py tests/test_rfq_workbook_contract.py",
37
+ "test:full": "python3 -m pytest -m full_render",
38
+ "test:runtime": "python3 -m pytest tests/test_runtime_bootstrap.py tests/test_run_manifests.py tests/test_installed_scripts.py",
39
+ "privacy:check": "python3 scripts/check-public-data.py",
40
+ "privacy:history": "python3 scripts/check-public-data.py --history",
41
+ "provenance:check": "python3 scripts/check-data-provenance.py",
42
+ "pack:check": "python3 -m pytest tests/test_package_contents.py tests/test_installed_scripts.py",
43
+ "pack:dry-run": "npm pack --dry-run",
44
+ "release:check": "npm test && npm run privacy:check && npm run pack:check && python3 scripts/check-data-provenance.py --release",
45
+ "prepublishOnly": "npm run release:check"
46
+ },
33
47
  "pi": {
34
48
  "skills": [
35
49
  "./skills"
36
- ]
50
+ ],
51
+ "image": "https://raw.githubusercontent.com/StructuPath/pi-steel/main/docs/assets/pi-steel-gallery.webp"
37
52
  },
38
53
  "files": [
39
54
  "skills",
55
+ "!skills/**/__pycache__",
56
+ "!skills/**/*.pyc",
57
+ "!skills/**/company-profile.json",
58
+ "scripts/doctor.py",
59
+ "scripts/check-public-data.py",
60
+ "scripts/check-data-provenance.py",
61
+ "pyproject.toml",
62
+ "requirements.txt",
63
+ "requirements-tested.txt",
64
+ "requirements-dev.txt",
65
+ "requirements-render.txt",
66
+ "DATA_PROVENANCE.md",
67
+ "DATA_PROVENANCE.json",
68
+ "docs/assets/pi-steel-demo.gif",
69
+ "docs/assets/pi-steel-gallery.webp",
40
70
  "README.md",
71
+ "PUBLIC_DATA_POLICY.md",
41
72
  "LICENSE"
42
73
  ]
43
74
  }
package/pyproject.toml ADDED
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "pi-steel-runtime"
3
+ version = "0.2.3"
4
+ description = "Python runtime dependencies and test configuration for pi-steel"
5
+ requires-python = ">=3.11,<3.14"
6
+ dependencies = [
7
+ "ezdxf>=1.3,<2",
8
+ "jsonschema>=4.23,<5",
9
+ "matplotlib>=3.9,<4",
10
+ "numpy>=2.1,<3",
11
+ "openpyxl>=3.1,<4",
12
+ "pandas>=2.2,<3",
13
+ ]
14
+
15
+ [project.optional-dependencies]
16
+ test = [
17
+ "pytest>=8.3,<10",
18
+ ]
19
+
20
+ [tool.pytest.ini_options]
21
+ addopts = "-ra"
22
+ testpaths = ["tests"]
23
+ markers = [
24
+ "full_render: requires LibreOffice and Poppler system tools",
25
+ ]
26
+
27
+ [tool.ruff.lint.per-file-ignores]
28
+ "tests/*.py" = ["E402"]
@@ -0,0 +1,5 @@
1
+ -r requirements.txt
2
+
3
+ # Base test tier; full-render tests also require the documented system tools.
4
+ pytest>=8.3,<10
5
+ ruff>=0.11,<0.12
@@ -0,0 +1 @@
1
+ # Full-render tests additionally require LibreOffice and Poppler system tools.
@@ -0,0 +1,9 @@
1
+ # Exact environment exercised during the v0.3 runtime bootstrap work.
2
+ jsonschema==4.26.0
3
+ openpyxl==3.1.5
4
+ pandas==2.3.3
5
+ pytest==9.0.2
6
+ ruff==0.11.13
7
+ ezdxf==1.4.4
8
+ matplotlib==3.10.9
9
+ numpy==2.4.1
@@ -0,0 +1,7 @@
1
+ # Required by the validated estimate and RFQ runtime.
2
+ ezdxf>=1.3,<2
3
+ jsonschema>=4.23,<5
4
+ matplotlib>=3.9,<4
5
+ numpy>=2.1,<3
6
+ openpyxl>=3.1,<4
7
+ pandas>=2.2,<3
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env python3
2
+ """Verify shipped shape data and enforce its public-release decision."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import hashlib
8
+ import json
9
+ from pathlib import Path
10
+
11
+
12
+ ROOT = Path(__file__).resolve().parents[1]
13
+ PROVENANCE_PATH = ROOT / "DATA_PROVENANCE.md"
14
+ PROVENANCE_RECORD_PATH = ROOT / "DATA_PROVENANCE.json"
15
+
16
+
17
+ def audit() -> dict:
18
+ errors: list[str] = []
19
+ try:
20
+ parsed_record = json.loads(PROVENANCE_RECORD_PATH.read_text(encoding="utf-8"))
21
+ except (OSError, json.JSONDecodeError):
22
+ errors.append("DATA_PROVENANCE.json is missing or invalid")
23
+ parsed_record = {}
24
+ if not isinstance(parsed_record, dict):
25
+ errors.append("DATA_PROVENANCE.json must contain an object")
26
+ parsed_record = {}
27
+
28
+ datasets = parsed_record.get("datasets", [])
29
+ if (
30
+ not isinstance(datasets, list)
31
+ or len(datasets) != 1
32
+ or not isinstance(datasets[0], dict)
33
+ ):
34
+ errors.append("expected exactly one declared shipped dataset object")
35
+ dataset = {}
36
+ else:
37
+ dataset = datasets[0]
38
+
39
+ shipped_file = dataset.get("shipped_file")
40
+ if not isinstance(shipped_file, str) or not shipped_file:
41
+ errors.append("declared dataset must have a non-empty shipped_file")
42
+ raw = b""
43
+ else:
44
+ shapes_path = ROOT / shipped_file
45
+ try:
46
+ raw = shapes_path.read_bytes()
47
+ except OSError:
48
+ errors.append("declared shape data is missing or unreadable")
49
+ raw = b""
50
+
51
+ checksum = hashlib.sha256(raw).hexdigest()
52
+ if checksum != dataset.get("sha256"):
53
+ errors.append("shape data checksum differs from DATA_PROVENANCE.json")
54
+
55
+ try:
56
+ rows = json.loads(raw)
57
+ except (UnicodeDecodeError, json.JSONDecodeError):
58
+ errors.append("declared shape data is not valid JSON")
59
+ rows = []
60
+
61
+ expected_rows = dataset.get("rows")
62
+ if not isinstance(rows, list) or len(rows) != expected_rows:
63
+ errors.append(f"expected {expected_rows} shape rows")
64
+ rows = rows if isinstance(rows, list) else []
65
+
66
+ required_fields = set(dataset.get("required_fields", []))
67
+ designations: list[str] = []
68
+ for index, row in enumerate(rows):
69
+ if not isinstance(row, dict):
70
+ errors.append(f"shape row {index} is not an object")
71
+ continue
72
+ missing = required_fields - row.keys()
73
+ if missing:
74
+ errors.append(f"shape row {index} is missing {sorted(missing)}")
75
+ designation = row.get("designation")
76
+ if isinstance(designation, str):
77
+ designations.append(designation)
78
+
79
+ if len(designations) != len(set(designations)):
80
+ errors.append("shape designations are not unique")
81
+
82
+ try:
83
+ provenance = PROVENANCE_PATH.read_text(encoding="utf-8")
84
+ except OSError:
85
+ errors.append("DATA_PROVENANCE.md is missing or unreadable")
86
+ provenance = ""
87
+
88
+ permission = dataset.get("redistribution_permission")
89
+ release_readiness = dataset.get("release_readiness")
90
+ if release_readiness == "ready" and permission != "authorized":
91
+ errors.append("release-ready data must have authorized redistribution")
92
+
93
+ documented_values = (
94
+ dataset.get("dataset_name"),
95
+ dataset.get("source"),
96
+ dataset.get("copyright_holder"),
97
+ dataset.get("license"),
98
+ dataset.get("authorization"),
99
+ str(expected_rows),
100
+ dataset.get("sha256"),
101
+ "Redistribution permission | Authorized",
102
+ "Release gate | Ready",
103
+ )
104
+ if not all(value and value in provenance for value in documented_values):
105
+ errors.append("DATA_PROVENANCE.md differs from the machine-readable record")
106
+
107
+ return {
108
+ "schema_version": "1.0.0",
109
+ "integrity": "passed" if not errors else "failed",
110
+ "dataset_id": dataset.get("dataset_id"),
111
+ "dataset_name": dataset.get("dataset_name"),
112
+ "copyright_holder": dataset.get("copyright_holder"),
113
+ "license": dataset.get("license"),
114
+ "release_readiness": release_readiness,
115
+ "redistribution_permission": permission,
116
+ "shape_rows": len(rows),
117
+ "sha256": checksum,
118
+ "errors": errors,
119
+ }
120
+
121
+
122
+ def main(argv=None) -> int:
123
+ parser = argparse.ArgumentParser(description=__doc__)
124
+ parser.add_argument(
125
+ "--release",
126
+ action="store_true",
127
+ help="fail while redistribution permission is unresolved",
128
+ )
129
+ args = parser.parse_args(argv)
130
+ report = audit()
131
+ print(json.dumps(report, indent=2, sort_keys=True))
132
+ if report["errors"]:
133
+ return 1
134
+ if args.release and report["release_readiness"] != "ready":
135
+ return 2
136
+ return 0
137
+
138
+
139
+ if __name__ == "__main__":
140
+ raise SystemExit(main())