Zum Inhalt

Immohai Datenmodell-Spezifikation

Status: verbindlich
Spezifikationsversion: 1.1.1
Modellbezug: 0.6.2
Implementierungsbezug: frontend-mvp-0.2.5
Stand: 2026-07-05

1. Zweck und Rang

Diese Datei ist die führende Wahrheit für Datenstrukturen und Feldverträge. Formeln stehen ausschließlich in docs/calculations/model-specification.md. Übergänge zwischen Eingabe, Szenarioauflösung, Engine, Speicherung und Reporting stehen in docs/specification/model-and-calculations/calculation-interfaces.md.

Diese Datei definiert:

kanonische Datenobjekte
Pflicht- und optionale Felder
zulässige Enumwerte
Versionierungsfelder
Snapshot- und Herkunftsregeln
Legacy-Lesealiase
Grenzen zwischen persönlichen Analysen, Mietdaten und Batchdaten

2. Grundsätze

Nur PropertyInput wird berechnet.
Quick, Expert und Batch verwenden dasselbe Modell.
Rohdaten werden nie direkt berechnet.
Berechnung und Speicherung sind getrennt.
Legacy-Aliase werden nur gelesen.
PropertyRecords sind unveränderbare Analysesnapshots.
Fachliche Feldnamen werden nicht je UI oder Report neu erfunden.
Reports und Vergleiche verwenden berechnete oder gespeicherte Snapshots.

3. Aktuelle Verträge und Versionen

PropertyInput:                 0.5.1
AnalysisResult:                0.1.1
PropertyRecord:                0.1.1
RentEstimate:                  0.1.0
MarketRentImport:              0.3.0
AnalysisDataQualityAssessment: 0.2.0
AnalysisDataQualityMethod:     analysis-dq-0.2.0

Maschinenlesbare Verträge:

model/schema/property-input.schema.json
model/schema/analysis-result.schema.json
model/schema/property-record.schema.json
model/schema/rent-estimate.schema.json
model/schema/market-rent-import.schema.json
model/schema/analysis-data-quality.schema.json

Die zusammengehörigen Versionen stehen in model/config/model-manifest.json. Frontend-Kopien unter frontend/assets/config/ müssen bytegleich sein.

4. Versionierungsfelder

PropertyInput.meta.schema_version
  = PropertyInput-Schemaversion

AnalysisResult.property_input_schema_version
AnalysisResult.analysis_result_schema_version

PropertyRecord.property_input_schema_version
PropertyRecord.analysis_result_schema_version
PropertyRecord.property_record_schema_version

Zusätzliche Konfigurations- und Methodenfelder:

default_assumptions_version
scenario_presets_version
object_types_version
market_rent_schema_version
rent_estimate_schema_version
analysis_data_quality_method_version

Das übergeordnete Feld schema_version auf AnalysisResult oder PropertyRecord ist nur ein nicht serialisierter Legacy-Lesealias für property_input_schema_version. Neue kanonische Ergebnisse schreiben es nicht.

5. ValueWithOrigin

Jeder berechnungsrelevante Eingabewert wird als ValueWithOrigin geführt.

Pflichtfelder:

value
unit
origin
source
scenario_policy

Optional:

confidence
note
evidence
value_status

origin:

manual
imported
scraped
estimated
derived
default
scenario
object_type_adjusted
missing

scenario_policy:

fixed
scenario_adjustable
sensitivity_only

value_status:

final
provisional

Regeln:

origin = missing -> value = null
value = null -> origin = missing
value_status = final -> scenario_policy = fixed
manuelle Eingabe allein bedeutet nicht automatisch final
source benennt die konkrete Herkunft oder den erzeugenden Schritt
confidence liegt, soweit numerisch geführt, zwischen 0 und 1

6. Fachstatus für Miete und Zins

6.1 rent_basis

current_contract_rent
market_rent_estimate
target_rent
manual_assumption

Standard-Policy:

current_contract_rent -> fixed
market_rent_estimate -> scenario_adjustable
target_rent -> scenario_adjustable
manual_assumption -> scenario_adjustable, außer value_status = final

