File size: 3,512 Bytes
2aabc58
 
3475963
 
2aabc58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23b513a
086c738
23b513a
 
 
 
 
 
2aabc58
 
23b513a
2aabc58
 
23b513a
2aabc58
 
 
 
 
 
 
 
 
 
 
23b513a
042edfb
 
 
2aabc58
042edfb
2aabc58
 
042edfb
 
 
2aabc58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3475963
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23b513a
 
 
 
 
 
 
 
 
3475963
 
 
 
 
 
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
"""Constants and helpers for the intake form schema."""

import hashlib
import json
from typing import Literal, TypedDict, List

DESIGN_ELEMENTS: List[str] = [
    "Hypotheses/Endpoints",
    "Multiplicity control",
    "Sample size and power",
    "Interim analyses",
    "Others",
]

QUESTION_TYPES: List[str] = [
    "extraction_only",
    "derivation_required",
]

QuestionType = Literal["extraction_only", "derivation_required", ""]
Status = Literal["pending", "reviewed", "needs_fix"]

VALID_STATUSES: List[str] = ["pending", "reviewed", "needs_fix"]

# Each criterion's importance (replaces the old numeric "points").
IMPORTANCE_OPTIONS: List[str] = ["High", "Medium", "Low"]


class Criterion(TypedDict):
    criterion: str
    importance: str


class Rubric(TypedDict):
    # A dimension block; holds one or more criteria.
    artifact: str
    dimension: str
    criteria: List[Criterion]


class Question(TypedDict):
    id: str
    design_element: str
    design_element_other: str
    question: str
    question_type: str
    rubrics: List[Rubric]


def dimensions_for_type(qt: str):
    """Fixed (artifact, dimension) blocks for a question type, each with the
    number of criterion rows to show by default. The user fills in one or more
    criteria under each; the first is primary, extras are optional."""
    if qt == "extraction_only":
        return [{"artifact": "output.json", "dimension": "", "default_criteria": 1}]
    if qt == "derivation_required":
        return [
            {"artifact": "output.json", "dimension": "Inputs used", "default_criteria": 1},
            {"artifact": "output.json", "dimension": "Calculated value", "default_criteria": 1},
            {"artifact": "output.json", "dimension": "Method", "default_criteria": 3},
        ]
    return []


def blank_question(qid: str) -> Question:
    return {
        "id": qid,
        "design_element": "",
        "design_element_other": "",
        "question": "",
        "question_type": "",
        "rubrics": [],
    }


def next_question_id(existing: List[Question]) -> str:
    nums = []
    for q in existing:
        qid = q.get("id", "")
        if qid.startswith("P-"):
            try:
                nums.append(int(qid[2:]))
            except ValueError:
                pass
    return f"P-{(max(nums) + 1 if nums else 1):03d}"


def question_content_hash(q: dict) -> str:
    """Stable hash of a question's *content* (excludes its id).

    Used to detect whether a question was edited since it was reviewed: if the
    current content hash differs from the hash stored on a review, that review
    no longer applies to the current content.
    """
    canonical = {
        "design_element": q.get("design_element", ""),
        "design_element_other": q.get("design_element_other", ""),
        "question": q.get("question", ""),
        "question_type": q.get("question_type", ""),
        "rubrics": [
            {
                "artifact": r.get("artifact", ""),
                "dimension": r.get("dimension", ""),
                "criteria": [
                    {
                        "criterion": c.get("criterion", ""),
                        "importance": c.get("importance", ""),
                    }
                    for c in (r.get("criteria") or [])
                ],
            }
            for r in (q.get("rubrics") or [])
        ],
    }
    blob = json.dumps(canonical, sort_keys=True, ensure_ascii=False)
    return hashlib.sha1(blob.encode("utf-8")).hexdigest()