Files
property-match/src/services/aiSearch/needSearchMapper.ts
T
Benjamin Sutter 07cea21143 feat(supply): Anfragencenter — embedded property cards in public need detail
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>
2026-06-23 00:03:36 +02:00

93 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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}`)
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}`)
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,
}
}