6.2 interest_basis

binding_offer
indicative_market_rate
manual_assumption

Standard-Policy:

binding_offer -> fixed
indicative_market_rate -> scenario_adjustable
manual_assumption -> scenario_adjustable, außer value_status = final

interest_reset_basis verwendet dieselben Werte und Regeln für den Zins nach dem Reset.

7. ListingSourceReference

Ein Inseratslink wird ausschließlich als Quellenreferenz gespeichert.

Kanonische Struktur:

source_kind = listing_link
source_url
source_platform
processing_status = reference_only
captured_at
note optional

Verwendung:

PropertyInput.meta.source_reference
PropertyRecord.source_reference
PropertyRecord.listing_import_reference

Regeln:

source_url ist eine gültige URI.
source_platform benennt die Quelle nachvollziehbar.
captured_at ist ein ISO-8601-Zeitstempel.
processing_status bleibt reference_only.
Es erfolgt keine automatische Portalabfrage.
Es erfolgt keine automatische Feldübernahme.
Webseiteninhalt wird nicht direkt berechnet.

listing_import_state bleibt ausschließlich ein Legacy-Lesealias.

8. PropertyInput 0.5.1

Gruppen:

meta
base_data
acquisition
financing
rental
costs
tax optional
assumptions
scenarios
checklist optional
sources optional
notes optional

Neue PropertyInputs schreiben ausschließlich kanonische Felder. Die Root-Struktur erlaubt keine unbekannten zusätzlichen Gruppen.

8.1 meta

property_input_id
model_version
calculation_engine_version
schema_version
created_at
input_mode
analysis_origin
input_channel
object_id optional
updated_at optional
source_reference optional
data_quality_score optional
data_quality_assessment optional

input_mode:

quick
expert
batch

analysis_origin:

manual_analysis
batch_analysis
imported_analysis

Regeln:

property_input_id ist nicht leer.
model_version = 0.6.2.
schema_version = 0.5.1.
created_at und updated_at verwenden ISO 8601.
object_id wird nur als Referenz gelesen und nicht nachträglich in einen berechneten Snapshot geschrieben.
source_reference erfüllt bei Vorhandensein den ListingSourceReference-Vertrag.
data_quality_score liegt zwischen 0 und 100 oder ist null.

8.2 base_data

Pflichtfelder:

purchase_price
living_area_sqm
property_category
rental_strategy
construction_status
assumption_profile

Optionale Felder:

rooms
construction_year
condition_class
city
district
postal_code
location_label
object_type_profile

property_category:

apartment
single_family_house
two_family_house
multi_family_house
commercial
mixed_use

rental_strategy:

standard_rental
shared_flat
short_term_rental

construction_status:

existing
new_build

object_type_profile ist eine UI- und Übergangshilfe. Neue kanonische Datensätze führen zusätzlich die getrennten Dimensionen property_category, rental_strategy und construction_status.

8.3 acquisition

Pflichtfelder:

closing_costs_pct
initial_renovation_costs

Optional:

cash_reserve_after_closing

Regeln:

closing_costs_pct liegt zwischen 0 und 1.
initial_renovation_costs ist nicht negativ.
cash_reserve_after_closing ist ein Informations- und Due-Diligence-Feld.

8.4 financing

Pflichtfelder:

requested_equity_contribution
interest_initial_pct
interest_basis
amortization_initial_pct
interest_reset_year
interest_reset_pct
interest_reset_basis

Regeln:

Geld- und Prozentsatzwerte sind nicht negativ.
interest_reset_year ist eine positive Ganzzahl.
interest_basis und interest_reset_basis bestimmen die Standard-Szenario-Policy.

8.5 rental

Pflichtfelder:

monthly_cold_rent
rent_basis

Optional:

rent_estimate_reference

Regeln:

monthly_cold_rent ist nicht negativ.
market_rent_estimate benötigt eine RentEstimateReference.
current_contract_rent verwendet standardmäßig fixed.

8.6 costs

Pflichtfelder:

non_recoverable_operating_costs_pa
reserve_contribution_pa
recurring_capex_method
vacancy_pct

Konditional oder optional:

recurring_capex_pct_of_gross_rent conditional
recurring_capex_fixed_pa conditional
special_capex optional
house_fee_monthly optional

recurring_capex_method:

percent_of_gross_rent
fixed_annual

Methodenregeln:

percent_of_gross_rent -> recurring_capex_pct_of_gross_rent ist erforderlich
percent_of_gross_rent -> recurring_capex_fixed_pa ist 0 oder nicht vorhanden
fixed_annual -> recurring_capex_fixed_pa ist erforderlich
fixed_annual -> recurring_capex_pct_of_gross_rent ist 0 oder nicht vorhanden

special_capex[]:

event_id
year
amount
description optional
origin optional
source optional

event_id ist nicht leer, year ist positiv und amount ist nicht negativ.

8.7 tax

tax ist ein optionaler, modularer Bereich. Er darf zusätzliche Felder enthalten, solange diese nicht als Teil der MVP-Hauptkennzahl Cashflow vor Steuer interpretiert werden.

8.8 assumptions

Pflichtfelder:

term_years
inflation_pct
rent_growth_pct
value_growth_pct
etf_return_pct
selling_costs_pct

Regeln:

term_years ist eine positive Ganzzahl.
inflation_pct und etf_return_pct sind nicht negativ.
selling_costs_pct liegt zwischen 0 und 1.
etf_return_pct verwendet scenario_policy = sensitivity_only.

8.9 scenarios

scenario_order = [Best, Base, Stress]
selected_base_scenario = Base
control_mode = preset | custom_sliders
controls
overrides

Bei custom_sliders sind alle sieben Steuerwerte Pflicht:

rent_variation_pct
interest_variation_pct
rent_growth_variation_pct
value_growth_variation_pct
inflation_variation_pct
vacancy_variation_pct
capex_variation_pct

Alle Steuerwerte sind ValueWithOrigin und liegen als Dezimalbruch zwischen 0 und 1.

overrides darf nur enthalten:

Best optional
Stress optional

Base-Overrides und ETF-Overrides sind nicht zulässig. Ein Feldoverride enthält mindestens:

value
origin = scenario
base_value
source
note optional

8.10 checklist, sources und notes

checklist:
  strukturierte Due-Diligence- und Prüfinformationen

sources:
  nachvollziehbare Quelleneinträge

notes:
  ergänzende, nicht berechnende Hinweise

Diese Bereiche dürfen keine alternative Berechnungslogik enthalten.

9. AnalysisDataQualityAssessment 0.2.0

Führende Methode: docs/specification/data-quality-and-validation/data-quality-assessment.md.

data_quality_score_total
required_fields_score
location_match_score
rent_estimate_score
cost_data_score
condition_score
financing_data_score
status
warnings
method_version

status:

good
borderline
insufficient

Speicherung:

PropertyInput.meta.data_quality_assessment
AnalysisResult.data_quality_assessment
PropertyRecord.data_quality_assessment

Regeln:

alle Scores liegen zwischen 0 und 100
method_version = analysis-dq-0.2.0
fehlende oder unzureichende Datenqualität führt zu UNRATED
Datenqualität verändert keine Finanzformel

10. RentEstimate 0.1.0

Führender Vertrag: docs/specification/product-domains-and-workflows/rent-analysis.md.

Gemeinsame Pflichtfelder:

rent_estimate_id
created_at
estimate_type
source_type
lookup_method
matched_location_level
cold_rent_per_sqm
monthly_cold_rent
confidence
confidence_level
confidence_method_version
required_fields_missing
warnings

Optionale oder quellabhängige Felder:

rent_reference_dataset_id
rent_reference_dataset_version
rent_reference_dataset_scope_id
matched_group_fields
p25_eur_per_sqm
median_eur_per_sqm
p75_eur_per_sqm
sample_size
data_from
data_until
fallback_reason

estimate_type:

official_rent_reference
market_rent_estimate
wg_estimate

source_type:

official_rent_reference
market_rent_dataset
wg_estimate

