Files
property-match/src/hooks/useSchattenmarktSignals.ts
T
Benjamin Sutter 5fe15bac62 feat: Future Availability Intelligence — rebrand & card redesign
Replaces "Schattenmarkt" branding with professional Future Availability
Intelligence throughout the UI. Two distinct card types: Pre-Market
(LEASE_EXPIRY + verified, green accent) and Market Signal (probabilistic,
blue accent). Professioneller Data-Header replaces dark purple gradient.
New detail panel sections: Erkannte Marktindikatoren, Strategische
Interpretation, Bestätigt/Nicht bestätigt. All 15 mock signals enriched
with concrete Swiss CRE market indicators and strategic interpretations.
Signal quality shown as 4-dot indicator (Hoch/Mittel/Niedrig).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 13:06:16 +02:00

87 lines
3.5 KiB
TypeScript

import { useMemo } from 'react'
import type { Property } from '../domain/property'
import type { FutureSignal } from '../domain/futureSignal'
import { SignalType, RiskLevel, ResultType } from '../domain/enums'
// Matches the mock date used throughout the prototype (currentDate context: 2026-05-20)
const MOCK_TODAY = new Date('2026-05-20')
export function useSchattenmarktSignals(properties: Property[]): FutureSignal[] {
return useMemo(() => {
const signals: FutureSignal[] = []
for (const p of properties) {
if (p.resultType !== ResultType.VERIFIED_PORTFOLIO) continue
const rel = p.schattenmarktRelease
if (!rel?.enabled) continue
const triggerDate = getEarliestTriggerDate(p, rel.leadTimeMonths)
if (!triggerDate || MOCK_TODAY < triggerDate) continue
signals.push(buildSignal(p))
}
return signals
}, [properties])
}
function getEarliestTriggerDate(p: Property, leadTimeMonths: number): Date | null {
const candidates: Date[] = []
if (p.leaseEndDate) {
const d = new Date(p.leaseEndDate)
d.setMonth(d.getMonth() - leadTimeMonths)
candidates.push(d)
}
if (p.breakoutOption && p.breakoutOptionDate) {
const d = new Date(p.breakoutOptionDate)
d.setMonth(d.getMonth() - leadTimeMonths)
candidates.push(d)
}
return candidates.length ? candidates.reduce((a, b) => (a < b ? a : b)) : null
}
function buildSignal(p: Property): FutureSignal {
const targetDate = p.breakoutOption && p.breakoutOptionDate
? new Date(p.breakoutOptionDate)
: p.leaseEndDate ? new Date(p.leaseEndDate) : new Date()
const monthsUntil = Math.max(1, Math.round(
(targetDate.getTime() - MOCK_TODAY.getTime()) / (1000 * 60 * 60 * 24 * 30)
))
const locationLabel = `${p.location.city}${p.location.district ? `, ${p.location.district}` : ''}`
const monthName = targetDate.toLocaleDateString('de-CH', { month: 'long', year: 'numeric' })
return {
id: `schattenmarkt-${p.id}`,
signalType: SignalType.LEASE_EXPIRY,
propertyId: p.id,
title: `${p.title} — frei ab ${monthName}`,
locationHint: locationLabel,
areaSqmEstimate: p.areaSqm,
probability: 0.92,
confidenceScore: 0.92,
timeHorizonMonths: monthsUntil,
source: { type: 'LEASE_CONTRACT', credibility: 'HIGH' },
sensitivityLevel: 'INTERNAL',
disclaimer: 'Verwaltung hat diese Fläche für Pre-Market-Sichtbarkeit freigegeben. Vertragsende aus internem ERP bestätigt — höchste Signalqualität.',
riskLevel: RiskLevel.LOW,
relevanceScore: 0.92,
isVerified: true,
organizationId: p.organizationId,
createdAt: MOCK_TODAY.toISOString(),
updatedAt: MOCK_TODAY.toISOString(),
aiSummary: `Vertrag der ${p.currentTenant ?? 'aktuellen Mietpartei'} läuft in ${monthsUntil} Monaten aus (${monthName}). Fläche: ${p.areaSqm.toLocaleString('de-CH')} m² · ${locationLabel}. Die Verwaltung hat dieses Objekt explizit für den Markt freigegeben — vertraglich bestätigt, keine Schätzung.`,
marketIndicators: [
`Vertragsende ${monthName} aus Verwaltungssystem bestätigt`,
`Fläche ${p.areaSqm.toLocaleString('de-CH')} m² — ${locationLabel}`,
`Pre-Market-Freigabe durch Verwaltung erteilt`,
],
confirmedFacts: [
`Vertragsende ${monthName} aus ERP bestätigt`,
`Fläche ${p.areaSqm.toLocaleString('de-CH')} m² — bestätigt`,
`Standort ${locationLabel} — bestätigt`,
],
unconfirmedFacts: [
'Ob Nachmieter bereits bekannt',
'Ob Umbaumassnahmen geplant',
],
}
}