qpilot 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qpilot-0.2.0/.gitignore +39 -0
- qpilot-0.2.0/CHANGELOG.md +441 -0
- qpilot-0.2.0/CITATION.cff +75 -0
- qpilot-0.2.0/CONTRIBUTING.md +76 -0
- qpilot-0.2.0/LICENSE +202 -0
- qpilot-0.2.0/PKG-INFO +511 -0
- qpilot-0.2.0/README.md +444 -0
- qpilot-0.2.0/SECURITY.md +23 -0
- qpilot-0.2.0/THIRD_PARTY_NOTICES.md +492 -0
- qpilot-0.2.0/THREAT_MODEL.md +103 -0
- qpilot-0.2.0/docs/README.md +75 -0
- qpilot-0.2.0/docs/agents.md +330 -0
- qpilot-0.2.0/docs/app.md +622 -0
- qpilot-0.2.0/docs/conformance.md +98 -0
- qpilot-0.2.0/docs/cq-tables.md +84 -0
- qpilot-0.2.0/docs/design-editor.md +96 -0
- qpilot-0.2.0/docs/engine.md +88 -0
- qpilot-0.2.0/docs/exports.md +75 -0
- qpilot-0.2.0/docs/install.md +138 -0
- qpilot-0.2.0/docs/integration.md +212 -0
- qpilot-0.2.0/docs/multiplex.md +100 -0
- qpilot-0.2.0/docs/prism.md +251 -0
- qpilot-0.2.0/docs/quantities.md +116 -0
- qpilot-0.2.0/docs/release.md +73 -0
- qpilot-0.2.0/docs/sandbox.md +113 -0
- qpilot-0.2.0/docs/science-review-2026-10.md +447 -0
- qpilot-0.2.0/docs/security-review-2026-10.md +91 -0
- qpilot-0.2.0/docs/studio-measured.md +66 -0
- qpilot-0.2.0/docs/studio.md +163 -0
- qpilot-0.2.0/docs/user-guide.md +503 -0
- qpilot-0.2.0/methods/absolute_quantification.md +54 -0
- qpilot-0.2.0/methods/amp_call.md +34 -0
- qpilot-0.2.0/methods/comparisons.md +70 -0
- qpilot-0.2.0/methods/contrast.md +24 -0
- qpilot-0.2.0/methods/curve_judge.md +161 -0
- qpilot-0.2.0/methods/design_checks.md +125 -0
- qpilot-0.2.0/methods/efficiency_model.md +32 -0
- qpilot-0.2.0/methods/evidence.md +50 -0
- qpilot-0.2.0/methods/genorm.md +38 -0
- qpilot-0.2.0/methods/instrument_context.md +66 -0
- qpilot-0.2.0/methods/inter_run_calibration.md +49 -0
- qpilot-0.2.0/methods/linregpcr.md +44 -0
- qpilot-0.2.0/methods/lookback.md +95 -0
- qpilot-0.2.0/methods/melt_curve.md +66 -0
- qpilot-0.2.0/methods/normfinder.md +104 -0
- qpilot-0.2.0/methods/replicate_budget.md +68 -0
- qpilot-0.2.0/methods/routes.md +28 -0
- qpilot-0.2.0/methods/sheetcheck.md +32 -0
- qpilot-0.2.0/methods/trace.md +82 -0
- qpilot-0.2.0/packs/research-default.yaml +339 -0
- qpilot-0.2.0/pyproject.toml +145 -0
- qpilot-0.2.0/qpilot/__init__.py +6 -0
- qpilot-0.2.0/qpilot/agents/__init__.py +1 -0
- qpilot-0.2.0/qpilot/agents/capabilities.py +1066 -0
- qpilot-0.2.0/qpilot/agents/idempotency.py +119 -0
- qpilot-0.2.0/qpilot/agents/outputs.py +785 -0
- qpilot-0.2.0/qpilot/agents/refusals.py +199 -0
- qpilot-0.2.0/qpilot/agents/surface.py +294 -0
- qpilot-0.2.0/qpilot/agents/views.py +254 -0
- qpilot-0.2.0/qpilot/ai/__init__.py +1 -0
- qpilot-0.2.0/qpilot/ai/ask.py +924 -0
- qpilot-0.2.0/qpilot/ai/client.py +328 -0
- qpilot-0.2.0/qpilot/ai/config.py +124 -0
- qpilot-0.2.0/qpilot/ai/explain.py +223 -0
- qpilot-0.2.0/qpilot/ai/interpret.py +1651 -0
- qpilot-0.2.0/qpilot/ai/payload_log.py +74 -0
- qpilot-0.2.0/qpilot/ai/prism_draft.py +460 -0
- qpilot-0.2.0/qpilot/ai/profile.py +75 -0
- qpilot-0.2.0/qpilot/ai/prompts.py +115 -0
- qpilot-0.2.0/qpilot/ai/suggest_only.py +100 -0
- qpilot-0.2.0/qpilot/ai/validator.py +11 -0
- qpilot-0.2.0/qpilot/app_state.py +436 -0
- qpilot-0.2.0/qpilot/claims/__init__.py +24 -0
- qpilot-0.2.0/qpilot/claims/assist.py +201 -0
- qpilot-0.2.0/qpilot/claims/check.py +1516 -0
- qpilot-0.2.0/qpilot/claims/examples.py +137 -0
- qpilot-0.2.0/qpilot/claims/extract.py +1167 -0
- qpilot-0.2.0/qpilot/claims/lexicon.py +442 -0
- qpilot-0.2.0/qpilot/claims/models.py +276 -0
- qpilot-0.2.0/qpilot/cli/__init__.py +71 -0
- qpilot-0.2.0/qpilot/cli/_console.py +33 -0
- qpilot-0.2.0/qpilot/cli/_types.py +6 -0
- qpilot-0.2.0/qpilot/cli/core_cmds.py +493 -0
- qpilot-0.2.0/qpilot/cli/engine_cmds.py +586 -0
- qpilot-0.2.0/qpilot/cli/melt_cmds.py +154 -0
- qpilot-0.2.0/qpilot/cli/prism_cmds.py +360 -0
- qpilot-0.2.0/qpilot/cli/quality_cmds.py +597 -0
- qpilot-0.2.0/qpilot/cli/quant_cmds.py +192 -0
- qpilot-0.2.0/qpilot/cli/studio_cmds.py +465 -0
- qpilot-0.2.0/qpilot/core/__init__.py +1 -0
- qpilot-0.2.0/qpilot/core/card_kinds.py +190 -0
- qpilot-0.2.0/qpilot/core/confidence.py +95 -0
- qpilot-0.2.0/qpilot/core/consequence.py +190 -0
- qpilot-0.2.0/qpilot/core/decisions.py +436 -0
- qpilot-0.2.0/qpilot/core/facts.py +102 -0
- qpilot-0.2.0/qpilot/core/firewall.py +275 -0
- qpilot-0.2.0/qpilot/core/gate.py +582 -0
- qpilot-0.2.0/qpilot/core/instruments.py +1392 -0
- qpilot-0.2.0/qpilot/core/names.py +97 -0
- qpilot-0.2.0/qpilot/core/proposals.py +1073 -0
- qpilot-0.2.0/qpilot/core/pseudonym.py +165 -0
- qpilot-0.2.0/qpilot/core/record.py +1112 -0
- qpilot-0.2.0/qpilot/core/robustness.py +395 -0
- qpilot-0.2.0/qpilot/core/rules.py +609 -0
- qpilot-0.2.0/qpilot/core/store.py +1416 -0
- qpilot-0.2.0/qpilot/core/validator.py +137 -0
- qpilot-0.2.0/qpilot/core/vocabulary.py +104 -0
- qpilot-0.2.0/qpilot/data/hgnc/LICENCE +9 -0
- qpilot-0.2.0/qpilot/data/hgnc/README.md +65 -0
- qpilot-0.2.0/qpilot/data/hgnc/hgnc_symbols.tsv +45279 -0
- qpilot-0.2.0/qpilot/explain/__init__.py +1 -0
- qpilot-0.2.0/qpilot/explain/evidence_text.py +387 -0
- qpilot-0.2.0/qpilot/explain/instrument_text.py +211 -0
- qpilot-0.2.0/qpilot/explain/lookback_text.py +140 -0
- qpilot-0.2.0/qpilot/explain/quantity_text.py +82 -0
- qpilot-0.2.0/qpilot/explain/sets.py +138 -0
- qpilot-0.2.0/qpilot/explain/sheetcheck_text.py +243 -0
- qpilot-0.2.0/qpilot/explain/templates.py +552 -0
- qpilot-0.2.0/qpilot/explain/trace_text.py +460 -0
- qpilot-0.2.0/qpilot/explain/words.py +17 -0
- qpilot-0.2.0/qpilot/harness/__init__.py +1 -0
- qpilot-0.2.0/qpilot/harness/make_evalset.py +315 -0
- qpilot-0.2.0/qpilot/harness/report.py +201 -0
- qpilot-0.2.0/qpilot/harness/run.py +96 -0
- qpilot-0.2.0/qpilot/harness/score.py +144 -0
- qpilot-0.2.0/qpilot/harness/studio.py +949 -0
- qpilot-0.2.0/qpilot/history/__init__.py +19 -0
- qpilot-0.2.0/qpilot/history/compare.py +271 -0
- qpilot-0.2.0/qpilot/history/replay.py +701 -0
- qpilot-0.2.0/qpilot/history/studio.py +453 -0
- qpilot-0.2.0/qpilot/integration/__init__.py +25 -0
- qpilot-0.2.0/qpilot/integration/__main__.py +166 -0
- qpilot-0.2.0/qpilot/integration/mcp_tools.py +407 -0
- qpilot-0.2.0/qpilot/integration/simulator.py +865 -0
- qpilot-0.2.0/qpilot/integration/workflow.py +1070 -0
- qpilot-0.2.0/qpilot/io/README.md +42 -0
- qpilot-0.2.0/qpilot/io/__init__.py +1 -0
- qpilot-0.2.0/qpilot/io/connectors/__init__.py +52 -0
- qpilot-0.2.0/qpilot/io/connectors/base.py +364 -0
- qpilot-0.2.0/qpilot/io/connectors/builder.py +346 -0
- qpilot-0.2.0/qpilot/io/connectors/cfx.py +293 -0
- qpilot-0.2.0/qpilot/io/connectors/common.py +264 -0
- qpilot-0.2.0/qpilot/io/connectors/headers.py +81 -0
- qpilot-0.2.0/qpilot/io/connectors/lightcycler.py +243 -0
- qpilot-0.2.0/qpilot/io/connectors/quantstudio.py +587 -0
- qpilot-0.2.0/qpilot/io/connectors/rdml_file.py +199 -0
- qpilot-0.2.0/qpilot/io/connectors/registry.py +531 -0
- qpilot-0.2.0/qpilot/io/connectors/sample_types.py +105 -0
- qpilot-0.2.0/qpilot/io/connectors/tables.py +502 -0
- qpilot-0.2.0/qpilot/io/connectors/tidy.py +328 -0
- qpilot-0.2.0/qpilot/io/datasets.py +220 -0
- qpilot-0.2.0/qpilot/io/inputs.py +151 -0
- qpilot-0.2.0/qpilot/io/rawness.py +377 -0
- qpilot-0.2.0/qpilot/io/rdml_io.py +747 -0
- qpilot-0.2.0/qpilot/io/sheets.py +989 -0
- qpilot-0.2.0/qpilot/io/symbols.py +419 -0
- qpilot-0.2.0/qpilot/io/trust.py +310 -0
- qpilot-0.2.0/qpilot/mcp_server.py +2325 -0
- qpilot-0.2.0/qpilot/models/__init__.py +21 -0
- qpilot-0.2.0/qpilot/models/curve_judge/METHOD_CARD.md +161 -0
- qpilot-0.2.0/qpilot/models/curve_judge/metrics.json +1888 -0
- qpilot-0.2.0/qpilot/models/curve_judge/model.json +1 -0
- qpilot-0.2.0/qpilot/models/curve_judge/model.sha256 +1 -0
- qpilot-0.2.0/qpilot/models/curve_judge/transfer.json +1844 -0
- qpilot-0.2.0/qpilot/prism/__init__.py +660 -0
- qpilot-0.2.0/qpilot/prism/project.py +580 -0
- qpilot-0.2.0/qpilot/prism/provenance.py +393 -0
- qpilot-0.2.0/qpilot/prism/pzfx.py +653 -0
- qpilot-0.2.0/qpilot/prism/roundtrip.py +1195 -0
- qpilot-0.2.0/qpilot/prism/script.py +454 -0
- qpilot-0.2.0/qpilot/prism/tables.py +461 -0
- qpilot-0.2.0/qpilot/prism/templates/README.md +109 -0
- qpilot-0.2.0/qpilot/qpcr/__init__.py +6 -0
- qpilot-0.2.0/qpilot/qpcr/absolute.py +1422 -0
- qpilot-0.2.0/qpilot/qpcr/absolute_study.py +156 -0
- qpilot-0.2.0/qpilot/qpcr/amp_call.py +341 -0
- qpilot-0.2.0/qpilot/qpcr/axes/__init__.py +322 -0
- qpilot-0.2.0/qpilot/qpcr/axes/efficiency.py +257 -0
- qpilot-0.2.0/qpilot/qpcr/axes/normaliser.py +343 -0
- qpilot-0.2.0/qpilot/qpcr/cards.py +62 -0
- qpilot-0.2.0/qpilot/qpcr/confidence_checks.py +176 -0
- qpilot-0.2.0/qpilot/qpcr/contrast.py +274 -0
- qpilot-0.2.0/qpilot/qpcr/cq_only.py +304 -0
- qpilot-0.2.0/qpilot/qpcr/curve_judge.py +709 -0
- qpilot-0.2.0/qpilot/qpcr/curves.py +781 -0
- qpilot-0.2.0/qpilot/qpcr/design.py +90 -0
- qpilot-0.2.0/qpilot/qpcr/design_checks.py +1478 -0
- qpilot-0.2.0/qpilot/qpcr/design_rules.py +1187 -0
- qpilot-0.2.0/qpilot/qpcr/designs.py +929 -0
- qpilot-0.2.0/qpilot/qpcr/evidence.py +1011 -0
- qpilot-0.2.0/qpilot/qpcr/export.py +1032 -0
- qpilot-0.2.0/qpilot/qpcr/export_runs.py +85 -0
- qpilot-0.2.0/qpilot/qpcr/gate_instr.py +230 -0
- qpilot-0.2.0/qpilot/qpcr/gate_lines.py +33 -0
- qpilot-0.2.0/qpilot/qpcr/gate_rt.py +245 -0
- qpilot-0.2.0/qpilot/qpcr/gate_rules.py +2189 -0
- qpilot-0.2.0/qpilot/qpcr/inspect.py +482 -0
- qpilot-0.2.0/qpilot/qpcr/instrument_context.py +2278 -0
- qpilot-0.2.0/qpilot/qpcr/interrun.py +344 -0
- qpilot-0.2.0/qpilot/qpcr/layout.py +881 -0
- qpilot-0.2.0/qpilot/qpcr/lookback.py +683 -0
- qpilot-0.2.0/qpilot/qpcr/melt.py +832 -0
- qpilot-0.2.0/qpilot/qpcr/multiplex.py +183 -0
- qpilot-0.2.0/qpilot/qpcr/multiplicity.py +189 -0
- qpilot-0.2.0/qpilot/qpcr/normfinder.py +383 -0
- qpilot-0.2.0/qpilot/qpcr/plan.py +1082 -0
- qpilot-0.2.0/qpilot/qpcr/position_history.py +480 -0
- qpilot-0.2.0/qpilot/qpcr/quant.py +248 -0
- qpilot-0.2.0/qpilot/qpcr/quantify_study.py +718 -0
- qpilot-0.2.0/qpilot/qpcr/refgenes.py +237 -0
- qpilot-0.2.0/qpilot/qpcr/repeat.py +868 -0
- qpilot-0.2.0/qpilot/qpcr/routes.py +166 -0
- qpilot-0.2.0/qpilot/qpcr/run_links.py +153 -0
- qpilot-0.2.0/qpilot/qpcr/sheetcheck.py +2980 -0
- qpilot-0.2.0/qpilot/qpcr/space.py +1455 -0
- qpilot-0.2.0/qpilot/qpcr/study.py +2101 -0
- qpilot-0.2.0/qpilot/qpcr/trace.py +2924 -0
- qpilot-0.2.0/qpilot/qpcr/trace_bench.py +1126 -0
- qpilot-0.2.0/qpilot/qpcr/validation.py +434 -0
- qpilot-0.2.0/qpilot/report/__init__.py +31 -0
- qpilot-0.2.0/qpilot/report/build.py +333 -0
- qpilot-0.2.0/qpilot/report/bundle.py +419 -0
- qpilot-0.2.0/qpilot/report/context.py +212 -0
- qpilot-0.2.0/qpilot/report/excel.py +462 -0
- qpilot-0.2.0/qpilot/report/figures.py +994 -0
- qpilot-0.2.0/qpilot/report/fmt.py +80 -0
- qpilot-0.2.0/qpilot/report/html.py +385 -0
- qpilot-0.2.0/qpilot/report/inputs.py +217 -0
- qpilot-0.2.0/qpilot/report/markdown.py +145 -0
- qpilot-0.2.0/qpilot/report/methods.py +919 -0
- qpilot-0.2.0/qpilot/report/miqe.py +860 -0
- qpilot-0.2.0/qpilot/report/miqe2_items.yaml +92 -0
- qpilot-0.2.0/qpilot/report/model.py +235 -0
- qpilot-0.2.0/qpilot/report/rdml_meta.py +237 -0
- qpilot-0.2.0/qpilot/report/rdml_out.py +257 -0
- qpilot-0.2.0/qpilot/report/review.py +761 -0
- qpilot-0.2.0/qpilot/report/run_review.py +624 -0
- qpilot-0.2.0/qpilot/report/svg.py +218 -0
- qpilot-0.2.0/qpilot/report/tables.py +1558 -0
- qpilot-0.2.0/qpilot/report/text.py +220 -0
- qpilot-0.2.0/qpilot/schemas.py +318 -0
- qpilot-0.2.0/qpilot/schemas_studio.py +787 -0
- qpilot-0.2.0/qpilot/studio_axes.py +143 -0
- qpilot-0.2.0/qpilot/studio_design.py +1063 -0
- qpilot-0.2.0/qpilot/studio_service.py +3299 -0
- qpilot-0.2.0/qpilot/studio_state.py +1302 -0
- qpilot-0.2.0/qpilot/trace/__init__.py +68 -0
- qpilot-0.2.0/qpilot/trace/context.py +771 -0
- qpilot-0.2.0/qpilot/trace/gene.py +964 -0
- qpilot-0.2.0/qpilot/trace/schema.py +398 -0
- qpilot-0.2.0/qpilot/trace/study.py +321 -0
- qpilot-0.2.0/qpilot/trace/welch.py +678 -0
- qpilot-0.2.0/qpilot/trace/words.py +149 -0
- qpilot-0.2.0/qpilot/vendor/__init__.py +1 -0
- qpilot-0.2.0/qpilot/vendor/rdmlpython/LICENSE +22 -0
- qpilot-0.2.0/qpilot/vendor/rdmlpython/README_VENDOR.md +39 -0
- qpilot-0.2.0/qpilot/vendor/rdmlpython/__init__.py +1 -0
- qpilot-0.2.0/qpilot/vendor/rdmlpython/rdml.py +15562 -0
- qpilot-0.2.0/qpilot/vendor/rdmlpython/schema/LICENSE +22 -0
- qpilot-0.2.0/qpilot/vendor/rdmlpython/schema/RDML_v1_0_REC.xsd +1206 -0
- qpilot-0.2.0/qpilot/vendor/rdmlpython/schema/RDML_v1_1_REC.xsd +1276 -0
- qpilot-0.2.0/qpilot/vendor/rdmlpython/schema/RDML_v1_2_REC.xsd +1308 -0
- qpilot-0.2.0/qpilot/vendor/rdmlpython/schema/RDML_v1_3_REC.xsd +1606 -0
- qpilot-0.2.0/qpilot/vendor/rdmlpython/schema/RDML_v1_4_CR.xsd +1660 -0
- qpilot-0.2.0/qpilot/web/__init__.py +1 -0
- qpilot-0.2.0/qpilot/web/absolute_views.py +505 -0
- qpilot-0.2.0/qpilot/web/axes_views.py +312 -0
- qpilot-0.2.0/qpilot/web/cache.py +277 -0
- qpilot-0.2.0/qpilot/web/compare_views.py +443 -0
- qpilot-0.2.0/qpilot/web/context.py +133 -0
- qpilot-0.2.0/qpilot/web/contract.py +1303 -0
- qpilot-0.2.0/qpilot/web/contract_absolute.py +319 -0
- qpilot-0.2.0/qpilot/web/contract_audit.py +776 -0
- qpilot-0.2.0/qpilot/web/contract_capture.py +84 -0
- qpilot-0.2.0/qpilot/web/contract_claims.py +63 -0
- qpilot-0.2.0/qpilot/web/contract_compare.py +139 -0
- qpilot-0.2.0/qpilot/web/contract_cqonly.py +93 -0
- qpilot-0.2.0/qpilot/web/contract_design.py +259 -0
- qpilot-0.2.0/qpilot/web/contract_evidence.py +173 -0
- qpilot-0.2.0/qpilot/web/contract_files.py +433 -0
- qpilot-0.2.0/qpilot/web/contract_gate.py +303 -0
- qpilot-0.2.0/qpilot/web/contract_history.py +366 -0
- qpilot-0.2.0/qpilot/web/contract_instruments.py +264 -0
- qpilot-0.2.0/qpilot/web/contract_melt.py +163 -0
- qpilot-0.2.0/qpilot/web/contract_norm.py +72 -0
- qpilot-0.2.0/qpilot/web/contract_ops.py +247 -0
- qpilot-0.2.0/qpilot/web/contract_planner.py +370 -0
- qpilot-0.2.0/qpilot/web/contract_report.py +195 -0
- qpilot-0.2.0/qpilot/web/contract_sheets.py +184 -0
- qpilot-0.2.0/qpilot/web/contract_trace.py +308 -0
- qpilot-0.2.0/qpilot/web/contract_trust.py +139 -0
- qpilot-0.2.0/qpilot/web/contract_units.py +134 -0
- qpilot-0.2.0/qpilot/web/cq_table_views.py +235 -0
- qpilot-0.2.0/qpilot/web/curves.py +113 -0
- qpilot-0.2.0/qpilot/web/events.py +301 -0
- qpilot-0.2.0/qpilot/web/folders.py +383 -0
- qpilot-0.2.0/qpilot/web/intake.py +661 -0
- qpilot-0.2.0/qpilot/web/jobs.py +234 -0
- qpilot-0.2.0/qpilot/web/openapi.py +30 -0
- qpilot-0.2.0/qpilot/web/ratelimit.py +642 -0
- qpilot-0.2.0/qpilot/web/references_views.py +165 -0
- qpilot-0.2.0/qpilot/web/results_views.py +1507 -0
- qpilot-0.2.0/qpilot/web/routers/__init__.py +48 -0
- qpilot-0.2.0/qpilot/web/routers/absolute.py +205 -0
- qpilot-0.2.0/qpilot/web/routers/audit.py +1179 -0
- qpilot-0.2.0/qpilot/web/routers/claims.py +113 -0
- qpilot-0.2.0/qpilot/web/routers/cqonly.py +29 -0
- qpilot-0.2.0/qpilot/web/routers/design.py +319 -0
- qpilot-0.2.0/qpilot/web/routers/evidence.py +528 -0
- qpilot-0.2.0/qpilot/web/routers/files.py +1141 -0
- qpilot-0.2.0/qpilot/web/routers/gate.py +1375 -0
- qpilot-0.2.0/qpilot/web/routers/history.py +111 -0
- qpilot-0.2.0/qpilot/web/routers/instruments.py +590 -0
- qpilot-0.2.0/qpilot/web/routers/melt.py +358 -0
- qpilot-0.2.0/qpilot/web/routers/ops.py +1286 -0
- qpilot-0.2.0/qpilot/web/routers/planner.py +775 -0
- qpilot-0.2.0/qpilot/web/routers/references.py +36 -0
- qpilot-0.2.0/qpilot/web/routers/report.py +519 -0
- qpilot-0.2.0/qpilot/web/routers/results.py +426 -0
- qpilot-0.2.0/qpilot/web/routers/sheets.py +254 -0
- qpilot-0.2.0/qpilot/web/routers/studio.py +748 -0
- qpilot-0.2.0/qpilot/web/routers/system.py +150 -0
- qpilot-0.2.0/qpilot/web/routers/trace.py +366 -0
- qpilot-0.2.0/qpilot/web/routers/trust.py +280 -0
- qpilot-0.2.0/qpilot/web/routers/units.py +51 -0
- qpilot-0.2.0/qpilot/web/routers/where.py +652 -0
- qpilot-0.2.0/qpilot/web/routers/workspaces.py +398 -0
- qpilot-0.2.0/qpilot/web/security.py +404 -0
- qpilot-0.2.0/qpilot/web/server.py +280 -0
- qpilot-0.2.0/qpilot/web/static/assets/AskDrawer-CUxSUS-x.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/ComparisonsPanel-D3FXhNHz.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/FactText-Dz8wh-gO.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/MeltPanel-Cw8Nsxcc.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/PrismFigure-CkTa0blX.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/RepeatPlan-BChHKdfV.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/TraceLink-DeQ7800y.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/WellCurve-CHQuPKbV.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/adapters-PmyB1S-K.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/agents-CoQ0p6Pa.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/api-B4NH57vK.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/api-lNzRMZwq.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/arrow-left-J4KquhEj.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/arrow-left-right-DskOmiWB.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/arrow-up-right-CUA0BG1N.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/audit-DoqbPwf9.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/charts-C5usZK2E.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/chevron-left-dRUodBJI.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/circle-minus-DeoM5xIK.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/circle-question-mark-VCGmGiNy.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/claims-DcoY19HA.js +2 -0
- qpilot-0.2.0/qpilot/web/static/assets/cpu-DlUwpr2b.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/curves-B1ifh85o.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/curves-XOLvFA_8.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/data-5sJA9TGe.js +3 -0
- qpilot-0.2.0/qpilot/web/static/assets/data-CnsZ1jie.css +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/design-96g2-GCi.js +4 -0
- qpilot-0.2.0/qpilot/web/static/assets/download-CbMqoFTx.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/eye-BfvztZUo.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/file-archive-BHfTFACh.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/file-down-C3_7QBi7.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/gate-CkJHqoy7.js +2 -0
- qpilot-0.2.0/qpilot/web/static/assets/gene-C_xoqCdK.js +2 -0
- qpilot-0.2.0/qpilot/web/static/assets/git-compare-DbZBcP5d.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/history-DpMybOc0.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/home-Cj1zA296.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/inbox-CYTddybL.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/index-9TwPw7mS.css +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/index-BgRULiZl.js +10 -0
- qpilot-0.2.0/qpilot/web/static/assets/info-BswNFHHj.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/instruments-jd32H0mH.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/jetbrains-mono-cyrillic-ext-wght-normal-EocZY2iu.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- qpilot-0.2.0/qpilot/web/static/assets/layers-D2r9RZW0.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/map-pin-BiPNAHHZ.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/planner-DxsNPgnB.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/plate-Dji8Bed1.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/plus-bJK5ipt2.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/point-lCZbdOEQ.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/prism-Beki88_Y.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/proposal-B8JUMKOg.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/react-D_CZztLv.js +9 -0
- qpilot-0.2.0/qpilot/web/static/assets/record-0B21W6z5.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/report-DUyk8Imm.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/results-C-a1kb0M.js +2 -0
- qpilot-0.2.0/qpilot/web/static/assets/rotate-ccw-BAyh8Avz.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/sections-CKGrfpub.js +2 -0
- qpilot-0.2.0/qpilot/web/static/assets/settings-2NKo8-rw.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/sheetcheck-u29C7m87.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/shield-alert-CM5JXUmj.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/shield-check-CR27b7sP.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/sparkles-BSio9YeX.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/src-DmrZXBmt.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/table-2-7dzo7oVA.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/trace-OgKBXZhz.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/ui-B9WviEwM.js +44 -0
- qpilot-0.2.0/qpilot/web/static/assets/ui-Dlfa4Gi0.css +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/undo-2-CNANXPsX.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/unlink-_3yYur6c.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/upload-CEiVqAZ1.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/useMutation-C3bsgiJY.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/useOpenWorkspace-CiHqAp_k.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/useQueries-Bpk0ankz.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/user-fGY6PXpe.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/words-B1YuDegT.js +1 -0
- qpilot-0.2.0/qpilot/web/static/assets/wrench-gyiojqmh.js +1 -0
- qpilot-0.2.0/qpilot/web/static/favicon.svg +1 -0
- qpilot-0.2.0/qpilot/web/static/index.html +25 -0
- qpilot-0.2.0/qpilot/web/studio_views.py +668 -0
- qpilot-0.2.0/qpilot/web/units_views.py +414 -0
- qpilot-0.2.0/qpilot/web/views.py +666 -0
- qpilot-0.2.0/qpilot/web/warm.py +562 -0
- qpilot-0.2.0/qpilot_profile.example.yaml +18 -0
- qpilot-0.2.0/sim/README.md +235 -0
- qpilot-0.2.0/sim/src/qsim/__init__.py +3 -0
- qpilot-0.2.0/sim/src/qsim/app.py +883 -0
- qpilot-0.2.0/sim/src/qsim/app_lab.py +512 -0
- qpilot-0.2.0/sim/src/qsim/ask.py +650 -0
- qpilot-0.2.0/sim/src/qsim/attribution.py +160 -0
- qpilot-0.2.0/sim/src/qsim/benchmark.py +182 -0
- qpilot-0.2.0/sim/src/qsim/cli.py +258 -0
- qpilot-0.2.0/sim/src/qsim/compare.py +83 -0
- qpilot-0.2.0/sim/src/qsim/data.py +388 -0
- qpilot-0.2.0/sim/src/qsim/decisions.py +293 -0
- qpilot-0.2.0/sim/src/qsim/demo.py +271 -0
- qpilot-0.2.0/sim/src/qsim/designs.py +100 -0
- qpilot-0.2.0/sim/src/qsim/engine.py +493 -0
- qpilot-0.2.0/sim/src/qsim/explore.py +178 -0
- qpilot-0.2.0/sim/src/qsim/external.py +623 -0
- qpilot-0.2.0/sim/src/qsim/inference.py +146 -0
- qpilot-0.2.0/sim/src/qsim/influence.py +217 -0
- qpilot-0.2.0/sim/src/qsim/issues.py +248 -0
- qpilot-0.2.0/sim/src/qsim/known_answers.py +1141 -0
- qpilot-0.2.0/sim/src/qsim/moves.py +473 -0
- qpilot-0.2.0/sim/src/qsim/nodiff.py +264 -0
- qpilot-0.2.0/sim/src/qsim/plates.py +277 -0
- qpilot-0.2.0/sim/src/qsim/report.py +629 -0
- qpilot-0.2.0/sim/src/qsim/study_space.py +134 -0
- qpilot-0.2.0/sim/src/qsim/troubleshoot.py +360 -0
- qpilot-0.2.0/sim/src/qsim/truth.py +227 -0
- qpilot-0.2.0/sim/src/qsim/validate.py +400 -0
- qpilot-0.2.0/sim/src/qsim/verdict.py +147 -0
qpilot-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.venv/
|
|
6
|
+
venv/
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
|
|
13
|
+
# Data: the demo dataset is fetched at runtime and never committed
|
|
14
|
+
*.rdml
|
|
15
|
+
!tests/fixtures/*.rdml
|
|
16
|
+
# The public instrument-export corpus: small files, byte-identical to source, listed in MANIFEST.yaml.
|
|
17
|
+
!tests/fixtures/corpus/**/*.rdml
|
|
18
|
+
# The Decision Simulator's validation plates are small public files, committed with provenance.
|
|
19
|
+
!sim/data/**/*.rdml
|
|
20
|
+
.cache/
|
|
21
|
+
*.tmp
|
|
22
|
+
|
|
23
|
+
# Local outputs
|
|
24
|
+
/out/
|
|
25
|
+
/records/
|
|
26
|
+
*.zip
|
|
27
|
+
sbom.json
|
|
28
|
+
|
|
29
|
+
# OS / editors
|
|
30
|
+
.DS_Store
|
|
31
|
+
.idea/
|
|
32
|
+
.vscode/
|
|
33
|
+
|
|
34
|
+
# The online demo: its build (npm run build:demo) and the assembled site
|
|
35
|
+
frontend/dist-demo/
|
|
36
|
+
/site/*
|
|
37
|
+
!/site/vercel.json
|
|
38
|
+
!/site/README.md
|
|
39
|
+
/site-proxy/
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to qPilot are recorded here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). qPilot is research software:
|
|
6
|
+
research use only, not for clinical decisions.
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.0] — 2026-10-28
|
|
11
|
+
|
|
12
|
+
From the demo study to a lab's own files: qPilot now opens the files labs actually publish
|
|
13
|
+
(multiplex plates and Cq tables included), lets a person finish the design on the page without a
|
|
14
|
+
model, compares several groups, quantifies against standards (or only quantifies, with no groups
|
|
15
|
+
at all), reads melt curves, plans the repeat, and writes the numbers, figures and a Prism project
|
|
16
|
+
a paper needs. The rule is unchanged: the software never makes a choice that can
|
|
17
|
+
change a conclusion; a person does, with a reason, in the hash-chained record.
|
|
18
|
+
|
|
19
|
+
On the demo study every design hash, study id and the locked demo output are unchanged, except
|
|
20
|
+
where a feature adds to them on purpose: the Run Gate pack is now `research-default` 1.4.0 (one
|
|
21
|
+
G-MELT and one G-IPC "not applicable" line; 1.4.0's rule changes give the demo the same verdicts), MIQE 5.19 reads the melt rule's sentence, MIQE 5.14 is answered
|
|
22
|
+
from the standard curves, and the exports gain the Standard curves and Quantities tables and a
|
|
23
|
+
Methods paragraph on them.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **The science review's offered options** (29 Oct 2026, [`docs/science-review-2026-10.md`](docs/science-review-2026-10.md)):
|
|
27
|
+
the amplification rule **"LinRegPCR's call, its 'no amplification' respected"**
|
|
28
|
+
(`linregpcr_call`; offered by the curves stage when a file has such wells, with how many; the
|
|
29
|
+
demo has 11 and no call changes); **standards that look wrong** (the series' order broken, or
|
|
30
|
+
a studentized residual beyond ±3.5) listed under their curve with the curve without them, and
|
|
31
|
+
**Propose leaving these out** files one `curve.exclude_well` per well for a person to decide
|
|
32
|
+
(`POST /api/studies/{id}/quantities/standards`); **Fisher's exact test** on the detection counts
|
|
33
|
+
of a gene detected in one group only (orientation, never a call); the LOD's words say how many
|
|
34
|
+
wells per level stand behind it (ten replicates around the limit recommended); NormFinder's
|
|
35
|
+
warning names the genes whose negative variance it counted as zero.
|
|
36
|
+
|
|
37
|
+
#### Getting files in
|
|
38
|
+
- **Real lab files open** (`qpilot/io/`, `qpilot/io/connectors/`): RDML archives whatever their
|
|
39
|
+
member is called, RDML 1.0 (StepOne Software) and 1.1–1.2 slips the vendored upgrader leaves,
|
|
40
|
+
LinRegPCR-written RDML 1.3, LightCycler 96 files with the samples' and targets' real names
|
|
41
|
+
(from their RDML description), and a person's own Excel **Cq table** (Sample, Gene, Condition,
|
|
42
|
+
Gene Type, Cq): its Gene Type column proposes the reference genes, its condition column becomes
|
|
43
|
+
the sample sheet, and its numbers are never shown before the design and curves are confirmed.
|
|
44
|
+
Every refusal of an RDML file says its own reason in words; reports and exports name a person's
|
|
45
|
+
own files, never qPilot's hashed copy.
|
|
46
|
+
- **A table of Cq values, analysed without curves** (`qpilot/qpcr/cq_only.py`,
|
|
47
|
+
`GET /api/sessions/{sid}/cq-table`, [`docs/cq-tables.md`](docs/cq-tables.md)): a Cq table
|
|
48
|
+
(Excel or CSV) or an RDML file without amplification curves goes through the same Studio
|
|
49
|
+
stages, engine and Analysis Record. Each well's N0 is E^−Cq, with E from the assay's dilution
|
|
50
|
+
series when the plate has one, else assumed 2.0 (then the log2 fold change is exactly −ΔΔCq);
|
|
51
|
+
the Curves stage shows **Your Cq table** to confirm instead of curves; what needs a curve
|
|
52
|
+
(LinRegPCR's fit, the Curve Judge, Import Trust, melt, two efficiency models) is said not to
|
|
53
|
+
apply; confidence is capped at Low while an efficiency is assumed, Moderate when every assay's
|
|
54
|
+
comes from its series.
|
|
55
|
+
- **Multiplex plates** (`qpilot/qpcr/multiplex.py`, [`docs/multiplex.md`](docs/multiplex.md)): every
|
|
56
|
+
dye of a well gets its own baseline, efficiency, Cq and N0. Before, the vendored LinRegPCR gave
|
|
57
|
+
the reaction volume to the first curve of a well only, so every other dye's N0 was 0 and a
|
|
58
|
+
duplex lost its reference gene to "undetermined" (on the corpus: LightCycler 96 X/Y/IPC 0 → 12,
|
|
59
|
+
12, 14 curves with an N0; QuantStudio 7 Pro IPC 0 → 95; CFX 4-plex 0 → 96 per dye; 7500 nCoV
|
|
60
|
+
0 → 21). A gene's relative quantity uses the reference measured in the same well, and leaving a
|
|
61
|
+
multiplex well out leaves out all its curves. Plate maps show the dyes per well, with a target
|
|
62
|
+
picker. Singleplex plates (the demo) are analysed exactly as before.
|
|
63
|
+
- **Instrument exports on Open** (`qpilot/io/connectors/`, `qpilot/web/intake.py`): drop a Bio-Rad
|
|
64
|
+
CFX export set, a QuantStudio / StepOne / 7500 export, a LightCycler 480 raw-data text or Cp
|
|
65
|
+
table, an RDES or a tidy table; qPilot sorts the files by content, reads them once into
|
|
66
|
+
deterministic RDML and opens that as a Studio session. The Design section shows what was read
|
|
67
|
+
(plates, wells, sample types, vendor Cq, omitted wells) and whether the curves look raw,
|
|
68
|
+
baseline-subtracted or normalised (`qpilot/io/rawness.py`).
|
|
69
|
+
- **The Import Trust card** (`capture.import_trust`, `qpilot/io/trust.py`,
|
|
70
|
+
`GET|POST /api/studies/{id}/import-trust[/decide]`): a Studio session opened from an export whose
|
|
71
|
+
curves were most likely baseline-subtracted before export (or whose processing qPilot could not
|
|
72
|
+
tell) is analysed from the curves as imported with every gene's confidence capped at Moderate
|
|
73
|
+
until a person decides on Results: re-export the raw curves, analyse them as they are (capped at
|
|
74
|
+
Moderate), or use the instrument's Cq values only (quantity 2^-Cq, capped at Low). The choice
|
|
75
|
+
is a `curve.import_trust` proposal a person accepts with a reason; an agent's request waits for
|
|
76
|
+
a person. Curves normalised to a passive reference (Rn) get no card. `StudyDesign.import_trust`
|
|
77
|
+
carries it, so a record re-runs to the same grades.
|
|
78
|
+
- **Which files qPilot reads** ([`docs/conformance.md`](docs/conformance.md)), generated by
|
|
79
|
+
`python scripts/conformance.py` over the public corpus: every real set with curves or Cq values
|
|
80
|
+
opens (newly the CFX RDML with a non-standard member name, the 17-plate soybean Cq-only RDML,
|
|
81
|
+
StepOne's RDML 1.0, LinRegPCR's 6-plate RDML and the hmuY Cq workbook); an isothermal plate
|
|
82
|
+
read and two RDML files dropped together are refused in words.
|
|
83
|
+
- Public instrument-export corpus for tests (`tests/fixtures/corpus/`, 18 real sets with
|
|
84
|
+
manifests and SHA-256, 2 synthetic sets with known truth) and red-team reader tests
|
|
85
|
+
(`tests/redteam/`).
|
|
86
|
+
|
|
87
|
+
#### Design
|
|
88
|
+
- **Finish the design on the page, with no model** (`qpilot/studio_design.py`,
|
|
89
|
+
`qpilot/web/routers/design.py`, [`docs/design-editor.md`](docs/design-editor.md)): set target
|
|
90
|
+
roles (reference gene, gene of interest, leave out), sample groups, sample types, standards
|
|
91
|
+
(top quantity and dilution step) and biological units by selecting samples or wells, or by
|
|
92
|
+
pasting two columns from Excel; choose the comparison; download a sample sheet pre-filled with
|
|
93
|
+
the plate's names, or attach one to the session. Each change is the person's own proposal,
|
|
94
|
+
checked by the engine and recorded with the person's reason (`made_by.kind = "person"`, only a
|
|
95
|
+
person can make one). Sample sheets may carry a target-role column (Gene Type, Reference,
|
|
96
|
+
Housekeeping, …). A whole 96-well plate records in about a second.
|
|
97
|
+
- **What the study is for: compare groups or quantify samples** (`StudyDesign.purpose`,
|
|
98
|
+
`design.purpose`, `qpilot/qpcr/quantify_study.py`): a run that compares nothing (a viral load,
|
|
99
|
+
wastewater, a construct's copy number, a library) chooses **Quantify samples** on the Design
|
|
100
|
+
page. It needs standards with quantities, not a comparison or a reference gene, and reaches
|
|
101
|
+
Results with each sample's quantity and what is reported for it (quantified, below the LOQ or
|
|
102
|
+
LOD, not detected, extrapolated). Two Decision Cards (which wells count as detected; how precise
|
|
103
|
+
a quantity must be to be reported) wait for a person only when another option changes what is
|
|
104
|
+
reported for a sample. Controls, the report, the exports, the record and MCP `qpcr_quantities`
|
|
105
|
+
follow; Gene, Audit, Trace, Claims, Prism and Plan next, which compare groups, say why they are
|
|
106
|
+
locked. Absent by default: every existing design hash is unchanged.
|
|
107
|
+
- **Several comparisons proposed by Studio**: with three or more groups the rules propose each
|
|
108
|
+
group against a control (when a group's name says control) or every pair, Holm-corrected; a
|
|
109
|
+
donor, animal or passage column proposes pairings (`design.pair`) for paired tests. A person
|
|
110
|
+
accepts, edits or rejects the plan like any proposal.
|
|
111
|
+
- `ProposalStore.decide_together`: one click of a person that proposes and decides several
|
|
112
|
+
proposals in one locked write, all or nothing (about 80 s → 1.1 s for 189 items).
|
|
113
|
+
|
|
114
|
+
#### Comparisons & statistics
|
|
115
|
+
- **Several comparisons per study** (`qpilot/qpcr/contrast.py`, `GET /api/studies/{id}/comparisons`,
|
|
116
|
+
`?comparison=` on the results routes): each group against a control, every pair, or a list a
|
|
117
|
+
person chooses. Each gene's p-values are corrected with Holm's method across its comparisons,
|
|
118
|
+
and q-values (Benjamini–Hochberg) are shown across genes. Pair by donor, animal or passage for a
|
|
119
|
+
paired t-test. With three or more groups Welch's one-way ANOVA is shown per gene, for
|
|
120
|
+
orientation only (never a call). Results has a comparison picker and every gene across every
|
|
121
|
+
comparison; Methods, MCP, the CLI and the Analysis Record follow (`grid_comparisons.csv`).
|
|
122
|
+
Method card [`methods/comparisons.md`](methods/comparisons.md).
|
|
123
|
+
- **Several comparisons in every section**: a Decision Card counts a changed call in every
|
|
124
|
+
comparison, so a choice that changes a call only in a later comparison waits for a person (it
|
|
125
|
+
names the comparison and shows each option's effect per comparison; the choice applies to the
|
|
126
|
+
whole study); an opt-in choice enters the grid when it matters in any comparison. Trace and
|
|
127
|
+
Where to look (`?comparison=`, MCP `qpcr_trace(comparison=…)`), a gene's page
|
|
128
|
+
(`/genes/{gene}/units?comparison=`; the figure draws every group with a bracket per
|
|
129
|
+
comparison) and History follow the comparison picked, with the corrected p the call uses; the
|
|
130
|
+
Run Review names each comparison. The robustness audit audits the first comparison and says
|
|
131
|
+
so; it refuses, in words, a paired or pooled-variance comparison or one whose correction
|
|
132
|
+
changes a call. Studies with one comparison are unchanged.
|
|
133
|
+
- **Dunnett's and Tukey's corrections** (`qpilot/qpcr/multiplicity.py`): a person's choice beside
|
|
134
|
+
Holm and none: Dunnett for each group against a control, Tukey(–Kramer) for every pair, each a
|
|
135
|
+
single-step adjusted p from a t-test with the spread pooled over the plan's groups (equal
|
|
136
|
+
spread on the log2 scale assumed; unpaired designs only).
|
|
137
|
+
- **Several comparisons on the Design page**: Design → Comparison → **Several comparisons**: each
|
|
138
|
+
group against a control, every pair of groups, or your own list, and the correction.
|
|
139
|
+
- **The robustness audit runs on designs with technical replicates** (`sim/`, `qpilot/integration/`):
|
|
140
|
+
each biological unit's wells are combined as qPilot combines them (the mean of their log2
|
|
141
|
+
amounts), so the audit reproduces qPilot at its baseline; with three or more wells per value it
|
|
142
|
+
also varies how replicates are combined (a ninth decision, `replicates`: the median well, or
|
|
143
|
+
leaving out one well outside the expected spread).
|
|
144
|
+
- **Gene → Your samples from the engine** (`GET /api/studies/{id}/genes/{gene}/units`): each
|
|
145
|
+
biological unit's value relative to the second group's mean, its replicate wells as small dots
|
|
146
|
+
on its line, each group's mean with its 95 % CI, without needing an audit. Click a unit for its
|
|
147
|
+
wells and curves.
|
|
148
|
+
|
|
149
|
+
#### Quantities & normalisation
|
|
150
|
+
- **Absolute quantification** (`qpilot/qpcr/absolute.py`, `qpilot/qpcr/absolute_study.py`,
|
|
151
|
+
`GET /api/studies/{id}/quantities[/{target}]`, [`docs/quantities.md`](docs/quantities.md)):
|
|
152
|
+
Results → **Quantities** shows standard curves (per plate, pooled across plates that carry the
|
|
153
|
+
same series), the efficiency with its 95 % CI, the limit of detection (95 % of wells detected;
|
|
154
|
+
logistic when a level is partly detected) and of quantification (back-calculated CV ≤ 35 %),
|
|
155
|
+
and every unit's copies with a 95 % prediction interval and plain-word flags (extrapolated,
|
|
156
|
+
below the LOD or LOQ). Per µL and per ng once a person records the reaction input
|
|
157
|
+
(`POST /api/studies/{id}/quantities/reaction`, a `choice.reaction_input` proposal, kept out of
|
|
158
|
+
the design hash). An ng → copies helper on the Design page (`GET /api/tools/copies`);
|
|
159
|
+
`qpilot quantify FILE…` and `qpilot copies --ng --length`. Standard curves and Quantities tables
|
|
160
|
+
in every export, a Methods paragraph, MIQE 5.13/5.14 answered from the curves. Method card
|
|
161
|
+
[`methods/absolute_quantification.md`](methods/absolute_quantification.md). On a real
|
|
162
|
+
QuantStudio 3 export: slope −3.327, efficiency 99.8 %.
|
|
163
|
+
- **NormFinder** (Andersen et al. 2004; `qpilot/qpcr/normfinder.py`,
|
|
164
|
+
`GET /api/studies/{id}/references`): next to geNorm on the reference-gene panel, with a plain
|
|
165
|
+
verdict on the best pair. Which reference genes are used stays a person's choice on the
|
|
166
|
+
reference-gene card. Method card [`methods/normfinder.md`](methods/normfinder.md).
|
|
167
|
+
- **What to normalise each gene to, opt-in** (`qpilot/qpcr/axes/normaliser.py`): your reference
|
|
168
|
+
genes, NormFinder's or geNorm's best pair, or the global mean (studies with at least 50 genes),
|
|
169
|
+
tested as one more choice of qPilot's grid with its own Decision Card. On the demo NormFinder's
|
|
170
|
+
pair changes 3 of 58 calls and the global mean 19.
|
|
171
|
+
- **How to correct differences between plates, opt-in** (`qpilot/qpcr/interrun.py`): an
|
|
172
|
+
inter-run calibrator, or factor correction (Ruijter et al. 2015), with a card only when a
|
|
173
|
+
correction changes a call. Method card
|
|
174
|
+
[`methods/inter_run_calibration.md`](methods/inter_run_calibration.md). `qpilot run
|
|
175
|
+
--normaliser all|LIST` and `--interrun all|LIST`; Results → "Also test other normalisers" and
|
|
176
|
+
"Also test corrections between plates".
|
|
177
|
+
- **The efficiency models, opt-in** (`qpilot/qpcr/axes/`, with the axis registry, stage caches and
|
|
178
|
+
grid cap): which efficiency turns each Cq into a quantity (LinRegPCR's mean per assay and
|
|
179
|
+
plate, each well's own, one per assay over its plates, an assumed 2.0, the dilution series') as
|
|
180
|
+
one more choice of the robustness grid, screened first, with its own Decision Card
|
|
181
|
+
(`numbers.efficiency_model`). Asked for in Studio (Results → "Also test efficiency models": a
|
|
182
|
+
`choice.axis` proposal the rule makes, D-AXIS checks and a person accepts) or with
|
|
183
|
+
`qpilot run --efficiency all|LIST`. The request lives in the design (`StudyDesign.axes`; absent
|
|
184
|
+
by default), so `qpilot verify` and `qpilot rerun` reproduce it. On the demo it changes 6 of 58
|
|
185
|
+
calls (each well's own 5, an assumed 2.0 4, the dilution series 3). Method card
|
|
186
|
+
[`methods/efficiency_model.md`](methods/efficiency_model.md).
|
|
187
|
+
|
|
188
|
+
#### Run Gate & melt
|
|
189
|
+
- **Melt curves** (`qpilot/qpcr/melt.py`, `GET /api/studies/{id}/melt`,
|
|
190
|
+
`GET /api/sessions/{sid}/melt`): each well's −dF/dT, its peaks and Tm for SYBR Green / EvaGreen
|
|
191
|
+
assays, judged in the Run Gate by **G-MELT** (added in pack `research-default` 1.2.0): one product within
|
|
192
|
+
1 °C of the assay's Tm, extra peaks, and what melted in no-template controls; G-NTC-1 treats a
|
|
193
|
+
primer-dimer in an NTC as a warning. A melt panel in Curves, the wells' melt curves on a G-MELT
|
|
194
|
+
check in Controls, MIQE 5.19 from the melt, StepOne "Melt Region" section files read, and
|
|
195
|
+
`qpilot melt FILE… [--json]` (reads only). On a CFX96 SYBR plate of the corpus (Hsieh et al.
|
|
196
|
+
2016), 96 of 96 wells within 0.5 °C of CFX Manager's own Tm. Method card [`methods/melt_curve.md`](methods/melt_curve.md).
|
|
197
|
+
- **G-IPC, inhibition from the internal positive control** (pack `research-default` 1.3.0): a
|
|
198
|
+
study well whose IPC (a target named IPC or IAC) comes up 3 or more cycles later than in the
|
|
199
|
+
no-template wells, or where nothing amplified, is a warning with its consequence test; the
|
|
200
|
+
3-cycle threshold is a lab-editable default. G-NTC-1 no longer judges an internal control (it
|
|
201
|
+
is meant to amplify in the no-template wells). On the QuantStudio 7 Pro eDNA plate, sample
|
|
202
|
+
M22's wells come up 3.5 to 4.2 cycles late.
|
|
203
|
+
- **Run Gate, the Controls section** (`qpilot/core/rules.py`, `qpilot/core/gate.py`,
|
|
204
|
+
`qpilot/qpcr/gate_rules.py`, `packs/research-default.yaml`, `GET /api/studies/{id}/gate`): every
|
|
205
|
+
plate's controls checked against a rule pack (G-TYPED, G-NTC-1, G-NAC, G-NRT, G-POS, G-STD,
|
|
206
|
+
G-REP), and for each rule that warns or fails, whether leaving out the wells it points at would
|
|
207
|
+
change a conclusion. Rules label; a person decides. Agreement with an independent oracle on the
|
|
208
|
+
demo: `validation/scientist/`.
|
|
209
|
+
- **G-RT and G-INSTR in the Run Gate** (pack 1.1.0, `qpilot/qpcr/run_links.py`): reverse-
|
|
210
|
+
transcription replicates per biological unit × target, and what the run file says per plate
|
|
211
|
+
(run completed, pauses, lid openings, calibrations past expiry, holds off their setpoint), with
|
|
212
|
+
sources, thresholds and the consequence test. On Controls a person links the run file the
|
|
213
|
+
instrument wrote (an `.eds`, a QuantStudio export, CFX run information) to a plate
|
|
214
|
+
(`POST /api/studies/{id}/gate/run-files`, `…/unlink`), kept for every analysis of the file. A
|
|
215
|
+
session opened from a vendor export reads each plate's run file among the export's own files
|
|
216
|
+
(`qpilot/qpcr/export_runs.py`) without a link.
|
|
217
|
+
- **Deciding a Run Gate card** (`POST /api/studies/{id}/gate/cards/{card_id}/decide`): in a Studio
|
|
218
|
+
session a person chooses an option of a `gate.rule` card with a reason. Leaving wells out
|
|
219
|
+
becomes one `curve.exclude_well` proposal per well, accepted by the person in one write to the
|
|
220
|
+
session's hash-chained inbox; the study is analysed again; **Put the wells back** undoes it.
|
|
221
|
+
- **Choices carried to a new analysis** (`StudyStore.carry_decisions`): when a person's decision
|
|
222
|
+
changes a confirmed design after the curves are confirmed (a Prism exclusion, a well left out
|
|
223
|
+
or put back, a further choice switched on or off), what the person chose on the earlier
|
|
224
|
+
analysis's Decision Cards is carried as new chain entries that name the earlier study and
|
|
225
|
+
entry. A choice that matters more in the new analysis is asked again.
|
|
226
|
+
- Confidence caps (`qpilot/core/confidence.py`): `grade(..., cap=Cap(level, check_id, note))`, a
|
|
227
|
+
ceiling with a named reason, reported among the checks.
|
|
228
|
+
|
|
229
|
+
#### Trace, repeat plan
|
|
230
|
+
- **Trace's Decide… applies the set-aside in a Studio session** (`Studio.decide_trace`): the wells
|
|
231
|
+
leave the analysis and the study is analysed again, with the person's Decision Card choices
|
|
232
|
+
carried; **Set aside from Trace** lists them, with **Put the wells back**. Outside Studio a
|
|
233
|
+
Trace decision is recorded and the numbers stay as analysed.
|
|
234
|
+
- **What to repeat, and why** (`qpilot/qpcr/repeat.py`, `GET /api/studies/{id}/review/repeat`):
|
|
235
|
+
the Run Review ranks each sample × gene worth re-running by what it does to the calls (the Run
|
|
236
|
+
Gate's warnings and failures, wells a person left out, samples in no comparison, no
|
|
237
|
+
amplification where the gene amplified elsewhere, Trace's places), lays out a downloadable
|
|
238
|
+
repeat plate (every repeated sample with its reference genes, per assay the inter-run
|
|
239
|
+
calibrator, no-RT and no-template controls) and shows the Run Gate per plate; Controls offers
|
|
240
|
+
the plan; `qpilot report run-review --repeat [--repeat-csv FILE] [--all-items]`. Re-running is
|
|
241
|
+
a person's call.
|
|
242
|
+
- **origin/main's 18 Oct work in the one page** (`INTEGRATION.md`): Shiva Teja's Trace engine with
|
|
243
|
+
its planted-fault benchmark, the Spreadsheet Check, the Evidence Ledger, the Run Review with a
|
|
244
|
+
person's sign-off, the instrument registry and logbook with lookback, the replicate budget
|
|
245
|
+
planner, Ask's Trace questions and the Excel export, as page sections (**What this study cannot
|
|
246
|
+
tell you**, **Where to look** with **Decide…**, **Sheet check**, **Instruments**).
|
|
247
|
+
|
|
248
|
+
#### Exports & Prism
|
|
249
|
+
- **Your numbers as numbers** (`qpilot/report/tables.py`, `qpilot/report/excel.py`,
|
|
250
|
+
[`docs/exports.md`](docs/exports.md)): Report → Export writes an Excel workbook (results per
|
|
251
|
+
gene with fold change, 95 % CI, p and n; each biological unit's ΔCq and relative quantity;
|
|
252
|
+
every well's Cq, N0 and efficiency and why a well was left out; Trace's places; decisions;
|
|
253
|
+
settings; Methods), the same tables as CSV, a figure per gene (each dot a biological unit, the
|
|
254
|
+
mean with its 95 % CI; editable SVG, 300-dpi PNG, its data and caption) or a journal supplement
|
|
255
|
+
(workbook, tables, MIQE checklist, figures, the RDML file with qPilot's Cq, N0 and left-out
|
|
256
|
+
wells, and the Analysis Record). Every file follows the options chosen on the Decision Cards and
|
|
257
|
+
adds the recommended analysis where it differs. Gene → **Figure** previews a gene's figure.
|
|
258
|
+
`qpilot export xlsx|csv|figures|supplement`.
|
|
259
|
+
- **Send to Prism writes a Prism project** (`.pzfx`; `qpilot/prism/`,
|
|
260
|
+
[`docs/prism.md`](docs/prism.md)) that opens in GraphPad Prism as it is: one table per gene, a
|
|
261
|
+
table of all genes and an Info sheet with who decided what. No template to build, no script to
|
|
262
|
+
run; steps for Windows and macOS on the page. To bring changes back, save the project in Prism
|
|
263
|
+
and choose it under Round trip (`POST /api/sessions/{sid}/prism/roundtrip/upload`): exclusions,
|
|
264
|
+
deletions and edits become questions with their consequences. A project a person accepted
|
|
265
|
+
stays valid while no decision changes a number in its tables. `qpilot export prism` writes the
|
|
266
|
+
project; `--scripts` adds the optional Prism scripts, `--scripts-only` writes the older bundle.
|
|
267
|
+
|
|
268
|
+
#### Agents/MCP
|
|
269
|
+
- **Seven new MCP tools** (33 in all: 28 `qpcr_*` and 5 `sim_*`): `qpcr_comparisons`, `qpcr_units`
|
|
270
|
+
(paged), `qpcr_melt`, `qpcr_quantities`, `qpcr_repeat_plan` (items paged, the plate as CSV),
|
|
271
|
+
`qpcr_export` (writes new files only; decides nothing) and `qpcr_request_set_aside` (a request
|
|
272
|
+
that always waits for a person). `qpcr_prism_diff` reads a project saved in Prism (`project_path`). Trace shows an
|
|
273
|
+
agent's request at its place: **Approve**, or **Turn down the request**; the Agents section
|
|
274
|
+
groups the tools as Read, Open, Propose, Request a decision and **Write a file**. See
|
|
275
|
+
[`docs/agents.md`](docs/agents.md).
|
|
276
|
+
- **The MCP tools' contract for agents** (`qpilot/agents/`): every refusal carries `error`, a
|
|
277
|
+
stable `code` and a `fix` and is marked `isError`; `pending_human` says why it waits
|
|
278
|
+
(`needs_human_decision`, `autonomy_forbids`) and is not an error. `qpcr_decide`, `qpcr_propose`,
|
|
279
|
+
`qpcr_export` and `qpcr_request_set_aside` take an `idempotency_key`. Every tool has a title,
|
|
280
|
+
honest annotations and an output schema each reply is checked against;
|
|
281
|
+
`response_format="concise"` on the reading tools; `fact_ids` on the study's numbers; the study's
|
|
282
|
+
files as read-only MCP resources.
|
|
283
|
+
- MCP `qpcr_gate` (read-only): the controls, the consequence tests and the `card_gate_*` cards;
|
|
284
|
+
`qpcr_decide` on a gate card is always `pending_human`.
|
|
285
|
+
- `qpcr_inspect` and `qpcr_study` read instrument exports inside `QPILOT_DATA_ROOTS`, as the
|
|
286
|
+
page's Open does; an export is analysed only with a design a person confirmed.
|
|
287
|
+
- Open Decision Card kinds (`qpilot/core/card_kinds.py`): a validated registry; a kind this build
|
|
288
|
+
does not know is a human decision (fail closed).
|
|
289
|
+
|
|
290
|
+
#### Performance
|
|
291
|
+
- **The page is faster** (`qpilot/web/warm.py`, `frontend/src/api/live.ts`): each decision re-reads
|
|
292
|
+
only the views it changed (a Studio action moves about 40–80 KB instead of 2–6 MB); unchanged
|
|
293
|
+
views answer `304` (ETags) and JSON is gzipped. After start-up and after each new analysis,
|
|
294
|
+
qPilot prepares Controls, the report, Trace, the audit and the Evidence Ledger in the
|
|
295
|
+
background. Measured on the demo, first visit: Controls 11.3 s → 0.35 s, the report 4.2 s →
|
|
296
|
+
0.49 s, Results 3.6 s → 0.67 s; rejecting a design proposal 2.7 s → 0.3 s; confirming the
|
|
297
|
+
curves 13.3 s → 3.3 s. Proposing the design is about twice as fast, the Curve Judge reads the
|
|
298
|
+
curves once, and the audit says how many analyses it has run.
|
|
299
|
+
|
|
300
|
+
#### Install & platforms
|
|
301
|
+
- **Install in one line** ([`docs/install.md`](docs/install.md)): `pipx install` or
|
|
302
|
+
`uv tool install` (from GitHub until the first PyPI release), on Windows, macOS or Linux,
|
|
303
|
+
Python 3.11–3.13, no Node and no compiler; Windows step by step in PowerShell; where your data
|
|
304
|
+
lives; offline installs.
|
|
305
|
+
- **Releases** ([`docs/release.md`](docs/release.md), `.github/workflows/release.yml`): a tag builds
|
|
306
|
+
the wheel and sdist, smoke-tests the installed wheel and makes a GitHub Release; PyPI Trusted
|
|
307
|
+
A `v*` tag publishes; an admin stops it with the repository variable `PYPI_PUBLISH` = `false`.
|
|
308
|
+
- **CI on Ubuntu, macOS and Windows** with a smoke test of the installed wheel
|
|
309
|
+
(`scripts/smoke_installed.py`).
|
|
310
|
+
- **End-to-end tests on real lab files with no model** (`frontend/e2e/`): a LightCycler 96 plate,
|
|
311
|
+
QuantStudio quantities, the K. phaffii study's several comparisons, Trace's set-aside and put
|
|
312
|
+
back, and phone width. The test suite holds on a loaded machine (jobs followed, CPU-time
|
|
313
|
+
budgets, a private test cache).
|
|
314
|
+
- `scripts/gate.sh` (the merge gate in one command) and `scripts/demo_regression.py` +
|
|
315
|
+
`scripts/demo_fingerprint.json` (the locked demo numbers, checked end to end).
|
|
316
|
+
|
|
317
|
+
### Changed
|
|
318
|
+
- **A science review of this round** ([`docs/science-review-2026-10.md`](docs/science-review-2026-10.md)):
|
|
319
|
+
every new method recomputed by an independent oracle on real lab files (`validation/excellence/`,
|
|
320
|
+
`tests/science/`): Welch, paired t, Holm, BH and Welch's ANOVA to 1e-15, Dunnett and Tukey,
|
|
321
|
+
NormFinder, inter-run calibration and factor correction against the papers, melt against CFX
|
|
322
|
+
Manager's own −dF/dT, the audit's replicate rules. Its judgement calls (J0–J12) were then
|
|
323
|
+
applied as the review recommends (29 Oct 2026; each J's "Applied" line in the review says what
|
|
324
|
+
and where):
|
|
325
|
+
- **Plan next / `qpilot plan` / `qpcr_plan` lead with the samples per group for qPilot's call**
|
|
326
|
+
(p < 0.05 and at least 2-fold, `n_for_call`), with the samples a t test alone needs beside it,
|
|
327
|
+
now exact (non-central t; the normal approximation was one sample short in 9 of 24 settings:
|
|
328
|
+
2-fold at SD 0.5 needs 6, not 5). The Cq of one copy comes from a standard curve when there is
|
|
329
|
+
one (`qpilot plan --curve-from FILE`, `qpcr_plan(study_id=…)`), else 35; the Poisson floor is
|
|
330
|
+
said to be a lower bound below 3 copies.
|
|
331
|
+
- **Run Gate pack `research-default` 1.4.0**: a no-template peak is a primer-dimer candidate only
|
|
332
|
+
3 °C or more below the product (`dimer_min_below_c`, lab-editable; closer is another product);
|
|
333
|
+
G-REP proposes no single well when the farthest wells tie (duplicates; 20.0/20.5/21.0); G-STD
|
|
334
|
+
cites Taylor et al. 2010 for 90–110 % and R² 0.98. The demo's gate is unchanged.
|
|
335
|
+
- **What to repeat**: "no amplification where one was expected" counts within the sample's own
|
|
336
|
+
group (at least three others there); the 90 % is lab-editable (`?expected_share=`,
|
|
337
|
+
`qpcr_repeat_plan(expected_share=)`).
|
|
338
|
+
- **The global mean** uses only the genes measured in every compared unit (a gene missing in a
|
|
339
|
+
unit is usually low there); 50 of them are needed. On the demo 45 of 64 are, so the opt-in
|
|
340
|
+
global mean is no longer offered there.
|
|
341
|
+
- **A unit of several samples** is the mean of its samples' means (a sample in three wells no
|
|
342
|
+
longer weighs three times one in one); one sample per unit, and the demo, are unchanged. The
|
|
343
|
+
audit combines them the same way, and its drop-outlier rule uses G-REP's tolerance (with the
|
|
344
|
+
Poisson term from the plate's standard curve) and tie rule.
|
|
345
|
+
- **Dunnett and Tukey** leave a group of capped values only out of the pooled variance; Welch's
|
|
346
|
+
ANOVA says per gene when it left a group out.
|
|
347
|
+
- **Plan next starts at 2.5×**, above the 2-fold call line, and says why; plans are faster.
|
|
348
|
+
`qpilot plan` and MCP `qpcr_plan` default to 2.5× too. `qpilot plan --cq/--expected-cq
|
|
349
|
+
[--cq-one-copy]` sets the well noise from Poisson sampling at that Cq
|
|
350
|
+
([`methods/replicate_budget.md`](methods/replicate_budget.md)).
|
|
351
|
+
- **A gene never detected in one group** is reported "detected only in ⟨group⟩" with that group's
|
|
352
|
+
ΔCq, never as a fold change made from capped values; no analysis compares a group made only of
|
|
353
|
+
capped values.
|
|
354
|
+
- **A sample missing one gene** is dropped from that gene only, not from every gene.
|
|
355
|
+
- **Claim Auditor**: a claim about groups the study does not compare (MYCN amplification, stage,
|
|
356
|
+
risk, another gene's status) is "Not supported: different groups" and says what the study
|
|
357
|
+
compares; p-values stay with their own gene; a normalisation sentence applies only to the claims
|
|
358
|
+
it is about.
|
|
359
|
+
- **Ask** finds genes by their HGNC alias (survivin → BIRC5).
|
|
360
|
+
- **Verify** names an altered or damaged file ("This record was altered or damaged after export:
|
|
361
|
+
decisions.jsonl"), the entry where a chain breaks, or "not a qPilot record".
|
|
362
|
+
- **One wording everywhere**: one story for the two robustness counts (qPilot's choice grid and
|
|
363
|
+
the audit's reasonable set, with "Why two counts?"), one name per group, plate and study,
|
|
364
|
+
"Decided by you", plurals without "(s)", folded refuted Curve Judge calls and Decision Cards
|
|
365
|
+
that fit a phone's width.
|
|
366
|
+
- Run Gate pack `research-default` 1.4.0 (1.2.0: G-MELT, and G-NTC-1 reads the melt of a
|
|
367
|
+
no-template control; 1.3.0: G-IPC, and G-NTC-1 leaves internal controls alone; 1.4.0: the
|
|
368
|
+
science review's G-MELT, G-REP and G-STD changes). The seven rules of 1.0.0 give the same
|
|
369
|
+
verdicts on the demo.
|
|
370
|
+
- **Names are the lab's own**: groups, units, samples and genes keep any letters, digits, spaces
|
|
371
|
+
and signs (`LPS + inhibitor`, `IL-6/ctrl`, `Day 3 (50 µM)`, `KO#2`, `α-syn`; at most 64
|
|
372
|
+
characters) on the Design page and in every export; file names are derived safely (an ASCII
|
|
373
|
+
slug and a short hash, `qpilot/core/names.py`). For Prism a group or unit cannot start with
|
|
374
|
+
`=`, `+` or `@`.
|
|
375
|
+
- Accessibility: the comparison picker is a radio group; scrollable tables are reachable with the
|
|
376
|
+
keyboard.
|
|
377
|
+
- A multiplex study analysed before this release no longer reproduces on re-analysis (its other
|
|
378
|
+
dyes now have an N0): Trace and the exports say so until it is analysed again.
|
|
379
|
+
- `openpyxl` is a required dependency (Excel files work without extras); the sdist is an
|
|
380
|
+
allow-list; the version is read from `qpilot/__init__.py`.
|
|
381
|
+
|
|
382
|
+
### Fixed
|
|
383
|
+
- A standard curve no longer includes wells LinRegPCR calls "no amplification" (they carried a
|
|
384
|
+
quantity): on the QuantStudio 7 Pro eDNA curve three flat wells gave 154 % efficiency, R² 0.41;
|
|
385
|
+
now 87.1 %, R² 0.9962 (science review J1).
|
|
386
|
+
- A quantity read from a weak standard curve gets the exact (Fieller) 95 % interval when the
|
|
387
|
+
curve's g is above 0.05; the classical one was too narrow and off-centre there (eDNA curve, g
|
|
388
|
+
0.21: [−0.56, 6.17] log10 instead of [−0.20, 5.80]). Every curve shows its g (science review J2).
|
|
389
|
+
- A study with three or more groups gets its report again; "The confirmed design" on the Design
|
|
390
|
+
page shows the latest decisions.
|
|
391
|
+
- Trace's what-ifs on a paired or Holm-corrected study are the engine's own evaluation (they were
|
|
392
|
+
an unpaired Welch update), so Trace's call is the engine's corrected call.
|
|
393
|
+
- The Claim Auditor checks a claim about another of the study's comparisons against that
|
|
394
|
+
comparison, not the first; a phrase naming another grouping is no longer read as the study's
|
|
395
|
+
group inside it (9 of 20 such sentences came back "Supported"; now 1 of 20).
|
|
396
|
+
- Excel-saved sample sheets were read without their text.
|
|
397
|
+
- The gene-symbol gate never reads a qPCR label as a gene (`REF` was proposed as ALYREF).
|
|
398
|
+
- The Evidence Ledger no longer shows LinRegPCR's "-1" (no mean efficiency) as a gene median.
|
|
399
|
+
- The robustness audit: a study with no data issue no longer fails, small panels get no noise
|
|
400
|
+
outliers, a value without any well is left out (never treated as undetermined), and its lab
|
|
401
|
+
panel counts wells, not samples, with technical replicates.
|
|
402
|
+
- A report on a one-gene, one-plate study reads right.
|
|
403
|
+
- Text is UTF-8 with LF line ends on every system, so a record verifies and keeps its id on
|
|
404
|
+
Windows; UTF-8 output through a Windows pipe; the pseudonym key is read as binary.
|
|
405
|
+
|
|
406
|
+
### Security
|
|
407
|
+
- **A security review of this round** ([`docs/security-review-2026-10.md`](docs/security-review-2026-10.md),
|
|
408
|
+
RT-EX-001 to 015, `THREAT_MODEL.md` rows 45–47), each finding shown by a failing test, then
|
|
409
|
+
fixed: `qpilot quantify --csv` defuses formula-like names (every person-facing CSV now does); a
|
|
410
|
+
figure of a name the exports refuse is a 409 in words, not a 500; `qpcr_export`'s copy never
|
|
411
|
+
replaces a file that appeared while the export was written; an MCP path outside the data roots
|
|
412
|
+
gets one refusal whether it exists or not; uploads are read off the event loop; the release's
|
|
413
|
+
published files are built by a job that runs no npm, and checkouts keep no token. In the public
|
|
414
|
+
sandbox (`QPILOT_HOSTED=1`) the Prism round trip reads only qPilot's export folder, a write is
|
|
415
|
+
refused (507) before the disk fills (`QPILOT_MIN_FREE_MB`), and unpacked XML is capped
|
|
416
|
+
(`QPILOT_MAX_UNPACKED_MB`).
|
|
417
|
+
- Setting the design and recording the reaction input require the decision-record head the page
|
|
418
|
+
showed (`expected_head`), so a stale page cannot overwrite a newer decision (RT-EX-012);
|
|
419
|
+
GitHub Actions are pinned by commit SHA (RT-EX-013).
|
|
420
|
+
- **The public sandbox limits each visitor** (RT-EX-015, [`docs/sandbox.md`](docs/sandbox.md)): a
|
|
421
|
+
hosted server (`QPILOT_HOSTED=1`) limits reads, writes, uploads, running jobs and live streams per
|
|
422
|
+
visitor, with sandbox-wide caps behind them, and answers `429` with `Retry-After` and how long to
|
|
423
|
+
wait in plain words. A visitor is the socket peer, or the forwarded address only from a proxy in
|
|
424
|
+
`QPILOT_TRUSTED_PROXIES`. The page and `qpilot app` on your own computer are never limited.
|
|
425
|
+
- RT-UI-025: every refusal of an RDML file says its own reason, never a traceback.
|
|
426
|
+
- A hostile Prism project (a DOCTYPE, external links, an oversized file) is refused.
|
|
427
|
+
- The new MCP tools obey the blind rule (`results_hidden` until a person confirms the design and
|
|
428
|
+
the curves), `qpcr_export` writes only new files named by qPilot (never over a file, never into
|
|
429
|
+
the study store, never to a path outside `QPILOT_DATA_ROOTS`), and a set-aside request never
|
|
430
|
+
sets anything aside; each was tried with hostile arguments (`tests/mcp/`). `THREAT_MODEL.md`
|
|
431
|
+
row 44.
|
|
432
|
+
- RT-001: with `QPILOT_DATA_ROOTS` unset, `qpilot serve` no longer shares the filesystem root or
|
|
433
|
+
the home folder as a data root when an MCP client starts it there.
|
|
434
|
+
- RT-003/004/005: package docstring vocabulary; name and id patterns end at `\Z`; an unsafe
|
|
435
|
+
member in a record zip is refused, not a traceback.
|
|
436
|
+
|
|
437
|
+
## [0.1.0] — baseline `81484f7`
|
|
438
|
+
|
|
439
|
+
The state of `main` when the agent team started: 14 CLI commands, 16 MCP tools, qPilot Studio,
|
|
440
|
+
the Decision Simulator (`qsim`), the Prism bridge, the model layer and its harness, and the
|
|
441
|
+
hash-chained Analysis Record with `verify` and `rerun`.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
title: "qPilot: open qPCR analysis that shows which choices could change the conclusion"
|
|
3
|
+
message: "If you use qPilot, please cite it and the methods and data it builds on (see references)."
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- alias: jeevithg090
|
|
7
|
+
- alias: ShivaTeja05
|
|
8
|
+
repository-code: "https://github.com/ShivaTeja05/Qpilot"
|
|
9
|
+
version: "0.2.0"
|
|
10
|
+
license: Apache-2.0
|
|
11
|
+
keywords:
|
|
12
|
+
- qPCR
|
|
13
|
+
- LinRegPCR
|
|
14
|
+
- geNorm
|
|
15
|
+
- RDML
|
|
16
|
+
- reproducibility
|
|
17
|
+
- robustness
|
|
18
|
+
references:
|
|
19
|
+
- type: article
|
|
20
|
+
title: "Amplification efficiency: linking baseline and bias in the analysis of quantitative PCR data"
|
|
21
|
+
authors:
|
|
22
|
+
- family-names: Ruijter
|
|
23
|
+
given-names: J. M.
|
|
24
|
+
year: 2009
|
|
25
|
+
journal: "Nucleic Acids Research"
|
|
26
|
+
volume: 37
|
|
27
|
+
issue: 6
|
|
28
|
+
doi: "10.1093/nar/gkp045"
|
|
29
|
+
- type: article
|
|
30
|
+
title: "Evaluation of qPCR curve analysis methods for reliable biomarker discovery: bias, resolution, precision, and implications"
|
|
31
|
+
authors:
|
|
32
|
+
- family-names: Ruijter
|
|
33
|
+
given-names: J. M.
|
|
34
|
+
year: 2013
|
|
35
|
+
journal: "Methods"
|
|
36
|
+
volume: 59
|
|
37
|
+
issue: 1
|
|
38
|
+
start: 32
|
|
39
|
+
end: 46
|
|
40
|
+
doi: "10.1016/j.ymeth.2012.08.011"
|
|
41
|
+
- type: article
|
|
42
|
+
title: "Web-based LinRegPCR: application for the visualization and analysis of (RT)-qPCR amplification and melting data"
|
|
43
|
+
authors:
|
|
44
|
+
- family-names: Untergasser
|
|
45
|
+
given-names: A.
|
|
46
|
+
year: 2021
|
|
47
|
+
journal: "BMC Bioinformatics"
|
|
48
|
+
volume: 22
|
|
49
|
+
- type: article
|
|
50
|
+
title: "Accurate normalization of real-time quantitative RT-PCR data by geometric averaging of multiple internal control genes"
|
|
51
|
+
authors:
|
|
52
|
+
- family-names: Vandesompele
|
|
53
|
+
given-names: J.
|
|
54
|
+
year: 2002
|
|
55
|
+
journal: "Genome Biology"
|
|
56
|
+
volume: 3
|
|
57
|
+
issue: 7
|
|
58
|
+
doi: "10.1186/gb-2002-3-7-research0034"
|
|
59
|
+
- type: article
|
|
60
|
+
title: "MIQE 2.0: revision of the Minimum Information for Publication of Quantitative Real-Time PCR Experiments guidelines"
|
|
61
|
+
authors:
|
|
62
|
+
- family-names: Bustin
|
|
63
|
+
given-names: S. A.
|
|
64
|
+
year: 2025
|
|
65
|
+
journal: "Clinical Chemistry"
|
|
66
|
+
- type: article
|
|
67
|
+
title: "Predicting outcomes for children with neuroblastoma using a multigene-expression signature: a retrospective SIOPEN/COG/GPOH study"
|
|
68
|
+
authors:
|
|
69
|
+
- family-names: Vermeulen
|
|
70
|
+
given-names: J.
|
|
71
|
+
year: 2009
|
|
72
|
+
journal: "The Lancet Oncology"
|
|
73
|
+
volume: 10
|
|
74
|
+
start: 663
|
|
75
|
+
end: 671
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Contributing to qPilot
|
|
2
|
+
|
|
3
|
+
Thank you for helping. qPilot is research software whose numbers people may rely on, so the rules below are about keeping every number traceable and every conclusion-changing choice with a person.
|
|
4
|
+
|
|
5
|
+
## The layers, and what each may import
|
|
6
|
+
|
|
7
|
+
| Layer | Where | Rule |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| I/O | `qpilot/io/` | Reads files safely (hardened XML parser, size caps, pinned downloads). No model calls. |
|
|
10
|
+
| qPCR plugin | `qpilot/qpcr/` | All the qPCR maths. Deterministic; no model calls, no network. |
|
|
11
|
+
| Core | `qpilot/core/` | Domain-agnostic: the robustness grid, Decision Cards and the escalation rule, the study store, the record, confidence, facts and the "no naked numbers" validator. Never imports `qpilot.qpcr` at import time and never a model. |
|
|
12
|
+
| Model layer | `qpilot/ai/`, `qpilot/harness/` | Optional. Explains and answers using `{{fact_id}}` tokens only; validated before anything is shown. |
|
|
13
|
+
| Front ends | `qpilot/web/` (the one server: the API, live events, the guard), `frontend/` (the one page), `qpilot/mcp_server.py`, `qpilot/cli/` | Wiring only: no analysis here, only calls to the layers above. The API returns JSON, never HTML; the page shows every string as text (React escapes it, and the build fails on `dangerouslySetInnerHTML` or `innerHTML`). Views in `qpilot/web/` are pure, tested functions. |
|
|
14
|
+
|
|
15
|
+
`tests/core/test_no_llm_in_engine.py` fails if importing any module of `qpilot.core` or `qpilot.qpcr` loads `qpilot.ai`, `anthropic` or `httpx`, or if `qpilot.core` loads `qpilot.qpcr` at import time. The data contract between layers is `qpilot/schemas.py`; change it only with both maintainers' agreement.
|
|
16
|
+
|
|
17
|
+
## Standing checks
|
|
18
|
+
|
|
19
|
+
Run these before every commit (the pre-commit hooks run the first two, plus a secrets scan):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
ruff format . && ruff check .
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pytest -q
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If you touched `frontend/`, also run the page's four checks (next section). CI runs lint, the unit tests (`pytest -m "not demo_data"`), `pip-audit`, and the page's checks and build. Tests marked `demo_data` need the cached demo study (`qpilot fetch-demo`) and run locally. Two rules for numbers:
|
|
30
|
+
|
|
31
|
+
- **Never type a number into prose, a template, the UI or a slide.** Numbers come from facts (`StudyResult.facts`) or generated files (`validation/validation.json`), rendered with their own format. Tests check that `README.md` and `docs/engine.md` quote the validation lines generated from `validation/validation.json`; the method cards still contain hand-copied demo numbers that no test checks yet.
|
|
32
|
+
- **If the engine disagrees with an expected number, report both.** Do not change a tolerance to make a test pass.
|
|
33
|
+
|
|
34
|
+
## Working on the page (`frontend/`)
|
|
35
|
+
|
|
36
|
+
The page is React, TypeScript, Vite and Tailwind CSS in `frontend/`; `npm run build` writes it into `qpilot/web/static/`, which the server (`qpilot app`) serves, so using qPilot never needs Node. You need Node 22 or newer only to change the page. Where things are: `src/ui/` the components every section shares, `src/charts/` the science charts, `src/lib/` number formats and the plain words, `src/api/` the typed client, `src/features/<section>/` one folder per section of the page. The page's rules (what it shows, never mocked data, design system) are in [`docs/app.md`](docs/app.md); read its principles first.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
(cd frontend && npm ci) # once, and after the lockfile changes
|
|
40
|
+
scripts/serve-dev.sh # terminal 1: qpilot app on :7860 with the development key
|
|
41
|
+
cd frontend && QPILOT_APP_TOKEN=qpilot-dev npm run dev # terminal 2: the page on http://127.0.0.1:5173
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`scripts/serve-dev.sh` runs `.venv/bin/qpilot` (create the virtual environment in the repository root, as in the README), fixes the launch key to `qpilot-dev` and keeps its studies apart in `~/.cache/qpilot/dev-studies`. `npm run dev` forwards `/api` to it and adds the same key to each forwarded request, so the development page needs no cookie. Never use a fixed key on a shared computer (see [`THREAT_MODEL.md`](THREAT_MODEL.md), row 24). `QPILOT_API` (default `http://127.0.0.1:7860`), `QPILOT_APP_TOKEN` and `VITE_PORT` (default 5173) override the defaults. Sections load real data from your own study store: fetch the demo (`qpilot fetch-demo`, or **Download 7.0 MB and open** on the page) and open it, rather than typing numbers into a component.
|
|
45
|
+
|
|
46
|
+
Before every commit that touches `frontend/` or the API's contract (all four must pass):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm run typecheck && npm run lint && npm test && npm run build
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
* **The API's types are generated.** Response models live in `qpilot/web/contract*.py`. After you change one, run `npm run gen:api` (it writes `frontend/src/api/schema.d.ts` from the server's OpenAPI schema, without starting a server) and commit both files together; `npm run check:api` fails when they differ, and CI runs it. Keep contract changes additive.
|
|
53
|
+
* **A new API route** is a module in `qpilot/web/routers/` that defines `router` (routers are discovered, there is no list to edit). Writes record the actor `human` (the server sets it; a request cannot choose), results of a Studio session stay blind until the design and the curves are confirmed (the server refuses, the page does not merely hide), and every route has a test in `tests/web/`. Slow work is a job (`202`, followed on the event stream), not a long request.
|
|
54
|
+
* **Tests use real data, never made-up numbers.** Component tests may use JSON recorded from your store's own API, kept in `frontend/src/test/recorded/` with the command that produced it (`frontend/src/test/recorded/record.py`). Do not write numbers, gene names or sample names into a component or a fixture to make a screen look finished.
|
|
55
|
+
* **What is committed:** `frontend/package.json` and `package-lock.json`, `frontend/src/`, and the built page in `qpilot/web/static/` (CI runs `npm run build` and fails if `git diff` then shows a difference, so the committed page is always what the source produces). `node_modules` is not committed. When `package-lock.json` changes, regenerate the npm part of [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) (the generator is in that file); a GPL-family licence in the page's production packages is a blocker.
|
|
56
|
+
* **Lint rules that protect the principles:** no `dangerouslySetInnerHTML`, no `innerHTML`, no external URL in the page's code (it makes no outbound request; the CSP would refuse it anyway).
|
|
57
|
+
|
|
58
|
+
## Commits and branches
|
|
59
|
+
|
|
60
|
+
[Conventional Commits](https://www.conventionalcommits.org): `feat(qpcr): …`, `fix(app): …`, `test: …`, `docs: …`. One branch per change, rebased on `main`; `main` moves only by fast-forward after CI passes. Code owners are in `CODEOWNERS`.
|
|
61
|
+
|
|
62
|
+
## How to add…
|
|
63
|
+
|
|
64
|
+
**An input format (a connector).** A connector is a function `path -> rdml.Rdml`; see [`qpilot/io/README.md`](qpilot/io/README.md). `CONNECTORS` in `qpilot/io/rdml_io.py` lists them by suffix, but nothing dispatches on it yet: route `qpilot/qpcr/curves.py::analyse_study` through it, add the suffix to `RDML_SUFFIXES` in `qpilot/mcp_server.py` and to `INPUT_SUFFIXES` in `qpilot/web/views.py` (what the page accepts as an upload), and apply size caps and safe parsing inside the connector (the hardened `load()` covers RDML/XML only). Add a small fixture under `tests/fixtures/` (with its licence in `THIRD_PARTY_NOTICES.md`) and a test that it loads.
|
|
65
|
+
|
|
66
|
+
**A new domain (another assay).** Implement `qpilot.schemas.DecisionSpace` (`dimensions`, `primary`, `items`, `evaluate(cell)`, `conclude(result)`), as `qpilot/qpcr/space.py` does for qPCR; the core builds the robustness grid from it. For Decision Cards, also pass a list of `qpilot.core.decisions.CardSpec` to `build_cards`, as `qpilot/qpcr/study.py` does. Card kinds are currently the three qPCR kinds in `qpilot/schemas.py`, so a new domain needs a schema change, and `qpilot rerun` calls the qPCR `run_study`. Register your facts with `qpilot.core.facts.FactRegistry` so explanations can cite them.
|
|
67
|
+
|
|
68
|
+
**A model backend.** Add a class with `name`, `model` and `complete(system, messages, max_tokens, temperature) -> str` in `qpilot/ai/client.py`; add its provider to `Provider` and the `from_env` check in `qpilot/ai/config.py`, and select it in `make_backend`. The provider, endpoint and key come from the environment (`QPILOT_MODEL_PROVIDER` and the variables in `qpilot/ai/config.py`); the page has no setting for them, by design (see [`THREAT_MODEL.md`](THREAT_MODEL.md), row 14). It must raise `ModelUnavailable` on any failure so the template answer is used, and its output still goes through the validator. Test it with `MockBackend`-style fakes; tests never call a real model or the network.
|
|
69
|
+
|
|
70
|
+
## Data and licences
|
|
71
|
+
|
|
72
|
+
Never commit the demo dataset or any `.rdml` outside `tests/fixtures/`. Do not import or copy GPL/EUPL code (for example `qslib`, `qpcR`) or NormFinder's original code. The vendored rdmlpython in `qpilot/vendor/` is never edited; a test checks its hashes.
|
|
73
|
+
|
|
74
|
+
## Reporting a vulnerability
|
|
75
|
+
|
|
76
|
+
See [`SECURITY.md`](SECURITY.md). Please do not open a public issue for it.
|