Commit graph

23 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
71f746984a refactor(epc): single source of truth for the SAP↔EPC band threshold ladder
The 92/81/69/55/39/21 band floors were defined in five places across TS and
SQL — @/app/utils.sapToEpc, a hand-copied sapToEpcLetter in actions, the
EPC_TO_SAP_MIN/MAX ranges, and the BAND_BUCKETS_SQL/EPC_MIN_SAP in both
scenario-metrics routes. "What SAP score is band C" now lives once in
@/lib/epc/thresholds (BAND_MIN_SAP), with a SQL twin in thresholdsSql.

- lib/epc/thresholds.ts: BAND_MIN_SAP, sapToBand, EPC_TO_SAP_MIN/MAX (derived)
- lib/epc/thresholdsSql.ts: sapBandBucketsSql() built from the same ladder
- @/app/utils.sapToEpc + @/app/utils/epc now delegate to the canonical ladder
- deleted the hand-copied sapToEpcLetter in actions/recommendations.ts
- both metrics routes use sapBandBucketsSql / BAND_MIN_SAP

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 13:26:08 +01:00
Khalim Conn-Kowlessar
7cd33ab18d Merge remote-tracking branch 'origin/main' into feature/portfolio-tagging
# Conflicts:
#	src/app/portfolio/[slug]/components/PropertyFilters.tsx
#	src/app/portfolio/[slug]/utils.ts
#	src/app/utils/propertyFilters.ts
2026-07-13 19:49:47 +00:00
Khalim Conn-Kowlessar
c5d58dbc96 feat(tags): table query + tag filter, filter-driven bulk assign, preview join, settings UI
Backend, end to end (ADR-0013):
- getProperties now returns each property's tags (name-ordered JSON); the
  PropertyWithRelations type gains a `tags: PropertyTag[]` field.
- A "tags" property-table filter (EXISTS/NOT-EXISTS on property_tag, any-of +
  an "Untagged" bucket) — PK-indexed by property_id, so no join added.
- getFilteredPropertyIds resolves the whole matching set for filter-driven Bulk
  tag assignment; the assignments route's `filter` mode uses it.
- Run filter: previewModellingRun joins property_tag for tag_ids; the two
  modelling routes accept tagIds. Distributor ask documented in
  docs/backend-asks/tag-ids-run-filter.md.

UI:
- Settings → Tags: create (name + colour), edit, delete-with-count-confirm,
  list with property counts (TanStack v4). Sidebar link added.

Typecheck clean; 431 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 16:23:49 +00:00
Khalim Conn-Kowlessar
3b04de622b feat(portfolio): Main Fuel EPC fallback + filterable Wall/Roof/Heating
Main Fuel no longer reads "Unknown" for new-approach properties. The new EPC
graph stores the main fuel as a JSONB scalar on
epc_main_heating_detail.main_fuel_type — a numeric RdSAP main_fuel code (~95%)
or an EPR text string (~5%). mainfuelSql now reads it (lodged over predicted)
instead of NULL; resolvedMainFuelSql keeps the override → EPC → 'Unknown'
precedence. The RdSAP code table (Model epc_codes.csv, cross-checked against
prod) + the EPR strings fold into MAINFUEL_OPTIONS, so the column label and the
filter work unchanged. resolveMainFuel gains the EPC branch as its pure twin;
coverage tests assert every code + string + override value is folded.
Verified: portfolio 824 now resolves code 26 → "Mains Gas".

