07cea21143
Add EmbeddedPropertyCard and surface matching properties inside the public need detail view. Add needToParsedCriteria() mapper to convert a stored Need back into parsed criteria for re-running search/matching. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
93 lines
4.1 KiB
TypeScript
93 lines
4.1 KiB
TypeScript
import { AssetType } from '../../domain/enums'
|
||
import type { ParsedNeedCriteria, WeightingKey } from '../../domain/needBuilder'
|
||
import type { CreateNeedInput, Need } from '../../domain/need'
|
||
|
||
const ASSET_LABELS_TEXT: Record<string, string> = {
|
||
OFFICE: 'Bürofläche', RETAIL: 'Retail-Fläche', LOGISTICS: 'Logistikfläche',
|
||
PRODUCTION: 'Produktionsfläche', LIGHT_INDUSTRIAL: 'Gewerbefläche', MIXED: 'gemischte Fläche',
|
||
}
|
||
|
||
export function needToParsedCriteria(need: Need): ParsedNeedCriteria {
|
||
return {
|
||
assetType: need.assetType,
|
||
areaRange: need.requiredArea,
|
||
preferredLocations: need.preferredLocations,
|
||
budgetRange: need.budgetRange,
|
||
timing: need.timing,
|
||
mustHaveCriteria: need.mustCriteriaText,
|
||
softFactors: need.softFactors,
|
||
requireGroundFloor: need.requireGroundFloor,
|
||
requiredFitOut: need.requiredFitOut,
|
||
requiredParkingMin: need.requiredParkingMin,
|
||
requireAirConditioning: need.requireAirConditioning,
|
||
requireLoadingDock: need.requireLoadingDock,
|
||
requireBarrierFree: need.requireBarrierFree,
|
||
minCeilingHeightM: need.minCeilingHeightM,
|
||
minContractDurationMonths: need.minContractDurationMonths,
|
||
searchRadius: need.searchRadius,
|
||
isAnonymous: need.isAnonymous,
|
||
requiresDivisibility: need.requiresDivisibility,
|
||
minDivisibleUnit: need.minDivisibleUnit,
|
||
fitOutBudgetMaxPerSqm: need.fitOutBudgetMaxPerSqm,
|
||
notes: need.notes,
|
||
companyName: need.companyName,
|
||
}
|
||
}
|
||
|
||
export function generateSummary(c: ParsedNeedCriteria): string {
|
||
const parts: string[] = []
|
||
if (c.assetType) parts.push(`Suche ${ASSET_LABELS_TEXT[c.assetType] ?? c.assetType}`)
|
||
if (c.areaRange && (c.areaRange.min > 0 || c.areaRange.max > 0))
|
||
parts.push(`${c.areaRange.min}–${c.areaRange.max} m²`)
|
||
if (c.preferredLocations?.length) parts.push(`in ${c.preferredLocations.join(', ')}`)
|
||
if (c.budgetRange?.maxPerSqm) parts.push(`Budget max. CHF ${c.budgetRange.maxPerSqm}/m²`)
|
||
if (c.timing?.earliestMoveIn) parts.push(`ab ${c.timing.earliestMoveIn}`)
|
||
if (c.mustHaveCriteria?.length) parts.push(`Must-haves: ${c.mustHaveCriteria.join(', ')}`)
|
||
if (c.searchRadius) parts.push(`Radius ${c.searchRadius} km`)
|
||
if (c.isAnonymous) parts.push('Anonyme Suche')
|
||
if (c.requiresDivisibility && c.minDivisibleUnit) parts.push(`Teilbar ab ${c.minDivisibleUnit} m²`)
|
||
if (c.fitOutBudgetMaxPerSqm) parts.push(`Ausbaubudget max. CHF ${c.fitOutBudgetMaxPerSqm}/m²`)
|
||
return parts.join(', ')
|
||
}
|
||
|
||
export function buildNeedInput(
|
||
criteria: ParsedNeedCriteria,
|
||
weights: Record<WeightingKey, number>,
|
||
needTitle: string,
|
||
overallConfidence: number,
|
||
status: 'DRAFT' | 'ACTIVE',
|
||
): CreateNeedInput {
|
||
return {
|
||
companyName: needTitle || criteria.companyName || 'Neue Suche',
|
||
assetType: criteria.assetType ?? AssetType.UNKNOWN,
|
||
requiredArea: criteria.areaRange ?? { min: 0, max: 0 },
|
||
preferredLocations: criteria.preferredLocations ?? [],
|
||
budgetRange: criteria.budgetRange ?? { maxPerSqm: 0, currency: 'CHF' },
|
||
timing: {
|
||
earliestMoveIn: criteria.timing?.earliestMoveIn ?? '',
|
||
latestMoveIn: criteria.timing?.latestMoveIn ?? criteria.timing?.earliestMoveIn ?? '',
|
||
contractDurationMonths: criteria.timing?.contractDurationMonths,
|
||
flexibleTiming: criteria.timing?.flexibleTiming ?? true,
|
||
},
|
||
weightingProfile: weights,
|
||
confidenceInCriteria: overallConfidence,
|
||
status,
|
||
mustCriteriaText: criteria.mustHaveCriteria ?? [],
|
||
requireGroundFloor: criteria.requireGroundFloor,
|
||
requiredFitOut: criteria.requiredFitOut,
|
||
requiredParkingMin: criteria.requiredParkingMin,
|
||
requireAirConditioning: criteria.requireAirConditioning,
|
||
requireLoadingDock: criteria.requireLoadingDock,
|
||
requireBarrierFree: criteria.requireBarrierFree,
|
||
minCeilingHeightM: criteria.minCeilingHeightM,
|
||
minContractDurationMonths: criteria.minContractDurationMonths,
|
||
searchRadius: criteria.searchRadius,
|
||
isAnonymous: criteria.isAnonymous,
|
||
requiresDivisibility: criteria.requiresDivisibility,
|
||
minDivisibleUnit: criteria.minDivisibleUnit,
|
||
fitOutBudgetMaxPerSqm: criteria.fitOutBudgetMaxPerSqm,
|
||
notes: criteria.notes,
|
||
extractedFromText: undefined,
|
||
}
|
||
}
|