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>
This commit is contained in:
Benjamin Sutter
2026-05-20 13:06:16 +02:00
parent d5a4862a28
commit 5fe15bac62
12 changed files with 531 additions and 358 deletions
@@ -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<string, { label: string; icon: React.ReactNode }> = {
LEASE_CONTRACT: { label: 'Vertrag verifiziert', icon: <ShieldCheck size={11} /> },
}
const SIGNAL_TYPE_LABELS: Record<string, string> = {
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<string, string> = {
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<string, string> = {
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<string, { label: string; color: string }> = {
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 (
<Box sx={{ position: 'relative' }}>
{/* Gradient hero background */}
<Box
sx={{
height: 140,
background: 'linear-gradient(135deg, #1a0a3a 0%, #3b0a6e 50%, #6d28d9 100%)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 0.5,
position: 'relative',
overflow: 'hidden',
px: 7,
}}
>
{/* Subtle pattern overlay */}
<Box sx={{
position: 'absolute', inset: 0, opacity: 0.08,
backgroundImage: 'radial-gradient(circle at 20% 50%, white 1px, transparent 1px), radial-gradient(circle at 80% 20%, white 1px, transparent 1px)',
backgroundSize: '40px 40px',
}} />
{/* Company initials */}
<Box sx={{
width: 40, height: 40, borderRadius: '50%',
bgcolor: 'rgba(255,255,255,0.15)',
border: '1.5px solid rgba(255,255,255,0.3)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
zIndex: 1, flexShrink: 0,
}}>
<Typography sx={{ color: 'white', fontWeight: 800, fontSize: '0.9rem', letterSpacing: 1 }}>
{initials}
</Typography>
</Box>
{/* Signal type label */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, zIndex: 1 }}>
<Zap size={10} color="#c4b5fd" />
<Typography sx={{ color: '#c4b5fd', fontSize: '0.63rem', fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.8 }}>
{signalLabel}
</Typography>
</Box>
{/* Relevance bridge — why this appears in results */}
{relevanceBridge && (
<Typography sx={{ color: 'rgba(255,255,255,0.85)', fontSize: '0.67rem', textAlign: 'center', lineHeight: 1.3, zIndex: 1 }}>
{relevanceBridge}
</Typography>
)}
{/* Verified badge */}
{vm.signalIsVerified && (
<Box sx={{ position: 'absolute', bottom: 8, right: 8, display: 'flex', alignItems: 'center', gap: 0.4, bgcolor: 'rgba(26,122,74,0.9)', borderRadius: 1, px: 0.75, py: 0.25 }}>
<ShieldCheck size={10} color="white" />
<Typography sx={{ color: 'white', fontSize: '0.6rem', fontWeight: 700 }}>Verifiziert</Typography>
</Box>
)}
</Box>
{/* Score badge — overlaid top-left */}
<Box sx={{
position: 'absolute', top: 10, left: 10,
background: theme.gradient, borderRadius: '10px',
px: 1.5, py: 0.5,
boxShadow: `0 4px 16px ${theme.glow}`,
border: `1px solid ${theme.border}`,
minWidth: 52, textAlign: 'center',
}}>
<Typography sx={{ fontWeight: 900, fontSize: '1.5rem', color: theme.text, lineHeight: 1 }}>
{vm.matchScore}%
</Typography>
{tier !== 'bronze' && (
<Typography sx={{ fontSize: '0.575rem', color: theme.text, opacity: 0.8, textTransform: 'uppercase', letterSpacing: 0.8, lineHeight: 1.2 }}>
{theme.label}
</Typography>
)}
</Box>
{/* Schattenmarkt chip — top-right */}
<Box sx={{ position: 'absolute', top: 10, right: 10 }}>
<Chip label="Schattenmarkt" size="small" sx={{ bgcolor: '#7c3aed', color: 'white', fontWeight: 700, fontSize: 10, height: 20 }} />
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
{c.dots.map((filled, i) => (
<Box key={i} sx={{ width: 7, height: 7, borderRadius: '50%', bgcolor: filled ? c.color : '#e2e8f0' }} />
))}
<Typography sx={{ fontSize: '0.68rem', color: c.color, fontWeight: 600, ml: 0.25 }}>
{c.label}
</Typography>
</Box>
)
}
// ── 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 (
<Box sx={{ px: 1.75, pt: 1.5, pb: 0, display: 'flex', flexDirection: 'column', gap: 1 }}>
{/* Source + credibility row */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, flexWrap: 'wrap' }}>
{sourceMeta && (
vm.signalSourceUrl ? (
<Box
component="a"
href={vm.signalSourceUrl}
target="_blank"
rel="noopener noreferrer"
sx={{ display: 'flex', alignItems: 'center', gap: 0.4, bgcolor: '#ede9fe', color: '#6d28d9', borderRadius: 1, px: 0.75, py: 0.25, textDecoration: 'none', '&:hover': { bgcolor: '#ddd6fe' } }}
>
{sourceMeta.icon}
<Typography sx={{ fontSize: '0.68rem', fontWeight: 600 }}>{sourceMeta.label}</Typography>
<ExternalLink size={9} style={{ opacity: 0.7 }} />
</Box>
) : (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.4, bgcolor: '#ede9fe', color: '#6d28d9', borderRadius: 1, px: 0.75, py: 0.25 }}>
{sourceMeta.icon}
<Typography sx={{ fontSize: '0.68rem', fontWeight: 600 }}>{sourceMeta.label}</Typography>
</Box>
)
)}
{credMeta && (
<Tooltip title={credMeta.label} arrow>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.3, bgcolor: '#f8fafc', border: `1px solid ${credMeta.color}30`, borderRadius: 1, px: 0.6, py: 0.2, cursor: 'default' }}>
<Box sx={{ width: 6, height: 6, borderRadius: '50%', bgcolor: credMeta.color }} />
<Typography sx={{ fontSize: '0.63rem', color: credMeta.color, fontWeight: 600 }}>
{vm.signalSourceCredibility === 'HIGH' ? 'Hoch' : vm.signalSourceCredibility === 'MEDIUM' ? 'Mittel' : 'Niedrig'}
</Typography>
</Box>
</Tooltip>
<Box sx={{
borderRadius: 2,
overflow: 'hidden',
border: `1px solid ${isControlled ? '#bbf7d0' : '#bfdbfe'}`,
borderLeft: `3px solid ${accentColor}`,
background: 'white',
display: 'flex', flexDirection: 'column',
boxShadow: '0 1px 8px rgba(0,0,0,0.06)',
transition: 'box-shadow 0.15s, transform 0.1s',
'&:hover': { boxShadow: '0 4px 20px rgba(0,0,0,0.10)', transform: 'translateY(-1px)' },
}}>
{/* Data Header */}
<Box sx={{ bgcolor: headerBg, px: 2, pt: 1.75, pb: 1.5, borderBottom: `1px solid ${isControlled ? '#bbf7d0' : '#bfdbfe'}` }}>
{/* Asset type + location */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 0.5 }}>
<Typography sx={{ fontSize: '0.63rem', fontWeight: 700, color: '#64748b', textTransform: 'uppercase', letterSpacing: 0.8 }}>
{assetLabel.toUpperCase()} · {vm.locationLabel?.split(',')[0]?.toUpperCase() ?? ''}
</Typography>
<Box sx={{ bgcolor: badgeBg, color: badgeColor, px: 0.875, py: 0.2, borderRadius: 1, fontSize: '0.63rem', fontWeight: 700, letterSpacing: 0.3 }}>
{badge}
</Box>
</Box>
{/* Opportunity headline */}
<Typography variant="subtitle1" sx={{ fontWeight: 700, lineHeight: 1.3, color: '#1e293b', mb: 0.75 }}>
{headline}
</Typography>
{/* Key facts row */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.25, flexWrap: 'wrap' }}>
{vm.signalAreaSqmEstimate ? (
<Typography sx={{ fontSize: '0.72rem', color: '#475569' }}>
~{vm.signalAreaSqmEstimate.toLocaleString('de-CH')} m²
</Typography>
) : null}
{vm.availabilityLabel && (
<Typography sx={{ fontSize: '0.72rem', color: '#475569' }}>
{vm.availabilityLabel}
</Typography>
)}
</Box>
</Box>
{/* Score + signal quality strip */}
<Box sx={{ px: 2, py: 1.25, display: 'flex', alignItems: 'center', gap: 1.5, borderBottom: '1px solid #f1f5f9', bgcolor: 'white' }}>
<Box sx={{
background: theme.gradient, borderRadius: '8px',
px: 1.25, py: 0.4, border: `1px solid ${theme.border}`,
boxShadow: `0 2px 8px ${theme.glow}`,
}}>
<Typography sx={{ fontWeight: 900, fontSize: '1.25rem', color: theme.text, lineHeight: 1 }}>
{vm.matchScore}%
</Typography>
</Box>
<SignalQualityDots quality={vm.signalQuality} />
{isControlled && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.35, ml: 'auto', bgcolor: '#f0fdf4', border: '1px solid #86efac', borderRadius: 1, px: 0.625, py: 0.2 }}>
<ShieldCheck size={10} color="#1a7a4a" />
<Typography sx={{ fontSize: '0.6rem', color: '#1a7a4a', fontWeight: 700 }}>Verifiziert</Typography>
</Box>
)}
</Box>
{/* Probability bar */}
{probPct !== null && (
<Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.3 }}>
<Typography sx={{ fontSize: '0.63rem', color: '#64748b', fontWeight: 600 }}>Eintretenswahrscheinlichkeit</Typography>
<Typography sx={{ fontSize: '0.63rem', fontWeight: 700, color: probPct >= 70 ? '#1a7a4a' : probPct >= 50 ? '#d97706' : '#c0392b' }}>
{probPct}%
</Typography>
</Box>
<LinearProgress
variant="determinate"
value={probPct}
sx={{
height: 4, borderRadius: 2,
bgcolor: '#e2e8f0',
'& .MuiLinearProgress-bar': {
bgcolor: probPct >= 70 ? '#1a7a4a' : probPct >= 50 ? '#d97706' : '#c0392b',
borderRadius: 2,
},
}}
/>
</Box>
)}
{/* Card body */}
<Box sx={{ px: 2, pt: 1.25, pb: 1.5, flex: 1, display: 'flex', flexDirection: 'column', gap: 0 }}>
{/* Market indicator */}
{vm.signalMarketIndicator && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, bgcolor: '#f0fdf4', borderRadius: 1, px: 0.75, py: 0.4 }}>
<TrendingUp size={11} color="#1a7a4a" style={{ flexShrink: 0 }} />
<Typography sx={{ fontSize: '0.68rem', color: '#1a7a4a', fontWeight: 500 }}>
{vm.signalMarketIndicator}
</Typography>
{/* CONTROLLED: Confirmed facts */}
{isControlled && (vm.signalConfirmedFacts?.length ?? 0) > 0 && (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.4, mb: 1.25 }}>
{(vm.signalConfirmedFacts ?? []).slice(0, 2).map((fact, i) => (
<Box key={i} sx={{ display: 'flex', alignItems: 'center', gap: 0.6 }}>
<CheckCircle2 size={12} color="#1a7a4a" style={{ flexShrink: 0 }} />
<Typography sx={{ fontSize: '0.72rem', color: '#1a7a4a', fontWeight: 500 }}>{fact}</Typography>
</Box>
))}
</Box>
)}
{/* MARKET SIGNAL: Market indicators */}
{!isControlled && (vm.signalMarketIndicators?.length ?? 0) > 0 && (
<>
<Typography sx={{ fontSize: '0.63rem', fontWeight: 700, color: '#64748b', textTransform: 'uppercase', letterSpacing: 0.6, mb: 0.5 }}>
Erkannte Marktindikatoren
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.35, mb: 1.25 }}>
{(vm.signalMarketIndicators ?? []).slice(0, 3).map((ind, i) => (
<Box key={i} sx={{ display: 'flex', alignItems: 'flex-start', gap: 0.6 }}>
<Box sx={{ width: 4, height: 4, borderRadius: '50%', bgcolor: '#1d4ed8', mt: '5px', flexShrink: 0 }} />
<Typography sx={{ fontSize: '0.72rem', color: '#475569', lineHeight: 1.3 }}>{ind}</Typography>
</Box>
))}
</Box>
</>
)}
{/* Why relevant (match reasons) */}
{vm.reasons.length > 0 && (
<>
<Divider sx={{ mb: 1 }} />
<Typography sx={{ fontSize: '0.63rem', fontWeight: 700, color: '#64748b', textTransform: 'uppercase', letterSpacing: 0.6, mb: 0.6 }}>
Warum relevant für Ihre Suche?
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
{vm.reasons.slice(0, 3).map((r, i) => (
<Box key={i} sx={{ display: 'flex', alignItems: 'flex-start', gap: 0.6 }}>
<CheckCircle2 size={12} color="#1a7a4a" style={{ marginTop: 2, flexShrink: 0 }} />
<Box>
<Typography sx={{ fontSize: '0.72rem', fontWeight: 600, color: '#1e293b', lineHeight: 1.3 }}>{r.label}</Typography>
<Typography sx={{ fontSize: '0.69rem', color: '#64748b', lineHeight: 1.3 }}>{r.explanation}</Typography>
</Box>
</Box>
))}
</Box>
</>
)}
{/* Actions */}
<Box sx={{ display: 'flex', gap: 0.75, mt: 1.5, pt: 1.25, borderTop: '1px solid rgba(0,0,0,0.06)' }}>
{vm.actions.map(a => (
<Button
key={a.id}
size="small"
variant={a.variant === 'primary' ? 'contained' : 'outlined'}
onClick={a.onClick}
disabled={a.disabled}
sx={{
textTransform: 'none', fontSize: '0.7rem', py: 0.375, px: 1,
...(a.variant === 'primary' && {
bgcolor: accentColor,
'&:hover': { bgcolor: isControlled ? '#166534' : '#1e40af' },
}),
}}
>
{a.label}
</Button>
))}
</Box>
)}
{/* Disclaimer footnote */}
{vm.disclaimer && (
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 1, fontSize: '0.65rem', lineHeight: 1.4, borderTop: '1px solid #f1f5f9', pt: 0.75 }}>
{vm.disclaimer}
</Typography>
)}
</Box>
</Box>
)
}
@@ -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 <FutureAvailabilityCard vm={vm} />
}
return (
<Box sx={{
borderRadius: 2,
overflow: 'hidden',
boxShadow: isFuture
? '0 2px 16px rgba(109,40,217,0.15), 0 0 0 2px #7c3aed'
: `0 2px 16px rgba(0,0,0,0.07), 0 0 0 1px ${theme.cardBorder}`,
boxShadow: `0 2px 16px rgba(0,0,0,0.07), 0 0 0 1px ${theme.cardBorder}`,
background: theme.cardBg,
display: 'flex',
flexDirection: 'column',
transition: 'box-shadow 0.15s, transform 0.1s',
'&:hover': {
boxShadow: isFuture
? '0 6px 28px rgba(109,40,217,0.2), 0 0 0 2px #7c3aed'
: `0 6px 28px rgba(0,0,0,0.12), 0 0 0 1px ${theme.cardBorder}`,
boxShadow: `0 6px 28px rgba(0,0,0,0.12), 0 0 0 1px ${theme.cardBorder}`,
transform: 'translateY(-1px)',
},
}}>
{/* ── Hero zone ── */}
{isFuture ? (
<SignalHero vm={vm} />
) : (
<Box sx={{ position: 'relative' }}>
<LocationPreview imageUrl={imageUrl} lat={lat} lng={lng} cityLabel={cityLabel} height={175} />
<Box sx={{
position: 'absolute', top: 10, left: 10,
background: theme.gradient, borderRadius: '10px',
px: 1.5, py: 0.5,
boxShadow: `0 4px 16px ${theme.glow}`,
border: `1px solid ${theme.border}`,
minWidth: 52, textAlign: 'center',
}}>
<Typography sx={{ fontWeight: 900, fontSize: '1.5rem', color: theme.text, lineHeight: 1 }}>
{vm.matchScore}%
<Box sx={{ position: 'relative' }}>
<LocationPreview imageUrl={imageUrl} lat={lat} lng={lng} cityLabel={cityLabel} height={175} />
<Box sx={{
position: 'absolute', top: 10, left: 10,
background: theme.gradient, borderRadius: '10px',
px: 1.5, py: 0.5,
boxShadow: `0 4px 16px ${theme.glow}`,
border: `1px solid ${theme.border}`,
minWidth: 52, textAlign: 'center',
}}>
<Typography sx={{ fontWeight: 900, fontSize: '1.5rem', color: theme.text, lineHeight: 1 }}>
{vm.matchScore}%
</Typography>
{tier !== 'bronze' && (
<Typography sx={{ fontSize: '0.575rem', color: theme.text, opacity: 0.8, textTransform: 'uppercase', letterSpacing: 0.8, lineHeight: 1.2 }}>
{theme.label}
</Typography>
{tier !== 'bronze' && (
<Typography sx={{ fontSize: '0.575rem', color: theme.text, opacity: 0.8, textTransform: 'uppercase', letterSpacing: 0.8, lineHeight: 1.2 }}>
{theme.label}
</Typography>
)}
</Box>
<Box sx={{ position: 'absolute', top: 10, right: 44, display: 'flex', flexDirection: 'column', gap: 0.5, alignItems: 'flex-end' }}>
<Chip label={rt.label} size="small" sx={{ bgcolor: rt.color, color: 'white', fontWeight: 600, fontSize: 10, height: 20 }} />
{vm.resultType === 'VERIFIED_PORTFOLIO' && (
<Chip
icon={<Building2 size={9} color="#1e3a5f" />}
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 } }}
/>
)}
</Box>
)}
</Box>
)}
{/* ── Signal metadata (Schattenmarkt only) ── */}
{isFuture && <SignalMetaStrip vm={vm} />}
<Box sx={{ position: 'absolute', top: 10, right: 44, display: 'flex', flexDirection: 'column', gap: 0.5, alignItems: 'flex-end' }}>
<Chip label={rt.label} size="small" sx={{ bgcolor: rt.color, color: 'white', fontWeight: 600, fontSize: 10, height: 20 }} />
{vm.resultType === 'VERIFIED_PORTFOLIO' && (
<Chip
icon={<Building2 size={9} color="#1e3a5f" />}
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 } }}
/>
)}
</Box>
</Box>
{/* ── Card body ── */}
<Box sx={{ p: isFuture ? 1.75 : 2, pt: isFuture ? 1 : 2, flex: 1, display: 'flex', flexDirection: 'column', gap: 0 }}>
<Box sx={{ p: 2, flex: 1, display: 'flex', flexDirection: 'column', gap: 0 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 700, lineHeight: 1.3 }} noWrap>
{vm.title}
</Typography>
@@ -328,25 +321,8 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel }: Pro
{[vm.locationLabel, vm.availabilityLabel].filter(Boolean).join(' · ')}
</Typography>
{/* AI Signal Summary (Schattenmarkt) */}
{isFuture && vm.aiSignalSummary && (
<>
<Divider sx={{ my: 1 }} />
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 0.75, bgcolor: '#faf5ff', borderRadius: 1, p: 1 }}>
<Zap size={12} color="#7c3aed" style={{ flexShrink: 0, marginTop: 2 }} />
<Typography variant="body2" sx={{
fontSize: '0.72rem', color: '#4c1d95', lineHeight: 1.5,
overflow: 'hidden', display: '-webkit-box',
WebkitLineClamp: 4, WebkitBoxOrient: 'vertical',
}}>
{vm.aiSignalSummary}
</Typography>
</Box>
</>
)}
{/* Regular explainability summary (non-future) */}
{!isFuture && vm.explainabilitySummary && (
{/* Regular explainability summary */}
{vm.explainabilitySummary && (
<>
<Divider sx={{ my: 1.25 }} />
<Typography variant="body2" sx={{
@@ -392,8 +368,8 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel }: Pro
sx={{
textTransform: 'none', fontSize: '0.7rem', py: 0.375, px: 1,
...(a.variant === 'primary' && {
bgcolor: isFuture ? '#7c3aed' : '#1e3a5f',
'&:hover': { bgcolor: isFuture ? '#6d28d9' : '#162d4a' },
bgcolor: '#1e3a5f',
'&:hover': { bgcolor: '#162d4a' },
}),
}}
>
@@ -402,13 +378,6 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel }: Pro
))}
</Box>
</Box>
{/* Disclaimer */}
{vm.disclaimer && (
<Alert severity="warning" sx={{ borderRadius: 0, py: 0.25, '& .MuiAlert-message': { fontSize: '0.7rem' } }}>
{vm.disclaimer}
</Alert>
)}
</Box>
)
}
@@ -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
@@ -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)
<Paper sx={{ p: 2.5, mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<Zap size={16} color="#7c3aed" />
<Typography variant="h6" sx={{ fontWeight: 700 }}>Schattenmarkt-Signal</Typography>
<Typography variant="h6" sx={{ fontWeight: 700 }}>Future Availability Signal</Typography>
{signal.isVerified && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.4, bgcolor: '#f0fdf4', border: '1px solid #86efac', borderRadius: 1, px: 0.75, py: 0.2 }}>
<ShieldCheck size={11} color="#1a7a4a" />
@@ -204,7 +206,64 @@ export function FutureAvailabilityContextPanel({ match: _match, signal }: Props)
</Box>
)}
{/* 4. Relevance bridge */}
<Divider sx={{ mb: 2 }} />
{/* 4. Erkannte Marktindikatoren */}
{signal.marketIndicators && signal.marketIndicators.length > 0 && (
<Box sx={{ mb: 2 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1, fontSize: '0.8125rem' }}>
Erkannte Marktindikatoren
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.625 }}>
{signal.marketIndicators.map((indicator, i) => (
<Box key={i} sx={{ display: 'flex', alignItems: 'flex-start', gap: 0.75 }}>
<Box sx={{ width: 6, height: 6, borderRadius: '50%', bgcolor: '#1d4ed8', mt: '5px', flexShrink: 0 }} />
<Typography variant="body2" sx={{ color: '#374151', lineHeight: 1.45 }}>{indicator}</Typography>
</Box>
))}
</Box>
</Box>
)}
{/* 5. Strategische Interpretation */}
{signal.strategicInterpretation && (
<Box sx={{ mb: 2 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 0.75, fontSize: '0.8125rem' }}>
Strategische Interpretation
</Typography>
<Box sx={{ bgcolor: '#f8fafc', border: '1px solid #e2e8f0', borderRadius: 1, p: 1.5 }}>
<Typography variant="body2" sx={{ color: '#374151', lineHeight: 1.65, fontStyle: 'italic' }}>
{signal.strategicInterpretation}
</Typography>
</Box>
</Box>
)}
{/* 6. Transparenz (Bestätigt / Nicht bestätigt) */}
{((signal.confirmedFacts && signal.confirmedFacts.length > 0) ||
(signal.unconfirmedFacts && signal.unconfirmedFacts.length > 0)) && (
<Box sx={{ mb: 2 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, mb: 1, fontSize: '0.8125rem' }}>
Transparenz
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
{(signal.confirmedFacts ?? []).map((fact, i) => (
<Box key={`c-${i}`} sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
<CheckCircle2 size={13} color="#1a7a4a" style={{ flexShrink: 0 }} />
<Typography variant="caption" sx={{ color: '#1a7a4a', fontWeight: 500 }}>{fact}</Typography>
</Box>
))}
{(signal.unconfirmedFacts ?? []).map((fact, i) => (
<Box key={`u-${i}`} sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
<AlertTriangle size={13} color="#d97706" style={{ flexShrink: 0 }} />
<Typography variant="caption" sx={{ color: '#d97706', fontWeight: 500 }}>{fact}</Typography>
</Box>
))}
</Box>
</Box>
)}
{/* 7. Relevance bridge */}
{relevanceBridge && (
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, bgcolor: '#faf5ff', border: '1px solid #e9d5ff', borderRadius: 1, px: 1.5, py: 1.25, mb: 2 }}>
<Zap size={14} color="#7c3aed" style={{ flexShrink: 0, marginTop: 2 }} />
+5 -5
View File
@@ -63,7 +63,7 @@ function Slide0() {
<FeatureRow
icon={<Zap size={18} />}
color="#7c3aed"
text="Schattenmarkt-Signale der KI"
text="Future Availability Intelligence"
/>
</Box>
</Box>
@@ -81,7 +81,7 @@ function Slide1() {
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
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.
</Typography>
<Box className="w-full text-left">
<FeatureRow
@@ -106,11 +106,11 @@ function Slide2() {
<Zap size={48} color="#7c3aed" />
</Box>
<Typography variant="h5" sx={{ fontWeight: 700, mb: 1 }}>
Schattenmarkt Vor dem Markt informiert
Future Availability Intelligence
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
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.
</Typography>
<Box className="w-full text-left">
<FeatureRow
+13 -13
View File
@@ -10,8 +10,8 @@ interface Props {
onFilterChange: (v: FilterSource) => 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({
<Divider orientation="vertical" flexItem sx={{ mx: 0.5 }} />
{/* Schattenmarkt toggle */}
{/* Future Availability toggle */}
<Chip
size="small"
clickable
icon={<Zap size={11} color={showSchattenmarkt ? '#7c3aed' : '#94a3b8'} />}
label="Schattenmarkt"
onClick={() => onShowSchattenmarktChange(!showSchattenmarkt)}
icon={<Zap size={11} color={showFutureAvailability ? '#7c3aed' : '#94a3b8'} />}
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 },
}}
/>
+6 -6
View File
@@ -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 (
<>
<Divider sx={{ my: 2 }} />
<SectionTitle title="Schattenmarkt" />
<SectionTitle title="Pre-Market Freigabe" />
<Box
sx={{
border: '1px solid',
@@ -383,10 +383,10 @@ function SchattenmarktReleasePanel({ p }: { p: Property }) {
<Zap size={15} color={enabled ? '#7c3aed' : '#94a3b8'} style={{ marginTop: 2 }} />
<Box>
<Typography variant="body2" sx={{ fontWeight: 600, lineHeight: 1.3 }}>
Für Schattenmarkt freigeben
Pre-Market freigeben
</Typography>
<Typography variant="caption" color="text.secondary">
Nachfragesuchende sehen dieses Objekt vor Vertragsende im Feed
Fläche vor offizieller Vermarktung im Markt sichtbar machen
</Typography>
</Box>
</Box>
@@ -455,7 +455,7 @@ function SchattenmarktReleasePanel({ p }: { p: Property }) {
{!enabled && (
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 1 }}>
Wenn aktiviert, erscheint dieses Objekt {leadTimeMonths} Monate vor Vertragsende als
verifiziertes Schattenmarkt-Signal im Nachfrage-Feed.
verifiziertes Pre-Market Signal im Nachfrage-Feed.
</Typography>
)}
</Box>
+6
View File
@@ -43,4 +43,10 @@ export interface FutureSignal {
matchabilityScore?: number // 0100: 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", ...]
}
+12
View File
@@ -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,
}
}
+15 -1
View File
@@ -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',
],
}
}
+169 -63
View File
@@ -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 812% 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 812% 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 13: 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 69 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 600800 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 912 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\'5002\'000 m²)', 'Ob Breitenrain oder andere Berner Lage bevorzugt', 'Zeitpunkt Einzug'],
},
]
+1 -1
View File
@@ -172,7 +172,7 @@ export default function MatchDetail() {
sx={{ bgcolor: '#f1f5f9', color: '#475569', fontWeight: 700, letterSpacing: 0.5 }}
/>
{isFuture && (
<Chip label="Probabilistisches Signal" size="small" sx={{ bgcolor: '#faf5ff', color: '#7c3aed', fontWeight: 600 }} />
<Chip label="Future Availability Signal" size="small" sx={{ bgcolor: '#faf5ff', color: '#7c3aed', fontWeight: 600 }} />
)}
</Box>
+6 -6
View File
@@ -38,7 +38,7 @@ export default function Results() {
const { currentUser } = useSessionStore()
const [filterSource, setFilterSource] = useState<FilterSource>('ALL')
const [sortBy, setSortBy] = useState<SortBy>('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 ? (
<FeedSkeleton />
) : sorted.length === 0 ? (
<FeedEmptyState filtered={filterSource !== 'ALL' || !showSchattenmarkt || !showOwnProperties} />
<FeedEmptyState filtered={filterSource !== 'ALL' || !showFutureAvailability || !showOwnProperties} />
) : (
<UnifiedResultFeed results={sorted} view={view} />
)}