Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96a2507979 | |||
| 87ea4c4dfc |
@@ -20,6 +20,8 @@ import {
|
|||||||
import { CompareCell, MissingDataCell } from './index'
|
import { CompareCell, MissingDataCell } from './index'
|
||||||
import { RESULT_TYPE_META, DS_COLORS } from '../../lib/ds'
|
import { RESULT_TYPE_META, DS_COLORS } from '../../lib/ds'
|
||||||
import { matchScoreHex } from '../../lib/utils'
|
import { matchScoreHex } from '../../lib/utils'
|
||||||
|
import { calcFitOutInvestment } from '../../lib/fitOutUtils'
|
||||||
|
import { FITOUT_AMORTIZATION_YEARS } from '../../lib/constants'
|
||||||
import type { UnifiedMatchResult } from '../../domain/unifiedResult'
|
import type { UnifiedMatchResult } from '../../domain/unifiedResult'
|
||||||
|
|
||||||
// ── Local helper ──────────────────────────────────────────────────────────────
|
// ── Local helper ──────────────────────────────────────────────────────────────
|
||||||
@@ -148,18 +150,70 @@ export function CompareTableBody({
|
|||||||
: <MissingDataCell />
|
: <MissingDataCell />
|
||||||
}))}
|
}))}
|
||||||
|
|
||||||
{/* 9. Rent / Budget Fit */}
|
{/* 9. Rent / Budget Fit — full cost breakdown incl. amortised fit-out */}
|
||||||
{row('9. Miete / Budget', compareItems.map(item => {
|
{row('9. Kosten / Budget', compareItems.map(item => {
|
||||||
const prop = getProp(item)
|
const prop = getProp(item)
|
||||||
if (!prop) return <MissingDataCell reason="Mietpreis nur für bestätigte Objekte verfügbar" />
|
if (!prop) return <MissingDataCell reason="Mietpreis nur für bestätigte Objekte verfügbar" />
|
||||||
|
|
||||||
|
const FIT_OUT_LABELS: Record<string, string> = {
|
||||||
|
SHELL: 'Rohbau', BASIC: 'Grundausbau', FULL: 'Vollausbau', PREMIUM: 'Premium-Ausbau',
|
||||||
|
}
|
||||||
|
const READY_TO_MOVE_IN = new Set(['FULL', 'PREMIUM'])
|
||||||
|
|
||||||
|
const monthlyRent = prop.totalRentMonthly
|
||||||
|
?? Math.round(prop.rentPricePerSqm * prop.areaSqm / 12)
|
||||||
|
// ancillaryCosts stored as CHF/m²/Monat
|
||||||
|
const monthlyNebenkosten = prop.ancillaryCosts != null
|
||||||
|
? Math.round(prop.ancillaryCosts * prop.areaSqm)
|
||||||
|
: null
|
||||||
|
const fitOut = prop.hardFacts?.fitOut
|
||||||
|
const fitOutLabel = fitOut ? (FIT_OUT_LABELS[fitOut] ?? fitOut) : null
|
||||||
|
const mabPerSqm = prop.hardFacts?.mieterausbaubeitragPerSqm ?? 0
|
||||||
|
|
||||||
|
// Amortised fit-out monthly cost (mid-range estimate)
|
||||||
|
let fitOutMonthly = 0
|
||||||
|
let fitOutMonthlyLabel: string | null = null
|
||||||
|
if (fitOut && !READY_TO_MOVE_IN.has(fitOut)) {
|
||||||
|
const inv = calcFitOutInvestment(fitOut, prop.areaSqm, mabPerSqm, 0)
|
||||||
|
if (inv && !inv.isFullyCovered) {
|
||||||
|
const months = FITOUT_AMORTIZATION_YEARS * 12
|
||||||
|
const midMin = Math.round(inv.netTotal.min / months)
|
||||||
|
const midMax = Math.round(inv.netTotal.max / months)
|
||||||
|
fitOutMonthly = Math.round((midMin + midMax) / 2)
|
||||||
|
fitOutMonthlyLabel = midMin === midMax
|
||||||
|
? `${midMin.toLocaleString('de-CH')}`
|
||||||
|
: `${midMin.toLocaleString('de-CH')}–${midMax.toLocaleString('de-CH')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalMonthly = monthlyRent
|
||||||
|
+ (monthlyNebenkosten ?? 0)
|
||||||
|
+ fitOutMonthly
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.4 }}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ fontWeight: 700 }}>
|
||||||
CHF {prop.rentPricePerSqm}/m²/Jahr
|
CHF {prop.rentPricePerSqm}/m²/Jahr
|
||||||
</Typography>
|
</Typography>
|
||||||
{prop.totalRentMonthly && (
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
{monthlyRent.toLocaleString('de-CH')} CHF/Monat (Miete)
|
||||||
|
</Typography>
|
||||||
|
{monthlyNebenkosten != null && (
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
{prop.totalRentMonthly.toLocaleString('de-CH')} CHF/Monat
|
+ {monthlyNebenkosten.toLocaleString('de-CH')} CHF/Monat (NK)
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{fitOutMonthlyLabel && (
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
+ {fitOutMonthlyLabel} CHF/Monat (Ausbau ÷ {FITOUT_AMORTIZATION_YEARS} J.)
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
<Typography variant="body2" sx={{ fontWeight: 700, color: '#1e3a5f', borderTop: '1px solid #e2e8f0', pt: 0.5, mt: 0.25 }}>
|
||||||
|
= {totalMonthly.toLocaleString('de-CH')} CHF/Monat
|
||||||
|
</Typography>
|
||||||
|
{fitOutLabel && (
|
||||||
|
<Typography variant="caption" sx={{ color: '#64748b', mt: 0.25 }}>
|
||||||
|
Ausbau: {fitOutLabel}{READY_TO_MOVE_IN.has(fitOut ?? '') ? ' (bezugsfertig)' : ''}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import { Box, Chip, Paper, Typography } from '@mui/material'
|
|||||||
import { HardHat } from 'lucide-react'
|
import { HardHat } from 'lucide-react'
|
||||||
import { calcFitOutInvestment } from '../../lib/fitOutUtils'
|
import { calcFitOutInvestment } from '../../lib/fitOutUtils'
|
||||||
import { DS_TEXT, DS_SURFACE, DS_BORDER } from '../../lib/ds'
|
import { DS_TEXT, DS_SURFACE, DS_BORDER } from '../../lib/ds'
|
||||||
|
import { FITOUT_AMORTIZATION_YEARS } from '../../lib/constants'
|
||||||
|
|
||||||
const FIT_OUT_LABELS: Record<string, string> = {
|
const FIT_OUT_LABELS: Record<string, string> = {
|
||||||
SHELL: 'Rohbau', BASIC: 'Basisausbau', FULL: 'Vollausbau', PREMIUM: 'Premiumausbau',
|
SHELL: 'Rohbau', BASIC: 'Basisausbau', FULL: 'Vollausbau', PREMIUM: 'Premiumausbau',
|
||||||
}
|
}
|
||||||
|
|
||||||
const AMORTIZATION_YEARS = 5
|
const AMORTIZATION_YEARS = FITOUT_AMORTIZATION_YEARS
|
||||||
const READY_TO_MOVE_IN = new Set(['FULL', 'PREMIUM'])
|
const READY_TO_MOVE_IN = new Set(['FULL', 'PREMIUM'])
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -69,7 +70,7 @@ export function FitOutCostPanel({ fitOut, areaSqm, mabPerSqm, rentPricePerSqm, t
|
|||||||
const isWarning = !isReadyToMoveIn && totalPerYear.max > rentPerYear * 1.3
|
const isWarning = !isReadyToMoveIn && totalPerYear.max > rentPerYear * 1.3
|
||||||
const disclaimer = isReadyToMoveIn
|
const disclaimer = isReadyToMoveIn
|
||||||
? null
|
? null
|
||||||
: 'Ausbaukosten nach CRB/BKP-Normen, amortisiert über 5 Jahre. Tatsächliche Kosten je nach Ausbauumfang.'
|
: `Ausbaukosten nach CRB/BKP-Normen, amortisiert über ${AMORTIZATION_YEARS} Jahre. Tatsächliche Kosten je nach Ausbauumfang.`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper sx={{ p: 2.5 }}>
|
<Paper sx={{ p: 2.5 }}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { Box, Chip, LinearProgress, Typography } from '@mui/material'
|
import { Box, Chip, LinearProgress, Tooltip, Typography } from '@mui/material'
|
||||||
import { MapPin, Maximize2, TrendingUp, Calendar } from 'lucide-react'
|
import { MapPin, Maximize2, TrendingUp, Calendar } from 'lucide-react'
|
||||||
import { getAssetTypeColor, getAssetTypeLabel, getAvailabilityLabel } from './propertyHelpers'
|
import { getAssetTypeColor, getAssetTypeLabel, getAvailabilityLabel } from './propertyHelpers'
|
||||||
import type { Property } from '../../domain/property'
|
import type { Property } from '../../domain/property'
|
||||||
@@ -153,13 +153,26 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({
|
|||||||
<Chip label={`🚇 ${p.softFactors.publicTransportMinutes} min ÖV`} size="small"
|
<Chip label={`🚇 ${p.softFactors.publicTransportMinutes} min ÖV`} size="small"
|
||||||
sx={{ height: 20, fontSize: '0.65rem', bgcolor: '#f0f9ff', color: '#0369a1' }} />
|
sx={{ height: 20, fontSize: '0.65rem', bgcolor: '#f0f9ff', color: '#0369a1' }} />
|
||||||
)}
|
)}
|
||||||
{p.hardFacts?.fitOut && (
|
{p.hardFacts?.fitOut && (() => {
|
||||||
<Chip label={p.hardFacts.fitOut} size="small"
|
const FIT_OUT_META: Record<string, { label: string; tip: string }> = {
|
||||||
sx={{ height: 20, fontSize: '0.65rem', bgcolor: '#f5f3ff', color: '#6d28d9' }} />
|
SHELL: { label: 'Rohbau', tip: 'Keine Einbauten — volle Ausbauinvestition durch Mieter erforderlich (Böden, Decken, Trennwände, TGA).' },
|
||||||
)}
|
BASIC: { label: 'Grundausbau', tip: 'Grundinfrastruktur vorhanden (Böden, Beleuchtung, WCs). Ausbau für Büro/Betrieb noch nötig.' },
|
||||||
|
FULL: { label: 'Vollausbau', tip: 'Bezugsfertig ausgebaut — keine Ausbauinvestition nötig. Direkt einzugsbereit.' },
|
||||||
|
PREMIUM: { label: 'Premium-Ausbau', tip: 'Hochwertig und repräsentativ ausgebaut. Sofort bezugsfertig ohne weiteren Ausbau.' },
|
||||||
|
}
|
||||||
|
const meta = FIT_OUT_META[p.hardFacts!.fitOut!] ?? { label: p.hardFacts!.fitOut!, tip: '' }
|
||||||
|
return (
|
||||||
|
<Tooltip title={meta.tip} placement="top" arrow>
|
||||||
|
<Chip label={meta.label} size="small"
|
||||||
|
sx={{ height: 20, fontSize: '0.65rem', bgcolor: '#f5f3ff', color: '#6d28d9', cursor: 'help' }} />
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
{p.contractDurationMonths && (
|
{p.contractDurationMonths && (
|
||||||
<Chip label={`${p.contractDurationMonths}M Vertrag`} size="small"
|
<Tooltip title={`Mindest-Vertragslaufzeit: ${p.contractDurationMonths} Monate (${Math.round(p.contractDurationMonths / 12)} Jahre)`} placement="top" arrow>
|
||||||
sx={{ height: 20, fontSize: '0.65rem', bgcolor: '#f1f5f9', color: '#475569' }} />
|
<Chip label={`${p.contractDurationMonths}M Vertrag`} size="small"
|
||||||
|
sx={{ height: 20, fontSize: '0.65rem', bgcolor: '#f1f5f9', color: '#475569', cursor: 'help' }} />
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -143,21 +143,30 @@ export function UnitStructurePanel({ p }: { p: Property }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* Inline min-unit editor */}
|
{/* Inline min-unit editor — show when actively editing OR when teilbar is on but min sqm not set */}
|
||||||
{editingFlexUnit === u.id && (
|
{(editingFlexUnit === u.id || (u.isFlexible && !u.minLettableSqm)) && (
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="Min m²"
|
autoFocus={!u.minLettableSqm}
|
||||||
|
placeholder="Min m² *"
|
||||||
value={flexDraft[u.id] ?? ''}
|
value={flexDraft[u.id] ?? ''}
|
||||||
onChange={e => setFlexDraft(d => ({ ...d, [u.id]: parseInt(e.target.value) || undefined }))}
|
onChange={e => setFlexDraft(d => ({ ...d, [u.id]: parseInt(e.target.value) || undefined }))}
|
||||||
sx={{ width: 72, '& .MuiInputBase-input': { fontSize: '0.68rem', py: 0.375, px: 0.75 } }}
|
error={!flexDraft[u.id] && u.isFlexible && !u.minLettableSqm}
|
||||||
|
sx={{
|
||||||
|
width: 80,
|
||||||
|
'& .MuiInputBase-input': { fontSize: '0.68rem', py: 0.375, px: 0.75 },
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
'& fieldset': { borderColor: (!flexDraft[u.id] && u.isFlexible && !u.minLettableSqm) ? '#ef4444' : undefined },
|
||||||
|
},
|
||||||
|
}}
|
||||||
slotProps={{ htmlInput: { min: 10, max: u.areaSqm, step: 10 } }}
|
slotProps={{ htmlInput: { min: 10, max: u.areaSqm, step: 10 } }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
disabled={!flexDraft[u.id]}
|
||||||
sx={{ fontSize: '0.58rem', py: 0.25, px: 0.75, minWidth: 0, bgcolor: '#1d4ed8', '&:hover': { bgcolor: '#1e40af' } }}
|
sx={{ fontSize: '0.58rem', py: 0.25, px: 0.75, minWidth: 0, bgcolor: '#1d4ed8', '&:hover': { bgcolor: '#1e40af' } }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateUnit.mutate({ unitId: u.id, data: { isFlexible: true, minLettableSqm: flexDraft[u.id] } })
|
updateUnit.mutate({ unitId: u.id, data: { isFlexible: true, minLettableSqm: flexDraft[u.id] } })
|
||||||
@@ -166,14 +175,16 @@ export function UnitStructurePanel({ p }: { p: Property }) {
|
|||||||
>
|
>
|
||||||
✓
|
✓
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{editingFlexUnit === u.id && u.minLettableSqm && (
|
||||||
size="small"
|
<Button
|
||||||
variant="text"
|
size="small"
|
||||||
sx={{ fontSize: '0.58rem', py: 0.25, px: 0.5, minWidth: 0, color: DS_TEXT.muted }}
|
variant="text"
|
||||||
onClick={() => setEditingFlexUnit(null)}
|
sx={{ fontSize: '0.58rem', py: 0.25, px: 0.5, minWidth: 0, color: DS_TEXT.muted }}
|
||||||
>
|
onClick={() => setEditingFlexUnit(null)}
|
||||||
✕
|
>
|
||||||
</Button>
|
✕
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -193,6 +204,14 @@ export function UnitStructurePanel({ p }: { p: Property }) {
|
|||||||
rentPricePerSqm: u.rentPricePerSqm ?? p.rentPricePerSqm,
|
rentPricePerSqm: u.rentPricePerSqm ?? p.rentPricePerSqm,
|
||||||
unitLabel: u.unitLabel,
|
unitLabel: u.unitLabel,
|
||||||
propertyId: p.id,
|
propertyId: p.id,
|
||||||
|
floor: floorLabel(u),
|
||||||
|
fitOut: p.hardFacts?.fitOut,
|
||||||
|
parking: p.hardFacts?.parking,
|
||||||
|
ceilingHeight: p.hardFacts?.ceilingHeightM,
|
||||||
|
mieterausbaubeitragPerSqm: p.hardFacts?.mieterausbaubeitragPerSqm,
|
||||||
|
isFlexible: u.isFlexible,
|
||||||
|
minLettableSqm: u.minLettableSqm,
|
||||||
|
availableFrom: u.listing?.availableFrom ?? p.availabilityDate,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -109,19 +109,19 @@ export function useNewListingForm(pre: Prefill): NewListingFormState & NewListin
|
|||||||
const [city, setCity] = useState(pre.city ?? '')
|
const [city, setCity] = useState(pre.city ?? '')
|
||||||
const [areaSqm, setAreaSqm] = useState(pre.areaSqm ? String(pre.areaSqm) : '')
|
const [areaSqm, setAreaSqm] = useState(pre.areaSqm ? String(pre.areaSqm) : '')
|
||||||
const [rentPerSqm, setRentPerSqm] = useState(pre.rentPricePerSqm ? String(pre.rentPricePerSqm) : '')
|
const [rentPerSqm, setRentPerSqm] = useState(pre.rentPricePerSqm ? String(pre.rentPricePerSqm) : '')
|
||||||
const [availableFrom, setAvailableFrom] = useState('')
|
const [availableFrom, setAvailableFrom] = useState(pre.availableFrom ?? '')
|
||||||
const [description, setDescription] = useState('')
|
const [description, setDescription] = useState('')
|
||||||
const [contactName, setContactName] = useState('')
|
const [contactName, setContactName] = useState('')
|
||||||
const [contactEmail, setContactEmail] = useState('')
|
const [contactEmail, setContactEmail] = useState('')
|
||||||
const [contactPhone, setContactPhone] = useState('')
|
const [contactPhone, setContactPhone] = useState('')
|
||||||
const [softLevels, setSoftLevels] = useState<Record<string, string>>(emptySoftLevels)
|
const [softLevels, setSoftLevels] = useState<Record<string, string>>(() => ({ ...emptySoftLevels(), ...pre.softLevels }))
|
||||||
const [floor, setFloor] = useState('')
|
const [floor, setFloor] = useState(pre.floor ?? '')
|
||||||
const [fitOut, setFitOut] = useState('')
|
const [fitOut, setFitOut] = useState(pre.fitOut ?? '')
|
||||||
const [parking, setParking] = useState('')
|
const [parking, setParking] = useState(pre.parking != null ? String(pre.parking) : '')
|
||||||
const [ceilingHeight, setCeilingHeight]= useState('')
|
const [ceilingHeight, setCeilingHeight]= useState(pre.ceilingHeight != null ? String(pre.ceilingHeight) : '')
|
||||||
const [mieterausbaubeitrag, setMieterausbaubeitrag] = useState('')
|
const [mieterausbaubeitrag, setMieterausbaubeitrag] = useState(pre.mieterausbaubeitragPerSqm != null ? String(pre.mieterausbaubeitragPerSqm) : '')
|
||||||
const [isFlexible, setIsFlexible] = useState(false)
|
const [isFlexible, setIsFlexible] = useState(pre.isFlexible ?? false)
|
||||||
const [minLettableSqm, setMinLettableSqm] = useState('')
|
const [minLettableSqm, setMinLettableSqm] = useState(pre.minLettableSqm != null ? String(pre.minLettableSqm) : '')
|
||||||
const [images, setImages] = useState<string[]>([])
|
const [images, setImages] = useState<string[]>([])
|
||||||
const [imageInput, setImageInput] = useState('')
|
const [imageInput, setImageInput] = useState('')
|
||||||
const [floorPlanUrl, setFloorPlanUrl] = useState('')
|
const [floorPlanUrl, setFloorPlanUrl] = useState('')
|
||||||
|
|||||||
@@ -139,3 +139,6 @@ export const FIT_OUT_COST_CHF_PER_SQM: Record<string, { min: number; max: number
|
|||||||
FULL: { min: 50, max: 200 },
|
FULL: { min: 50, max: 200 },
|
||||||
PREMIUM: { min: 0, max: 50 },
|
PREMIUM: { min: 0, max: 50 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assumed amortization period for fit-out investment — change here to affect all cost calculations
|
||||||
|
export const FITOUT_AMORTIZATION_YEARS = 5
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ export default function AISearch() {
|
|||||||
isManualTextRef.current = text !== ''
|
isManualTextRef.current = text !== ''
|
||||||
setIsAutoGen(false)
|
setIsAutoGen(false)
|
||||||
setInputText(text)
|
setInputText(text)
|
||||||
|
// Clear stale parse results when user edits the text manually
|
||||||
|
setCriteria({})
|
||||||
|
setParseResult(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAiAutofill() {
|
function handleAiAutofill() {
|
||||||
|
|||||||
@@ -49,6 +49,15 @@ export interface LocationState {
|
|||||||
rentPricePerSqm?: number
|
rentPricePerSqm?: number
|
||||||
unitLabel?: string
|
unitLabel?: string
|
||||||
propertyId?: string
|
propertyId?: string
|
||||||
|
floor?: string
|
||||||
|
fitOut?: string
|
||||||
|
parking?: number
|
||||||
|
ceilingHeight?: number
|
||||||
|
mieterausbaubeitragPerSqm?: number
|
||||||
|
isFlexible?: boolean
|
||||||
|
minLettableSqm?: number
|
||||||
|
availableFrom?: string
|
||||||
|
softLevels?: Record<string, string>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,14 +47,17 @@ export function mockParseNeed(input: string): ParseNeedResult {
|
|||||||
return re.test(lower)
|
return re.test(lower)
|
||||||
}).map(([, v]) => v)
|
}).map(([, v]) => v)
|
||||||
|
|
||||||
// Infer Zürich when Zürich-specific districts or landmarks are mentioned
|
// Infer Zürich when Zürich-specific districts or landmarks are mentioned.
|
||||||
const ZURICH_SIGNALS = [
|
// "kreis N" patterns require a word boundary so "umkreis 22" does NOT match "kreis 2".
|
||||||
|
const ZURICH_SIGNAL_WORDS = [
|
||||||
'seefeld', 'bellevue', 'paradeplatz', 'bahnhofstrasse', 'zürich-west', 'zürich west',
|
'seefeld', 'bellevue', 'paradeplatz', 'bahnhofstrasse', 'zürich-west', 'zürich west',
|
||||||
'oerlikon', 'altstetten', 'kreis 1', 'kreis 2', 'kreis 3', 'kreis 4', 'kreis 5',
|
'oerlikon', 'altstetten', 'langstrasse', 'hardbrücke', 'freilager',
|
||||||
'kreis 6', 'kreis 7', 'kreis 8', 'langstrasse', 'hardbrücke', 'freilager',
|
|
||||||
'europaallee', 'zürich nord', 'zürich süd',
|
'europaallee', 'zürich nord', 'zürich süd',
|
||||||
]
|
]
|
||||||
if (!preferredLocations.includes('Zürich') && ZURICH_SIGNALS.some(s => lower.includes(s))) {
|
const ZURICH_KREIS_RE = /(?<![a-zA-ZäöüÄÖÜß])kreis\s+[1-8](?![0-9a-zA-ZäöüÄÖÜß])/
|
||||||
|
if (!preferredLocations.includes('Zürich') && (
|
||||||
|
ZURICH_SIGNAL_WORDS.some(s => lower.includes(s)) || ZURICH_KREIS_RE.test(lower)
|
||||||
|
)) {
|
||||||
preferredLocations.push('Zürich')
|
preferredLocations.push('Zürich')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user