P5b: self-contained HTML QC report and QC table (simurg qc report)

- pipeline/qc_report: dataset discovery/labels/types (QC-041..043), robust TSV/CSV reading (QC-061),
  join + unmatched warning (QC-040), privacy options (QC-046), xlsx (4 sheets, conditional fills) / csv
  table (QC-045), byte-deterministic HTML with gzip+base64 embedded data (QC-044, QC-047)
- qc/report: template, CSS and JS app — Control Dashboard, Automated QC (table with sort/filter/search/
  paging/column menu/export, metric scatters with threshold lines), Sample Heatmaps (chip/plate,
  continuous/binary), Trend Analysis (grouped summary, Tukey box plots, counts, sex, PCA), thresholds modal
  (apply / download / upload YAML), linked selection across plots and table (QC-048..060)
- bundled plotly.js 2.35.2 (MIT); openpyxl dependency (MIT)
- tests: discovery/labels/errors/determinism/xlsx + headless-browser self-test
- known_unknowns K-020: remaining report deviations

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01814GKVzZVL3FuF3DeiZXhv
This commit is contained in:
kemalandClaude Opus 5.5 committed 2026-09-28 09:20:18 +01:00
1 parent 56d250c621
commit 95be85a76b
13 files changed
+1601

No files matched your search

+56
View File
@@ -602,6 +602,62 @@ def qc_loci_cmd(
_qc_cohort("loci", out, bpm, gtcs, sheet, egt, threads, force, log_json, debug, quiet)
@qc_app.command("report")
def qc_report_cmd(
data: Annotated[list[str], typer.Option("--data", help="Dataset folder(s): one, a comma list, or a parent.")],
out: Annotated[Path, typer.Option("--out", help="Output folder.")] = Path("."),
label: Annotated[
list[str] | None, typer.Option("--label", help="Dataset labels (list or discovery order).")
] = None,
config: Annotated[Path | None, typer.Option("--config", help="QC threshold YAML (QC-020).")] = None,
profile: Annotated[str | None, typer.Option("--profile", help="QC profile name or 'auto'.")] = None,
threshold_call_rate: Annotated[float | None, typer.Option("--threshold-call-rate")] = None,
threshold_lrr_sd: Annotated[float | None, typer.Option("--threshold-lrr-sd")] = None,
threshold: Annotated[list[str] | None, typer.Option("--threshold", help="KEY=VALUE (repeatable).")] = None,
output_format: Annotated[str, typer.Option("--output-format", help="xlsx|csv|both")] = "xlsx",
report_name: Annotated[str | None, typer.Option("--report-name", help="Fixed output file stem.")] = None,
drop_metadata: Annotated[
list[str] | None, typer.Option("--drop-metadata", help="Metadata columns to drop.")
] = None,
keep_metadata: Annotated[list[str] | None, typer.Option("--keep-metadata", help="Metadata allowlist.")] = None,
no_metadata: Annotated[bool, typer.Option("--no-metadata", help="Embed no metadata columns.")] = False,
redact_paths: Annotated[bool, typer.Option("--redact-paths", help="Blank sample_folder.")] = False,
split_by: Annotated[str, typer.Option("--split-by", help="none|dataset")] = "none",
log_json: LogJson = False,
debug: Debug = False,
quiet: Quiet = False,
) -> None:
"""Self-contained HTML QC report and QC table from dataset folders (QC-040 … QC-063)."""
from simurg.pipeline.qc_report import QcReportOptions, run_qc_report
from simurg.qc.thresholds import parse_cli_pairs, resolve_thresholds
rep = _reporter("qc report", None, log_json, debug, quiet)
def body() -> int:
cli: dict[str, Any] = parse_cli_pairs(list(threshold or []))
if threshold_call_rate is not None:
cli["callRate"] = threshold_call_rate
if threshold_lrr_sd is not None:
cli["logRDev"] = threshold_lrr_sd
th = resolve_thresholds(config=config, profile=profile, cli=cli)
opts = QcReportOptions(
data=list(data),
out=out,
labels=list(label) if label else None,
output_format=output_format,
report_name=report_name,
drop_metadata=list(drop_metadata or []),
keep_metadata=list(keep_metadata) if keep_metadata else None,
no_metadata=no_metadata,
redact_paths=redact_paths,
split_by=split_by,
thresholds=th,
)
return run_qc_report(opts, rep)
run_guarded(rep, body)
@qc_app.command("suggest-thresholds")
def qc_suggest_cmd(
data: Annotated[list[Path], typer.Option("--data", help="Dataset folder(s) with controls_qc.tsv.")],