"""Export study artifacts: final sheet, Anki CSV.""" from __future__ import annotations import csv import io from typing import Any from plane_mode_scholar.core.config import DEFAULT_USER_ID from plane_mode_scholar.memory.scheduler import get_due_reviews from plane_mode_scholar.storage.sqlite_store import SQLiteStore from plane_mode_scholar.study.mastery import MasteryTracker def build_final_sheet_markdown( store: SQLiteStore, pack_id: str, user_id: str = DEFAULT_USER_ID, ) -> str: pack = store.get_pack(pack_id) mastery = MasteryTracker(store) due = get_due_reviews(store, pack_id, user_id) weak = mastery.get_weak_topics(pack_id) topics = store.list_topics(pack_id) misconceptions = [ m for m in store.list_memories(pack_id=pack_id, user_id=user_id, status="active") if m.type.value == "misconception" ] lines = [ f"# Final sheet — {pack.name if pack else pack_id}", "", "## Must-review (due now)", ] if due: for m in due[:8]: lines.append(f"- {m.content}") else: lines.append("- None due — great job!") lines.append("") lines.append("## Weak topics") if weak: for w in weak[:6]: lines.append(f"- **{w['name']}** ({int(w.get('mastery', 0) * 100)}%)") else: lines.append("- Complete quizzes to identify weak areas.") lines.append("") lines.append("## Common gaps") if misconceptions: for m in misconceptions[:6]: lines.append(f"- {m.content[:120]}") else: lines.append("- No recorded misconceptions yet.") lines.append("") lines.append("## Syllabus checklist") for t in topics[:12]: lines.append(f"- [ ] {t.name}") lines.append("") lines.append("---") lines.append("*Generated by Plane Mode Scholar — study offline, remember on landing.*") return "\n".join(lines) def build_anki_csv( store: SQLiteStore, pack_id: str, user_id: str = DEFAULT_USER_ID, ) -> str: """Anki-compatible CSV: front, back, tags.""" buf = io.StringIO() writer = csv.writer(buf, quoting=csv.QUOTE_MINIMAL) writer.writerow(["Front", "Back", "Tags"]) memories = store.list_memories(pack_id=pack_id, user_id=user_id, status="active") for mem in memories: if mem.type.value in ("misconception", "review_item", "open_loop"): front = mem.content[:200] if mem.type.value == "misconception" and " instead of " in mem.content: parts = mem.content.split(" instead of ") back = parts[-1].strip("'") if parts else mem.content else: back = mem.content tags = " ".join(t for t in mem.tags[:5] if not t.startswith(("due:", "interval:", "reps:"))) writer.writerow([front, back[:300], tags or mem.type.value]) chunks = store.list_chunks(pack_id)[:20] for chunk in chunks: text = chunk.text.strip() if len(text) < 40: continue sentences = [s.strip() for s in text.split(".") if len(s.strip()) > 25] if sentences: front = f"What does {chunk.source_file} say about this topic?" back = sentences[0][:300] writer.writerow([front, back, f"chunk {chunk.source_file}"]) return buf.getvalue() def build_final_sheet_html( store: SQLiteStore, pack_id: str, user_id: str = DEFAULT_USER_ID, ) -> str: md = build_final_sheet_markdown(store, pack_id, user_id) body = md.replace("\n## ", "\n

").replace("\n- ", "\n
  • ") body = body.replace("# ", "

    ").replace("**", "", 1) return f'
    {md}
    '