feat: unit-level matching — each floor lettable independently or as bundle

- PropertyUnit: add isFlexible, minLettableSqm, offeredSqm fields
- New UnitBundle + UnitNeedMatch domain types
- unitMatchService: match individual units or bundles against LatentNeeds
  (exact, partial/Teilfläche, bundle match types with area scoring)
- Mock units updated: prop-001/007/012/013 free units flagged as flexible
  with minimum lettable areas
- UnitStructurePanel replaces old Stockwerkstruktur table:
  - Checkboxes on free units (when ≥2 exist) for bundle selection
  - Inline match pills per free unit (top need + score)
  - Expand row to see all matching needs with area details
  - 'Teilfläche möglich' chip for flexible units with min sqm
  - Bundle panel appears when 2+ units selected: shows combined sqm,
    bundle matches, and note about remaining area after contract

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-19 20:24:17 +02:00
parent 7cda66f98a
commit f0e58f5f7a
4 changed files with 387 additions and 37 deletions
+23
View File
@@ -94,6 +94,29 @@ export interface PropertyUnit {
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
}
// 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 ──────────────────────────────────────────────────────────────────