feat: remove EXTERNAL_MARKET result type entirely

All external market properties now surface as VERIFIED_PORTFOLIO.
Removes the type from enums, domain types, mock data, matching engine,
components, hooks, and tests — zero user-visible distinction remains.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-24 17:28:42 +02:00
parent 11de05025c
commit 609a3634bd
29 changed files with 60 additions and 102 deletions
@@ -38,20 +38,17 @@ describe('rankMatches', () => {
expect(ranked[0].propertyId).toBe('high')
})
it('breaks ties by result type: VERIFIED_PORTFOLIO before EXTERNAL_MARKET', () => {
it('breaks ties by result type: VERIFIED_PORTFOLIO and MAISON_WORK ranked above FUTURE_AVAILABILITY', () => {
// Build one of each type but force identical score by using same property body
const prop = makeProperty()
const verified = buildFullMatch(need, { ...prop, id: 'v', resultType: ResultType.VERIFIED_PORTFOLIO })
const external = buildFullMatch(need, { ...prop, id: 'e', resultType: ResultType.EXTERNAL_MARKET, confidenceScore: 0.85 })
const future = buildFullMatch(need, { ...prop, id: 'f', resultType: ResultType.FUTURE_AVAILABILITY, confidenceScore: 0.85 })
// If scores differ, force them equal for a clean tiebreak test
if (verified.matchScore !== external.matchScore) {
const lower = Math.min(verified.matchScore, external.matchScore)
verified.matchScore = lower
external.matchScore = lower
}
// Force same score for a clean tiebreak test
verified.matchScore = 75
future.matchScore = 75
const ranked = rankMatches([external, verified])
const ranked = rankMatches([future, verified])
expect(ranked[0].resultType).toBe(ResultType.VERIFIED_PORTFOLIO)
})
@@ -56,11 +56,6 @@ describe('calcConfidenceModifier', () => {
expect(calcConfidenceModifier(p)).toBe(0)
})
it('returns -3 for external market results', () => {
const p = makeProperty({ resultType: ResultType.EXTERNAL_MARKET, confidenceScore: 0.80 })
expect(calcConfidenceModifier(p)).toBe(-3)
})
it('returns -2 for Maison Work results', () => {
const p = makeProperty({ resultType: ResultType.MAISON_WORK, confidenceScore: 0.80 })
expect(calcConfidenceModifier(p)).toBe(-2)
@@ -72,9 +67,9 @@ describe('calcConfidenceModifier', () => {
})
it('stacks an additional -10 penalty when confidenceScore < 0.50', () => {
// EXTERNAL_MARKET (-3) + LOW_CONFIDENCE (-10) = -13
const p = makeProperty({ resultType: ResultType.EXTERNAL_MARKET, confidenceScore: 0.45 })
expect(calcConfidenceModifier(p)).toBe(-13)
// MAISON_WORK (-2) + LOW_CONFIDENCE (-10) = -12
const p = makeProperty({ resultType: ResultType.MAISON_WORK, confidenceScore: 0.45 })
expect(calcConfidenceModifier(p)).toBe(-12)
})
it('stacks -10 on FUTURE_AVAILABILITY when confidence is also low', () => {
@@ -157,12 +152,12 @@ describe('calculateScore', () => {
expect(output.finalScore).toBeGreaterThanOrEqual(85)
})
it('scores a weak match (wrong city, over budget, poor DQ, external market) below 50', () => {
it('scores a weak match (wrong city, over budget, poor DQ, low confidence) below 50', () => {
const need = makeNeed({ preferredLocations: ['Zürich'] })
const prop = makeProperty({
location: { city: 'Basel', country: 'CH' },
rentPricePerSqm: 70, // 140% of max budget 50 — not excluded but severe penalty
resultType: ResultType.EXTERNAL_MARKET,
resultType: ResultType.VERIFIED_PORTFOLIO,
confidenceScore: 0.45, // triggers LOW_CONFIDENCE -10 stacking
dataQuality: {
score: 0.30, // CRITICAL → -15
@@ -132,7 +132,7 @@ describe('softFactorEnrichmentService — score range invariant (all keys, 01
describe('softFactorEnrichmentService — Pre-Market vs Market Signal', () => {
it('returns identical estimate for same location regardless of resultType', () => {
// The enrichment service is location-only — resultType is irrelevant.
// FUTURE_AVAILABILITY (pre-market) and EXTERNAL_MARKET should produce the same soft factor score.
// FUTURE_AVAILABILITY (pre-market) and VERIFIED_PORTFOLIO should produce the same soft factor score.
const sharedLocation = { city: 'Zürich', district: 'Oerlikon', country: 'CH' }
const sharedAddress = { street: 'Thurgauerstrasse', houseNumber: '1', postalCode: '8050', city: 'Zürich', country: 'CH' }
@@ -141,16 +141,16 @@ describe('softFactorEnrichmentService — Pre-Market vs Market Signal', () => {
address: sharedAddress,
resultType: ResultType.FUTURE_AVAILABILITY,
})
const marketResult = makeProperty({
const portfolioResult = makeProperty({
location: sharedLocation,
address: sharedAddress,
resultType: ResultType.EXTERNAL_MARKET,
resultType: ResultType.VERIFIED_PORTFOLIO,
})
const futureEst = softFactorEnrichmentService.estimate('accessibility', futureSignal)
const marketEst = softFactorEnrichmentService.estimate('accessibility', marketResult)
expect(futureEst?.score).toBe(marketEst?.score)
expect(futureEst?.label).toBe(marketEst?.label)
const portfolioEst = softFactorEnrichmentService.estimate('accessibility', portfolioResult)
expect(futureEst?.score).toBe(portfolioEst?.score)
expect(futureEst?.label).toBe(portfolioEst?.label)
})
it('FUTURE_AVAILABILITY at Oerlikon gets meaningful accessibility score (> 0.70)', () => {