feat(matching): annuity-based fit-out cost in score + fix unapplied DQ/confidence modifiers

Mieterausbau / fit-out economics — surface true total cost of occupancy:
- Annuity calc (annuityFactor + effectiveAnnualBurdenPerSqm) replaces straight-line ÷5; FITOUT_ANNUITY_RATE=5%
- New hardFacts.fitOutByLandlord: "Wer baut aus?" toggle in NewListing — landlord-borne fit-out is priced into rent (no surcharge), tenant-borne SHELL/BASIC adds annuitized cost minus MAB
- scoreBudget now compares effective annual burden (rent + fit-out annuity) vs budget instead of cold rent only; FULL/PREMIUM and landlord-borne unchanged
- FitOutCostPanel + CompareTableBody compute annuitized, tenant-aware burden
- Central FIT_OUT_LABELS with industry/international vocabulary (Rohbau·Core&Shell, Edelrohbau·CAT A, etc.)
- Activate existing generateFitOutAdvice via useFitOutAdvice hook + new FitOutAdvicePanel (MIETERAUSBAU/BKZ/MAB-Amortisation + negotiation tip), shown for tenant-borne SHELL/BASIC
- MAB field only asked when tenant builds out (optional) — one new toggle, no extra data burden for property managers

Fix: DQ/confidence modifiers were computed but never applied to finalScore (hardcoded 0 in output) — now folded into rawFinal and exposed. Trust-first: weak data quality lowers the score. Resolves 3 pre-existing red tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin SutterandClaude Sonnet 4.6 committed 2026-06-20 18:19:58 +02:00
1 parent 7a0909e36a
commit e169f8e310
18 files changed
+313 -104

No files matched your search

@@ -181,6 +181,27 @@ describe('calculateScore', () => {
expect(output.excludedReason).toBeTruthy()
})
// ── Ausbau-Annuität im Budget-Faktor ────────────────────────────────────────
function budgetFactor(need: ReturnType<typeof makeNeed>, prop: ReturnType<typeof makeProperty>) {
return calculateScore(need, prop).allHardFactors.find(f => f.criterion === 'budget')!
}
it('tenant-borne SHELL lowers the budget factor vs a fitted-out (FULL) property', () => {
const need = makeNeed({ budgetRange: { maxPerSqm: 50, currency: 'CHF' } })
const base = { rentPricePerSqm: 48, areaSqm: 300 }
const shell = makeProperty({ ...base, hardFacts: { fitOut: 'SHELL' } })
const full = makeProperty({ ...base, hardFacts: { fitOut: 'FULL' } })
expect(budgetFactor(need, shell).score).toBeLessThan(budgetFactor(need, full).score)
})
it('landlord-borne SHELL keeps the budget factor (no fit-out surcharge)', () => {
const need = makeNeed({ budgetRange: { maxPerSqm: 50, currency: 'CHF' } })
const base = { rentPricePerSqm: 48, areaSqm: 300 }
const tenant = makeProperty({ ...base, hardFacts: { fitOut: 'SHELL' } })
const landlord = makeProperty({ ...base, hardFacts: { fitOut: 'SHELL', fitOutByLandlord: true } })
expect(budgetFactor(need, landlord).score).toBeGreaterThan(budgetFactor(need, tenant).score)
})
it('produces identical scores on repeated calls with the same inputs (determinism)', () => {
const need = makeNeed()
const prop = makeProperty()