feat: unit-first architecture — floor + street as primary card title

Every VERIFIED_PORTFOLIO property now has explicit units in mock data
(added to prop-002, prop-008, prop-009, prop-010, prop-011, prop-014).

PropertyUnit extracted to domain/unit.ts with formatFloorLabel,
formatUnitTitle, formatMultiUnitFloors helpers; re-exported from
property.ts so all existing imports are unaffected.

Cards now show floor + street as the primary title:
- Specific unit match: "1.OG Nord · Zollstrasse 12"
- Multi-unit (whole building): "1.OG–3.OG · Zollstrasse 12"
- EG retail: "EG Verkauf · Löwenplatz 3"
Building name (e.g. "Bürofläche Zollstrasse 12") appears as subtitle.
Both IntelligenceMatchCard (grid) and MatchCardCompact (list) updated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-21 11:19:13 +02:00
parent a39c6ed200
commit 34a4dcfb29
7 changed files with 145 additions and 76 deletions
+18 -7
View File
@@ -11,6 +11,7 @@ import type {
MatchCardReason,
} from '../../components/match-card/MatchCardViewModel'
import { getCityIntelligence } from '../../lib/locationIntelligence'
import { formatUnitTitle, formatMultiUnitFloors } from '../../domain/unit'
const HARD_CRITERIA = new Set(['area', 'location', 'budget', 'timing'])
@@ -50,8 +51,22 @@ export function buildMatchCardViewModel(
? `${city}${district ? `, ${district}` : ''}`
: signal?.locationHint ?? ''
// For PRE-MARKET: title comes from unit if available, then signal, then property
// Unit-first title: specific unit → floor range of all units → signal property
let cardTitle: string
let propertySubtitle: string | undefined
if (unit && property?.address) {
cardTitle = formatUnitTitle(unit, property.address)
propertySubtitle = property.title
} else if (signal?.title) {
cardTitle = signal.title
} else if (property?.units && property.units.length > 0 && property.address) {
const floorRange = formatMultiUnitFloors(property.units)
cardTitle = `${floorRange} · ${property.address.street} ${property.address.houseNumber}`
propertySubtitle = property.title
} else {
cardTitle = property?.title ?? signal?.companyName ?? signal?.locationHint ?? ''
}
const availabilityLabel =
property?.availabilityDate ??
@@ -74,12 +89,8 @@ export function buildMatchCardViewModel(
return {
id: result.matchId,
title:
signal?.title ??
property?.title ??
signal?.companyName ??
signal?.locationHint ??
'',
title: cardTitle,
propertySubtitle,
resultType,
assetType: property?.assetType,
matchScore,