update
This commit is contained in:
@@ -111,6 +111,13 @@ async def init_db():
|
|||||||
await db.execute("ALTER TABLE baustein_progress ADD COLUMN streak INTEGER NOT NULL DEFAULT 0")
|
await db.execute("ALTER TABLE baustein_progress ADD COLUMN streak INTEGER NOT NULL DEFAULT 0")
|
||||||
except aiosqlite.OperationalError:
|
except aiosqlite.OperationalError:
|
||||||
pass
|
pass
|
||||||
|
# Offene-Frage-Anker: Basis/Streak VOR der aktuell offenen Frage — macht die Bewertung
|
||||||
|
# serverseitig idempotent (Re-Bewertung) und driftfrei über Fragen hinweg.
|
||||||
|
for _spalte, _typ in (("offene_frage", "TEXT"), ("offene_basis", "INTEGER"), ("offene_streak", "INTEGER")):
|
||||||
|
try:
|
||||||
|
await db.execute(f"ALTER TABLE baustein_progress ADD COLUMN {_spalte} {_typ}")
|
||||||
|
except aiosqlite.OperationalError:
|
||||||
|
pass
|
||||||
# Migration: alte vertiefungen-Tabelle → baustein_texte (Bestand = lange Form, art 'deepdive')
|
# Migration: alte vertiefungen-Tabelle → baustein_texte (Bestand = lange Form, art 'deepdive')
|
||||||
cursor = await db.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'vertiefungen'")
|
cursor = await db.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'vertiefungen'")
|
||||||
if await cursor.fetchone():
|
if await cursor.fetchone():
|
||||||
@@ -321,6 +328,38 @@ async def list_baustein_progress(topic: str) -> list[dict]:
|
|||||||
return [{"baustein": b, "gute_antworten": n, "streak": s, "absolviert": a, "verstanden": v, "gemeistert": m} for b, n, s, a, v, m in rows]
|
return [{"baustein": b, "gute_antworten": n, "streak": s, "absolviert": a, "verstanden": v, "gemeistert": m} for b, n, s, a, v, m in rows]
|
||||||
|
|
||||||
|
|
||||||
|
async def get_baustein_progress(topic: str, baustein: str) -> dict:
|
||||||
|
"""Eine Baustein-Zeile inkl. Offene-Frage-Anker. Defaults, falls noch keine existiert."""
|
||||||
|
db = await get_db()
|
||||||
|
cursor = await db.execute(
|
||||||
|
"SELECT gute_antworten, streak, absolviert, verstanden, gemeistert, "
|
||||||
|
"offene_frage, offene_basis, offene_streak FROM baustein_progress "
|
||||||
|
"WHERE topic = ? AND baustein = ?",
|
||||||
|
(topic, baustein),
|
||||||
|
)
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
if row is None:
|
||||||
|
return {"gute_antworten": 0, "streak": 0, "absolviert": None, "verstanden": None,
|
||||||
|
"gemeistert": None, "offene_frage": None, "offene_basis": None, "offene_streak": None}
|
||||||
|
return {"gute_antworten": row[0], "streak": row[1], "absolviert": row[2], "verstanden": row[3],
|
||||||
|
"gemeistert": row[4], "offene_frage": row[5], "offene_basis": row[6], "offene_streak": row[7]}
|
||||||
|
|
||||||
|
|
||||||
|
async def set_offene_frage(topic: str, baustein: str, frage: str, basis: int, streak: int) -> None:
|
||||||
|
"""Friert Basis + Streak VOR der jetzt offenen Frage ein (Anker für idempotente Re-Bewertung)."""
|
||||||
|
db = await get_db()
|
||||||
|
now = _now()
|
||||||
|
await db.execute(
|
||||||
|
"""INSERT INTO baustein_progress (topic, baustein, offene_frage, offene_basis, offene_streak, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT(topic, baustein) DO UPDATE SET
|
||||||
|
offene_frage = excluded.offene_frage, offene_basis = excluded.offene_basis,
|
||||||
|
offene_streak = excluded.offene_streak, updated_at = excluded.updated_at""",
|
||||||
|
(topic, baustein, frage, basis, streak, now),
|
||||||
|
)
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
|
||||||
async def set_baustein_score(topic: str, baustein: str, score: int) -> int:
|
async def set_baustein_score(topic: str, baustein: str, score: int) -> int:
|
||||||
"""Setzt den Score absolut (vom Aufrufer geclampt) und liefert ihn zurück."""
|
"""Setzt den Score absolut (vom Aufrufer geclampt) und liefert ihn zurück."""
|
||||||
db = await get_db()
|
db = await get_db()
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ def _doppelfrage_mangel(frage: str) -> str | None:
|
|||||||
|
|
||||||
async def _frage_mit_kritik(
|
async def _frage_mit_kritik(
|
||||||
topic: str, baustein: str, section_block: str, kompakt_block: str,
|
topic: str, baustein: str, section_block: str, kompakt_block: str,
|
||||||
transcript: str, provider: str,
|
transcript: str, vermeide_block: str, provider: str,
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""Frage generieren, vom Kritiker prüfen lassen, bei Mängeln neu (max KRITIK_MAX_RUNDEN)."""
|
"""Frage generieren, vom Kritiker prüfen lassen, bei Mängeln neu (max KRITIK_MAX_RUNDEN)."""
|
||||||
kritik_block = "(keine)"
|
kritik_block = "(keine)"
|
||||||
@@ -182,7 +182,8 @@ async def _frage_mit_kritik(
|
|||||||
data = await _gen_call(
|
data = await _gen_call(
|
||||||
"Baustein-Frage", "fast", _frage_schema, provider,
|
"Baustein-Frage", "fast", _frage_schema, provider,
|
||||||
topic=topic, baustein=baustein, section_block=section_block,
|
topic=topic, baustein=baustein, section_block=section_block,
|
||||||
kompakt_block=kompakt_block, transcript=transcript, kritik_block=kritik_block,
|
kompakt_block=kompakt_block, transcript=transcript, vermeide_block=vermeide_block,
|
||||||
|
kritik_block=kritik_block,
|
||||||
)
|
)
|
||||||
if data is None:
|
if data is None:
|
||||||
return None
|
return None
|
||||||
@@ -190,7 +191,7 @@ async def _frage_mit_kritik(
|
|||||||
probleme = await _kritik_call(
|
probleme = await _kritik_call(
|
||||||
"Baustein-Frage-Kritik", provider, role="guide", # starke KI prüft die Regeln
|
"Baustein-Frage-Kritik", provider, role="guide", # starke KI prüft die Regeln
|
||||||
topic=topic, baustein=baustein, section_block=section_block,
|
topic=topic, baustein=baustein, section_block=section_block,
|
||||||
kompakt_block=kompakt_block, transcript=transcript, frage=frage,
|
kompakt_block=kompakt_block, transcript=transcript, vermeide_block=vermeide_block, frage=frage,
|
||||||
)
|
)
|
||||||
hart = _doppelfrage_mangel(frage) # erzwingt Neugenerierung, auch wenn der KI-Kritiker es übersah
|
hart = _doppelfrage_mangel(frage) # erzwingt Neugenerierung, auch wenn der KI-Kritiker es übersah
|
||||||
if hart:
|
if hart:
|
||||||
@@ -242,15 +243,22 @@ def _bloecke(section: str, kompakt: str | None) -> tuple[str, str]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _vermeide_block(vermeide: list[str] | None) -> str:
|
||||||
|
eintraege = [f.strip() for f in (vermeide or []) if f and f.strip()]
|
||||||
|
return "\n".join(f"- {f}" for f in eintraege) or "(keine)"
|
||||||
|
|
||||||
|
|
||||||
async def pruefung_frage(
|
async def pruefung_frage(
|
||||||
topic: str, baustein: str, section: str, kompakt: str | None,
|
topic: str, baustein: str, section: str, kompakt: str | None,
|
||||||
messages: list[dict], provider: str = DEFAULT_PROVIDER,
|
messages: list[dict], vermeide: list[str] | None = None, provider: str = DEFAULT_PROVIDER,
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""Aktion 'frage': nächste Frage generieren (Generator + Kritiker) · None bei Fehler."""
|
"""Aktion 'frage': nächste Frage generieren (Generator + Kritiker) · None bei Fehler."""
|
||||||
try:
|
try:
|
||||||
section_block, kompakt_block = _bloecke(section, kompakt)
|
section_block, kompakt_block = _bloecke(section, kompakt)
|
||||||
transcript = _transcript(messages) if messages else "(leer)"
|
transcript = _transcript(messages) if messages else "(leer)"
|
||||||
return await _frage_mit_kritik(topic, baustein, section_block, kompakt_block, transcript, provider)
|
return await _frage_mit_kritik(
|
||||||
|
topic, baustein, section_block, kompakt_block, transcript, _vermeide_block(vermeide), provider,
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
log.warning("[%s] Frage fehlgeschlagen (%s)", topic, baustein, exc_info=True)
|
log.warning("[%s] Frage fehlgeschlagen (%s)", topic, baustein, exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -215,10 +215,10 @@ class BausteinPruefungRequest(BaseModel):
|
|||||||
section: str = Field(default="", max_length=20000) # ausführliche Fassung
|
section: str = Field(default="", max_length=20000) # ausführliche Fassung
|
||||||
section_kompakt: str = Field(default="", max_length=20000) # kompakte Fassung (Merksätze)
|
section_kompakt: str = Field(default="", max_length=20000) # kompakte Fassung (Merksätze)
|
||||||
aktion: Literal["frage", "diskussion", "antwort", "antwort_pruefen"] = "frage"
|
aktion: Literal["frage", "diskussion", "antwort", "antwort_pruefen"] = "frage"
|
||||||
frage: str = Field(default="", max_length=2000) # aktuell geprüfte Frage (für diskussion/antwort)
|
frage: str = Field(default="", max_length=2000) # aktuell geprüfte Frage (für diskussion/antwort); Anker der Basis
|
||||||
letzte_bewertung: str = Field(default="", max_length=2000) # Feedback der letzten Bewertung (Kontext für diskussion)
|
letzte_bewertung: str = Field(default="", max_length=2000) # Feedback der letzten Bewertung (Kontext für diskussion)
|
||||||
score_vor_frage: int = 0 # Score, als die Frage gestellt wurde → driftfreies (Re-)Bewerten
|
vermeide: list[str] = [] # schon gestellte + vorgemerkte Fragen — sinngemäß nicht wiederholen
|
||||||
streak: int = 0 # Folge korrekter Antworten VOR dieser Frage (Streak-Bonus)
|
# Basis/Streak werden serverseitig je Frage geführt (offene_frage-Anker) — nicht mehr vom Client.
|
||||||
tier2: bool = False # ganzer Guide absolviert (alle ≥3) → −1 bei falsch, Deckel 10
|
tier2: bool = False # ganzer Guide absolviert (alle ≥3) → −1 bei falsch, Deckel 10
|
||||||
tier3: bool = False # ganzer Guide verstanden (alle ≥10) → Meisterpfad, −2 bei falsch, Deckel 25
|
tier3: bool = False # ganzer Guide verstanden (alle ≥10) → Meisterpfad, −2 bei falsch, Deckel 25
|
||||||
messages: list[ChatMessage] = [] # Dialog bisher; leer = erste Frage
|
messages: list[ChatMessage] = [] # Dialog bisher; leer = erste Frage
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ from database import (
|
|||||||
create_topic, list_topics as db_list_topics, delete_topic,
|
create_topic, list_topics as db_list_topics, delete_topic,
|
||||||
list_progress, set_progress, delete_progress,
|
list_progress, set_progress, delete_progress,
|
||||||
create_element, list_elements, get_element, update_element, delete_element,
|
create_element, list_elements, get_element, update_element, delete_element,
|
||||||
list_baustein_progress, set_baustein_score, set_baustein_streak, set_baustein_absolviert, set_baustein_verstanden, set_baustein_gemeistert, delete_baustein_daten,
|
list_baustein_progress, get_baustein_progress, set_offene_frage,
|
||||||
|
set_baustein_score, set_baustein_streak, set_baustein_absolviert, set_baustein_verstanden, set_baustein_gemeistert, delete_baustein_daten,
|
||||||
)
|
)
|
||||||
from bausteine import generate_bausteine, cancel_bausteine, bausteine_status, active_bausteine, reset_bausteine, lade_quelle, lade_uebersicht
|
from bausteine import generate_bausteine, cancel_bausteine, bausteine_status, active_bausteine, reset_bausteine, lade_quelle, lade_uebersicht
|
||||||
from elements import generate_element, chat_with_guide, chat_with_element, check_element, style_element, refine_suggestion
|
from elements import generate_element, chat_with_guide, chat_with_element, check_element, style_element, refine_suggestion
|
||||||
@@ -233,12 +234,32 @@ async def baustein_chat_route(req: BausteinChatRequest):
|
|||||||
return {"reply": reply}
|
return {"reply": reply}
|
||||||
|
|
||||||
|
|
||||||
|
# Bewertungen je (topic, baustein) serialisieren — sonst überschreiben zwei
|
||||||
|
# gleichzeitige Bewertungen den absoluten Score mit veralteter Basis (Race).
|
||||||
|
_pruef_locks: dict[tuple[str, str], asyncio.Lock] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _pruef_lock(topic: str, baustein: str) -> asyncio.Lock:
|
||||||
|
key = (topic, baustein)
|
||||||
|
lock = _pruef_locks.get(key)
|
||||||
|
if lock is None:
|
||||||
|
lock = _pruef_locks[key] = asyncio.Lock()
|
||||||
|
return lock
|
||||||
|
|
||||||
|
|
||||||
|
def _basis(stand: dict, frage: str) -> tuple[int, int, bool]:
|
||||||
|
"""Score-/Streak-Basis VOR der Frage. Gleiche offene Frage → Re-Bewertung auf derselben
|
||||||
|
Basis (idempotent); sonst neue Frage auf dem aktuellen Stand. → (basis, streak_basis, re_bewertung)."""
|
||||||
|
re_bewertung = stand["offene_frage"] == frage and stand["offene_basis"] is not None
|
||||||
|
if re_bewertung:
|
||||||
|
streak_basis = stand["offene_streak"] if stand["offene_streak"] is not None else stand["streak"]
|
||||||
|
return stand["offene_basis"], streak_basis, True
|
||||||
|
return stand["gute_antworten"], stand["streak"], False
|
||||||
|
|
||||||
|
|
||||||
@router.post("/bausteine/pruefung", response_model=BausteinPruefungResponse)
|
@router.post("/bausteine/pruefung", response_model=BausteinPruefungResponse)
|
||||||
async def baustein_pruefung_route(req: BausteinPruefungRequest):
|
async def baustein_pruefung_route(req: BausteinPruefungRequest):
|
||||||
stand = next(
|
stand = await get_baustein_progress(req.topic, req.baustein)
|
||||||
(p for p in await list_baustein_progress(req.topic) if p["baustein"] == req.baustein),
|
|
||||||
{"gute_antworten": 0, "absolviert": None, "verstanden": None, "gemeistert": None},
|
|
||||||
)
|
|
||||||
gute = stand["gute_antworten"]
|
gute = stand["gute_antworten"]
|
||||||
absolviert = stand["absolviert"] is not None
|
absolviert = stand["absolviert"] is not None
|
||||||
verstanden = stand["verstanden"] is not None
|
verstanden = stand["verstanden"] is not None
|
||||||
@@ -247,7 +268,7 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest):
|
|||||||
msgs = [m.model_dump() for m in req.messages]
|
msgs = [m.model_dump() for m in req.messages]
|
||||||
|
|
||||||
if req.aktion == "frage":
|
if req.aktion == "frage":
|
||||||
frage = await pruefung_frage(req.topic, req.baustein, req.section, kompakt, msgs, provider=req.provider)
|
frage = await pruefung_frage(req.topic, req.baustein, req.section, kompakt, msgs, vermeide=req.vermeide, provider=req.provider)
|
||||||
if frage is None:
|
if frage is None:
|
||||||
raise HTTPException(502, "Frage fehlgeschlagen — bitte erneut versuchen")
|
raise HTTPException(502, "Frage fehlgeschlagen — bitte erneut versuchen")
|
||||||
return {"frage": frage, "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
|
return {"frage": frage, "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
|
||||||
@@ -271,27 +292,38 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest):
|
|||||||
raise HTTPException(400, "Antwort braucht eine laufende Frage")
|
raise HTTPException(400, "Antwort braucht eine laufende Frage")
|
||||||
|
|
||||||
if req.aktion == "antwort":
|
if req.aktion == "antwort":
|
||||||
# Vorschau: nur Bewerter, kein Kritiker, KEIN Persist, KEINE Meilensteine.
|
# Vorschau: nur Bewerter, kein Kritiker, KEIN Persist. Basis serverseitig (best effort).
|
||||||
data = await pruefung_bewertung_schnell(
|
data = await pruefung_bewertung_schnell(
|
||||||
req.topic, req.baustein, req.section, kompakt, req.frage, msgs, gute, provider=req.provider,
|
req.topic, req.baustein, req.section, kompakt, req.frage, msgs, gute, provider=req.provider,
|
||||||
)
|
)
|
||||||
if data is None:
|
if data is None:
|
||||||
raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen")
|
raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen")
|
||||||
score = score_berechnen(req.score_vor_frage, data["bewertung"] == "gut", req.streak, req.tier2, req.tier3, absolviert, verstanden, gemeistert)
|
basis, streak_basis, _ = _basis(stand, req.frage)
|
||||||
|
score = score_berechnen(basis, data["bewertung"] == "gut", streak_basis, req.tier2, req.tier3, absolviert, verstanden, gemeistert)
|
||||||
return {"feedback": data["feedback"], "bewertung": data["bewertung"], "gute_antworten": score,
|
return {"feedback": data["feedback"], "bewertung": data["bewertung"], "gute_antworten": score,
|
||||||
"absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
|
"absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
|
||||||
|
|
||||||
# aktion == "antwort_pruefen": verbindlich (Bewerter + Kritiker), persistiert Score + Meilensteine.
|
# aktion == "antwort_pruefen": verbindlich, persistiert. Serialisiert je Baustein, damit
|
||||||
|
# die Basis immer den geprüften Vorgänger-Stand sieht (kein Stale-Overwrite).
|
||||||
|
async with _pruef_lock(req.topic, req.baustein):
|
||||||
|
stand = await get_baustein_progress(req.topic, req.baustein) # nach evtl. Vorgänger-Persist neu lesen
|
||||||
|
absolviert = stand["absolviert"] is not None
|
||||||
|
verstanden = stand["verstanden"] is not None
|
||||||
|
gemeistert = stand["gemeistert"] is not None
|
||||||
|
basis, streak_basis, re_bewertung = _basis(stand, req.frage)
|
||||||
|
if not re_bewertung:
|
||||||
|
# Neue Frage: Basis/Streak einfrieren (Anker für spätere Re-Bewertung dieser Frage).
|
||||||
|
await set_offene_frage(req.topic, req.baustein, req.frage, basis, streak_basis)
|
||||||
|
|
||||||
data = await pruefung_bewertung(
|
data = await pruefung_bewertung(
|
||||||
req.topic, req.baustein, req.section, kompakt, req.frage, msgs, gute, provider=req.provider,
|
req.topic, req.baustein, req.section, kompakt, req.frage, msgs, basis, provider=req.provider,
|
||||||
role="guide" if req.gruendlich else "judge",
|
role="guide" if req.gruendlich else "judge",
|
||||||
)
|
)
|
||||||
if data is None:
|
if data is None:
|
||||||
raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen")
|
raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen")
|
||||||
|
|
||||||
# Score driftfrei aus dem Basis-Score rechnen (Re-Bewertung ersetzt das vorige Ergebnis).
|
|
||||||
score = score_berechnen(
|
score = score_berechnen(
|
||||||
req.score_vor_frage, data["bewertung"] == "gut", req.streak, req.tier2, req.tier3, absolviert, verstanden, gemeistert,
|
basis, data["bewertung"] == "gut", streak_basis, req.tier2, req.tier3, absolviert, verstanden, gemeistert,
|
||||||
)
|
)
|
||||||
war_absolviert, war_verstanden = absolviert, verstanden
|
war_absolviert, war_verstanden = absolviert, verstanden
|
||||||
gute = await set_baustein_score(req.topic, req.baustein, score)
|
gute = await set_baustein_score(req.topic, req.baustein, score)
|
||||||
@@ -307,7 +339,7 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest):
|
|||||||
gemeistert = True
|
gemeistert = True
|
||||||
# Streak persistieren — Reset nur am Deckel der gerade aktiven Stufe.
|
# Streak persistieren — Reset nur am Deckel der gerade aktiven Stufe.
|
||||||
streak = streak_berechnen(
|
streak = streak_berechnen(
|
||||||
req.streak, data["bewertung"] == "gut", req.tier2, req.tier3,
|
streak_basis, data["bewertung"] == "gut", req.tier2, req.tier3,
|
||||||
absolviert and not war_absolviert, verstanden and not war_verstanden,
|
absolviert and not war_absolviert, verstanden and not war_verstanden,
|
||||||
)
|
)
|
||||||
await set_baustein_streak(req.topic, req.baustein, streak)
|
await set_baustein_streak(req.topic, req.baustein, streak)
|
||||||
|
|||||||
@@ -77,13 +77,13 @@ export async function chatBaustein({ topic, baustein, section, section_kompakt =
|
|||||||
|
|
||||||
export async function pruefeBaustein({
|
export async function pruefeBaustein({
|
||||||
topic, baustein, section, section_kompakt = '', provider,
|
topic, baustein, section, section_kompakt = '', provider,
|
||||||
aktion = 'frage', frage = '', letzte_bewertung = '', score_vor_frage = 0, streak = 0,
|
aktion = 'frage', frage = '', letzte_bewertung = '', vermeide = [],
|
||||||
tier2 = false, tier3 = false, messages = [], gruendlich = false,
|
tier2 = false, tier3 = false, messages = [], gruendlich = false,
|
||||||
}) {
|
}) {
|
||||||
const res = await fetch(`${BASE}/bausteine/pruefung`, {
|
const res = await fetch(`${BASE}/bausteine/pruefung`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ topic, baustein, section, section_kompakt, aktion, frage, letzte_bewertung, score_vor_frage, streak, tier2, tier3, messages, provider, gruendlich }),
|
body: JSON.stringify({ topic, baustein, section, section_kompakt, aktion, frage, letzte_bewertung, vermeide, tier2, tier3, messages, provider, gruendlich }),
|
||||||
})
|
})
|
||||||
return jsonOrThrow(res)
|
return jsonOrThrow(res)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ const pruefMessages = slot.messages // {role, kind: 'frage'|'nachfrage'|'antwort
|
|||||||
const pruefPhase = slot.phase
|
const pruefPhase = slot.phase
|
||||||
const aktuelleFrage = slot.aktuelleFrage // ankert Bewertung/Diskussion
|
const aktuelleFrage = slot.aktuelleFrage // ankert Bewertung/Diskussion
|
||||||
const letztesFeedback = slot.letztesFeedback // Kontext für die Diskussion über eine Bewertung
|
const letztesFeedback = slot.letztesFeedback // Kontext für die Diskussion über eine Bewertung
|
||||||
const scoreVorFrage = slot.scoreVorFrage // Score, als die Frage gestellt wurde → driftfreies (Re-)Bewerten
|
|
||||||
const streakVorFrage = slot.streakVorFrage // Streak, als die Frage gestellt wurde (driftfrei)
|
|
||||||
// Transient (pro Instanz): Eingabe-Entwurf, Lade-Spinner.
|
// Transient (pro Instanz): Eingabe-Entwurf, Lade-Spinner.
|
||||||
const pruefInput = ref('')
|
const pruefInput = ref('')
|
||||||
const pruefLoading = ref(false)
|
const pruefLoading = ref(false)
|
||||||
@@ -107,12 +105,10 @@ async function pruefSenden(payload, onOk) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frage anzeigen (frisch oder vorgemerkt). scoreVorFrage = LIVE-Score zur Anzeigezeit (driftfrei).
|
// Frage anzeigen (frisch oder vorgemerkt). Basis/Streak führt der Server je Frage.
|
||||||
function frageZeigen(text) {
|
function frageZeigen(text) {
|
||||||
aktuelleFrage.value = text
|
aktuelleFrage.value = text
|
||||||
letztesFeedback.value = ''
|
letztesFeedback.value = ''
|
||||||
scoreVorFrage.value = st.value.gute_antworten
|
|
||||||
streakVorFrage.value = st.value.streak
|
|
||||||
pruefMessages.value.push({ role: 'assistant', kind: 'frage', content: text })
|
pruefMessages.value.push({ role: 'assistant', kind: 'frage', content: text })
|
||||||
pruefPhase.value = 'frage_offen'
|
pruefPhase.value = 'frage_offen'
|
||||||
}
|
}
|
||||||
@@ -134,17 +130,26 @@ function istBekannt(text) {
|
|||||||
return !n || gestellteFragen().has(n) || pool.value.some((f) => normFrage(f) === n)
|
return !n || gestellteFragen().has(n) || pool.value.some((f) => normFrage(f) === n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Schon gestellte + bereits vorgemerkte Fragen — der Generator/Kritiker soll sie
|
||||||
|
// (auch sinngemäß) meiden. Original-Texte, nicht normalisiert.
|
||||||
|
function vermeideListe() {
|
||||||
|
const gestellt = pruefMessages.value.filter((m) => m.kind === 'frage').map((m) => m.content)
|
||||||
|
return [...gestellt, ...pool.value]
|
||||||
|
}
|
||||||
|
|
||||||
async function frageEinzeln() {
|
async function frageEinzeln() {
|
||||||
const res = await pruefeBaustein({
|
const res = await pruefeBaustein({
|
||||||
topic: props.topic, baustein: props.baustein, section: props.section, section_kompakt: props.sectionKompakt,
|
topic: props.topic, baustein: props.baustein, section: props.section, section_kompakt: props.sectionKompakt,
|
||||||
provider: props.provider, aktion: 'frage', messages: pruefDialog(),
|
provider: props.provider, aktion: 'frage', messages: pruefDialog(), vermeide: vermeideListe(),
|
||||||
})
|
})
|
||||||
return res.frage
|
return res.frage
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pool parallel auf ZIEL auffüllen; jede Auflösung dedupt + füllt nach.
|
// Pool SEQUENZIELL auffüllen — je eine Generierung; jede neue sieht die vorigen
|
||||||
|
// (Verlauf + Pool) in der Vermeide-Liste und divergiert. Verhindert ähnliche Dubletten.
|
||||||
function poolFuellen() {
|
function poolFuellen() {
|
||||||
while (pool.value.length + inflight.value < POOL_ZIEL && poolMiss < 6) {
|
if (inflight.value > 0) return
|
||||||
|
if (pool.value.length >= POOL_ZIEL || poolMiss >= 6) return
|
||||||
inflight.value++
|
inflight.value++
|
||||||
frageEinzeln()
|
frageEinzeln()
|
||||||
.then((text) => {
|
.then((text) => {
|
||||||
@@ -154,7 +159,6 @@ function poolFuellen() {
|
|||||||
.catch(() => { poolMiss++ })
|
.catch(() => { poolMiss++ })
|
||||||
.finally(() => { inflight.value--; poolFuellen() })
|
.finally(() => { inflight.value--; poolFuellen() })
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Eine Frage aus dem Pool nehmen; ist er leer, auf die erste eintreffende warten.
|
// Eine Frage aus dem Pool nehmen; ist er leer, auf die erste eintreffende warten.
|
||||||
async function poolNehmen() {
|
async function poolNehmen() {
|
||||||
@@ -222,10 +226,8 @@ function bewerten(res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function antwortPayload() {
|
function antwortPayload() {
|
||||||
return {
|
// Basis/Streak führt der Server je Frage (frage = Anker). Frontend schickt sie nicht mehr.
|
||||||
aktion: 'antwort', frage: aktuelleFrage.value, score_vor_frage: scoreVorFrage.value,
|
return { aktion: 'antwort', frage: aktuelleFrage.value, tier2: props.tier2, tier3: props.tier3 }
|
||||||
streak: streakVorFrage.value, tier2: props.tier2, tier3: props.tier3,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hintergrund: voller Bewerter+Kritiker. Persistiert serverseitig; korrigiert die Anzeige bei Abweichung.
|
// Hintergrund: voller Bewerter+Kritiker. Persistiert serverseitig; korrigiert die Anzeige bei Abweichung.
|
||||||
@@ -239,8 +241,8 @@ async function verdictPruefen(gruendlich = false) {
|
|||||||
topic: props.topic, baustein: props.baustein, section: props.section, section_kompakt: props.sectionKompakt,
|
topic: props.topic, baustein: props.baustein, section: props.section, section_kompakt: props.sectionKompakt,
|
||||||
provider: props.provider, messages: pruefDialog(), ...antwortPayload(), aktion: 'antwort_pruefen', gruendlich,
|
provider: props.provider, messages: pruefDialog(), ...antwortPayload(), aktion: 'antwort_pruefen', gruendlich,
|
||||||
})
|
})
|
||||||
if (meine !== pruefenRun) return // eine neuere Bewertung läuft → diese verwerfen
|
// Server führt den Score (serialisiert, absolut) — IMMER übernehmen, auch wenn man
|
||||||
// Streak kommt verbindlich vom Backend (persistiert) und fließt über applyPruefung in st.
|
// schon bei der nächsten Frage ist. Sonst geht die Korrektur verloren.
|
||||||
applyPruefung(res)
|
applyPruefung(res)
|
||||||
if (ziel) {
|
if (ziel) {
|
||||||
if (res.bewertung !== ziel.bewertung || (res.feedback && res.feedback !== ziel.content)) {
|
if (res.bewertung !== ziel.bewertung || (res.feedback && res.feedback !== ziel.content)) {
|
||||||
@@ -250,7 +252,7 @@ async function verdictPruefen(gruendlich = false) {
|
|||||||
ziel.geprueft = true
|
ziel.geprueft = true
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
if (meine === pruefenRun && ziel) ziel.geprueft = true
|
if (ziel) ziel.geprueft = true
|
||||||
} finally {
|
} finally {
|
||||||
if (gruendlich && meine === pruefenRun) pruefLoading.value = false
|
if (gruendlich && meine === pruefenRun) pruefLoading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ export function usePruefSlot(key) {
|
|||||||
phase: ref('idle'),
|
phase: ref('idle'),
|
||||||
aktuelleFrage: ref(''),
|
aktuelleFrage: ref(''),
|
||||||
letztesFeedback: ref(''),
|
letztesFeedback: ref(''),
|
||||||
scoreVorFrage: ref(0),
|
|
||||||
streakVorFrage: ref(0),
|
|
||||||
pool: ref([]), // vorgenerierte, geprüfte, unbeantwortete Fragen
|
pool: ref([]), // vorgenerierte, geprüfte, unbeantwortete Fragen
|
||||||
inflight: ref(0), // laufende Pool-Generierungen (instanzübergreifend koordiniert)
|
inflight: ref(0), // laufende Pool-Generierungen (instanzübergreifend koordiniert)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
SECTION-AUFBAU
|
SECTION-AUFBAU
|
||||||
|
|
||||||
Jeder Baustein ist ein kleiner, eigenständiger Lern-Guide: er stellt EIN Konzept vor, erklärt es von Grund auf und macht es nutzbar. Der Leser bringt KEIN Vorwissen mit — du holst ihn ab und bringst ihm die Sache bei. Eine Section ist kein Stichwort-Zettel zum Nachschlagen.
|
Jeder Baustein ist ein kleiner, eigenständiger Lern-Guide: er stellt EIN Konzept vor, erklärt es von Grund auf und macht es nutzbar. Zielgruppe: ein Junior-Entwickler, der das Thema NEU lernt und KEIN Vorwissen mitbringt. Du holst ihn ab und bringst ihm die Sache bei. Eine Section ist kein Stichwort-Zettel zum Nachschlagen.
|
||||||
|
|
||||||
Aufbau je Baustein — drei Beats, fließend ineinander, OHNE Zwischenüberschriften:
|
Aufbau je Baustein — drei Beats, fließend ineinander, OHNE Zwischenüberschriften:
|
||||||
1. Einordnung — welche Frage beantwortet der Baustein, welches Problem löst er? Ein Satz, der den Leser abholt. Bei selbsterklärenden Bausteinen weglassen.
|
1. Einordnung (PFLICHT, der Ankerpunkt) — welches Problem löst der Baustein, wozu braucht man ihn? Knüpfe an etwas Bekanntes/Alltägliches an, bevor das Neue kommt. Ohne diesen Anker steht ein Neuling im Leeren. Nie weglassen.
|
||||||
2. Erklärung — was es ist UND wie/warum es funktioniert. Alltagssprache, von der Intuition zum Detail. Fachbegriffe beim ersten Auftreten in einem Halbsatz auflösen. Eine Analogie oder ein Bild ist erlaubt und oft besser als eine Definition.
|
2. Erklärung — was es ist UND wie/warum es funktioniert. Alltagssprache, von der Intuition zum Detail. JEDEN Fachbegriff beim ersten Auftreten in einem Halbsatz auflösen — auch Begriffe aus dem Section-Titel oder anderen Bausteinen NIE als bekannt voraussetzen. Eine Analogie oder ein Bild ist erlaubt und oft besser als eine Definition. „Wie"-Abläufe Schritt für Schritt zeigen (nicht nur das Ergebnis nennen). Kleine Beispiele/Mini-Snippets dürfen schon hier mitten im Text stehen, wo sie einen Punkt sofort greifbar machen.
|
||||||
3. Beispiel(e) — das Konzept konkret gemacht (siehe BEISPIELFORMAT).
|
3. Beispiel(e) — das Konzept konkret gemacht (siehe BEISPIELFORMAT).
|
||||||
|
|
||||||
LESBARKEIT — wichtiger als Kürze:
|
LESBARKEIT — wichtiger als Kürze:
|
||||||
@@ -16,8 +16,8 @@ LESBARKEIT — wichtiger als Kürze:
|
|||||||
|
|
||||||
LÄNGE — so lang wie nötig:
|
LÄNGE — so lang wie nötig:
|
||||||
- KEIN festes Wortlimit. Die Länge richtet sich nach der Schwierigkeit des Konzepts.
|
- KEIN festes Wortlimit. Die Länge richtet sich nach der Schwierigkeit des Konzepts.
|
||||||
- Verständnis-Test: Versteht ein Anfänger das Konzept allein aus dieser Section? Wenn nein → einfacher erklären, NICHT verdichten.
|
- Verständnis-Test: Versteht ein NEULING das Konzept allein aus dieser Section, ohne anderswo nachzulesen? Wenn nein → einen Schritt mehr erklären (Warum + Wie), NICHT verdichten. Diese Section trägt die volle Tiefe selbst — es gibt keine zweite Ausbaustufe mehr, die nachliefert.
|
||||||
- Weglassen: Füllsätze, Einleitungsfloskeln („In diesem Abschnitt…"), Wiederholungen, Fazit. Nicht jeden Randfall nennen — das Übliche erklären; Varianten in die Beispiele, mehr Tiefe in die Vertiefung.
|
- Weglassen: Füllsätze, Einleitungsfloskeln („In diesem Abschnitt…"), Wiederholungen, Fazit. Nicht jeden Randfall nennen — das Übliche erklären, seltene Varianten in die Beispiele.
|
||||||
|
|
||||||
BEISPIELFORMAT — am Thema ausrichten, nicht pauschal an Code:
|
BEISPIELFORMAT — am Thema ausrichten, nicht pauschal an Code:
|
||||||
- Code-/Tool-Thema (Sprache, Framework, CLI, Konfiguration): Codeblock mit Sprachangabe, wenige Zeilen, Minimalbeispiel.
|
- Code-/Tool-Thema (Sprache, Framework, CLI, Konfiguration): Codeblock mit Sprachangabe, wenige Zeilen, Minimalbeispiel.
|
||||||
|
|||||||
@@ -9,13 +9,16 @@ KOMPAKTE FASSUNG (Merksätze, falls vorhanden):
|
|||||||
BISHERIGER PRÜFUNGS-VERLAUF (nur frühere Fragen und Antworten):
|
BISHERIGER PRÜFUNGS-VERLAUF (nur frühere Fragen und Antworten):
|
||||||
{transcript}
|
{transcript}
|
||||||
|
|
||||||
|
BEREITS GESTELLT / SCHON VORGEMERKT (Frage darf keiner davon gleichen):
|
||||||
|
{vermeide_block}
|
||||||
|
|
||||||
ZU PRÜFENDE FRAGE:
|
ZU PRÜFENDE FRAGE:
|
||||||
{frage}
|
{frage}
|
||||||
|
|
||||||
PRÜFE GEGEN DIESE KRITERIEN:
|
PRÜFE GEGEN DIESE KRITERIEN:
|
||||||
- Stil: GENAU EINE Frage, ein einziges Fragezeichen, eine einzige Sache. Kein Mehrteiler ("und"/"sowie", "sowohl … als auch …", "nenne drei …").
|
- Stil: GENAU EINE Frage, ein einziges Fragezeichen, eine einzige Sache. Kein Mehrteiler ("und"/"sowie", "sowohl … als auch …", "nenne drei …").
|
||||||
- Kürze: maximal 1–2 Sätze, kein Szenario-Aufbau über mehrere Sätze, keine lange Vorrede.
|
- Kürze: maximal 1–2 Sätze, kein Szenario-Aufbau über mehrere Sätze, keine lange Vorrede.
|
||||||
- Keine Wiederholung einer Frage aus dem Verlauf.
|
- KEINE Wiederholung — beanstande, wenn die Frage einer aus Verlauf ODER Vermeide-Liste SINNGEMÄSS zu ähnlich ist (gleiche Kernsache, nur umformuliert).
|
||||||
- Fachlich korrekt: Die Frage muss aus dem Material oben beantwortbar sein und darf der Referenz NICHT widersprechen. Keine erfundenen Zusatzannahmen.
|
- Fachlich korrekt: Die Frage muss aus dem Material oben beantwortbar sein und darf der Referenz NICHT widersprechen. Keine erfundenen Zusatzannahmen.
|
||||||
|
|
||||||
Beanstande NUR echte Verstöße. Ist die Frage knapp, einzeln und korrekt, ist sie in Ordnung — verlange nichts darüber hinaus.
|
Beanstande NUR echte Verstöße. Ist die Frage knapp, einzeln und korrekt, ist sie in Ordnung — verlange nichts darüber hinaus.
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ KOMPAKTE FASSUNG (Merksätze, falls vorhanden):
|
|||||||
BISHERIGER PRÜFUNGS-VERLAUF (nur frühere Fragen und Antworten):
|
BISHERIGER PRÜFUNGS-VERLAUF (nur frühere Fragen und Antworten):
|
||||||
{transcript}
|
{transcript}
|
||||||
|
|
||||||
|
BEREITS GESTELLT / SCHON VORGEMERKT — auch SINNGEMÄSS NICHT wiederholen:
|
||||||
|
{vermeide_block}
|
||||||
|
|
||||||
HARTE REGELN FÜR DIE FRAGE — wichtiger als alles andere:
|
HARTE REGELN FÜR DIE FRAGE — wichtiger als alles andere:
|
||||||
- GENAU EINE Frage. Ein einziges Fragezeichen. Eine einzige Sache.
|
- GENAU EINE Frage. Ein einziges Fragezeichen. Eine einzige Sache.
|
||||||
- Maximal 1–2 Sätze. Kein Szenario-Aufbau, keine Vorrede, kein "Angenommen … und außerdem …".
|
- Maximal 1–2 Sätze. Kein Szenario-Aufbau, keine Vorrede, kein "Angenommen … und außerdem …".
|
||||||
@@ -17,7 +20,7 @@ HARTE REGELN FÜR DIE FRAGE — wichtiger als alles andere:
|
|||||||
- KEIN Faktenabruf ("welche Daten…", "wie viele…", "was enthält…"): das prüft Auswendiglernen, nicht Verständnis.
|
- KEIN Faktenabruf ("welche Daten…", "wie viele…", "was enthält…"): das prüft Auswendiglernen, nicht Verständnis.
|
||||||
- Frag nur zu dem, was der Abschnitt wirklich erklärt — nicht zu am Rand Erwähntem (ein Stichwort), das er nicht ausführt.
|
- Frag nur zu dem, was der Abschnitt wirklich erklärt — nicht zu am Rand Erwähntem (ein Stichwort), das er nicht ausführt.
|
||||||
- Passt eine Transferfrage nicht in einen Satz, wähle eine einfachere Frage.
|
- Passt eine Transferfrage nicht in einen Satz, wähle eine einfachere Frage.
|
||||||
- Wiederhole keine Frage aus dem Verlauf.
|
- Wiederhole KEINE Frage aus Verlauf oder Vermeide-Liste — auch nicht sinngemäß/umformuliert. Frag eine ANDERE Sache (anderer Aspekt, anderer Subbaustein).
|
||||||
|
|
||||||
FACHLICHE REFERENZ — WICHTIG:
|
FACHLICHE REFERENZ — WICHTIG:
|
||||||
- Die Guide-Fassung und die Vertiefung oben sind die Referenz. Deine Frage darf ihr NIE widersprechen.
|
- Die Guide-Fassung und die Vertiefung oben sind die Referenz. Deine Frage darf ihr NIE widersprechen.
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ Dir zugeteilt sind folgende Kapitel und Bausteine — verbindlich: jeder zugetei
|
|||||||
|
|
||||||
Sammle pro Baustein:
|
Sammle pro Baustein:
|
||||||
- Kernpunkte / Lernziele: was muss der Leser begreifen? Decke JEDEN gelisteten Subbaustein mit mindestens einem Kernpunkt ab. Hat ein Baustein keine Subbausteine, sammle 3–7 knappe Punkte.
|
- Kernpunkte / Lernziele: was muss der Leser begreifen? Decke JEDEN gelisteten Subbaustein mit mindestens einem Kernpunkt ab. Hat ein Baustein keine Subbausteine, sammle 3–7 knappe Punkte.
|
||||||
|
- Voraussetzungen: welche Begriffe/Ideen muss man vorher kennen, um das hier zu verstehen? Jede in einem Halbsatz erklärbar — das sind die Ankerpunkte für Neulinge.
|
||||||
|
- Typische Hürden: wo verstehen Anfänger es erfahrungsgemäß falsch oder stolpern?
|
||||||
- Belegte Fakten (Versionen, Namen, Werte) — nichts Unbelegtes; Unsicheres per Websuche prüfen.
|
- Belegte Fakten (Versionen, Namen, Werte) — nichts Unbelegtes; Unsicheres per Websuche prüfen.
|
||||||
- Eine konkrete Beispiel-Idee: was soll das Beispiel zeigen?
|
- Eine konkrete Beispiel-Idee: was soll das Beispiel zeigen?
|
||||||
- Scope: nur was DIESER Baustein hergibt. Nicht mehr, nicht weniger. Am Rand Erwähntes nicht zum Thema aufblasen.
|
- Scope: nur was DIESER Baustein hergibt. Nicht mehr, nicht weniger. Am Rand Erwähntes nicht zum Thema aufblasen.
|
||||||
@@ -16,6 +18,8 @@ Schreibe NUR die Datei {out_path} — pro Baustein ein section-Marker (Titel EXA
|
|||||||
<!-- section: Exakter Baustein-Titel -->
|
<!-- section: Exakter Baustein-Titel -->
|
||||||
- Kernpunkt …
|
- Kernpunkt …
|
||||||
- Kernpunkt …
|
- Kernpunkt …
|
||||||
|
Voraussetzung: was vorher kurz erklärt werden muss (Anker)
|
||||||
|
Hürde: typischer Anfänger-Irrtum
|
||||||
Beispiel: kurze Idee, was das Beispiel zeigt
|
Beispiel: kurze Idee, was das Beispiel zeigt
|
||||||
|
|
||||||
Die Marker-Zeile exakt so schreiben. Kein Text außerhalb der Sections, kein Fließtext-Guide.
|
Die Marker-Zeile exakt so schreiben. Kein Text außerhalb der Sections, kein Fließtext-Guide.
|
||||||
|
|||||||
@@ -12,13 +12,14 @@ Jeder Baustein bekommt ZWEI Fassungen mit DENSELBEN Inhalten (dieselben Subbaust
|
|||||||
- **ausführlich**: der zusammenhängende Anfänger-Lerntext (siehe unten).
|
- **ausführlich**: der zusammenhängende Anfänger-Lerntext (siehe unten).
|
||||||
Beide MÜSSEN dieselben Subbausteine abdecken. Nichts, das nur in einer Fassung steht.
|
Beide MÜSSEN dieselben Subbausteine abdecken. Nichts, das nur in einer Fassung steht.
|
||||||
|
|
||||||
SO SCHREIBST DU die ausführliche Fassung — EIN zusammenhängender Text, kein Stichwort-Stakkato:
|
SO SCHREIBST DU die ausführliche Fassung — EIN zusammenhängender Text für einen Junior, der das Thema NEU lernt:
|
||||||
- Schreibe pro Baustein EINEN fließenden Lern-Text laut SECTION-SPEZIFIKATION (Einordnung → Erklärung → Beispiel). Ein roter Faden, mit Übergängen.
|
- Beginne mit dem Anker (Einordnung): welches Problem/wozu, angeknüpft an etwas Bekanntes — erst dann das Neue. Pflicht, nie weglassen.
|
||||||
|
- Löse JEDEN Fachbegriff bei Erstnennung in einem Halbsatz auf. Setze nichts voraus — auch keine Begriffe aus dem Titel oder anderen Bausteinen. Nutze die „Voraussetzung"-Hinweise aus den Inhalten als Anker, die „Hürde"-Hinweise, um Missverständnisse vorweg auszuräumen.
|
||||||
- Die Subbausteine sind deine INHALTS-CHECKLISTE, keine Überschriften: webe sie in den Fließtext ein. Behandle jeden, aber als Teil eines Ganzen — nicht als isolierten Mini-Absatz.
|
- Die Subbausteine sind deine INHALTS-CHECKLISTE, keine Überschriften: webe sie in den Fließtext ein. Behandle jeden, aber als Teil eines Ganzen — nicht als isolierten Mini-Absatz.
|
||||||
- Reihenfolge: erst die `[einfach]`-Punkte (das Fundament), dann `[mittel]` (Details/Varianten), zuletzt `[schwer]` (Sonderfälle/Tücken — knapp, als fortgeschrittener Hinweis).
|
- Reihenfolge: erst die `[einfach]`-Punkte (das Fundament), dann `[mittel]` (Details/Varianten), zuletzt `[schwer]` (Sonderfälle/Tücken — knapp, als fortgeschrittener Hinweis).
|
||||||
- Tiefe folgt der Zahl der Subbausteine: ein Baustein mit vielen Subbausteinen wird ein langer, gehaltvoller Text; einer mit wenigen bleibt kurz. Das ist gewollt.
|
- Diese Fassung trägt die volle Tiefe selbst — es gibt keine zweite Ausbaustufe. „Wie"-Abläufe Schritt für Schritt zeigen, nicht nur das Ergebnis.
|
||||||
- KEIN festes Längen-Limit. So lang wie nötig, damit ein Anfänger es ohne Vorwissen versteht. Lieber ein Satz mehr und klar.
|
- KEIN festes Längen-Limit. So lang wie nötig, damit ein Neuling es OHNE Vorwissen versteht. Im Zweifel ein Satz mehr und klar.
|
||||||
- Beispiel großzügig einsetzen, wo es hilft — bei Code-/Tool-Themen fast immer ein kurzes Code-Snippet (Format laut Spezifikation).
|
- Beispiele großzügig — auch kleine Mini-Beispiele mitten im Text, wo sie einen Punkt sofort greifbar machen, zusätzlich zum `### Beispiel` am Ende (Format laut Spezifikation).
|
||||||
|
|
||||||
GEPRÜFTE INHALTE je Baustein — das ist verbindlich, was gelehrt werden muss:
|
GEPRÜFTE INHALTE je Baustein — das ist verbindlich, was gelehrt werden muss:
|
||||||
{inhalte}
|
{inhalte}
|
||||||
|
|||||||
Reference in New Issue
Block a user