File size: 2,917 Bytes
4a4df15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations
import html
import re
from .schemas import EvaluationResult, Rubric


def md(value: str) -> str:
    """Escape model/document text; do not render injected HTML, image URLs or links."""
    value = html.escape(str(value), quote=False)
    return re.sub(r"([\\`*_{}\[\]()#+!|])", r"\\\1", value)


def render_feedback(rubric: Rubric, result: EvaluationResult, source_map: dict) -> str:
    concepts = {c.id: c for c in rubric.concepts}
    blocks = []
    for importance in ("essential", "important", "minor"):
        blocks.append(f"## {importance.title()} omissions")
        reports = [c for c in result.checks if c.status in ("partial", "missing", "incorrect")
                   and concepts[c.concept_id].importance == importance]
        if not reports:
            blocks.append("No confirmed omissions in this category were identified in this check.")
        for check in reports:
            concept = concepts[check.concept_id]
            label = "**Needs correction:** " if check.status == "incorrect" else ""
            blocks.append(f"- **{md(concept.name)}** - {label}{md(check.missing_detail)}")
            if check.status == "partial" and check.answer_evidence:
                blocks.append(f"  Already covered in your answer: \"{md(check.answer_evidence)}\"")
            refs = []
            for e in concept.evidence:
                c = source_map[e.source_id]
                refs.append(f"{md(c.filename)}, {md(c.location)}")
            blocks.append("  *Primary reference: " + "; ".join(dict.fromkeys(refs)) + "*")
            blocks.append("  **Review hint:** " + md(check.what_to_add))
            if check.concept_id in result.supporting_hints:
                blocks.append("  **Optional supporting explanation:** " + md(result.supporting_hints[check.concept_id]))
            related = result.supporting_ids.get(check.concept_id, [])
            if related:
                labels = [f"{md(source_map[x].filename)}, {md(source_map[x].location)}" for x in related]
                blocks.append("  *Suggested supporting reading (not an extra requirement): " + "; ".join(labels) + "*")
    if result.warnings:
        blocks.append("**Check limitations:** " + " ".join(md(t) for t in result.warnings))
    blocks.append("> **Preparation note:** This is an automated, deliberately thorough study aid, not an exam grade. "
                  "Equivalent wording counts. A missing item in the model's output is never treated as an omission in your answer. "
                  "References and feedback can still be imperfect; check the cited primary material.")
    return "\n\n".join(blocks)


def source_list(rubric: Rubric, source_map: dict) -> str:
    return "\n\n".join(f"**{md(source_map[sid].filename)} - {md(source_map[sid].location)}**\n\n{md(source_map[sid].text)}"
                         for sid in rubric.context_ids if sid in source_map)