feat: unified error handling + AI service modularisation
Error handling (Prompt 2): - src/services/errors.ts: AppError class, normalizeError(), throwServiceError() helper - 6 services wrapped with try/catch (property, match, need, shortlist, futureSignal, inquiry) - inquiryService aligned from custom ServiceResult<T> to standard ServiceResponse types - Results, MatchCenter, FutureAvailability pages show <ErrorState onRetry> on query failure AI modularisation (Prompt 3): - src/services/aiService.ts reduced from 755 → 19 lines (barrel re-export) - src/services/ai/IAIService.ts: typed interface + all response types - src/services/ai/mock/: needParser, compareBuilder, decisionBrief, listingParser, MockAIService - src/services/ai/openrouter/OpenRouterAIService.ts: model-agnostic skeleton - src/services/ai/prompts/: 4 prompt template files (needParsing, matchExplanation, compareSummary, decisionBrief) - src/services/ai/index.ts: factory selects Mock or OpenRouter via VITE_USE_REAL_AI flag - All existing import paths unchanged — zero call-site modifications Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import type { ItemResponse } from '../types'
|
||||
import type { CreateNeedInput } from '../../domain/need'
|
||||
import type { ParseNeedResult, ParsedNeedCriteria, FollowUpQuestion } from '../../domain/needBuilder'
|
||||
import type { UnifiedMatchResult } from '../../domain/unifiedResult'
|
||||
|
||||
// ── 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<string, string>
|
||||
parking?: number
|
||||
fitOut?: string
|
||||
}
|
||||
|
||||
// ── Legacy types (kept for backward compatibility) ────────────────────────────
|
||||
|
||||
export interface CriteriaExtractionResult {
|
||||
extractedCriteria: Partial<CreateNeedInput>
|
||||
confidence: number
|
||||
missingFields: string[]
|
||||
assumptions: string[]
|
||||
followUpQuestions: string[]
|
||||
}
|
||||
|
||||
export interface AIServiceProvider {
|
||||
extractCriteria(naturalLanguageInput: string): Promise<CriteriaExtractionResult>
|
||||
generateFollowUp(partialNeed: Partial<CreateNeedInput>): Promise<string[]>
|
||||
}
|
||||
|
||||
// ── Service Interface ─────────────────────────────────────────────────────────
|
||||
|
||||
export interface IAIService {
|
||||
// Need parsing (F008)
|
||||
parseNeed(input: string): Promise<ItemResponse<ParseNeedResult>>
|
||||
generateFollowUpQuestions(criteria: ParsedNeedCriteria): Promise<ItemResponse<FollowUpQuestion[]>>
|
||||
|
||||
// Compare (F014)
|
||||
summarizeComparison(items: UnifiedMatchResult[]): Promise<ItemResponse<ComparisonSummary>>
|
||||
|
||||
// Shortlist decision brief
|
||||
generateDecisionBrief(shortlistId: string): Promise<ItemResponse<DecisionBrief>>
|
||||
|
||||
// Offer email (supply side)
|
||||
generateOfferEmail(payload: OfferEmailPayload): Promise<ItemResponse<{ subject: string; body: string }>>
|
||||
|
||||
// Legacy methods
|
||||
extractCriteria(input: string): Promise<ItemResponse<CriteriaExtractionResult>>
|
||||
generateFollowUp(partialNeed: Partial<CreateNeedInput>): Promise<ItemResponse<string[]>>
|
||||
}
|
||||
Reference in New Issue
Block a user