- Memorizer tracks user_expectation (conversational/delegated/waiting_input/observing) - Output node adjusts phrasing per expectation - PA retry loop: reformulates job on expert failure (all retries exhausted or tool skip) - Machine state in PA context: get_machine_summary includes current state, buttons, stored data - Expert writes to machine state via update_machine + transition_machine - Expanded baked schema coverage - Awareness panel shows color-coded expectation state - Dashboard and workspace component updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
226 lines
9.4 KiB
Python
226 lines
9.4 KiB
Python
"""Eras Expert: Heizkostenabrechnung domain specialist.
|
|
|
|
The expert knows the full database schema. No DESCRIBE at runtime.
|
|
All queries use verified column names and JOIN patterns.
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
|
|
from .expert_base import ExpertNode
|
|
from ..db import run_db_query
|
|
|
|
log = logging.getLogger("runtime")
|
|
|
|
|
|
class ErasExpertNode(ExpertNode):
|
|
name = "eras_expert"
|
|
default_database = "eras2_production"
|
|
|
|
DOMAIN_SYSTEM = """You are the Eras domain expert for Heizkostenabrechnung (German heating cost billing).
|
|
|
|
BUSINESS CONTEXT:
|
|
Eras is software for Hausverwaltungen and Messdienste who manage properties, meters, and billings.
|
|
The USER of this agent is an Eras customer exploring their data. They think in domain terms
|
|
(Kunden, Objekte, Wohnungen, Zaehler) — NOT in SQL. Never expose SQL or table names to the user.
|
|
|
|
DOMAIN MODEL:
|
|
- Kunden = property managers (Hausverwaltungen). 693 in the system.
|
|
- Objekte = buildings/Liegenschaften managed by Kunden. 780 total. Linked via objektkunde (m:n).
|
|
- Nutzeinheiten = apartments/units inside Objekte. 4578 total.
|
|
- Nutzer = tenants/occupants of Nutzeinheiten. 8206 total.
|
|
- Geraete = measurement devices (Heizkostenverteiler, Zaehler). 56726 total.
|
|
- Verbraeuche = consumption readings from Geraete. 1.3M readings.
|
|
- Adressen = postal addresses, linked via objektadressen/kundenadressen.
|
|
|
|
RESPOND IN DOMAIN LANGUAGE:
|
|
- Say "Kunde Jaeger hat 3 Objekte" not "SELECT COUNT..."
|
|
- Say "12 Wohnungen mit 45 Geraeten" not "nutzeinheit rows"
|
|
- Present data as summaries, not raw tables"""
|
|
|
|
SCHEMA = """COMPLETE DATABASE SCHEMA (eras2_production) — use these exact column names:
|
|
|
|
=== kunden (693 rows) ===
|
|
PK: ID (int)
|
|
Name1, Name2, Name3 (longtext) — customer name parts
|
|
Kundennummer (longtext) — customer number
|
|
AnredeID (FK), BriefanredeID (FK), ZugeordneterKomplettdruckID (FK)
|
|
Anmerkung, Fremdnummer, Ansprechpartner (longtext)
|
|
Steuernummer, UmsatzsteuerID (longtext)
|
|
HatHistorie, IstWebkunde, IstNettoKunde, BrennstoffkostenNachFIFO, BelegePerEmail (bool)
|
|
MietpreisAnpassungProzent (decimal)
|
|
|
|
=== objektkunde (911 rows) — JUNCTION: kunden ↔ objekte (many-to-many) ===
|
|
PK: ID (int)
|
|
KundeID (FK → kunden.ID)
|
|
ObjektID (FK → objekte.ID)
|
|
ZeitraumVon, ZeitraumBis (datetime)
|
|
IstKunde, IstEigentuemer, IstRechnungsempfaenger, IstAbrechnungsempfaenger (bool)
|
|
|
|
=== objekte (780 rows) ===
|
|
PK: ID (int)
|
|
Objektnummer (longtext) — building reference number
|
|
AbleserID, MonteurID, UVIRefObjektID, ZugeordneterKomplettdruckID (FK)
|
|
Anmerkung, AnmerkungIntern (longtext)
|
|
HatHistorie, VorauszahlungGetrennt, Selbstablesung, IstObjektFreigegeben (bool)
|
|
|
|
=== objektadressen — JUNCTION: objekte ↔ adressen ===
|
|
PK: ID, ObjektID (FK → objekte.ID), AdresseID (FK → adressen.ID), IstPrimaer (bool)
|
|
|
|
=== kundenadressen — JUNCTION: kunden ↔ adressen ===
|
|
PK: ID, KundeID (FK → kunden.ID), AdresseID (FK → adressen.ID), TypDerAdresseID (FK)
|
|
|
|
=== adressen (1762 rows) ===
|
|
PK: ID (int)
|
|
Strasse, Hausnummer, Postleitzahl, Ort, Adresszusatz, Postfach (longtext)
|
|
LandID (FK), Laengengrad, Breitengrad (double)
|
|
|
|
=== nutzeinheit (4578 rows) ===
|
|
PK: ID (int)
|
|
ObjektID (FK → objekte.ID)
|
|
NeNummerInt (longtext) — unit number
|
|
Lage, Stockwerk, Flaeche, Nutzflaeche (various)
|
|
AdresseID (FK), CustomStatusKeyID (FK)
|
|
|
|
=== kundenutzeinheit — JUNCTION: kunden ↔ nutzeinheit ===
|
|
PK: ID, KundeID (FK → kunden.ID), NutzeinheitID (FK → nutzeinheit.ID), Von, Bis (datetime)
|
|
|
|
=== nutzer (8206 rows) — tenants/occupants ===
|
|
PK: ID (int)
|
|
NutzeinheitID (FK → nutzeinheit.ID)
|
|
Name1, Name2, Name3, Name4 (longtext) — tenant name
|
|
NutzungVon, NutzungBis (datetime)
|
|
ArtDerNutzung (int), AnredeID (FK), BriefanredeID (FK)
|
|
IstGesperrt, Selbstableser (bool)
|
|
|
|
=== geraete (56726 rows) — meters/devices ===
|
|
PK: ID (int)
|
|
NutzeinheitID (FK → nutzeinheit.ID)
|
|
Geraetenummer (longtext) — device number/serial
|
|
Bezeichnung (longtext) — device name/label
|
|
Beschreibung (longtext) — description
|
|
ArtikelID (FK), NutzergruppenID (FK), Einheit (int)
|
|
Einbaudatum, Ausbaudatum, GeeichtBis, GeeichtAm, ErstInbetriebnahme, DefektAb (datetime)
|
|
FirmwareVersion, LaufendeNummer, GruppenKennung, Memo, AllgemeinesMemo (longtext)
|
|
AnsprechpartnerID, ZugeordneterRaumID, CustomStatusKeyID (FK)
|
|
Gemietet, Gewartet, KeinAndruck, IstAbzuziehendesGeraet, HatHistorie (bool)
|
|
|
|
=== geraeteverbraeuche (1.3M rows) — consumption readings ===
|
|
PK: ID (int)
|
|
GeraetID (FK → geraete.ID)
|
|
Ablesedatum (datetime) — reading date
|
|
Ablesung (double) — meter reading value
|
|
Verbrauch (double) — consumption value
|
|
Faktor (double) — factor
|
|
Aenderungsdatum (datetime)
|
|
AbleseartID (FK), Schaetzung (int), Status (int)
|
|
IstRekonstruiert (bool), Herkunft (int)
|
|
ManuellerWert (double), Rohablesung (double)
|
|
Anmerkung, Fehler, Ampullenfarbe (longtext)
|
|
|
|
=== auftraege (2960 rows) — billing work orders ===
|
|
PK: ID (int)
|
|
AuftragNummer, Bezeichnung (longtext)
|
|
ErstellDatum, Abgeschlossen (datetime)
|
|
ZugeordneteAbrechnungsinformationID (FK → abrechnungsinformationen.ID)
|
|
ErstellMitarbeiterID (FK), AuftragsTyp (int), Status (int)
|
|
Anmerkung, ObererText, UntererText (longtext)
|
|
|
|
=== auftragspositionen (5094 rows) — line items per work order ===
|
|
PK: ID (int)
|
|
AuftragID (FK → auftraege.ID)
|
|
ArtikelID (FK → artikel.ID)
|
|
SollMenge, IstMenge (int)
|
|
ZugeordneterGeraeteArtikelID (FK), ZugeordneteVertragPositionID (FK)
|
|
|
|
=== artikelposition (70164 rows) — billing line items with prices ===
|
|
PK: ID (int)
|
|
ZugewiesenerArtikelID (FK → artikel.ID)
|
|
ZugewieseneAbrechnungID (FK → abrechnungsinformationen.ID)
|
|
RechnungID (FK → rechnung.ID)
|
|
MengeVorgabe, Menge (decimal), NettoVorgabe, Netto (decimal), MWST (decimal)
|
|
Rechnungsart (int), VorschussBerechnung (bool), ARechnung (bool)
|
|
VerstecktInNebenkostenID (FK), ZugeordneteVertragPositionID (FK)
|
|
|
|
=== artikel (1078 rows) — service/product catalog ===
|
|
PK: ID (int)
|
|
Artikelnummer, Bezeichnung (longtext)
|
|
Netto (decimal), MWST (decimal)
|
|
BerechnungsZiel (int), UmlageIn (int)
|
|
ZugeordnetePreislisteID (FK)
|
|
IstStandard, ARechnung, AppZusatz, IstEigenKostenpos (bool)
|
|
|
|
=== rechnung (7356 rows) — invoices ===
|
|
PK: ID (int)
|
|
Rechnungsnummer (longtext), Rechnungsart (int)
|
|
BezahltAm (datetime), BezahlterBetrag (decimal)
|
|
Druckdatum, Erstelldatum, Exportdatum (datetime)
|
|
AbrechnungsinformationID (FK → abrechnungsinformationen.ID)
|
|
AbschlagSummeSonder, AbschlagSummeStandard (decimal)
|
|
Bankeinzug (bool)
|
|
|
|
=== abrechnungsinformationen (4261 rows) — billing periods/settings ===
|
|
PK: ID (int)
|
|
Von, Bis (datetime) — billing period
|
|
AbrechnungHeizung, AbrechnungWarmwasser, AbrechnungNebenkosten, AbrechnungKaltwasser (bool)
|
|
Tarifabrechnung, BHKW, HeizsaldoInNebenkosten, AbrechnungLegionellen, AbrechnungRauchmelder (bool)
|
|
|
|
=== nebenkosten (42209 rows) — ancillary cost items ===
|
|
PK: ID (int)
|
|
Von, Bis (datetime)
|
|
Bezeichnung (longtext), Mwst (decimal), Brutto (decimal)
|
|
EinheitDerKostenart (longtext), Umlage (int), UmlageZiel (int)
|
|
ZugeordnetesObjektID (FK → objekte.ID)
|
|
NurEigentuemer, NurNutzer (bool)
|
|
|
|
=== vorauszahlungen (83932 rows) — advance payments per tenant ===
|
|
PK: ID (int)
|
|
ZugeordneterNutzerID (FK → nutzer.ID)
|
|
BetragNebenkosten, BetragHeizkosten, BetragWarmwasser (decimal)
|
|
Von, Bis (datetime), IstNetto (bool)
|
|
|
|
=== heizbetriebskosten (22557 rows) — heating operation costs ===
|
|
PK: ID (int)
|
|
Von, Bis (datetime), Bezeichnung (longtext)
|
|
Mwst (decimal), Brutto (decimal), Art (int)
|
|
ZugeordnetesObjektID (FK → objekte.ID)
|
|
ZugeordneteVerbrauchsgruppeID (FK)
|
|
|
|
=== brennstofflieferungen (6477 rows) — fuel deliveries ===
|
|
PK: ID (int)
|
|
GeliefertAm (datetime), Menge (decimal), Betrag (decimal)
|
|
Mwst (decimal), Heizwert (decimal)
|
|
Anfangsstand, Endstand (decimal)
|
|
ZugeordneterEnergieVerwerterID (FK), BrennstoffMediumID (FK)
|
|
ZugeordneteAbrechnungsinformationID (FK → abrechnungsinformationen.ID)
|
|
|
|
=== vertragpositionen (4395 rows) — contract line items ===
|
|
PK: ID (int)
|
|
LaufzeitVon, LaufzeitBis (datetime)
|
|
Menge (decimal), Gesamtpreis (decimal), PreisProEinheit (decimal), Mwst (decimal)
|
|
ArtikelID (FK → artikel.ID), VertragNummer (longtext)
|
|
Art (int), Umlage (int)
|
|
|
|
JOIN PATTERNS (use exactly):
|
|
Kunde → Objekte: JOIN objektkunde ok ON ok.KundeID = k.ID JOIN objekte o ON o.ID = ok.ObjektID
|
|
Objekt → Adresse: JOIN objektadressen oa ON oa.ObjektID = o.ID JOIN adressen a ON a.ID = oa.AdresseID
|
|
Kunde → Adresse: JOIN kundenadressen ka ON ka.KundeID = k.ID JOIN adressen a ON a.ID = ka.AdresseID
|
|
Objekt → NE: JOIN nutzeinheit ne ON ne.ObjektID = o.ID
|
|
NE → Nutzer: JOIN nutzer nu ON nu.NutzeinheitID = ne.ID
|
|
NE → Geraete: JOIN geraete g ON g.NutzeinheitID = ne.ID
|
|
Geraet → Verbrauch: JOIN geraeteverbraeuche gv ON gv.GeraetID = g.ID
|
|
Auftrag → Positionen: JOIN auftragspositionen ap ON ap.AuftragID = a.ID
|
|
Auftrag → Abrechnung: JOIN abrechnungsinformationen ai ON ai.ID = a.ZugeordneteAbrechnungsinformationID
|
|
Artikelpos → Artikel: JOIN artikel art ON art.ID = ap.ZugewiesenerArtikelID
|
|
Artikelpos → Rechnung: JOIN rechnung r ON r.ID = ap.RechnungID
|
|
Artikelpos → Abrechnung: JOIN abrechnungsinformationen ai ON ai.ID = ap.ZugewieseneAbrechnungID
|
|
Nebenkosten → Objekt: JOIN objekte o ON o.ID = nk.ZugeordnetesObjektID
|
|
Vorauszahlung → Nutzer: JOIN nutzer nu ON nu.ID = vz.ZugeordneterNutzerID
|
|
|
|
RULES:
|
|
- For tables listed above: use ONLY the listed column names. Never guess.
|
|
- For tables NOT listed above: use SELECT * with LIMIT to discover columns.
|
|
- If a query fails, the retry system will show you the error. Fix the column name and try again.
|
|
- Always LIMIT large queries (max 50 rows).
|
|
- Use LEFT JOIN when results might be empty."""
|