e169f8e310
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>
83 lines
3.6 KiB
TypeScript
83 lines
3.6 KiB
TypeScript
import { Box, Chip, CircularProgress, Paper, Typography } from '@mui/material'
|
|
import { Lightbulb, MessageSquareQuote } from 'lucide-react'
|
|
import { useFitOutAdvice } from '../../hooks/useAI'
|
|
import { DS_TEXT, DS_SURFACE, DS_BORDER } from '../../lib/ds'
|
|
|
|
const RECOMMENDATION_META: Record<string, { label: string; bg: string; border: string; fg: string }> = {
|
|
MIETERAUSBAU: { label: 'Mieterausbau', bg: DS_SURFACE.blue.bg, border: DS_SURFACE.blue.border, fg: DS_TEXT.signalDark },
|
|
BKZ: { label: 'Baukostenzuschuss (BKZ)', bg: DS_SURFACE.success.bg, border: DS_SURFACE.success.border, fg: DS_TEXT.success },
|
|
MAB_AMORTISATION: { label: 'MAB-Amortisation', bg: DS_SURFACE.success.bg, border: DS_SURFACE.success.border, fg: DS_TEXT.success },
|
|
}
|
|
|
|
interface Props {
|
|
fitOut: string
|
|
areaSqm: number
|
|
mabPerSqm: number
|
|
rentPricePerSqm: number
|
|
requiredFitOut?: string
|
|
tenantBudgetPerSqm?: number
|
|
}
|
|
|
|
export function FitOutAdvicePanel({ fitOut, areaSqm, mabPerSqm, rentPricePerSqm, requiredFitOut, tenantBudgetPerSqm }: Props) {
|
|
const { data, isLoading, isError } = useFitOutAdvice({
|
|
fitOut,
|
|
areaSqm,
|
|
mabPerSqm,
|
|
requiredFitOut,
|
|
tenantBudgetPerSqm,
|
|
monthlyRentPerSqm: Math.round(rentPricePerSqm / 12),
|
|
})
|
|
|
|
// Fallback: KI-Ausfall blockiert nie — Panel wird einfach nicht gezeigt
|
|
if (isError) return null
|
|
|
|
if (isLoading || !data) {
|
|
return (
|
|
<Paper sx={{ p: 2.5, display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
<CircularProgress size={16} />
|
|
<Typography variant="body2" sx={{ color: DS_TEXT.muted }}>KI-Ausbauempfehlung wird erstellt…</Typography>
|
|
</Paper>
|
|
)
|
|
}
|
|
|
|
const advice = data.data
|
|
const meta = RECOMMENDATION_META[advice.recommendation] ?? RECOMMENDATION_META.MIETERAUSBAU
|
|
|
|
return (
|
|
<Paper sx={{ p: 2.5 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1.5 }}>
|
|
<Lightbulb size={15} color={DS_TEXT.secondary} />
|
|
<Typography variant="h6" sx={{ fontWeight: 700 }}>KI-Ausbauempfehlung</Typography>
|
|
<Chip
|
|
label={meta.label}
|
|
size="small"
|
|
sx={{ ml: 'auto', bgcolor: meta.bg, color: meta.fg, fontWeight: 700, fontSize: 11, height: 22, border: `1px solid ${meta.border}` }}
|
|
/>
|
|
</Box>
|
|
|
|
<Typography variant="body2" sx={{ fontWeight: 700, mb: 0.75 }}>{advice.headline}</Typography>
|
|
<Typography variant="body2" sx={{ color: DS_TEXT.secondary, mb: 1.5, lineHeight: 1.55 }}>
|
|
{advice.explanation}
|
|
</Typography>
|
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', py: 1, px: 1.5, mb: 1.5, bgcolor: DS_SURFACE.neutral.bg, border: `1px solid ${DS_BORDER.default}`, borderRadius: 1 }}>
|
|
<Typography variant="caption" sx={{ color: DS_TEXT.muted, fontWeight: 600 }}>Geschätzte Nettoinvestition</Typography>
|
|
<Typography variant="body2" sx={{ fontWeight: 700 }}>{advice.estimatedNetInvestment}</Typography>
|
|
</Box>
|
|
|
|
<Box sx={{ display: 'flex', gap: 1, alignItems: 'flex-start' }}>
|
|
<MessageSquareQuote size={14} color={DS_TEXT.muted} style={{ marginTop: 3, flexShrink: 0 }} />
|
|
<Typography variant="caption" sx={{ color: DS_TEXT.secondary, lineHeight: 1.5 }}>
|
|
<strong>Verhandlungstipp:</strong> {advice.negotiationTip}
|
|
</Typography>
|
|
</Box>
|
|
|
|
{data.provenance?.fallbackUsed && (
|
|
<Typography variant="caption" sx={{ color: DS_TEXT.muted, display: 'block', mt: 1.25 }}>
|
|
Hinweis: KI nicht verfügbar — Richtwert-basierte Empfehlung.
|
|
</Typography>
|
|
)}
|
|
</Paper>
|
|
)
|
|
}
|