Spaces:
Running
Running
| """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"] | |
| class Rubric(TypedDict): | |
| artifact: str | |
| dimension: str | |
| points: str | |
| criterion: str | |
| tolerance: str | |
| class Question(TypedDict): | |
| id: str | |
| design_element: str | |
| design_element_other: str | |
| question: str | |
| question_type: str | |
| rubrics: List[Rubric] | |
| def blank_rubric(artifact: str = "", dimension: str = "") -> Rubric: | |
| return { | |
| "artifact": artifact, | |
| "dimension": dimension, | |
| "points": "", | |
| "criterion": "", | |
| "tolerance": "", | |
| } | |
| def rubrics_for_type(qt: str) -> List[Rubric]: | |
| if qt == "extraction_only": | |
| return [blank_rubric("output.json", "")] | |
| if qt == "derivation_required": | |
| return [ | |
| blank_rubric("output.json", "Inputs used"), | |
| blank_rubric("output.json", "Calculated value"), | |
| blank_rubric("output.json", "Method"), | |
| blank_rubric("output.R", "Reproducibility"), | |
| ] | |
| 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": [ | |
| { | |
| k: r.get(k, "") | |
| for k in ("artifact", "dimension", "points", "criterion", "tolerance") | |
| } | |
| 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() | |