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.
Files changed (452) hide show
  1. qpilot-0.2.0/.gitignore +39 -0
  2. qpilot-0.2.0/CHANGELOG.md +441 -0
  3. qpilot-0.2.0/CITATION.cff +75 -0
  4. qpilot-0.2.0/CONTRIBUTING.md +76 -0
  5. qpilot-0.2.0/LICENSE +202 -0
  6. qpilot-0.2.0/PKG-INFO +511 -0
  7. qpilot-0.2.0/README.md +444 -0
  8. qpilot-0.2.0/SECURITY.md +23 -0
  9. qpilot-0.2.0/THIRD_PARTY_NOTICES.md +492 -0
  10. qpilot-0.2.0/THREAT_MODEL.md +103 -0
  11. qpilot-0.2.0/docs/README.md +75 -0
  12. qpilot-0.2.0/docs/agents.md +330 -0
  13. qpilot-0.2.0/docs/app.md +622 -0
  14. qpilot-0.2.0/docs/conformance.md +98 -0
  15. qpilot-0.2.0/docs/cq-tables.md +84 -0
  16. qpilot-0.2.0/docs/design-editor.md +96 -0
  17. qpilot-0.2.0/docs/engine.md +88 -0
  18. qpilot-0.2.0/docs/exports.md +75 -0
  19. qpilot-0.2.0/docs/install.md +138 -0
  20. qpilot-0.2.0/docs/integration.md +212 -0
  21. qpilot-0.2.0/docs/multiplex.md +100 -0
  22. qpilot-0.2.0/docs/prism.md +251 -0
  23. qpilot-0.2.0/docs/quantities.md +116 -0
  24. qpilot-0.2.0/docs/release.md +73 -0
  25. qpilot-0.2.0/docs/sandbox.md +113 -0
  26. qpilot-0.2.0/docs/science-review-2026-10.md +447 -0
  27. qpilot-0.2.0/docs/security-review-2026-10.md +91 -0
  28. qpilot-0.2.0/docs/studio-measured.md +66 -0
  29. qpilot-0.2.0/docs/studio.md +163 -0
  30. qpilot-0.2.0/docs/user-guide.md +503 -0
  31. qpilot-0.2.0/methods/absolute_quantification.md +54 -0
  32. qpilot-0.2.0/methods/amp_call.md +34 -0
  33. qpilot-0.2.0/methods/comparisons.md +70 -0
  34. qpilot-0.2.0/methods/contrast.md +24 -0
  35. qpilot-0.2.0/methods/curve_judge.md +161 -0
  36. qpilot-0.2.0/methods/design_checks.md +125 -0
  37. qpilot-0.2.0/methods/efficiency_model.md +32 -0
  38. qpilot-0.2.0/methods/evidence.md +50 -0
  39. qpilot-0.2.0/methods/genorm.md +38 -0
  40. qpilot-0.2.0/methods/instrument_context.md +66 -0
  41. qpilot-0.2.0/methods/inter_run_calibration.md +49 -0
  42. qpilot-0.2.0/methods/linregpcr.md +44 -0
  43. qpilot-0.2.0/methods/lookback.md +95 -0
  44. qpilot-0.2.0/methods/melt_curve.md +66 -0
  45. qpilot-0.2.0/methods/normfinder.md +104 -0
  46. qpilot-0.2.0/methods/replicate_budget.md +68 -0
  47. qpilot-0.2.0/methods/routes.md +28 -0
  48. qpilot-0.2.0/methods/sheetcheck.md +32 -0
  49. qpilot-0.2.0/methods/trace.md +82 -0
  50. qpilot-0.2.0/packs/research-default.yaml +339 -0
  51. qpilot-0.2.0/pyproject.toml +145 -0
  52. qpilot-0.2.0/qpilot/__init__.py +6 -0
  53. qpilot-0.2.0/qpilot/agents/__init__.py +1 -0
  54. qpilot-0.2.0/qpilot/agents/capabilities.py +1066 -0
  55. qpilot-0.2.0/qpilot/agents/idempotency.py +119 -0
  56. qpilot-0.2.0/qpilot/agents/outputs.py +785 -0
  57. qpilot-0.2.0/qpilot/agents/refusals.py +199 -0
  58. qpilot-0.2.0/qpilot/agents/surface.py +294 -0
  59. qpilot-0.2.0/qpilot/agents/views.py +254 -0
  60. qpilot-0.2.0/qpilot/ai/__init__.py +1 -0
  61. qpilot-0.2.0/qpilot/ai/ask.py +924 -0
  62. qpilot-0.2.0/qpilot/ai/client.py +328 -0
  63. qpilot-0.2.0/qpilot/ai/config.py +124 -0
  64. qpilot-0.2.0/qpilot/ai/explain.py +223 -0
  65. qpilot-0.2.0/qpilot/ai/interpret.py +1651 -0
  66. qpilot-0.2.0/qpilot/ai/payload_log.py +74 -0
  67. qpilot-0.2.0/qpilot/ai/prism_draft.py +460 -0
  68. qpilot-0.2.0/qpilot/ai/profile.py +75 -0
  69. qpilot-0.2.0/qpilot/ai/prompts.py +115 -0
  70. qpilot-0.2.0/qpilot/ai/suggest_only.py +100 -0
  71. qpilot-0.2.0/qpilot/ai/validator.py +11 -0
  72. qpilot-0.2.0/qpilot/app_state.py +436 -0
  73. qpilot-0.2.0/qpilot/claims/__init__.py +24 -0
  74. qpilot-0.2.0/qpilot/claims/assist.py +201 -0
  75. qpilot-0.2.0/qpilot/claims/check.py +1516 -0
  76. qpilot-0.2.0/qpilot/claims/examples.py +137 -0
  77. qpilot-0.2.0/qpilot/claims/extract.py +1167 -0
  78. qpilot-0.2.0/qpilot/claims/lexicon.py +442 -0
  79. qpilot-0.2.0/qpilot/claims/models.py +276 -0
  80. qpilot-0.2.0/qpilot/cli/__init__.py +71 -0
  81. qpilot-0.2.0/qpilot/cli/_console.py +33 -0
  82. qpilot-0.2.0/qpilot/cli/_types.py +6 -0
  83. qpilot-0.2.0/qpilot/cli/core_cmds.py +493 -0
  84. qpilot-0.2.0/qpilot/cli/engine_cmds.py +586 -0
  85. qpilot-0.2.0/qpilot/cli/melt_cmds.py +154 -0
  86. qpilot-0.2.0/qpilot/cli/prism_cmds.py +360 -0
  87. qpilot-0.2.0/qpilot/cli/quality_cmds.py +597 -0
  88. qpilot-0.2.0/qpilot/cli/quant_cmds.py +192 -0
  89. qpilot-0.2.0/qpilot/cli/studio_cmds.py +465 -0
  90. qpilot-0.2.0/qpilot/core/__init__.py +1 -0
  91. qpilot-0.2.0/qpilot/core/card_kinds.py +190 -0
  92. qpilot-0.2.0/qpilot/core/confidence.py +95 -0
  93. qpilot-0.2.0/qpilot/core/consequence.py +190 -0
  94. qpilot-0.2.0/qpilot/core/decisions.py +436 -0
  95. qpilot-0.2.0/qpilot/core/facts.py +102 -0
  96. qpilot-0.2.0/qpilot/core/firewall.py +275 -0
  97. qpilot-0.2.0/qpilot/core/gate.py +582 -0
  98. qpilot-0.2.0/qpilot/core/instruments.py +1392 -0
  99. qpilot-0.2.0/qpilot/core/names.py +97 -0
  100. qpilot-0.2.0/qpilot/core/proposals.py +1073 -0
  101. qpilot-0.2.0/qpilot/core/pseudonym.py +165 -0
  102. qpilot-0.2.0/qpilot/core/record.py +1112 -0
  103. qpilot-0.2.0/qpilot/core/robustness.py +395 -0
  104. qpilot-0.2.0/qpilot/core/rules.py +609 -0
  105. qpilot-0.2.0/qpilot/core/store.py +1416 -0
  106. qpilot-0.2.0/qpilot/core/validator.py +137 -0
  107. qpilot-0.2.0/qpilot/core/vocabulary.py +104 -0
  108. qpilot-0.2.0/qpilot/data/hgnc/LICENCE +9 -0
  109. qpilot-0.2.0/qpilot/data/hgnc/README.md +65 -0
  110. qpilot-0.2.0/qpilot/data/hgnc/hgnc_symbols.tsv +45279 -0
  111. qpilot-0.2.0/qpilot/explain/__init__.py +1 -0
  112. qpilot-0.2.0/qpilot/explain/evidence_text.py +387 -0
  113. qpilot-0.2.0/qpilot/explain/instrument_text.py +211 -0
  114. qpilot-0.2.0/qpilot/explain/lookback_text.py +140 -0
  115. qpilot-0.2.0/qpilot/explain/quantity_text.py +82 -0
  116. qpilot-0.2.0/qpilot/explain/sets.py +138 -0
  117. qpilot-0.2.0/qpilot/explain/sheetcheck_text.py +243 -0
  118. qpilot-0.2.0/qpilot/explain/templates.py +552 -0
  119. qpilot-0.2.0/qpilot/explain/trace_text.py +460 -0
  120. qpilot-0.2.0/qpilot/explain/words.py +17 -0
  121. qpilot-0.2.0/qpilot/harness/__init__.py +1 -0
  122. qpilot-0.2.0/qpilot/harness/make_evalset.py +315 -0
  123. qpilot-0.2.0/qpilot/harness/report.py +201 -0
  124. qpilot-0.2.0/qpilot/harness/run.py +96 -0
  125. qpilot-0.2.0/qpilot/harness/score.py +144 -0
  126. qpilot-0.2.0/qpilot/harness/studio.py +949 -0
  127. qpilot-0.2.0/qpilot/history/__init__.py +19 -0
  128. qpilot-0.2.0/qpilot/history/compare.py +271 -0
  129. qpilot-0.2.0/qpilot/history/replay.py +701 -0
  130. qpilot-0.2.0/qpilot/history/studio.py +453 -0
  131. qpilot-0.2.0/qpilot/integration/__init__.py +25 -0
  132. qpilot-0.2.0/qpilot/integration/__main__.py +166 -0
  133. qpilot-0.2.0/qpilot/integration/mcp_tools.py +407 -0
  134. qpilot-0.2.0/qpilot/integration/simulator.py +865 -0
  135. qpilot-0.2.0/qpilot/integration/workflow.py +1070 -0
  136. qpilot-0.2.0/qpilot/io/README.md +42 -0
  137. qpilot-0.2.0/qpilot/io/__init__.py +1 -0
  138. qpilot-0.2.0/qpilot/io/connectors/__init__.py +52 -0
  139. qpilot-0.2.0/qpilot/io/connectors/base.py +364 -0
  140. qpilot-0.2.0/qpilot/io/connectors/builder.py +346 -0
  141. qpilot-0.2.0/qpilot/io/connectors/cfx.py +293 -0
  142. qpilot-0.2.0/qpilot/io/connectors/common.py +264 -0
  143. qpilot-0.2.0/qpilot/io/connectors/headers.py +81 -0
  144. qpilot-0.2.0/qpilot/io/connectors/lightcycler.py +243 -0
  145. qpilot-0.2.0/qpilot/io/connectors/quantstudio.py +587 -0
  146. qpilot-0.2.0/qpilot/io/connectors/rdml_file.py +199 -0
  147. qpilot-0.2.0/qpilot/io/connectors/registry.py +531 -0
  148. qpilot-0.2.0/qpilot/io/connectors/sample_types.py +105 -0
  149. qpilot-0.2.0/qpilot/io/connectors/tables.py +502 -0
  150. qpilot-0.2.0/qpilot/io/connectors/tidy.py +328 -0
  151. qpilot-0.2.0/qpilot/io/datasets.py +220 -0
  152. qpilot-0.2.0/qpilot/io/inputs.py +151 -0
  153. qpilot-0.2.0/qpilot/io/rawness.py +377 -0
  154. qpilot-0.2.0/qpilot/io/rdml_io.py +747 -0
  155. qpilot-0.2.0/qpilot/io/sheets.py +989 -0
  156. qpilot-0.2.0/qpilot/io/symbols.py +419 -0
  157. qpilot-0.2.0/qpilot/io/trust.py +310 -0
  158. qpilot-0.2.0/qpilot/mcp_server.py +2325 -0
  159. qpilot-0.2.0/qpilot/models/__init__.py +21 -0
  160. qpilot-0.2.0/qpilot/models/curve_judge/METHOD_CARD.md +161 -0
  161. qpilot-0.2.0/qpilot/models/curve_judge/metrics.json +1888 -0
  162. qpilot-0.2.0/qpilot/models/curve_judge/model.json +1 -0
  163. qpilot-0.2.0/qpilot/models/curve_judge/model.sha256 +1 -0
  164. qpilot-0.2.0/qpilot/models/curve_judge/transfer.json +1844 -0
  165. qpilot-0.2.0/qpilot/prism/__init__.py +660 -0
  166. qpilot-0.2.0/qpilot/prism/project.py +580 -0
  167. qpilot-0.2.0/qpilot/prism/provenance.py +393 -0
  168. qpilot-0.2.0/qpilot/prism/pzfx.py +653 -0
  169. qpilot-0.2.0/qpilot/prism/roundtrip.py +1195 -0
  170. qpilot-0.2.0/qpilot/prism/script.py +454 -0
  171. qpilot-0.2.0/qpilot/prism/tables.py +461 -0
  172. qpilot-0.2.0/qpilot/prism/templates/README.md +109 -0
  173. qpilot-0.2.0/qpilot/qpcr/__init__.py +6 -0
  174. qpilot-0.2.0/qpilot/qpcr/absolute.py +1422 -0
  175. qpilot-0.2.0/qpilot/qpcr/absolute_study.py +156 -0
  176. qpilot-0.2.0/qpilot/qpcr/amp_call.py +341 -0
  177. qpilot-0.2.0/qpilot/qpcr/axes/__init__.py +322 -0
  178. qpilot-0.2.0/qpilot/qpcr/axes/efficiency.py +257 -0
  179. qpilot-0.2.0/qpilot/qpcr/axes/normaliser.py +343 -0
  180. qpilot-0.2.0/qpilot/qpcr/cards.py +62 -0
  181. qpilot-0.2.0/qpilot/qpcr/confidence_checks.py +176 -0
  182. qpilot-0.2.0/qpilot/qpcr/contrast.py +274 -0
  183. qpilot-0.2.0/qpilot/qpcr/cq_only.py +304 -0
  184. qpilot-0.2.0/qpilot/qpcr/curve_judge.py +709 -0
  185. qpilot-0.2.0/qpilot/qpcr/curves.py +781 -0
  186. qpilot-0.2.0/qpilot/qpcr/design.py +90 -0
  187. qpilot-0.2.0/qpilot/qpcr/design_checks.py +1478 -0
  188. qpilot-0.2.0/qpilot/qpcr/design_rules.py +1187 -0
  189. qpilot-0.2.0/qpilot/qpcr/designs.py +929 -0
  190. qpilot-0.2.0/qpilot/qpcr/evidence.py +1011 -0
  191. qpilot-0.2.0/qpilot/qpcr/export.py +1032 -0
  192. qpilot-0.2.0/qpilot/qpcr/export_runs.py +85 -0
  193. qpilot-0.2.0/qpilot/qpcr/gate_instr.py +230 -0
  194. qpilot-0.2.0/qpilot/qpcr/gate_lines.py +33 -0
  195. qpilot-0.2.0/qpilot/qpcr/gate_rt.py +245 -0
  196. qpilot-0.2.0/qpilot/qpcr/gate_rules.py +2189 -0
  197. qpilot-0.2.0/qpilot/qpcr/inspect.py +482 -0
  198. qpilot-0.2.0/qpilot/qpcr/instrument_context.py +2278 -0
  199. qpilot-0.2.0/qpilot/qpcr/interrun.py +344 -0
  200. qpilot-0.2.0/qpilot/qpcr/layout.py +881 -0
  201. qpilot-0.2.0/qpilot/qpcr/lookback.py +683 -0
  202. qpilot-0.2.0/qpilot/qpcr/melt.py +832 -0
  203. qpilot-0.2.0/qpilot/qpcr/multiplex.py +183 -0
  204. qpilot-0.2.0/qpilot/qpcr/multiplicity.py +189 -0
  205. qpilot-0.2.0/qpilot/qpcr/normfinder.py +383 -0
  206. qpilot-0.2.0/qpilot/qpcr/plan.py +1082 -0
  207. qpilot-0.2.0/qpilot/qpcr/position_history.py +480 -0
  208. qpilot-0.2.0/qpilot/qpcr/quant.py +248 -0
  209. qpilot-0.2.0/qpilot/qpcr/quantify_study.py +718 -0
  210. qpilot-0.2.0/qpilot/qpcr/refgenes.py +237 -0
  211. qpilot-0.2.0/qpilot/qpcr/repeat.py +868 -0
  212. qpilot-0.2.0/qpilot/qpcr/routes.py +166 -0
  213. qpilot-0.2.0/qpilot/qpcr/run_links.py +153 -0
  214. qpilot-0.2.0/qpilot/qpcr/sheetcheck.py +2980 -0
  215. qpilot-0.2.0/qpilot/qpcr/space.py +1455 -0
  216. qpilot-0.2.0/qpilot/qpcr/study.py +2101 -0
  217. qpilot-0.2.0/qpilot/qpcr/trace.py +2924 -0
  218. qpilot-0.2.0/qpilot/qpcr/trace_bench.py +1126 -0
  219. qpilot-0.2.0/qpilot/qpcr/validation.py +434 -0
  220. qpilot-0.2.0/qpilot/report/__init__.py +31 -0
  221. qpilot-0.2.0/qpilot/report/build.py +333 -0
  222. qpilot-0.2.0/qpilot/report/bundle.py +419 -0
  223. qpilot-0.2.0/qpilot/report/context.py +212 -0
  224. qpilot-0.2.0/qpilot/report/excel.py +462 -0
  225. qpilot-0.2.0/qpilot/report/figures.py +994 -0
  226. qpilot-0.2.0/qpilot/report/fmt.py +80 -0
  227. qpilot-0.2.0/qpilot/report/html.py +385 -0
  228. qpilot-0.2.0/qpilot/report/inputs.py +217 -0
  229. qpilot-0.2.0/qpilot/report/markdown.py +145 -0
  230. qpilot-0.2.0/qpilot/report/methods.py +919 -0
  231. qpilot-0.2.0/qpilot/report/miqe.py +860 -0
  232. qpilot-0.2.0/qpilot/report/miqe2_items.yaml +92 -0
  233. qpilot-0.2.0/qpilot/report/model.py +235 -0
  234. qpilot-0.2.0/qpilot/report/rdml_meta.py +237 -0
  235. qpilot-0.2.0/qpilot/report/rdml_out.py +257 -0
  236. qpilot-0.2.0/qpilot/report/review.py +761 -0
  237. qpilot-0.2.0/qpilot/report/run_review.py +624 -0
  238. qpilot-0.2.0/qpilot/report/svg.py +218 -0
  239. qpilot-0.2.0/qpilot/report/tables.py +1558 -0
  240. qpilot-0.2.0/qpilot/report/text.py +220 -0
  241. qpilot-0.2.0/qpilot/schemas.py +318 -0
  242. qpilot-0.2.0/qpilot/schemas_studio.py +787 -0
  243. qpilot-0.2.0/qpilot/studio_axes.py +143 -0
  244. qpilot-0.2.0/qpilot/studio_design.py +1063 -0
  245. qpilot-0.2.0/qpilot/studio_service.py +3299 -0
  246. qpilot-0.2.0/qpilot/studio_state.py +1302 -0
  247. qpilot-0.2.0/qpilot/trace/__init__.py +68 -0
  248. qpilot-0.2.0/qpilot/trace/context.py +771 -0
  249. qpilot-0.2.0/qpilot/trace/gene.py +964 -0
  250. qpilot-0.2.0/qpilot/trace/schema.py +398 -0
  251. qpilot-0.2.0/qpilot/trace/study.py +321 -0
  252. qpilot-0.2.0/qpilot/trace/welch.py +678 -0
  253. qpilot-0.2.0/qpilot/trace/words.py +149 -0
  254. qpilot-0.2.0/qpilot/vendor/__init__.py +1 -0
  255. qpilot-0.2.0/qpilot/vendor/rdmlpython/LICENSE +22 -0
  256. qpilot-0.2.0/qpilot/vendor/rdmlpython/README_VENDOR.md +39 -0
  257. qpilot-0.2.0/qpilot/vendor/rdmlpython/__init__.py +1 -0
  258. qpilot-0.2.0/qpilot/vendor/rdmlpython/rdml.py +15562 -0
  259. qpilot-0.2.0/qpilot/vendor/rdmlpython/schema/LICENSE +22 -0
  260. qpilot-0.2.0/qpilot/vendor/rdmlpython/schema/RDML_v1_0_REC.xsd +1206 -0
  261. qpilot-0.2.0/qpilot/vendor/rdmlpython/schema/RDML_v1_1_REC.xsd +1276 -0
  262. qpilot-0.2.0/qpilot/vendor/rdmlpython/schema/RDML_v1_2_REC.xsd +1308 -0
  263. qpilot-0.2.0/qpilot/vendor/rdmlpython/schema/RDML_v1_3_REC.xsd +1606 -0
  264. qpilot-0.2.0/qpilot/vendor/rdmlpython/schema/RDML_v1_4_CR.xsd +1660 -0
  265. qpilot-0.2.0/qpilot/web/__init__.py +1 -0
  266. qpilot-0.2.0/qpilot/web/absolute_views.py +505 -0
  267. qpilot-0.2.0/qpilot/web/axes_views.py +312 -0
  268. qpilot-0.2.0/qpilot/web/cache.py +277 -0
  269. qpilot-0.2.0/qpilot/web/compare_views.py +443 -0
  270. qpilot-0.2.0/qpilot/web/context.py +133 -0
  271. qpilot-0.2.0/qpilot/web/contract.py +1303 -0
  272. qpilot-0.2.0/qpilot/web/contract_absolute.py +319 -0
  273. qpilot-0.2.0/qpilot/web/contract_audit.py +776 -0
  274. qpilot-0.2.0/qpilot/web/contract_capture.py +84 -0
  275. qpilot-0.2.0/qpilot/web/contract_claims.py +63 -0
  276. qpilot-0.2.0/qpilot/web/contract_compare.py +139 -0
  277. qpilot-0.2.0/qpilot/web/contract_cqonly.py +93 -0
  278. qpilot-0.2.0/qpilot/web/contract_design.py +259 -0
  279. qpilot-0.2.0/qpilot/web/contract_evidence.py +173 -0
  280. qpilot-0.2.0/qpilot/web/contract_files.py +433 -0
  281. qpilot-0.2.0/qpilot/web/contract_gate.py +303 -0
  282. qpilot-0.2.0/qpilot/web/contract_history.py +366 -0
  283. qpilot-0.2.0/qpilot/web/contract_instruments.py +264 -0
  284. qpilot-0.2.0/qpilot/web/contract_melt.py +163 -0
  285. qpilot-0.2.0/qpilot/web/contract_norm.py +72 -0
  286. qpilot-0.2.0/qpilot/web/contract_ops.py +247 -0
  287. qpilot-0.2.0/qpilot/web/contract_planner.py +370 -0
  288. qpilot-0.2.0/qpilot/web/contract_report.py +195 -0
  289. qpilot-0.2.0/qpilot/web/contract_sheets.py +184 -0
  290. qpilot-0.2.0/qpilot/web/contract_trace.py +308 -0
  291. qpilot-0.2.0/qpilot/web/contract_trust.py +139 -0
  292. qpilot-0.2.0/qpilot/web/contract_units.py +134 -0
  293. qpilot-0.2.0/qpilot/web/cq_table_views.py +235 -0
  294. qpilot-0.2.0/qpilot/web/curves.py +113 -0
  295. qpilot-0.2.0/qpilot/web/events.py +301 -0
  296. qpilot-0.2.0/qpilot/web/folders.py +383 -0
  297. qpilot-0.2.0/qpilot/web/intake.py +661 -0
  298. qpilot-0.2.0/qpilot/web/jobs.py +234 -0
  299. qpilot-0.2.0/qpilot/web/openapi.py +30 -0
  300. qpilot-0.2.0/qpilot/web/ratelimit.py +642 -0
  301. qpilot-0.2.0/qpilot/web/references_views.py +165 -0
  302. qpilot-0.2.0/qpilot/web/results_views.py +1507 -0
  303. qpilot-0.2.0/qpilot/web/routers/__init__.py +48 -0
  304. qpilot-0.2.0/qpilot/web/routers/absolute.py +205 -0
  305. qpilot-0.2.0/qpilot/web/routers/audit.py +1179 -0
  306. qpilot-0.2.0/qpilot/web/routers/claims.py +113 -0
  307. qpilot-0.2.0/qpilot/web/routers/cqonly.py +29 -0
  308. qpilot-0.2.0/qpilot/web/routers/design.py +319 -0
  309. qpilot-0.2.0/qpilot/web/routers/evidence.py +528 -0
  310. qpilot-0.2.0/qpilot/web/routers/files.py +1141 -0
  311. qpilot-0.2.0/qpilot/web/routers/gate.py +1375 -0
  312. qpilot-0.2.0/qpilot/web/routers/history.py +111 -0
  313. qpilot-0.2.0/qpilot/web/routers/instruments.py +590 -0
  314. qpilot-0.2.0/qpilot/web/routers/melt.py +358 -0
  315. qpilot-0.2.0/qpilot/web/routers/ops.py +1286 -0
  316. qpilot-0.2.0/qpilot/web/routers/planner.py +775 -0
  317. qpilot-0.2.0/qpilot/web/routers/references.py +36 -0
  318. qpilot-0.2.0/qpilot/web/routers/report.py +519 -0
  319. qpilot-0.2.0/qpilot/web/routers/results.py +426 -0
  320. qpilot-0.2.0/qpilot/web/routers/sheets.py +254 -0
  321. qpilot-0.2.0/qpilot/web/routers/studio.py +748 -0
  322. qpilot-0.2.0/qpilot/web/routers/system.py +150 -0
  323. qpilot-0.2.0/qpilot/web/routers/trace.py +366 -0
  324. qpilot-0.2.0/qpilot/web/routers/trust.py +280 -0
  325. qpilot-0.2.0/qpilot/web/routers/units.py +51 -0
  326. qpilot-0.2.0/qpilot/web/routers/where.py +652 -0
  327. qpilot-0.2.0/qpilot/web/routers/workspaces.py +398 -0
  328. qpilot-0.2.0/qpilot/web/security.py +404 -0
  329. qpilot-0.2.0/qpilot/web/server.py +280 -0
  330. qpilot-0.2.0/qpilot/web/static/assets/AskDrawer-CUxSUS-x.js +1 -0
  331. qpilot-0.2.0/qpilot/web/static/assets/ComparisonsPanel-D3FXhNHz.js +1 -0
  332. qpilot-0.2.0/qpilot/web/static/assets/FactText-Dz8wh-gO.js +1 -0
  333. qpilot-0.2.0/qpilot/web/static/assets/MeltPanel-Cw8Nsxcc.js +1 -0
  334. qpilot-0.2.0/qpilot/web/static/assets/PrismFigure-CkTa0blX.js +1 -0
  335. qpilot-0.2.0/qpilot/web/static/assets/RepeatPlan-BChHKdfV.js +1 -0
  336. qpilot-0.2.0/qpilot/web/static/assets/TraceLink-DeQ7800y.js +1 -0
  337. qpilot-0.2.0/qpilot/web/static/assets/WellCurve-CHQuPKbV.js +1 -0
  338. qpilot-0.2.0/qpilot/web/static/assets/adapters-PmyB1S-K.js +1 -0
  339. qpilot-0.2.0/qpilot/web/static/assets/agents-CoQ0p6Pa.js +1 -0
  340. qpilot-0.2.0/qpilot/web/static/assets/api-B4NH57vK.js +1 -0
  341. qpilot-0.2.0/qpilot/web/static/assets/api-lNzRMZwq.js +1 -0
  342. qpilot-0.2.0/qpilot/web/static/assets/arrow-left-J4KquhEj.js +1 -0
  343. qpilot-0.2.0/qpilot/web/static/assets/arrow-left-right-DskOmiWB.js +1 -0
  344. qpilot-0.2.0/qpilot/web/static/assets/arrow-up-right-CUA0BG1N.js +1 -0
  345. qpilot-0.2.0/qpilot/web/static/assets/audit-DoqbPwf9.js +1 -0
  346. qpilot-0.2.0/qpilot/web/static/assets/charts-C5usZK2E.js +1 -0
  347. qpilot-0.2.0/qpilot/web/static/assets/chevron-left-dRUodBJI.js +1 -0
  348. qpilot-0.2.0/qpilot/web/static/assets/circle-minus-DeoM5xIK.js +1 -0
  349. qpilot-0.2.0/qpilot/web/static/assets/circle-question-mark-VCGmGiNy.js +1 -0
  350. qpilot-0.2.0/qpilot/web/static/assets/claims-DcoY19HA.js +2 -0
  351. qpilot-0.2.0/qpilot/web/static/assets/cpu-DlUwpr2b.js +1 -0
  352. qpilot-0.2.0/qpilot/web/static/assets/curves-B1ifh85o.js +1 -0
  353. qpilot-0.2.0/qpilot/web/static/assets/curves-XOLvFA_8.js +1 -0
  354. qpilot-0.2.0/qpilot/web/static/assets/data-5sJA9TGe.js +3 -0
  355. qpilot-0.2.0/qpilot/web/static/assets/data-CnsZ1jie.css +1 -0
  356. qpilot-0.2.0/qpilot/web/static/assets/design-96g2-GCi.js +4 -0
  357. qpilot-0.2.0/qpilot/web/static/assets/download-CbMqoFTx.js +1 -0
  358. qpilot-0.2.0/qpilot/web/static/assets/eye-BfvztZUo.js +1 -0
  359. qpilot-0.2.0/qpilot/web/static/assets/file-archive-BHfTFACh.js +1 -0
  360. qpilot-0.2.0/qpilot/web/static/assets/file-down-C3_7QBi7.js +1 -0
  361. qpilot-0.2.0/qpilot/web/static/assets/gate-CkJHqoy7.js +2 -0
  362. qpilot-0.2.0/qpilot/web/static/assets/gene-C_xoqCdK.js +2 -0
  363. qpilot-0.2.0/qpilot/web/static/assets/git-compare-DbZBcP5d.js +1 -0
  364. qpilot-0.2.0/qpilot/web/static/assets/history-DpMybOc0.js +1 -0
  365. qpilot-0.2.0/qpilot/web/static/assets/home-Cj1zA296.js +1 -0
  366. qpilot-0.2.0/qpilot/web/static/assets/inbox-CYTddybL.js +1 -0
  367. qpilot-0.2.0/qpilot/web/static/assets/index-9TwPw7mS.css +1 -0
  368. qpilot-0.2.0/qpilot/web/static/assets/index-BgRULiZl.js +10 -0
  369. qpilot-0.2.0/qpilot/web/static/assets/info-BswNFHHj.js +1 -0
  370. qpilot-0.2.0/qpilot/web/static/assets/instruments-jd32H0mH.js +1 -0
  371. qpilot-0.2.0/qpilot/web/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  372. qpilot-0.2.0/qpilot/web/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  373. qpilot-0.2.0/qpilot/web/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  374. qpilot-0.2.0/qpilot/web/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  375. qpilot-0.2.0/qpilot/web/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  376. qpilot-0.2.0/qpilot/web/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  377. qpilot-0.2.0/qpilot/web/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  378. qpilot-0.2.0/qpilot/web/static/assets/jetbrains-mono-cyrillic-ext-wght-normal-EocZY2iu.woff2 +0 -0
  379. qpilot-0.2.0/qpilot/web/static/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  380. qpilot-0.2.0/qpilot/web/static/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  381. qpilot-0.2.0/qpilot/web/static/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  382. qpilot-0.2.0/qpilot/web/static/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  383. qpilot-0.2.0/qpilot/web/static/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  384. qpilot-0.2.0/qpilot/web/static/assets/layers-D2r9RZW0.js +1 -0
  385. qpilot-0.2.0/qpilot/web/static/assets/map-pin-BiPNAHHZ.js +1 -0
  386. qpilot-0.2.0/qpilot/web/static/assets/planner-DxsNPgnB.js +1 -0
  387. qpilot-0.2.0/qpilot/web/static/assets/plate-Dji8Bed1.js +1 -0
  388. qpilot-0.2.0/qpilot/web/static/assets/plus-bJK5ipt2.js +1 -0
  389. qpilot-0.2.0/qpilot/web/static/assets/point-lCZbdOEQ.js +1 -0
  390. qpilot-0.2.0/qpilot/web/static/assets/prism-Beki88_Y.js +1 -0
  391. qpilot-0.2.0/qpilot/web/static/assets/proposal-B8JUMKOg.js +1 -0
  392. qpilot-0.2.0/qpilot/web/static/assets/react-D_CZztLv.js +9 -0
  393. qpilot-0.2.0/qpilot/web/static/assets/record-0B21W6z5.js +1 -0
  394. qpilot-0.2.0/qpilot/web/static/assets/report-DUyk8Imm.js +1 -0
  395. qpilot-0.2.0/qpilot/web/static/assets/results-C-a1kb0M.js +2 -0
  396. qpilot-0.2.0/qpilot/web/static/assets/rotate-ccw-BAyh8Avz.js +1 -0
  397. qpilot-0.2.0/qpilot/web/static/assets/sections-CKGrfpub.js +2 -0
  398. qpilot-0.2.0/qpilot/web/static/assets/settings-2NKo8-rw.js +1 -0
  399. qpilot-0.2.0/qpilot/web/static/assets/sheetcheck-u29C7m87.js +1 -0
  400. qpilot-0.2.0/qpilot/web/static/assets/shield-alert-CM5JXUmj.js +1 -0
  401. qpilot-0.2.0/qpilot/web/static/assets/shield-check-CR27b7sP.js +1 -0
  402. qpilot-0.2.0/qpilot/web/static/assets/sparkles-BSio9YeX.js +1 -0
  403. qpilot-0.2.0/qpilot/web/static/assets/src-DmrZXBmt.js +1 -0
  404. qpilot-0.2.0/qpilot/web/static/assets/table-2-7dzo7oVA.js +1 -0
  405. qpilot-0.2.0/qpilot/web/static/assets/trace-OgKBXZhz.js +1 -0
  406. qpilot-0.2.0/qpilot/web/static/assets/ui-B9WviEwM.js +44 -0
  407. qpilot-0.2.0/qpilot/web/static/assets/ui-Dlfa4Gi0.css +1 -0
  408. qpilot-0.2.0/qpilot/web/static/assets/undo-2-CNANXPsX.js +1 -0
  409. qpilot-0.2.0/qpilot/web/static/assets/unlink-_3yYur6c.js +1 -0
  410. qpilot-0.2.0/qpilot/web/static/assets/upload-CEiVqAZ1.js +1 -0
  411. qpilot-0.2.0/qpilot/web/static/assets/useMutation-C3bsgiJY.js +1 -0
  412. qpilot-0.2.0/qpilot/web/static/assets/useOpenWorkspace-CiHqAp_k.js +1 -0
  413. qpilot-0.2.0/qpilot/web/static/assets/useQueries-Bpk0ankz.js +1 -0
  414. qpilot-0.2.0/qpilot/web/static/assets/user-fGY6PXpe.js +1 -0
  415. qpilot-0.2.0/qpilot/web/static/assets/words-B1YuDegT.js +1 -0
  416. qpilot-0.2.0/qpilot/web/static/assets/wrench-gyiojqmh.js +1 -0
  417. qpilot-0.2.0/qpilot/web/static/favicon.svg +1 -0
  418. qpilot-0.2.0/qpilot/web/static/index.html +25 -0
  419. qpilot-0.2.0/qpilot/web/studio_views.py +668 -0
  420. qpilot-0.2.0/qpilot/web/units_views.py +414 -0
  421. qpilot-0.2.0/qpilot/web/views.py +666 -0
  422. qpilot-0.2.0/qpilot/web/warm.py +562 -0
  423. qpilot-0.2.0/qpilot_profile.example.yaml +18 -0
  424. qpilot-0.2.0/sim/README.md +235 -0
  425. qpilot-0.2.0/sim/src/qsim/__init__.py +3 -0
  426. qpilot-0.2.0/sim/src/qsim/app.py +883 -0
  427. qpilot-0.2.0/sim/src/qsim/app_lab.py +512 -0
  428. qpilot-0.2.0/sim/src/qsim/ask.py +650 -0
  429. qpilot-0.2.0/sim/src/qsim/attribution.py +160 -0
  430. qpilot-0.2.0/sim/src/qsim/benchmark.py +182 -0
  431. qpilot-0.2.0/sim/src/qsim/cli.py +258 -0
  432. qpilot-0.2.0/sim/src/qsim/compare.py +83 -0
  433. qpilot-0.2.0/sim/src/qsim/data.py +388 -0
  434. qpilot-0.2.0/sim/src/qsim/decisions.py +293 -0
  435. qpilot-0.2.0/sim/src/qsim/demo.py +271 -0
  436. qpilot-0.2.0/sim/src/qsim/designs.py +100 -0
  437. qpilot-0.2.0/sim/src/qsim/engine.py +493 -0
  438. qpilot-0.2.0/sim/src/qsim/explore.py +178 -0
  439. qpilot-0.2.0/sim/src/qsim/external.py +623 -0
  440. qpilot-0.2.0/sim/src/qsim/inference.py +146 -0
  441. qpilot-0.2.0/sim/src/qsim/influence.py +217 -0
  442. qpilot-0.2.0/sim/src/qsim/issues.py +248 -0
  443. qpilot-0.2.0/sim/src/qsim/known_answers.py +1141 -0
  444. qpilot-0.2.0/sim/src/qsim/moves.py +473 -0
  445. qpilot-0.2.0/sim/src/qsim/nodiff.py +264 -0
  446. qpilot-0.2.0/sim/src/qsim/plates.py +277 -0
  447. qpilot-0.2.0/sim/src/qsim/report.py +629 -0
  448. qpilot-0.2.0/sim/src/qsim/study_space.py +134 -0
  449. qpilot-0.2.0/sim/src/qsim/troubleshoot.py +360 -0
  450. qpilot-0.2.0/sim/src/qsim/truth.py +227 -0
  451. qpilot-0.2.0/sim/src/qsim/validate.py +400 -0
  452. qpilot-0.2.0/sim/src/qsim/verdict.py +147 -0
@@ -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.