Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| from pathlib import Path | |
| from typing import Any | |
| from module_runner import run_registry | |
| from trial_profile import normalize_trial | |
| DEFAULT_PROJECT_ROOT = Path(__file__).resolve().parents[1] | |
| def build_report(raw_form: dict[str, Any], project_root: Path | None = None) -> dict[str, Any]: | |
| profile = normalize_trial(raw_form) | |
| validation = profile["validation"] | |
| if validation["errors"]: | |
| return { | |
| "ok": False, | |
| "trial_feature_profile": profile, | |
| "modules": {}, | |
| "final_output": { | |
| "status": "needs_input", | |
| "message": "Complete required fields before running the evidence summary.", | |
| "plain_language": [ | |
| "The protocol entry is incomplete.", | |
| "Fix the required fields shown in validation errors, then run the report again.", | |
| ], | |
| }, | |
| } | |
| root = _resolve_project_root(project_root) | |
| bundle = run_registry(profile, root) | |
| modules = bundle["modules"] | |
| capabilities = bundle["capabilities"] | |
| pipeline = bundle["pipeline"] | |
| evidence = modules["historical_comparator"]["result"] | |
| final_output = _final_output(profile, modules, capabilities, pipeline) | |
| return { | |
| "ok": True, | |
| "trial_feature_profile": profile, | |
| "modules": modules, | |
| "historical_evidence": evidence, | |
| "final_output": final_output, | |
| } | |
| def _resolve_project_root(project_root: Path | None) -> Path: | |
| if project_root is None: | |
| return DEFAULT_PROJECT_ROOT | |
| resolved = project_root.resolve() | |
| if resolved.is_file() or resolved.suffix.lower() == ".zip": | |
| return resolved.parent | |
| return resolved | |
| def _final_output( | |
| profile: dict[str, Any], | |
| modules: dict[str, Any], | |
| capabilities: dict[str, Any], | |
| pipeline: dict[str, Any], | |
| ) -> dict[str, Any]: | |
| evidence = modules["historical_comparator"]["result"] | |
| comparison = evidence["comparison"] | |
| summary = evidence["summary"] | |
| completeness = modules["protocol_completeness"]["result"] | |
| outlook = capabilities.get("publication_outlook", {}) | |
| narrative = capabilities.get("narrative_summary", {}) | |
| title = profile.get("brief_title") or "Untitled trial" | |
| module_statuses = { | |
| name: { | |
| "status": module.get("status"), | |
| "language": module.get("language"), | |
| "selection": module.get("selection"), | |
| "capability": module.get("capability"), | |
| "provenance": module.get("provenance"), | |
| "reason": module.get("reason"), | |
| "description": module.get("description"), | |
| } | |
| for name, module in modules.items() | |
| } | |
| review_flags = comparison["flags"] | |
| display_flags = review_flags or ["No major review flags from this simple comparator pass."] | |
| return { | |
| "title": title, | |
| "review_priority": comparison["review_priority"], | |
| "takeaway": narrative.get("takeaway", ""), | |
| "summary": narrative.get("summary", []), | |
| "summary_source": narrative.get("source", "template"), | |
| "summary_warning": narrative.get("warning"), | |
| "headline": _headline(profile, evidence, outlook, completeness, comparison), | |
| "actions": narrative.get("actions") or _actions(review_flags, completeness), | |
| "key_numbers": { | |
| "planned_enrollment": profile.get("enrollment"), | |
| "comparator_median_enrollment": summary["median_enrollment"], | |
| "planned_facilities": profile.get("number_of_facilities"), | |
| "comparator_median_facilities": summary["median_facilities"], | |
| "planned_arms": profile.get("number_of_arms"), | |
| "comparator_median_arms": summary["median_arms"], | |
| "planned_primary_outcomes": profile.get("number_of_primary_outcomes"), | |
| "comparator_median_primary_outcomes": summary["median_primary_outcomes"], | |
| "planned_secondary_outcomes": profile.get("number_of_secondary_outcomes"), | |
| "comparator_median_secondary_outcomes": summary["median_secondary_outcomes"], | |
| "comparator_publication_rate": summary["publication_rate"], | |
| "comparator_results_reported_rate": summary["results_reported_rate"], | |
| "comparator_median_time_to_publication_days": summary["median_time_to_publication_days"], | |
| }, | |
| "review_flags": display_flags, | |
| "module_statuses": module_statuses, | |
| "pipeline": pipeline, | |
| "plain_language": narrative.get("summary", []), | |
| "model_status": outlook.get("provenance_detail", comparison["note"]), | |
| } | |
| def _headline( | |
| profile: dict[str, Any], | |
| evidence: dict[str, Any], | |
| outlook: dict[str, Any], | |
| completeness: dict[str, Any], | |
| comparison: dict[str, Any], | |
| ) -> list[dict[str, Any]]: | |
| """The skimmable top-of-report numbers, most decision-relevant first.""" | |
| pub = outlook.get("publication_likelihood") | |
| results = outlook.get("results_reporting_likelihood") | |
| priority = comparison["review_priority"] | |
| priority_tone = {"high": "alert", "focused": "watch", "standard": "good"}.get(priority, "neutral") | |
| return [ | |
| { | |
| "label": "Publication likelihood", | |
| "value": _percent(pub), | |
| "raw": pub, | |
| "tone": _rate_tone(pub), | |
| "provenance": outlook.get("provenance_label", "Historical comparator"), | |
| "sub": f"among {outlook.get('basis_rows', evidence.get('used_rows'))} comparable trials", | |
| }, | |
| { | |
| "label": "Results reporting likelihood", | |
| "value": _percent(results), | |
| "raw": results, | |
| "tone": _rate_tone(results), | |
| "provenance": outlook.get("provenance_label", "Historical comparator"), | |
| "sub": "of comparable trials posted results", | |
| }, | |
| { | |
| "label": "Review priority", | |
| "value": priority.title(), | |
| "raw": priority, | |
| "tone": priority_tone, | |
| "provenance": "Design flags", | |
| "sub": f"{len(comparison['flags'])} flag(s) raised", | |
| }, | |
| { | |
| "label": "Protocol completeness", | |
| "value": _percent(completeness["completion_ratio"]), | |
| "raw": completeness["completion_ratio"], | |
| "tone": _rate_tone(completeness["completion_ratio"], good=0.9, watch=0.7), | |
| "provenance": "Protocol entry", | |
| "sub": f"{completeness['filled_fields']}/{completeness['total_fields']} fields", | |
| }, | |
| ] | |
| def _actions(review_flags: list[str], completeness: dict[str, Any]) -> list[dict[str, Any]]: | |
| """Actionable improvement items, design review first then completeness gaps.""" | |
| actions: list[dict[str, Any]] = [ | |
| {"severity": "review", "text": flag} for flag in review_flags | |
| ] | |
| for section in completeness["weakest_sections"]: | |
| if section["missing"]: | |
| fields = ", ".join(field.replace("_", " ") for field in section["missing"]) | |
| actions.append( | |
| { | |
| "severity": "completeness", | |
| "text": f"Complete the {section['section'].replace('_', ' ')} section — add: {fields}.", | |
| } | |
| ) | |
| if not actions: | |
| actions.append({"severity": "ok", "text": "No design or completeness issues flagged in this pass."}) | |
| return actions | |
| def _rate_tone(value: float | None, good: float = 0.6, watch: float = 0.35) -> str: | |
| if value is None: | |
| return "neutral" | |
| if value >= good: | |
| return "good" | |
| if value >= watch: | |
| return "watch" | |
| return "alert" | |
| def _percent(value: float | None) -> str: | |
| if value is None: | |
| return "NA" | |
| return f"{round(value * 100)}%" | |