Semantik:

confidence = Zahl 0 bis 1
confidence_level = high | medium | low | insufficient

Für market_rent_estimate sind Dataset-ID, Version, Scope, Gruppierungsfelder, P25, Median, P75 und Samplegröße Pflicht. Für offizielle Einzelreferenzen und WG-Schätzungen sind diese Felder nur zu führen, wenn sie fachlich vorhanden sind.

11. RentEstimateReference

Die im PropertyInput gespeicherte Referenz verwendet dieselben kanonischen Namen.

Gemeinsame Pflichtfelder:

rent_estimate_id
estimate_type
source_type
matched_location_level
matched_group_fields
confidence
confidence_level
confidence_method_version
warnings

Optional:

rent_reference_dataset_id
rent_reference_dataset_version
rent_reference_dataset_scope_id
lookup_method
p25_eur_per_sqm
median_eur_per_sqm
p75_eur_per_sqm
sample_size
data_from
data_until
fallback_reason

Für estimate_type = market_rent_estimate zusätzlich Pflicht:

rent_reference_dataset_id
rent_reference_dataset_version
rent_reference_dataset_scope_id
matched_group_fields
p25_eur_per_sqm
median_eur_per_sqm
p75_eur_per_sqm
sample_size

Nicht zulässig in neuen Datensätzen:

source_dataset_id
source_dataset_version
match_level

Diese Namen bleiben ausschließlich Legacy-Lesealiase.

12. ScenarioInput

ScenarioInput ist die vollständig aufgelöste Recheneingabe für genau ein Szenario.

Es enthält:

kanonische Zahlenwerte
fachliche Basisfelder
Herkunftsmetadaten
Szenario-Policies
Szenariosteuerungsart
Datenqualität
Modell-, Engine-, Schema- und Konfigurationsversionen
scenario_name

ScenarioInput ist kein dauerhaftes Nutzereingabeformat und wird nicht direkt aus Rohdaten oder DOM-Feldern erzeugt.

13. AnnualProjectionRow

Jede Jahreszeile enthält:

scenario_name
year
property_value
gross_rent
vacancy_loss
non_recoverable_operating_costs
reserve_contribution
recurring_capex
special_capex
noi
dscr_numerator
dscr
interest_rate
interest
principal
debt_service
debt_start
debt
current_ltv
cashflow_before_tax
annual_support_required
cumulative_support_required
side_account
property_equity_value
property_wealth_before_exit
net_vs_etf_before_exit
selling_costs
net_sale_proceeds
terminal_property_wealth
terminal_net_vs_etf
etf_value

Regeln:

year ist eine positive Ganzzahl.
scenario_name ist Best, Base oder Stress.
Nicht anwendbare Werte werden als null und nicht als erfundene Zahl gespeichert.

14. Kanonische KPIs

purchase_price
term_years
initial_renovation_costs
purchase_price_per_sqm
rent_per_sqm_month
closing_costs
total_acquisition_cost
total_initial_investment
requested_equity_contribution
equity_contributed
excess_available_equity
loan_amount
non_purchase_costs_financed_amount
initial_ltv
current_ltv_y1
gross_rent_y1
gross_yield_y1
noi_yield_y1
purchase_price_factor
noi_y1
reserve_contribution_y1
recurring_capex_y1
debt_service_y1
dscr_y1
cashflow_before_tax_y1
minimum_cashflow
cumulative_support_required
first_positive_cashflow_year
durable_self_funding_year
property_wealth_before_exit_end
terminal_property_wealth_end
etf_value_end
net_vs_etf_before_exit_end
terminal_net_vs_etf_after_exit
first_etf_break_even_year
durable_etf_break_even_year
selling_costs_end
data_quality_score

KPIs werden auf ScenarioResult-Ebene geführt. base_summary_kpis ist die unveränderte Base-Zusammenfassung im AnalysisResult.

15. KpiAssessment

Ein KPI-Assessment enthält mindestens:

status
label
reason
threshold_summary optional

Schwellen und fachliche Gründe stammen ausschließlich aus docs/calculations/model-specification.md und der Engine. UI und Reports leiten keine eigenen Assessments ab.

