loopmath 0.2.1__tar.gz → 0.2.3__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 (380) hide show
  1. {loopmath-0.2.1 → loopmath-0.2.3}/PKG-INFO +3 -1
  2. {loopmath-0.2.1 → loopmath-0.2.3}/README.md +2 -0
  3. {loopmath-0.2.1 → loopmath-0.2.3}/pyproject.toml +1 -1
  4. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/__init__.py +1 -1
  5. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/design.py +19 -7
  6. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/fit.py +40 -3
  7. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/forest.py +1 -0
  8. loopmath-0.2.3/src/loopmath/belief/pricing.py +142 -0
  9. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/priors.py +46 -7
  10. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/state.py +9 -3
  11. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/builder/context.py +149 -20
  12. loopmath-0.2.3/src/loopmath/builder/predict.py +306 -0
  13. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/builder/server.py +15 -11
  14. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/cli_registry.py +7 -2
  15. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/onboard/commands.py +5 -0
  16. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/__init__.py +31 -8
  17. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/benchmarks.py +6 -1
  18. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/benchmarks.toml +472 -5
  19. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/build.py +107 -32
  20. loopmath-0.2.3/src/loopmath/priors/bundle/README.md +7 -0
  21. loopmath-0.2.3/src/loopmath/priors/bundle/e0.jsonl.gz +0 -0
  22. loopmath-0.2.3/src/loopmath/priors/bundle/lanes.jsonl.gz +0 -0
  23. loopmath-0.2.3/src/loopmath/priors/bundle/manifest.json +357 -0
  24. loopmath-0.2.3/src/loopmath/priors/bundle/rq1.jsonl.gz +0 -0
  25. loopmath-0.2.3/src/loopmath/priors/bundle/sweep.jsonl.gz +0 -0
  26. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/commands.py +18 -12
  27. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/e0.py +8 -3
  28. loopmath-0.2.3/src/loopmath/priors/lanes.py +281 -0
  29. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/ocpdoc.py +45 -0
  30. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/reduce.py +4 -2
  31. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/registry.py +11 -6
  32. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/rq1.py +13 -3
  33. loopmath-0.2.3/src/loopmath/priors/show.py +182 -0
  34. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/sweep.py +34 -7
  35. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/recommend/commands.py +42 -18
  36. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/recommend/engine.py +55 -17
  37. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/recommend/message.py +50 -4
  38. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/recommend/search.py +9 -6
  39. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/recommend/storeread.py +8 -0
  40. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/skill/skills/loopmath-import-runs/SKILL.md +2 -2
  41. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/skill/skills/loopmath-onboard/SKILL.md +3 -1
  42. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/skill/skills/loopmath-update-fit/SKILL.md +2 -2
  43. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/skill/skills/reference.md +3 -3
  44. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/config.py +5 -2
  45. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/types.py +9 -1
  46. loopmath-0.2.3/src/loopmath/views/assets/builder.css +307 -0
  47. loopmath-0.2.3/src/loopmath/views/assets/builder.html +70 -0
  48. loopmath-0.2.3/src/loopmath/views/assets/builder.js +1350 -0
  49. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/plans.css +1 -0
  50. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/plans.js +29 -4
  51. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/results.js +12 -3
  52. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/posterior.py +14 -1
  53. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath.egg-info/PKG-INFO +3 -1
  54. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath.egg-info/SOURCES.txt +6 -0
  55. loopmath-0.2.3/tests/test_starting_prior.py +233 -0
  56. loopmath-0.2.1/src/loopmath/builder/predict.py +0 -180
  57. loopmath-0.2.1/src/loopmath/priors/bundle/README.md +0 -1
  58. loopmath-0.2.1/src/loopmath/priors/bundle/e0.jsonl.gz +0 -0
  59. loopmath-0.2.1/src/loopmath/priors/bundle/manifest.json +0 -202
  60. loopmath-0.2.1/src/loopmath/priors/bundle/rq1.jsonl.gz +0 -0
  61. loopmath-0.2.1/src/loopmath/priors/bundle/sweep.jsonl.gz +0 -0
  62. loopmath-0.2.1/src/loopmath/priors/show.py +0 -72
  63. loopmath-0.2.1/src/loopmath/views/assets/builder.html +0 -109
  64. {loopmath-0.2.1 → loopmath-0.2.3}/LICENSE +0 -0
  65. {loopmath-0.2.1 → loopmath-0.2.3}/setup.cfg +0 -0
  66. {loopmath-0.2.1 → loopmath-0.2.3}/spec/ocp-v0.2.schema.json +0 -0
  67. {loopmath-0.2.1 → loopmath-0.2.3}/spec/ocp-v0.schema.json +0 -0
  68. {loopmath-0.2.1 → loopmath-0.2.3}/spec/ocp_conformance.py +0 -0
  69. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/__main__.py +0 -0
  70. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapter_host.py +0 -0
  71. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/__init__.py +0 -0
  72. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/atrium.py +0 -0
  73. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/base.py +0 -0
  74. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/bb.py +0 -0
  75. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/bb_support.py +0 -0
  76. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/omp.py +0 -0
  77. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/omp_facts.py +0 -0
  78. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/omp_links.py +0 -0
  79. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/omp_parse.py +0 -0
  80. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/omp_types.py +0 -0
  81. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/opencode.py +0 -0
  82. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/opencode_store.py +0 -0
  83. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/orca.py +0 -0
  84. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/otel_genai.py +0 -0
  85. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/otel_genai_graph.py +0 -0
  86. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/otel_genai_parse.py +0 -0
  87. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/otel_genai_types.py +0 -0
  88. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/otel_genai_usage.py +0 -0
  89. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/paseo.py +0 -0
  90. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/pi.py +0 -0
  91. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/pi_support.py +0 -0
  92. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/adapters/registry.py +0 -0
  93. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/__init__.py +0 -0
  94. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/block.py +0 -0
  95. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/check_pymc.py +0 -0
  96. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/commands.py +0 -0
  97. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/compose.py +0 -0
  98. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/features.py +0 -0
  99. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/gaussian.py +0 -0
  100. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/lookahead.py +0 -0
  101. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/belief/outcome.py +0 -0
  102. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/builder/__init__.py +0 -0
  103. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/builder/cli.py +0 -0
  104. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/cli.py +0 -0
  105. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/cli_graph.py +0 -0
  106. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/cli_support.py +0 -0
  107. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/e0/__init__.py +0 -0
  108. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/e0/arms.py +0 -0
  109. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/e0/cli_e0.py +0 -0
  110. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/e0/estimate.py +0 -0
  111. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/e0/estimate_rework.py +0 -0
  112. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/e0/figures.py +0 -0
  113. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/e0/io.py +0 -0
  114. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/e0/verb.py +0 -0
  115. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/explain.py +0 -0
  116. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/fit.py +0 -0
  117. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/fit_assembly.py +0 -0
  118. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/fit_bayes.py +0 -0
  119. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/fit_masks.py +0 -0
  120. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/fit_predict.py +0 -0
  121. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/fit_pricing.py +0 -0
  122. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/fit_transfer.py +0 -0
  123. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/gaps.py +0 -0
  124. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/gitwalk.py +0 -0
  125. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/grade.py +0 -0
  126. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/grade_support.py +0 -0
  127. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/__init__.py +0 -0
  128. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/artifact_git.py +0 -0
  129. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/artifact_join.py +0 -0
  130. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/artifact_kinds.py +0 -0
  131. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/artifacts.py +0 -0
  132. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/bashparse.py +0 -0
  133. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/bashread_ops.py +0 -0
  134. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/bashwrite_ops.py +0 -0
  135. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/bashwrites.py +0 -0
  136. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/cache.py +0 -0
  137. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/codexio.py +0 -0
  138. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/codexio_parse.py +0 -0
  139. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/codexio_scan.py +0 -0
  140. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/dataset.py +0 -0
  141. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/dataset_build.py +0 -0
  142. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/dataset_core.py +0 -0
  143. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/dataset_edges.py +0 -0
  144. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/dataset_nodes.py +0 -0
  145. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/extract.py +0 -0
  146. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/html_common.py +0 -0
  147. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/html_costcurve.py +0 -0
  148. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/html_css.py +0 -0
  149. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/html_data.py +0 -0
  150. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/html_force.py +0 -0
  151. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/html_render.py +0 -0
  152. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/html_swimlanes.py +0 -0
  153. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler.py +0 -0
  154. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_cli.py +0 -0
  155. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_common.py +0 -0
  156. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_parse.py +0 -0
  157. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_pricing.py +0 -0
  158. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_prompt.py +0 -0
  159. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_prompt_build.py +0 -0
  160. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_prompt_dispatch.py +0 -0
  161. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_prompt_evidence.py +0 -0
  162. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_prompt_node.py +0 -0
  163. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_prompt_records.py +0 -0
  164. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_prompt_result.py +0 -0
  165. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_prompt_session.py +0 -0
  166. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_prompt_tools.py +0 -0
  167. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_prompt_validation.py +0 -0
  168. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_prompt_workflow.py +0 -0
  169. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_report.py +0 -0
  170. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labeler_score.py +0 -0
  171. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/labels.py +0 -0
  172. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/launch.py +0 -0
  173. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/launch_origin.py +0 -0
  174. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/ocp.py +0 -0
  175. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/ocp_emit.py +0 -0
  176. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/ocp_support.py +0 -0
  177. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/render.py +0 -0
  178. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/runfile.py +0 -0
  179. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/runfile_export.py +0 -0
  180. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/runfile_finish.py +0 -0
  181. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/scan.py +0 -0
  182. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/schema.py +0 -0
  183. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/graph/token_completeness.py +0 -0
  184. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ingest/__init__.py +0 -0
  185. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ingest/base.py +0 -0
  186. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ingest/claude_code.py +0 -0
  187. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ingest/claude_code_support.py +0 -0
  188. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ingest/codex.py +0 -0
  189. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ingest/codex_support.py +0 -0
  190. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ingest/ocp.py +0 -0
  191. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ingest/ocp_analysis.py +0 -0
  192. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ingest/ocp_artifact.py +0 -0
  193. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ingest/ocp_common.py +0 -0
  194. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ingest/ocp_convert.py +0 -0
  195. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ingest/ocp_projection.py +0 -0
  196. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/logmatch/__init__.py +0 -0
  197. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/logmatch/artifacts.py +0 -0
  198. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/logmatch/clip.py +0 -0
  199. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/logmatch/costs.py +0 -0
  200. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/logmatch/match.py +0 -0
  201. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/logmatch/settle.py +0 -0
  202. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/logmatch/tariff.py +0 -0
  203. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ocp/__init__.py +0 -0
  204. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ocp/canonical.py +0 -0
  205. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ocp/commands.py +0 -0
  206. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ocp/conformance.py +0 -0
  207. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ocp/contractv3.py +0 -0
  208. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ocp/emit.py +0 -0
  209. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ocp/migrate.py +0 -0
  210. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ocp/schema/ocp-v0.2.schema.json +0 -0
  211. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ocp/schema/ocp-v0.3.schema.json +0 -0
  212. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ocp/schema/ocp-v0.schema.json +0 -0
  213. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/ocp/version.py +0 -0
  214. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/onboard/__init__.py +0 -0
  215. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/onboard/history.py +0 -0
  216. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/onboard/label.py +0 -0
  217. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/onboard/usual.py +0 -0
  218. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/output.py +0 -0
  219. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/pool.py +0 -0
  220. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/price.py +0 -0
  221. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/price_validation.py +0 -0
  222. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/prices.toml +0 -0
  223. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/history.py +0 -0
  224. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/priors/validate.py +0 -0
  225. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/receipts.py +0 -0
  226. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/recommend/__init__.py +0 -0
  227. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/recommend/backlog.py +0 -0
  228. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/recommend/curve.py +0 -0
  229. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/recommend/gain.py +0 -0
  230. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/recommend/receipts.py +0 -0
  231. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/recommend/stats.py +0 -0
  232. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/report/__init__.py +0 -0
  233. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/report/html.py +0 -0
  234. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/report/terminal.py +0 -0
  235. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/report/terminal_exclusions.py +0 -0
  236. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/report/terminal_format.py +0 -0
  237. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/report/terminal_read.py +0 -0
  238. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/research_defaults.py +0 -0
  239. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/research_paths.py +0 -0
  240. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/scoring.py +0 -0
  241. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/scoring_comparisons.py +0 -0
  242. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/scoring_predictive.py +0 -0
  243. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/share/__init__.py +0 -0
  244. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/share/commands.py +0 -0
  245. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/share/export.py +0 -0
  246. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/share/import_.py +0 -0
  247. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/skill/__init__.py +0 -0
  248. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/skill/commands.py +0 -0
  249. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/skill/doctor.py +0 -0
  250. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/skill/install.py +0 -0
  251. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/skill/skills/loopmath/SKILL.md +0 -0
  252. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/skill/skills/loopmath-plan-task/SKILL.md +0 -0
  253. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/skill/skills/loopmath-record-run/SKILL.md +0 -0
  254. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/__init__.py +0 -0
  255. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/budget.py +0 -0
  256. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/commands.py +0 -0
  257. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/finish.py +0 -0
  258. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/fitjob.py +0 -0
  259. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/home.py +0 -0
  260. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/ids.py +0 -0
  261. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/lock.py +0 -0
  262. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/record.py +0 -0
  263. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/report.py +0 -0
  264. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/runs.py +0 -0
  265. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/store/status.py +0 -0
  266. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/surface.py +0 -0
  267. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/surface_bootstrap.py +0 -0
  268. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/surface_estimation.py +0 -0
  269. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/surface_frame.py +0 -0
  270. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/surface_presentation.py +0 -0
  271. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/surface_support.py +0 -0
  272. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/taskmodel.py +0 -0
  273. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/__init__.py +0 -0
  274. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/README.md +0 -0
  275. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/base.css +0 -0
  276. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/common.js +0 -0
  277. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/estimates.css +0 -0
  278. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/estimates.js +0 -0
  279. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/graph.css +0 -0
  280. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/graph.js +0 -0
  281. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/results.css +0 -0
  282. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/runs.js +0 -0
  283. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/viz.css +0 -0
  284. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/assets/viz.js +0 -0
  285. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/common.py +0 -0
  286. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/plans.py +0 -0
  287. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/views/runs.py +0 -0
  288. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/__init__.py +0 -0
  289. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/candidates.py +0 -0
  290. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/catalog/best_of_n.toml +0 -0
  291. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/catalog/implement_review.toml +0 -0
  292. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/catalog/plan_implement.toml +0 -0
  293. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/catalog/plan_implement_review.toml +0 -0
  294. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/catalog/solo.toml +0 -0
  295. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/catalog/swarm.toml +0 -0
  296. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/commands.py +0 -0
  297. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/diff.py +0 -0
  298. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/format.py +0 -0
  299. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/graphview.py +0 -0
  300. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/ids.py +0 -0
  301. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/infer.py +0 -0
  302. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/models.py +0 -0
  303. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/ocp.py +0 -0
  304. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath/workflows/shapes.py +0 -0
  305. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath.egg-info/dependency_links.txt +0 -0
  306. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath.egg-info/entry_points.txt +0 -0
  307. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath.egg-info/requires.txt +0 -0
  308. {loopmath-0.2.1 → loopmath-0.2.3}/src/loopmath.egg-info/top_level.txt +0 -0
  309. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_adapter_atrium.py +0 -0
  310. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_adapter_bb.py +0 -0
  311. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_adapter_omp.py +0 -0
  312. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_adapter_opencode.py +0 -0
  313. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_adapter_opencode_output_tokens.py +0 -0
  314. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_adapter_orca.py +0 -0
  315. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_adapter_otel_genai.py +0 -0
  316. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_adapter_paseo.py +0 -0
  317. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_adapter_services.py +0 -0
  318. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_adapters.py +0 -0
  319. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_adapters_pi.py +0 -0
  320. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_arms.py +0 -0
  321. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_cli_graph.py +0 -0
  322. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_cli_help_lists.py +0 -0
  323. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_cli_labeler.py +0 -0
  324. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_cli_main_module.py +0 -0
  325. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_cli_plan_hidden.py +0 -0
  326. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_cli_prices.py +0 -0
  327. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_cli_startup.py +0 -0
  328. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_color_proof.py +0 -0
  329. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_duplicate_guards.py +0 -0
  330. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_estimate.py +0 -0
  331. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_explain.py +0 -0
  332. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_fit.py +0 -0
  333. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_gaps.py +0 -0
  334. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_gitwalk.py +0 -0
  335. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_grade.py +0 -0
  336. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_artifact_recording.py +0 -0
  337. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_artifacts.py +0 -0
  338. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_bashwrites.py +0 -0
  339. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_cache.py +0 -0
  340. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_codexio.py +0 -0
  341. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_dataset.py +0 -0
  342. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_extract.py +0 -0
  343. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_glue.py +0 -0
  344. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_html.py +0 -0
  345. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_labeler.py +0 -0
  346. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_labeler_evidence_matrix.py +0 -0
  347. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_launch.py +0 -0
  348. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_ocp.py +0 -0
  349. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_render.py +0 -0
  350. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_runfile.py +0 -0
  351. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_graph_schema.py +0 -0
  352. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_home_guard.py +0 -0
  353. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_html.py +0 -0
  354. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_ingest.py +0 -0
  355. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_ingest_claude_code.py +0 -0
  356. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_ingest_codex.py +0 -0
  357. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_ingest_ocp.py +0 -0
  358. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_io.py +0 -0
  359. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_not_run_summary.py +0 -0
  360. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_ocp_conformance.py +0 -0
  361. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_ocp_golden.py +0 -0
  362. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_ocp_open_enums.py +0 -0
  363. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_ocp_versioning.py +0 -0
  364. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_output_html_path.py +0 -0
  365. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_packaging_metadata.py +0 -0
  366. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_packaging_smoke.py +0 -0
  367. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_pool.py +0 -0
  368. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_preview_script.py +0 -0
  369. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_price.py +0 -0
  370. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_receipts.py +0 -0
  371. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_release_paths.py +0 -0
  372. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_runfile_privacy_n1b.py +0 -0
  373. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_scoring.py +0 -0
  374. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_split_rebindings.py +0 -0
  375. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_surface.py +0 -0
  376. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_taskmodel_features.py +0 -0
  377. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_terminal.py +0 -0
  378. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_types_control.py +0 -0
  379. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_types_piece_prediction.py +0 -0
  380. {loopmath-0.2.1 → loopmath-0.2.3}/tests/test_v01_scaffold.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loopmath
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Plan agent workflows, record how they went, and learn from your own Claude Code and Codex logs
5
5
  Author: QKV Labs
