From 5fe15bac62ed1a182224864de9f83f88b68d038b Mon Sep 17 00:00:00 2001 From: Benjamin Sutter Date: Wed, 20 May 2026 13:06:16 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Future=20Availability=20Intelligence=20?= =?UTF-8?q?=E2=80=94=20rebrand=20&=20card=20redesign?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../match-card/IntelligenceMatchCard.tsx | 491 ++++++++---------- .../match-card/MatchCardViewModel.ts | 7 + .../FutureAvailabilityContextPanel.tsx | 63 ++- src/components/onboarding/WelcomeDialog.tsx | 10 +- src/components/results/ResultFilterBar.tsx | 26 +- src/components/supply/PropertyDetailView.tsx | 12 +- src/domain/futureSignal.ts | 6 + src/features/matching/matchCardAdapter.ts | 12 + src/hooks/useSchattenmarktSignals.ts | 16 +- src/mock-data/futureSignals.ts | 232 ++++++--- src/pages/demand/MatchDetail.tsx | 2 +- src/pages/demand/Results.tsx | 12 +- 12 files changed, 531 insertions(+), 358 deletions(-) diff --git a/src/components/match-card/IntelligenceMatchCard.tsx b/src/components/match-card/IntelligenceMatchCard.tsx index 4146fbc..c1ac62a 100644 --- a/src/components/match-card/IntelligenceMatchCard.tsx +++ b/src/components/match-card/IntelligenceMatchCard.tsx @@ -1,4 +1,4 @@ -import { Alert, Box, Button, Chip, Divider, LinearProgress, Tooltip, Typography } from '@mui/material' +import { Box, Button, Chip, Divider, Typography } from '@mui/material' import { BarChart2, Briefcase, @@ -9,9 +9,7 @@ import { FileText, Newspaper, ShieldCheck, - TrendingUp, User, - Zap, } from 'lucide-react' import { LocationPreview } from '../shared/LocationPreview' import { getScoreTier, SCORE_THEME } from '../shared/scoreTheme' @@ -36,210 +34,211 @@ const SOURCE_META: Record = { LEASE_CONTRACT: { label: 'Vertrag verifiziert', icon: }, } -const SIGNAL_TYPE_LABELS: Record = { - EXPANSION: 'Expansion', - POSSIBLE_MOVE_OUT: 'Möglicher Auszug', - CONSTRUCTION_PROJECT:'Bauprojekt', - RESTRUCTURING: 'Restrukturierung', - PROJECT_DEVELOPMENT: 'Projektentwicklung', - SPACE_CONSOLIDATION: 'Flächenkonsolidierung', - LEASE_EXPIRY: 'Vertragsende', +const ASSET_TYPE_LABELS: Record = { + OFFICE: 'Bürofläche', + LOGISTICS: 'Lagerfläche', + RETAIL: 'Retailfläche', + PRODUCTION: 'Produktionsfläche', + MIXED: 'Gewerbefläche', } -// Human-readable explanation of WHY a signal type is relevant to a space search -const SIGNAL_RELEVANCE_BRIDGE: Record = { - EXPANSION: 'Neuer Flächenbedarf entsteht — Fläche wird gesucht', - POSSIBLE_MOVE_OUT: 'Fläche könnte frei werden — frühzeitig beobachten', - CONSTRUCTION_PROJECT:'Neubau geplant — zukünftige Verfügbarkeit möglich', - RESTRUCTURING: 'Flächenveränderung durch Restrukturierung möglich', - PROJECT_DEVELOPMENT: 'Projektentwicklung — neue Flächen in Planung', - SPACE_CONSOLIDATION: 'Konsolidierung — Teilflächen könnten verfügbar werden', +// Signal type → opportunity headline (with asset type) +function getOpportunityHeadline(signalType: string | undefined, assetTypeLabel: string): string { + switch (signalType) { + case 'LEASE_EXPIRY': return `${assetTypeLabel} wird verfügbar` + case 'POSSIBLE_MOVE_OUT': return `Mögliche ${assetTypeLabel} erkannt` + case 'EXPANSION': return `Unternehmen sucht ${assetTypeLabel}` + case 'CONSTRUCTION_PROJECT': return `Neubau: ${assetTypeLabel} in Planung` + case 'RESTRUCTURING': return `Mögliche Flächenfreigabe erkannt` + case 'SPACE_CONSOLIDATION': return `Mögliche Teilfläche erkannt` + case 'PROJECT_DEVELOPMENT': return `Neue Fläche in Projektentwicklung` + default: return `Potenzielle ${assetTypeLabel} erkannt` + } } -const CREDIBILITY_META: Record = { - HIGH: { label: 'Hohe Quellenqualität', color: '#1a7a4a' }, - MEDIUM: { label: 'Mittlere Quellenqualität', color: '#d97706' }, - LOW: { label: 'Niedrige Quellenqualität', color: '#c0392b' }, -} - -// ── Schattenmarkt hero zone ─────────────────────────────────────────────────── - -function SignalHero({ vm }: { vm: MatchCardViewModel }) { - const tier = getScoreTier(vm.matchScore) - const theme = SCORE_THEME[tier] - const signalLabel = vm.signalType ? (SIGNAL_TYPE_LABELS[vm.signalType] ?? vm.signalType) : 'Signal' - const relevanceBridge = vm.signalType ? (SIGNAL_RELEVANCE_BRIDGE[vm.signalType] ?? null) : null - const initials = (vm.title ?? '?') - .split(/\s+/) - .slice(0, 2) - .map(w => w[0]) - .join('') - .toUpperCase() - +// Signal quality dots display +function SignalQualityDots({ quality }: { quality: 'HIGH' | 'MEDIUM' | 'LOW' | undefined }) { + const config = { + HIGH: { dots: [1, 1, 1, 1], color: '#1a7a4a', label: 'Hohe Signalqualität' }, + MEDIUM: { dots: [1, 1, 1, 0], color: '#d97706', label: 'Mittlere Signalqualität' }, + LOW: { dots: [1, 1, 0, 0], color: '#c0392b', label: 'Niedrige Signalqualität' }, + } + const c = quality ? config[quality] : config.LOW return ( - - {/* Gradient hero background */} - - {/* Subtle pattern overlay */} - - - {/* Company initials */} - - - {initials} - - - - {/* Signal type label */} - - - - {signalLabel} - - - - {/* Relevance bridge — why this appears in results */} - {relevanceBridge && ( - - {relevanceBridge} - - )} - - {/* Verified badge */} - {vm.signalIsVerified && ( - - - Verifiziert - - )} - - - {/* Score badge — overlaid top-left */} - - - {vm.matchScore}% - - {tier !== 'bronze' && ( - - {theme.label} - - )} - - - {/* Schattenmarkt chip — top-right */} - - - + + {c.dots.map((filled, i) => ( + + ))} + + {c.label} + ) } -// ── Signal metadata strip ───────────────────────────────────────────────────── +// ── Future Availability Card ────────────────────────────────────────────────── -function SignalMetaStrip({ vm }: { vm: MatchCardViewModel }) { - const sourceMeta = vm.signalSourceType ? SOURCE_META[vm.signalSourceType] : null - const credMeta = vm.signalSourceCredibility ? CREDIBILITY_META[vm.signalSourceCredibility] : null - const probPct = vm.signalProbability ? Math.round(vm.signalProbability * 100) : null +function FutureAvailabilityCard({ vm }: { vm: MatchCardViewModel }) { + const isControlled = vm.signalIsControlled ?? false + const accentColor = isControlled ? '#1a7a4a' : '#1d4ed8' + const headerBg = isControlled ? '#f0fdf4' : '#f8fafc' + const badge = isControlled ? 'Pre-Market' : 'Market Signal' + const badgeBg = isControlled ? '#dcfce7' : '#dbeafe' + const badgeColor = isControlled ? '#1a7a4a' : '#1d4ed8' + + const assetLabel = vm.assetType ? (ASSET_TYPE_LABELS[vm.assetType] ?? vm.assetType) : 'Fläche' + const headline = getOpportunityHeadline(vm.signalType, assetLabel) + + const tier = getScoreTier(vm.matchScore) + const theme = SCORE_THEME[tier] return ( - - {/* Source + credibility row */} - - {sourceMeta && ( - vm.signalSourceUrl ? ( - - {sourceMeta.icon} - {sourceMeta.label} - - - ) : ( - - {sourceMeta.icon} - {sourceMeta.label} - - ) - )} - {credMeta && ( - - - - - {vm.signalSourceCredibility === 'HIGH' ? 'Hoch' : vm.signalSourceCredibility === 'MEDIUM' ? 'Mittel' : 'Niedrig'} - - - + + + {/* Data Header */} + + {/* Asset type + location */} + + + {assetLabel.toUpperCase()} · {vm.locationLabel?.split(',')[0]?.toUpperCase() ?? ''} + + + {badge} + + + + {/* Opportunity headline */} + + {headline} + + + {/* Key facts row */} + + {vm.signalAreaSqmEstimate ? ( + + ~{vm.signalAreaSqmEstimate.toLocaleString('de-CH')} m² + + ) : null} + {vm.availabilityLabel && ( + + {vm.availabilityLabel} + + )} + + + + {/* Score + signal quality strip */} + + + + {vm.matchScore}% + + + + {isControlled && ( + + + Verifiziert + )} - {/* Probability bar */} - {probPct !== null && ( - - - Eintretenswahrscheinlichkeit - = 70 ? '#1a7a4a' : probPct >= 50 ? '#d97706' : '#c0392b' }}> - {probPct}% - - - = 70 ? '#1a7a4a' : probPct >= 50 ? '#d97706' : '#c0392b', - borderRadius: 2, - }, - }} - /> - - )} + {/* Card body */} + - {/* Market indicator */} - {vm.signalMarketIndicator && ( - - - - {vm.signalMarketIndicator} - + {/* CONTROLLED: Confirmed facts */} + {isControlled && (vm.signalConfirmedFacts?.length ?? 0) > 0 && ( + + {(vm.signalConfirmedFacts ?? []).slice(0, 2).map((fact, i) => ( + + + {fact} + + ))} + + )} + + {/* MARKET SIGNAL: Market indicators */} + {!isControlled && (vm.signalMarketIndicators?.length ?? 0) > 0 && ( + <> + + Erkannte Marktindikatoren + + + {(vm.signalMarketIndicators ?? []).slice(0, 3).map((ind, i) => ( + + + {ind} + + ))} + + + )} + + {/* Why relevant (match reasons) */} + {vm.reasons.length > 0 && ( + <> + + + Warum relevant für Ihre Suche? + + + {vm.reasons.slice(0, 3).map((r, i) => ( + + + + {r.label} + {r.explanation} + + + ))} + + + )} + + {/* Actions */} + + {vm.actions.map(a => ( + + ))} - )} + + {/* Disclaimer footnote */} + {vm.disclaimer && ( + + {vm.disclaimer} + + )} + ) } @@ -260,67 +259,61 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel }: Pro const rt = RESULT_TYPE_META[vm.resultType] ?? { label: vm.resultType, color: '#64748b' } const isFuture = vm.resultType === 'FUTURE_AVAILABILITY' + // Future availability cards use the new design + if (isFuture) { + return + } + return ( {/* ── Hero zone ── */} - {isFuture ? ( - - ) : ( - - - - - {vm.matchScore}% + + + + + {vm.matchScore}% + + {tier !== 'bronze' && ( + + {theme.label} - {tier !== 'bronze' && ( - - {theme.label} - - )} - - - - {vm.resultType === 'VERIFIED_PORTFOLIO' && ( - } - label="Ihr Objekt" - size="small" - sx={{ bgcolor: 'rgba(30,58,95,0.85)', color: 'white', fontWeight: 700, fontSize: 9, height: 18, '& .MuiChip-icon': { ml: 0.5 } }} - /> - )} - + )} - )} - - {/* ── Signal metadata (Schattenmarkt only) ── */} - {isFuture && } + + + {vm.resultType === 'VERIFIED_PORTFOLIO' && ( + } + label="Ihr Objekt" + size="small" + sx={{ bgcolor: 'rgba(30,58,95,0.85)', color: 'white', fontWeight: 700, fontSize: 9, height: 18, '& .MuiChip-icon': { ml: 0.5 } }} + /> + )} + + {/* ── Card body ── */} - + {vm.title} @@ -328,25 +321,8 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel }: Pro {[vm.locationLabel, vm.availabilityLabel].filter(Boolean).join(' · ')} - {/* AI Signal Summary (Schattenmarkt) */} - {isFuture && vm.aiSignalSummary && ( - <> - - - - - {vm.aiSignalSummary} - - - - )} - - {/* Regular explainability summary (non-future) */} - {!isFuture && vm.explainabilitySummary && ( + {/* Regular explainability summary */} + {vm.explainabilitySummary && ( <> @@ -402,13 +378,6 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel }: Pro ))} - - {/* Disclaimer */} - {vm.disclaimer && ( - - {vm.disclaimer} - - )} ) } diff --git a/src/components/match-card/MatchCardViewModel.ts b/src/components/match-card/MatchCardViewModel.ts index e797c3d..86d7400 100644 --- a/src/components/match-card/MatchCardViewModel.ts +++ b/src/components/match-card/MatchCardViewModel.ts @@ -58,6 +58,13 @@ export interface MatchCardViewModel { signalType?: string signalIsVerified?: boolean aiSignalSummary?: string + signalQuality?: 'HIGH' | 'MEDIUM' | 'LOW' + signalMarketIndicators?: string[] + signalStrategicInterpretation?: string + signalConfirmedFacts?: string[] + signalUnconfirmedFacts?: string[] + signalIsControlled?: boolean + signalAreaSqmEstimate?: number // States isSelected?: boolean diff --git a/src/components/match-detail/FutureAvailabilityContextPanel.tsx b/src/components/match-detail/FutureAvailabilityContextPanel.tsx index ae1818b..6c69e4c 100644 --- a/src/components/match-detail/FutureAvailabilityContextPanel.tsx +++ b/src/components/match-detail/FutureAvailabilityContextPanel.tsx @@ -1,7 +1,9 @@ import { Box, Button, Chip, Divider, Paper, Typography } from '@mui/material' import { + AlertTriangle, BarChart2, Briefcase, + CheckCircle2, ExternalLink, FileCheck, FileText, @@ -74,7 +76,7 @@ export function FutureAvailabilityContextPanel({ match: _match, signal }: Props) - Schattenmarkt-Signal + Future Availability Signal {signal.isVerified && ( @@ -204,7 +206,64 @@ export function FutureAvailabilityContextPanel({ match: _match, signal }: Props) )} - {/* 4. Relevance bridge */} + + + {/* 4. Erkannte Marktindikatoren */} + {signal.marketIndicators && signal.marketIndicators.length > 0 && ( + + + Erkannte Marktindikatoren + + + {signal.marketIndicators.map((indicator, i) => ( + + + {indicator} + + ))} + + + )} + + {/* 5. Strategische Interpretation */} + {signal.strategicInterpretation && ( + + + Strategische Interpretation + + + + {signal.strategicInterpretation} + + + + )} + + {/* 6. Transparenz (Bestätigt / Nicht bestätigt) */} + {((signal.confirmedFacts && signal.confirmedFacts.length > 0) || + (signal.unconfirmedFacts && signal.unconfirmedFacts.length > 0)) && ( + + + Transparenz + + + {(signal.confirmedFacts ?? []).map((fact, i) => ( + + + {fact} + + ))} + {(signal.unconfirmedFacts ?? []).map((fact, i) => ( + + + {fact} + + ))} + + + )} + + {/* 7. Relevance bridge */} {relevanceBridge && ( diff --git a/src/components/onboarding/WelcomeDialog.tsx b/src/components/onboarding/WelcomeDialog.tsx index 6d083a4..12a1d0e 100644 --- a/src/components/onboarding/WelcomeDialog.tsx +++ b/src/components/onboarding/WelcomeDialog.tsx @@ -63,7 +63,7 @@ function Slide0() { } color="#7c3aed" - text="Schattenmarkt-Signale der KI" + text="Future Availability Intelligence" /> @@ -81,7 +81,7 @@ function Slide1() { Schreiben Sie Ihren Flächenbedarf in natürlicher Sprache. Die KI extrahiert alle Kriterien - und findet die besten Matches aus Portfolio, Markt und Schattenmarkt. + und findet die besten Matches aus Portfolio, Markt und Future Availability. - Schattenmarkt — Vor dem Markt informiert + Future Availability Intelligence - Die KI überwacht Stelleninserate, Pressemeldungen und Baubewilligungen — frühzeitige - Hinweise auf mögliche Verfügbarkeiten, bevor sie ausgeschrieben werden. + Frühzeitige Marktintelligenz zu potenziell verfügbaren Flächen — verifizierte + Vertragsenden und erkannte Marktsignale, bevor sie öffentlich werden. void sortBy: SortBy onSortChange: (v: SortBy) => void - showSchattenmarkt: boolean - onShowSchattenmarktChange: (v: boolean) => void + showFutureAvailability: boolean + onShowFutureAvailabilityChange: (v: boolean) => void showOwnProperties?: boolean onShowOwnPropertiesChange?: (v: boolean) => void isPropertyManager?: boolean @@ -34,8 +34,8 @@ export function ResultFilterBar({ onFilterChange, sortBy, onSortChange, - showSchattenmarkt, - onShowSchattenmarktChange, + showFutureAvailability, + onShowFutureAvailabilityChange, showOwnProperties, onShowOwnPropertiesChange, isPropertyManager, @@ -68,19 +68,19 @@ export function ResultFilterBar({ - {/* Schattenmarkt toggle */} + {/* Future Availability toggle */} } - label="Schattenmarkt" - onClick={() => onShowSchattenmarktChange(!showSchattenmarkt)} + icon={} + label="Future Availability" + onClick={() => onShowFutureAvailabilityChange(!showFutureAvailability)} sx={{ - bgcolor: showSchattenmarkt ? '#f3e8ff' : 'transparent', - color: showSchattenmarkt ? '#6d28d9' : 'text.disabled', - border: `1px solid ${showSchattenmarkt ? '#c4b5fd' : '#e2e8f0'}`, - fontWeight: showSchattenmarkt ? 600 : 400, - textDecoration: showSchattenmarkt ? 'none' : 'line-through', + bgcolor: showFutureAvailability ? '#f3e8ff' : 'transparent', + color: showFutureAvailability ? '#6d28d9' : 'text.disabled', + border: `1px solid ${showFutureAvailability ? '#c4b5fd' : '#e2e8f0'}`, + fontWeight: showFutureAvailability ? 600 : 400, + textDecoration: showFutureAvailability ? 'none' : 'line-through', '& .MuiChip-icon': { ml: 0.75 }, }} /> diff --git a/src/components/supply/PropertyDetailView.tsx b/src/components/supply/PropertyDetailView.tsx index a227294..e2847b3 100644 --- a/src/components/supply/PropertyDetailView.tsx +++ b/src/components/supply/PropertyDetailView.tsx @@ -309,7 +309,7 @@ function UnitStructurePanel({ p }: { p: Property }) { ) } -// ── Schattenmarkt release panel ─────────────────────────────────────────────── +// ── Pre-Market release panel ────────────────────────────────────────────────── const MOCK_TODAY = new Date('2026-05-20') @@ -346,7 +346,7 @@ function SchattenmarktReleasePanel({ p }: { p: Property }) { await propertyService.update(p.id, { schattenmarktRelease: { enabled: nextEnabled, leadTimeMonths: nextLeadTime } }) await queryClient.invalidateQueries({ queryKey: ['property', p.id] }) await queryClient.invalidateQueries({ queryKey: ['properties'] }) - showToast(nextEnabled ? 'Schattenmarkt-Freigabe aktiviert.' : 'Schattenmarkt-Freigabe deaktiviert.', 'success') + showToast(nextEnabled ? 'Pre-Market Freigabe aktiviert.' : 'Pre-Market Freigabe deaktiviert.', 'success') } catch { showToast('Fehler beim Speichern.', 'error') } finally { @@ -367,7 +367,7 @@ function SchattenmarktReleasePanel({ p }: { p: Property }) { return ( <> - + - Für Schattenmarkt freigeben + Pre-Market freigeben - Nachfragesuchende sehen dieses Objekt vor Vertragsende im Feed + Fläche vor offizieller Vermarktung im Markt sichtbar machen @@ -455,7 +455,7 @@ function SchattenmarktReleasePanel({ p }: { p: Property }) { {!enabled && ( Wenn aktiviert, erscheint dieses Objekt {leadTimeMonths} Monate vor Vertragsende als - verifiziertes Schattenmarkt-Signal im Nachfrage-Feed. + verifiziertes Pre-Market Signal im Nachfrage-Feed. )} diff --git a/src/domain/futureSignal.ts b/src/domain/futureSignal.ts index c463e7e..35d8820 100644 --- a/src/domain/futureSignal.ts +++ b/src/domain/futureSignal.ts @@ -43,4 +43,10 @@ export interface FutureSignal { matchabilityScore?: number // 0–100: how well this signal can be matched to needs reviewStatus?: ReviewStatus aiSummary?: string // AI-generated explanation of what this signal means + + // Strategic intelligence fields + marketIndicators?: string[] // ["Mitarbeiterzahl rückläufig -15%", ...] + strategicInterpretation?: string // "Die Signale deuten auf ..." + confirmedFacts?: string[] // ["Region bestätigt", ...] + unconfirmedFacts?: string[] // ["Exakte Fläche unbekannt", ...] } diff --git a/src/features/matching/matchCardAdapter.ts b/src/features/matching/matchCardAdapter.ts index 0cd60fe..5c5048a 100644 --- a/src/features/matching/matchCardAdapter.ts +++ b/src/features/matching/matchCardAdapter.ts @@ -104,5 +104,17 @@ export function buildMatchCardViewModel( signalType: signal?.signalType, signalIsVerified: signal?.isVerified, aiSignalSummary: signal?.aiSummary, + signalQuality: (() => { + if (!signal) return undefined + if (signal.isVerified || signal.probability >= 0.70) return 'HIGH' + if (signal.probability >= 0.50) return 'MEDIUM' + return 'LOW' + })(), + signalIsControlled: signal?.signalType === 'LEASE_EXPIRY' && signal?.isVerified === true, + signalMarketIndicators: signal?.marketIndicators, + signalStrategicInterpretation: signal?.strategicInterpretation, + signalConfirmedFacts: signal?.confirmedFacts, + signalUnconfirmedFacts: signal?.unconfirmedFacts, + signalAreaSqmEstimate: signal?.areaSqmEstimate, } } diff --git a/src/hooks/useSchattenmarktSignals.ts b/src/hooks/useSchattenmarktSignals.ts index d598a8a..26c0011 100644 --- a/src/hooks/useSchattenmarktSignals.ts +++ b/src/hooks/useSchattenmarktSignals.ts @@ -60,7 +60,7 @@ function buildSignal(p: Property): FutureSignal { timeHorizonMonths: monthsUntil, source: { type: 'LEASE_CONTRACT', credibility: 'HIGH' }, sensitivityLevel: 'INTERNAL', - disclaimer: 'Verwaltung hat dieses Objekt für den Schattenmarkt freigegeben. Vertragsende aus internem ERP bestätigt — höchste Signalqualität.', + 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, @@ -68,5 +68,19 @@ function buildSignal(p: Property): FutureSignal { 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', + ], } } diff --git a/src/mock-data/futureSignals.ts b/src/mock-data/futureSignals.ts index 6a24634..a9f3e79 100644 --- a/src/mock-data/futureSignals.ts +++ b/src/mock-data/futureSignals.ts @@ -2,13 +2,14 @@ import { SignalType, RiskLevel } from '../domain/enums' import type { FutureSignal } from '../domain/futureSignal' export const mockFutureSignals: FutureSignal[] = [ - // --- signal-001: DataCloud Expansion Zürich-West --- + // --- signal-001: DataCloud Systems AG — Büroexpansion Zürich-West --- { id: 'signal-001', signalType: SignalType.EXPANSION, propertyId: 'prop-005', companyName: 'DataCloud Systems AG', locationHint: 'Zürich-West / Technopark', + title: 'DataCloud Systems AG sucht ~600 m² Bürofläche in Zürich-West', areaSqmEstimate: 600, probability: 0.72, confidenceScore: 0.68, @@ -30,15 +31,24 @@ export const mockFutureSignals: FutureSignal[] = [ createdAt: '2025-05-01T07:00:00Z', updatedAt: '2025-05-10T07:00:00Z', aiSummary: 'KI hat in 90 Tagen 14 Stelleninserate von DataCloud Systems AG auf LinkedIn und Indeed ausgewertet — davon 11 mit explizitem Standort Zürich-West/Technopark und hybridem Arbeitsmodell. Das Wachstumsmuster (DevOps, Cloud-Architektur, Sales) deutet auf eine Teamvergrösserung von ~40 Personen hin, was einem Flächenbedarf von ca. 600 m² entspricht. Der Tech-Sektor in Zürich-West verzeichnet 2024 den stärksten Stellenzuwachs seit 2018.', + marketIndicators: [ + '14 Stelleninserate mit Standort Zürich-West in 90 Tagen', + 'Teamwachstum ~40 Personen (DevOps, Cloud-Architektur, Sales)', + 'Tech-Sektor Zürich-West: +38% Stellenwachstum 2024', + ], + strategicInterpretation: 'DataCloud Systems AG befindet sich in einer klaren Wachstumsphase mit Fokus auf Zürich-West. Die Muster sprechen für organische Expansion — kein Standortwechsel erkennbar.', + confirmedFacts: ['Region Zürich-West aus Inseraten bestätigt', 'Flächentyp Büro plausibel', 'Wachstumstrend seit 6 Monaten vorhanden'], + unconfirmedFacts: ['Exakter Umzugszeitpunkt', 'Ob interner Ausbau am aktuellen Standort geplant', 'Finale Flächengrösse'], }, - // --- signal-002: Helvetia Produktion possible move-out Reinach --- + // --- signal-002: Helvetia Produktion GmbH — möglicher Auszug Reinach BL --- { id: 'signal-002', signalType: SignalType.POSSIBLE_MOVE_OUT, companyName: 'Helvetia Produktion GmbH', propertyId: 'prop-006', locationHint: 'Reinach BL, Industriezone Nord', + title: 'Helvetia Produktion GmbH — Standort Reinach unter Restrukturierungsdruck', areaSqmEstimate: 3200, probability: 0.48, confidenceScore: 0.44, @@ -60,13 +70,22 @@ export const mockFutureSignals: FutureSignal[] = [ createdAt: '2025-05-03T08:00:00Z', updatedAt: '2025-05-10T08:00:00Z', aiSummary: 'Drei unabhängige Presseartikel (NZZ, Handelszeitung, Basellandschaftliche Zeitung) berichten über die angekündigte Restrukturierung der Muttergesellschaft mit geplantem Stellenabbau von 8–12% bis Ende 2025. Der Standort Reinach BL wird in internen Dokumenten als "unter Überprüfung" eingestuft. KI-Konfidenz bleibt moderat, da kein Auszugstermin bestätigt wurde.', + marketIndicators: [ + '3 unabhängige Presseartikel zu Restrukturierung der Muttergesellschaft', + 'Geplanter Stellenabbau 8–12% bis Ende 2025 bestätigt', + 'Standort Reinach BL in internen Dokumenten als «unter Überprüfung» eingestuft', + ], + strategicInterpretation: 'Die Restrukturierung der Muttergesellschaft erhöht die Wahrscheinlichkeit einer Standortschliessung oder -reduktion in Reinach. Kein Auszugstermin bestätigt — frühes Beobachtungsstadium.', + confirmedFacts: ['Restrukturierung der Muttergesellschaft offiziell bestätigt', 'Stellenabbau angekündigt', 'Logistik-Standort Reinach betroffen'], + unconfirmedFacts: ['Ob Standort Reinach geschlossen wird', 'Zeitpunkt des Auszugs', 'Ob Fläche vermietet oder verkauft wird'], }, - // --- signal-003: Bern Wankdorf Neubau Büro/Gewerbe --- + // --- signal-003: Bern Wankdorf — Neubau 4'500 m² Büro/Gewerbe --- { id: 'signal-003', signalType: SignalType.CONSTRUCTION_PROJECT, locationHint: 'Bern, Wankdorf', + title: 'Neubau Wankdorf West — 4\'500 m² Büro/Gewerbe, Fertigstellung Q1 2027', areaSqmEstimate: 4500, probability: 0.85, confidenceScore: 0.80, @@ -89,14 +108,23 @@ export const mockFutureSignals: FutureSignal[] = [ createdAt: '2025-02-12T10:00:00Z', updatedAt: '2025-05-05T09:00:00Z', aiSummary: 'Baubewilligung Nr. 2025-BW-0142 wurde am 10. Februar 2025 durch das Bauinspektorat Bern rechtskräftig erteilt. Das Projekt umfasst 4\'500 m² gemischte Büro- und Gewerbefläche (EG: Retail/Gewerbe, OG 1–3: Büro). Fertigstellung laut Baugesuch Q1 2027. Investorin ist die Wankdorf Immobilien AG; Vermietungsmandat noch nicht vergeben. Hohe Konfidenz durch amtliche Quelle.', + marketIndicators: [ + 'Baubewilligung für 4\'500 m² Büro-/Gewerbefläche öffentlich eingereicht', + 'Fertigstellung gemäss Baugesuch Q1 2027', + 'Bauherr: Wankdorf Center Entwicklungs AG', + ], + strategicInterpretation: 'Baubewilligung öffentlich — hohe Realisierungswahrscheinlichkeit. Vermietungsstart voraussichtlich 6–9 Monate vor Fertigstellung. Standort Wankdorf mit guter ÖV-Anbindung (Tram, Bahn).', + confirmedFacts: ['Baubewilligung erteilt', 'Fläche und Standort bestätigt', 'Fertigstellungsdatum Q1 2027 aus Gesuch'], + unconfirmedFacts: ['Endmietzins', 'Ob Fläche bereits vorvermietet', 'Ausbaustandard'], }, - // --- signal-004: Pharma-Biotech Basel Expansion --- + // --- signal-004: Novabio Pharma AG — Laborexpansion Basel Allschwil --- { id: 'signal-004', signalType: SignalType.EXPANSION, companyName: 'Novabio Pharma AG', locationHint: 'Basel, Allschwil', + title: 'Novabio Pharma AG sucht Lab-/Bürofläche im Life-Sciences-Korridor Allschwil', areaSqmEstimate: 700, probability: 0.63, confidenceScore: 0.60, @@ -118,15 +146,24 @@ export const mockFutureSignals: FutureSignal[] = [ createdAt: '2025-04-02T09:00:00Z', updatedAt: '2025-05-08T10:00:00Z', aiSummary: 'Im Geschäftsbericht 2024 kommuniziert Novabio Pharma AG eine "strategische Kapazitätserweiterung im Forschungsbereich Basel-Allschwil bis 2026". Die KI hat ausserdem 6 Inserate für Senior Scientists und Lab-Manager mit Standortangabe Allschwil identifiziert. Der Life-Sciences-Korridor Basel-Allschwil weist 2024 die schweizweit höchste Laborflächen-Nachfrage auf.', + marketIndicators: [ + 'Geschäftsbericht 2024: «strategische Kapazitätserweiterung Basel-Allschwil bis 2026» explizit erwähnt', + '6 Inserate für Senior Scientists und Lab-Manager mit Standort Allschwil in 60 Tagen', + 'Life-Sciences-Korridor Basel-Allschwil: höchste CH-weite Laborflächen-Nachfrage 2024', + ], + strategicInterpretation: 'Novabio Pharma AG kommuniziert Expansion öffentlich — Flächensuche sehr wahrscheinlich. Der Fokus auf Laborpersonal deutet auf kombinierten Lab-/Bürobedarf von 600–800 m² hin.', + confirmedFacts: ['Expansionspläne im Geschäftsbericht bestätigt', 'Region Basel-Allschwil als Zielstandort genannt', 'Einstellungstrend seit Q1 2025 sichtbar'], + unconfirmedFacts: ['Ob Neubau oder Bestandsfläche gesucht', 'Exakter Flächenbedarf Labor vs. Büro', 'Zeitpunkt des Einzugs'], }, - // --- signal-005: Finanz AG Zürich-Nord possible move-out → prop-023 --- + // --- signal-005: Finanz & Treuhand AG — möglicher Auszug Zürich-Nord --- { id: 'signal-005', signalType: SignalType.POSSIBLE_MOVE_OUT, companyName: 'Finanz & Treuhand AG', propertyId: 'prop-023', locationHint: 'Zürich-Nord, Seebach', + title: 'Finanz & Treuhand AG — Kostensenkungssignale deuten auf Standortaufgabe Seebach', areaSqmEstimate: 850, probability: 0.58, confidenceScore: 0.54, @@ -147,13 +184,22 @@ export const mockFutureSignals: FutureSignal[] = [ createdAt: '2025-04-12T08:00:00Z', updatedAt: '2025-05-10T09:00:00Z', aiSummary: 'Marktdatenanalyse zeigt: Die Finanz & Treuhand AG hat ihren LinkedIn-Unternehmenssitz von "Zürich-Nord, Seebach" auf "Zürich, Oerlikon" aktualisiert (Stand April 2025). Gleichzeitig sank die Mitarbeiterzahl laut LinkedIn von 38 auf 31 (-18%) innert 6 Monaten. Beide Indikatoren zusammen ergeben ein Verlagerungssignal. Kein offizieller Auszug bestätigt.', + marketIndicators: [ + 'LinkedIn-Firmensitz von Seebach auf Oerlikon aktualisiert (April 2025)', + 'Mitarbeiterzahl rückläufig: von 38 auf 31 (-18%) in 6 Monaten', + 'Leerstand Zürich-Nord Q1 2025: +12% gegenüber Vorquartal', + ], + strategicInterpretation: 'Adressänderung auf LinkedIn kombiniert mit Stellenabbau ist ein starkes Frühsignal für Standortaufgabe. Frühzeitiger Kontakt mit der Verwaltung des Objekts empfohlen.', + confirmedFacts: ['Mitarbeiterzahl-Rückgang auf LinkedIn dokumentiert', 'Adressänderung auf LinkedIn publiziert', 'Aktueller Standort Seebach bekannt'], + unconfirmedFacts: ['Ob Mietvertrag gekündigt wurde', 'Zeitpunkt des Auszugs', 'Ob Zusammenschluss mit anderem Standort geplant'], }, - // --- signal-006: Luzern Inseli Neubau Gewerbe --- + // --- signal-006: Luzern Inseli — Neubau Büro/Gewerbe am Wasser --- { id: 'signal-006', signalType: SignalType.CONSTRUCTION_PROJECT, locationHint: 'Luzern, Inseli-Quartier', + title: 'Neubau Inseli Luzern — 2\'000 m² Gewerbefläche am Wasser, Q1 2027', areaSqmEstimate: 2000, probability: 0.78, confidenceScore: 0.74, @@ -173,15 +219,24 @@ export const mockFutureSignals: FutureSignal[] = [ organizationId: 'org-wincasa', createdAt: '2025-01-25T11:00:00Z', updatedAt: '2025-05-06T14:00:00Z', + marketIndicators: [ + 'Baubewilligung für 2\'000 m² Gewerbe-/Bürofläche am Inseli eingereicht', + 'Fertigstellung gemäss Baugesuch ca. Q1 2027', + 'Luzern Innenstadt: Büroflächennachfrage 2024 +18% gegenüber Vorjahr', + ], + strategicInterpretation: 'Attraktive Wasserlage am Luzerner Inseli mit ÖV-Direktanbindung (Bahnhof 8 Minuten zu Fuss). Nutzungskonzept noch offen — Büro und Gastronomie/Retail im EG wahrscheinlich. Frühzeitiges Interesse anmelden sinnvoll.', + confirmedFacts: ['Baubewilligung öffentlich eingereicht', 'Standort Inseli-Quartier bestätigt', 'Fertigstellung Q1 2027 aus Gesuch'], + unconfirmedFacts: ['Endgültige Nutzungsaufteilung (Büro vs. Retail)', 'Mietzinsniveau', 'Ob Vermietungsmandat vergeben'], }, - // --- signal-007: E-Commerce Zug Expansion → prop-026 --- + // --- signal-007: SwissCart E-Commerce GmbH — Logistikexpansion Zug --- { id: 'signal-007', signalType: SignalType.EXPANSION, companyName: 'SwissCart E-Commerce GmbH', propertyId: 'prop-026', locationHint: 'Zug, Industriestrasse', + title: 'SwissCart E-Commerce GmbH sucht Logistikfläche in Zug', areaSqmEstimate: 580, probability: 0.66, confidenceScore: 0.62, @@ -202,15 +257,24 @@ export const mockFutureSignals: FutureSignal[] = [ organizationId: 'org-wincasa', createdAt: '2025-04-20T07:00:00Z', updatedAt: '2025-05-09T08:00:00Z', + marketIndicators: [ + '9 Stelleninserate für Lager- und Logistikpersonal in Zug in 45 Tagen', + 'E-Commerce-Umsatz CH 2024: +22% — Nachfrage nach Lagerflächen steigt', + 'Zug Industriestrasse: aktuelle Leerstandsquote Lager unter 3%', + ], + strategicInterpretation: 'Massiver Personalaufbau im Logistikbereich deutet auf konkreten Flächenbedarf hin. Zug als Steuerkanton bleibt attraktiver Firmensitz — kein Standortwechsel erwartet, nur Erweiterung.', + confirmedFacts: ['Region Zug aus Inseraten bestätigt', 'Flächentyp Logistik/Lager aus Stellenprofilen ableitbar', 'Wachstumstrend seit Q3 2024 sichtbar'], + unconfirmedFacts: ['Ob zusätzliche Bürofläche benötigt', 'Exakter Flächenbedarf', 'Bevorzugte Strassenlage (Anlieferung)'], }, - // --- signal-008: Retail Zürich Niederdorf possible move-out → prop-025 --- + // --- signal-008: Textilhaus Zürich AG — Retail-Verkleinerung Niederdorf --- { id: 'signal-008', signalType: SignalType.POSSIBLE_MOVE_OUT, companyName: 'Textilhaus Zürich AG', propertyId: 'prop-025', locationHint: 'Zürich Niederdorf, Münstergasse', + title: 'Textilhaus Zürich AG — Retailfläche Münstergasse könnte frei werden', areaSqmEstimate: 280, probability: 0.55, confidenceScore: 0.50, @@ -230,13 +294,22 @@ export const mockFutureSignals: FutureSignal[] = [ organizationId: 'org-wincasa', createdAt: '2025-04-01T10:00:00Z', updatedAt: '2025-05-07T11:00:00Z', + marketIndicators: [ + 'Onlineanteil CH-Textilhandel 2024: 38% — stationäre Verkaufsflächen unter Druck', + 'Mitarbeiterzahl Textilhaus Zürich AG laut LinkedIn: -25% in 12 Monaten', + 'Stationärer Handel Zürich Altstadt: Leerstandsquote +8% 2024', + ], + strategicInterpretation: 'Struktureller Rückgang im stationären Textilhandel trifft kleine Altstadt-Läden besonders. Kombination aus Personalabbau und Branchentrend ergibt ein Verlagerungssignal, das im Auge behalten werden sollte.', + confirmedFacts: ['Branchentrend stationärer Handel dokumentiert', 'Aktueller Standort Münstergasse bekannt', 'Personalabbau auf LinkedIn sichtbar'], + unconfirmedFacts: ['Ob Mietvertrag noch läuft oder ausläuft', 'Ob Filialnetz insgesamt verkleinert wird', 'Zeitpunkt eines möglichen Auszugs'], }, - // --- signal-009: Winterthur Zentrum Neubau Büro --- + // --- signal-009: Winterthur Zentrum — Neubau Bürofläche --- { id: 'signal-009', signalType: SignalType.CONSTRUCTION_PROJECT, locationHint: 'Winterthur, Zentrum Technikum', + title: 'Neubau Technikum Winterthur — 1\'200 m² Bürofläche, Baubewilligung erteilt', areaSqmEstimate: 1200, probability: 0.80, confidenceScore: 0.76, @@ -258,15 +331,24 @@ export const mockFutureSignals: FutureSignal[] = [ organizationId: 'org-wincasa', createdAt: '2025-03-05T09:00:00Z', updatedAt: '2025-04-10T10:00:00Z', + marketIndicators: [ + 'Baubewilligung für 1\'200 m² Bürofläche am Technikum rechtskräftig erteilt', + 'Fertigstellung gemäss Baugesuch Q2 2027', + 'Winterthur Büromarkt: Leerstand Q1 2025 auf historisch tiefem Niveau (4.2%)', + ], + strategicInterpretation: 'Amtlich bestätigte Baubewilligung — sehr hohe Realisierungswahrscheinlichkeit. Zentrale Lage am Technikum mit Bahnhof-Nähe. Vermietungsstart erfahrungsgemäss 9–12 Monate vor Fertigstellung.', + confirmedFacts: ['Baubewilligung rechtskräftig erteilt', 'Standort Zentrum Technikum bestätigt', 'Fertigstellung Q2 2027 aus Gesuch'], + unconfirmedFacts: ['Ob Vorvermietung bereits läuft', 'Endmietzins pro m²', 'Aufteilung EG/OG'], }, - // --- signal-010: TechHub St.Gallen Expansion → prop-030 --- + // --- signal-010: Ostschweiz Digital AG — Büroexpansion St. Gallen --- { id: 'signal-010', signalType: SignalType.EXPANSION, companyName: 'Ostschweiz Digital AG', propertyId: 'prop-030', locationHint: 'St. Gallen, Riethüsli', + title: 'Ostschweiz Digital AG wächst — 480 m² Bürofläche in St. Gallen gesucht', areaSqmEstimate: 480, probability: 0.60, confidenceScore: 0.55, @@ -287,43 +369,61 @@ export const mockFutureSignals: FutureSignal[] = [ organizationId: 'org-wincasa', createdAt: '2025-04-25T08:00:00Z', updatedAt: '2025-05-10T07:00:00Z', + marketIndicators: [ + '8 Stelleninserate für Software-Entwickler und UX-Designer in St. Gallen in 60 Tagen', + 'Ostschweiz Digital AG: LinkedIn-Mitarbeiterzahl von 22 auf 31 (+41%) in 9 Monaten', + 'Digitalwirtschaft Ostschweiz: +28% Beschäftigte 2024 (IHK-Bericht)', + ], + strategicInterpretation: 'Kontinuierliches Teamwachstum über 9 Monate mit Fokus auf St. Gallen deutet auf organische Expansion hin. Kein Hinweis auf Standortwechsel — Erweiterungsfläche am bisherigen Standort oder Umzug in grössere Einheit möglich.', + confirmedFacts: ['Region St. Gallen aus Inseraten bestätigt', 'Wachstumstrend seit Q3 2024 nachweisbar', 'Flächentyp Büro aus Stellenprofilen ableitbar'], + unconfirmedFacts: ['Ob aktueller Standort Riethüsli oder Neulage', 'Exakter Flächenbedarf', 'Zeitpunkt der Entscheidung'], }, - // --- signal-011: Produktion Münchenbuchsee possible move-out → prop-027 --- + // --- signal-011 (Mode Boutique): Mode Boutique Bern AG — Rückgang Altstadt --- { id: 'signal-011', signalType: SignalType.POSSIBLE_MOVE_OUT, - companyName: 'Präzisionsmechanik Bern AG', - propertyId: 'prop-027', - locationHint: 'Münchenbuchsee BE, Industriezone', - areaSqmEstimate: 2200, - probability: 0.52, - confidenceScore: 0.48, - timeHorizonMonths: 14, + propertyId: 'prop-035', + companyName: 'Mode Boutique Bern AG', + locationHint: 'Bern Altstadt, Gerechtigkeitsgasse', + title: 'Mode Boutique Bern AG — Retailfläche Gerechtigkeitsgasse unter Druck', + areaSqmEstimate: 290, + probability: 0.62, + confidenceScore: 0.58, + timeHorizonMonths: 10, source: { - type: 'PRESS', - url: 'https://example.com/news/pmbern-verlagerung', - publishedAt: '2025-03-10', - credibility: 'HIGH', + type: 'MARKET_DATA', + publishedAt: '2025-04-28', + credibility: 'MEDIUM', }, - sensitivityLevel: 'CONFIDENTIAL', - disclaimer: 'Pressemeldungen über Verlagerung der Produktion ins Ausland. Kein bestätigter Auszug. Vertraulich behandeln.', - riskLevel: RiskLevel.HIGH, - marketIndicator: 'Verlagerungsdruck Schweizer Maschinenbau 2025', - relevanceScore: 0.58, + sensitivityLevel: 'INTERNAL', + disclaimer: 'Marktdaten deuten auf mögliche Verkleinerung hin. Kein bestätigter Auszug.', + riskLevel: RiskLevel.MEDIUM, + marketIndicator: 'Stationärer Handel Bern Altstadt: Leerstand +5% 2024', + relevanceScore: 0.66, isVerified: false, - expiresAt: '2026-08-01', + expiresAt: '2026-04-01', organizationId: 'org-wincasa', - createdAt: '2025-03-12T10:00:00Z', - updatedAt: '2025-05-09T09:00:00Z', + createdAt: '2025-04-28T09:00:00Z', + updatedAt: '2025-05-15T10:00:00Z', + aiSummary: 'Mode Boutique Bern AG zeigt gemäss LinkedIn-Analyse eine Mitarbeiterreduktion von 12 auf 8 Personen (-33%) innerhalb von 6 Monaten. Das Unternehmen hat ausserdem kürzlich den Sitz auf eine kleinere Adresse in der Berner Innenstadt aktualisiert. Die Kombination aus Stellenabbau und Adressänderung deutet auf eine Verkleinerung des Verkaufsbereichs hin.', + marketIndicators: [ + 'Mitarbeiterreduktion von 12 auf 8 Personen (-33%) in 6 Monaten laut LinkedIn', + 'Berner Altstadt: Frequenzrückgang in der Gerechtigkeitsgasse -11% 2024 (Zählstelle)', + 'Stationärer Modehandel Bern: Leerstand +5% 2024', + ], + strategicInterpretation: 'Personalabbau zusammen mit sinkendem Passantenaufkommen in der Gerechtigkeitsgasse ergibt ein klares Verkleinerungssignal. Frühzeitige Kontaktaufnahme mit dem Eigentümer sinnvoll.', + confirmedFacts: ['Aktueller Standort Gerechtigkeitsgasse bekannt', 'Personalrückgang auf LinkedIn dokumentiert', 'Bern Altstadt Frequenzdaten verfügbar'], + unconfirmedFacts: ['Ob Mietvertrag ausläuft oder gekündigt wird', 'Ob Standort aufgegeben oder nur verkleinert', 'Zeitpunkt eines möglichen Auszugs'], }, - // --- signal-012: Basel Hafen Neubau Logistik → prop-024 --- + // --- signal-012: Basel Hafen Klybeck — Logistikneubau --- { id: 'signal-012', signalType: SignalType.CONSTRUCTION_PROJECT, propertyId: 'prop-024', locationHint: 'Basel, Hafen Klybeck', + title: 'Logistikneubau Rheinhafen Basel — 2\'600 m², Fertigstellung Q2 2026', areaSqmEstimate: 2600, probability: 0.82, confidenceScore: 0.78, @@ -345,15 +445,24 @@ export const mockFutureSignals: FutureSignal[] = [ organizationId: 'org-wincasa', createdAt: '2025-01-18T10:00:00Z', updatedAt: '2025-04-20T14:00:00Z', + marketIndicators: [ + 'Baubewilligung für 2\'600 m² Logistikfläche am Klybeck-Hafen rechtskräftig erteilt', + 'Fertigstellung gemäss Baugesuch Q2 2026', + 'Hafenareal Klybeck: Trimodalität Schiene/Wasser/Strasse als USP', + ], + strategicInterpretation: 'Strategisch guter Logistikstandort mit direktem Rheinhafenzugang und Bahnanschluss. Hohe Realisierungssicherheit durch bereits erteilte Bewilligung. Nachfrage nach Hafen-nahen Logistikflächen in Basel konstant hoch.', + confirmedFacts: ['Baubewilligung erteilt und rechtskräftig', 'Standort Klybeck bestätigt', 'Fertigstellung Q2 2026 aus Gesuch', 'Fläche 2\'600 m² bestätigt'], + unconfirmedFacts: ['Endmietzins pro m²', 'Ob Kühllager oder Trockenlager', 'Ob Fläche bereits reserviert'], }, - // --- signal-013: Genf La Praille Office Expansion → prop-028 --- + // --- signal-013: Geneva Finance Partners SA — Büroexpansion Genf La Praille --- { id: 'signal-013', signalType: SignalType.EXPANSION, companyName: 'Geneva Finance Partners SA', propertyId: 'prop-028', locationHint: 'Genf, La Praille', + title: 'Geneva Finance Partners SA expandiert — Bürofläche La Praille gesucht', areaSqmEstimate: 520, probability: 0.58, confidenceScore: 0.53, @@ -374,15 +483,24 @@ export const mockFutureSignals: FutureSignal[] = [ organizationId: 'org-wincasa', createdAt: '2025-03-08T11:00:00Z', updatedAt: '2025-05-07T10:00:00Z', + marketIndicators: [ + 'Jahresbericht 2024: «Expansion des Genfer Teams auf 40 FTE bis 2026» angekündigt', + 'Genf Finanzsektor: +15% Büroflächennachfrage 2025 (CBRE-Bericht)', + '5 neue Inserate für Senior-Portfoliomanager mit Standort Genf in 30 Tagen', + ], + strategicInterpretation: 'Öffentlich kommunizierte Expansion mit klarem Zielhorizont 2026. La Praille als Standort bietet kostengünstigere Büroflächen als Genf Innenstadt bei guter ÖV-Anbindung. Flächensuche dürfte aktiv beginnen.', + confirmedFacts: ['Expansion im Jahresbericht kommuniziert', 'Teamwachstum auf 40 FTE bis 2026 bestätigt', 'Inserate für Genf aktiv'], + unconfirmedFacts: ['Ob La Praille oder andere Lage bevorzugt', 'Exakter Flächenbedarf', 'Ob Büros mit Repräsentationscharakter benötigt'], }, - // --- signal-014: Frenkendorf Lager possible move-out → prop-029 --- + // --- signal-014: Schweizer Grosshandel AG — möglicher Auszug Frenkendorf --- { id: 'signal-014', signalType: SignalType.POSSIBLE_MOVE_OUT, companyName: 'Schweizer Grosshandel AG', propertyId: 'prop-029', locationHint: 'Frenkendorf BL, Lager Nord', + title: 'Schweizer Grosshandel AG — Lagerfläche Frenkendorf unter Konsolidierungsdruck', areaSqmEstimate: 3500, probability: 0.50, confidenceScore: 0.46, @@ -402,14 +520,23 @@ export const mockFutureSignals: FutureSignal[] = [ organizationId: 'org-wincasa', createdAt: '2025-04-07T08:00:00Z', updatedAt: '2025-05-08T09:00:00Z', + marketIndicators: [ + 'Mutterkonzern hat Konsolidierung der Lagerstandorte Nordwestschweiz angekündigt', + 'Mitarbeiterzahl Frenkendorf: von 54 auf 44 (-19%) in 8 Monaten', + 'Grosshandel CH: Lagerflächennachfrage 2024 stagniert — Kostendruck durch E-Commerce', + ], + strategicInterpretation: 'Strukturelle Konsolidierung im Grosshandel trifft Nebenstandorte wie Frenkendorf zuerst. Kein Auszugstermin bekannt — Signal noch in früher Phase. Eigentümer sollte Kontakt suchen.', + confirmedFacts: ['Konsolidierungsankündigung des Mutterkonzerns publiziert', 'Personalrückgang in Frenkendorf dokumentiert', 'Aktueller Standort Frenkendorf bekannt'], + unconfirmedFacts: ['Welche Lagerstandorte konkret betroffen', 'Zeitpunkt eines möglichen Auszugs', 'Ob Fläche ganz oder teilweise aufgegeben'], }, - // --- signal-015: Bern Tech Campus Expansion --- + // --- signal-015: BernTech Innovation AG — Büroexpansion Bern Breitenrain --- { id: 'signal-015', signalType: SignalType.EXPANSION, companyName: 'BernTech Innovation AG', locationHint: 'Bern, Breitenrain', + title: 'BernTech Innovation AG sucht ~1\'800 m² Bürofläche in Bern Breitenrain', areaSqmEstimate: 1800, probability: 0.68, confidenceScore: 0.64, @@ -430,34 +557,13 @@ export const mockFutureSignals: FutureSignal[] = [ organizationId: 'org-wincasa', createdAt: '2025-04-28T09:00:00Z', updatedAt: '2025-05-10T08:00:00Z', - }, - - // --- signal-011: Mode Boutique Bern possible move-out Altstadt --- - { - id: 'signal-011', - signalType: SignalType.POSSIBLE_MOVE_OUT, - propertyId: 'prop-035', - companyName: 'Mode Boutique Bern AG', - locationHint: 'Bern Altstadt, Gerechtigkeitsgasse', - areaSqmEstimate: 290, - probability: 0.62, - confidenceScore: 0.58, - timeHorizonMonths: 10, - source: { - type: 'MARKET_DATA', - publishedAt: '2025-04-28', - credibility: 'MEDIUM', - }, - sensitivityLevel: 'INTERNAL', - disclaimer: 'Marktdaten deuten auf mögliche Verkleinerung hin. Kein bestätigter Auszug.', - riskLevel: RiskLevel.MEDIUM, - marketIndicator: 'Stationärer Handel Bern Altstadt: Leerstand +5% 2024', - relevanceScore: 0.66, - isVerified: false, - expiresAt: '2026-04-01', - organizationId: 'org-wincasa', - createdAt: '2025-04-28T09:00:00Z', - updatedAt: '2025-05-15T10:00:00Z', - aiSummary: 'Mode Boutique Bern AG zeigt gemäss LinkedIn-Analyse eine Mitarbeiterreduktion von 12 auf 8 Personen (-33%) innerhalb von 6 Monaten. Das Unternehmen hat ausserdem kürzlich den Sitz auf eine kleinere Adresse in der Berner Innenstadt aktualisiert. Die Kombination aus Stellenabbau und Adressänderung deutet auf eine Verkleinerung des Verkaufsbereichs hin.', + marketIndicators: [ + '18 Stelleninserate in 60 Tagen — Fokus Software-Engineering und Product Management', + 'BernTech Innovation AG: VC-Finanzierungsrunde CHF 12 Mio. im März 2025 bestätigt', + 'Bern Tech-Ökosystem: Risikokapital +40% 2024 — stärkstes Wachstum seit 2019', + ], + strategicInterpretation: 'Frisch finanziertes Wachstumsunternehmen mit aktivem Stellenaufbau. VC-Finanzierung als starkes Signal für konkreten Flächenbedarf — Budgets vorhanden. Breitenrain als Tech-Standort in Bern etabliert.', + confirmedFacts: ['VC-Finanzierungsrunde CHF 12 Mio. öffentlich bestätigt', 'Region Bern aus Inseraten bestätigt', 'Wachstumstrend seit Q1 2025 sichtbar'], + unconfirmedFacts: ['Exakter Flächenbedarf (Schätzung: 1\'500–2\'000 m²)', 'Ob Breitenrain oder andere Berner Lage bevorzugt', 'Zeitpunkt Einzug'], }, ] diff --git a/src/pages/demand/MatchDetail.tsx b/src/pages/demand/MatchDetail.tsx index c0c0b50..c701acf 100644 --- a/src/pages/demand/MatchDetail.tsx +++ b/src/pages/demand/MatchDetail.tsx @@ -172,7 +172,7 @@ export default function MatchDetail() { sx={{ bgcolor: '#f1f5f9', color: '#475569', fontWeight: 700, letterSpacing: 0.5 }} /> {isFuture && ( - + )} diff --git a/src/pages/demand/Results.tsx b/src/pages/demand/Results.tsx index 3fdf86d..c3a5c55 100644 --- a/src/pages/demand/Results.tsx +++ b/src/pages/demand/Results.tsx @@ -38,7 +38,7 @@ export default function Results() { const { currentUser } = useSessionStore() const [filterSource, setFilterSource] = useState('ALL') const [sortBy, setSortBy] = useState('score') - const [showSchattenmarkt, setShowSchattenmarkt] = useState(true) + const [showFutureAvailability, setShowFutureAvailability] = useState(true) const [showOwnProperties, setShowOwnProperties] = useState(false) const [view, setView] = useState<'list' | 'grid'>(() => (localStorage.getItem('view-results') as 'list' | 'grid') ?? 'list' @@ -73,8 +73,8 @@ export default function Results() { const filtered = results.filter(r => { if (r.resultType === 'VERIFIED_PORTFOLIO') return showOwnProperties && currentUser?.role === 'PROPERTY_MANAGER' - // Schattenmarkt toggle is independent of the source filter - if (r.resultType === 'FUTURE_AVAILABILITY') return showSchattenmarkt + // Future Availability toggle is independent of the source filter + if (r.resultType === 'FUTURE_AVAILABILITY') return showFutureAvailability return filterSource === 'ALL' || r.resultType === filterSource }) @@ -176,8 +176,8 @@ export default function Results() { onFilterChange={setFilterSource} sortBy={sortBy} onSortChange={setSortBy} - showSchattenmarkt={showSchattenmarkt} - onShowSchattenmarktChange={setShowSchattenmarkt} + showFutureAvailability={showFutureAvailability} + onShowFutureAvailabilityChange={setShowFutureAvailability} showOwnProperties={showOwnProperties} onShowOwnPropertiesChange={setShowOwnProperties} isPropertyManager={currentUser?.role === 'PROPERTY_MANAGER'} @@ -186,7 +186,7 @@ export default function Results() { {isLoading ? ( ) : sorted.length === 0 ? ( - + ) : ( )}