Wall Type / Roof Type / Heating System become filterable. Their resolved values
are free-text (override vocab / epc_energy_element.description / legacy column)
with high cardinality, so exact dbValues can't scale — each coarse bucket is a
set of case-insensitive needles matched by prefix (wall/roof) or substring
(heating). classifyDescriptor is the pure twin of the SQL bucketing in
buildConditionSql; the "Unknown" bucket matches the complement of every known
needle. Buckets validated against the full prod vocabulary (heating Unknown =
28/640k rows). Wired through FilterField, FIELD_OPTIONS, buildConditionSql,
EPC_JOIN_FILTER_FIELDS and the count-query override joins; columns stay
free-text display. Coverage tests assert every Wall/Roof/MainHeatingSystem
override value buckets to a real, non-Unknown label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 14:30:50 +00:00
Khalim Conn-Kowlessar
78db9815ba feat(portfolio): remove Tenure from the property list column + filter
Slice 5. Tenure was broken for new-approach (read p.tenure, NULL) but has no
override component and its epc_property.tenure values don't reconcile with the
filter dbValues, so it's removed from the list rather than fixed (ADR-0012):
the optional column, the filter field, TENURE_OPTIONS, and the CSV column. The
per-property building passport (resolvePropertyMeta/resolvePropertyDescriptors)
still resolves tenure, and property.tenure is retained.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 23:56:42 +00:00
Khalim Conn-Kowlessar
fc2f7d5b30 feat(portfolio): resolve Main Fuel column + filter via override precedence
Slice 4. The new EPC graph exposes no main-fuel string (documented gap), so for
new-approach properties the landlord override is the only source. resolveMainFuel
returns the raw value (override → legacy EPC → 'Unknown'); the cell's
resolveEnumLabel folds it to a chip. MAINFUEL_OPTIONS.dbValues extended with the
override vocabulary so overridden fuels are both displayable and filterable —
guarded by a coverage test over MainFuelValues. resolvedMainFuelSql mirrors it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 23:51:40 +00:00
Khalim Conn-Kowlessar
558e20f2fe feat(portfolio): band-native Construction Age column + filter (was Year Built)
Slice 3. Construction age is modelled as an RdSAP band, not a year (ADR-0012) —
the only representation that faithfully carries an 'Unknown' override. resolve-
ConstructionAge owns override(letter)→label / EPC band(letter)→label / legacy
year→band bucketing / 'Unknown', with 10 unit tests incl. band boundaries.
resolvedConstructionAgeSql mirrors it (emits the band label). YEAR_BUILT_OPTIONS
becomes the band labels + Unknown, guarded against label/en-dash drift by a new
epcSources.test. Column + filter + CSV header renamed to 'Construction Age'
(internal 'yearBuilt' key retained to avoid a wide rename).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 23:35:14 +00:00
Khalim Conn-Kowlessar
e2ab52e4f9 feat(portfolio): resolve Built Form column + filter via override precedence
Slice 2. Same shape as slice 1: resolveBuiltForm + builtFormOverrideJoin /
resolvedBuiltFormSql, wired into getProperties SELECT, the filter colMap, and the
count query's join set (builtForm added to EPC_JOIN_FILTER_FIELDS). BUILT_FORM_OPTIONS
gains Unknown (Not Recorded now matches only an explicit override of that literal).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 23:26:07 +00:00
Khalim Conn-Kowlessar
3bf816130c feat(portfolio): resolve Property Type column + filter via override precedence
Slice 1 of the portfolio-list descriptor fix. Property Type was read from the
raw property row (p.property_type), which is NULL for new-approach properties
(updated_at >= 2026-06-01), so the column and filter broke for new portfolios.

- New pure resolver resolvePropertyType (descriptorResolution.ts, mirrors the
  provenance.ts split) owning override → EPC-derived (lodged over predicted,
  RdSAP codes mapped, legacy fallback) → 'Unknown', with 9 unit tests.
- SQL twin resolvedPropertyTypeSql + propertyTypeOverrideJoin in epcSources.ts.
- Wired into getProperties SELECT, the filter colMap, and the count query's
  conditional join set (propertyType added to EPC_JOIN_FILTER_FIELDS).
- PROPERTY_TYPE_OPTIONS gains Park home + Unknown.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 23:21:22 +00:00
Khalim Conn-Kowlessar
ea988cfe52 feat(epc): provenance signals + effective-baseline portfolio table
Theme 1 — EPC card provenance. New resolveProvenanceSignal (pure, unit-
tested) + resolveProvenance expose provenanceSignal ("estimated" /
"remodelled" / "none") and the lodged headline on property-meta. The
building-passport card shows a "Lodged EPC" badge on re-modelled
properties (even within-band), never on predicted ones; the existing
"estimated from nearby homes" banner already covers predicted.

Theme 2 — portfolio table. "Current EPC" + reporting now read the
effective baseline (effectiveSapSql/effectiveEpcBandSql); a dedicated
"Lodged EPC" column reads the source-gated lodged value (blank for
predicted); a "Predicted" pill marks estimated rows; a provenance filter
is added and the broken currentEpc/lodgedEpc filters (on null row
columns) point at the effective/lodged baselines. Expected EPC now uses
the default plan's post rating (expectedEpcRating, unit-tested) instead
of current + Σ rec SAP, which double-counted across plans (732385: A→C);
the same rec aggregate is scoped to the default plan, fixing the Cost
column's 2x double-count.

See ADR-0002. Pure logic covered by provenance.test.ts + expectedEpc.test.ts;
SQL/query/UI verified against live properties 732385 and 729529.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 22:21:33 +00:00
Jun-te Kim
8170601efb refactored with improve-code-archievture and grill-with-docs 2026-05-06 15:49:34 +00:00
Jun-te Kim
119a800995 save latest changes and pulled from main 2026-04-20 15:59:11 +00:00
Khalim Conn-Kowlessar
680935e1fa Improving upload logging 2026-04-20 14:04:06 +00:00
Jun-te Kim
b81a1aaf61 added trigger of sqs 2026-04-18 18:55:19 +00:00
Khalim Conn-Kowlessar
41c068c419 improving visuals of the property table 2026-04-13 13:49:34 +00:00
Khalim Conn-Kowlessar
c70a34f174 improving filter abilities 2026-04-11 15:40:06 +00:00
537239c3ee added workflow to allow me to debug quicker 2026-01-19 13:01:57 +00:00
c9bfb0ce9c added db filtereing 2026-01-05 17:30:58 +00:00
3c5dc722ef added new file 2025-12-08 13:53:54 +00:00
Jun-te kim
0a835eb025 upload 2025-08-20 11:56:52 +00:00
Jun-te kim
cf0cc5e79a added sqs utils 2025-08-19 13:10:49 +00:00
Jun-te kim
c7b9d6de9d upload works 2025-08-12 19:28:40 +00:00
Jun-te kim
08705f708a add utils 2025-08-12 16:01:42 +00:00