import { MockupMatchProvider } from '../provider/MockupMatchProvider' import { MockupPropertyProvider } from '../provider/MockupPropertyProvider' import { MockupNeedProvider } from '../provider/MockupNeedProvider' import { MockupInquiryProvider } from '../provider/MockupInquiryProvider' import type { MatchFilters } from '../provider/IMatchProvider' import type { Match, PropertyNeedMatch } from '../domain/match' import type { Need } from '../domain/need' import type { Property } from '../domain/property' import type { StrongMatchItem } from '../domain/dashboard' import type { ScoreBreakdown } from '../domain/match' import type { AdditionalPropertyMatch } from '../domain/additionalMatch' import type { ListResponse, ItemResponse } from './types' import { buildFullMatch, computeRankedMatches } from '../features/matching/rankingEngine' import { ResultType } from '../domain/enums' import { throwServiceError } from './errors' const provider = MockupMatchProvider export const matchService = { async getAll(filters?: MatchFilters): Promise> { 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> { try { const data = await provider.getById(id) return { data } } catch (err) { throwServiceError(err) } }, async getByNeed(needId: string): Promise> { try { const data = await provider.getByNeed(needId) return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } } } catch (err) { throwServiceError(err) } }, async getByProperty(propertyId: string): Promise> { 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 approve(id: string, reviewedBy: string): Promise> { try { const data = await provider.approve(id, reviewedBy) return { data } } catch (err) { throwServiceError(err) } }, async getMatchesForProperty(propertyId: string): Promise> { 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 getMatchDetail(id: string): Promise> { try { const data = await provider.getById(id) return { data } } catch (err) { throwServiceError(err) } }, async getScoreBreakdown(matchId: string): Promise> { try { const match = await provider.getById(matchId) return { data: match?.scoreBreakdown ?? null } } catch (err) { throwServiceError(err) } }, // ── Engine-based methods ────────────────────────────────────────────────── computeMatch(need: Need, property: Property): Match { return buildFullMatch(need, property) }, async computeMatchesForNeed(needId: string): Promise> { try { const [need, properties] = await Promise.all([ MockupNeedProvider.getById(needId), MockupPropertyProvider.getAll(), ]) if (!need) return { data: [], meta: { total: 0, page: 1, pageSize: 0, hasMore: false } } const data = computeRankedMatches(need, properties) return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } } } catch (err) { throwServiceError(err) } }, async getNeedMatchesForProperty( propertyId: string, opts?: { minScore?: number }, ): Promise { try { const minScore = opts?.minScore ?? 80 const [matches, needs] = await Promise.all([ provider.getByProperty(propertyId), MockupNeedProvider.getAll(), ]) return matches .filter(m => m.matchScore >= minScore) .sort((a, b) => b.matchScore - a.matchScore) .map(m => { const need = needs.find(n => n.id === m.needId) if (!need) return null return { matchId: m.id, score: m.matchScore, needId: need.id, needTitle: `${need.companyName}`, company: need.companyName, areaRequired: `${need.requiredArea.min}–${need.requiredArea.max} m²`, budget: `max ${need.budgetRange.maxPerSqm} CHF/m²/Jahr`, locations: need.preferredLocations, assetType: need.assetType, timing: need.timing?.earliestMoveIn ?? '–', mustHaves: need.mustCriteriaText ?? [], matchHighlights: m.positiveFactors.map(f => f.explanation), } satisfies PropertyNeedMatch }) .filter((x): x is PropertyNeedMatch => x !== null) } catch (err) { throwServiceError(err) } }, async getAdditionalMatchesForInquiry( inquiryId: string, opts?: { minScore?: number; excludePropertyId?: string }, ): Promise { try { const minScore = opts?.minScore ?? 80 const inquiry = await MockupInquiryProvider.getById(inquiryId) if (!inquiry) return [] const excludeId = opts?.excludePropertyId ?? inquiry.propertyId const [allProperties, allMatches] = await Promise.all([ MockupPropertyProvider.getAll(), provider.getAll(), ]) const portfolioProperties = allProperties.filter( p => p.resultType === ResultType.VERIFIED_PORTFOLIO && p.id !== excludeId, ) return portfolioProperties .map(p => { const match = allMatches.find(m => m.propertyId === p.id) const score = match?.matchScore ?? 0 return { property: p, score, match } }) .filter(({ score }) => score >= minScore) .sort((a, b) => b.score - a.score) .slice(0, 5) .map(({ property: p, score, match }) => ({ propertyId: p.id, title: p.title, location: `${p.location.city}${p.location.district ? `, ${p.location.district}` : ''}`, areaSqm: p.areaSqm, rentPricePerSqm: p.rentPricePerSqm, matchScore: score, imageUrl: p.images?.[0], reasons: (match?.positiveFactors ?? []).slice(0, 3).map(f => f.explanation ?? f.label ?? '').filter(Boolean), topUncertainty: (match?.tradeoffs ?? match?.tradeOffs ?? [])[0]?.description ?? (match?.negativeFactors ?? [])[0]?.explanation ?? undefined, })) } catch (err) { throwServiceError(err) } }, async getStrongMatches(minScore = 80): Promise { try { const [matches, properties] = await Promise.all([ provider.getAll(), MockupPropertyProvider.getAll(), ]) return matches .filter(m => m.matchScore >= minScore) .slice(0, 5) .map(m => { const prop = properties.find(p => p.id === m.propertyId) const topFactor = m.positiveFactors?.[0] const firstAction = m.nextBestActions?.[0] return { matchId: m.id, propertyId: m.propertyId, propertyTitle: prop?.title ?? 'Unbekanntes Objekt', propertyAddress: prop?.address ? `${prop.address.street} ${prop.address.houseNumber}, ${prop.address.city}` : '–', needSummary: m.needId, matchScore: m.matchScore, topReason: topFactor?.explanation ?? topFactor?.criterion ?? '–', missingDataCount: m.missingData?.length ?? 0, nextBestAction: firstAction?.label ?? '–', } satisfies StrongMatchItem }) } catch (err) { throwServiceError(err) } }, }