Commit graph

1820 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
e8fe65041e fix(reporting): keep the Postgres driver out of the client bundle
The Vercel build failed with "Can't resolve 'fs'" (pg-connection-string):
reporting client components transitively imported the db via the Drizzle
schema and epcSources. tsc can't see this — only bundling does.

- New schema-free pure modules: @/lib/epc/bands (EPC_BANDS + goal strings)
  and @/lib/epc/labels (property-type / built-form labels).
- reporting/model.ts, EpcLadder, StockBreakdown, the PDF page and
  ReportingClientArea now import bands/goals/labels from the pure modules
  instead of @/lib/scenarios/model, @/app/db/schema/portfolio and
  @/lib/services/epcSources.
- epcSources re-exports the label constants, so server callers are
  unchanged.
- Drift guards (bands.test.ts) assert the mirrors match the schema.

tsc clean, 442 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:46:52 +00:00
Jun-te Kim
4c65bca0c3
Merge pull request #367 from Hestia-Homes/feature/uprn-confirmation-onboarding
Confirm UPRNs before finalise: two-tab bulk-upload review + retire Unmatched tab (ADR-0057)
2026-07-08 10:28:09 +01:00
Khalim Conn-Kowlessar
4b0eb59d52 feat(reporting): measures allocation, PDF rebuild, remove old components
- MeasureAllocation ('where the money goes'): allocation bar by category
  + inline grouped measures table with CSV and per-measure drill-down,
  replacing the old modal. Grouping is a pure TDD'd module
  (lib/reporting/measures.ts) moved out of the deleted modal.
- PDF rebuilt as a print-register server component on the shared model
  (buildHeadline, computeLedger figures, carsOffTheRoad): cover headline +
  stats, current-vs-scenario EPC distribution, investment ledger. Screen
  and report now share definitions and can't drift.
- Deleted the old gradient-era components (DashboardSummaryCards,
  ScenarioFinancialDrawer, BreakdownChart, EpcQualityCards,
  PlaceholderMetricCards, ReportingFunctionalityButtons,
  RecommendationsOptions, ScenarioMeasuresModal, SectionDivider, scenario
  selector pair) — nothing references them now.

