.vault-tools (ohne trading-plattform-pruefung und watermarks-remover), .claude/agents, .claude/skills, .claude/workflows, .claude/settings.json. Co-Authored-By: Claude Opus 5.5 <[email protected]>
143 lines
3.9 KiB
Python
143 lines
3.9 KiB
Python
"""Drei-Wege-Vergleich zwischen todo.md, Plane und dem Stand des letzten Laufs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
ANLEGEN = "ANLEGEN"
|
|
NACH_PLANE = "NACH_PLANE"
|
|
NACH_VAULT = "NACH_VAULT"
|
|
KONFLIKT = "KONFLIKT"
|
|
ANHAENGEN = "ANHAENGEN"
|
|
ZEILE_ENTFERNT = "ZEILE_ENTFERNT"
|
|
ITEM_ENTFERNT = "ITEM_ENTFERNT"
|
|
UNVERAENDERT = "UNVERAENDERT"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Aufgabe:
|
|
"""Eine Checkbox-Zeile aus todo.md."""
|
|
|
|
zeilennummer: int
|
|
text: str
|
|
erledigt: bool
|
|
marker: str | None
|
|
einrueckung: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class WorkItem:
|
|
id: str
|
|
marker: str
|
|
titel: str
|
|
erledigt: bool
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Basiseintrag:
|
|
marker: str
|
|
work_item_id: str
|
|
titel: str
|
|
erledigt: bool
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Aktion:
|
|
art: str
|
|
marker: str | None
|
|
vault: Aufgabe | None = None
|
|
plane: WorkItem | None = None
|
|
basis: Basiseintrag | None = None
|
|
|
|
|
|
ERLEDIGT_GRUPPEN = ("completed", "cancelled")
|
|
ZIEL_GRUPPE_ERLEDIGT = "completed"
|
|
ZIEL_GRUPPE_OFFEN = "unstarted"
|
|
|
|
|
|
def status_fuer(states: list[dict], erledigt: bool) -> str:
|
|
"""Erste State-UUID der Zielgruppe, sortiert nach sequence."""
|
|
gruppe = ZIEL_GRUPPE_ERLEDIGT if erledigt else ZIEL_GRUPPE_OFFEN
|
|
passend = [s for s in states if s.get("group") == gruppe]
|
|
if not passend:
|
|
raise ValueError(
|
|
f"Das Projekt hat keinen Status der Gruppe „{gruppe}“. "
|
|
f"Vorhanden sind: {sorted({s.get('group') for s in states})}."
|
|
)
|
|
return min(passend, key=lambda s: s.get("sequence", 0))["id"]
|
|
|
|
|
|
def item_erledigt(state_gruppe: str) -> bool:
|
|
return state_gruppe in ERLEDIGT_GRUPPEN
|
|
|
|
|
|
def _abweichung(titel_a: str, erledigt_a: bool, titel_b: str, erledigt_b: bool) -> bool:
|
|
return titel_a != titel_b or erledigt_a != erledigt_b
|
|
|
|
|
|
def berechne(
|
|
aufgaben: list[Aufgabe],
|
|
work_items: list[WorkItem],
|
|
basis: dict[str, Basiseintrag],
|
|
) -> list[Aktion]:
|
|
"""Drei-Wege-Vergleich. Ruft nichts auf und schreibt nichts."""
|
|
aktionen: list[Aktion] = []
|
|
items_nach_marker = {item.marker: item for item in work_items}
|
|
gesehene_marker: set[str] = set()
|
|
|
|
for aufgabe in aufgaben:
|
|
if aufgabe.marker is None:
|
|
aktionen.append(Aktion(art=ANLEGEN, marker=None, vault=aufgabe))
|
|
continue
|
|
|
|
gesehene_marker.add(aufgabe.marker)
|
|
item = items_nach_marker.get(aufgabe.marker)
|
|
eintrag = basis.get(aufgabe.marker)
|
|
|
|
if item is None:
|
|
aktionen.append(
|
|
Aktion(art=ITEM_ENTFERNT, marker=aufgabe.marker, vault=aufgabe, basis=eintrag)
|
|
)
|
|
continue
|
|
|
|
if eintrag is None:
|
|
# Ohne Basis lässt sich nicht entscheiden, wer sich bewegt hat.
|
|
art = (
|
|
UNVERAENDERT
|
|
if not _abweichung(aufgabe.text, aufgabe.erledigt, item.titel, item.erledigt)
|
|
else KONFLIKT
|
|
)
|
|
aktionen.append(
|
|
Aktion(art=art, marker=aufgabe.marker, vault=aufgabe, plane=item)
|
|
)
|
|
continue
|
|
|
|
vault_geaendert = _abweichung(
|
|
aufgabe.text, aufgabe.erledigt, eintrag.titel, eintrag.erledigt
|
|
)
|
|
plane_geaendert = _abweichung(
|
|
item.titel, item.erledigt, eintrag.titel, eintrag.erledigt
|
|
)
|
|
|
|
if vault_geaendert and plane_geaendert:
|
|
art = KONFLIKT
|
|
elif vault_geaendert:
|
|
art = NACH_PLANE
|
|
elif plane_geaendert:
|
|
art = NACH_VAULT
|
|
else:
|
|
art = UNVERAENDERT
|
|
|
|
aktionen.append(
|
|
Aktion(art=art, marker=aufgabe.marker, vault=aufgabe, plane=item, basis=eintrag)
|
|
)
|
|
|
|
for item in work_items:
|
|
if item.marker in gesehene_marker:
|
|
continue
|
|
eintrag = basis.get(item.marker)
|
|
art = ZEILE_ENTFERNT if eintrag else ANHAENGEN
|
|
aktionen.append(Aktion(art=art, marker=item.marker, plane=item, basis=eintrag))
|
|
|
|
return aktionen
|