fix: resolve all TypeScript errors and refactor oversized components
- Fix MUI v9 API: PaperProps/InputLabelProps/inputProps → slotProps in 6 components - Add ExternalMarketResult alias, PropertyUnit import, OPERATIONS workspace config - Fix TradeOff.description → .concern, ScoreFactor.label → .criterion - Make schattenmarktRelease.leadTimeMonths optional, fix mock-data enum values - Fix useMatchDetailData query typing, weightingService missing WeightProfile keys - Split Pipeline/Compare/MatchDetail/IntelligenceMatchCard into sub-components - Fix all test fixtures (CreateNeedInput, CreatePropertyInput, TradeOffInput, etc.) - Add vercel.json for deployment, zero tsc errors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ describe('futureSignalService', () => {
|
||||
const result = await futureSignalService.getAll()
|
||||
expect(Array.isArray(result.data)).toBe(true)
|
||||
expect(result.data.length).toBeGreaterThan(0)
|
||||
expect(result.meta.total).toBe(result.data.length)
|
||||
expect(result.meta!.total).toBe(result.data.length)
|
||||
})
|
||||
|
||||
it('each signal has required fields', async () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ describe('matchService', () => {
|
||||
const result = await matchService.getAll()
|
||||
expect(Array.isArray(result.data)).toBe(true)
|
||||
expect(result.data.length).toBeGreaterThan(0)
|
||||
expect(result.meta.total).toBe(result.data.length)
|
||||
expect(result.meta!.total).toBe(result.data.length)
|
||||
})
|
||||
|
||||
it('each match has required fields', async () => {
|
||||
|
||||
@@ -7,9 +7,9 @@ describe('needService', () => {
|
||||
const result = await needService.getAll()
|
||||
expect(Array.isArray(result.data)).toBe(true)
|
||||
expect(result.data.length).toBeGreaterThan(0)
|
||||
expect(result.meta.total).toBe(result.data.length)
|
||||
expect(result.meta.page).toBe(1)
|
||||
expect(result.meta.hasMore).toBe(false)
|
||||
expect(result.meta!.total).toBe(result.data.length)
|
||||
expect(result.meta!.page).toBe(1)
|
||||
expect(result.meta!.hasMore).toBe(false)
|
||||
})
|
||||
|
||||
it('each need has required fields', async () => {
|
||||
@@ -49,6 +49,9 @@ describe('needService', () => {
|
||||
preferredLocations: ['Zürich'],
|
||||
budgetRange: { maxPerSqm: 400, maxMonthlyTotal: 10000, currency: 'CHF' as const },
|
||||
organizationId: 'org-test',
|
||||
timing: { earliestMoveIn: '2025-01-01', latestMoveIn: '2026-01-01', flexibleTiming: true },
|
||||
weightingProfile: { area: 0.3, location: 0.3, budget: 0.2, timing: 0.1, prestige: 0, accessibility: 0, expansionPotential: 0, flexibility: 0, visibility: 0, footfall: 0, talentAccess: 0, esg: 0, taxEnvironment: 0 },
|
||||
confidenceInCriteria: 0.8,
|
||||
}
|
||||
const result = await needService.create(input)
|
||||
expect(result.data.companyName).toBe('Test GmbH')
|
||||
@@ -79,6 +82,9 @@ describe('needService', () => {
|
||||
preferredLocations: ['Bern'],
|
||||
budgetRange: { maxPerSqm: 300, maxMonthlyTotal: 5000, currency: 'CHF' as const },
|
||||
organizationId: 'org-test',
|
||||
timing: { earliestMoveIn: '2025-01-01', latestMoveIn: '2026-01-01', flexibleTiming: true },
|
||||
weightingProfile: { area: 0.3, location: 0.3, budget: 0.2, timing: 0.1, prestige: 0, accessibility: 0, expansionPotential: 0, flexibility: 0, visibility: 0, footfall: 0, talentAccess: 0, esg: 0, taxEnvironment: 0 },
|
||||
confidenceInCriteria: 0.8,
|
||||
})
|
||||
const id = created.data.id
|
||||
await needService.remove(id)
|
||||
|
||||
@@ -7,8 +7,8 @@ describe('propertyService', () => {
|
||||
const result = await propertyService.getAll()
|
||||
expect(Array.isArray(result.data)).toBe(true)
|
||||
expect(result.data.length).toBeGreaterThan(0)
|
||||
expect(result.meta.total).toBe(result.data.length)
|
||||
expect(result.meta.hasMore).toBe(false)
|
||||
expect(result.meta!.total).toBe(result.data.length)
|
||||
expect(result.meta!.hasMore).toBe(false)
|
||||
})
|
||||
|
||||
it('each property has required fields', async () => {
|
||||
@@ -62,9 +62,14 @@ describe('propertyService', () => {
|
||||
assetType: 'LOGISTICS' as const,
|
||||
resultType: 'VERIFIED_PORTFOLIO' as const,
|
||||
location: { city: 'Basel', country: 'CH' as const },
|
||||
address: { street: 'Teststr.', houseNumber: '1', postalCode: '4000', city: 'Basel', country: 'CH' },
|
||||
areaSqm: 500,
|
||||
rentPricePerSqm: 120,
|
||||
availabilityDate: '2025-01-01',
|
||||
availabilityStatus: 'AVAILABLE_NOW' as const,
|
||||
sourceType: 'DIRECT',
|
||||
confidenceScore: 0.8,
|
||||
dataQuality: { score: 0.8, missingCriticalFields: [], missingOptionalFields: [], freshness: 'FRESH' as const, warnings: [] },
|
||||
organizationId: 'org-test',
|
||||
}
|
||||
const result = await propertyService.create(input)
|
||||
|
||||
@@ -73,8 +73,8 @@ describe('MockAIService.generateFollowUpQuestions', () => {
|
||||
assetType: 'OFFICE',
|
||||
areaRange: { min: 300, max: 600 },
|
||||
preferredLocations: ['Zürich'],
|
||||
budgetRange: { maxPerSqm: 400 },
|
||||
timing: { earliestMoveIn: '2025-09-01' },
|
||||
budgetRange: { maxPerSqm: 400, currency: 'CHF' },
|
||||
timing: { earliestMoveIn: '2025-09-01', latestMoveIn: '2026-09-01', flexibleTiming: false },
|
||||
mustHaveCriteria: ['ÖV-Anbindung'],
|
||||
}
|
||||
const result = await run(MockAIService.generateFollowUpQuestions(criteria))
|
||||
@@ -116,7 +116,7 @@ describe('MockAIService provenance', () => {
|
||||
describe('MockAIService.summarizeTradeOffs', () => {
|
||||
it('returns LOW risk when no HIGH severity trade-offs', async () => {
|
||||
const tradeoffs: TradeOffInput[] = [
|
||||
{ concern: 'Lage', severity: 'LOW', mitigation: 'Umgebung gut erschlossen' },
|
||||
{ criterion: 'Lage', concern: 'Lage', severity: 'LOW', mitigation: 'Umgebung gut erschlossen' },
|
||||
]
|
||||
const result = await run(MockAIService.summarizeTradeOffs(tradeoffs))
|
||||
expect(result.data.overallRisk).toBe('LOW')
|
||||
@@ -124,7 +124,7 @@ describe('MockAIService.summarizeTradeOffs', () => {
|
||||
|
||||
it('returns MEDIUM risk when exactly 1 HIGH severity trade-off', async () => {
|
||||
const tradeoffs: TradeOffInput[] = [
|
||||
{ concern: 'Fläche zu klein', severity: 'HIGH', mitigation: '' },
|
||||
{ criterion: 'Fläche', concern: 'Fläche zu klein', severity: 'HIGH', mitigation: '' },
|
||||
]
|
||||
const result = await run(MockAIService.summarizeTradeOffs(tradeoffs))
|
||||
expect(result.data.overallRisk).toBe('MEDIUM')
|
||||
@@ -132,8 +132,8 @@ describe('MockAIService.summarizeTradeOffs', () => {
|
||||
|
||||
it('returns HIGH risk when 2+ HIGH severity trade-offs', async () => {
|
||||
const tradeoffs: TradeOffInput[] = [
|
||||
{ concern: 'Fläche zu klein', severity: 'HIGH', mitigation: '' },
|
||||
{ concern: 'Budget weit überschritten', severity: 'HIGH', mitigation: '' },
|
||||
{ criterion: 'Fläche', concern: 'Fläche zu klein', severity: 'HIGH', mitigation: '' },
|
||||
{ criterion: 'Budget', concern: 'Budget weit überschritten', severity: 'HIGH', mitigation: '' },
|
||||
]
|
||||
const result = await run(MockAIService.summarizeTradeOffs(tradeoffs))
|
||||
expect(result.data.overallRisk).toBe('HIGH')
|
||||
@@ -155,6 +155,7 @@ describe('MockAIService.generateMatchExplanation', () => {
|
||||
propertyCity: 'Zürich',
|
||||
positiveFactors: [{ criterion: 'Lage', explanation: 'Zentrale Lage', weight: 0.3 }],
|
||||
negativeFactors: [{ criterion: 'Preis', explanation: 'Etwas über Budget', weight: 0.2 }],
|
||||
needSummary: 'Bürofläche 200–400 m² in Zürich',
|
||||
}
|
||||
|
||||
it('generates STARK headline for score >= 78', async () => {
|
||||
|
||||
@@ -4,16 +4,11 @@ import type { UnifiedMatchResult } from '../../../domain/unifiedResult'
|
||||
import type {
|
||||
IAIService,
|
||||
AIResponse,
|
||||
DecisionBrief,
|
||||
ComparisonSummary,
|
||||
CriteriaExtractionResult,
|
||||
OfferEmailPayload,
|
||||
MatchExplanationInput,
|
||||
MatchExplanation,
|
||||
TradeOffInput,
|
||||
TradeOffSummary,
|
||||
DataQualityInput,
|
||||
DataQualitySummary,
|
||||
MarketSignalClassification,
|
||||
} from '../IAIService'
|
||||
import { mockProvenance } from '../IAIService'
|
||||
|
||||
@@ -316,7 +316,7 @@ export const OpenRouterAIService: IAIService = {
|
||||
preferredLocations: ai.preferredLocations,
|
||||
budgetRange: ai.budgetRange ?? undefined,
|
||||
timing: ai.timing
|
||||
? { ...ai.timing, latestMoveIn: ai.timing.latestMoveIn ?? undefined }
|
||||
? { ...ai.timing, earliestMoveIn: ai.timing.earliestMoveIn ?? '', flexibleTiming: ai.timing.flexibleTiming ?? false }
|
||||
: undefined,
|
||||
mustHaveCriteria: ai.mustHaveCriteria,
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ export const aiTraceStore = new AITraceStore()
|
||||
// window.__aiTraces.stats() → summary statistics
|
||||
// window.__aiTraces.clear() → clear all
|
||||
if (import.meta.env.DEV && typeof window !== 'undefined') {
|
||||
;(window as Record<string, unknown>).__aiTraces = aiTraceStore
|
||||
;(window as unknown as Record<string, unknown>).__aiTraces = aiTraceStore
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -175,8 +175,8 @@ export const matchService = {
|
||||
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
|
||||
reasons: (match?.positiveFactors ?? []).slice(0, 3).map(f => f.explanation ?? f.criterion ?? '').filter(Boolean),
|
||||
topUncertainty: (match?.tradeoffs ?? match?.tradeOffs ?? [])[0]?.concern
|
||||
?? (match?.negativeFactors ?? [])[0]?.explanation
|
||||
?? undefined,
|
||||
}))
|
||||
|
||||
@@ -6,22 +6,27 @@ const PROFILES: Record<string, WeightProfile> = {
|
||||
OFFICE: {
|
||||
area: 0.20, location: 0.25, budget: 0.20, timing: 0.15,
|
||||
prestige: 0.10, accessibility: 0.05, expansionPotential: 0.03, flexibility: 0.02,
|
||||
visibility: 0, talentAccess: 0, footfall: 0, esg: 0, taxEnvironment: 0,
|
||||
},
|
||||
LOGISTICS: {
|
||||
area: 0.30, location: 0.20, budget: 0.20, timing: 0.15,
|
||||
prestige: 0.02, accessibility: 0.08, expansionPotential: 0.03, flexibility: 0.02,
|
||||
visibility: 0, talentAccess: 0, footfall: 0, esg: 0, taxEnvironment: 0,
|
||||
},
|
||||
RETAIL: {
|
||||
area: 0.15, location: 0.30, budget: 0.20, timing: 0.10,
|
||||
prestige: 0.12, accessibility: 0.08, expansionPotential: 0.03, flexibility: 0.02,
|
||||
visibility: 0, talentAccess: 0, footfall: 0, esg: 0, taxEnvironment: 0,
|
||||
},
|
||||
PRODUCTION: {
|
||||
area: 0.30, location: 0.20, budget: 0.20, timing: 0.15,
|
||||
prestige: 0.02, accessibility: 0.07, expansionPotential: 0.04, flexibility: 0.02,
|
||||
visibility: 0, talentAccess: 0, footfall: 0, esg: 0, taxEnvironment: 0,
|
||||
},
|
||||
DEFAULT: {
|
||||
area: 0.25, location: 0.25, budget: 0.20, timing: 0.15,
|
||||
prestige: 0.07, accessibility: 0.05, expansionPotential: 0.02, flexibility: 0.01,
|
||||
visibility: 0, talentAccess: 0, footfall: 0, esg: 0, taxEnvironment: 0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user