Fixes: 'upgraded homes' KPI now deep-links to the property table (was an
invalid empty-measureType drill); measure drill-down guarded to numeric
scenarios. tsc clean, 440 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:02:09 +00:00
Jun-te Kim
72c6d9dd5a Merge remote-tracking branch 'origin/main' into feature/uprn-confirmation-onboarding
# Conflicts:
#	src/app/portfolio/[slug]/components/PropertyTable.tsx
2026-07-08 08:59:17 +00:00
Khalim Conn-Kowlessar
4321fcd029 feat(reporting): compare view + data-quality page (Screens D & E)
Screen D (/reporting/compare): CompareClientArea runs N parallel client
calls to the existing metrics route (useQueries), rows ordered
outcome→investment→value-for-money, best-in-row marked via pickBestIndex
(TDD'd — nulls never win). Up to 4 columns; only modelled scenarios
listed (ADR-0003). Column headers link back to the scenario view.

Screen E (/reporting/data-quality): getDataQualityMetrics adds evidence
composition (in-date/expired/estimated, each home once) and band-movement
counts — likely downgrades AND the new likely-upgrades signal, both by
lodged-vs-effective band per CONTEXT.md. Page reuses the drill-down shelf
for every issue; unlock modules list columns with templates gated behind
'coming soon' pending the data-team spec.

Reporting page's Compare button and 'Review data quality' link now wired.
tsc clean, 435 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 08:47:43 +00:00
Khalim Conn-Kowlessar
42afd7a3cb feat(reporting): rebuild the reporting page UI (Screens A–C)
Replaces the old gradient-card page with the v2.3 design system (#370):
one quiet panel vocabulary, EPC colour + semantic deltas only, numbers
as the centre. New components under reporting/components/:

- ScenarioCombobox: searchable, Current stock + Recommended plans pinned,
  unmodelled scenarios disabled with 'Awaiting modelling' (ADR-0003)
- KpiBand: current-stock (incl. energy use) and scenario before/after
  with semantic deltas via shapeKpiDelta
- EpcLadder: statutory band colours, estimated share, navy scenario overlay
- GoalCallout: goal-aware, per selectGoalCallout (portfolio goal on current
  stock, scenario goal on scenario view)
- InvestmentLedger: canonical gross/net, cost tooltips, car equivalents
- FilterChips: inline hide-non-compliant / compliance-window / lodged
  toggles, EPC-only, apply immediately (no popover)
- DrillDownShelf: full-width, wired to /reporting/properties, deep links
- StockBreakdown, ConfidenceStrip

Page now loads portfolio goal + listScenariosWithStatus. Old components
retained only for the not-yet-rebuilt PDF route. tsc clean, 431 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 08:37:49 +00:00
Khalim Conn-Kowlessar
52f7a7a10f feat(reporting): route layer — canonical ledger, compliance window, drill-down
Metrics route (#370): net_cost/gross_per_unit now use computeLedger's
all-in definitions (was construction−funding, delivery/contingency
excluded); adds ADR-0010 complianceBand+complianceDate params (validated,
lodged-only membership, applied at all four query sites, scenario-side
only); renames useOriginalBaseline → useLodgedBaseline reading lodgedSapSql
with a keep-on-unknown COALESCE so new-approach homes aren't dropped.

New drill-down endpoint /reporting/properties: one filter per request
(band/estimated/expired/likely-downgrade/likely-upgrade/measure),
paginated, powering Screen C and the CSVs. Band-movement filters use the
certificate-gated lodgedEpcBandSql per CONTEXT.md.

Reporting page swaps the thin local getScenarios for
listScenariosWithStatus (config + derived modelled state, ADR-0003).

Route tests on the mocked-db pattern; full suite 430 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 08:28:01 +00:00
Khalim Conn-Kowlessar
bff6967ef2 feat(reporting): pure domain model for the reporting redesign
TDD'd src/lib/reporting/model.ts per PRD #370 — the executable spec for
the grilled decisions: all-in gross/net ledger (PROJECT_DELIVERY_RATE
disclosed), ADR-0010 compliance-window membership (lodged-only, band+date
boundaries), band-movement likely-downgrade/upgrade signals, goal-aware
callout selection (portfolio goal on current stock, scenario goal on
scenario view), board headline generation, car equivalents, KPI deltas.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 22:29:36 +00:00
Khalim Conn-Kowlessar
83f65567cf docs: reporting glossary terms + ADR-0010 compliance-window filter
Gross/Net cost, Project delivery, Current stock, Likely downgrade/upgrade
(band movement) added to CONTEXT.md from the reporting-redesign grilling
session. ADR-0010 records the compliance-window filter as a report-view
parameter, not a Scenario constraint. Also commits the previously
untracked ADR-0001/0002 docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 22:08:35 +00:00
KhalimCK
1344d1a77d
Merge pull request #369 from Hestia-Homes/feature/home-portfolio-redesign
feat(home): portfolio home redesign — folders, starring, drag-to-file
2026-07-07 22:18:38 +01:00
Khalim Conn-Kowlessar
8d56283df8 docs(adr): 0009 — portfolio organisation is per-user, not shared
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 21:11:21 +00:00
KhalimCK
326e191b79
Merge pull request #365 from Hestia-Homes/fix/scenario-exclusions-backend-vocab
fix(scenarios): exclusion vocabulary = backend MeasureType exactly
2026-07-07 18:55:37 +01:00
KhalimCK
4a437ec526
Merge pull request #360 from Hestia-Homes/feature/bulk-trigger-modelling
feat: bulk-trigger modelling runs with property filters
2026-07-07 18:55:20 +01:00
Jun-te Kim
3a1f9146b7 Merge branch 'main' of https://github.com/Hestia-Homes/assessment-model into feature/uprn-confirmation-page
# Conflicts:
#	src/app/db/migrations/meta/0261_snapshot.json
#	src/app/db/migrations/meta/_journal.json
2026-07-07 17:53:42 +00:00
Khalim Conn-Kowlessar
2caac6fa6c fix(portfolio): actually refetch the property table after adding properties
invalidateQueries only refetches mounted queries; the property table isn't
mounted on the add page, and useProperties' refetchOnMount: false then
suppresses the refetch when it does mount — so the invalidation never
produced a fetch. Removing the cached query instead guarantees the initial
fetch on mount.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 17:50:47 +00:00
Khalim Conn-Kowlessar
0b475d8e03 fix(portfolio): refresh server-rendered data after adding properties or saving a scenario
Both mutations navigated with router.push alone, so the destination served
the pre-mutation render from the client router cache: new properties were
missing from the portfolio table (compounded by useProperties' 5-minute
staleTime, now invalidated too) and a first scenario still showed
"No scenarios yet". router.refresh() after push busts the cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 17:37:16 +00:00
Khalim Conn-Kowlessar
8f7f5b3a88 fix(modelling): set-and-forget trigger state, visible back link, filter clearing
Post-review UX feedback on the run journey:

- The post-trigger card computed its ETA from the just-cleared scenario
  selection, so it always promised "~5 minutes" whatever the run size.
  ETA now comes from the captured run (estimateRunMinutes, TDD'd) and the
  done state is a compact status banner above a still-visible Recent runs
  table, which polls every 5s while a run is moving (historyRefetchInterval,
  TDD'd) — trigger, glance, leave.
- The only way back was a text-xs gray-400 breadcrumb (~2.5:1, sub-AA);
  replaced with an accessible "← Back to {portfolio}" link.
- "Remove all filters" button on Step 2.
- Scenario rows were a checkbox nested inside a button (state invisible to
  screen readers); now a label wrapping a real checkbox.
- Large-run confirm lists the scenario names and selection being committed,
  is labelled, closes on Escape, and starts focus on "Go back".
- Postcode filter no longer fails silently: loading and error-with-retry
  states.
- Run status "Dispatched" reads "Starting up"; scenarios page gains a
  "Run modelling" entry point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 17:37:16 +00:00
Khalim Conn-Kowlessar
7e35f3c0a2 feat(home): Headless UI menus, drag-to-file onto rail, folder view in URL
Card and folder menus move to Headless UI v2 (Menu / Popover with
anchor positioning): panels portal to the top layer so the list view
can't clip them, and Esc, outside-click close, arrow-key nav, and menu
semantics come from the primitive — replaces the details/summary menus
and the fixed-position workaround. The folder popover hosts the
two-step remove confirm. Cards and table rows are draggable onto rail
targets (folder = file, Starred = star, All portfolios = unfile) with
drop highlighting; folder-reorder drag is type-guarded from card drags.
Selected rail view persists in the URL via replaceState (?folder=id /
?view=starred), read by the server component; unknown folder ids fall
back to All, and deleting the folder being viewed returns to All.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 17:20:47 +00:00
Khalim Conn-Kowlessar
ade33195cb fix(home): inline delete confirm, visible mutation errors, unclipped list menu
Impeccable critique (25/40) fixes: window.confirm replaced by a
two-step confirm inside the folder menu (states the unfile consequence,
red confirm takes focus, Escape cancels); mutation failures now render
a dismissible notice as well as the live region, and folder-create
errors are no longer swallowed; list-view card menu uses fixed
positioning measured on open so the table's overflow container can't
clip it; Escape closes card/folder menus with focus returned. Also:
search placeholder and rail-count contrast to >=4.5:1, folder-menu and
New-folder hit targets to >=24px, th scope=col, decorative ticks
aria-hidden, rename input regains a visible focus state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 17:01:16 +00:00
Khalim Conn-Kowlessar
1940f02cf3 feat(home): delete-folder confirm, view preference cookie, show-more batching
Folder removal now confirms with copy stating portfolios are unfiled,
not deleted. Grid/list preference persists in a cookie read by the
server component, so the chosen view renders on first paint. Long lists
render the first 24 rows with a show-more button; the reveal count
resets when the view or search changes, and starred float keeps pinned
work on the first screen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 16:32:19 +00:00
Jun-te Kim
3f649755b4 feat(portfolio): retire the Unmatched tab; surface low_score (ADR-0057 Phase 4/5)
Phase 4: the portfolio landing page now renders the property table directly.
UPRN matching is confirmed during onboarding, so properties arrive matched and
the post-onboarding "Unmatched" tab is no longer needed (tab components left in
place pending a legacy-no-UPRN reconciliation follow-up).
Phase 5: AddressesPanel labels the new "low_score" status "Low-confidence match".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:29:19 +00:00
Jun-te Kim
8b99e7029c feat(bulk-upload): two-tab review with pre-finalise Addresses/UPRN confirmation (ADR-0057)
At awaiting_review, when address2uprn flagged rows, OnboardingProgress now
shows two tabs: Classification (existing verify/unknown/ordering panels) and
Addresses. The Addresses tab (AddressesPanel) lists each flagged combiner row
with the OS Places matcher (reused search → residential candidates) and a
"No UPRN" action, saving corrections via useSaveUprnCorrection. Finalise is
gated on unresolvedAddresses === 0 with a matching disabled reason; the tab
carries a badge of the unresolved count.

tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:19:18 +00:00
Khalim Conn-Kowlessar
cfa181f3c7 feat(home): portfolio home redesign — folder rail, starred view, grid/list
Client page driven by useQuery on /api/me/portfolio-home with optimistic
mutations (star, move-to-folder, folder create/rename/delete, drag or
menu-based reorder). TDD'd view-model in portfolioHomeView.ts (status
pill mapping, search over display language, sort incl needs-attention,
money/date formatting, optimistic config upsert);
groupPortfoliosForHome generalised over id type so the client re-groups
with wire-format string ids. List view is a semantic table; replaces
CardTiles/Card/AddNewCard (NewPortfolioModal retained).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 16:18:44 +00:00
Jun-te Kim
ee22764e06 feat(bulk-upload): client hooks for address matches + corrections (ADR-0057)
useAddressMatches (flagged rows + unresolved count) and useSaveUprnCorrection
(assign UPRN / mark no-UPRN), matching the existing TanStack Query v4 conventions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:10:56 +00:00
Jun-te Kim
6743cd8bae feat(bulk-upload): gate finalise on unresolved addresses + overlay corrections (ADR-0057)
dispatchFinaliser now:
- blocks (before the CAS claim) when any flagged combiner row is still
  unresolved -> new "unresolved_addresses" outcome; finalize route returns 409.
- overlays the user's confirmed UPRNs onto the combiner CSV (buildConfirmedCombinerUri
  writes a sibling .confirmed.csv) and points the finaliser at it, so a confirmed
  UPRN drives identity + property_overrides in one pass. No corrections -> original
  URI unchanged.

tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:09:21 +00:00
Jun-te Kim
e960b76afc feat(bulk-upload): read flagged combiner rows + save UPRN corrections (ADR-0057)
Server module + two routes for the pre-finalise Addresses tab:
- getFlaggedAddresses: reads the combiner CSV from combinedOutputS3Uri,
  returns rows address2uprn could not confidently match (empty UPRN or a
  non-"matched" status), joined with any saved correction.
- upsertUprnCorrection: upserts a confirmed UPRN / no-UPRN per source_row_id.
- GET .../address-matches, POST .../address-corrections (thin, auth-guarded).

tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:06:23 +00:00
Khalim Conn-Kowlessar
6e30ca311d fix(scenarios): exclusion vocabulary = backend MeasureType exactly
Scenario 1275's modelling run crashed the engine: its exclusions carried
'room_roof_insulation', which isn't a backend MeasureType — the strict
_parse_exclusions raises for every property in the scenario. The authoring
vocabulary had seven such tokens (room_roof_insulation, boiler_upgrade,
secondary_heating, ventilation, fireplace, hot_water_tank_insulation,
cylinder_thermostat).

Scenario authoring now uses its own SCENARIO_MEASURES vocabulary — exactly
the backend MeasureType set (drifted names corrected, unimplemented
measures dropped, missing system_tune_up/_zoned + sloping_ceiling gained),
pinned by a test that fails if either side moves alone (the backend's
ADR-0056 shared-contract rule, applied app-side). room_roof_insulation
leaves DISRUPTIVE_MEASURES until the backend implements the measure;
legacy tokens on existing rows still display in constraint summaries.
The global measuresDisplayLabels vocabulary (recommendations display, old
file-based trigger) is untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 16:00:01 +00:00
Jun-te Kim
e0d2b9dbef feat(bulk-upload): add uprn corrections table for pre-finalise confirmation (ADR-0057)
Foundation for the "Addresses" review tab: captures the user's confirmed
UPRN per combiner row, keyed by source_row_id (no property.id exists
pre-finalise). At dispatchFinaliser these are overlaid onto the combiner
CSV so the finaliser builds identity + property_overrides from the
confirmed UPRN in one pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 15:46:58 +00:00
Khalim Conn-Kowlessar
633c127620 feat(home): per-user portfolio config planning layer + /api/me routes
TDD'd pure planning core (planFolderReorder with payload validation,
groupPortfoliosForHome with starred float + recency, planFolderDeletion
encoding unfile-then-delete, buildStarChange owning starred_at-as-flag),
applied by thin route handlers: folder create/rename/delete/reorder,
per-portfolio config upsert, and the grouped home read.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:12:07 +00:00
Khalim Conn-Kowlessar
31b7c28ad5 docs(design): home-page redesign context — Stitch refs, PRODUCT.md, impeccable trial
Design references and critique snapshot for the portfolio home redesign;
impeccable design skill added to devcontainer postCreateCommand on trial.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:12:07 +00:00
KhalimCK
ce6a6ffa2d
Merge pull request #363 from Hestia-Homes/feature/user-portfolio-config
feat(db): user portfolio config + folders (home-page redesign schema)
2026-07-07 14:53:41 +01:00
Khalim Conn-Kowlessar
4073671127 feat(db): add user_portfolio_config and user_portfolio_folders tables
Per-user portfolio workspace layer for the home-page redesign: starred
portfolios (starred_at, non-null = starred, orderable) and personal
folders (name + position, drag-reorderable). Composite FK
(folder_id, user_id) -> (id, user_id) makes cross-user folder
assignment impossible at the database level. Config rows are created
lazily via upsert on (user_id, portfolio_id); portfolioUsers remains
pure access control.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:23:15 +00:00
Jun-te Kim
1964f35044 Merge branch 'main' of github.com:Hestia-Homes/assessment-model 2026-07-07 13:15:44 +00:00
Jun-te Kim
4ab153ad59
Merge pull request #362 from Hestia-Homes/feat/property-certificate-number
Add certificate_number column to property table
2026-07-07 14:14:52 +01:00
Jun-te Kim
1ac9f0fdcf fix(portfolio): open the bulk-upload dialog from "Bulk excel import"
The BulkUploadComingSoonModal (drag/drop upload, template download, header
validation → creates the upload and routes into the map-columns onboarding
step) existed but was never wired to anything. The "Bulk excel import" menu
item routed to the uploads list instead. Point it at the dialog so the bulk
upload onboarding starts from there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:53:26 +00:00
Jun-te Kim
114ccbba02 Add certificate_number column to property table
The address2uprn lookup already returns the EPC certificate number
alongside the UPRN, but only the UPRN is persisted today. This adds
a nullable column so a follow-up change can start saving it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 11:50:59 +00:00
Jun-te Kim
fe55a28622 fix(portfolio): truncate long match addresses; hide Needs attention tab when empty
- MatchAddress: long OS addresses overflowed the candidate row (text column
  wasn't width-constrained). Give it min-w-0 flex-1 and truncate both lines
  (full address on hover) so the dialog layout holds.
- PortfolioTabs: when nothing is unmatched, render just the properties table
  with no tab strip (the "Needs attention" tab only appears when count > 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:38:38 +00:00
Jun-te Kim
3f8920acd0 feat(portfolio): match unmatched properties to a residential UPRN
Turns the "Needs attention" tab into an actionable per-property match flow:
edit the address, confirm the postcode, search Ordnance Survey, and pick from
the RESIDENTIAL addresses in that postcode. Every candidate is a real OS
address, so picking one assigns a guaranteed valid, residential UPRN — the
property then leaves the tab and joins the main table.

- MatchAddress dialog: postcode search + residential-only candidate list
  (non-residential filtered out; already-in-portfolio shown but non-selectable,
  each row shows its UPRN). Replaces the edit-only EditAddress.
- PATCH /properties/[propertyId]: assigns uprn + address + postcode (user input
  kept as the fallback); (portfolio_id, uprn) clash → friendly 409.
- client.ts: searchAddresses() + useAssignUprn().

Reuses the existing /properties/search endpoint, which read-through caches OS
Places results per postcode in postcode_search (30-day TTL) — repeat searches
of the same postcode hit our DB, not the OS API (surfaced as a "from cache"
hint in the dialog). No backend trigger needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:33:01 +00:00
Khalim Conn-Kowlessar
06ad4eec0d Merge remote-tracking branch 'origin/main' into feature/bulk-trigger-modelling 2026-07-07 11:28:34 +00:00
KhalimCK
668588d3ce
Merge pull request #361 from Hestia-Homes/chore/tasks-inputs-column
feat(db): add inputs column to tasks
2026-07-07 12:27:40 +01:00
Jun-te Kim
aab3984b5c copy: reword unmatched-properties help text to AraAddressMatcher
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:21:44 +00:00
Jun-te Kim
7ac632a7aa feat(portfolio): move unmatched properties into a "Needs attention" tab
Reworks the separation from a card stacked above the table into a proper
second tab, and takes unmatched properties out of the main list entirely:

- PortfolioTabs: "Properties" | "Needs attention" tab shell with a count badge
  on the second tab. Both panels stay mounted (hidden with CSS) so the main
  table keeps its filter/pagination state across tab switches.
- getProperties / getPropertiesCount now exclude uprn IS NULL, so unmatched
  properties no longer appear (or count) in the main table until resolved.
  (Both functions are only used by the main table's /api/properties route.)
- UnmatchedProperties: lives in the tab; shows an "all matched" empty state
  instead of rendering nothing, and copy updated for the edit-only scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:17:48 +00:00
Jun-te Kim
f1cef2e643 feat(portfolio): enable the "Bulk excel import" entry point
The Add-properties menu had "Bulk excel import" disabled behind a "Soon" badge.
The bulk-upload flow exists (/portfolio/[id]/bulk-upload), so make the item
clickable and route to it, matching the "Search by postcode" item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:05:40 +00:00
Jun-te Kim
c0087e08c3
Merge pull request #356 from Hestia-Homes/feat/landlord-overrides-nine-categories
feat(landlord-overrides): wire all 9 override categories through the app layer
2026-07-07 12:04:26 +01:00
Khalim Conn-Kowlessar
83117e90c5 feat(modelling-runs): run config on tasks.inputs; backend contract updates
- The run's config now lives on the task row's new `inputs` column
  (migration PR #361) instead of a config subtask — sub_tasks belong
  entirely to the distributor's execution work.
- Distributor path corrected to /v1/modelling/trigger-run.
- Sub-task granularity is batches, not properties: history shows progress
  as a percentage under the status chip; the Plans column always shows the
  plan count (batch counts are never comparable to property/plan numbers).
- ADR-0008 amended accordingly.

Live verification of the task write waits for PR #361 to be applied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:46:29 +00:00
Jun-te Kim
369485058b fix(building-passport): handle properties with no UPRN
Opening the building passport of an unmatched property 500'd: the whole page is
UPRN-keyed and getSpatialData did BigInt(propertyMeta.uprn) with a null uprn
("Cannot convert null to a BigInt"). Since a property with no UPRN has no
spatial data, installed measures, or EPC to show, render a clear "not matched
yet" state with a link back to the portfolio instead of crashing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 10:38:28 +00:00
Khalim Conn-Kowlessar
fe521aa8d5 feat(db): add inputs column to tasks
Mirrors sub_task.inputs (JSON as text): the task's request inputs. For a
modelling run (ADR-0008, feature/bulk-trigger-modelling) the task row itself
carries the run's config — portfolio id, scenario ids, filters — with
sub_tasks holding the per-property execution work. Column-only change so it
can be applied to the database ahead of the feature merging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:31:11 +00:00
Daniel Roth
27254e7622
Merge pull request #357 from Hestia-Homes/feature/hubspot_deal_data_migration
add client booking reference to hubspot deal data
2026-07-07 09:47:01 +01:00
Jun-te Kim
23fba68727 feat(portfolio): edit address/postcode on unmatched properties
Each unmatched-property row now opens a small dialog to correct its address and
postcode, saved via a new endpoint. UPRN matching (the address search) is left
for a later change — this only fixes the address text so the record is right
before matching.

- PATCH /api/portfolio/[portfolioId]/properties/[propertyId]: auth + zod,
  updates address/postcode scoped by (property, portfolio).
- properties/client.ts: useUpdateAddress() mutation.
- EditAddress: the edit dialog; on save the page re-fetches (router.refresh)
  so the row reflects the new values.
- UnmatchedProperties: row action swapped from the placeholder to the live
  Edit dialog; portfolioId threaded through from the page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 18:41:19 +00:00
Khalim Conn-Kowlessar
10888e3fff refactor(modelling-runs): minimal dispatch payload
The distributor gets {task_id, portfolio_id, scenario_ids, filters} only —
previewed count and triggered_by are app provenance, already durable on the
config subtask keyed by the same task_id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 17:40:09 +00:00