"""Turn the statistics (and Claude's reviews, if any) into a Markdown report.""" from __future__ import annotations from itemstats import TestStats def fmt(x: float | None, digits: int = 2) -> str: return "n/a" if x is None else f"{x:.{digits}f}" def build_report(quiz: dict, stats: TestStats, reviews: list | None = None) -> str: items_by_id = {item["id"]: item for item in quiz["items"]} reviews_by_id = {r.item_id: r for r in (reviews or [])} flagged = [s for s in stats.items if s.flags] lines = [ f"# Item analysis: {quiz['title']}", "", f"- Learners: {stats.n_learners}", f"- Items: {stats.n_items}", f"- Mean score: {stats.mean_score:.2f} of {stats.n_items} (SD {stats.sd_score:.2f})", f"- Reliability (KR-20): {fmt(stats.kr20)}", f"- Items flagged: {len(flagged)}", "", "## All items", "", "| Item | Key | Difficulty (p) | Discrimination (r) | D index | KR-20 if removed | Flags |", "|---|---|---|---|---|---|---|", ] for s in stats.items: lines.append( f"| {s.item_id} | {s.key} | {fmt(s.difficulty)} | {fmt(s.discrimination)} | " f"{fmt(s.d_index)} | {fmt(s.alpha_if_deleted)} | {len(s.flags) or ''} |" ) lines += ["", "## Flagged items", ""] if not flagged: lines.append("Nothing was flagged.") for s in flagged: item = items_by_id[s.item_id] lines += [f"### {s.item_id}: {item['stem']}", ""] lines += ["| Option | Text | Picked | Top 27% | Bottom 27% | r with rest of test |", "|---|---|---|---|---|---|"] for o in s.options: label = f"**{o.option}** (key)" if o.option == s.key else o.option lines.append( f"| {label} | {item['options'][o.option]} | {o.count} ({o.share:.0%}) | " f"{o.upper_share:.0%} | {o.lower_share:.0%} | {fmt(o.point_biserial)} |" ) lines += ["", "Flags:", ""] lines += [f"- {flag}" for flag in s.flags] review = reviews_by_id.get(s.item_id) if review: lines += [ "", f"Claude's read: **{review.action}** (likely cause: {review.likely_cause.replace('_', ' ')})", "", review.diagnosis, ] if review.revised_item: r = review.revised_item lines += ["", "Suggested revision:", "", f"> {r.stem}", ">"] for o in sorted(r.options, key=lambda o: o.letter): mark = " (key)" if o.letter == r.key else "" lines.append(f"> {o.letter}. {o.text}{mark} ") lines += ["", review.what_changed] lines.append("") return "\n".join(lines).rstrip() + "\n"