mirror of
http://100.103.83.12:3003/fegger/pv-agent.git
synced 2026-09-17 16:06:23 +00:00
feat(agent): support cost questions and thinking fallback
This commit is contained in:
+66
-10
@@ -13,6 +13,8 @@ import re
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
|
||||
from .normalize import normalize_text
|
||||
|
||||
YEAR_RE = re.compile(r"\b(?:19|20)\d{2}\b")
|
||||
JSON_RE = re.compile(r"\{.*\}", re.DOTALL)
|
||||
|
||||
@@ -20,9 +22,30 @@ AGGREGATION_RE = re.compile(
|
||||
r"neuerungen|übersicht|zusammenfassung|alle\s|übersicht", re.I
|
||||
)
|
||||
COMPARISON_RE = re.compile(r"unterschied|vergleic|\bbzw\.|\bsowie\b", re.I)
|
||||
|
||||
# Muster gelten auf normalize_text(question) (NFKD-Folding). Neben dem
|
||||
# gefoldeten "gunstig/losung" decken die Alternativen auch die ue-Schreibweise
|
||||
# ab ("guenstigste loesung"), die Nutzer häufig ohne Umlaute eingeben.
|
||||
DECISION_SUPPORT_RE = re.compile(
|
||||
r"günstig(?:ste|er|e)?\s+lösung|abgabenbegünstigt|steuerfrei.*(?:auszahl|präm)|"
|
||||
r"(?:zusätzlich|extra).*(?:auszahl|präm)",
|
||||
r"gu(e)?nstig(e|er|ste)?\s+lo(e)?sung"
|
||||
r"|abgabenbegu(e)?nstigt"
|
||||
r"|steuerfrei.*(?:auszahl|pra(e)?m)"
|
||||
r"|(?:zusa(e)?tzl?ich|extra).*(?:auszahl|pra(e)?m)"
|
||||
r"|\beinmalig(e|n|r|s)?\b.{0,80}?\bauszah"
|
||||
r"|\bbar\s+auszah|\bbarauszah"
|
||||
r"|\b(?:wie\s*viel|wieviel)\b.{0,60}?\bkostet\b"
|
||||
r"|\bkostet\s+mich\b"
|
||||
r"|\bmitarbeiterpra(e)?m",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
BROAD_RE = re.compile(
|
||||
r"gu(e)?nstig|m(?:o|e)?glichkeiten|\bstatt\b|alternativ", re.IGNORECASE
|
||||
)
|
||||
COST_INTENT_RE = re.compile(
|
||||
r"\b(?:wie\s*viel|wieviel)\b.{0,60}?\bkostet\b"
|
||||
r"|\bkostet\s+mich\b"
|
||||
r"|\beinmalig(e|n|r|s)?\b.{0,80}?\bauszah"
|
||||
r"|\bbar\s+auszah|\bbarauszah",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
@@ -59,8 +82,12 @@ class SubQuery:
|
||||
|
||||
|
||||
def is_decision_support(question: str) -> bool:
|
||||
"""Erkennt Gestaltungsfragen, die den deterministischen Plan benötigen."""
|
||||
return bool(DECISION_SUPPORT_RE.search(question))
|
||||
"""Erkennt Gestaltungsfragen, die den deterministischen Plan benötigen.
|
||||
|
||||
Die Frage wird vorher normalisiert, damit auch Schreibweisen ohne Umlaute
|
||||
("guenstigste loesung") erkannt werden.
|
||||
"""
|
||||
return bool(DECISION_SUPPORT_RE.search(normalize_text(question)))
|
||||
|
||||
|
||||
def should_plan(question: str) -> bool:
|
||||
@@ -97,6 +124,7 @@ def decision_support_plan(
|
||||
"""
|
||||
if not is_decision_support(question):
|
||||
return None
|
||||
folded = normalize_text(question)
|
||||
explicit_year = YEAR_RE.search(question)
|
||||
year = explicit_year.group(0) if explicit_year else default_year
|
||||
year_text = f" {year}" if year else ""
|
||||
@@ -126,13 +154,41 @@ def decision_support_plan(
|
||||
stand_year=year,
|
||||
scope="gesetz",
|
||||
)
|
||||
broad = bool(
|
||||
re.search(r"günstig|möglichkeiten|statt|alternative", question, re.IGNORECASE)
|
||||
# Kostenaufstellung: einmalige Barzahlung -> Lohnsteuer-Behandlung der
|
||||
# Bezüge plus Arbeitgeberbelastung (SV-Beitragssätze, DB/DZ, KommSt).
|
||||
cost_tax = SubQuery(
|
||||
text=(
|
||||
"einmalige Bezüge Sonderzahlungen Lohnsteuer Jahressechstel "
|
||||
"6 Prozent steuerfrei 620 Freibetrag Zuwendung EStG"
|
||||
),
|
||||
stand_year=year,
|
||||
scope="gesetz",
|
||||
)
|
||||
# Ein Gestaltungsvergleich ist kein Korpus-Survey: Die drei gezielten
|
||||
# Queries passen in den normalen Antwortpfad und vermeiden unnötiges
|
||||
# Map-Reduce samt zusätzlicher Latenz/Fehlverweigerungsrisiko.
|
||||
return ([direct, cash_and_benefits, purpose_bound] if broad else [direct]), "specific"
|
||||
cost_lnk = SubQuery(
|
||||
text=(
|
||||
"Beitragssätze des jeweiligen Jahres für Dienstnehmer "
|
||||
"Dienstgeberanteil Sozialversicherungsbeiträge DB DZ Zuschlag "
|
||||
"zum Dienstgeberbeitrag Kommunalsteuer Lohnnebenkosten Zuwendung"
|
||||
),
|
||||
stand_year=year,
|
||||
scope="gesetz",
|
||||
)
|
||||
broad = bool(BROAD_RE.search(folded))
|
||||
cost_intent = bool(COST_INTENT_RE.search(folded))
|
||||
queries = [direct]
|
||||
if broad and not cost_intent:
|
||||
# Ein Gestaltungsvergleich ist kein Korpus-Survey: Die gezielten
|
||||
# Queries passen in den normalen Antwortpfad und vermeiden unnötiges
|
||||
# Map-Reduce samt zusätzlicher Latenz/Fehlverweigerungsrisiko.
|
||||
queries += [cash_and_benefits, purpose_bound]
|
||||
elif broad:
|
||||
# Explizite Kostenaufstellung: Die Zukunftssicherungs-Query verdrängt
|
||||
# bei Slots für vier Queries die Beitrags-/Lohnsteuerquellen; die
|
||||
# zweckgebundenen Alternativen bleiben über purpose_bound abgedeckt.
|
||||
queries.append(purpose_bound)
|
||||
if cost_intent:
|
||||
queries += [cost_tax, cost_lnk]
|
||||
return queries[:4], "specific"
|
||||
|
||||
|
||||
def parse_plan(raw: str, original: str) -> tuple[list[SubQuery], str]:
|
||||
|
||||
Reference in New Issue
Block a user