feat: Schlagwörter je Kategorie + auf-/zuklappbares Kategorie-Dropdown
- Manuelle Schlagwörter je Kategorie (Popup mit Chips, Doppelklick zum Bearbeiten) - Treffer über Empfänger ODER Verwendungszweck; längstes Schlagwort gewinnt; Kind-Filter (Ausgabe/Einnahme); wirken nur bei künftigen Importen - Lernende Korrektur-Erkennung: ab 5 Anwendungen und >= 1/3 Korrekturen erscheint eine Prüf-Meldung in den Einstellungen (Behalten/Entfernen) - Neue CategorySelect-Komponente (Baum + Suche, per Portal) ersetzt die nativen optgroup-Selects in Buchungen und Import-Prüfansicht - Migration 0006 (source/applied_count/overridden_count/confirmed, match_field any) - Endpunkte, i18n DE/EN, Tests (test_keywords: 7 neu, gesamt 27 grün) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+18
-1
@@ -94,6 +94,12 @@ class AmountMode(str, enum.Enum):
|
||||
class MatchField(str, enum.Enum):
|
||||
payee = "payee"
|
||||
purpose = "purpose"
|
||||
any = "any" # trifft, wenn das Muster im Empfänger ODER im Zweck vorkommt
|
||||
|
||||
|
||||
class RuleSource(str, enum.Enum):
|
||||
learned = "learned" # automatisch aus bestätigten Zuordnungen gelernt
|
||||
manual = "manual" # vom Nutzer als Schlagwort angelegt
|
||||
|
||||
|
||||
class ImportStatus(str, enum.Enum):
|
||||
@@ -466,7 +472,13 @@ class ImportBatch(Base):
|
||||
|
||||
|
||||
class CategorizationRule(Base):
|
||||
"""Lernende Regel: Empfänger/Zweck enthält Muster -> Kategorie (oder Transfer)."""
|
||||
"""Regel: Empfänger/Zweck enthält Muster -> Kategorie.
|
||||
|
||||
Zwei Quellen (``source``): ``learned`` wird beim Import automatisch aus
|
||||
bestätigten Zuordnungen gelernt; ``manual`` legt der Nutzer als Schlagwort je
|
||||
Kategorie an. ``applied_count``/``overridden_count`` zählen, wie oft die Regel
|
||||
zutraf und wie oft ihr Vorschlag wegkorrigiert wurde – daraus wird ein
|
||||
manuelles Schlagwort ggf. als „stimmt vielleicht nicht" markiert."""
|
||||
|
||||
__tablename__ = "categorization_rules"
|
||||
|
||||
@@ -479,8 +491,13 @@ class CategorizationRule(Base):
|
||||
category_id: Mapped[int | None] = mapped_column(
|
||||
ForeignKey("categories.id", ondelete="CASCADE"), nullable=True
|
||||
)
|
||||
source: Mapped[RuleSource] = mapped_column(_enum(RuleSource), nullable=False, default=RuleSource.learned)
|
||||
priority: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
hits: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
applied_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
overridden_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
# „Behalten, nicht mehr fragen" – unterdrückt das Flaggen trotz Korrekturen.
|
||||
confirmed: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now)
|
||||
|
||||
__table_args__ = (
|
||||
|
||||
Reference in New Issue
Block a user