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
+3 -43
View File
@@ -4,6 +4,9 @@ import type {
} from './enums'
import { AvailabilityStatus as AS } from './enums'
// Re-export unit types so all existing imports from 'property' continue to work
export type { PropertyUnit, UnitBundle, UnitNeedMatch } from './unit'
// ── Location / Address ────────────────────────────────────────────────────────
export interface Location {
@@ -83,49 +86,6 @@ export interface SoftFactors {
infrastructureNotes?: string
}
// ── Floor / Unit structure ────────────────────────────────────────────────────
export interface PropertyUnit {
id: string
propertyId?: string // FK to parent Property (set when stored independently)
floorLevel: number // 0=EG, 1=1.OG, -1=UG
unitLabel?: string // e.g. "Ost", "West", "Einheit A"
areaSqm: number
available: boolean
rentPricePerSqm?: number
currentTenant?: string
leaseTerm?: string
leaseEndDate?: string
// Flexible letting
isFlexible?: boolean // can be partially leased (Teilfläche)
minLettableSqm?: number // minimum area that can be leased standalone
offeredSqm?: number // currently offered area (≤ areaSqm); undefined = full unit
// Unit-level pre-market release
schattenmarktRelease?: {
enabled: boolean
availableFrom?: string // ISO date; overrides property-level leaseEndDate for this unit
}
}
// A bundle groups multiple free units for a combined offer
export interface UnitBundle {
unitIds: string[]
combinedSqm: number
label: string // e.g. "1.OG + 2.OG"
}
// Result of unit-level need matching
export interface UnitNeedMatch {
needId: string
tenantName: string
tenantCompany?: string
requiredSqmMin: number
requiredSqmMax: number
matchScore: number
matchType: 'exact' | 'partial' | 'bundle' // how the unit satisfies the need
suggestedSqm?: number // for partial: how much of the unit to offer
}
// ── Property ──────────────────────────────────────────────────────────────────
export interface Property {