Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ jobs:
done
- name: Validate rule packs (schema + envelope + check_fn refs)
run: node scripts/validate-rules.mjs
- name: Verify docs/RULES.md is up to date
run: node scripts/rules-report.mjs --check

check-links:
name: Link Check
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/health.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Health Check

# Daily self-test against the bundled fixtures. Fails fast if the
# extract → evaluate → score path regresses on a known-good plan.

on:
schedule:
- cron: '17 6 * * *' # 06:17 UTC daily
workflow_dispatch: {}

permissions:
contents: read

jobs:
selftest:
name: Self-test fixtures
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 20
- name: Run headless self-test
run: node scripts/status.mjs
- name: Upload status.json
if: always()
uses: actions/upload-artifact@v4
with:
name: status-json
path: status.json
if-no-files-found: warn
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ dist/
*.log
*.tmp

# Status / health-check output (regenerated by scripts/status.mjs)
status.json

# ── Private / owner-only artifacts ─────────────────────────────────────
# The `private/` directory is for owner-only notes that must NOT be
# committed (e.g., the plaintext list of donor emails that map to the
Expand Down
110 changes: 110 additions & 0 deletions assets/data/fixtures/selftest/clean-office.dxf
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
0
SECTION
2
ENTITIES
0
TEXT
8
A-TITLE
1
PROJECT: SAMPLE OFFICE BUILDING
0
TEXT
8
A-TITLE
1
GROSS FLOOR AREA: 12000 sq ft
0
TEXT
8
A-EGRESS
1
OCCUPANT LOAD: 80
0
TEXT
8
A-EGRESS
1
NUMBER OF STORIES: 2
0
TEXT
8
A-EGRESS
1
BUILDING HEIGHT: 24 ft
0
TEXT
8
A-CORR
1
CORRIDOR WIDTH: 48 in
0
TEXT
8
A-DOOR
1
DOOR CLEAR WIDTH: 36 in
0
TEXT
8
A-STAIR
1
STAIR TREAD: 11 in
0
TEXT
8
A-STAIR
1
STAIR RISER: 7 in
0
TEXT
8
A-STAIR
1
STAIR WIDTH: 44 in
0
TEXT
8
A-FIRE
1
FIRE SPRINKLER NFPA 13 SYSTEM PROVIDED
0
TEXT
8
A-FIRE
1
FIRE ALARM NFPA 72 PROVIDED
0
TEXT
8
A-FIRE
1
EXIT SIGN AT EACH DOOR
0
TEXT
8
A-FIRE
1
EMERGENCY LIGHTING WITH BATTERY BACKUP
0
TEXT
8
A-PARK
1
TOTAL PARKING: 40
0
TEXT
8
A-PARK
1
ACCESSIBLE PARKING: 2
0
TEXT
8
A-OCC
1
OCCUPANCY GROUP: B
0
ENDSEC
0
EOF
86 changes: 86 additions & 0 deletions assets/data/fixtures/selftest/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"$comment": "Golden expectations for Plan-Examiner self-test fixtures. Each entry's flagged_ids must be a SUBSET of actual FLAGGED finding ids; counts/score use inclusive [min,max] bands tolerant of rule-pack growth. min_facts is a lower bound on extracted fact keys (excluding internal _ keys and form-injected metadata).",
"version": "1.0.0",
"generated_against": {
"engine_version": "2.0.0",
"rules_index_version": "3.0.0",
"date": "2026-05-06"
},
"fixtures": [
{
"id": "clean-office",
"file": "clean-office.dxf",
"label": "Compliant 2-story office (Use Group B)",
"form_data": { "buildingType": "Commercial", "buildingCode": "2024 IBC", "city": "Test", "state": "CA", "country": "US" },
"expected": {
"source": "dxf",
"use_group": "B",
"min_facts": 12,
"required_fact_keys": [
"grossArea", "occupantLoad", "stories", "buildingHeightFt",
"corridorWidthInches", "doorWidthInches", "stairTreadDepthIn",
"stairRiserHeightIn", "stairWidthInches",
"totalParkingSpaces", "accessibleParkingSpaces", "occupancyGroup"
],
"required_bool_flags": {
"hasSprinklers": true,
"hasFireAlarm": true,
"hasExitSigns": true,
"hasEmergencyLighting": true
},
"must_flag_ids": [],
"must_not_flag_ids": ["IBC-1011.5", "IBC-1010.1", "ADA-404.2.3"],
"counts": {
"FLAGGED": { "min": 0, "max": 1 },
"REVIEW": { "min": 30, "max": 200 },
"PASS": { "min": 8, "max": 200 }
},
"score_band": { "min": 60, "max": 100 }
}
},
{
"id": "non-compliant-assembly",
"file": "non-compliant-assembly.dxf",
"label": "Assembly hall with substandard egress (Use Group A)",
"form_data": { "buildingType": "Commercial", "buildingCode": "2024 IBC", "city": "Test", "state": "NY", "country": "US" },
"expected": {
"source": "dxf",
"use_group": "A",
"min_facts": 10,
"required_fact_keys": [
"grossArea", "occupantLoad", "corridorWidthInches",
"doorWidthInches", "stairTreadDepthIn", "stairRiserHeightIn",
"occupancyGroup"
],
"must_flag_ids": ["IBC-1011.5", "IBC-1010.1", "ADA-404.2.3"],
"must_not_flag_ids": [],
"counts": {
"FLAGGED": { "min": 3, "max": 50 },
"REVIEW": { "min": 20, "max": 200 },
"PASS": { "min": 0, "max": 200 }
},
"score_band": { "min": 30, "max": 80 }
}
},
{
"id": "sparse-warehouse",
"file": "sparse-warehouse.dxf",
"label": "Sparse warehouse with minimal annotations (Use Group S)",
"form_data": { "buildingType": "Industrial", "buildingCode": "2024 IBC", "city": "Test", "state": "TX", "country": "US" },
"expected": {
"source": "dxf",
"use_group": "S",
"min_facts": 1,
"required_fact_keys": ["grossArea", "occupancyGroup"],
"must_flag_ids": [],
"must_not_flag_ids": [],
"counts": {
"FLAGGED": { "min": 0, "max": 5 },
"REVIEW": { "min": 10, "max": 200 },
"PASS": { "min": 0, "max": 200 }
},
"score_band": { "min": 40, "max": 100 }
}
}
]
}
86 changes: 86 additions & 0 deletions assets/data/fixtures/selftest/non-compliant-assembly.dxf
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
0
SECTION
2
ENTITIES
0
TEXT
8
A-TITLE
1
PROJECT: NON-COMPLIANT ASSEMBLY HALL
0
TEXT
8
A-TITLE
1
GROSS FLOOR AREA: 8000 sq ft
0
TEXT
8
A-EGRESS
1
OCCUPANT LOAD: 600
0
TEXT
8
A-EGRESS
1
NUMBER OF STORIES: 1
0
TEXT
8
A-EGRESS
1
BUILDING HEIGHT: 18 ft
0
TEXT
8
A-CORR
1
CORRIDOR WIDTH: 30 in
0
TEXT
8
A-DOOR
1
DOOR CLEAR WIDTH: 28 in
0
TEXT
8
A-STAIR
1
STAIR TREAD: 9 in
0
TEXT
8
A-STAIR
1
STAIR RISER: 8.5 in
0
TEXT
8
A-OCC
1
OCCUPANCY GROUP: A
0
TEXT
8
A-OCC
1
USE: ASSEMBLY HALL RESTAURANT
0
TEXT
8
A-PARK
1
TOTAL PARKING: 50
0
TEXT
8
A-PARK
1
ACCESSIBLE PARKING: 1
0
ENDSEC
0
EOF
26 changes: 26 additions & 0 deletions assets/data/fixtures/selftest/sparse-warehouse.dxf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
0
SECTION
2
ENTITIES
0
TEXT
8
A-TITLE
1
PROJECT: SPARSE INDUSTRIAL WAREHOUSE
0
TEXT
8
A-TITLE
1
GROSS FLOOR AREA: 25000 sq ft
0
TEXT
8
A-OCC
1
USE: WAREHOUSE STORAGE INDUSTRIAL
0
ENDSEC
0
EOF
Loading
Loading