Zum Inhalt

XOLIB SCORE — VOLLSTÄNDIGE IMPLEMENTIERUNG

Claude Code Prompt · Version 2.0 · März 2026


KONTEXT

Du arbeitest an Xolib, einer KI-nativen B2B SaaS-Plattform für Hausverwaltung. Stack: Next.js, TypeScript (strict), Prisma, PostgreSQL, OpenAI GPT-4o-mini, Tailwind CSS, Lucide React. UI-Regeln: Kein Emoji, nur Lucide-Icons, Dark Theme (#0f172a / #1e293b / #334155 / #6366f1), keine hardcodierten deutschen Strings (i18n Pflicht, 8 Sprachen). Lies zuerst CLAUDE.md und MEMORY.md bevor du anfängst.


AUFGABE

Implementiere den Xolib Score vollständig — von Datenbankschema bis UI. Der Xolib Score ist ein objektiver, datengetriebener Gebäude-Bewertungsindex von 0–100. Er ist das strategisch wichtigste Feature der Plattform — nicht als UI-Element, sondern als Daten-Asset und zukünftiger B2B-Umsatzstrom für Banken, Versicherungen und Investoren.

Arbeite die Phasen 1–6 sequenziell ab. Teste nach jeder Phase. Committe nach jeder Phase mit aussagekräftiger Message. Aktualisiere CLAUDE.md und MEMORY.md nach Abschluss.


ARCHITEKTUR-PRINZIPIEN — NIEMALS VERLETZEN

  1. Score NIEMALS im synchronen API-Request berechnen — immer async via Background Job oder Queue
  2. Jede Score-Berechnung wird in PropertyScoreHistory gespeichert — kein Silent Update
  3. Score ist NICHT manuell überschreibbar — er ist berechnet, nicht gesetzt
  4. Fehlende Daten senken NICHT den Score, sondern die Konfidenz (zwei unabhängige Dimensionen)
  5. Erklärbarkheit vor Präzision: lieber erklärbarer 78 als unerklärlicher 79
  6. Bank-API-Kompatibilität von Tag 1: Schema so designen dass externe API später ohne Breaking Change möglich
  7. i18n auf ALLEN neuen Komponenten und API-Responses — keine hardcodierten Strings
  8. Jedes Feld das für ML nützlich sein könnte wird gespeichert — mehr Daten ist immer besser

PHASE 1 — DATENBANKSCHEMA

Füge folgende Modelle zu schema.prisma hinzu:

PropertyScore

model PropertyScore {
  id                  String   @id @default(cuid())
  tenantId            String
  propertyId          String   @unique

  totalScore          Float
  scoreGrade          String   // 'A+' | 'A' | 'B' | 'C' | 'D' | 'F'
  percentile          Float?

  substanzScore       Float
  technologieScore    Float
  ertragScore         Float
  complianceScore     Float
  instandhaltungScore Float

  potentialScore      Float
  potentialGap        Float

  scoreDelta30d       Float?
  scoreDelta90d       Float?
  trendDirection      String   // 'improving' | 'stable' | 'declining'

  riskFlags           Json     // [{flag, severity, category, detectedAt, titleKey, descriptionKey}]
  criticalIssues      Int      @default(0)

  calculatedAt        DateTime @default(now())
  dataCompleteness    Float
  confidenceScore     Float
  version             Int      @default(1)

  tenant              Tenant   @relation(fields: [tenantId], references: [id])
  property            Property @relation(fields: [propertyId], references: [id])
  history             PropertyScoreHistory[]
  factors             ScoreFactor[]

  @@index([tenantId, totalScore(sort: Desc)])
  @@index([tenantId, scoreGrade])
  @@index([propertyId])
}

PropertyScoreHistory

model PropertyScoreHistory {
  id                  String   @id @default(cuid())
  tenantId            String
  propertyId          String
  scoreId             String

  totalScore          Float
  scoreGrade          String
  substanzScore       Float
  technologieScore    Float
  ertragScore         Float
  complianceScore     Float
  instandhaltungScore Float
  potentialScore      Float

  changedFactors      Json
  changeReason        String   // 'scheduled_recalc' | 'data_update' | 'manual_trigger'
  triggeredBy         String?

  // Hash-Chaining
  scoreHash           String
  previousHash        String?
  chainValid          Boolean  @default(true)
  chainValidatedAt    DateTime?
  externalAnchorTx    String?
  externalAnchorAt    DateTime?

  calculatedAt        DateTime @default(now())

  score               PropertyScore @relation(fields: [scoreId], references: [id])

  @@index([propertyId, calculatedAt(sort: Desc)])
  @@index([tenantId, calculatedAt(sort: Desc)])
  @@index([scoreHash])
}

ScoreFactor

model ScoreFactor {
  id              String   @id @default(cuid())
  scoreId         String
  propertyId      String
  category        String   // 'substanz' | 'technologie' | 'ertrag' | 'compliance' | 'instandhaltung'
  factorKey       String
  factorLabelKey  String   // i18n key
  rawValue        Float
  normalizedValue Float
  weight          Float
  contribution    Float
  dataSource      String
  dataVersion     String   // 'v1_manual' | 'v2_auto' | 'v3_iot'
  confidence      Float    // 0-100: Konfidenz dieses einzelnen Faktors
  measuredAt      DateTime

  score           PropertyScore @relation(fields: [scoreId], references: [id])

  @@index([scoreId, category])
  @@index([propertyId, factorKey])
}

EnergyReading

model EnergyReading {
  id            String   @id @default(cuid())
  tenantId      String
  propertyId    String
  unitId        String?
  period        String   // Format: 'YYYY-MM'
  heatKwh       Float?
  coldWaterM3   Float?
  hotWaterM3    Float?
  electricityKwh Float?
  source        String   // 'techem' | 'ista' | 'brunata' | 'manual' | 'smart_meter'
  createdAt     DateTime @default(now())

  tenant        Tenant   @relation(fields: [tenantId], references: [id])
  property      Property @relation(fields: [propertyId], references: [id])

  @@unique([propertyId, unitId, period, source])
  @@index([propertyId, period])
}

WorkerRating

model WorkerRating {
  id                String   @id @default(cuid())
  tenantId          String
  ticketId          String   @unique
  serviceProviderId String
  propertyId        String
  stars             Int      // 1-5
  comment           String?
  reworkRequired    Boolean  @default(false)
  quotedAmount      Float?
  finalAmount       Float?
  ratedAt           DateTime @default(now())
  ratedByUserId     String

  tenant            Tenant          @relation(fields: [tenantId], references: [id])
  property          Property        @relation(fields: [propertyId], references: [id])

  @@index([propertyId, ratedAt(sort: Desc)])
  @@index([serviceProviderId])
}

GlobalChainAnchor

model GlobalChainAnchor {
  id          String   @id @default(cuid())
  anchorDate  DateTime @unique
  chainTip    String
  totalScores Int
  externalRef String?
  createdAt   DateTime @default(now())
}

Erweiterungen bestehender Modelle

Füge zum Property-Modell hinzu:

// Substanz
constructionYear      Int?
lastRenovationYear    Int?
roofRenovationYear    Int?
windowsRenovationYear Int?
heatingYear           Int?

// Technologie
energyCertificate     String?  // 'A+' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G'
heatingType           String?  // 'gas' | 'oil' | 'heat_pump' | 'district' | 'pellet' | 'electric'
hasFiber              Boolean  @default(false)
hasElevator           Boolean  @default(false)
hasSmartMeter         Boolean  @default(false)
hasEvCharging         Boolean  @default(false)
isBarrierfree         String?  // 'full' | 'partial' | 'none'

Führe nach allen Schema-Änderungen durch:

npx prisma migrate dev --name add_xolib_score
npx prisma generate


PHASE 2 — BERECHNUNGS-SERVICE

Erstelle src/lib/score/ als neues Verzeichnis mit folgenden Dateien:

src/lib/score/types.ts

Alle TypeScript-Typen für Score-Berechnung. ScoreData, CategoryScore, ScoreFactorData, RiskFlag, ScoreResult.

src/lib/score/normalize.ts

Hilfsfunktionen zur Normalisierung (0–100): - normalizeLinear(value, min, max) — lineare Normalisierung - normalizeSteps(value, steps: {threshold, score}[]) — stufenbasiert - normalizeAge(year, thresholds) — für Jahreszahlen - confidenceFromDataVersion(version: 'v1_manual' | 'v2_auto' | 'v3_iot') → v1=75, v2=95, v3=100

src/lib/score/categories/ertrag.ts

Berechnet Ertrag-Score (Gewicht 25%) aus: - Belegungsquote 90 Tage (35%): 100%=100, 95%=85, 90%=65, 85%=40, <80%=0 - Zahlungspünktlichkeit 12 Monate (30%): 0 Tage Verzug=100, <3=85, <7=65, <14=35, >14=0 - Leerstandstage 12 Monate (20%): 0=100, <7=85, <30=60, <90=30, >90=0 - Mietpreis vs. Markt (15%): Erst ab 100+ Einheiten im System, vorher 50 Pkt neutral - Zahlungstrend 24 Monate (neu): >20% Verbesserung=100, stabil=60, Verschlechterung=20 - Chronische Zahler >3x verspeätet (neu): 0=100, 1=70, 2=40, 3+=0

Datenquellen: Prisma-Abfragen auf units, tenancies, payments des propertyId.

src/lib/score/categories/instandhaltung.ts

Berechnet Instandhaltungs-Score (Gewicht 15%) aus: - Ticket-Lösungsrate 90 Tage (25%): >95%=100, >85%=80, >70%=55, >50%=25, <50%=0 - Durchschn. Lösungszeit (20%): <1 Tag=100, <3=85, <7=65, <14=35, >14=0 - Offene kritische Tickets (25%): 0=100, 1=60, 2=30, 3+=0. -10 Pkt je Ticket >30 Tage offen - Prävention vs. Reaktion (15%): >30% präventiv=100, >20%=75, >10%=50, <10%=25 - Handwerker-Durchschnittsbewertung (neu, 20% wenn Daten vorhanden): Ø5=100, Ø4.5=85, Ø4=70, Ø3.5=50, <3=0. Nur wenn min. 5 Ratings - Nachbearbeitungsquote (neu): <5%=100, <10%=80, <20%=55, >20%=0

Datenquellen: tickets, WorkerRating des propertyId.

src/lib/score/categories/compliance.ts

Berechnet Compliance-Score (Gewicht 15%) aus: - Freistellungsbescheinigungen (30%): 100% gültig=100, 1 abgelaufen=60, 2+=20, fehlend=0 - Versicherungsstatus (20%): alle gültig+hochgeladen=100, fehlende Doku=50, abgelaufen=0 - Dokument-Vollständigkeit (30%): Energieausweis (25%), Gebudeversicherung (25%), Übergabeprotokolle (15%), Wartungsverträge (15%), Freistellungsbescheinigungen (20%) - Offene gesetzliche Fristen (20%): keine=100, 1 Frist <30 Tage=60, 1 überfällig=20, 2+ überfällig=0 - BK-Abrechnungspünktlichkeit (neu): fristgerecht=100, <30 Tage verspätet=60, >30 Tage=0 - BK-Abweichungsquote (neu): <50 EUR Abweichung=100, <100=80, <200=55, <400=25, >400=0

Datenquellen: serviceProviders, documents, operatingCosts des propertyId.

src/lib/score/categories/substanz.ts

Berechnet Substanz-Score (Gewicht 25%) aus: - Gebäudealter (25%): <5J=100, <15=85, <30=65, <50=45, >50 ohne Sanierung=20 - Letzte Kernsanierung (30%): <5J=100, <10=85, <20=60, <30=35, >30 oder unbekannt=15 - Dach-Zustand (20%): <10J=100, <20=70, <30=40, >30=10, unbekannt=40 (neutral) - Fenster-Zustand (15%): <10J=100, <20=75, <30=40, >30=10, unbekannt=40 (neutral) - Heizungs-Alter (10%): <5J=100, <10=85, <15=60, <20=30, >20=0

Datenquellen: property Stammdaten.

src/lib/score/categories/technologie.ts

Berechnet Technologie-Score (Gewicht 25%) aus: - Energiezertifikat (35%): A+=100, A=90, B=75, C=55, D=35, E=20, F=10, G=0, fehlt=25 - Heizungstyp (25%): Wärmepumpe=100, Fernwärme=85, Pellet=75, Gas(neu)=55, Gas(alt)=35, Öl=10 - Smart Infrastructure (25%): +25 je Feature: Glasfaser, Smart Meter, Aufzug (>3 Etagen), EV-Ladeinfra - Barrierefreiheit (15%): full=100, partial=60, none=20, unbekannt=40 - Energieverbrauch real (neu, ersetzt Zertifikat-Proxy wenn vorhanden): <50kWh/m²=100, <80=85, <120=65, <160=40, >160=15

Datenquellen: property Stammdaten, EnergyReading.

src/lib/score/categories/sentiment.ts

Berechnet Kommunikations-Sentiment aus Ticket-Texten: - V1 (sofort): Keyword-basierte Klassifikation - Eskalations-Keywords DE: ['Anwalt', 'Klage', 'Mietminderung', 'fristlos', 'Verbraucherzentrale', 'Rechtsanwalt', 'gerichtlich', 'Schadensersatz'] - Positive Keywords: ['Danke', 'zufrieden', 'super', 'schnell erledigt', 'freundlich'] - Je Ticket: Score 0-100 basierend auf Keyword-Treffer - Durchschnittliches Sentiment 90 Tage (40%): positiv=100, neutral=70, negativ=40, eskalierend=0 - Eskalationsrate (35%): 0%=100, <5%=80, <10%=55, <20%=25, >20%=0 - Reaktionszeit-Frustrations-Proxy (25%): Korrelation hohe Wartezeit + negative Sentiment-Folge

src/lib/score/integrity.ts

Hash-Chaining Implementierung:

import crypto from 'crypto';

export interface ScoreHashInput {
  propertyId: string;
  totalScore: number;
  substanzScore: number;
  technologieScore: number;
  ertragScore: number;
  complianceScore: number;
  instandhaltungScore: number;
  calculatedAt: string; // ISO string
  previousHash: string | null;
}

export function calculateScoreHash(input: ScoreHashInput): string {
  // Felder MÜSSEN in dieser exakten Reihenfolge serialisiert werden
  const payload = JSON.stringify({
    propertyId:           input.propertyId,
    totalScore:           input.totalScore,
    substanzScore:        input.substanzScore,
    technologieScore:     input.technologieScore,
    ertragScore:          input.ertragScore,
    complianceScore:      input.complianceScore,
    instandhaltungScore:  input.instandhaltungScore,
    calculatedAt:         input.calculatedAt,
    previousHash:         input.previousHash ?? 'GENESIS',
  });
  return crypto.createHash('sha256').update(payload, 'utf8').digest('hex');
}

export async function verifyPropertyChain(propertyId: string): Promise<{
  valid: boolean;
  totalEntries: number;
  brokenAt?: string;
  brokenAtDate?: Date;
}> {
  // Lädt gesamte History aufsteigend, verifiziert jeden Hash gegen Vorgänger
  // Gibt erstes gebrochenes Entry zurück wenn Manipulation erkannt
}

export async function runDailyChainVerification(): Promise<void> {
  // Verifiziert ALLE Properties aller aktiven Mandanten
  // Schreibt Ergebnis in GlobalChainAnchor
  // Loggt Fehler ohne Exception (kein Score-Prozess blockieren)
}

src/lib/score/calculatePropertyScore.ts

Haupt-Berechnungsfunktion:

export async function calculatePropertyScore(
  propertyId: string,
  reason: 'scheduled_recalc' | 'data_update' | 'manual_trigger',
  triggeredBy?: string
): Promise<ScoreResult>

Ablauf: 1. Lade Property mit allen relevanten Relations 2. Berechne alle 5 Kategorien parallel (Promise.all) 3. Berechne Gesamt-Score: (substanz0.25 + technologie0.25 + ertrag0.25 + compliance0.15 + instandhaltung*0.15) 4. Berechne Potenzial-Score (was wäre bei 100% Datenvollständigkeit) 5. Berechne Konfidenz-Score 6. Berechne Score-Grade: A+=90+, A=80+, B=65+, C=50+, D=35+, F=<35 7. Berechne Risk-Flags (Liste aller kritischen/hohen Probleme) 8. Lade Previous-Hash aus letztem History-Eintrag 9. Berechne neuen Hash via calculateScoreHash() 10. Upsert PropertyScore + Insert PropertyScoreHistory + Insert ScoreFactors 11. Return ScoreResult

src/lib/score/backgroundJob.ts

export async function runNightlyScoreRecalculation(): Promise<void>
// Läuft täglich 02:00 Uhr
// Alle Properties aktiver Mandanten
// Fehler je Property werden geloggt, propagieren nicht
// Am Ende: runDailyChainVerification() + GlobalChainAnchor schreiben

export async function triggerScoreRecalculation(
  propertyId: string,
  reason: 'data_update',
  triggeredBy: string
): Promise<void>
// Async, nicht awaited im Haupt-Request
// Debounce: max. 1 Neuberechnung je Property alle 5 Minuten

PHASE 3 — TRIGGER-HOOKS

Füge Score-Neuberechnung als After-Hook in folgende bestehende API-Endpunkte ein:

  • POST/PATCH /api/v1/tickets/:id — wenn Status zu RESOLVED/CLOSED wechselt → triggerScoreRecalculation
  • POST /api/v1/payments — bei neuer Zahlung → triggerScoreRecalculation
  • PATCH /api/v1/payments/:id — wenn als überfällig markiert → triggerScoreRecalculation
  • POST/PATCH /api/v1/tenancies — bei Mietverhältnis-Änderung → triggerScoreRecalculation
  • PATCH /api/v1/properties/:id — wenn Score-relevante Felder geändert → triggerScoreRecalculation
  • POST /api/v1/worker-ratings — nach Rating-Eingabe → triggerScoreRecalculation

Wichtig: triggerScoreRecalculation IMMER ohne await aufrufen — nie den API-Request blockieren.


PHASE 4 — API-ENDPUNKTE

Erstelle folgende neue Endpunkte unter src/app/api/v1/:

GET /api/v1/properties/[id]/score

Response:

{
  score: PropertyScore & { factors: ScoreFactor[] },
  history: PropertyScoreHistory[], // letzte 12 Monate
  integrity: {
    hash: string,
    chainValid: boolean,
    lastVerified: Date,
    totalVersions: number,
  },
  benchmark: {
    percentile: number,
    sampleSize: number,
    available: boolean, // false wenn <50 Objekte im System
  }
}

GET /api/v1/properties/[id]/score/history

Query-Params: ?from=&to=&limit=100 Für Chart-Darstellung optimiert — nur Score-Werte, keine Faktoren.

POST /api/v1/properties/[id]/score/recalculate

Nur für OWNER_ADMIN. Async — liefert { jobId, status: 'queued' } zurück.

GET /api/v1/score/portfolio

Alle Objekte des Mandanten mit Score, Grade, Trend, Top-Risiko. Sortierbar, filterbar. Für Portfolio-Dashboard-Matrix.

GET /api/v1/score/portfolio/export

PDF-Export aller Portfolio-Scores. Für Banken und Investoren.

POST /api/v1/worker-ratings

Erstellt WorkerRating nach Ticket-Abschluss. Validierung: ticketId muss Status RESOLVED/CLOSED haben. Nach Erstellung: triggerScoreRecalculation.


PHASE 5 — UI-KOMPONENTEN

Erstelle unter src/components/score/ folgende Komponenten. Design-Vorgaben: Dark Theme, Lucide Icons, keine Emojis, i18n auf allen Strings, Tailwind.

ScoreHero.tsx

Volle Seitenbreite, sichtbar ohne Scrollen. Enthält: - LINKS: Große Score-Zahl + Grade-Badge + Trend-Indikator (Pfeil + Delta-Wert) - MITTE: 5 Kategorie-Kreisanzeigen (je Score + Label + Klick-Handler für Drill-Down) - Farb-Kodierung: Grün >=75 / Gelb 50-74 / Rot <50 - RECHTS: Konfidenz-Badge + Berechnungsdatum + Integritäts-Badge ('Hash-Kette gültig') - UNTEN: Drei Kennzahlen: Potenzial-Score / Benchmark-Percentile / Aktive Risiko-Flags - KI-Insight-Box: Automatisch generierter Satz zur wichtigsten Score-Veränderung

ScoreTrendChart.tsx

Recharts LineChart. Gesamt-Score über Zeit + optionale Kategorie-Linien (Toggle). Ereignis-Marker auf der Timeline (Hover zeigt was sich verändert hat). Benchmark-Linie (Marktdurchschnitt, gestrichelt). Zeitraum-Toggle: 3M / 6M / 12M / Gesamt. CSV-Export-Button.

ScoreCategoryGrid.tsx

5 Karten in Grid. Je Karte: - Kategorie-Name + Score + Gewicht - Liste aller Faktoren: Name · Wert · Score · Datenquelle · Messdatum - Datenstatus-Icon je Faktor: ✓ aktuell / ⏱ veraltet / ✗ fehlend / ★ manuell - Fehlende Daten mit CTA: 'Energieverbrauch fehlt — +6 Pkt bei Eingabe. Jetzt ergänzen →' - KI-Empfehlung: größte Verbesserungsmöglichkeit in dieser Kategorie - Klick öffnet ScoreFactorDrawer

ScoreFactorDrawer.tsx

Drawer von rechts (480px). Öffnet bei Klick auf Kategorie-Karte. Enthält: - Alle Faktoren der Kategorie als detaillierte Tabelle mit Rohdaten - Inline-Eingabe für manuelle Felder direkt im Drawer - Score-Preview: 'Nach Eingabe würde Score auf XX steigen' (Live-Berechnung) - Sparkline (Mini-Trendlinie) je Faktor letzte 6 Monate - Quellen-Transparenz: exakte Anzahl Datenpunkte und Zeitraum - Hash-Integritäts-Badge mit letztem Verifikations-Timestamp

RiskFlagPanel.tsx

Liste aller aktiven Risk-Flags. Je Flag: - Titel + Beschreibung + Kategorie + Schweregrad (KRITISCH/HOCH/MITTEL) - Erkannt-seit-Datum + Empfohlene Maßnahme + Geschätzte Score-Verbesserung - Direkte CTAs: 'Ticket erstellen' / 'Dokument hochladen' / 'Handwerker beauftragen' - Gelöste Flags letzte 30 Tage als graue Sektion: 'Behoben — Score +X Pkt'

ScoreIntegrityPanel.tsx

Standardmäßig zugeklappt (Accordion). Für Banken und externe Partner. Enthält: - Score-ID (zitierfähig) + SHA-256 Hash (anzeigen + kopieren) - Chain-Status: Anzahl Versionen + letzte Verifikation + Status - Externe Verankerung: Link zu GitHub oder Bitcoin TX - Verifikations-Anleitung: Step-by-Step für Nicht-Techniker (aufklappbar) - Download: Score-Zertifikat als PDF (digital signiert) - API-Endpunkt-Anzeige für direkte Bank-Abfrage

PortfolioScoreMatrix.tsx

Tabelle aller Objekte des Mandanten. Spalten: Name · Score · Grade · Trend · Konfidenz · Top-Risiko. Sortierbar nach allen Spalten. Filter: Grade-Filter / Trend-Filter / Risiko-Filter. Portfolio-Kennzahlen oben: Ø Score · Verteilung A/B/C/D/F als Stacked Bar · Anzahl kritischer Flags. Benchmark-Vergleich Zeile. PDF-Export für gesamten Portfolio-Report.

WorkerRatingModal.tsx

Öffnet automatisch wenn Ticket-Status auf RESOLVED/CLOSED wechselt. 1–5 Sterne Rating + optionaler Kommentar. Checkbox: 'Nachbearbeitung notwendig'. Felder: 'Angebotsbetrag' + 'Rechnungsbetrag' (optional). Submit löst triggerScoreRecalculation aus.


PHASE 6 — SCORE-SEITE

Erstelle src/app/[locale]/(dashboard)/properties/[id]/score/page.tsx

Vollständige Score-Analyse-Seite. Layout: 1. ScoreHero (volle Breite) 2. ScoreTrendChart (volle Breite) 3. ScoreCategoryGrid (volle Breite) 4. RiskFlagPanel (volle Breite) 5. ScoreIntegrityPanel (volle Breite, zugeklappt)

Navigation: Link von Property-Detail-Seite ('Score analysieren →'). Score-Badge auf Property-Karte in Listen-Ansicht (Grade-Badge + Score-Zahl).

Cron-Job registrieren für nächtliche Berechnung (02:00 Uhr): - Wenn bereits ein Cron-System vorhanden: dort eintragen - Sonst: /api/cron/score-recalculation Endpunkt mit CRON_SECRET Authentifizierung erstellen


NACH ABSCHLUSS

  1. Führe alle bestehenden Tests aus: npm test
  2. Schreibe Unit-Tests für:
  3. Alle normalizeSteps() Berechnungen (Grenzwerte)
  4. calculateScoreHash() Deterministik
  5. verifyPropertyChain() mit manipuliertem Eintrag
  6. Commit: feat: implement xolib score v1+v2 with hash-chaining integrity
  7. Aktualisiere CLAUDE.md: neue Modelle, neue Endpunkte, neue Komponenten
  8. Aktualisiere MEMORY.md: Score-Architektur als wichtigsten strategischen Kontext

WICHTIGE KONTEXTINFORMATIONEN

  • Xolib hat bereits: 46 Prisma-Modelle, 84 API-Endpunkte, 8 KI-Agenten, GPT-4o-mini Integration
  • Bestehende Modelle die der Score nutzt: Property, Tenant, Unit, Tenancy, Payment, Ticket, ServiceProvider, Document, OperatingCost
  • Der Score ist mandanten-isoliert (tenantId) — kein Cross-Tenant-Vergleich ohne explizite Pseudonymisierung
  • Löschen von PropertyScoreHistory ist dauerhaft verboten — Audit-Trail muss vollständig bleiben
  • Der Score ist das strategische Daten-Asset das langfristig als Bank-API monetarisiert wird — jede Implementierungsentscheidung muss das berücksichtigen