6
6
  License-Expression: MIT
@@ -103,6 +103,8 @@ Reading the history uses up to 8 worker processes (one per CPU, at most 8). Set
103
103
 
104
104
  loopmath ships with a prior built from our own sweeps and experiments, so `recommend` gives an answer before you have any history. Your own runs then move it.
105
105
 
106
+ The prior ships inside the package: our runs, in the shared form (no titles, paths, commands or free text), and the published benchmark results. They are the same files we fit with, and each release updates them. `onboard` and the results page say in one line which prior they start from, and `loopmath prior show` lists it source by source.
107
+
106
108
  ## The loop
107
109
 
108
110
  The plan and record skills take an agent through the loop in three commands:
@@ -65,6 +65,8 @@ Reading the history uses up to 8 worker processes (one per CPU, at most 8). Set
65
65
 
66
66
  loopmath ships with a prior built from our own sweeps and experiments, so `recommend` gives an answer before you have any history. Your own runs then move it.
67
67
 
68
+ The prior ships inside the package: our runs, in the shared form (no titles, paths, commands or free text), and the published benchmark results. They are the same files we fit with, and each release updates them. `onboard` and the results page say in one line which prior they start from, and `loopmath prior show` lists it source by source.
69
+
68
70
  ## The loop
69
71
 
