feat(supply): per-unit fit-out/cost-bearer/MAB/parking with object fallback (resolveUnitFacts)
- unit: add fitOutByLandlord, mieterausbaubeitragPerSqm, parkingSpots (parking = allocation from object pool) - lib/unitFacts.resolveUnitFacts: single resolver (unit value ?? object value) used by scorer + card adapter - matchSyncService: pre-market unit scored via resolved facts (fit-out, MAB, cost-bearer, parking allocation) - matchCardAdapter: fit-out label/viability/investment via resolveUnitFacts - UnitStructurePanel: consolidate ALL per-unit overrides into the one expandable row editor (price, availability, Ausbaustandard, Wer-baut-aus, MAB, parking allocation); show parking on row - PreMarketUnitGrid/Panel: drop the duplicate per-unit fit-out select (now in the unit editor) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import type { Need } from '../domain/need'
|
||||
import type { Property } from '../domain/property'
|
||||
import { getEffectiveUnits } from '../domain/property'
|
||||
import { calculateScore } from '../features/matching/scoreCalculator'
|
||||
import { resolveUnitFacts } from '../lib/unitFacts'
|
||||
|
||||
function strengthFromScore(s: number): MatchStrength {
|
||||
if (s >= 75) return MatchStrength.STRONG
|
||||
@@ -71,16 +72,16 @@ function scoreProperty(
|
||||
overrideArea?: number,
|
||||
overridePrice?: number,
|
||||
overrideResultType?: string,
|
||||
overrideFitOut?: 'SHELL' | 'BASIC' | 'FULL' | 'PREMIUM',
|
||||
overrideHardFacts?: Partial<NonNullable<Property['hardFacts']>>,
|
||||
): MatchEngineOutput {
|
||||
if (overrideArea !== undefined || overridePrice !== undefined || overrideResultType !== undefined || overrideFitOut !== undefined) {
|
||||
if (overrideArea !== undefined || overridePrice !== undefined || overrideResultType !== undefined || overrideHardFacts !== undefined) {
|
||||
return calculateScore(need, {
|
||||
...prop,
|
||||
areaSqm: overrideArea ?? prop.areaSqm,
|
||||
rentPricePerSqm: overridePrice ?? prop.rentPricePerSqm,
|
||||
resultType: (overrideResultType ?? prop.resultType) as ResultType,
|
||||
// Einheit-Ausbau überschreibt den Objekt-Ausbau (Fallback bleibt prop.hardFacts.fitOut)
|
||||
hardFacts: overrideFitOut ? { ...prop.hardFacts, fitOut: overrideFitOut } : prop.hardFacts,
|
||||
// Einheit-Werte überschreiben Objekt-Werte (Fallback bleibt prop.hardFacts)
|
||||
hardFacts: overrideHardFacts ? { ...prop.hardFacts, ...overrideHardFacts } : prop.hardFacts,
|
||||
})
|
||||
}
|
||||
return calculateScore(need, prop)
|
||||
@@ -104,7 +105,13 @@ export function generateMatchesForNeed(need: Need): void {
|
||||
}
|
||||
for (const unit of prop.units!) {
|
||||
if (!unit.schattenmarktRelease?.enabled) continue
|
||||
const unitOutput = scoreProperty(need, prop, unit.areaSqm, unit.rentPricePerSqm ?? prop.rentPricePerSqm, ResultType.FUTURE_AVAILABILITY, unit.fitOut)
|
||||
const facts = resolveUnitFacts(prop, unit)
|
||||
const unitOutput = scoreProperty(need, prop, unit.areaSqm, unit.rentPricePerSqm ?? prop.rentPricePerSqm, ResultType.FUTURE_AVAILABILITY, {
|
||||
fitOut: facts.fitOut,
|
||||
mieterausbaubeitragPerSqm: facts.mabPerSqm,
|
||||
fitOutByLandlord: facts.fitOutByLandlord,
|
||||
parking: facts.parkingSpots,
|
||||
})
|
||||
if (unitOutput.excluded || unitOutput.finalScore < MIN_SCORE) continue
|
||||
const resultId = `schattenmarkt-${prop.id}-${unit.id}`
|
||||
matchStore.push(buildMatch(prop, unit.id, need, unitOutput, ResultType.FUTURE_AVAILABILITY, resultId, now))
|
||||
|
||||
Reference in New Issue
Block a user