From 1649a046d2872191c4b0274944592eb17906aa1c Mon Sep 17 00:00:00 2001 From: Team3 Date: Sun, 7 Jun 2026 12:18:07 +0200 Subject: [PATCH] update --- backend/routes.py | 42 ++++++++++++++++++++---- frontend/src/App.vue | 8 +++-- frontend/src/api.js | 5 +++ frontend/src/components/TopicSidebar.vue | 16 ++++++--- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/backend/routes.py b/backend/routes.py index 7bd0905..3b363b2 100644 --- a/backend/routes.py +++ b/backend/routes.py @@ -46,10 +46,31 @@ async def get_topics(): return db_topics + sorted(derived - set(db_topics)) -# Lernschulden-Regel: JE Format (MiniGuide/Guide/FullGuide) höchstens 5 erstellte, -# aber nicht absolvierte Guides. Darüber sind nur Neu-Generierungen bereits -# erstellter Guides erlaubt. Themen, Bausteine und OnePager sind unbegrenzt. -MAX_OFFENE_GUIDES = 5 +# Lernschulden-Regeln (nur Neu-Erstellungen; Themen, Bausteine, OnePager unbegrenzt): +# - JE Format (MiniGuide/Guide/FullGuide) höchstens 3 erstellte, nicht absolvierte Guides +# - Progression pro Thema: Guide erst nach absolviertem MiniGuide, FullGuide erst nach absolviertem Guide +MAX_OFFENE_GUIDES = 3 +VORSTUFE = {"Guide": "MiniGuide", "FullGuide": "Guide"} + + +async def _ist_absolviert(topic: str, fmt: str) -> bool: + """Alle Kapitel des neuesten fertigen Guides (Thema+Format) abgehakt?""" + neueste = None + for g in await list_guides(): + if g["topic"] == topic and g["format"] == fmt and g["status"] == "done": + if neueste is None or g["created_at"] > neueste["created_at"]: + neueste = g + if neueste is None: + return False + path = guide_content_path(topic, fmt) + if not path.exists(): + return False + try: + chapters = json.loads(path.read_text(encoding="utf-8")).get("chapters", []) + except ValueError: + return False + titles = {c.get("title") for c in chapters} + return bool(titles) and titles <= set(await list_progress(neueste["id"])) async def _formate_stats() -> dict: @@ -88,6 +109,12 @@ async def get_stats(): return {"themen": len(themen), "formate": await _formate_stats()} +@router.get("/topics/fortschritt") +async def topic_fortschritt(topic: str): + """Absolviert-Status pro Format — fürs Freischalten der nächsten Ausbaustufe.""" + return {fmt: await _ist_absolviert(topic, fmt) for fmt in ("MiniGuide", "Guide", "FullGuide")} + + @router.post("/topics") async def add_topic(req: TopicCreateRequest): await create_topic(req.name.strip()) @@ -169,10 +196,13 @@ async def create(req: GuideCreateRequest): for g in await list_guides(): if g["topic"] == req.topic.strip() and g["format"] == req.format and g["status"] in ("queued", "generating"): raise HTTPException(409, "Generierung läuft bereits") - # Lernschulden-Regel: neue Guides nur, wenn das Format weniger als 5 offene hat (erstellt, nicht absolviert). - # Resume (Schritt-Dateien vorhanden) ist ausgenommen — der Guide wurde bereits angefangen. + # Lernschulden-Regeln — nur für Neu-Erstellungen; Resume (Schritt-Dateien + # vorhanden) und Neu-Generieren bestehender Guides sind ausgenommen. content = guide_content_path(req.topic.strip(), req.format) if req.format != "OnePager" and not content.exists() and not guide_slot_dateien(content): + vorstufe = VORSTUFE.get(req.format) + if vorstufe and not await _ist_absolviert(req.topic.strip(), vorstufe): + raise HTTPException(409, f"Erst den {vorstufe} dieses Themas absolvieren") stat = (await _formate_stats()).get(req.format, {"erstellt": 0, "absolviert": 0}) offen = stat["erstellt"] - stat["absolviert"] if offen >= MAX_OFFENE_GUIDES: diff --git a/frontend/src/App.vue b/frontend/src/App.vue index fbb0fea..b83e77b 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,6 +1,6 @@