70
72
  The plan and record skills take an agent through the loop in three commands:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "loopmath"
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  description = "Plan agent workflows, record how they went, and learn from your own Claude Code and Codex logs"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -5,4 +5,4 @@ The command line (`loopmath`, or its alias `loop`) is the interface; see the
5
5
  README and `loopmath --help`.
6
6
  """
7
7
 
8
- __version__ = "0.2.1"
8
+ __version__ = "0.2.3"
@@ -13,7 +13,7 @@ A term is `(node_id, parent_id, value)`. Rows:
13
13
  from __future__ import annotations
14
14
 
15
15
  import math
16
- from collections.abc import Iterable
16
+ from collections.abc import Iterable, Mapping
17
17
  from dataclasses import dataclass, field
18
18
  from datetime import datetime
19
19
  from functools import lru_cache
@@ -25,6 +25,7 @@ from ..types import (
25
25
  Workflow,
26
26
  )
27
27
  from .forest import canonical_model_id, canonical_role, model_path
28
+ from .pricing import price_term
28
29
 
29
30
  Term = tuple[str, "str | None", float]
30
31
 
@@ -324,12 +325,15 @@ def _effort_scaled(terms: tuple[Term, ...], weight: float) -> tuple[Term, ...]:
324
325
 
325
326
 
326
327
  def cost_rest(st: Structure, piece: str, k: int, setting: Setting | None = None, *,
327
- source: str | None = None, effort: float = 1.0) -> tuple[Term, ...]:
328
+ source: str | None = None, effort: float = 1.0,
329
+ prices: Mapping[str, float] | None = None) -> tuple[Term, ...]:
328
330
  """Cost and tokens rows without the task part: setting, role, topology, position, round, control,
