Changelog¶
All notable changes to this project are documented here. Format: Keep a Changelog. Versioning: SemVer.
[Unreleased]¶
Changed (Session 19 — phase numbering resolved, frontend scope detailed; docs-only)¶
ROADMAP.md,docs/status/roadmap.md— v1.0 now ships at the end of Phase 3, and Phase 4 is explicitly post-v1.0. Marco's ruling, applied to both documents, which until now ran incompatible phase schemes (root: five phases 0–4; status page: three phases 1–3, its Phase 3 collapsing root's 3+4). RootROADMAP.md: thev1.0.0release-tag deliverable moves out of Phase 4 into the end of Phase 3 — "API polish, frontend, validation", weeks 11–14, the range already on the file — alongside a new licence-and-attribution-closure item and the repo flip that was already there; Phase 4 is retitled "External validation & dissemination (post-v1.0, weeks 15–16)" and carries a standing note that it reviews an already-released tool and does not gate the tag.docs/status/roadmap.md: "Phase 3 — Release and critical review" is split into Phase 3 — Release (repo flip, licensing/attribution closure,v1.0.0tag) and a new Phase 4 — External validation and dissemination (post-v1.0) (ISO 14071 critical review, methodology white paper, IFC EDGE / UNEP GlobalABC / DGNB partnerships). The two documents now number their phases the same way.docs/architecture/module-map.mdand thedocs/status/roadmap.mdreconciliation note — "v1.0 ships at the end of Phase 4" → "at the end of Phase 3". Under the split this is the correct statement, not a correction of a factual error: a Phase 2 module is still in scope for v1 and simply not started.docs/status/roadmap.md— the Frontend/Integration self-contradiction is resolved and the tracks now carry real scope. The page had simultaneously listed both as Phase-1 tracks, declared "No scope defined in this phase", and listed "any frontend work" as out of scope for Phase 1. The out-of-scope clause is struck (operational-energy settings UI and ASHRAE-based methods, superseded per AR-31, remain correctly excluded), and Marco's eight-item standard scope is on the page as checkboxes. Frontend: project/building intake UI (Keur Songho now, Kalan Deme So once thequantitiesmodule'sNotImplementedErroris resolved in Phase 2); a per-stage results view that rendersBLOCKED/Nonestates honestly, with their reason — never hidden, never zero-filled; the earth-vs-conventional comparison view; per-number provenance/citation drill-down; and a frontend test suite (component + integration) held to the same non-optional standard as the backend perAGENTS.md. Integration: a typed API client generated from the OpenAPI schema so it cannot drift from the contract; a frontend ↔ API integration test against a running or mocked backend; and CI wiring for the frontend build and test run — no frontend job exists inci.ymltoday. No Chiwara-specific requirements were invented beyond that list.docs/project/for-stakeholders.md— the release and the review are de-conflated. The page read "targeting version 1.0 in about four months, with an external ISO 14071 critical review as the target", fusing two distinct milestones into one. It now states that v1.0 means a complete, released tool — backend, frontend, docs site, seed-EPD library,v1.0.0tag — and that the ISO 14071 critical review is a separate, subsequent validation step that examines the released tool. The glossary entry gains the same timing ("planned for after v1.0 is released").
Flagged, not changed (Session 19 — this redefines v1.0 against a locked decision)¶
PROJECT.mdnow disagrees with the roadmap about what v1.0 contains, and was deliberately left alone.PROJECT.md:9heads its list "Deliverables at month 4 (v1.0)" and includes the methodology white paper and the external ISO 14071 methodology critical review; decision #13 ("Review path — external ISO 14071 methodology critical review at month 4") is one of the locked decisions. Under this ruling both are post-v1.0.CLAUDE.mdforbids silently deviating from a locked decision, soPROJECT.mdwas not edited and adecision-reviewGitHub issue is owed — the same requirement decision #17 carries and has open against it.- Cross-doc frontend tension persists by instruction. Root
ROADMAP.mdschedules the three headline frontend deliverables under its Phase 3 anddocs/architecture/index.md:9labels the client "React frontend (Phase 3)", whiledocs/status/roadmap.mdstarts the frontend track in Phase 1. The brief was to detail the track on the status page, not to relocate root's line items, so the status page now states plainly that the track runs across both.PROJECT.mddecision #10 ("Minimal React … Not the focus in v1") is compatible as written and untouched. - Reviewed and left standing:
METHODOLOGY.md:3("target of an external ISO 14071 critical review at month 4") anddocs/index.md:11("target of external ISO 14071 critical review") state the review as a goal without implying it gates the release; Phase 4 at weeks 15–16 is month 4, so both remain accurate. The historical Session 14 log entry inROADMAP.mdstill says "v1.0 ships at the end of Phase 4"; session logs are a dated record of what was decided then and were not rewritten.
Added (Session 17 — climate-data licence clearance and the cooling-load method pivot; docs-only)¶
docs/methodology/climate-data-licence-clearance.md— new investigation report, the companion to the EPD source licence clearance and structured to match it. Eleven candidate climate / cooling-load sources cleared or blocked against a test the EPD clearance never had to apply: AGPL downstream parity — every recipient of the conveyed program must receive the same freedom to redistribute and modify, so a licence granting rights to this project only fails however generous it is. CLEARED: NASA POWER (US-Government open data, no copyright), ERA5/Copernicus and PVGIS (attribution), and RTS/CTF coefficients computed from first principles. BLOCKED: ASHRAE CLTD/CLF/SCL tables, ASHRAE Weather Data Viewer, climate.onebuilding.org ("All Rights Reserved"), Meteonorm (EULA §II.c.8.2 bars indirect provision via software, CHF 3 000/instance penalty at §VI). RESTRICTED: the ASHRAE Software Developer License — $2 000, but non-exclusive to the purchaser, and covering Chapter 14 design conditions rather than the cooling-load tables. MIXED: EnergyPlus EPW, per file. Includes the US/EU copyright analysis (Feist; Directive 96/9/EC with BHB / Football Dataco; ProCD v. Zeidenberg), and why ASTM v. Public.Resource.Org — in which ASHRAE was a plaintiff — does not help.docs/status/roadmap.md— Phase 1 progress checklist, four tracks (Research, Backend, Frontend, Integration), 4 / 12 modules. Carries a note that it tracks the v1 module set and therefore lists modules the phase plan schedules for Phase 2; ticking a box does not move a module into Phase 1. Requirespymdownx.tasklist, now enabled indocs/mkdocs.yml.docs/project/for-stakeholders.md— the plain-language grant-reviewer / non-technical-stakeholder page enters the repo, under a new top-level "The Project" nav section placed immediately after Home. Renamed fromFor Stakeholders & Reviewers.mdto match the repo's kebab-case convention.THIRD_PARTY.md§2a — Climate and weather data, the full verdict table from the new clearance, inserted before §3 so the existing section numbering is untouched.
Changed (Session 17 — action register, METHODOLOGY.md §5a, stale-reference sweep; docs-only)¶
docs/status/action-register.md— AR-31 resolved. "Determine whether transcribed ASHRAE values require a licence" is answered: they cannot be vendored in the public AGPL repo, and no available ASHRAE grant would change that. Resolved rather than deleted, so the reasoning stays visible. Register 59 → 63 rows; Claude Code rows 8 → 11.docs/status/action-register.md— AR-60 opened: the methodology pivot. Out — ASHRAE CLTD/CLF/SCL table lookup by latitude band. In — an RTS / Heat-Balance kernel (or an independently implemented EN ISO 52016-1) with CTF and radiant-time-factor coefficients computed in project code, driven by NASA POWER, with ERA5 and PVGIS secondary. ASHRAE itself deprecated CLTD/CLF/SCL in favour of RTS, so the licensing answer and the accuracy answer are the same answer. A from-scratch build, not a migration —domains/energy,domains/thermalandlca.b6are five-line scaffolds andservices/interpolation.pyhas zero call sites acrossbackend/, verified by grep rather than assumed. The hemisphere-transform concern from the interpolation-status audit is moot: NASA POWER has no hemisphere-specific data gap.docs/status/action-register.md— AR-62 opened: the workbook does not interpolate to the project's latitude.1c_energetic_analysis.xlsx's "Chosen correction" blockU6:AA16holds hard literals that are bit-for-bit the 12°N block, and the workbook has no latitude input cell at all. Keur Songho at 14.7°N therefore computes on 12°N — not the interpolated value, not even nearest-neighbour (16°N is 1.3° away, 12°N is 2.7°). Mean |Δ| 0.341 K, typical 0.75 K, max 1.687 K, against a 5–20 K base CLTD drivingq = U·A·CLTD— 8–30 % on that surface-hour's cooling load. When the calculation is built it must diverge from this, with a divergence test citing the normative basis; a parity test would make the defect part of the acceptance criteria.docs/status/action-register.md— AR-63 opened:seed_epds/drives no calculation.grep -rn "seed_epds" backend/app/ --include="*.py"returns zero hits; the four provenance-gated records are read only bytest_seed_epd_integrity.py, while all 18 A1–A3 factors ina1_a3.pyare inline_Factor(...)literals — two parallel, disconnected systems. Deliberately scoped rather than fixed: no 18-material migration now (a real refactor, entangled with AR-46's mass path and per-m²/per-kg declared units); B3b establishes theseed_epds/-first lookup pattern infactor_for()for its own batch (cement-VDZ, rebar, crushed rock, sand). The other fourteen stay inline and are tracked as explicit debt.docs/status/action-register.md— AR-61 opened: three Phase 3 legal-review items — EU sui generis database right on any re-expressed ASHRAE-derived coefficients (contingent on ever revisiting that path), contract-versus-copyright on residual ASHRAE-informed methodology knowledge, and NASA POWER / ERA5 / PVGIS attribution wording in docs site, API responses and exported reports, mirroring the ÖKOBAUDAT discipline of AR-28.METHODOLOGY.md§5a revised, not appended — retitled "Interpolation policy". The ASHRAE-latitude-band framing is replaced by continuous gridded climate data and first-principles load computation. All five rules survive unchanged in substance and now govern spatial grid-point interpolation: linear (bilinear on two axes) by default, mandatory audit trail, extrapolation raises rather than proceeding silently, nearest-neighbour forbidden by default, one cross-cutting service. Rule 4 gained AR-62 as its worked example.backend/app/services/interpolation.py— docstring only;interpolate_linear()remains aNotImplementedErrorstub and no behaviour changed. Three corrections: NASA POWER replaces ASHRAE as the data source; the consumer isdomains/energy, notservices/energy, which does not exist; and grid-factor interpolation is assigned toservices/proxy_engine, notlca.b6. The third was checked againstdocs/architecture/data-flow.mdrather than taken on instruction — data-flow.md already saidproxy_engine, so the docstring was the side that was wrong.- Stale-reference sweep beyond the brief.
docs/architecture/data-flow.md— theenergynode label, the interpolation trigger table (now NASA POWER grid nodes), footnote ❶, and the "tabulated reference points" phrasing.docs/methodology/module-inputs.md— the B6 climate-library input block and its "±25% for CLTD method" uncertainty line.docs/methodology/epd-source-licence-clearance.md§5 item 2 — struck through and closed, pointing at the new clearance.THIRD_PARTY.md§2 ASHRAE row marked superseded, and the §3 legal-review blockquote re-pointed. - Left untouched and tracked, not silently skipped:
PROJECT.mdlines 275 and 285 still describe ASHRAE latitude bands in the cross-cutting interpolation convention.PROJECT.mdis the locked-decisions record and was outside this session's stated scope; the follow-up is recorded on AR-60.backend/app/domains/lca/b6.py's one-line docstring still says "simplified CLTD/CLF" and will be rewritten when the module is built. - Test suite unchanged at 204 passed / 2 skipped / 9 xfailed — the Session 16 baseline exactly, as a docs-and-docstring session should be.
ruff check,ruff format --check(139 files),mypy(51 files) andmkdocs build --strictall clean.
Added (Session 16 — glass seed EPD, AR-56 resolved provisionally, AR-59 opened; PR #32, 7c9dfee)¶
backend/app/data/seed_epds/glass.json— fourth seed EPD. Bundesverband Flachglas e.V. float glass (ÖKOBAUDATe94dbfcc-56f2-4e55-8e0e-c2e8a2b576ae, v00.00.016, EF 3.0), GWP-total A1–A3 = 3.06 kg CO₂-eq/m² on a native 1 m² @ 1 mm declared basis — read straight off the dataset, not derived by dividing an area figure through a grammage as the ceramic-tile and PE-film records were. Every figure and all metadata were re-verified live against the soda4LCA API before seeding and every pedigree axis scored on that evidence rather than copied from the previous batch:referenceYear 2024,dataSetValidUntil 2029, Independent external review, registered M-EPDM-EPD-FEG-002000(ift Rosenheim), annual-average time representativeness, ÖKOBAUDAT template dataset. Pedigree: reliability 1, completeness 2 (template dataset, no sample size disclosed — not scored 1), temporal 1 (2024 vs 2026, under three years, not lapsed), geographical 5, technological 2. Cascade step 3.- Flagged EF 3.0, not project-standard EF 3.1 — no EF 3.1 flat-glass successor exists in ÖKOBAUDAT; the Bundesverband Flachglas and Guardian Europe lineages were checked independently and both cap at EF 3.0 as of 2026-08-27. The older Bundesverband lineage (
dc96eace-…) is EN 15804 A1-generation, expired 2022, and carries noGWP-totalat all. RER→DEUgeography substitution recorded, not silent. The dataset declares its location asRER(Europe), a regional code that is not an ISO 3166-1 alpha-3 country and will not validate against the schema'sCountryAlpha3.data_geographycarriesDEU— the association's country — as the admissible stand-in, with the substitution and its slightly wider true scope stated inclimate_analogue_rationale.- Guardian Europe lineage deliberately not seeded (one record only, no averaging across two unreconciled lineages). Its live record
ec8ac2c5-…v00.00.036 is on the same 1 mm / 2.5 kg·m⁻² basis at 2.7157 kg CO₂-eq/m², an 11 % difference — not the 4.33 mm / ~5× per-kg divergence the seeding brief described. The decision stands; the stale rationale was replaced with the arithmetic that holds.
Changed (Session 16 — action register; PR #32, 7c9dfee)¶
docs/status/action-register.md— AR-56 resolved (provisional). The row existed and was Open, so it was resolved rather than duplicated. A5 site electrical energy is taken as 100 % diesel-generator-supplied with no grid connection, per the product-owner statement of 2026-08-27, "until specified otherwise". This is a product-owner statement and not a Chiwara field confirmation, so it is labelled provisional and explicitly not closed as final. It unblocks AR-54's sourcing on a diesel-generator basis — a fuel-combustion factor, not the Senegalese grid factor AR-09 asks for, which therefore no longer closes AR-54 on its own — while AR-54 itself remains open for the value. AR-54's "settle AR-56 first" instruction and the two narrative paragraphs calling AR-56 "the question nobody has asked" were corrected in the same pass.docs/status/action-register.md— AR-59 added. The seeded glass record diverges from the workbook's uncitedLCI Final!I16base (9.71 kg CO₂-eq/m² @ 6 mm, ρ = 2200 kg/m³) by 1.89× on an area basis and 1.66× per kg. The density assumption is the suspected driver but 2500/2200 accounts for only 1.14× — the residual is unexplained, and the row records that rather than asserting the hypothesis. The seed is deliberately not wired intoa1_a3.py; the glass factor stays unresolved until AR-59 is reconciled. Same shape as AR-05 (floor tiles). Register 58 → 59 rows.- The AR-52 resemblance in
a1_a3.pywas flagged inside the session and answered, not assumed. The glass record carriesprovenance_verdict = BLOCKEDwithstatus = COMPUTED— the shape of the AR-52 defect. Answered (Marco, 2026-08-28): it is not AR-52 recurring.BLOCKEDcarries two distinct meanings in this file — the module-level gate (factor = None, hard block; earth, crude oil, floor tiles) and the narrowerprovenance_verdictfield (weak or interim citation, computed value still permitted) — and glass'sOKOBAUDAT_INTERIMis an explicitly permitted lower-confidence source class, unlike pesticide's uncited-chemical class which decision #26 bars outright. Glass is by design and functionally correct. The residual is a naming and documentation hazard, not a logic bug: one enum value means two things depending on which field it is attached to, with nothing in the code saying so. Fix is a one-line docstring clarification, deferred to the B3b session by decision — B3b touches the same file for the cement factor, so it costs no extra discovery. Recorded on AR-59 with Claude Code as owner. No.pyfile was touched this session. - Test suite: 204 passed / 2 skipped / 9 xfailed (was 200/2/9 at
25d953c; +3 from PR #30's seed records, +1 for glass —test_seed_epd_provenance_gate[glass.json], added by the parametrised collector with no test file edited).ruff check,ruff format --check(137 files),mypy(51 files) andmkdocs build --strictall clean.
Sessions 11–13 deferred their changelog entries. PRs #27, #28 and #29 shipped without a
CHANGELOG.mdorROADMAP.mdentry; the #29 commit message records the deferral explicitly. The four entries below were written in Session 14 from the merged commits, and their test counts were measured by runningpytestat each merge commit rather than recalled.
Added (Session 14 — docs sweep: roadmap page, AGENTS.md, scaffold-status reconciliation; docs-only)¶
AGENTS.md(repo root, new) — the engineering standard every contributor, human or AI, works to, stated once so it can be pointed at in review. Eight rules: readability over cleverness; tests are not optional; no silent placeholders (a missing or uncited value raises, never defaults — the BLOCKED/Nonepattern, AR-52); small single-purpose PRs; docs ship in the same PR as the behaviour they describe; provenance and citations are a code-quality requirement, not a separate concern; lint and type checks are enforced, never bypassed; and a scaffold or stub says so, in the code and the docs, never ambiguous about its real status. Framed against the fact that this codebase is AGPL-3.0, published, and targets external ISO 14071 critical review.docs/status/roadmap.md— the narrative roadmap page (drafted out-of-band, previously untracked) is now tracked and wired in as the first entry under Status in the mkdocs nav. It separates modules built from factors and masses sourced — a module can be fully implemented, tested and merged while still emittingNone, because one half ofE = weight × factoris deliberately BLOCKED — and states the critical path to the first citable number: seed the clean factors, re-source the six A1-only records, obtain the Chiwara earth data package, rebuild the mass path. Steps 3–4 gate on field data and are the true long poles.
Changed (Session 14 — docs sweep; docs-only)¶
docs/architecture/module-map.md— the scaffold-vs-map inconsistency is resolved. All seven files (b4,b6,c1,c2,c3,c4,d) were opened and read, not assumed: each is exactly five lines — AGPL header, one-line docstring,# TODO: Phase 2 — see ROADMAP.md. No function, no schema, no test, no behaviour of any kind to call. The status table now tags all seven scaffold only — 5-line file, no logic; the mermaid legend says the same for both amber and grey; and a new!!! dangernote states that amber means the Excel sheet has been reverse-engineered, not that the module is partly built — amber and grey differ only in whether a Step-1 spec exists, the code state is identical. A second note reconciles the Phase column: all seven are Phase 2, matching the TODO the files themselves declare, so the column is no longer provisional; "in scope for v1" and "Phase 2" were never in conflict, because v1.0 ships at the end of Phase 4. Also corrected a stale count — the table said "Three modules have reached implemented" when four have (quantities,lca.a1_a3,lca.a4,lca.a5).docs/methodology/lca-stages-b4-c3-c4-d-spec.md— one status blockquote added at the top; the spec itself was not rewritten. It states in the present tense throughout that "lca.b4raises", "lca.c3raises", which reads as shipped behaviour; the note records that these are requirements on modules that do not yet exist.docs/status/action-register.md— AR-58 resolved. The row already existed and was Open, so it was resolved rather than duplicated. Decision recorded: v1 declares A5 as on-site plant energy only. Every other EN 15804+A2 §6.3.4.4 sub-scope — material installation losses and their A1–A4 plus C1–C4 burden, on-site water, packaging waste, temporary works, transport of plant and workers (haul energy within A5), and site earthworks — is out of v1 scope by declaration: a scope limitation under EN 15804+A2 §6.3.5, carried inA5Result.scope_noteon every response, not a data gap. Revisit for v2 or earlier if a critical reviewer requires a quantified cut-off; installation losses are the first candidate. The narrative below the tables updated accordingly — AR-56 (generator vs grid) is now the only open A5 question, and it still gates AR-54's sourcing.ROADMAP.md—lca.a5ticked under Phase 1. The top-line Current status banner, still reading "Next: Session 6 —lca.a1_a3", replaced with the actual state: four modules implemented, all four reportingNone, the remaining Phase 1 work being sourcing rather than module-building. Session-log entries added for Sessions 11–14, with a note that the A5 spec and register narrative label the A5 loop "Session 9" — a numbering slip; this log is the authoritative sequence.docs/mkdocs.yml— new "Status → Roadmap" nav entry, placed first in the section.- No
.pyfile touched. Test baseline unchanged: 200 passed / 2 skipped / 9 xfailed;mkdocs build --strictclean.
Added (Session 13 — A5 construction stage + lca.a5 implementation; PR #29, 25d953c)¶
docs/methodology/lca-a5-construction-spec.md— full reverse-engineering of theA5sheet (B4:H8) of2_lca_vn.xlsx, withC1(Demolition)andINTERPRETATIONread for cross-check and all 12 sheets enumerated first; raw formulas (data_only=False) and computed values (data_only=True). Step 1 fork outcome: an A5 calculation EXISTS, so it was reverse-engineered rather than proposed.backend/app/schemas/a5.py— Pydantic schemas for A5, including a newSiteEquipmentenum. A5 is the first stage that is not material-keyed: one row per item of site plant (generator, cutting machine, welding machine) rather than perMaterialCategory.backend/app/domains/lca/a5.py— A5 module (scaffold → implementation) computingA5_e = power[kW] × usage[h] × factor[kg CO₂-eq/kWh],total = Σ,share = G/$G$8— all VALIDATED (Axis 1). The sheet is already in the EN 15804+A2 energy-carrier form, so unlike A4 no algebraic reduction was needed and parity is exact to double precision. No CORRECTED formula on the sheet, so no divergence fixture (decision #23).factor_for()raises; all three plant items are BLOCKED and the GWP total isNone— buttotal_energy_demand= 1 080 kWh is reported, because power × hours needs no factor. That split is the shape every blocked module should aim for.A5Result.scope_notestates the scope limitation on every response.backend/app/api/routes/a5.py—GET /lca/a5/keur-songho; wired intobackend/app/main.py.backend/tests/fixtures/test_a5_construction.py,backend/tests/regression/test_a5_keur_songho.py,backend/tests/unit/test_a5.py— A5 fixtures (includingC1_GENERATOR_HOURS/C1_GENERATOR_IMPACT_KG_CO2for the forward C1 dependency), parity within 0.1 %, and the BLOCKED-parameter contract.
Changed (Session 13 — A5 construction stage; PR #29, 25d953c)¶
- The A5 site-energy factor is BLOCKED (Axis 2):
A5!F5:F7 = 0.26 kg CO₂-eq/kWh. Four independent reasons, any one sufficient: it is a bare literal repeated in all three plant rows with no source column, cell comment or legend anywhere on the sheet; decision #26 names A5 an energy-linked stage and bars EU grid factors outright, and0.26sits in the EU-average band; the same workbook carries a Senegal grid factor 4.2× larger (B1!D5 = 1.1, itself unverified — AR-09); and the largest consumer is a diesel generator, which draws no grid electricity at all, so a grid-shaped number is a category error whatever grid it came from. docs/status/action-register.md— five rows added: AR-54 (the site-energy factor; the A5 half of AR-09, so one IEA/IPCC EFDB lookup closes both — but AR-56 gates it), AR-55 (plant powers27.6 / 4.8 / 12 kWand usage=10*2 / 10*1 / 10*4h have no attribution of any kind;27.6 kWis an oddly precise genset figure and a 10-day construction period for a school complex is implausible on its face), AR-56 (the sheet may double-count the site's electricity — rows 6 and 7 are electrically-driven machines that on an off-grid rural site run off the row-5 generator; the workbook never states the site-power arrangement and the thesis records none, so the spec records the ambiguity rather than correcting it), AR-57 (lca.c1inherits the same uncited0.26by cell reference,C1!C5 = 'A5'!$C$5— exactly the shape of AR-17), AR-58 (A5 scope covers plant energy only).backend/app/schemas/units.py,backend/app/main.py,docs/api-reference/endpoints.md,docs/api-reference/openapi.json,docs/architecture/module-map.md,docs/methodology/module-coverage.md,docs/mkdocs.yml— updated for the new module and route.- Test suite: 200 passed / 2 skipped / 9 xfailed (was 162/2/8) — +38 tests, +1 xfail (the AR-54 BLOCKED-factor fixture).
- ROADMAP.md and CHANGELOG.md entries were deferred to a later docs sweep — cleared in Session 14.
Fixed (Session 12 — AR-52: anti-termite A1–A3 factor blocked as uncited; PR #28, 48a211e)¶
backend/app/domains/lca/a1_a3.py— the first defect in shipped code on the action register, not a gap in the data. Anti-termite (PESTICIDE) was returned withstatus = COMPUTEDandfactor = 0.650— a bare literal fromLCI Final!I17with no source note of any kind — while the same record'sprovenance_verdictandgeographic_provenancealready readBLOCKED. The module contradicted its own provenance verdict, and 1 445.75 kg of anti-termite contributed to a result that would present as computed. Decision #26 classes termiticide as a speciality chemical product, BLOCKED unless a supplier EPD from an approved programme operator is on file; none is.factor_for(PESTICIDE)now raisesValueErrorlike Earth, Crude oil and Floor Tiles, making anti-termite the fifth BLOCKED material — no parity target, no impact contribution, listed inincomplete_reason.backend/app/api/routes/a1_a3.py,backend/tests/fixtures/test_a1_a3_material_database.py,backend/tests/regression/test_a1_a3_keur_songho.py,backend/tests/unit/test_a1_a3.py,docs/api-reference/openapi.json— updated to treat anti-termite as BLOCKED, with the regression test that would catch the reversion.docs/status/action-register.md— AR-52 marked Resolved; the row is kept so the finding stays visible.- Nothing user-facing was wrong at the time — the A1–A3 total is
Nonefor other reasons — but the defect would have surfaced the moment AR-03…AR-06 cleared. AR-16 remains open: it questions the anti-termite reference area (roof area used for a foundation treatment), where this fix addressed the factor. Both must clear before anti-termite is defensible. - Test suite: 162 passed / 2 skipped / 8 xfailed (was 161/2/8).
- ROADMAP.md and CHANGELOG.md entries were deferred — cleared in Session 14.
Added (Session 11 — seed-EPD scoreboard for the 18-material lca.a4 inventory; PR #27, 178edbb, docs-only)¶
docs/status/seed-epd-scoreboard.md— every one of the 18 materials the tool actually handles (rows 5–22 of theA4sheet), scored for seed-EPD readiness against the Session 10 ÖKOBAUDAT resolution pass. This is the per-material view of the sourcing backlog; the action register remains the authoritative list of open items. Counts: 4 LEGIT · 4 PLACEHOLDER · 5 BLOCKED · 1 EARTH-TRACK · 1 CUT-OFF · 3 UNSOURCED. Batch 1 covers 10 of the 18; the other 8 are the "11–18 gap".- The 18-count reconciles across
quantities,lca.a1_a3andlca.a4. Two discrepancies did not, and were reported rather than papered over: (a)quantitiesemitsMaterialCategory.RAW_EARTHwhilelca.a1_a3andlca.a4key onMaterialCategory.EARTH— both raiseKeyError, verified, not inferred, latent only because both stages read the hardcoded mass path instead of callingquantities(→ AR-53); (b)quantitiesemits native units (m³/m²/L/kg) while the LCA stages consume kg for all eighteen — folded into AR-46, which already owns the mass-path rebuild, rather than opening a duplicate row.
Changed (Session 11 — seed-EPD scoreboard; PR #27, 178edbb, docs-only)¶
docs/status/action-register.md— four rows added: AR-50 (gravel has no admissible ÖKOBAUDAT dataset — the undried record is A1-only with noGWP-total, and the only survivor is thermally dried at 12.3× the undried figure, which a Senegalese site laying pit-run aggregate does not do), AR-51 (the only EF 3.1 PE-film successor isPE foil, dimpled— a drainage membrane, not the plain sheet film laid as a capillary barrier; the declared unit matches the inventory, so it would seed cleanly and be wrong in kind rather than in magnitude), AR-52 (the anti-termite code defect — fixed in Session 12), AR-53 (theRAW_EARTH/EARTHkey mismatch). Register 49 → 53 rows; Claude Code 5 → 7.docs/mkdocs.yml— new "Status → Seed-EPD scoreboard" nav entry.- Test suite: 161 passed / 2 skipped / 8 xfailed — unchanged, confirming docs-only. No seed JSON written, no
lcacode touched, no value re-resolved over the API; every UUID and number carried unchanged from the value-resolution pass. - ROADMAP.md and CHANGELOG.md entries were deferred — cleared in Session 14.
Added (Session 10 — ÖKOBAUDAT value resolution for the 10 seed-EPD candidates; docs-only, no seeds written)¶
docs/status/seed-epd-value-resolution.md— the resolved numeric and metadata facts for the sourcing sub-chat's ten ÖKOBAUDAT candidates, read from the soda4LCA REST API (/resource/processes/{uuid}?format=json, which unlike thedatasetdetailxhtml pages is not robots-blocked). Per record: GWP-total A1–A3 as declared, not normalised, the exact indicator string read, the declared unit with its verbatim basis, the EN 15804 reference version, the impact method actually used, and the current superseding version. Headline: only 2 of the 10 picks are seedable as they stand — six are EN 15804+A1-generation datasets that contain no GWP-total indicator at all, carrying the older CMLGlobal warming potential (GWP)(method ds77e416eb-…), where the sub-chat had recorded all ten asEN 15804+A2 → EF 3.0-based. Clean and EF 3.1: #02 rebar (0.642238043961809 kg CO₂-eq/kg, confirming 0.642) and #05 crushed rock (0.00827818122415727/kg). Clean after a version bump: #04 sand →286b0072-…v20.26.070 (0.0027386873388177/kg). A1-only and unusable: #03 gravel, #06 glass, #07 steel profile, #08 bitumen, #09 PE film, #10 tile. Cement recommendation: the VDZ industry-average CEM I (87371640-…v00.02.001,EPD-VDZ-20220153-IAG1-DE) at 665 kg CO₂-eq/t over CEM I 52,5 R at 817 kg CO₂-eq/t — the inventory carries generic "Portland cement" and 52,5 R is a finer-ground high-early-strength product sitting 23 % above the average; both are EF 3.0, so either carries a reconciliation note. Documents four unit traps left deliberately unresolved: cement is per tonne in two different encodings (meanAmount = 1000.0versusmeanAmount = 1.0plusconversion factor to 1 kg = 0.001, the latter present on only 1 of 11 records — neither field alone is sufficient); flat glass changes declared thickness 4 mm → 1 mm between the pick and its successor, a 4× error if carried forward; the undried gravel dataset was discontinued, leaving only a dried variant at 12.3× the undried figure; and ceramic tile rises 3.16× at identical 20 kg/m² grammage, which bears on AR-05. Also records three ingestion gotchas: EF 3.1 labels GWP-total asClimate changein English so a monolingual matcher silently finds nothing; the compliance string is the reliable A1/A2/EF discriminator; and a UUID is not a lineage — Sphera issues a new UUID per release, so every version string the sub-chat supplied was stale or wrong. Wired into the mkdocs Status nav.
Changed (Session 10 — ÖKOBAUDAT value resolution; docs-only)¶
docs/status/action-register.md— AR-49 added (SOURCING; owners Track-B sourcing + Marco): 376.476 kg of neat bitumen has no per-kg ÖKOBAUDAT match. The whole bitumen category (32 records) was searched against the live API; every candidate is declared per m² or is a formulated product — composite membrane27ccffb8-…(5 kg/m² grammage: bitumen + glass-fleece carrier + mineral surfacing), 60 %-water emulsion88c0f648-…(0.450 kg CO₂-eq/kg), polymer-modified thick coating85c48e5f-…(1.189 kg CO₂-eq/kg), or Chiwara primary data. No proxy selected — decision deferred, because this is a declared-unit reconciliation rather than a like-for-like substitution and cannot be settled by picking the nearest number. Records explicitly that the membrane's grammage may not be divided into 376.476 kg to synthesise an area, since it is the mass of the whole composite and not its bitumen fraction; and that the sub-chat's original pick (64da45fc-…) is additionally A1-only with no GWP-total, so it is unusable regardless. Register is now 49 rows; Track-B sourcing 9 → 10 and Marco 18 → 19 for the two-owner row.docs/mkdocs.yml— new "Status → Seed-EPD value resolution" nav entry.
Changed (Session 9 follow-up — Chiwara asks routed through docs; docs-only)¶
docs/status/action-register.md— two rows added, both owner Chiwara team. AR-47 (SOURCING): raw-earth primary data package — as-placed bulk density (kg/m³) and A1–A3 GWP for local earth extraction and preparation, asked as one request because they are the two halves ofE = weight × factorfor the largest material in the building (751.9 m³ corrected). The density is what AR-46 needs to rebuild the mass path; the factor is what AR-03 needs; the 1 800 kg/m³ implied byLCI Final(1 494 888.525 kg ÷ 830.5 m³) is back-solved and inherits AR-22. BBSC binder and adobe/BBSC process data requested in the same package (feeds AR-15). Records explicitly that decision #26 / theLCI Final!I6 hard block bars ÖKOBAUDAT for earth, so no substitute exists and a documented "no primary data" answer is itself the finding. AR-48 (ATTRIBUTION): confirmation that mixing water (299 583.55 kg, A1–A3 GWP = 0) is excluded by a declared EN 15804+A2 §6.3.3 cut-off rather than an undeclared convention, with a named confirming party — computationally inert, audit-trail only. Register is now 48 rows; Chiwara team owns 20.docs/methodology/chiwara-attribution-requests.md— the detail home for both: Item 15 (raw-earth data package, with the density-and-factor rationale, the acceptable-source list, the no-substitute rule, and explicit permission to answer "no primary data exists") and Item 16 (mixing-water cut-off confirmation, including why a low-stakes confirm is logged here rather than settled off-channel).
Changed (Session 9 — AR-46 mass-path finding registered; docs-only, no code fix)¶
docs/status/action-register.md— AR-46 added (DEV-BLOCKER, owner Claude Code):_KEUR_SONGHO_WEIGHTS_KGis a pre-correction mass path. Extracted from2_lca_vn.xlsxLCI Finalcolumn F — downstream of the spec-faithful quantities — it embeds C1 (gravel/sand transposition), C2 (full-circle vault arc: earth 830.5 m³ vs the corrected 751.9 m³, −9.5%) and pre-C4 glass, solca.a1_a3andlca.a4multiply corrected-era factors by spec-era masses. Not a current-build blocker: both aggregates are alreadyNoneand the a1_a3/a4 parity tests assert Excel replication, so they stay green. It blocks the first citable number — the fix is to rebuild the kg path from correctedquantitiesm³ × sourced densities (seed-EPD provenance gate applies; the densities back-solvable fromLCI Finalinherit AR-22), plus a drift test tying the mass path tocompute_keur_songho(). No code changed this session. Register is now 46 rows; Claude Code owns 5 of them. Tracked as issue #23, labelmethodology.
Changed (Session 8 follow-up — CF Pages row + branching rule; docs-only)¶
docs/status/action-register.md— AR-45 added (GOVERNANCE): Cloudflare Pages project configuration. Two symptoms observed during PR #21: theCloudflare Pagescheck reportsfailureat the "Building" stage on preview deploys (identically on #20 and #21) whilemaindeploys report success; and the production URLchwr-lca.pages.devserved the unmerged PR branch, including a page absent frommain. Dashboard-side fix, owner Marco. Register is now 45 rows.CLAUDE.md— branch hygiene rule added: always branch from a freshorigin/main, never off another feature branch. Rationale recorded inline — stacking means the PR carries the parent branch's commits, which conflict once that branch is squash-merged, and GitHub does not runpull_requestworkflows on a conflicted PR, so CI goes silent rather than red. Recovery command included.
Added (Session 8 — action register + architecture diagrams; docs-only)¶
docs/status/action-register.md— the single consolidated, criticality-ordered view of every open item, swept and de-duplicated fromPROJECT.md,ROADMAP.md, all open GitHub issues,chiwara-attribution-requests.md, and everydocs/methodology/*-spec.mdsourcing section. 44 rows across five sections: SOURCING (13, top — the path to any citable number), DEV-BLOCKER (5), ATTRIBUTION (9), LEGAL (7), GOVERNANCE (10). Columns: ID · Item · Category · Owner · Action required · Blocks · Status · Source link. Owner split: Chiwara team 18, Marco 17, Track-B sourcing 9, external 5, Claude Code 4, unassigned 1. Rows link to their issue/spec/file rather than duplicating content. Wired into the mkdocs nav under a new "Status" section.
Changed (Session 8 — action register + architecture diagrams; docs-only)¶
docs/architecture/module-map.md— the lead diagram is now a vertical EN 15978 modular boundary (A1–A3 → A4 → A5 → B1–B7 → C1 → C2 → C3 → C4 → D), colour-coded by current build status derived from the repo: green = implemented and tested (quantities,lca.a1_a3,lca.a4), amber = RE spec committed but unimplemented (b4,c3,c4,d), grey = not started (a5,b6,c1,c2), dashed = excluded from v1 per decision #2 (B1, B2, B3, B5, B7). Legend added; the LR dependency graph and the status tables are retained below it. Status table corrected — the stale "all modules are currently stub" line is gone, and per-module notes record why each stub is where it is.docs/architecture/data-flow.md— rebuilt as the tool pipeline view, explicitly distinct from the life-cycle-stage view. Vertical, numbered ① → ⑦, with the Bill of Quantities marked as the TOOL ENTRY POINT (the tool starts at the BoQ, not at raw-material extraction). Three edge colours: blue = pipeline sequence, teal = internal data dependency (quantitiesmass feedinga1_a3/a4/a5, resolved factors feeding the calculation modules), red dashed = module currently emitsNonebecause a factor it depends on is BLOCKED (lca.a1_a3partial,lca.a4total). Edge and node legends added; both point at the action register.docs/mkdocs.yml—site_name"chwr_LCA — Sankofa LCA" → "chwr_LCA"; new "Status → Action register" nav section.docs/index.md— H1 shortened to "chwr_LCA" to match the site header.CLAUDE.md— Step 6 of the module loop now also updatesdocs/status/action-register.md: close resolved rows, add a row for every new blocker. A session that raises on a new BLOCKED parameter without the matching register row is not complete.
Added (Session 7 — A4 transport RE + lca.a4 implementation)¶
docs/methodology/lca-a4-transport-spec.md— full reverse-engineering of the2_lca_vn.xlsxA4sheet (all 12 sheets enumerated first; raw formulas via openpyxldata_only=Falseplus computed values), cross-checked against Pedrazzi thesis pp. 58–61. Key structural findings: (A4-F1) the truck-power modelI·(F/G)·(C/D)·Kreduces algebraically to the EN 15804 per-t·km form mandated by decision #26, because its four vehicle constants are row-invariant — 45.114 g CO₂-eq/t·km; (A4-F2)A4!L11 = 182.4/(F11*N11)is a back-solve from an unexplained absolute constant (182.4 kg CO₂-eq), broadcast to every sea-freighted material via$L$11— 6.442 g CO₂-eq/t·km. Verdicts per decision #23: all four formulas VALIDATED (no CORRECTED formula exists in the sheet, hence no divergence fixture); both emission factors BLOCKED (Axis 2). Vendorability (decision #25) and geographic tags (decision #26) assigned; six sourcing/attribution tasks A4-1…A4-6 recorded.backend/app/schemas/a4.py— Pydantic schemas for A4:TransportMode(road truck, sea freight — only the modes the reference data actually uses),TransportLeg,TransportFactor,TransportLegResult(distance, transport work in t·km, factor, impact, route note, distance source, provenance),MaterialA4Result,A4Result. Re-exports the shared decision #23/#26 verdict enums fromschemas/a1_a3.py— one scheme across all LCA stages.backend/app/domains/lca/a4.py— A4 module implementingA4_i = Σ_legs (mass[t] × distance[km] × factor[kg CO₂-eq/t·km])(VALIDATED). Mass is read from the LCI mass path (lca.a1_a3.KEUR_SONGHO_WEIGHTS_KG) and never re-derived, mirroringA4!F = 'LCI Final'!F. Result policy identical tolca.a1_a3. Both modes BLOCKED in the shippedTRANSPORT_FACTORSregistry:factor_for()raisesValueError, 15 of 18 materials are BLOCKED and the aggregatetotalisNone. Water, Crude oil and Waste Oil are on-site (zero distance perA4!B25) and are COMPUTED at exactly 0 — a zero-length leg contributes nothing whatever the factor turns out to be.EXCEL_REFERENCE_FACTORScarries the workbook's own (BLOCKED) values, labelled "EXCEL REPLICATION ONLY", solely so the VALIDATED formula can be parity-tested without giving a BLOCKED parameter a default.backend/app/api/routes/a4.py—GET /lca/a4/keur-songhoendpoint mirroring the A1-A3 route; wired intobackend/app/main.py.backend/tests/fixtures/test_a4_transport.py— A4 fixtures: per-material truck/sea distances,A4!O5:O22impacts and theA4!O23total (3 772.674 kg CO₂-eq), and the two Excel-derived per-t·km magnitudes proven to reduce from the workbook cells. Integrity checks (18 rows, total = sum of rows, zero-distance set) plus two strict xfails for the BLOCKED road and sea factors (A4-1, A4-2/A4-3) and guards that the production registry carries no default and the replication registry is labelled as such.backend/tests/regression/test_a4_keur_songho.py— formula parity within 0.1% per material and for the project total underEXCEL_REFERENCE_FACTORS(observed error 0.000000%); leg-split test pinning rebar's sea leg to the workbook's 182.4 kg CO₂-eq anchor; distance-fidelity test; and the shipped-path test assertingtotal is Nonewith exactly 15 blockers.backend/tests/unit/test_a4.py— BLOCKED-parameter contract (factor_forraises for both modes), None-total policy, blocked materials still returning full leg detail, the zero-transport-work exemption (zero distance and zero mass), and the mass-source assertion.
Changed (Session 7 — A4 transport RE + lca.a4 implementation)¶
backend/app/schemas/units.py— addedUnitEnum.KMfor transport-leg distances (A4, later C2). No migration required:UnitEnumis not used as a database column type.backend/app/domains/lca/a1_a3.py— exportedKEUR_SONGHO_WEIGHTS_KGas a public alias of the existing LCI Final mass table, so downstream stages read mass from a public name instead of a private one. Additive only; no behavioural change.docs/api-reference/endpoints.md— documentedGET /lca/a4/keur-songho, including why the A4 total isnullfor every transported material rather than just some.docs/methodology/module-coverage.md,docs/architecture/module-map.md,docs/architecture/data-flow.md—lca.a4advanced stub → implemented.docs/mkdocs.yml— "A4 transport spec" page added to Methodology nav.docs/api-reference/openapi.json— regenerated for the new A4 route and schemas.- Test suite: 161 passed / 2 skipped / 8 xfailed (was 135/2/6). Ruff + mypy clean.
Changed (Session 6.1 — CI tooling)¶
pyproject.toml— pinnedruff==0.16.0in the dev extra (PR #19), so local and CI lint runs cannot drift apart between ruff releases.
Added (Session 6 — lca.a1_a3 implementation)¶
backend/app/schemas/a1_a3.py— Pydantic schemas for the A1-A3 product stage:MaterialA1A3Result(value, unit, factor, source citation, verdict, geographic-provenance tag +pending_african_dataflag, Weidema axis-4 score/note) andA1A3Result(per-material list + aggregate total +incomplete_reason). New enumsFormulaVerdict,ProvenanceVerdict,GeographicProvenance,MaterialResultStatus.backend/app/domains/lca/a1_a3.py— A1-A3 module implementingE = weight × factor(VALIDATED, decision #23) for all 18 materials. Authoritative factor table traced tomaterial-impact-database-spec.md§4/§5/§6 (no invented numbers; no ICE/BuildX per decision #4; aggregates share 0.002854 as a documented simplification). Result policy: per-material results ALWAYS returned with status; aggregatetotalisNonewhenever any material is not COMPUTED, withincomplete_reasonlisting them. BLOCKED (no value): Earth (decision #26 hard block), Crude oil (anomalous 134), Floor Tiles (anomalous 5.09). PROVISIONAL: Antirust paint (CORRECTED 4.87).factor_for()raisesValueErrorfor BLOCKED materials. Every EU-contextualised factor surfaces its "pending African data" flag (decision #26).backend/app/api/routes/a1_a3.py—GET /lca/a1-a3/keur-songhoendpoint mirroring the quantities routes; wired intobackend/app/main.py.backend/tests/regression/test_a1_a3_keur_songho.py— parity of the 14 computable materials within 0.1% of spec §4; asserts total isNonewith the four blockers listed; asserts BLOCKED materials carryfactor=None/impact=None.backend/tests/unit/test_a1_a3.py— formula check; None-total policy (blocked vs all-computed); each BLOCKED path (factor_forraises); antirust PROVISIONAL handling; decision #26 geographic-provenance flags.
Changed (Session 6 — lca.a1_a3 implementation)¶
backend/tests/fixtures/test_a1_a3_material_database.py— added module-integration bridge tests tyinglca.a1_a3.factor_forto the authoritative fixture table (factors match; anomalies raise).docs/api-reference/endpoints.md— documentedGET /lca/a1-a3/keur-songho, including the intentionalnullaggregate total.docs/methodology/module-coverage.md—lca.a1_a3status stub → implemented (4 materials BLOCKED/PROVISIONAL).docs/architecture/module-map.mdanddocs/architecture/data-flow.md—lca.a1_a3advanced stub → implemented.- Test suite: 135 passed / 2 skipped / 6 xfailed (was 114/2/6). Ruff + mypy clean.
Added (Session 6 — A1–A3 material impact database RE)¶
- PROJECT.md decision #26 — per-stage geographic-provenance policy: EARTH materials require local/primary data (no ÖKOBAUDAT/ICE substitute, decision #26 hard block); globally-similar materials permit ÖKOBAUDAT-interim defaults stamped "EU-contextualised, pending African data"; energy-linked stages (A5, B6, C1) BLOCKED on EU grid factors pending African IEA/IPCC data; geographic adaptation coefficients deferred to Phase 2+.
docs/methodology/material-impact-database-spec.md— full reverse-engineering of A1–A3 material impact database: (i)reference/excel/embodied_carbon_material_database.xlsxBuildX Database structure and source assessment (ICE source — NOT approved per decision #4); (ii)reference/excel/materials_database.xlsxscope; (iii)reference/excel/2_lca_vn.xlsxLCI Final column I raw formulas extracted via openpyxl. Material-by-material reconciliation across all three sources. Three new anomalies: crude oil I14=134 kg CO₂/kg (likely MJ/kg unit error), floor tiles I20=5.09 (likely kg CO₂/m² unit error), earth I6="Pietro's Thesis" (BLOCKED — local-data required). Authoritative factor table, verdict/geographic tags, Weidema axis-4 notes, MaterialCategory mapping. Session-6 prerequisite status.backend/tests/fixtures/test_a1_a3_material_database.py— regression fixtures for A1-A3 factors: BOQ weights; authoritative GWP factor table (VALIDATED factors for parity; anomalous factors in separate dict); per-material A1-A3 impacts; Excel total 92 361 kg CO₂ and corrected antirust total 78 543 kg CO₂. Divergence tests: antirust paint delta −13 818 kg CO₂; Excel total cross-check; antirust correction validates. Three xfail tests: earth BLOCKED (item 12), crude oil anomaly (item 13), floor tiles anomaly (item 14). 3 passed, 3 xfailed.docs/methodology/chiwara-attribution-requests.mdItems 12–14 — three new attribution requests: earth A1-A3 primary source (CRAterre/RILEM/Chiwara required, decision #26 hard block); crude oil A1-A3=134 anomaly investigation; floor tiles A1-A3=5.09 unit error.
Changed (Session 6 — A1–A3 material impact database RE)¶
docs/mkdocs.yml— "A1-A3 material impact database spec" page added to Methodology nav.ROADMAP.md— Session 6 log entry; Phase 0 "reverse-engineer embodied_carbon_material_database.xlsx" item ticked; Session-6 prerequisite note updated to COMPLETE; next action updated to lca.a1_a3 implementation.
Added (Session 6 sub-chat — B4/C3/C4/D RE)¶
docs/methodology/lca-stages-b4-c3-c4-d-spec.md— full reverse-engineering ofreference/excel/2_lca_vn.xlsxsheetsB4 (Replacement),C3(Waste Processing),C4 (Disposal),D. Extracted raw formulas and computed values via openpyxl. Verdict + vendorability tags applied per decisions #23, #24, #25. 12 formula verdicts (8 VALIDATED, 4 CORRECTED); all impact factors and disposal rates BLOCKED. EN 15804+A2 Module D conformance assessment (gross-vs-net accounting finding for aggregates). Antirust paint CORRECTED verdict carried forward from antirust investigation.backend/tests/fixtures/test_b4_c3_c4_d_fixtures.py— regression fixtures for all four stages: per-material impact tables extracted from workbook; spec totals (B4: 67 218, C3: 7 144, C4: 2 282, D: −13 647 kg CO₂ eq); divergence tests for CORRECTED formulas (crude oil B4-F2: −32 221 kg CO₂; antirust A1-A3 −13 818 kg CO₂; D gross-to-net for aggregates; D G24 formula error); two xfail tests for BLOCKED RSL items (glass, floor tiles).docs/methodology/chiwara-attribution-requests.mdItems 8–11 — four new attribution requests covering B4 RSL/finishes fractions, crude oil B4 scope, C3/C4 disposal rates and factors, and Module D net accounting basis.
Changed (Session 6 sub-chat — B4/C3/C4/D RE)¶
docs/mkdocs.yml— "LCA stages B4/C3/C4/D spec" page added to Methodology nav.ROADMAP.md— Phase-2 B4/C3/C4/D RE item ticked; Session 6 sub-chat log entry.
Added (Session 5.2)¶
docs/methodology/antirust-paint-investigation.md— sub-chat deliverable: GWP anomaly in2_lca_vn.xlsx(105 kg CO₂/kg) traced to uncited cellM19; verdictCORRECTED; provisional replacement 4.87 kg CO₂/kg from INIES FDES 2-152:2020 (SIPEV solvent-phase alkyd); two blockers (FDES expired June 2025; solvent- vs water-phase classification pending Chiwara). Staged viadocs/tmp/→docs/methodology/.docs/methodology/epd-source-licence-clearance.md— sub-chat deliverable: full licence clearance for all six EPD programme operators; §3 verdict table (CLEARED / RESTRICTED / BLOCKED / UNKNOWN); §4 practical implications; §5 six pre-public-release legal-review items. Staged viadocs/tmp/→docs/methodology/.- PROJECT.md decision #25 — EPD source vendoring policy: public repo builds from ÖKOBAUDAT seeds alone (DL-DE-BY-2.0); RESTRICTED sources (INIES, EPD Norge, IBU-direct) are runtime/citation only; IBU data mirrored in ÖKOBAUDAT reached via ÖKOBAUDAT copy; EPD International BLOCKED; EPDASA UNKNOWN. Geographic representativeness caveat: every seeded factor documents geographic scope; energy-linked stages require explicit treatment pending Africa-specific data.
- GitHub issue #13 (decision-review): "Permitted-source list is not all-vendorable — only ÖKOBAUDAT clears for the public repo (decision #25)."
- GitHub issue #14 (methodology): "Geographic representativeness of EU-contextualised ÖKOBAUDAT factors for Sub-Saharan Africa."
- GitHub issue #15 (methodology): "Antirust paint FDES 2-152:2020 (SIPEV) expired June 2025 — source renewed FDES before seeding."
- GitHub issue #16 (legal-review): "Pre-public-release licence blockers — EF 3.1, ASHRAE, ÖKOBAUDAT attribution, DTU 26.1, EPDASA."
legal-reviewlabel created. docs/methodology/chiwara-attribution-requests.mdItem 7 — antirust paint solvent-phase vs water-phase: Chiwara attribution required before seeding.docs/methodology/quantities-formula-reconciliation.md §8— antirust GWP anomaly recorded as CORRECTED + SOURCED(provisional); two blockers explicit; scope note that B4/C3/C4/D are unbuilt; note that full2_lca_vn.xlsxRE is a Phase-2 prerequisite.
Changed (Session 5.2)¶
- THIRD_PARTY.md §3 — EPD programme operator rows rewritten with vendoring verdicts from licence clearance: ÖKOBAUDAT CLEARED (DL-DE-BY-2.0); openEPD CLEARED (format only, Apache-2.0); INIES/EPD Norge/IBU RESTRICTED (runtime/cited-only); EPD International BLOCKED; EPDASA UNKNOWN. Continuity-risk and mitigation columns added. Decision #25 reference and legal-review items callout added.
- docs/mkdocs.yml — two new pages added to Methodology nav: "Antirust paint GWP investigation" and "EPD source licence clearance".
- ROADMAP.md — 3.10a (material DB) now unblocked-but-constrained (ÖKOBAUDAT seeds only); new Phase-2 item for full
2_lca_vn.xlsxreverse-engineering (B4/C3/C4/D); Session 5.2 log.
Added (Session 5.1)¶
- PROJECT.md decision #24 — recurrence and Module B4: quantities outputs installed quantity (one-time); RSL is a provenance-gated parameter (SOURCED / EXPERT-JUDGEMENT / BLOCKED — raises, no "never-replaced" default); replacements = ⌈ study period ÷ RSL ⌉ − 1 per EN 15978 §6.2.5 + ISO 15686. References GitHub issue #11.
- docs/methodology/differentiators.md — new page documenting four methodological differentiators: provenance-gated numbers; Excel as requirements artifact (not oracle); project-owned MaterialCategory spine; first-class RSL for earth materials correcting the systematic B4 omission. Every claim tied to a PROJECT.md decision or normative standard.
Changed (Session 5.1)¶
- docs/architecture/data-flow.md — B4 node annotated as PLANNED with quantities → B4 handoff seam (installed qty + RSL per material); footnote ❸ explains the seam and the RSL provenance gate per decision #24.
- docs/mkdocs.yml — Differentiators page added to Methodology nav.
Added¶
- Session 5: backend/app/domains/quantities/init.py — full implementation of the
quantitiesmodule; 74 Keur Songho line-item dispatchers; CORRECTED formulas C1 (row 6 sand/gravel), C2 (vault arc π×span/2), C4 (glass scaled by count); C3 (slab rebar) raises ValueError until Chiwara mesh spec attributed; C5 (anti-termite area) preserved as spec with EXPERT-JUDGEMENT pending. - Session 5: backend/app/schemas/quantities.py — Pydantic v2 schemas: BoQLineItem, BoQInput, MaterialQuantity, QuantitiesResult.
- Session 5: backend/app/schemas/enums.py — MaterialCategory extended with 18 new members for quantities module (RAW_EARTH, AGGREGATE_COARSE, AGGREGATE_FINE, ROCK_STONE, WATER, CEMENT, STEEL_REBAR, STEEL_SHEET, STEEL_PROFILE, PLASTIC_FILM, BITUMEN, PETROLEUM_DISTILLATE, WASTE_OIL, GLASS, PESTICIDE, PAINT_WALL, PAINT_ANTIRUST, CERAMIC_TILE).
- Session 5: backend/tests/unit/test_quantities.py — 20 unit tests: schema validation, unit discipline, BLOCKED raises, golden-path VALIDATED, corrected-formula divergence (C1–C4).
- Session 5: backend/tests/regression/test_quantities_keur_songho.py — 3 regression tests: parity vs CORRECTED_PROJECT_TOTALS (all VALIDATED lines ≤0.1%), BLOCKED steel_rebars behaviour, raises-not-defaults.
- Session 5: backend/app/api/routes/quantities.py — FastAPI router: GET /quantities/keur-songho, POST /quantities/explode.
- Session 5: backend/tests/fixtures/test_quantities_divergence.py — added _C2_water_delta (K29 follows C2 arc correction; reconciliation §4.3 "K29 follows automatically"); CORRECTED_PROJECT_TOTALS updated with water_m3 delta.
Changed¶
- Session 5: backend/app/main.py — quantities router included.
- Session 5: docs/api-reference/endpoints.md — /quantities/keur-songho and /quantities/explode documented with curl examples.
- Session 5: docs/methodology/module-coverage.md — quantities status updated to "implemented".
- Session 5: docs/architecture/module-map.md — quantities status updated to "implemented".
- Session 5: docs/architecture/data-flow.md — quantities node updated from STUB to IMPLEMENTED.
- Session 5: docs/api-reference/openapi.json — regenerated.
Added¶
- Session 4: docs/methodology/quantities-spec.md — full reverse-engineering of
1a_boq_input_lca.xlsxQuantitatifssheet; 74 line items, 22 documented errors (E1–E22), 4 parity fixtures, MaterialCategory mapping table, Factors table, Open Questions. - Session 4: docs/methodology/quantities-formula-reconciliation.md — independent formula-by-formula verdict: VALIDATED (parity 0.1%) vs CORRECTED (divergence test required); five new findings (C1–C5) beyond the spec's E-errors; §6 attribution stubs.
- Session 4: docs/methodology/chiwara-attribution-requests.md — standalone attribution-request document for the Chiwara field team; 2 BLOCKED items (slab mesh spec, BBSC composition), 4 EXPERT-JUDGEMENT items (OMPU source, anti-termite area, steel density for fenestration, sand density scope), 1 open question (vault brick dimensions).
- Session 4: backend/tests/fixtures/test_quantities_divergence.py — 7 divergence-fixture tests (6 pass + 1 xfail) asserting the expected numeric difference between the buggy spec and the corrected Python output for C1–C5; SPEC_PROJECT_TOTALS and CORRECTED_PROJECT_TOTALS module-level constants for downstream regression tests.
- Session 4: PROJECT.md decision #23 — two-axis verdict scheme (VALIDATED/CORRECTED × SOURCED/EXPERT-JUDGEMENT/BLOCKED); hard rules on BLOCKED parameters (module raises, no default) and CORRECTED formulas (divergence test, not parity test). Replaces blanket 0.1% parity gate for all domain modules.
Changed¶
- Session 4: CLAUDE.md — new "Formula parity gate" section installing the two-axis verdict scheme; cites PROJECT.md decision #23 and GitHub issues #3–#7. Replaces implicit "0.1% parity" as the only gate.
- Session 4: docs/mkdocs.yml — quantities spec, reconciliation, and Chiwara attribution requests added to Methodology nav.
- Session 4: docs/architecture/module-map.md —
quantitiesstatus updated to "stub — spec+reconciliation complete"; spec and reconciliation docs linked. - Session 4: ROADMAP.md — Phase 1 opened; Session 4 log added; next = Session 5 (implement quantities module, Step 2 schema).
Added¶
- Session 3.8: THIRD_PARTY.md (repo root) — full register of third-party standards, EPD data sources, classification systems, Python libraries, and infrastructure services. Licences confirmed from importlib.metadata; UNKNOWNs flagged with action items. Linked from docs/architecture/index.md and mkdocs nav.
- Session 3.8: PROJECT.md decision #22 — no-vendored-ND-data policy; all third-party deps tracked in THIRD_PARTY.md. Tracking: GitHub issue #2 (decision-review).
Changed¶
- Session 3.8: PROJECT.md decision #19 rewritten — MaterialCategory (project-owned StrEnum) is the canonical internal classification spine; Uniclass/IFC/UN CPC are optional external codes validated for format only (never membership); no classification tables vendored. Phase 1 mapping changes from
french_devis → uniclass → ifctofrench_devis → MaterialCategorywith optional external hints. Tracking: GitHub issue #2 (decision-review). - Session 3.8: docs/methodology/classification-taxonomy.md — rewritten to document MaterialCategory as canonical classification; external code format-only validation; no vendored tables; Phase 1 mapping strategy.
- Session 3.8: docs/contributing/cla-text.md — project identifier changed from "Sankofa LCA (chwr_LCA)" to "the Project" throughout both HA-CLA-I and HA-CLA-E. Definition block added at top: "the Project" = software at https://github.com/marcoNEEV/chwr_LCA, stewarded by Chiwara S.r.l. (Italy). Note that project name is provisional and does not affect the agreement.
- Session 3.8: PROJECT.md cross-cutting convention on classification — updated to document MaterialCategory as canonical; external codes as format-validated external metadata.
- Session 3.8: ROADMAP.md — Uniclass Phase 0 task updated to reflect removal; Session 3.8 log added; next session set to 3.9.
- Session 3.8: docs/architecture/index.md — added no-vendored-ND-data policy key constraint linking to THIRD_PARTY.md.
- Session 3.8: mkdocs nav — THIRD_PARTY.md added as "Third-party register" page.
Removed¶
- Session 3.8: backend/app/data/classification/uniclass/ — deleted in its entirety (Uniclass2015_Ss_v1_42.xlsx, Uniclass2015_Pr_v1_42.xlsx, Uniclass2015_Ac_v1_26.xlsx, Uniclass2015-change-log-2026-04.xlsx, SOURCE.md). Reason: CC BY-ND 4.0 NoDerivatives clause; NBS policy; earth-material codes absent from Uniclass. No code changes required — classification.py used format-only regex; tables were never read at runtime.
Added (Session 3.7)¶
- Session 3.7: backend/app/data/classification/uniclass/ — Uniclass 2015 tables Ss v1.42 (Systems), Pr v1.42 (Products), Ac v1.26 (Activities), and April 2026 change log, downloaded from https://uniclass.thenbs.com under CC BY-ND 4.0.
- Session 3.7: backend/app/data/classification/uniclass/SOURCE.md — download provenance, table versions, licence explanation (CC BY-ND 4.0, not CC-BY), attribution statement, and rationale for table selection.
- Session 3.7: docs/contributing/cla-text.md — instantiated Harmony CLA text for Sankofa LCA (HA-CLA-I individual + HA-CLA-E entity); inbound = AGPL-3.0-or-later; outbound = AGPL + any OSI-approved; jurisdiction = Italy; media licence = CC-BY-SA-4.0.
- Session 3.7: .github/workflows/cla.yml — CLA Assistant GitHub Action (contributor-assistant/github-action@v2.6.1); signatures stored in .github/cla-signatures.json; marcoNEEV allowlisted.
- Session 3.7: CLA text linked in mkdocs nav (Contributing → CLA text).
Changed¶
- Session 3.7: CLAUDE.md "Before you end the session" — added step 4: push discipline: verify remote CI green via
gh run list --limit 3/gh run watchbefore declaring session complete. - Session 3.7: PROJECT.md decision 19 — corrected licence from "CC-BY" to "CC BY-ND 4.0" (redistribution permitted, no derivatives; see SOURCE.md).
Added¶
- Session 3.6: docs/architecture/index.md — system overview with Mermaid component diagram (FastAPI → schemas → domains/services → SQLAlchemy 2 → PostgreSQL 16).
- Session 3.6: docs/architecture/module-map.md — dependency graph of all lca., quantities, thermal, energy, and services/; status table (all stub); update discipline note.
- Session 3.6: docs/architecture/data-flow.md — BoQ → material explosion → EPD proxy → per-module calc → aggregation → MC uncertainty → reporting; interpolation trigger table; planned modules visually marked.
- Session 3.6: docs/requirements.txt — pinned clean-room MkDocs build deps (mkdocs 1.6.1, mkdocs-material 9.7.6, pymdown-extensions 11.0.1); verified in throwaway venv.
- Session 3.6: PROJECT.md decision #21 — docs hosting = Cloudflare Pages; site_url as single source of truth; Chiwara domain deferred to Phase 3; provenance citations third-party only.
- Session 3.6: Architecture nav section in mkdocs.yml (before Methodology).
- Session 3.6: Architecture docs discipline rule in CLAUDE.md and ROADMAP.md file-update table (module-map.md + data-flow.md required on every new module/service).
Changed¶
- Session 3.6: mkdocs.yml site_url changed from GitHub Pages placeholder to https://chwr-lca.pages.dev/ (Cloudflare Pages; Phase 3 Chiwara domain noted in comment).
- Session 3.6: mkdocs.yml pymdownx.superfences — added custom_fences for Mermaid diagram rendering.
- Session 3.6: .github/workflows/docs.yml rewritten — installs from docs/requirements.txt; runs mkdocs build --strict on all PRs and pushes to main; no deploy step (Cloudflare owns deployment).
Added¶
- Session 3.5: shared enums module (backend/app/schemas/enums.py) — LCModule, ImpactIndicator, ProgrammeOperator, MaterialCategory.
- Session 3.5: UnitEnum extended with KG_PER_M3, KG_PER_M2, KG_PER_M, CELSIUS, W_PER_M_K, J_PER_KG_K.
- Session 3.5: source_epd_reference required for cascade_step > 1 (methodology audit critical gap 1).
- Session 3.5: peer_review_citation URL resolution + DOI normalisation in seed-EPD integrity gate (methodology audit critical gap 2).
- Session 3.5: programme_operator approved-set check in seed-EPD integrity gate (methodology audit gap 4).
- Session 3.5: LCIA method bridge (schemas/epd.lcia_method_to_openepd_key) + validate_programme_operator helper.
- Session 3.5: lat/lon range validation (ge/le) and comfort_setpoint_celsius range validation on ProjectIdentity / FunctionalUnit.
- Session 3.5: modules_included must contain A1-A3 (SystemBoundary validator).
- Session 3.5: excluded modules require documented rationale in exclusion_rationales (SystemBoundary validator).
- Session 3.5: ProjectCreate distinguished from Project (Project carries id, created_at, updated_at, created_by).
- Session 3.5: Assembly.recipe key type dict[UUID, Quantity]; code_display Field(max_length=50).
- Session 3.5: SQLAlchemy Base.metadata naming_convention (standards audit #1).
- Session 3.5: Alembic migration a07c3619bd9e — CHECK constraint for material.category; density_unit default kg_m3.
- Session 3.5: Ruff ANN + TCH + W rulesets; pydantic.mypy + sqlalchemy.ext.mypy.plugin; mypy in pre-commit; asyncio_mode = auto.
- Session 3.5: AGPL headers added to 24 previously missing source files (architecture audit #6).
- Session 3.5: Data table interpolation policy (PROJECT.md cross-cutting; METHODOLOGY.md §5a; services/interpolation.py stub).
- Session 3.5: Governance interim entry in PROJECT.md (Chiwara S.r.l.).
- Session 3.5: test_enums.py (18 new enum tests); test_schemas.py extended (+27 tests); test_seed_epd_integrity.py extended (DOI, PO gate, peer_review).
Changed¶
- Session 3.5: LCIAMethodChoice: EF 3.0 removed; only EF 3.1 valid per decision #1.
- Session 3.5: Material.category: str → MaterialCategory StrEnum.
- Session 3.5: Material.density.unit: KG → KG_PER_M3.
- Session 3.5: SystemBoundary.modules_included / modules_excluded / indicators_reported: list[str] → typed StrEnum lists.
- Session 3.5: METHODOLOGY.md §5a renumbered to §5b (openEPD subset); new §5a is Table interpolation policy.
Fixed¶
- Session 3.5: Methodology audit critical gap 1: proxy provenance chain no longer breakable at cascade_step > 1.
- Session 3.5: Methodology audit critical gap 2: peer_review_citation now verified in CI gate.
- Session 3.5: Architecture audit finding #3: density unit semantically correct (kg/m³ not kg).
- Session 3.5: Standards audit finding #1: DB constraint naming convention deterministic.
- Session 3.5: Standards audit findings #2–4: mypy plugins, ruff rules, mypy in pre-commit.
- docs/contributing/licence-agpl.md:
../../LICENSErelative link replaced with absolute GitHub URLhttps://github.com/marcoNEEV/chwr_LCA/blob/main/LICENSE.
Added (Session 3B)¶
- Session 3B: PROJECT.md decision #20 (openEPD subset & proxy_provenance attachment point at
Epd.ext['chwr']['proxy_provenance']). - Session 3B: docs/methodology/openepd-subset-decision.md (openEPD 6.34.2 subset spec, field paths reconciled against installed library, commit SHA
1d48815c135e2d599583e7c10cd65ca706266ea1). - Session 3B: docs/data-schema/proxy-provenance-ext.md (Weidema pedigree extension, GSD² formula corrected, rubric registry v1.0.0, JSON fixture reconciled against openEPD 6.34.2).
- Session 3B: Pydantic v2 schema layer —
units.UnitEnum,units.Quantity,classificationcodes (Uniclass, IFC, UN CPC),ProxyProvenance,Material,Assembly,Project,Epdwrapper helpers. - Session 3B: SQLAlchemy 2 async models mirroring the Pydantic schemas (project, material, assembly, epd) with JSONB payload columns and indexed lookup columns.
- Session 3B: Alembic initialised; first migration
05b0985c61fecreates project/material/assembly/epd tables; up and down verified. - Session 3B:
backend/app/config.py(Pydantic Settings from.env),backend/app/database.py(async engine + session factory). - Session 3B:
services/pedigree.pystub (raises NotImplementedError pending Phase 1 Step 1 Ciroth/Brightway2 sub-chat). - Session 3B:
backend/tests/data/test_seed_epd_integrity.pyenforcing seed-EPD provenance gate rules. - Session 3B:
backend/tests/data/seed_epds/turkish_portland_proxy.jsongolden fixture. - Session 3B: METHODOLOGY.md sections on internal data schema (§5a) and proxy-EPD data quality (§6a); §9 Module D non-netting rule.
- Session 3B: schema unit tests (27 passed) covering Quantity, classification codes, ProxyProvenance conditional requirements, Material, Assembly.
- Session 3B: openEPD subset decision nav entry added to mkdocs.yml.
- Session 2.5: PROJECT.md decision #19 — BoQ classification taxonomy (Uniclass primary, IFC secondary, UN CPC optional at material level).
- Session 2.5: PROJECT.md cross-cutting schema conventions section (Quantity type discipline; classification codes required on Assembly and Material).
- Session 2.5: docs/methodology/classification-taxonomy.md.
- Session 2.5: docs/contributing/cla.md (Harmony CLA plan; instantiated text TODO Phase 0).
- Session 2.5: ROADMAP.md — Phase 0 IP tasks (trademark search, Harmony CLA setup, Uniclass download); Phase 1 mapping-table sub-chat deliverable and new-material extensibility test; Phase 2 climate-zone / EPD-operator / typology extensibility tests; Phase 3 BoQ input form as v1 landing frontend feature + Zenodo DOI for seed EPD library; Phase 4 institutional partnership approaches + methodology paper submission.
- Session 2: docs site skeleton expanded to first-class deliverable (methodology, api-reference, data-schema, testing, contributing sections).
- Session 2: file-update discipline table in ROADMAP.md.
- Session 2: 6-step incremental build-and-test loop documented in ROADMAP.md Phase 1 header.
- Session 2: sub-chat spawning rules documented in CLAUDE.md.
- Session 2: seed-EPD provenance gate documented in CLAUDE.md.
- Session 2: module inputs reference document (docs/methodology/module-inputs.md).
- Session 2: OpenAPI schema auto-generation script and CI integration.
- Session 2: reference source-of-truth files (6 xlsx + 1 pdf) committed under reference/.
[0.0.1] — Session 1¶
Added¶
- Initial repository scaffold under AGPL-3.0-or-later.
- Folder skeleton: backend/, frontend/, docs/, reference/, .github/workflows/.
- pyproject.toml (Python 3.12, FastAPI, Pydantic v2, SQLAlchemy 2, Alembic, pytest, ruff, mypy).
- docker-compose.yml (Postgres 16 alpine), Dockerfile (backend runtime).
- Meta documents: CLAUDE.md, PROJECT.md, ROADMAP.md, METHODOLOGY.md, README.md.
- FastAPI /health endpoint and passing regression test.
- CI workflow (lint, type-check, test), docs workflow stub.
- Pre-commit hooks (ruff, standard hygiene).
Fixed¶
- pyproject.toml
[tool.hatch.build.targets.wheel] packagesset to["backend"](was["backend/app"]) sobackend.app.*imports resolve in tests.