Files
property-match/src/domain/property.ts
T
Benjamin Sutter d902feb6c0 feat: Objekt→Einheit architecture — unit-level pre-market, property detail page, click-through from cards
- Domain: PropertyUnit extended with propertyId + schattenmarktRelease per unit
- Domain: FutureAvailabilityResult carries resolved property + unit
- useSchattenmarktSignals: generates unit-level signals (schattenmarkt-{propId}-{unitId})
- useUnifiedResults: resolves backing property + unit on FUTURE_AVAILABILITY fast path
- IUnitProvider + MockupUnitProvider: first-class unit access and mutation
- matchCardAdapter: maps preMarketUnit, preMarketAllUnits, propertyId, unitId to ViewModel
- IntelligenceMatchCard: PRE-MARKET VERIFIED shows unit info strip + "Zur Einheit →" button
- PropertyDetailView: unit-level toggles + date pickers inside PreMarketPanel
- New page: /demand/property/:propertyId with unit table, status chips, inquiry form
- App.tsx: demand route /demand/property/:propertyId registered
- Mock data: prop-001/007/037 units updated with correct lease dates + unit-level schattenmarktRelease

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 19:47:59 +02:00

199 lines
5.9 KiB
TypeScript

import type {
AssetType, ResultType, AvailabilityStatus, AvailabilityType,
FreshnessStatus, RiskLevel, SourceType, DataQualityLevel,
} from './enums'
// ── Location / Address ────────────────────────────────────────────────────────
export interface Location {
city: string
district?: string
canton?: string
region?: string
country: string
coordinates?: { lat: number; lng: number }
}
export interface Address {
street: string
houseNumber: string
postalCode: string
city: string
country: string
}
// ── Source Metadata ───────────────────────────────────────────────────────────
export interface SourceMeta {
sourceType: SourceType | string
sourceLabel?: string
sourceUrl?: string
sourceUpdatedAt?: string
externalId?: string
}
// ── Data Quality ──────────────────────────────────────────────────────────────
export interface DataQuality {
score: number
qualityLevel?: DataQualityLevel
missingCriticalFields: string[]
missingOptionalFields: string[]
lastVerifiedAt?: string
freshness: FreshnessStatus
warnings: string[]
}
// ── Hard Facts ────────────────────────────────────────────────────────────────
export interface PropertyHardFacts {
floor?: number
fitOut?: 'SHELL' | 'BASIC' | 'FULL' | 'PREMIUM'
parking?: number
publicTransportScore?: number
usageType?: string
ceilingHeightM?: number
loadingDocksCount?: number
powerSupplyKva?: number
hasServerRoom?: boolean
isBarrierFree?: boolean
}
// ── Soft Factors ──────────────────────────────────────────────────────────────
export interface SoftFactors {
prestigeScore?: number
visibilityScore?: number
footfallScore?: number
commuterAccessScore?: number
talentAccessScore?: number
esgScore?: number
flexibilityScore?: number
expansionPotentialScore?: number
taxEnvironmentScore?: number
// Legacy aliases kept for mock-data backward compatibility
prestige?: number
accessibility?: number
talentAccess?: number
esgRating?: string
passerbyFrequency?: 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY_HIGH'
parkingSpots?: number
publicTransportMinutes?: number
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 {
id: string
organizationId?: string
title: string
assetType: AssetType
resultType: ResultType
location: Location
address: Address
areaSqm: number
areaSqmMin?: number
areaSqmMax?: number
rentPricePerSqm: number
rentChfSqmYear?: number
totalRentMonthly?: number
ancillaryCosts?: number
availabilityDate: string
availabilityStatus: AvailabilityStatus
availabilityType?: AvailabilityType
sourceType: string
sourceLabel?: string
sourceUrl?: string
sourceUpdatedAt?: string
sourceMeta?: SourceMeta
confidenceScore: number
dataQuality: DataQuality
softFactors?: SoftFactors
hardFacts?: PropertyHardFacts
// Legacy fields — kept for backward compat
floorLevel?: number
expansionPotentialSqm?: number
contractDurationMonths?: number
riskLevel?: RiskLevel
description?: string
images?: string[]
propertyNumber?: string
units?: PropertyUnit[]
mapImageUrl?: string
leaseTerm?: string
leaseStartDate?: string
leaseEndDate?: string
breakoutOption?: boolean
breakoutOptionDate?: string
currentTenant?: string
importedFrom?: string
importedAt?: string
lastUpdatedAt?: string
schattenmarktRelease?: { enabled: boolean; leadTimeMonths: number }
status?: 'ACTIVE' | 'INACTIVE' | 'DRAFT' | 'ARCHIVED'
lastReviewedAt?: string
createdAt: string
updatedAt: string
}
export type CreatePropertyInput = Omit<Property, 'id' | 'createdAt' | 'updatedAt'>
export type UpdatePropertyInput = Partial<CreatePropertyInput>