d902feb6c0
- 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>
199 lines
5.9 KiB
TypeScript
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>
|