feat: OpenRouter-ready AI service — all 11 methods implemented

IAIService:
+ generateMatchExplanation, summarizeTradeOffs, generateDataQualitySummary,
  classifyMarketSignal (4 new methods covering all documented AI output types)

OpenRouterAIService:
- All 11 methods now make real API calls via withFallback() pattern
- Every fallback is explicit (console.warn/error) — no silent mock bleed-through
- Proper JSON extraction with type-safe parsers, no any casts
- parseNeed: AI JSON → ParseNeedResult mapping (no TODO stubs)
- generateFollowUpQuestions, generateMatchExplanation, summarizeTradeOffs,
  generateDataQualitySummary, classifyMarketSignal, generateOfferEmail,
  extractCriteria, generateFollowUp: fully implemented

Prompts: +followUpQuestionsPrompt, +tradeOffPrompt, +dataQualityPrompt, +marketSignalPrompt

Factory (index.ts):
- VITE_AI_PROVIDER=mock|openrouter (new, takes priority)
- VITE_USE_REAL_AI=true still supported (legacy compat)
- Missing API key → explicit console.warn + MockAIService fallback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-24 12:43:51 +02:00
parent 723f553939
commit c972392b78
9 changed files with 755 additions and 88 deletions
+66
View File
@@ -49,6 +49,62 @@ export interface ParsedListingData {
fitOut?: string
}
// ── New Response Types ────────────────────────────────────────────────────────
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[]
}
// ── Legacy types (kept for backward compatibility) ────────────────────────────
export interface CriteriaExtractionResult {
@@ -71,12 +127,22 @@ export interface IAIService {
parseNeed(input: string): Promise<ItemResponse<ParseNeedResult>>
generateFollowUpQuestions(criteria: ParsedNeedCriteria): Promise<ItemResponse<FollowUpQuestion[]>>
// Match explainability (F004)
generateMatchExplanation(input: MatchExplanationInput): Promise<ItemResponse<MatchExplanation>>
summarizeTradeOffs(tradeoffs: TradeOffInput[]): Promise<ItemResponse<TradeOffSummary>>
// Compare (F014)
summarizeComparison(items: UnifiedMatchResult[]): Promise<ItemResponse<ComparisonSummary>>
// Shortlist decision brief
generateDecisionBrief(shortlistId: string): Promise<ItemResponse<DecisionBrief>>
// Data quality
generateDataQualitySummary(propertyId: string, quality: DataQualityInput): Promise<ItemResponse<DataQualitySummary>>
// Market signal classification (OPERATIONS)
classifyMarketSignal(signalText: string): Promise<ItemResponse<MarketSignalClassification>>
// Offer email (supply side)
generateOfferEmail(payload: OfferEmailPayload): Promise<ItemResponse<{ subject: string; body: string }>>