16. ScenarioAssessment

cashflow_status
dscr_status
liquidity_status
terminal_etf_status
critical_findings

ScenarioAssessment fasst Case-bezogene Ergebnisdimensionen zusammen und ist kein vollständiges Investment Rating.

17. ScenarioRating

scenario_rating
reason
hard_critical_findings

scenario_rating:

GREEN
YELLOW
RED
UNRATED

ScenarioRating dient ausschließlich der sichtbaren Case-Bewertung im Szenariovergleich. Es ist kein InvestmentRating und beeinflusst keine andere Berechnung.

18. ScenarioResult

scenario_name
projection
kpis
kpi_assessments
scenario_assessment
scenario_rating
plausibility_checks
warnings

Regeln:

scenario_name ist Best, Base oder Stress.
projection enthält ausschließlich AnnualProjectionRows.
kpis verwendet nur kanonische KPI-Namen.
kpi_assessments verwendet Engine-Ergebnisse.
Kein ScenarioResult enthält ein serialisiertes investment_rating oder generisches rating.

19. InvestmentRating

investment_rating
reason
components
hard_critical_findings

investment_rating:

GREEN
YELLOW
RED
UNRATED

InvestmentRating existiert genau einmal auf AnalysisResult-Ebene. Seine Regeln stehen ausschließlich in docs/calculations/model-specification.md.

20. AnalysisResult 0.1.1

analysis_result_id
property_input_id
property_input
model_version
calculation_engine_version
property_input_schema_version
analysis_result_schema_version
default_assumptions_version
scenario_presets_version
object_types_version
market_rent_schema_version optional
rent_estimate_schema_version optional
analysis_data_quality_method_version
created_at
scenario_results
base_summary_kpis
investment_rating
data_quality_assessment
warnings
calculation_status

calculation_status:

calculated
calculated_with_warnings
failed

Regeln:

analysis_result_id und property_input_id sind nicht leer.
property_input ist die unveränderbare Kopie des tatsächlich berechneten PropertyInput.
property_input_id = property_input.meta.property_input_id.
property_input erfüllt PropertyInput 0.5.1.
scenario_results enthält Best, Base und Stress.
base_summary_kpis stammt aus Base.
InvestmentRating wird genau einmal auf AnalysisResult-Ebene gespeichert.
Das Formular wird nach dem Rechenlauf nicht erneut als Eingabesnapshot interpretiert.
Runtime-Aliase wie results, baseResult und schema_version sind nicht enumerable und werden nicht serialisiert.
Snapshot-Metadaten wie snapshot_source und snapshot_object_id sind nicht Teil des serialisierten Vertrags.

21. PropertyRecord 0.1.1

object_id
analysis_result_id
display_name
created_at
updated_at
created_by optional
updated_by optional
owner_user_id optional
owner_display_name optional
model_version
calculation_engine_version
property_input_schema_version
analysis_result_schema_version
property_record_schema_version
default_assumptions_version
scenario_presets_version
object_types_version
market_rent_schema_version optional
rent_estimate_schema_version optional
analysis_data_quality_method_version
source_type
source_reference optional
batch_run_id optional
batch_row_id optional
listing_import_reference optional
status
tags
location
property_input
scenario_results
summary_kpis
rating
data_quality_assessment
plausibility_checks
warnings
calculation_status
notes

source_type:

manual_analysis
batch_analysis
imported_analysis

status:

watchlist
in_review
rejected
archived

location:

city
district
postal_code
location_label

Regeln:

object_id, analysis_result_id und display_name sind nicht leer.
property_record_schema_version = 0.1.1.
property_input erfüllt PropertyInput 0.5.1.
scenario_results erfüllt den ScenarioResult-Vertrag.
summary_kpis erfüllt den kanonischen KPI-Vertrag.
rating enthält ausschließlich das InvestmentRating.
source_reference und listing_import_reference erfüllen bei Vorhandensein ListingSourceReference.
object_id wird nicht in den gebundenen PropertyInput-Snapshot geschrieben.
Berechnen allein erzeugt keinen PropertyRecord.
Speichern erfolgt ausschließlich durch bewusste Nutzeraktion.

