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:
@@ -4,53 +4,82 @@ import type { FutureSignal } from '../domain/futureSignal'
|
||||
import type { ReviewStatus } from '../domain/enums'
|
||||
import type { FutureSignalSummary } from '../domain/dashboard'
|
||||
import type { ListResponse, ItemResponse } from './types'
|
||||
import { throwServiceError } from './errors'
|
||||
|
||||
const provider = MockupFutureSignalProvider
|
||||
|
||||
export const futureSignalService = {
|
||||
async getAll(filters?: FutureSignalFilters): Promise<ListResponse<FutureSignal>> {
|
||||
const data = await provider.getAll(filters)
|
||||
return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } }
|
||||
try {
|
||||
const data = await provider.getAll(filters)
|
||||
return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } }
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
}
|
||||
},
|
||||
async getById(id: string): Promise<ItemResponse<FutureSignal | null>> {
|
||||
const data = await provider.getById(id)
|
||||
return { data }
|
||||
try {
|
||||
const data = await provider.getById(id)
|
||||
return { data }
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
}
|
||||
},
|
||||
async getByProperty(propertyId: string): Promise<ListResponse<FutureSignal>> {
|
||||
const data = await provider.getByProperty(propertyId)
|
||||
return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } }
|
||||
try {
|
||||
const data = await provider.getByProperty(propertyId)
|
||||
return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } }
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
}
|
||||
},
|
||||
async verify(id: string, verifiedBy: string): Promise<ItemResponse<FutureSignal>> {
|
||||
const data = await provider.verify(id, verifiedBy)
|
||||
return { data }
|
||||
try {
|
||||
const data = await provider.verify(id, verifiedBy)
|
||||
return { data }
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
}
|
||||
},
|
||||
async updateReviewStatus(id: string, status: ReviewStatus): Promise<ItemResponse<FutureSignal>> {
|
||||
const data = await provider.updateReviewStatus(id, status)
|
||||
return { data }
|
||||
try {
|
||||
const data = await provider.updateReviewStatus(id, status)
|
||||
return { data }
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
}
|
||||
},
|
||||
|
||||
async getSignalsForProperty(propertyId: string): Promise<ListResponse<FutureSignal>> {
|
||||
const data = await provider.getByProperty(propertyId)
|
||||
return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } }
|
||||
try {
|
||||
const data = await provider.getByProperty(propertyId)
|
||||
return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } }
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
}
|
||||
},
|
||||
|
||||
async getSignalSummary(): Promise<FutureSignalSummary> {
|
||||
const signals = await provider.getAll()
|
||||
const RESTRICTED: readonly string[] = ['CONFIDENTIAL', 'INTERNAL']
|
||||
return {
|
||||
total: signals.length,
|
||||
highConfidence: signals.filter(s => s.confidenceScore >= 0.75).length,
|
||||
restricted: signals.filter(s => RESTRICTED.includes(s.sensitivityLevel)).length,
|
||||
needsReview: signals.filter(s => !s.isVerified).length,
|
||||
avgTimeHorizonMonths:
|
||||
signals.length > 0
|
||||
? Math.round(signals.reduce((sum, s) => sum + (s.timeHorizonMonths ?? 0), 0) / signals.length)
|
||||
: 0,
|
||||
timeHorizonDistribution: {
|
||||
short: signals.filter(s => (s.timeHorizonMonths ?? 0) <= 6).length,
|
||||
medium: signals.filter(s => { const m = s.timeHorizonMonths ?? 0; return m > 6 && m <= 12 }).length,
|
||||
long: signals.filter(s => (s.timeHorizonMonths ?? 0) > 12).length,
|
||||
},
|
||||
try {
|
||||
const signals = await provider.getAll()
|
||||
const RESTRICTED: readonly string[] = ['CONFIDENTIAL', 'INTERNAL']
|
||||
return {
|
||||
total: signals.length,
|
||||
highConfidence: signals.filter(s => s.confidenceScore >= 0.75).length,
|
||||
restricted: signals.filter(s => RESTRICTED.includes(s.sensitivityLevel)).length,
|
||||
needsReview: signals.filter(s => !s.isVerified).length,
|
||||
avgTimeHorizonMonths:
|
||||
signals.length > 0
|
||||
? Math.round(signals.reduce((sum, s) => sum + (s.timeHorizonMonths ?? 0), 0) / signals.length)
|
||||
: 0,
|
||||
timeHorizonDistribution: {
|
||||
short: signals.filter(s => (s.timeHorizonMonths ?? 0) <= 6).length,
|
||||
medium: signals.filter(s => { const m = s.timeHorizonMonths ?? 0; return m > 6 && m <= 12 }).length,
|
||||
long: signals.filter(s => (s.timeHorizonMonths ?? 0) > 12).length,
|
||||
},
|
||||
}
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user