import type { CreateNeedInput } from '../../domain/need' import type { ParseNeedResult, ParsedNeedCriteria, FollowUpQuestion } from '../../domain/needBuilder' import type { UnifiedMatchResult } from '../../domain/unifiedResult' // ── AI Provenance ───────────────────────────────────────────────────────────── // Attached to every AI response so the UI can always trace where data came from. export interface AIProvenance { /** Which AI provider produced this response */ provider: 'openrouter' | 'mock' | 'backend' /** Exact model ID (e.g. 'anthropic/claude-3-5-haiku') or 'mock' */ model: string /** ISO-8601 timestamp of generation */ generatedAt: string /** Prompt version string used to generate this response */ promptVersion: string /** Zod schema version used for validation */ schemaVersion: string /** Whether the response is AI-only, mock-only, or a hybrid merge */ source: 'ai' | 'mock' | 'hybrid' /** True when the original AI call failed and mock was substituted */ fallbackUsed: boolean /** Human-readable reason why a fallback occurred — undefined when no fallback */ fallbackReason?: string /** True when the AI response passed Zod schema validation */ validationPassed: boolean /** Unique request ID — correlates AIResponse with AITrace.id */ traceId: string /** Wall-clock latency for this call in milliseconds */ latencyMs?: number } /** * All AI service methods return AIResponse instead of ItemResponse. * `data` is backward-compatible — existing call sites using `result.data.xxx` * continue to work unchanged. */ export type AIResponse = { data: T provenance: AIProvenance } // ── Helper ──────────────────────────────────────────────────────────────────── export function mockProvenance(overrides?: Partial): AIProvenance { return { provider: 'mock', model: 'mock', generatedAt: new Date().toISOString(), promptVersion: 'mock', schemaVersion: 'mock', source: 'mock', fallbackUsed: false, validationPassed: true, traceId: crypto.randomUUID(), ...overrides, } } // ── Response Types ──────────────────────────────────────────────────────────── export interface DecisionBrief { id: string shortlistId: string summary: string sections: { title: string; body: string }[] generatedAt: string isDraft: true } export interface ComparisonSummary { strongestOption: { matchId: string; label: string; reason: string } bestValue: { matchId: string; label: string; reason: string } | null highestConfidence: { matchId: string; label: string; confidenceLevel: number } biggestTradeoffs: string[] missingDataWarnings: string[] recommendedNextStep: string overallAssessment: string perPropertyAssessment: Array<{ matchId: string label: string strengths: string[] weaknesses: string[] bestFor: string keyRisk: string | null }> recommendation: string } export interface OfferEmailPayload { needTitle: string properties: string[] matchScores: number[] } export interface ParsedListingData { assetType?: string areaSqm?: number rentPerSqm?: number city?: string softLevels?: Record parking?: number fitOut?: string } export interface MatchExplanation { headline: string summary: string keyReasons: string[] } export interface TradeOffSummary { headline: string items: Array<{ concern: string; severity: 'LOW' | 'MEDIUM' | 'HIGH'; mitigation?: string }> overallRisk: 'LOW' | 'MEDIUM' | 'HIGH' } export interface DataQualitySummary { overallAssessment: string missingCriticalFields: string[] recommendation: string confidence: number } export interface MarketSignalClassification { signalType: 'VACANCY' | 'CONSTRUCTION' | 'RESTRUCTURING' | 'EXPANSION' | 'RELOCATION' | 'UNKNOWN' probability: number timeHorizonMonths: number | null areaSqmEstimate: number | null credibility: 'LOW' | 'MEDIUM' | 'HIGH' reasoning: string } // ── Input Types ─────────────────────────────────────────────────────────────── export interface MatchExplanationInput { propertyTitle: string propertyCity: string matchScore: number positiveFactors: Array<{ criterion: string; explanation: string }> negativeFactors: Array<{ criterion: string; explanation: string }> needSummary: string } export interface TradeOffInput { criterion: string concern: string severity: 'LOW' | 'MEDIUM' | 'HIGH' mitigation?: string } export interface DataQualityInput { score: number freshness: string missingCriticalFields: string[] missingOptionalFields: string[] warnings: string[] } // ── Fit-out advice ──────────────────────────────────────────────────────────── export interface FitOutAdviceInput { fitOut: string areaSqm: number mabPerSqm: number requiredFitOut?: string tenantBudgetPerSqm?: number monthlyRentPerSqm: number } export interface FitOutAdvice { recommendation: 'MIETERAUSBAU' | 'BKZ' | 'MAB_AMORTISATION' headline: string explanation: string negotiationTip: string estimatedNetInvestment: string } // ── Pre-market rent recommendation ───────────────────────────────────────────── export interface PreMarketRentInput { city: string assetType: string areaSqm: number currentRentPerSqm: number availableFrom?: string // ISO — Pre-Market liegt in der Zukunft } export interface PreMarketRentRecommendation { recommendedPerSqm: number rangeMinPerSqm: number rangeMaxPerSqm: number verdict: 'UNDERPRICED' | 'FAIR' | 'AMBITIOUS' // Bewertung des heutigen Preises deltaVsCurrentPct: number // Empfehlung vs. heutiger Preis drivers: string[] // Vergleichsmiete, Angebot, Nachfrage, Trend … rationale: string confidence: 'LOW' | 'MEDIUM' | 'HIGH' } // ── Legacy types (kept for backward compatibility) ──────────────────────────── export interface CriteriaExtractionResult { extractedCriteria: Partial confidence: number missingFields: string[] assumptions: string[] followUpQuestions: string[] } export interface AIServiceProvider { extractCriteria(naturalLanguageInput: string): Promise generateFollowUp(partialNeed: Partial): Promise } // ── Service Interface ───────────────────────────────────────────────────────── export interface IAIService { // Need parsing (F008) parseNeed(input: string): Promise> generateFollowUpQuestions(criteria: ParsedNeedCriteria): Promise> // Match explainability (F004) generateMatchExplanation(input: MatchExplanationInput): Promise> summarizeTradeOffs(tradeoffs: TradeOffInput[]): Promise> // Compare (F014) summarizeComparison(items: UnifiedMatchResult[]): Promise> // Shortlist decision brief generateDecisionBrief(shortlistId: string): Promise> // Data quality generateDataQualitySummary(propertyId: string, quality: DataQualityInput): Promise> // Market signal classification (OPERATIONS) classifyMarketSignal(signalText: string): Promise> // Offer email (supply side) generateOfferEmail(payload: OfferEmailPayload): Promise> // Fit-out investment advice (demand side) generateFitOutAdvice(input: FitOutAdviceInput): Promise> // Pre-market rent recommendation (supply side) — based on regional comparables, supply & demand recommendPreMarketRent(input: PreMarketRentInput): Promise> // Legacy methods extractCriteria(input: string): Promise> generateFollowUp(partialNeed: Partial): Promise> }