Tier 1 SRS — 4D, 5D, 6D, 7D, Audit Trail, 3D Native¶
Foundation: BBC · DATA_MODEL · BIM_COBOL · MANIFESTO · TestArchitecture
Version: 1.1 | Date: 2026-03-20
Scope: Six bounded items, +4 scorecard points (27→31/36) + live 4D/5D DAOs
Architecture: Java DAO backend + thin bridge (ndjson TCP) + light Bonsai panel
Companion: StrategicIndustryPositioning.md (scorecard)
IfcOpenShell reference: Federation addon tandem/ — Python PoC proves all 4 capabilities
Architecture Principle¶
Java-smart / Python-dumb. The DAO layer owns all SQL, rollup logic, and business rules. The Bonsai addon receives pre-computed JSON and renders it. The thin bridge is ndjson over TCP — same pattern as the existing 28 actions in
DesignerServer.java. Each new capability = 1 DAO + 1 wire action + 1 panel.
┌─────────────────────────────────────────────────────────┐
│ Bonsai Addon (Python) LIGHT UI │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ 6D Panel │ │ 7D Panel │ │ Audit │ │ 3D View │ │
│ │ Carbon │ │ FM Sched │ │ History │ │ (existing)│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └───────────┘ │
│ │ │ │ │
│ └─────────────┴────────────┘ │
│ │ ndjson TCP │
├─────────────────────┼───────────────────────────────────┤
│ Java Backend │ SOLID DAO │
│ ┌──────────────────▼──────────────────────────────┐ │
│ │ DesignerServer.java (action dispatcher) │ │
│ │ case "carbonFootprint" → api.carbonFootprint()│ │
│ │ case "maintenanceSchedule" → api.maintenance()│ │
│ │ case "changelog" → api.changelog() │ │
│ └──────────────────┬──────────────────────────────┘ │
│ ┌──────────────────▼──────────────────────────────┐ │
│ │ DesignerAPIImpl.java (orchestration) │ │
│ │ opens component_library.db + BOM.db │ │
│ │ delegates to SustainabilityDAO / FacilityDAO │ │
│ │ delegates to ChangelogDAO │ │
│ └──────────────────┬──────────────────────────────┘ │
│ ┌──────────────────▼──────────────────────────────┐ │
│ │ DAO Layer (SQL against existing databases) │ │
│ │ SustainabilityDAO → component_library.db │ │
│ │ FacilityMgmtDAO → component_library.db │ │
│ │ ChangelogDAO → output.db │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ databases: component_library.db {P}_BOM.db │
│ output.db ERP.db │
└─────────────────────────────────────────────────────────┘
§1 — 6D Sustainability (embodied carbon + BOM rollup)¶
§1.1 Problem¶
The scorecard shows 6D=1* (PoC only). The Federation addon's
carbon_pipeline.py hardcodes ICE Database factors into Python. We need the
same capability re-grounded on ERP columns so the compiler can rollup carbon
per BOM tree — same pattern as cost rollup.
§1.2 Schema (migration V010 — already exists)¶
migration/V010_sustainability_columns.sql adds to M_Product in
component_library.db:
| Column | Type | Default | Source |
|---|---|---|---|
carbon_kg_per_unit |
REAL | 0 | ICE Database v3.0 (Bath Uni) |
recyclability |
TEXT | 'UNKNOWN' | UNKNOWN / HIGH / MEDIUM / LOW / NONE |
eol_strategy |
TEXT | 'LANDFILL' | LANDFILL / RECYCLE / REUSE / DECONSTRUCT |
lifespan_years |
INTEGER | 50 | Product-specific, CIBSE Guide M |
maintenance_interval_months |
INTEGER | 12 | Product-specific (shared with §2) |
Seed data: Populate from ICE Database factors for the ~30 product types already in component_library.db. Example:
UPDATE M_Product SET carbon_kg_per_unit = 0.15, recyclability = 'HIGH'
WHERE product_type = 'concrete' AND name LIKE '%C30%';
-- Concrete C30: 0.15 kgCO2e/kg (ICE v3.0)
UPDATE M_Product SET carbon_kg_per_unit = 1.55, recyclability = 'HIGH'
WHERE product_type = 'steel' AND name LIKE '%structural%';
-- Structural steel: 1.55 kgCO2e/kg (ICE v3.0, world average)
Migration: V010b_carbon_seed_data.sql (seed only, append-only).
§1.3 DAO — SustainabilityDAO¶
// Implementing TIER1_SRS.md §1.3 — Witness: W-6D-CARBON-1
public class SustainabilityDAO {
private final Connection compLibConn; // component_library.db
private final Connection bomConn; // {PREFIX}_BOM.db
// ── Record types ────────────────────────────────────
record CarbonLine(String bomId, String productName, String material,
int qty, double carbonPerUnit, double totalCarbon,
String recyclability, String eolStrategy) {}
record CarbonSummary(double totalCarbonKg, double carbonPerSqM,
double grossFloorAreaSqM,
List<CarbonLine> lines,
Map<String, Double> byDiscipline,
Map<String, Double> byMaterial) {}
// ── Queries ─────────────────────────────────────────
/**
* Rollup embodied carbon for a building.
* Joins m_bom_line (qty) × M_Product (carbon_kg_per_unit).
* Groups by discipline and material for breakdown charts.
*/
CarbonSummary carbonFootprint(String buildingId);
/**
* Single-element carbon lookup — for the properties popup (§26.6).
* Returns carbon + recyclability + EOL for one M_Product.
*/
CarbonLine elementCarbon(String bomId);
/**
* Material passport — circular economy inventory.
* Groups all BOM lines by material, sums mass, carbon, recyclability.
*/
List<MaterialPassportLine> materialPassport(String buildingId);
record MaterialPassportLine(String material, double totalMassKg,
double totalCarbonKg, String avgRecyclability,
int productCount) {}
}
SQL pattern (carbonFootprint):
SELECT bl.bom_id, p.name, p.product_type,
bl.qty, p.carbon_kg_per_unit,
(bl.qty * p.carbon_kg_per_unit) AS total_carbon,
p.recyclability, p.eol_strategy,
bom.m_product_category_id AS discipline
FROM m_bom_line bl
JOIN m_bom bom ON bl.m_bom_id = bom.m_bom_id
JOIN M_Product p ON bl.m_product_id = p.m_product_id
WHERE bom.building_id = ?
ORDER BY total_carbon DESC;
§1.4 Wire Protocol¶
→ {"action":"carbonFootprint","buildingId":"Terminal_KLIA"}
← {"success":true,"totalCarbonKg":284500.0,"carbonPerSqM":42.3,
"grossFloorAreaSqM":6726.0,
"lines":[{"bomId":"TE_GF_SLAB_01","productName":"C30 Slab",
"material":"concrete","qty":120,"carbonPerUnit":0.15,
"totalCarbon":18.0,"recyclability":"HIGH","eolStrategy":"RECYCLE"},...],
"byDiscipline":{"STR":180000,"ARC":60000,"MEP":44500},
"byMaterial":{"concrete":180000,"steel":80000,"glass":24500}}
§1.5 Light UI — Carbon Panel¶
┌─ 6D SUSTAINABILITY ──────────────────────────┐
│ Building: Terminal KLIA │
│ Total Embodied Carbon: 284,500 kgCO₂e │
│ Carbon Intensity: 42.3 kgCO₂e/m² │
│ │
│ By Discipline: By Material: │
│ ■ STR 180,000 (63%) ■ Concrete 180,000 │
│ ■ ARC 60,000 (21%) ■ Steel 80,000 │
│ ■ MEP 44,500 (16%) ■ Glass 24,500 │
│ │
│ [Refresh] [Export CSV] [Material Passport] │
└───────────────────────────────────────────────┘
Python addon: panel_sustainability.py — one draw() method, reads JSON
from bridge, renders labels + proportion bars. No SQL, no logic.
§1.6 Witnesses¶
| ID | Claim | Gate |
|---|---|---|
| W-6D-CARBON-1 | SustainabilityDAO.carbonFootprint() returns non-empty CarbonSummary for SH building |
Unit |
| W-6D-CARBON-2 | Total carbon = SUM(qty × carbon_kg_per_unit) across all BOM lines | Unit |
| W-6D-CARBON-3 | byDiscipline map keys match BOM discipline categories |
Unit |
| W-6D-CARBON-4 | Wire action carbonFootprint returns JSON with totalCarbonKg > 0 |
Integration |
| W-6D-SEED-1 | V010b seed data populates ≥ 10 products with carbon_kg_per_unit > 0 |
Migration |
§1.7 Scorecard Impact¶
6D: 1* → 2 (built-in). The DAO rollups carbon from ERP product table, not a hardcoded script. The data comes from the same M_Product catalog that drives procurement — carbon is a first-class product attribute.
§2 — 7D Facility Management (maintenance schedule + lifecycle)¶
§2.1 Problem¶
Scorecard 7D=1* (PoC only). The Federation addon's maintenance_manager.py
+ pm_scheduler.py implement full work-order CRUD in Python. We need the
same query capability on the ERP side: given a compiled building, generate
a maintenance schedule from product attributes.
§2.2 Schema (shared with §1 — V010 already exists)¶
Uses the same M_Product columns from V010:
- maintenance_interval_months — how often this product type needs service
- lifespan_years — expected replacement age
No new tables needed. The maintenance schedule is a computed view over the BOM — not a stored work order. Work orders are the ERP system's job (iDempiere C_Order). We provide the data to generate them.
Seed data (V010b, same migration as §1):
UPDATE M_Product SET maintenance_interval_months = 3, lifespan_years = 15
WHERE product_type = 'mechanical' AND name LIKE '%AHU%';
-- Air handling units: quarterly inspection, 15-year life
UPDATE M_Product SET maintenance_interval_months = 6, lifespan_years = 25
WHERE product_type = 'electrical' AND name LIKE '%DB%';
-- Distribution boards: 6-monthly check, 25-year life
UPDATE M_Product SET maintenance_interval_months = 60, lifespan_years = 100
WHERE product_type = 'concrete';
-- Structural concrete: 5-year inspection, 100-year design life
§2.3 DAO — FacilityMgmtDAO¶
// Implementing TIER1_SRS.md §2.3 — Witness: W-7D-FM-1
public class FacilityMgmtDAO {
private final Connection compLibConn;
private final Connection bomConn;
// ── Record types ────────────────────────────────────
record MaintenanceItem(String bomId, String productName,
String location, String discipline,
int intervalMonths, int lifespanYears,
int qtyInBuilding) {}
record MaintenanceSchedule(String buildingId,
List<MaintenanceItem> items,
int totalAssets,
Map<String, Integer> byInterval,
double annualMaintenanceEvents) {}
record LifecycleItem(String productName, String material,
int lifespanYears, int qty,
double replacementCostUnit,
double totalReplacementCost) {}
record LifecycleSummary(String buildingId,
List<LifecycleItem> items,
double totalReplacementCost,
Map<Integer, Double> costByDecade) {}
// ── Queries ─────────────────────────────────────────
/**
* Generate maintenance schedule for a compiled building.
* Joins m_bom_line × M_Product(maintenance_interval_months).
* Groups by interval for calendar planning.
*/
MaintenanceSchedule maintenanceSchedule(String buildingId);
/**
* Lifecycle cost projection — replacement cost over building life.
* Groups by decade: what needs replacing at 10/20/30/40/50 years.
*/
LifecycleSummary lifecycleCost(String buildingId, int horizonYears);
/**
* Asset register — COBie-compatible list of maintainable items.
* Returns location (storey + room), type, interval, manufacturer.
*/
List<AssetRecord> assetRegister(String buildingId);
record AssetRecord(String guid, String type, String location,
String floor, String system, String manufacturer,
int intervalMonths) {}
}
SQL pattern (maintenanceSchedule):
SELECT bl.bom_id, p.name, bom.m_product_category_id AS discipline,
parent_bom.name AS location,
p.maintenance_interval_months, p.lifespan_years,
COUNT(*) AS qty_in_building
FROM m_bom_line bl
JOIN m_bom bom ON bl.m_bom_id = bom.m_bom_id
JOIN M_Product p ON bl.m_product_id = p.m_product_id
LEFT JOIN m_bom parent_bom ON bom.parent_bom_id = parent_bom.m_bom_id
WHERE bom.building_id = ?
AND p.maintenance_interval_months > 0
GROUP BY p.m_product_id, bom.m_product_category_id
ORDER BY p.maintenance_interval_months ASC;
Annual maintenance events = SUM(12 / interval_months × qty) across items.
§2.4 Wire Protocol¶
→ {"action":"maintenanceSchedule","buildingId":"Terminal_KLIA"}
← {"success":true,"buildingId":"Terminal_KLIA","totalAssets":342,
"annualMaintenanceEvents":1860.0,
"byInterval":{"3":120,"6":80,"12":100,"60":42},
"items":[{"bomId":"TE_3F_AHU_01","productName":"AHU-140",
"location":"Level 3","discipline":"MEP",
"intervalMonths":3,"lifespanYears":15,"qtyInBuilding":24},...]}
→ {"action":"lifecycleCost","buildingId":"Terminal_KLIA","horizonYears":50}
← {"success":true,"totalReplacementCost":45000000.0,
"costByDecade":{"10":2000000,"20":8000000,"30":15000000,"40":12000000,"50":8000000},
"items":[...]}
§2.5 Light UI — FM Panel¶
┌─ 7D FACILITY MANAGEMENT ─────────────────────┐
│ Building: Terminal KLIA │
│ Total Maintainable Assets: 342 │
│ Annual Maintenance Events: 1,860 │
│ │
│ By Interval: │
│ ■ Quarterly (3mo) 120 assets 480 events/yr │
│ ■ Bi-annual (6mo) 80 assets 160 events/yr │
│ ■ Annual (12mo) 100 assets 100 events/yr │
│ ■ 5-yearly (60mo) 42 assets 8 events/yr │
│ │
│ Lifecycle Cost (50yr): $45,000,000 │
│ Peak decade: 30yr ($15M replacements) │
│ │
│ [Refresh] [Export COBie] [Lifecycle Chart] │
└────────────────────────────────────────────────┘
§2.6 Witnesses¶
| ID | Claim | Gate |
|---|---|---|
| W-7D-FM-1 | FacilityMgmtDAO.maintenanceSchedule() returns items with intervalMonths > 0 for SH building |
Unit |
| W-7D-FM-2 | annualMaintenanceEvents = SUM(12 / intervalMonths × qty) |
Unit |
| W-7D-FM-3 | lifecycleCost() costByDecade sums to totalReplacementCost |
Unit |
| W-7D-FM-4 | assetRegister() returns COBie-compatible records with location hierarchy |
Unit |
| W-7D-FM-5 | Wire action maintenanceSchedule returns JSON with totalAssets > 0 |
Integration |
§2.7 Scorecard Impact¶
7D: 1* → 2 (built-in). Maintenance schedule derived from ERP product master — same M_Product table that drives procurement. Lifecycle cost uses product cost × lifespan, not a separate FM system.
§3 — Audit Trail (bim_changelog + DAO interceptor)¶
§3.1 Problem¶
Scorecard CR/Audit=1* (spec only). The Federation addon's
asset_history table tracks asset changes. We need the same pattern on
the output.db side: every save() logs what changed, enabling
undo and multi-user conflict detection.
§3.2 Schema — migration V011_changelog.sql¶
New table in output.db (not component_library.db — changelog tracks design edits, not catalog changes):
-- V011_changelog.sql
-- Audit trail for design changes in output.db
-- APPEND-ONLY: never modify this migration after first run
-- Implementing TIER1_SRS.md §3.2 — Witness: W-AUDIT-DDL-1
CREATE TABLE IF NOT EXISTS bim_changelog (
changelog_id INTEGER PRIMARY KEY AUTOINCREMENT,
building_id TEXT NOT NULL,
variant_id TEXT, -- W_Variant.variant_id if save-triggered
user_id TEXT DEFAULT 'local',-- future: AD_User.user_id
timestamp TEXT NOT NULL, -- ISO 8601: 2026-03-20T14:30:00Z
action TEXT NOT NULL, -- SAVE, PLACE, MOVE, RESIZE, DELETE, PROMOTE
entity_type TEXT NOT NULL, -- C_OrderLine, W_Variant, M_BOM
entity_id TEXT NOT NULL, -- the row ID that changed
field_name TEXT, -- column name (null for INSERT/DELETE)
old_value TEXT, -- previous value (null for INSERT)
new_value TEXT, -- new value (null for DELETE)
m_bom_id INTEGER, -- FK to m_bom(M_BOM_ID) — spatial context
CONSTRAINT ck_action CHECK (action IN
('SAVE','PLACE','MOVE','RESIZE','DELETE','PROMOTE','UNDO'))
);
CREATE INDEX IF NOT EXISTS idx_changelog_building
ON bim_changelog(building_id, timestamp);
CREATE INDEX IF NOT EXISTS idx_changelog_entity
ON bim_changelog(entity_type, entity_id);
§3.3 DAO — ChangelogDAO¶
// Implementing TIER1_SRS.md §3.3 — Witness: W-AUDIT-DAO-1
public class ChangelogDAO {
private final Connection outputConn; // output.db
// ── Record types ────────────────────────────────────
record ChangeEntry(long changelogId, String buildingId,
String variantId, String userId,
String timestamp, String action,
String entityType, String entityId,
String fieldName, String oldValue,
String newValue, String bomId) {}
// ── Write ───────────────────────────────────────────
/**
* Log a single field change. Called by the interceptor wrapper
* around WorkOutputDAO.save().
*/
void logChange(String buildingId, String variantId,
String action, String entityType, String entityId,
String fieldName, String oldValue, String newValue,
String bomId);
/**
* Log a batch of changes from a save() — one entry per bbox
* that differs from its previous state.
*/
void logSave(String buildingId, String variantId,
List<DesignBBox> oldBboxes, List<DesignBBox> newBboxes);
// ── Read ────────────────────────────────────────────
/**
* Changelog for a building, newest first.
* Used by the Audit History panel.
*/
List<ChangeEntry> getChangelog(String buildingId, int limit);
/**
* Changes between two variants — the diff.
*/
List<ChangeEntry> getChangesBetween(String buildingId,
String fromVariantId,
String toVariantId);
// ── Undo ────────────────────────────────────────────
/**
* Undo the most recent N changes for a building.
* Reads old_value from changelog, writes it back to C_OrderLine.
* Logs the undo itself as action='UNDO'.
*
* @return number of changes reverted
*/
int undoChanges(String buildingId, int count);
}
§3.4 Interceptor Pattern¶
The interceptor wraps WorkOutputDAO.save() — no changes to save() itself:
// In DesignerAPIImpl.save():
public SaveResponse save(String buildingId, List<DesignBBox> bboxes, String label) {
// 1. Read current state (old bboxes)
List<DesignBBox> oldBboxes = workDao.recallLatest(buildingId);
// 2. Delegate to existing save
SaveResponse resp = workDao.save(buildingId, bboxes, label);
// 3. Log the diff (interceptor pattern)
if (resp.success()) {
changelogDao.logSave(buildingId, resp.variantId(),
oldBboxes, bboxes);
}
return resp;
}
Diff logic in logSave(): Compare old vs new bbox lists by bomId.
For each bbox that exists in both:
- If position changed → log MOVE with old/new x,y,z
- If dimensions changed → log RESIZE with old/new w,d,h
For bboxes only in new → log PLACE. For bboxes only in old → log DELETE.
§3.5 Wire Protocol¶
→ {"action":"changelog","buildingId":"SampleHouse","limit":20}
← {"success":true,"entries":[
{"changelogId":42,"buildingId":"SampleHouse","userId":"local",
"timestamp":"2026-03-20T14:30:00Z","action":"MOVE",
"entityType":"C_OrderLine","entityId":"OL_102",
"fieldName":"offset_x_mm","oldValue":"1500","newValue":"2000",
"bomId":"SH_GF_BD_01"},
...]}
→ {"action":"undoChanges","buildingId":"SampleHouse","count":1}
← {"success":true,"reverted":1,"currentVariantId":"v_003"}
§3.6 Light UI — Audit History Panel¶
┌─ AUDIT TRAIL ─────────────────────────────────┐
│ Building: SampleHouse │
│ │
│ 14:30 MOVE SH_GF_BD_01 x: 1500 → 2000 │
│ 14:28 RESIZE SH_GF_LR_01 w: 4000 → 4500 │
│ 14:25 PLACE SH_GF_KT_01 (new kitchen) │
│ 14:20 SAVE variant: "Option A" │
│ 14:15 DELETE SH_GF_ST_02 (removed store) │
│ │
│ [Undo Last] [Undo 5] [Export Log] │
└────────────────────────────────────────────────┘
§3.7 Witnesses¶
| ID | Claim | Gate |
|---|---|---|
| W-AUDIT-DDL-1 | bim_changelog table created by V011 migration, 11 columns + 2 indexes |
Migration |
| W-AUDIT-DAO-1 | ChangelogDAO.logChange() inserts row with correct timestamp + action |
Unit |
| W-AUDIT-DAO-2 | logSave() detects MOVE (position diff) and RESIZE (dimension diff) |
Unit |
| W-AUDIT-DAO-3 | undoChanges(1) reverts most recent change and logs UNDO action |
Unit |
| W-AUDIT-WIRE-1 | Wire action changelog returns entries with action ∈ {SAVE,MOVE,RESIZE,PLACE,DELETE} |
Integration |
| W-AUDIT-INTERCEPT-1 | save() with changed bboxes produces changelog entries; save() with identical bboxes produces zero entries |
Integration |
§3.8 Scorecard Impact¶
CR/Audit: 1* → 2 (built-in). Every design edit logged with old/new values, undo capability, diff between variants. Multi-user is separate (Tier 3) — this gives single-user audit trail now.
§4 — 3D Score Bump (2→3, native via Blender viewport)¶
§4.1 Problem¶
Scorecard 3D=2 (WF-BB Phase 2 + Federation 50K loading). Score 3 requires "native 3D" — prove the compiler's output is visually correct in a real 3D viewport, not just mathematically.
§4.2 What Already Exists¶
- Federation addon — loads
output.db→ Blender objects with materials, storey hierarchy, discipline colours. Proven on 48K+ elements. - WF-BB Phase 2 — wireframe bbox rendering with per-object BOUNDS display. Working in Bonsai viewport.
- BlenderBridge — incremental delta update (spec, partially wired).
§4.3 What's Needed¶
A single visual proof: compile SH (SampleHouse), load in Bonsai, screenshot, add to test evidence. No new code — just exercise the existing pipeline end-to-end and capture the result.
Steps:
- Run
./scripts/run_RosettaStones.sh classify_sh.yaml→ SH output.db - Open Bonsai, load output.db via Federation addon's "Load Federation DB"
- Verify: 58 elements visible, correct storey hierarchy, materials assigned
- Screenshot →
~/Pictures/Screenshots/SH_3D_proof.png - Add screenshot path to
RosettaStoneGateTest.javaG7 claim comment
§4.4 Wire Action (optional — for programmatic screenshot)¶
→ {"action":"screenshot","buildingId":"Ifc4_SampleHouse","outputPath":"/tmp/sh_3d.png"}
← {"success":true,"path":"/tmp/sh_3d.png","elementCount":58,"format":"PNG"}
This is a convenience action — the real proof is the manual screenshot.
The wire action calls bpy.ops.render.opengl() via the bridge.
§4.5 Witnesses¶
| ID | Claim | Gate |
|---|---|---|
| W-3D-VIS-1 | SH building loads in Bonsai with 58 visible objects | Visual |
| W-3D-VIS-2 | Storey hierarchy visible in outliner (GF, FF, Roof) | Visual |
| W-3D-VIS-3 | Screenshot saved to ~/Pictures/Screenshots/ |
Visual |
§4.6 Scorecard Impact¶
3D: 2 → 3 (native). The output is rendered in Blender's native viewport via the Federation addon. Not a plugin viewer, not a web viewer — the same GPU-accelerated viewport used by 3D artists. Blender IS the viewer.
§5 — Implementation Order¶
| Step | Item | Effort | Depends On | Deliverable |
|---|---|---|---|---|
| 1 | V010 migration (run) | 10 min | — | Columns exist in component_library.db |
| 2 | V010b seed data | 30 min | Step 1 | ≥ 10 products with carbon + interval data |
| 3 | SustainabilityDAO + test | 1.5 hrs | Step 2 | W-6D-CARBON-1..3 GREEN |
| 4 | FacilityMgmtDAO + test | 1.5 hrs | Step 2 | W-7D-FM-1..4 GREEN |
| 5 | V011 changelog migration | 10 min | — | bim_changelog table in output.db |
| 6 | ChangelogDAO + interceptor + test | 2 hrs | Step 5 | W-AUDIT-DAO-1..3, W-AUDIT-INTERCEPT-1 GREEN |
| 7 | Wire actions (3 new) | 1 hr | Steps 3,4,6 | W-6D-CARBON-4, W-7D-FM-5, W-AUDIT-WIRE-1 GREEN |
| 8 | 3D visual proof | 30 min | — | W-3D-VIS-1..3 (screenshot) |
| 9 | Panel stubs (Python) | 30 min | Step 7 | 3 light panels render JSON from bridge |
Total: ~8 hours, +4 scorecard points (27→31/36).
Steps 1-2 are shared between §1 and §2 (one migration serves both). Steps 3+4 can run in parallel (independent DAOs). Step 8 is independent of everything else.
§6 — What We Do NOT Build¶
| Out of Scope | Why | Where Instead |
|---|---|---|
| Work order CRUD | ERP system's job (iDempiere C_Order) | DocAction_SRS.md §1 |
| Technician assignment | FM system's job | Federation addon PoC |
| Sensor/IoT monitoring | Requires live infra, not compile-time | Federation addon PoC |
| Multi-user audit | Needs AD_User + AD_Session | Tier 3 |
| PDF report generation | Needs template engine | Phase RE (ACTION_ROADMAP.md) |
| COBie XLSX export | Needs 19-sheet writer | Phase RE (ACTION_ROADMAP.md) |
The boundary is clear: we add product attributes + rollup queries + audit logging. The ERP/FM systems consume our data. We don't replicate them.
§7 — Traceability Matrix¶
| SRS Section | Migration | DAO | Wire Action | Panel | Witnesses |
|---|---|---|---|---|---|
| §1 6D Carbon | V010 + V010b | SustainabilityDAO | carbonFootprint |
panel_sustainability.py | W-6D-CARBON-1..5 |
| §2 7D FM | V010 (shared) | FacilityMgmtDAO | maintenanceSchedule, lifecycleCost |
panel_fm.py | W-7D-FM-1..5 |
| §3 Audit | V011 | ChangelogDAO | changelog, undoChanges |
panel_audit.py | W-AUDIT-DDL-1, W-AUDIT-DAO-1..3, W-AUDIT-WIRE-1, W-AUDIT-INTERCEPT-1 |
| §4 3D | — | — | screenshot (optional) |
— | W-3D-VIS-1..3 |
| §5 4D Schedule | V012 + V012b | ScheduleDAO | constructionSchedule | panel_schedule.py | W-4D-SCHED-1..5 |
| §6 5D Cost | V012 (shared) | CostDAO | costBreakdown | panel_cost.py | W-5D-COST-1..6 |
Total new witnesses: 29 (18 from §1-§4 + 11 from §5-§6)
§5 — 4D Construction Schedule (CIDB sequence rules)¶
§5.1 Problem¶
4D scheduling was offline — W_Verb_Node verb ordering in the compiler pipeline. No live wire action for the UI to query a Gantt schedule.
§5.2 Schema (migration V012)¶
New columns on M_Product in component_library.db:
| Column | Type | Source |
|---|---|---|
construction_phase |
TEXT | Substructure / Superstructure / Architecture / Finishes |
construction_sequence |
INTEGER | 1=footings, 2=columns, 3=beams, 4=slabs, 6=walls, 7=doors, 8=roof, 10=finishes |
labor_resource |
TEXT | CONCRETE_GANG / STEEL_ERECTOR / MASON / CARPENTER / ROOFER |
labor_rate_rm_per_day |
REAL | CIDB 2024 daily rate (RM) |
labor_crew_size |
INTEGER | Workers per gang |
productivity_rate |
REAL | Units per day (M/day, M²/day, EA/day) |
equipment_type |
TEXT | MOBILE_CRANE_20T / CONCRETE_PUMP / SCISSOR_LIFT_8M |
equipment_rate_rm_per_day |
REAL | Daily hire rate (RM) |
equipment_duration_factor |
REAL | Fraction of labor days needing equipment |
§5.3 DAO — ScheduleDAO¶
Algorithm: BOM lines → group by (phase, sequence, discipline) → calculate durations (qty / productivity) → chain phases → Malaysian calendar (Mon-Sat, skip Sunday).
Wire action: {"action":"constructionSchedule","buildingId":"SH","projectStartDate":"2026-04-01"}
§5.4 Witnesses¶
| ID | Claim |
|---|---|
| W-4D-SCHED-1 | Tasks ordered by construction sequence |
| W-4D-SCHED-2 | Phases follow order: Substructure → Superstructure → Architecture → Finishes |
| W-4D-SCHED-3 | All dates valid, finish > start |
| W-4D-SCHED-4 | Labor days by resource populated with ≥3 resources |
| W-4D-SCHED-5 | addWorkingDays skips Sundays (Malaysian calendar) |
§6 — 5D Cost Breakdown (CIDB Malaysia 2024 rates)¶
§6.1 Problem¶
5D cost was offline BOM → C_OrderLine. No live 3-component cost breakdown.
§6.2 Schema (shared with §5 — V012)¶
Additional M_Product columns:
| Column | Type | Source |
|---|---|---|
unit_cost_rm |
REAL | CIDB/PWD 203A 2024 material rate (RM per unit) |
cost_uom |
TEXT | Trade UOM: EA (fittings, terminals), M (pipe/duct/beam), M2 (wall/slab/covering), KG (rebar). M3 for concrete volume only. See DISC_VALIDATION_DB_SRS §10.4.11 T3.5 |
cost_spec |
TEXT | Spec description (e.g. "Grade 50, shop fab, fire protection") |
§6.3 DAO — CostDAO¶
Three-component cost per BOM line: - Material = qty × unit_cost_rm - Labor = (qty / productivity) × crew_size × labor_rate_rm_per_day - Equipment = labor_days × equipment_duration_factor × equipment_rate_rm_per_day
Wire action: {"action":"costBreakdown","buildingId":"SH"}
§6.4 Dual Access Pattern¶
The same DAO serves both UI and back-office:
- Frontend (Bonsai): Panel sends costBreakdown for the loaded building → renders bar chart
- Back-office: CLI/REST client sends same JSON, selects any project by buildingId
- Print format: Backend controls JSON structure; client renders as needed (table, chart, PDF)
- Templates: M_Product rates are the template — users adjust via direct SQL or a rate editor
§6.5 Witnesses¶
| ID | Claim |
|---|---|
| W-5D-COST-1 | costBreakdown returns lines with material + labor + equipment > 0 |
| W-5D-COST-2 | grandTotal = materialTotal + laborTotal + equipmentTotal |
| W-5D-COST-3 | Percentages sum to 100% and material > labor |
| W-5D-COST-4 | byDiscipline keys match BOM categories (STR, ARC) |
| W-5D-COST-5 | byPhase covers construction phases (Superstructure etc) |
| W-5D-COST-6 | elementCost returns single-product lookup (10 beams × RM680 = RM6800) |
TIER1_SRS.md v1.1 — 4D-7D + Audit + 3D, 29 witnesses, 242 tests GREEN Architecture: Java DAO (solid) + thin bridge (ndjson) + light panel (Python) Cross-references: StrategicIndustryPositioning.md, BlenderBridge.md CIDB rates: Federation addon boq/comprehensive_boq_export.py → M_Product columns