329
331
  and with a `source` the position x source node `psrc:<shape>#<pos>|<source>` and the family x source
330
332
  node `fsrc:<family>|<source>` (spec 04 section 1). `effort` is the value of the terms a timebox caps, the
331
333
  effort and shape (topology, position) terms (`effort_weight`: below 1 under a timebox); the role, psrc and
332
- fsrc terms stay at 1."""
334
+ fsrc terms stay at 1. `prices` is a fit's price offsets (`FitState.price_offsets`, spec 04 section 2): the
335
+ model's `price:offset` term goes last. The cost head has that node and the tokens head does not, so a
336
+ prediction's shared row prices only its cost."""
333
337
  s = setting or st.settings[piece]
334
338
  h, m, e = _s(s)
335
339
  shape = st.shape or st.workflow_id
@@ -343,7 +347,8 @@ def cost_rest(st: Structure, piece: str, k: int, setting: Setting | None = None,
343
347
  fam = model_path(m)[1]
344
348
  terms += ((f"psrc:{position}|{source}", f"position:{position}", 1.0),
345
349
  (f"fsrc:{fam}|{source}", f"family:{fam}", 1.0))
346
- return terms + tuple(round_terms(k)) + tuple(_control_terms(st.k_max, st.widths[piece]))
350
+ return (terms + tuple(round_terms(k)) + tuple(_control_terms(st.k_max, st.widths[piece]))
351
+ + price_term(prices, m))
347
352
 
348
353
 
349
354
  def gate_rest(st: Structure, gate: GateInfo, k: int) -> tuple[Term, ...]:
@@ -421,6 +426,7 @@ class AttemptObs:
421
426
  weight: float
422
427
  setting: Setting
423
428
  repriced: bool = True # False: the recorded dollars, not the current tariff
429
+ streams: tuple[float, ...] = () # input, cache read, cache write, output tokens (pricing.STREAMS order)
424
430
 
425
431
 
426
432
  @dataclass
@@ -458,8 +464,12 @@ def source_label(doc: dict) -> str:
458
464
  ext = run.get("ext") or {}
459
465
  share = ext.get("dev.loopmath.share")
460
466
  if isinstance(share, dict):
461
- org = share.get("org") or share.get("org_hash") or (run.get("task") or {}).get("org") or "shared"
462
- return f"shared:{org}"
467
+ # the importer writes `source` and `task.org` as `shared:<org_hash>` already: never prefix twice. With no
468
+ # org at all the source is `shared`, the name `fit --without shared` takes (0.2.3, 22X review N1)
469
+ org = (share.get("source") or share.get("org") or share.get("org_hash") or (run.get("task") or {}).get("org")
470
+ or "")
471
+ name = str(org).removeprefix("shared:")
472
+ return f"shared:{name}" if name and name != "shared" else "shared"
463
473
  src = (run.get("task") or {}).get("source")
464
474
  kind = src.get("kind") if isinstance(src, dict) else src
465
475
  kind = str(kind or "").strip().lower()
@@ -761,7 +771,9 @@ def parse_run(doc: dict, *, now: datetime | None = None, rule: AcceptanceRule |
761
771
  if weight <= 0 or ((usd is None or usd <= 0) and not tokens):
762
772
  dropped["attempt without usable cost"] = dropped.get("attempt without usable cost", 0) + 1
763
773
  continue
764
- attempts.append(AttemptObs(piece, k, usd if usd and usd > 0 else None, tokens, weight, setting, repriced))
774
+ streams = tuple(_ocp_fields(cost)[name] for name in _OCP_TOKEN_FIELDS)
775
+ attempts.append(AttemptObs(piece, k, usd if usd and usd > 0 else None, tokens, weight, setting, repriced,
776
+ streams))
765
777
 
766
778
  gates = _gate_observations(doc, st, by_piece_round)
767
779
  use_rule = rule or rule_from_doc(doc)
@@ -36,6 +36,7 @@ from .design import (DESIGN_VERSION, Term, Unusable, cost_row, gate_row, parse_r
36
36
  from .features import FeatureTally, add_horizon, drop_features, horizon_nodes, horizon_specs
37
37
  from .block import leaf_split
38
38
  from .forest import Forest, default_scale, fixed_sd, scale_group
39
+ from .pricing import PRICE_NODE, PRICE_PRIOR_SD, STREAMS, fit_offsets, price_term
39
40
  from .gaussian import N_DRAWS, Factors, GaussianHead, HeadFit, LogisticHead, Prior
40
41
  from .state import SCORE_CLAMP, inverse_transform, transform # noqa: F401 (re-exported)
41
42
 
@@ -163,6 +164,7 @@ class HeadRows:
163
164
  sources: list[str] = field(default_factory=list)
164
165
  types: list[str] = field(default_factory=list)
165
166
  not_repriced: int = 0 # cost rows on recorded dollars, not the current tariff
167
+ prices: dict = field(default_factory=dict) # cost head: the fit's price offsets (pricing.fit_offsets)
166
168
 
167
169
  def add(self, terms, y, w, run, source, ttype):
168
170
  self.rows.append(terms)
@@ -213,6 +215,8 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
213
215
  stored: set[str] = set() # run ids of the user's store, whose copy wins
214
216
  overlap: Counter = Counter()
215
217
  labels: Counter = Counter()
218
+ model_runs: dict[str, set[str]] = defaultdict(set) # the price offset's inputs (spec 04 section 2)
219
+ model_streams: dict[str, list[float]] = defaultdict(lambda: [0.0] * len(STREAMS))
216
220
  for item in docs:
217
221
  origin, doc = item if isinstance(item, tuple) else (None, item)
218
222
  if origin is not None and _excluded(origin, without):
@@ -258,6 +262,10 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
258
262
  if a.usd:
259
263
  heads["cost"].add(terms, math.log(a.usd), a.weight, pr.run_id, pr.source, ttype)
260
264
  heads["cost"].not_repriced += not a.repriced
265
+ model_runs[a.setting.model].add(pr.run_id)
266
+ acc = model_streams[a.setting.model]
267
+ for i, v in enumerate(a.streams):
268
+ acc[i] += v
261
269
  if a.tokens:
262
270
  heads["tokens"].add(terms, math.log(a.tokens), a.weight, pr.run_id, pr.source, ttype)
263
271
  for gi, k, passed in pr.gates:
@@ -289,10 +297,34 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
289
297
  coding = tally.horizon_coding() # the horizon terms need every run's horizon first (spec 04 section 1)
290
298
  for hr in heads.values():
291
299
  hr.rows = add_horizon(drop_features(hr.rows, tally.nodes), hr.runs, tally.run_horizon, coding)
300
+ add_prices(heads["cost"], {m: len(r) for m, r in model_runs.items()}, model_streams)
292
301
  return (heads, score_info, dict(dropped), dict(runs_by_source), {c: dict(n) for c, n in config_support.items()},
293
302
  dict(checks), dict(overlap), dict(labels), tally)
294
303
 
295
304
 
305
+ def add_prices(hr: HeadRows, runs: dict[str, int], streams: dict[str, list[float]]) -> None:
306
+ """The price offset (spec 04 section 2): the fit's offsets from its runs and token mix under the current price
307
+ table, and each cost row with its model's `price:offset` term (`pricing.price_term`, as at prediction)."""
308
+ hr.prices = fit_offsets(runs, streams, design_rows._price_table())
309
+ offsets = hr.prices["models"]
310
+ if not offsets:
311
+ return
312
+ rows = []
313
+ for row in hr.rows:
314
+ model = next((n[len("model:"):] for n, _, _ in row if n.startswith("model:")), None)
315
+ term = price_term(offsets, model) if model else ()
316
+ rows.append(row + list(term) if term else row)
317
+ hr.rows = rows
318
+
319
+
320
+ def price_spec(hr: HeadRows) -> list:
321
+ """The prior that holds `price:offset` at 1, on the cost head of a fit with offsets, --no-prior included."""
322
+ if not hr.prices.get("models"):
323
+ return []
324
+ return [prior_data.FactorSpec("cost", [(PRICE_NODE, None, 1.0)], 1.0, PRICE_PRIOR_SD ** 2,
325
+ f"{PRICE_NODE} held at 1, prior N(1, {PRICE_PRIOR_SD}^2)")]
326
+
327
+
296
328
  # ---------------------------------------------------------------- matrices and priors
297
329
 
298
330
  def build_matrix(rows: list[list[Term]], forest: Forest, extra_nodes: Iterable[tuple[str, str | None]] = ()):
@@ -494,7 +526,8 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
494
526
  with fit_lock(home, wait_s):
495
527
  remove_partials(home)
496
528
  config = _read_config(home)
497
- weight = float(config.get("benchmark_prior_weight", prior_data.BENCHMARK_PRIOR_WEIGHT))
529
+ raw_weight = config.get("benchmark_prior_weight") # unset: each benchmark's own weight (spec 04 section 5)
530
+ weight = None if raw_weight is None else float(raw_weight)
498
531
  try:
499
532
  features, features_error = FeatureSet.from_config(config.get("features")), None
500
533
  except FeatureConfigError as exc: # a hand-edited config.toml: fit with the built-ins, say why
@@ -518,9 +551,10 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
518
551
  raise UnknownSource(unknown_source_message(unknown, known))
519
552
  if not any(hr.rows for hr in heads_rows.values()):
520
553
  raise NothingToFit(_nothing_to_fit(no_prior, without, dropped))
521
- specs = []
554
+ specs, bench_weights = [], {}
522
555
  if not no_prior and "benchmark" not in without:
523
556
  specs = prior_data.benchmark_factors(benchmarks, weight=weight)
557
+ bench_weights = prior_data.benchmark_weights(benchmarks, weight=weight)
524
558
  seed_key = input_key(heads_rows, specs, {"no_prior": no_prior, "without": sorted(without), "eb": eb,
525
559
  "benchmark_prior_weight": weight})
526
560
  forest = Forest()
@@ -532,6 +566,8 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
532
566
  nodes = horizon_nodes(hr.rows)
533
567
  if nodes: # the horizon priors, --no-prior included (spec 04 section 1)
534
568
  head_specs += horizon_specs(name, hr.kind, nodes)
569
+ if name == "cost": # the price offset (spec 04 section 2)
570
+ head_specs += price_spec(hr)
535
571
  fitted[name] = fit_head(hr, forest, head_specs, eb=eb)
536
572
  fit_id = new_fit_id(fits, now)
537
573
  partial = fits / f"{fit_id}.partial"
@@ -570,7 +606,7 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
570
606
  "seed_key": seed_key, "timebox_effort": design_rows.TIMEBOX_EFFORT_COST,
571
607
  "timebox_terms": list(design_rows.TIMEBOX_LEVELS),
572
608
  "options": {"no_prior": no_prior, "without": list(without), "full": full, "eb": eb,
573
- "benchmark_prior_weight": weight},
609
+ "benchmark_prior_weight": weight, "benchmark_weights": bench_weights},
574
610
  "runs_by_source": runs_by_source,
575
611
  "n_runs": {"prior": sum(v for k, v in runs_by_source.items() if k != "user"),
576
612
  "user": runs_by_source.get("user", 0)},
@@ -592,6 +628,7 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
592
628
  "design_version": DESIGN_VERSION,
593
629
  "features": {**tally.report, **({"error": features_error} if features_error else {})},
594
630
  "horizons": tally.horizon_report(),
631
+ "price_offsets": heads_rows["cost"].prices,
595
632
  }
596
633
  if full:
597
634
  meta["full"] = _full_check(fitted)
@@ -55,6 +55,7 @@ FIXED_SD: dict[str, tuple[float, float, float]] = {
55
55
  "round": (2.0, 2.0, 2.0),
56
56
  "control": (1.0, 1.0, 1.0),
57
57
  "horizon": (10.0, 10.0, 10.0), # wide: the informative prior is a FactorSpec in fit() (spec 04 section 1)
58
+ "price": (10.0, 10.0, 10.0), # wide: fit() holds `price:offset` at 1 by a FactorSpec (spec 04 section 2)
58
59
  }
59
60
 
60
61
  HYPER_SD = 0.7 # sd of the log-normal hyperprior on each phi, around the defaults above
@@ -0,0 +1,142 @@
1
+ """The price offset on the cost head (spec 04 section 2, 0.2.2, lane 22C).
2
+
3
+ A model's run cost moves with its list price. Every cost row of a model version `m` (not the tokens, gate,
4
+ success or score rows) carries the fixed term `price:offset` with the value
5
+
6
+ o_m = s_m * log(P_m / R_m), s_m = n0 / (n0 + n_m),
7
+
8
+ and the node's coefficient is held at 1 by a tight prior, so the term is a fixed offset on the log cost scale,
9
+ the same at fit and at prediction:
10
+
11
+ - `P_m` is m's blended list price ($/Mtok, the current price table) under the token mix (the shares of input,
12
+ cache read, cache write and output tokens) of the cost rows of m's family in the fit.
13
+ - `R_m` is the run-weighted geometric mean of the blended prices, under the same mix, of the family's models
14
+ with cost rows. A family with no cost rows anchors on its provider the same way (the provider's mix and
15
+ models). No anchor, or no price for m, gives no term.
16
+ - `n_m` is m's runs with a cost row in the fit; `PRICE_OFFSET_RUNS` is n0: the price counts like n0 of the
17
+ model's own runs, and its share of the prediction shrinks as they accumulate.
18
+
19
+ `fit_offsets` computes the offset of every model with cost rows and of every model in the price table once per
20
+ fit; `meta.json` `price_offsets` stores them, and prediction and the workflow search read them from there.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import math
26
+ from collections.abc import Iterable, Mapping
27
+
28
+ from .forest import canonical_model_id, model_path
29
+
30
+ PRICE_NODE = "price:offset"
31
+ PRICE_LEVEL = "price"
32
+ # n0: the list price counts like this many of the model's own runs (as one benchmark result counts like
33
+ # BENCHMARK_PRIOR_WEIGHT = 5 runs). A heuristic, to tune when stores have new versions with a few runs.
34
+ PRICE_OFFSET_RUNS = 5.0
35
+ PRICE_PRIOR_SD = 1e-3 # the coefficient's prior N(1, sd^2): a fixed offset, not a learned elasticity
36
+ STREAMS = ("input", "cache_read", "cache_write", "output") # price table rate keys, in the order of `streams`
37
+
38
+
39
+ def mix_of(totals: Iterable[float]) -> tuple[float, ...] | None:
40
+ """Token shares per stream (STREAMS order), or None without tokens."""
41
+ t = [max(0.0, float(x)) for x in totals]
42
+ s = sum(t)
43
+ return tuple(x / s for x in t) if s > 0 else None
44
+
45
+
46
+ def blended(rate: Mapping[str, float] | None, mix: tuple[float, ...] | None) -> float | None:
47
+ """$/Mtok under `mix`, or None when the model has no rate, no mix, or a blended price of 0 (free)."""
48
+ if rate is None or mix is None:
49
+ return None
50
+ p = sum(share * float(rate.get(stream) or 0.0) for share, stream in zip(mix, STREAMS))
51
+ return p if p > 0 else None
52
+
53
+
54
+ def shrink(runs: float, n0: float = PRICE_OFFSET_RUNS) -> float:
55
+ return n0 / (n0 + max(0.0, float(runs)))
56
+
57
+
58
+ def _add(acc: dict[str, list[float]], key: str, values: Iterable[float]) -> None:
59
+ cur = acc.setdefault(key, [0.0] * len(STREAMS))
60
+ for i, v in enumerate(values):
61
+ cur[i] += float(v)
62
+
63
+
64
+ def fit_offsets(runs: Mapping[str, int], streams: Mapping[str, Iterable[float]], table, *,
65
+ n0: float = PRICE_OFFSET_RUNS) -> dict:
66
+ """The fit's `price_offsets`: {n0, models: {model: offset}, detail, anchors}.
67
+
68
+ `runs`: model (canonical id) to its runs with a cost row; `streams`: model to its token totals over those
69
+ rows (STREAMS order); `table`: a price.PriceTable, or None (no offsets). Offsets of 0 are left out of
70
+ `models`; `detail` says for each priced model which anchor it used, or why it has none."""
71
+ out: dict = {"n0": n0, "models": {}, "detail": {}, "anchors": {}}
72
+ if table is None:
73
+ return out
74
+ fam_tok: dict[str, list[float]] = {}
75
+ prov_tok: dict[str, list[float]] = {}
76
+ fam_models: dict[str, dict[str, int]] = {}
77
+ prov_models: dict[str, dict[str, int]] = {}
78
+ for model, n in sorted(runs.items()):
79
+ if n <= 0:
80
+ continue
81
+ prov, fam, _ = model_path(model)
82
+ fam_models.setdefault(fam, {})[model] = int(n)
83
+ prov_models.setdefault(prov, {})[model] = int(n)
84
+ values = list(streams.get(model) or [0.0] * len(STREAMS))
85
+ _add(fam_tok, fam, values)
86
+ _add(prov_tok, prov, values)
87
+ candidates = set(runs) | {canonical_model_id(k) for k in table.rates}
88
+ anchors: dict[str, dict | None] = {}
89
+
90
+ def anchor(key: str, members: dict[str, int], tokens: list[float] | None) -> dict | None:
91
+ if key in anchors:
92
+ return anchors[key]
93
+ mix = mix_of(tokens or [])
94
+ priced = {m: (n, blended(table.rate(m), mix)) for m, n in members.items()}
95
+ priced = {m: (n, p) for m, (n, p) in priced.items() if p is not None}
96
+ found = None
97
+ if mix is not None and priced:
98
+ total = sum(n for n, _ in priced.values())
99
+ log_ref = sum(n * math.log(p) for n, p in priced.values()) / total
100
+ found = {"mix": mix, "log_ref": log_ref,
101
+ "reference_usd_per_mtok": round(math.exp(log_ref), 6),
102
+ "models": {m: n for m, (n, _) in sorted(priced.items())},
103
+ "todo": sorted(m for m in priced if table.is_todo(m))}
104
+ anchors[key] = found
105
+ return found
106
+
107
+ for model in sorted(candidates):
108
+ prov, fam, _ = model_path(model)
109
+ a, key = None, None
110
+ if fam in fam_models:
111
+ key = f"family:{fam}"
112
+ a = anchor(key, fam_models[fam], fam_tok.get(fam))
113
+ if a is None and prov in prov_models and fam not in fam_models:
114
+ key = f"provider:{prov}"
115
+ a = anchor(key, prov_models[prov], prov_tok.get(prov))
116
+ if a is None:
117
+ out["detail"][model] = {"runs": int(runs.get(model, 0)), "note": "no anchor with runs and prices"}
118
+ continue
119
+ p = blended(table.rate(model), a["mix"])
120
+ if p is None:
121
+ out["detail"][model] = {"runs": int(runs.get(model, 0)), "anchor": key, "note": "no price"}
122
+ continue
123
+ n = int(runs.get(model, 0))
124
+ s = shrink(n, n0)
125
+ offset = s * (math.log(p) - a["log_ref"])
126
+ out["detail"][model] = {"runs": n, "anchor": key, "usd_per_mtok": round(p, 6), "shrink": round(s, 6),
127
+ "log_ratio": round(math.log(p) - a["log_ref"], 6), "offset": round(offset, 6),
128
+ **({"todo": True} if table.is_todo(model) else {})}
129
+ if offset != 0.0:
130
+ out["models"][model] = offset
131
+ out["anchors"] = {k: {**{kk: vv for kk, vv in v.items() if kk != "log_ref"}, "mix": [round(x, 6) for x in v["mix"]]}
132
+ for k, v in sorted(anchors.items()) if v}
133
+ return out
134
+
135
+
136
+ def price_term(offsets: Mapping[str, float] | None, model: str) -> tuple[tuple[str, None, float], ...]:
137
+ """The cost row's price term for `model` (a canonical id) under a fit's offsets: none for an offset of 0 or
138
+ a fit without offsets."""
139
+ if not offsets:
140
+ return ()
141
+ o = offsets.get(model)
142
+ return ((PRICE_NODE, None, float(o)),) if o else ()
@@ -10,8 +10,11 @@
10
10
  version nodes: success head, the logit gap to the benchmark's reference model with variance
11
11
  `1 / (w p (1 - p))`; cost head, the log cost ratio with variance `1 / w`; tokens head
12
12
  (`kind = "tokens"`, published total tokens for the evaluation), the log token ratio
13
- with variance `1 / w`. `w` is config `benchmark_prior_weight` (default 5): one benchmark
14
- result counts like about 5 runs.
13
+ with variance `1 / w`. `w` is one model's share of the benchmark's weight `w_b` (0.2.3,
14
+ lane 23K): the `weight` of its `[[benchmark]]` table (default 5, about 5 runs), shared by
15
+ the model's k results on that benchmark, so each gets `w_b / k` and a model with five
16
+ efforts pulls as hard in total as a model with one. Config `benchmark_prior_weight`, when
17
+ set, is one `w_b` for every benchmark; 0 turns the factors off.
15
18
  """
16
19
 
17
20
  from __future__ import annotations
@@ -91,8 +94,30 @@ def _model_terms(model: str, effort: str | None, sign: float) -> list[tuple[str,
91
94
  return terms
92
95
 
93
96
 
94
- def benchmark_factors(path: Path | None = None, *, weight: float = BENCHMARK_PRIOR_WEIGHT) -> list[FactorSpec]:
95
- """Prior factors from `benchmarks.toml`. A missing file gives no factors."""
97
+ def benchmark_weights(path: Path | None = None, *, weight: float | None = None) -> dict[str, float]:
98
+ """Each benchmark's weight `w_b`: `weight` for every benchmark when it is given (config
99
+ `benchmark_prior_weight`), else the benchmark's own `weight` field, else 5. Empty for a missing file."""
100
+ path = path or benchmark_path()
101
+ if not path.is_file():
102
+ return {}
103
+ data = tomllib.loads(path.read_text(encoding="utf-8"))
104
+ return {str(b["id"]): _weight_of(b, weight) for b in data.get("benchmark") or [] if b.get("id")}
105
+
106
+
107
+ def _weight_of(bench: dict, weight: float | None) -> float:
108
+ if weight is not None:
109
+ return float(weight)
110
+ own = bench.get("weight")
111
+ return float(own) if isinstance(own, (int, float)) and not isinstance(own, bool) else BENCHMARK_PRIOR_WEIGHT
112
+
113
+
114
+ def benchmark_factors(path: Path | None = None, *, weight: float | None = None) -> list[FactorSpec]:
115
+ """Prior factors from `benchmarks.toml`. A missing file gives no factors.
116
+
117
+ `weight` (config `benchmark_prior_weight`) overrides every benchmark's own weight; None reads
118
+ each benchmark's `weight`. A model's k results on one benchmark share its weight: each factor
119
+ has weight `w_b / k` (B1). A benchmark with weight 0 or less gives no factors.
120
+ """
96
121
  path = path or benchmark_path()
97
122
  if not path.is_file():
98
123
  return []
@@ -103,11 +128,15 @@ def benchmark_factors(path: Path | None = None, *, weight: float = BENCHMARK_PRI
103
128
  for bid, bench in benches.items():
104
129
  kind = str(bench.get("kind") or "success")
105
130
  ref = canonical_model_id(str(bench.get("reference") or ""))
131
+ w_b = _weight_of(bench, weight)
132
+ if w_b <= 0:
133
+ continue
106
134
  rows = [r for r in results if r["benchmark"] == bid]
107
135
  ref_rows = [r for r in rows if canonical_model_id(str(r["model"])) == ref]
108
136
  if not ref_rows:
109
137
  continue
110
138
  ref_value = float(ref_rows[0]["value"])
139
+ by_model: dict[str, list[tuple[str, list, float, dict]]] = {} # model -> (head, terms, mean, row) per result
111
140
  for r in rows:
112
141
  model = canonical_model_id(str(r["model"]))
113
142
  if model == ref:
@@ -117,14 +146,24 @@ def benchmark_factors(path: Path | None = None, *, weight: float = BENCHMARK_PRI
117
146
  terms = _cancel(terms)
118
147
  if not terms:
119
148
  continue
120
- note = f"{bid}: {model} {value} against {ref} {ref_value} ({r.get('url', '')}, {r.get('date', '')})"
121
149
  if kind == "success":
122
150
  p = min(max(value, 0.01), 0.99)
123
151
  p_ref = min(max(ref_value, 0.01), 0.99)
124
152
  gap = math.log(p / (1 - p)) - math.log(p_ref / (1 - p_ref))
125
- out.append(FactorSpec("success", terms, gap, 1.0 / (weight * p * (1 - p)), note))
153
+ by_model.setdefault(model, []).append(("success", terms, gap, r))
126
154
  elif kind in ("cost", "tokens") and value > 0 and ref_value > 0: # Tokens feed the tokens head only
127
- out.append(FactorSpec(kind, terms, math.log(value / ref_value), 1.0 / weight, note))
155
+ by_model.setdefault(model, []).append((kind, terms, math.log(value / ref_value), r))
156
+ for model, items in by_model.items():
157
+ w = w_b / len(items) # one budget per model and benchmark (B1)
158
+ share = f"weight {w_b:g} over {len(items)}" if len(items) > 1 else f"weight {w_b:g}"
159
+ for head, terms, mean, r in items:
160
+ note = (f"{bid}: {model} {float(r['value'])} against {ref} {ref_value} ({share}; "
161
+ f"{r.get('url', '')}, {r.get('date', '')})")
162
+ if head == "success":
163
+ p = min(max(float(r["value"]), 0.01), 0.99)
164
+ out.append(FactorSpec("success", terms, mean, 1.0 / (w * p * (1 - p)), note))
165
+ else:
166
+ out.append(FactorSpec(head, terms, mean, 1.0 / w, note))
128
167
  return out
129
168
 
130
169
 
@@ -67,7 +67,7 @@ MIN_SCORE_SWITCH = 5 # spec 04 section 2: g = p_reach once the score head has 5
67
67
  BASE_HEADS = {"cost": ("gaussian", "cost"), "tokens": ("gaussian", "cost"), "gate": ("logistic", "logit"),
68
68
  "success": ("logistic", "logit")}
69
69
  TASK_CHAIN = ("task", "subtype", "repo", "type")
70
- FIXED_LEVELS = ("fixed", "round", "control")
70
+ FIXED_LEVELS = ("fixed", "round", "control", "price")
71
71
  SCORE_CLAMP = (0.005, 0.995)
72
72
  _CACHE_LIMIT = 200_000
73
73
  MEAN_NODES = 12 # Gauss-Hermite nodes over an unseen normal effect
@@ -339,6 +339,8 @@ class FitState:
339
339
  self.sim_seed = _seed(self.seed_key, "simulate")
340
340
  # spec 04 section 1: the value of the timebox-capped terms (effort, shape) in a timeboxed task's cost rows
341
341
  self.timebox_effort = float(self.meta.get("timebox_effort", 1.0))
342
+ # spec 04 section 2: the fit's price offset per model (log scale); none in fits before 0.2.2
343
+ self.price_offsets: dict[str, float] = dict((self.meta.get("price_offsets") or {}).get("models") or {})
342
344
  if heads is None:
343
345
  heads = {}
344
346
  for name, hmeta in (self.meta.get("heads") or {}).items():
@@ -418,7 +420,8 @@ class FitState:
418
420
  for piece in st.pieces:
419
421
  rounds = st.k_max if st.piece_loop.get(piece) is not None else 1
420
422
  for k in range(1, rounds + 1):
421
- cost[(piece, k)] = cost_rest(st, piece, k, source=PREDICT_SOURCE, effort=effort)
423
+ cost[(piece, k)] = cost_rest(st, piece, k, source=PREDICT_SOURCE, effort=effort,
424
+ prices=self.price_offsets)
422
425
  gate = {}
423
426
  for gi, g in enumerate(st.gates):
424
427
  for k in range(1, (st.k_max if st.gate_loop.get(gi) is not None else 1) + 1):
@@ -789,10 +792,13 @@ class FitState:
789
792
  gate_pass=res[hg] if hg is not None else None, rounds=res[hr],
790
793
  cost_per_round=Money(usd=res[hpr[0]], tokens=res[hpr[1]]) if hpr else None)
791
794
  for piece, (hu, ht, hg, hr, hpr) in handles.items()}
795
+ # The typical run (0.2.3): the median of the same simulated runs, one per draw, the 80% range is read from
796
+ run_usd = dataclasses.replace(res[h_run[1]], median=float(np.median(rd.sim_usd)))
797
+ run_tokens = dataclasses.replace(res[h_run[2]], median=float(np.median(rd.sim_tokens)))
792
798
  pred = Prediction(
793
799
  config=cfg.id,
794
800
  p_success=res[h_run[0]],
795
- cost=Money(usd=res[h_run[1]], tokens=res[h_run[2]]),
801
+ cost=Money(usd=run_usd, tokens=run_tokens),
796
802
  ell=Money(usd=res[h_run[3]], tokens=res[h_run[4]]),
797
803
  rounds=res[h_run[5]],
798
804
  per_piece=per_piece,