import type { UnifiedMatchResult, VerifiedPortfolioResult, ExternalMarketResult, FutureAvailabilityResult, } from '../../domain/unifiedResult' import type { ScoreFactor } from '../../domain/match' import type { MatchCardViewModel, MatchCardAction, MatchCardReason, } from '../../components/match-card/MatchCardViewModel' import { getCityIntelligence } from '../../lib/locationIntelligence' const HARD_CRITERIA = new Set(['area', 'location', 'budget', 'timing']) function buildReasons(positiveFactors: ScoreFactor[]): MatchCardReason[] { return positiveFactors.slice(0, 3).map(f => ({ type: (HARD_CRITERIA.has(f.criterion) ? 'HARD_FACT' : 'SOFT_FACTOR') as MatchCardReason['type'], label: capitalize(f.criterion), explanation: f.explanation, score: f.score, })) } function capitalize(s: string): string { return s.charAt(0).toUpperCase() + s.slice(1) } export function buildMatchCardViewModel( result: UnifiedMatchResult, actions: MatchCardAction[], ): MatchCardViewModel { const { match, matchScore, resultType } = result const isFuture = resultType === 'FUTURE_AVAILABILITY' const property = !isFuture ? (result as VerifiedPortfolioResult | ExternalMarketResult).property : (result as FutureAvailabilityResult).property const signal = isFuture ? (result as FutureAvailabilityResult).signal : undefined const unit = isFuture ? (result as FutureAvailabilityResult).unit : undefined const city = property?.location?.city const district = property?.location?.district const locationLabel = city ? `${city}${district ? `, ${district}` : ''}` : signal?.locationHint ?? '–' // For PRE-MARKET: title comes from unit if available, then signal, then property const availabilityLabel = property?.availabilityDate ?? (signal?.timeHorizonMonths ? `~${signal.timeHorizonMonths} Monate` : undefined) const sourceLabel = property?.sourceLabel ?? property?.sourceMeta?.sourceLabel ?? property?.sourceMeta?.sourceType const externalUrl = property?.sourceUrl ?? property?.sourceMeta?.sourceUrl const reasons = buildReasons(match.positiveFactors) const cityIntel = city ? getCityIntelligence(city) : null const taxCalculatorUrl = cityIntel?.taxCalculatorUrl const dataQualityScore = property?.dataQuality?.score ?? signal?.confidenceScore ?? 0.5 return { id: result.matchId, title: signal?.title ?? property?.title ?? signal?.companyName ?? signal?.locationHint ?? '–', resultType, assetType: property?.assetType, matchScore, confidenceScore: match.confidenceLevel, dataQualityScore, locationLabel, availabilityLabel, sourceLabel, externalUrl, reasons, tradeoffs: match.tradeoffs ?? [], risks: match.risks ?? [], missingData: match.missingData ?? [], actions, disclaimer: isFuture ? (signal?.disclaimer ?? 'Probabilistisches Signal – kein bestätigtes Objekt') : undefined, explainabilitySummary: match.explainabilitySummary, taxCalculatorUrl, isReviewRequired: match.status === 'PENDING_REVIEW', isStaleData: false, // Schattenmarkt signal fields signalSourceType: signal?.source?.type, signalSourceUrl: signal?.source?.url, signalSourceCredibility: signal?.source?.credibility, signalProbability: signal?.probability, signalMarketIndicator: signal?.marketIndicator, 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, propertyId: property?.id ?? signal?.propertyId, unitId: unit?.id ?? signal?.unitId, preMarketUnit: unit, preMarketAllUnits: property?.units, } }