Beim Öffnen:

der gespeicherte Snapshot wird angezeigt
es startet kein automatischer Rechenlauf
alle im Formular repräsentierbaren Werte werden wiederhergestellt
Szenario-Steuerungsmodus und alle sieben Sliderwerte werden wiederhergestellt
nicht sichtbare Snapshot-Annahmen bleiben für eine bewusste Neuberechnung erhalten
Versionsunterschiede bleiben sichtbar

Eine bewusste Neuberechnung erzeugt ein neues PropertyInput und AnalysisResult, verändert aber den alten PropertyRecord nicht automatisch.

22. Warnungen und Plausibilitätschecks

Warnungen enthalten mindestens:

code
severity
message

Optional:

affected_fields
scenario_name
weitere strukturierte Kontextfelder

severity für aggregierte Warnungen:

info
warning
critical
high
medium
low

Plausibilitätschecks enthalten mindestens:

code
severity = info | warning | critical
message
affected_fields optional

Warnungen und Plausibilitätschecks erklären Ergebnisse, verändern aber keine Formel.

23. Legacy-Lesealiase

monthly_rent_market -> monthly_cold_rent
rent_mode -> rent_basis nur über explizite Migrationsabbildung
annual_gross_rent_y1 -> gross_rent_y1
net_yield_before_financing_y1 -> noi_yield_y1
ltv_y1 -> current_ltv_y1
net_vs_etf_end -> terminal_net_vs_etf_after_exit
property_wealth_end -> terminal_property_wealth_end
equity -> requested_equity_contribution oder equity_contributed je Altvertrag
schema_version auf AnalysisResult/PropertyRecord -> property_input_schema_version
source_dataset_id -> rent_reference_dataset_id
source_dataset_version -> rent_reference_dataset_version
match_level -> matched_location_level
listing_import_state -> listing_import_reference
non_recoverable_costs -> non_recoverable_operating_costs
running_capex -> recurring_capex
property_wealth -> terminal_property_wealth
net_vs_etf -> terminal_net_vs_etf

Regeln:

Legacy-Aliase sind nicht enumerable, soweit sie zur Laufzeit bereitgestellt werden.
Neue kanonische Datensätze schreiben keine Legacy-Aliase.
Eine dauerhafte Migration erfolgt nur explizit und versioniert.

24. Weitere Datenbereiche

24.1 Marktmiete

RentImportRun
RentObservation
RentAreaStats
RentReferenceDataset
RentEstimate

Diese Datenbereiche gehören zur Mietreferenzverwaltung und werden nicht als persönliche PropertyRecords gespeichert.

24.2 Batch

BatchImportRun
BatchImportRow
NormalizedListing
BatchAnalysisResult

Roh- und normalisierte Batchdaten werden nicht direkt zu PropertyRecords. Erst eine bewusste Übernahme eines vollständig berechneten Ergebnisses erzeugt einen PropertyRecord.

24.3 FinancingProfile

Ein späteres FinancingProfile ist eine versionierte Eingabehilfe zur Erzeugung normaler PropertyInputs. Es ist kein zweites Berechnungsmodell. Der Detailvertrag ist noch nicht verbindlich festgelegt.

25. Speicher

Aktuelle lokale Speicher:

immohai.propertyRecords.v1
immohai.marketRentDatabase.v1

Storage Keys werden nur über eine explizite Migration geändert.

26. Validierung

Verbindliche Prüfungen stehen in docs/specification/data-quality-and-validation/validation.md.

Mindestens zu validieren:

vollständige Schemaerfüllung
semantische ValueWithOrigin-Regeln
konditionale CAPEX-Felder
konditionale RentEstimateReference-Felder
Szenario-Reihenfolge und Sliderpflichten
ListingSourceReference
Snapshot-Identitäten
Nichtserialisierung von Runtime- und Legacy-Aliasen
PropertyRecord-Wiederherstellung und bewusste Neuberechnung