File size: 6,470 Bytes
f8d4986
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""Render a schema-valid case+note dict into classroom-ready Markdown.

The hero of Case Forge is that the output is *usable in class*, not a wall of
JSON. This turns the contract (`data/schema.py`) into a well-structured case and
teaching note, and surfaces the genre's quality checks as visible badges.
"""

from __future__ import annotations

# Section labels per language (UI chrome lives in shared/i18n; these are the
# document headings, kept next to the renderer that emits them).
_L = {
    "case": {
        "en": {
            "decision": "The decision", "protagonist": "Protagonist",
            "context": "Context", "data": "Data & evidence",
            "exhibits": "Exhibits", "alternatives": "Paths to weigh",
            "closing": "Where it stands", "references": "References",
        },
        "pt": {
            "decision": "A decisão", "protagonist": "Protagonista",
            "context": "Contexto", "data": "Dados e evidências",
            "exhibits": "Anexos", "alternatives": "Caminhos a ponderar",
            "closing": "Onde isso para", "references": "Referências",
        },
    },
    "note": {
        "en": {
            "title": "Teaching note", "summary": "Summary",
            "audience": "Audience", "relevance": "Managerial relevance",
            "objectives": "Learning objectives", "sources": "Data sources",
            "anchor": "Theoretical anchor", "plan": "Discussion plan",
            "questions": "Discussion questions", "analysis": "Analysis & expected answers",
            "closure": "Closure", "epilogue": "Epilogue", "biblio": "Bibliography",
            "min": "min",
        },
        "pt": {
            "title": "Nota de ensino", "summary": "Resumo",
            "audience": "Público-alvo", "relevance": "Relevância gerencial",
            "objectives": "Objetivos de aprendizagem", "sources": "Fontes dos dados",
            "anchor": "Ancoragem teórica", "plan": "Plano de discussão",
            "questions": "Questões de discussão", "analysis": "Análise e respostas esperadas",
            "closure": "Fechamento", "epilogue": "Epílogo", "biblio": "Bibliografia",
            "min": "min",
        },
    },
}


def _lang(obj: dict) -> str:
    return obj.get("language") if obj.get("language") in ("pt", "en") else "pt"


def render_case(obj: dict) -> str:
    """The case itself, as Markdown — stops at the decision point."""
    if not obj:
        return ""
    lang = _lang(obj)
    L = _L["case"][lang]
    c = obj.get("case") or {}
    out: list[str] = []

    out.append(f"# {obj.get('title', '').strip()}")
    domain = obj.get("domain", "").strip()
    if domain:
        out.append(f"*{domain}*")
    out.append("")
    if c.get("hook"):
        out.append(c["hook"].strip())
        out.append("")
    if c.get("protagonist"):
        out.append(f"**{L['protagonist']}:** {c['protagonist'].strip()}")
        out.append("")
    if c.get("decision_point"):
        out.append(f"> **{L['decision']}:** {c['decision_point'].strip()}")
        out.append("")
    if c.get("context"):
        out.append(f"## {L['context']}")
        out.append(c["context"].strip())
        out.append("")
    if c.get("data"):
        out.append(f"## {L['data']}")
        out += [f"- {d.strip()}" for d in c["data"]]
        out.append("")
    if c.get("exhibits"):
        out.append(f"## {L['exhibits']}")
        for ex in c["exhibits"]:
            out.append(f"**{ex.get('title', '').strip()}**")
            out.append("")
            out.append(str(ex.get("content", "")).strip())
            out.append("")
    if c.get("alternatives"):
        out.append(f"## {L['alternatives']}")
        out += [f"- {a.strip()}" for a in c["alternatives"]]
        out.append("")
    if c.get("closing"):
        out.append(f"## {L['closing']}")
        out.append(c["closing"].strip())
        out.append("")
    if c.get("references"):
        out.append(f"## {L['references']}")
        out += [f"- {r.strip()}" for r in c["references"]]
        out.append("")
    return "\n".join(out).strip()


def render_note(obj: dict) -> str:
    """The teaching note, as Markdown — instructor-facing (the epilogue lives here)."""
    if not obj:
        return ""
    lang = _lang(obj)
    L = _L["note"][lang]
    n = obj.get("teaching_note") or {}
    out: list[str] = [f"# {L['title']}", ""]

    def block(label_key: str, text):
        if text:
            out.append(f"## {L[label_key]}")
            out.append(str(text).strip())
            out.append("")

    def bullets(label_key: str, items, ordered=False):
        if items:
            out.append(f"## {L[label_key]}")
            for i, it in enumerate(items, 1):
                out.append(f"{i}. {str(it).strip()}" if ordered else f"- {str(it).strip()}")
            out.append("")

    block("summary", n.get("summary"))
    block("audience", n.get("audience"))
    block("relevance", n.get("managerial_relevance"))
    bullets("objectives", n.get("learning_objectives"), ordered=True)
    block("sources", n.get("data_sources"))
    bullets("anchor", n.get("theoretical_anchor"))

    plan = n.get("discussion_plan") or []
    if plan:
        out.append(f"## {L['plan']}")
        for b in plan:
            mins = b.get("minutes")
            head = f"**{b.get('block', '').strip()}**"
            if mins:
                head += f" — {mins} {L['min']}"
            out.append(head)
            if b.get("activity"):
                out.append(f"  {b['activity'].strip()}")
            out.append("")

    bullets("questions", n.get("discussion_questions"), ordered=True)
    block("analysis", n.get("analysis"))
    block("closure", n.get("closure"))
    block("epilogue", n.get("epilogue"))
    bullets("biblio", n.get("bibliography"))
    return "\n".join(out).strip()


def quality_flags(obj: dict, errors: list[str], warnings: list[str]) -> dict[str, bool]:
    """Map the validator result to the four classroom quality checks the UI shows."""
    note = (obj or {}).get("teaching_note") or {}
    objs = note.get("learning_objectives") or []
    leak = any("revelar a decisão" in w or "reveal" in w.lower() for w in warnings)
    no_source = any("citar fonte" in w or "sourced" in w.lower() for w in warnings)
    return {
        "schema": not errors,
        "noleak": not leak,
        "objectives": 1 <= len(objs) <= 4,
        "sourced": not no_source,
    }


__all__ = ["render_case", "render_note", "quality_flags"]