loopmath 0.2.7__tar.gz → 0.2.8__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 (386) hide show
  1. {loopmath-0.2.7/src/loopmath.egg-info → loopmath-0.2.8}/PKG-INFO +1 -1
  2. {loopmath-0.2.7 → loopmath-0.2.8}/pyproject.toml +1 -1
  3. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/__init__.py +1 -1
  4. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/compose.py +45 -11
  5. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/design.py +6 -2
  6. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/features.py +2 -0
  7. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/fit.py +36 -13
  8. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/lookahead.py +1 -1
  9. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/pricing.py +22 -3
  10. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/state.py +106 -58
  11. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ingest/base.py +1 -0
  12. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/prices.toml +12 -2
  13. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/benchmarks.py +4 -4
  14. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/recommend/commands.py +8 -6
  15. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/skill/skills/loopmath-plan-task/SKILL.md +2 -2
  16. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/estimates.js +1 -1
  17. {loopmath-0.2.7 → loopmath-0.2.8/src/loopmath.egg-info}/PKG-INFO +1 -1
  18. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath.egg-info/SOURCES.txt +2 -1
  19. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_price.py +5 -5
  20. loopmath-0.2.8/tests/test_v028_sol_6_1.py +80 -0
  21. {loopmath-0.2.7 → loopmath-0.2.8}/LICENSE +0 -0
  22. {loopmath-0.2.7 → loopmath-0.2.8}/README.md +0 -0
  23. {loopmath-0.2.7 → loopmath-0.2.8}/setup.cfg +0 -0
  24. {loopmath-0.2.7 → loopmath-0.2.8}/spec/ocp-v0.2.schema.json +0 -0
  25. {loopmath-0.2.7 → loopmath-0.2.8}/spec/ocp-v0.schema.json +0 -0
  26. {loopmath-0.2.7 → loopmath-0.2.8}/spec/ocp_conformance.py +0 -0
  27. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/__main__.py +0 -0
  28. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapter_host.py +0 -0
  29. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/__init__.py +0 -0
  30. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/atrium.py +0 -0
  31. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/base.py +0 -0
  32. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/bb.py +0 -0
  33. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/bb_support.py +0 -0
  34. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/omp.py +0 -0
  35. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/omp_facts.py +0 -0
  36. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/omp_links.py +0 -0
  37. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/omp_parse.py +0 -0
  38. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/omp_types.py +0 -0
  39. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/opencode.py +0 -0
  40. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/opencode_store.py +0 -0
  41. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/orca.py +0 -0
  42. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/otel_genai.py +0 -0
  43. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/otel_genai_graph.py +0 -0
  44. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/otel_genai_parse.py +0 -0
  45. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/otel_genai_types.py +0 -0
  46. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/otel_genai_usage.py +0 -0
  47. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/paseo.py +0 -0
  48. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/pi.py +0 -0
  49. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/pi_support.py +0 -0
  50. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/adapters/registry.py +0 -0
  51. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/__init__.py +0 -0
  52. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/block.py +0 -0
  53. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/check_pymc.py +0 -0
  54. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/commands.py +0 -0
  55. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/forest.py +0 -0
  56. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/gaussian.py +0 -0
  57. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/outcome.py +0 -0
  58. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/priors.py +0 -0
  59. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/belief/rules.py +0 -0
  60. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/builder/__init__.py +0 -0
  61. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/builder/cli.py +0 -0
  62. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/builder/context.py +0 -0
  63. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/builder/predict.py +0 -0
  64. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/builder/server.py +0 -0
  65. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/cli.py +0 -0
  66. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/cli_graph.py +0 -0
  67. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/cli_registry.py +0 -0
  68. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/cli_support.py +0 -0
  69. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/e0/__init__.py +0 -0
  70. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/e0/arms.py +0 -0
  71. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/e0/cli_e0.py +0 -0
  72. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/e0/estimate.py +0 -0
  73. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/e0/estimate_rework.py +0 -0
  74. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/e0/figures.py +0 -0
  75. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/e0/io.py +0 -0
  76. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/e0/verb.py +0 -0
  77. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/explain.py +0 -0
  78. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/fit.py +0 -0
  79. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/fit_assembly.py +0 -0
  80. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/fit_bayes.py +0 -0
  81. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/fit_masks.py +0 -0
  82. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/fit_predict.py +0 -0
  83. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/fit_pricing.py +0 -0
  84. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/fit_transfer.py +0 -0
  85. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/gaps.py +0 -0
  86. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/gitwalk.py +0 -0
  87. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/grade.py +0 -0
  88. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/grade_support.py +0 -0
  89. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/__init__.py +0 -0
  90. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/artifact_git.py +0 -0
  91. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/artifact_join.py +0 -0
  92. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/artifact_kinds.py +0 -0
  93. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/artifacts.py +0 -0
  94. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/bashparse.py +0 -0
  95. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/bashread_ops.py +0 -0
  96. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/bashwrite_ops.py +0 -0
  97. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/bashwrites.py +0 -0
  98. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/cache.py +0 -0
  99. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/codexio.py +0 -0
  100. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/codexio_parse.py +0 -0
  101. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/codexio_scan.py +0 -0
  102. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/dataset.py +0 -0
  103. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/dataset_build.py +0 -0
  104. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/dataset_core.py +0 -0
  105. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/dataset_edges.py +0 -0
  106. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/dataset_nodes.py +0 -0
  107. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/extract.py +0 -0
  108. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/html_common.py +0 -0
  109. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/html_costcurve.py +0 -0
  110. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/html_css.py +0 -0
  111. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/html_data.py +0 -0
  112. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/html_force.py +0 -0
  113. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/html_render.py +0 -0
  114. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/html_swimlanes.py +0 -0
  115. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler.py +0 -0
  116. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_cli.py +0 -0
  117. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_common.py +0 -0
  118. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_parse.py +0 -0
  119. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_pricing.py +0 -0
  120. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt.py +0 -0
  121. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_build.py +0 -0
  122. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_dispatch.py +0 -0
  123. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_evidence.py +0 -0
  124. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_node.py +0 -0
  125. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_records.py +0 -0
  126. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_result.py +0 -0
  127. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_session.py +0 -0
  128. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_tools.py +0 -0
  129. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_validation.py +0 -0
  130. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_prompt_workflow.py +0 -0
  131. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_report.py +0 -0
  132. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labeler_score.py +0 -0
  133. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/labels.py +0 -0
  134. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/launch.py +0 -0
  135. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/launch_origin.py +0 -0
  136. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/ocp.py +0 -0
  137. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/ocp_emit.py +0 -0
  138. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/ocp_support.py +0 -0
  139. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/render.py +0 -0
  140. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/runfile.py +0 -0
  141. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/runfile_export.py +0 -0
  142. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/runfile_finish.py +0 -0
  143. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/scan.py +0 -0
  144. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/schema.py +0 -0
  145. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/graph/token_completeness.py +0 -0
  146. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ingest/__init__.py +0 -0
  147. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ingest/claude_code.py +0 -0
  148. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ingest/claude_code_support.py +0 -0
  149. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ingest/codex.py +0 -0
  150. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ingest/codex_support.py +0 -0
  151. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ingest/ocp.py +0 -0
  152. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ingest/ocp_analysis.py +0 -0
  153. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ingest/ocp_artifact.py +0 -0
  154. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ingest/ocp_common.py +0 -0
  155. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ingest/ocp_convert.py +0 -0
  156. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ingest/ocp_projection.py +0 -0
  157. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/logmatch/__init__.py +0 -0
  158. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/logmatch/artifacts.py +0 -0
  159. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/logmatch/clip.py +0 -0
  160. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/logmatch/codex_window.py +0 -0
  161. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/logmatch/costs.py +0 -0
  162. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/logmatch/match.py +0 -0
  163. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/logmatch/settle.py +0 -0
  164. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/logmatch/tariff.py +0 -0
  165. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/__init__.py +0 -0
  166. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/canonical.py +0 -0
  167. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/commands.py +0 -0
  168. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/conformance.py +0 -0
  169. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/contractv3.py +0 -0
  170. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/emit.py +0 -0
  171. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/graph_contract.py +0 -0
  172. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/membership.py +0 -0
  173. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/migrate.py +0 -0
  174. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/schema/ocp-v0.2.schema.json +0 -0
  175. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/schema/ocp-v0.3.schema.json +0 -0
  176. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/schema/ocp-v0.schema.json +0 -0
  177. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/ocp/version.py +0 -0
  178. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/onboard/__init__.py +0 -0
  179. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/onboard/commands.py +0 -0
  180. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/onboard/history.py +0 -0
  181. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/onboard/label.py +0 -0
  182. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/onboard/usual.py +0 -0
  183. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/output.py +0 -0
  184. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/pool.py +0 -0
  185. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/price.py +0 -0
  186. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/price_validation.py +0 -0
  187. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/__init__.py +0 -0
  188. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/benchmarks.toml +0 -0
  189. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/build.py +0 -0
  190. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/bundle/README.md +0 -0
  191. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/bundle/e0.jsonl.gz +0 -0
  192. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/bundle/lanes.jsonl.gz +0 -0
  193. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/bundle/manifest.json +0 -0
  194. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/bundle/rq1.jsonl.gz +0 -0
  195. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/bundle/sweep.jsonl.gz +0 -0
  196. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/commands.py +0 -0
  197. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/e0.py +0 -0
  198. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/history.py +0 -0
  199. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/lanes.py +0 -0
  200. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/ocpdoc.py +0 -0
  201. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/reduce.py +0 -0
  202. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/registry.py +0 -0
  203. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/rq1.py +0 -0
  204. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/show.py +0 -0
  205. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/sweep.py +0 -0
  206. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/priors/validate.py +0 -0
  207. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/receipts.py +0 -0
  208. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/recommend/__init__.py +0 -0
  209. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/recommend/backlog.py +0 -0
  210. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/recommend/curve.py +0 -0
  211. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/recommend/engine.py +0 -0
  212. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/recommend/gain.py +0 -0
  213. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/recommend/message.py +0 -0
  214. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/recommend/receipts.py +0 -0
  215. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/recommend/search.py +0 -0
  216. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/recommend/stats.py +0 -0
  217. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/recommend/storeread.py +0 -0
  218. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/report/__init__.py +0 -0
  219. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/report/eligibility.py +0 -0
  220. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/report/html.py +0 -0
  221. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/report/terminal.py +0 -0
  222. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/report/terminal_exclusions.py +0 -0
  223. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/report/terminal_format.py +0 -0
  224. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/report/terminal_read.py +0 -0
  225. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/research_defaults.py +0 -0
  226. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/research_paths.py +0 -0
  227. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/scoring.py +0 -0
  228. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/scoring_comparisons.py +0 -0
  229. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/scoring_predictive.py +0 -0
  230. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/share/__init__.py +0 -0
  231. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/share/commands.py +0 -0
  232. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/share/export.py +0 -0
  233. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/share/import_.py +0 -0
  234. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/skill/__init__.py +0 -0
  235. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/skill/commands.py +0 -0
  236. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/skill/doctor.py +0 -0
  237. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/skill/install.py +0 -0
  238. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/skill/skills/loopmath/SKILL.md +0 -0
  239. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/skill/skills/loopmath-import-runs/SKILL.md +0 -0
  240. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/skill/skills/loopmath-onboard/SKILL.md +0 -0
  241. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/skill/skills/loopmath-record-run/SKILL.md +0 -0
  242. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/skill/skills/loopmath-update-fit/SKILL.md +0 -0
  243. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/skill/skills/reference.md +0 -0
  244. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/__init__.py +0 -0
  245. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/budget.py +0 -0
  246. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/commands.py +0 -0
  247. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/config.py +0 -0
  248. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/finish.py +0 -0
  249. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/fitjob.py +0 -0
  250. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/home.py +0 -0
  251. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/ids.py +0 -0
  252. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/lock.py +0 -0
  253. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/record.py +0 -0
  254. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/report.py +0 -0
  255. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/rule_change.py +0 -0
  256. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/run_names.py +0 -0
  257. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/runs.py +0 -0
  258. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/status.py +0 -0
  259. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/store/verify.py +0 -0
  260. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/surface.py +0 -0
  261. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/surface_bootstrap.py +0 -0
  262. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/surface_estimation.py +0 -0
  263. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/surface_frame.py +0 -0
  264. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/surface_presentation.py +0 -0
  265. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/surface_support.py +0 -0
  266. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/taskmodel.py +0 -0
  267. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/types.py +0 -0
  268. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/__init__.py +0 -0
  269. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/README.md +0 -0
  270. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/base.css +0 -0
  271. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/builder.css +0 -0
  272. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/builder.html +0 -0
  273. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/builder.js +0 -0
  274. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/common.js +0 -0
  275. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/estimates.css +0 -0
  276. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/graph.css +0 -0
  277. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/graph.js +0 -0
  278. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/plans.css +0 -0
  279. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/plans.js +0 -0
  280. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/results.css +0 -0
  281. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/results.js +0 -0
  282. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/runs.js +0 -0
  283. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/viz.css +0 -0
  284. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/assets/viz.js +0 -0
  285. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/common.py +0 -0
  286. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/plans.py +0 -0
  287. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/posterior.py +0 -0
  288. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/views/runs.py +0 -0
  289. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/__init__.py +0 -0
  290. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/candidates.py +0 -0
  291. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/catalog/best_of_n.toml +0 -0
  292. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/catalog/implement_review.toml +0 -0
  293. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/catalog/plan_implement.toml +0 -0
  294. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/catalog/plan_implement_review.toml +0 -0
  295. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/catalog/solo.toml +0 -0
  296. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/catalog/swarm.toml +0 -0
  297. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/commands.py +0 -0
  298. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/diff.py +0 -0
  299. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/format.py +0 -0
  300. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/graphview.py +0 -0
  301. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/ids.py +0 -0
  302. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/infer.py +0 -0
  303. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/models.py +0 -0
  304. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/ocp.py +0 -0
  305. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath/workflows/shapes.py +0 -0
  306. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath.egg-info/dependency_links.txt +0 -0
  307. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath.egg-info/entry_points.txt +0 -0
  308. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath.egg-info/requires.txt +0 -0
  309. {loopmath-0.2.7 → loopmath-0.2.8}/src/loopmath.egg-info/top_level.txt +0 -0
  310. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_adapter_atrium.py +0 -0
  311. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_adapter_bb.py +0 -0
  312. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_adapter_omp.py +0 -0
  313. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_adapter_opencode.py +0 -0
  314. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_adapter_opencode_output_tokens.py +0 -0
  315. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_adapter_orca.py +0 -0
  316. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_adapter_otel_genai.py +0 -0
  317. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_adapter_paseo.py +0 -0
  318. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_adapter_services.py +0 -0
  319. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_adapters.py +0 -0
  320. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_adapters_pi.py +0 -0
  321. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_analyze_cli.py +0 -0
  322. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_arms.py +0 -0
  323. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_cli_graph.py +0 -0
  324. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_cli_help_lists.py +0 -0
  325. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_cli_labeler.py +0 -0
  326. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_cli_main_module.py +0 -0
  327. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_cli_plan_hidden.py +0 -0
  328. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_cli_prices.py +0 -0
  329. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_cli_startup.py +0 -0
  330. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_color_proof.py +0 -0
  331. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_duplicate_guards.py +0 -0
  332. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_estimate.py +0 -0
  333. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_explain.py +0 -0
  334. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_fit.py +0 -0
  335. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_gaps.py +0 -0
  336. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_gitwalk.py +0 -0
  337. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_grade.py +0 -0
  338. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_artifact_recording.py +0 -0
  339. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_artifacts.py +0 -0
  340. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_bashwrites.py +0 -0
  341. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_cache.py +0 -0
  342. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_codexio.py +0 -0
  343. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_dataset.py +0 -0
  344. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_extract.py +0 -0
  345. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_glue.py +0 -0
  346. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_html.py +0 -0
  347. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_labeler.py +0 -0
  348. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_labeler_evidence_matrix.py +0 -0
  349. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_launch.py +0 -0
  350. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_ocp.py +0 -0
  351. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_render.py +0 -0
  352. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_runfile.py +0 -0
  353. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_graph_schema.py +0 -0
  354. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_home_guard.py +0 -0
  355. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_html.py +0 -0
  356. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_ingest.py +0 -0
  357. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_ingest_claude_code.py +0 -0
  358. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_ingest_codex.py +0 -0
  359. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_ingest_ocp.py +0 -0
  360. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_io.py +0 -0
  361. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_not_run_summary.py +0 -0
  362. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_ocp_conformance.py +0 -0
  363. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_ocp_golden.py +0 -0
  364. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_ocp_open_enums.py +0 -0
  365. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_ocp_versioning.py +0 -0
  366. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_output_html_path.py +0 -0
  367. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_packaging_metadata.py +0 -0
  368. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_packaging_smoke.py +0 -0
  369. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_pool.py +0 -0
  370. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_preview_script.py +0 -0
  371. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_receipts.py +0 -0
  372. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_release_paths.py +0 -0
  373. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_report_eligibility.py +0 -0
  374. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_research_cli.py +0 -0
  375. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_runfile_privacy_n1b.py +0 -0
  376. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_scoring.py +0 -0
  377. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_split_rebindings.py +0 -0
  378. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_starting_prior.py +0 -0
  379. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_surface.py +0 -0
  380. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_taskmodel_features.py +0 -0
  381. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_terminal.py +0 -0
  382. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_types_control.py +0 -0
  383. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_types_piece_prediction.py +0 -0
  384. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_v01_scaffold.py +0 -0
  385. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_v027_p3.py +0 -0
  386. {loopmath-0.2.7 → loopmath-0.2.8}/tests/test_v027_small_fixes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loopmath
3
- Version: 0.2.7
3
+ Version: 0.2.8
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "loopmath"
7
- version = "0.2.7"
7
+ version = "0.2.8"
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.7"
8
+ __version__ = "0.2.8"
@@ -18,6 +18,13 @@ simulated per draw (run noise and gate outcomes), and the interval is its 10th a
18
18
  percentiles. Rounds, success and `ell` are parameter-draw quantities. Without a generator,
19
19
  `compose` computes the expectations only (the mean path in state.py).
20
20
 
21
+ Tokens (0.2.8): one execution's dollars are `U = exp(eta_u + sigma_u z1)` and its price per token
22
+ `R = exp(eta_r + sigma_r z2)`, from the rate head; its tokens are `T = U / max(R, f)`, with `f` the model's
23
+ cache-read rate (its floor, none for a model without one). So `T f <= U` in every draw, and every shown pair of
24
+ dollars and tokens clears the floor: per draw, in the mean, at every percentile and summed over pieces. The two
25
+ normals are drawn in 0.2.7's order (dollars, then the second one), so the simulated dollars and the gates after
26
+ them are 0.2.7's. The expected tokens are `E[U] E[1 / max(R, f)]` (`inv_rate_mean`).
27
+
21
28
  Per piece, `PieceDraws.exp_*` is the piece's expected full-run contribution, summed over
22
29
  the rounds it runs, so pieces add up to the run. `cost_per_round()` gives the expected cost of
23
30
  one execution: the per-execution prediction when the piece's cost does not depend on the round
@@ -28,8 +35,11 @@ from __future__ import annotations
28
35
 
29
36
  from dataclasses import dataclass, field
30
37
 
38
+ import math
39
+ from collections.abc import Mapping
40
+
31
41
  import numpy as np
32
- from scipy.special import expit
42
+ from scipy.special import expit, log_ndtr
33
43
 
34
44
  from .design import Structure
35
45
 
@@ -64,12 +74,33 @@ class RunDraws:
64
74
  reach: dict[tuple[str, int], np.ndarray] = field(default_factory=dict) # (piece, k) -> P(piece runs in round k)
65
75
 
66
76
 
67
- def compose(st: Structure, cost_eta: dict, tokens_eta: dict, gate_eta: dict, *, sigma_usd: float,
68
- sigma_tokens: float, rng: np.random.Generator | None) -> RunDraws:
69
- """Compose one configuration. `*_eta[(piece, k)]` and `gate_eta[(gate index, k)]` are draw arrays.
70
- With `rng` None nothing is simulated: the `sim_*` arrays stay zero."""
77
+ def inv_rate_mean(m, v, floor: float | None) -> np.ndarray:
78
+ """`E[1 / max(R, f)]` for `log R ~ N(m, v)` and the floor `f` ($ per token; None or 0: no floor), per entry
79
+ of `m`. With `l = log f`: `exp(-m + v/2) Phi((m - l)/sqrt(v) - sqrt(v)) + exp(-l) Phi((l - m)/sqrt(v))`,
80
+ each term in logs (`log Phi`), so a far tail never multiplies an overflow by 0. With `v = 0` the rate is
81
+ certain: `1 / max(exp(m), f)`."""
82
+ m = np.asarray(m, float)
83
+ v = np.broadcast_to(np.asarray(v, float), m.shape)
84
+ if not floor or floor <= 0:
85
+ return np.exp(-m + 0.5 * v)
86
+ lf = math.log(floor)
87
+ certain = v <= 0
88
+ sd = np.sqrt(np.where(certain, 1.0, v))
89
+ spread = np.exp(-m + 0.5 * v + log_ndtr((m - lf) / sd - sd)) + np.exp(log_ndtr((lf - m) / sd) - lf)
90
+ return np.where(certain, np.exp(-np.maximum(m, lf)), spread)
91
+
92
+
93
+ def compose(st: Structure, cost_eta: dict, rate_eta: dict, gate_eta: dict, *, sigma_usd: float,
94
+ sigma_rate: float, floors: Mapping[str, float] | None = None, rate_var: Mapping | None = None,
95
+ rng: np.random.Generator | None) -> RunDraws:
96
+ """Compose one configuration. `cost_eta[(piece, k)]`, `rate_eta[(piece, k)]` and `gate_eta[(gate index, k)]`
97
+ are draw arrays; `floors[piece]` is the piece model's floor in $ per token (absent or 0: none). The expected
98
+ tokens take `log R ~ N(rate_eta, rate_var[(piece, k)])`, `sigma_rate ** 2` when `rate_var` is None (the mean
99
+ path passes the rate rows' known parts and adds their unseen variance there). With `rng` None nothing is
100
+ simulated: the `sim_*` arrays stay zero."""
71
101
  n = len(next(iter(cost_eta.values())))
72
- half_u, half_t = 0.5 * sigma_usd ** 2, 0.5 * sigma_tokens ** 2
102
+ half_u, var_r = 0.5 * sigma_usd ** 2, sigma_rate ** 2
103
+ floors = floors or {}
73
104
  exp_u, exp_t = np.zeros(n), np.zeros(n)
74
105
  sim_u, sim_t = np.zeros(n), np.zeros(n)
75
106
  rounds = np.ones(n)
@@ -79,7 +110,7 @@ def compose(st: Structure, cost_eta: dict, tokens_eta: dict, gate_eta: dict, *,
79
110
  pieces[p] = PieceDraws(np.zeros(n), np.zeros(n), np.zeros(n), np.zeros(n), np.ones(n), sim_runs=np.zeros(n))
80
111
  if st.piece_loop.get(p) is not None:
81
112
  pieces[p].round_free = all(
82
- np.array_equal(cost_eta[(p, k)], cost_eta[(p, 1)]) and np.array_equal(tokens_eta[(p, k)], tokens_eta[(p, 1)])
113
+ np.array_equal(cost_eta[(p, k)], cost_eta[(p, 1)]) and np.array_equal(rate_eta[(p, k)], rate_eta[(p, 1)])
83
114
  for k in range(2, st.k_max + 1))
84
115
  gate_after = {g.after: gi for gi, g in enumerate(st.gates)}
85
116
  for piece, gi in gate_after.items():
@@ -88,17 +119,20 @@ def compose(st: Structure, cost_eta: dict, tokens_eta: dict, gate_eta: dict, *,
88
119
 
89
120
  def run_piece(piece: str, k: int, weight: np.ndarray, alive: np.ndarray):
90
121
  w = st.widths[piece]
91
- eu, et = cost_eta[(piece, k)], tokens_eta[(piece, k)]
122
+ eu, er = cost_eta[(piece, k)], rate_eta[(piece, k)]
123
+ f = float(floors.get(piece) or 0.0)
92
124
  cu = w * np.exp(eu + half_u)
93
- ct = w * np.exp(et + half_t)
125
+ ct = cu * inv_rate_mean(er, var_r if rate_var is None else rate_var[(piece, k)], f)
94
126
  pd = pieces[piece]
95
127
  pd.exp_usd += weight * cu
96
128
  pd.exp_tokens += weight * ct
97
129
  su = np.zeros(n)
98
130
  stt = np.zeros(n)
99
131
  for _ in range(w if rng is not None else 0):
100
- su += np.exp(eu + sigma_usd * rng.standard_normal(n))
101
- stt += np.exp(et + sigma_tokens * rng.standard_normal(n))
132
+ u = np.exp(eu + sigma_usd * rng.standard_normal(n))
133
+ r = np.exp(er + sigma_rate * rng.standard_normal(n))
134
+ su += u
135
+ stt += u / (np.maximum(r, f) if f > 0 else r)
102
136
  pd.sim_usd += alive * su
103
137
  pd.sim_tokens += alive * stt
104
138
  pd.sim_runs += alive
@@ -30,8 +30,12 @@ from .pricing import price_term
30
30
  Term = tuple[str, "str | None", float]
31
31
 
32
32
  # Node names of this design (spec 04 section 4). 2 (loopmath 0.2): shape keys, psrc nodes and role groups.
33
- # 3 (0.2): `horizon:log2h` is relative to the fit's reference horizon. A fit of another version is not read.
34
- DESIGN_VERSION = 3
33
+ # 3 (0.2): `horizon:log2h` is relative to the fit's reference horizon. 4 (0.2.8): a `rate` head on the price
34
+ # per token replaces the tokens head. A fit of another version is not read.
35
+ DESIGN_VERSION = 4
36
+ # The version the seed key hashes (spec 04 section 3). It stays 3 from 0.2.8: the same data seeds the same
37
+ # draws as in 0.2.7, so every number that is not a token count is 0.2.7's.
38
+ SEED_VERSION = 3
35
39
  HEURISTIC_COST_WEIGHT = 0.7 # spec 04 section 2 and D27: allocated (heuristic) cost rows
36
40
  USER_SOURCES = ("live", "backlog", "history", "user", "orchestrator", "onboard", "habit", "")
37
41
  HORIZON_NODES = ("horizon:timebox", "horizon:log2h") # fixed terms of a timeboxed run (spec 04 section 1)
@@ -35,6 +35,8 @@ LN2 = math.log(2.0)
35
35
  # cost and tokens scale with an elasticity of 1 +- 0.5; logit and score heads start flat.
36
36
  HORIZON_PRIOR = {
37
37
  "cost": {"horizon:log2h": (LN2, 0.35), "horizon:timebox": (0.0, 1.5)},
38
+ # the price per token (0.2.8): a longer horizon buys more tokens, not dearer ones
39
+ "rate": {"horizon:log2h": (0.0, 0.35), "horizon:timebox": (0.0, 1.5)},
38
40
  "logit": {"horizon:log2h": (0.0, 0.25), "horizon:timebox": (0.0, 1.0)},
39
41
  "score": {"horizon:log2h": (0.0, 0.25), "horizon:timebox": (0.0, 1.0)},
40
42
  }
@@ -31,12 +31,12 @@ from scipy import sparse
31
31
  from ..taskmodel import FeatureConfigError, FeatureSet
32
32
  from . import design as design_rows
33
33
  from . import priors as prior_data
34
- from .design import (DESIGN_VERSION, Term, Unusable, cost_row, gate_row, parse_run, run_row, structure,
35
- task_features, task_terms)
34
+ from .design import (DESIGN_VERSION, SEED_VERSION, Term, Unusable, cost_row, gate_row, parse_run, run_row,
35
+ structure, task_features, task_terms)
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
+ from .pricing import PRICE_NODE, PRICE_PRIOR_SD, STREAMS, fit_offsets, price_term, rate_floors
40
40
  from .gaussian import N_DRAWS, Factors, GaussianHead, HeadFit, LogisticHead, Prior
41
41
  from .state import SCORE_CLAMP, inverse_transform, transform # noqa: F401 (re-exported)
42
42
 
@@ -44,8 +44,12 @@ FIT_LOCK = ".fit.lock"
44
44
  KEEP_FITS = 5 # The newest fits kept, besides the ones a receipt or stored recommendation names
45
45
  TASK_LEVELS = ("org", "type", "repo", "subtype", "task")
46
46
  MIN_SCORE_RUNS = 3 # spec 04 section 2: a score head needs at least 3 measured runs
47
- HEAD_KIND = {"cost": ("gaussian", "cost"), "tokens": ("gaussian", "cost"),
47
+ # `tokens` (log tokens) is 0.2.7's head: from 0.2.8 its rows are built for the seed key and the node index
48
+ # only, never fitted. `rate` (log usd - log tokens, the price per token) is fitted in its place (spec 04 section 2).
49
+ HEAD_KIND = {"cost": ("gaussian", "cost"), "tokens": ("gaussian", "cost"), "rate": ("gaussian", "cost"),
48
50
  "gate": ("logistic", "logit"), "success": ("logistic", "logit")}
51
+ KEY_ONLY = ("tokens",) # heads whose rows the seed key and the node index read, and no fit
52
+ NOT_KEYED = ("rate",) # heads the seed key leaves out, so 0.2.8 seeds every draw as 0.2.7 did
49
53
 
50
54
 
51
55
  class FitBusy(RuntimeError):
@@ -273,6 +277,9 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
273
277
  acc[i] += v
274
278
  if a.tokens:
275
279
  heads["tokens"].add(terms, math.log(a.tokens), a.weight, pr.run_id, pr.source, ttype)
280
+ if a.usd and a.tokens:
281
+ heads["rate"].add(terms, math.log(a.usd) - math.log(a.tokens), a.weight, pr.run_id, pr.source,
282
+ ttype)
276
283
  for gi, k, passed in pr.gates:
277
284
  heads["gate"].add(gate_row(pr.task, pr.source, st, st.gates[gi], k, features), 1.0 if passed else 0.0,
278
285
  1.0, pr.run_id, pr.source, ttype)
@@ -305,6 +312,8 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
305
312
  for hr in heads.values():
306
313
  hr.rows = add_horizon(drop_features(hr.rows, tally.nodes), hr.runs, tally.run_horizon, coding)
307
314
  add_prices(heads["cost"], {m: len(r) for m, r in model_runs.items()}, model_streams)
315
+ heads["rate"].prices = heads["cost"].prices # the price per token carries the same offset (spec 04 section 2)
316
+ _price_rows(heads["rate"])
308
317
  return (heads, score_info, dict(dropped), dict(runs_by_source), {c: dict(n) for c, n in config_support.items()},
309
318
  dict(checks), dict(overlap), dict(labels), tally)
310
319
 
@@ -313,7 +322,12 @@ def add_prices(hr: HeadRows, runs: dict[str, int], streams: dict[str, list[float
313
322
  """The price offset (spec 04 section 2): the fit's offsets from its runs and token mix under the current price
314
323
  table, and each cost row with its model's `price:offset` term (`pricing.price_term`, as at prediction)."""
315
324
  hr.prices = fit_offsets(runs, streams, design_rows._price_table())
316
- offsets = hr.prices["models"]
325
+ _price_rows(hr)
326
+
327
+
328
+ def _price_rows(hr: HeadRows) -> None:
329
+ """Each row with its model's `price:offset` term under `hr.prices`."""
330
+ offsets = hr.prices.get("models")
317
331
  if not offsets:
318
332
  return
319
333
  rows = []
@@ -325,10 +339,11 @@ def add_prices(hr: HeadRows, runs: dict[str, int], streams: dict[str, list[float
325
339
 
326
340
 
327
341
  def price_spec(hr: HeadRows) -> list:
328
- """The prior that holds `price:offset` at 1, on the cost head of a fit with offsets, --no-prior included."""
342
+ """The prior that holds `price:offset` at 1, on the cost and rate heads of a fit with offsets, --no-prior
343
+ included."""
329
344
  if not hr.prices.get("models"):
330
345
  return []
331
- return [prior_data.FactorSpec("cost", [(PRICE_NODE, None, 1.0)], 1.0, PRICE_PRIOR_SD ** 2,
346
+ return [prior_data.FactorSpec(hr.name, [(PRICE_NODE, None, 1.0)], 1.0, PRICE_PRIOR_SD ** 2,
332
347
  f"{PRICE_NODE} held at 1, prior N(1, {PRICE_PRIOR_SD}^2)")]
333
348
 
334
349
 
@@ -417,18 +432,21 @@ def seed_for(fit_id: str, head: str) -> int:
417
432
 
418
433
 
419
434
  def input_key(heads_rows: dict[str, HeadRows], specs: list, settings: dict) -> str:
420
- """The fit's `seed_key` (spec 04 section 3): the sha256 of the design version, the settings that change the
435
+ """The fit's `seed_key` (spec 04 section 3): the sha256 of the seed version, the settings that change the
421
436
  posterior, the prior factors and every head's rows (terms, response, weight, run, source), each head's rows
422
437
  sorted, so the same data and settings seed the same draws whatever the store's reading order.
423
438
 
424
439
  A success row's weight is its q. With `outcome.q` configured, the key takes each row's q under the built-in
425
- map (`HeadRows.seed_w`), so setting q redraws nothing: only the success head's posterior moves (0.2.7)."""
440
+ map (`HeadRows.seed_w`), so setting q redraws nothing: only the success head's posterior moves (0.2.7).
441
+
442
+ From 0.2.8 the key reads 0.2.7's heads: the tokens rows, which no head fits any more, and not the rate rows,
443
+ with the seed version 0.2.7 hashed as its design version. The same data seeds the same draws as in 0.2.7."""
426
444
  h = hashlib.sha256()
427
- h.update(repr((DESIGN_VERSION, design_rows.TIMEBOX_EFFORT_COST, design_rows.TIMEBOX_LEVELS,
445
+ h.update(repr((SEED_VERSION, design_rows.TIMEBOX_EFFORT_COST, design_rows.TIMEBOX_LEVELS,
428
446
  sorted(settings.items()))).encode())
429
447
  for spec in specs:
430
448
  h.update(repr((spec.head, spec.terms, spec.mean, spec.var)).encode())
431
- for name in sorted(heads_rows):
449
+ for name in sorted(n for n in heads_rows if n not in NOT_KEYED):
432
450
  hr = heads_rows[name]
433
451
  h.update(f"\n{name}:{len(hr.rows)}".encode())
434
452
  weights = hr.seed_w or hr.w
@@ -618,9 +636,12 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
618
636
  continue
619
637
  nodes = horizon_nodes(hr.rows)
620
638
  if nodes: # the horizon priors, --no-prior included (spec 04 section 1)
621
- head_specs += horizon_specs(name, hr.kind, nodes)
622
- if name == "cost": # the price offset (spec 04 section 2)
639
+ head_specs += horizon_specs(name, "rate" if name == "rate" else hr.kind, nodes)
640
+ if name in ("cost", "rate"): # the price offset (spec 04 section 2)
623
641
  head_specs += price_spec(hr)
642
+ if name in KEY_ONLY: # 0.2.7's tokens head: its nodes enter the index where they did, unfitted
643
+ build_matrix(hr.rows, forest, [(n, p) for spec in head_specs for n, p, _ in spec.terms])
644
+ continue
624
645
  fitted[name] = fit_head(hr, forest, head_specs, eb=eb)
625
646
  fit_id = new_fit_id(fits, now)
626
647
  partial = fits / f"{fit_id}.partial"
@@ -685,6 +706,8 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
685
706
  "features": {**tally.report, **({"error": features_error} if features_error else {})},
686
707
  "horizons": tally.horizon_report(),
687
708
  "price_offsets": heads_rows["cost"].prices,
709
+ # the cheapest rate of each priced model, $ per token: a draw's tokens never cost less (spec 04 section 3)
710
+ "rate_floors": rate_floors(design_rows._price_table()),
688
711
  }
689
712
  if full:
690
713
  meta["full"] = _full_check(fitted)
@@ -394,7 +394,7 @@ def _condition_head(head: HeadState, terms: Sequence[tuple], tau2: float) -> Hea
394
394
  def conditioned(state: FitState, task: Task, config: Configuration, rule: AcceptanceRule | None = None) -> FitState:
395
395
  heads = dict(state.heads)
396
396
  terms, n_obs = _explore_cost_obs(state, task, config)
397
- for name in ("cost", "tokens"):
397
+ for name in ("cost", "rate"):
398
398
  heads[name] = _condition_head(state.heads[name], terms, state.heads[name].sigma ** 2 / n_obs)
399
399
  run_terms = state.task_terms(task) + list(state._plan(config)["run"])
400
400
  succ = state.heads["success"]
@@ -1,7 +1,8 @@
1
- """The price offset on the cost head (spec 04 section 2, 0.2.2).
1
+ """The price offset on the cost and rate heads (spec 04 section 2, 0.2.2), and the rate floors (0.2.8).
2
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
3
+ A model's run cost moves with its list price. Every cost row of a model version `m`, and from 0.2.8 every rate
4
+ row (the price per token), carries the fixed term `price:offset` (not the gate, success or score rows), with
5
+ the value
5
6
 
6
7
  o_m = s_m * log(P_m / R_m), s_m = n0 / (n0 + n_m),
7
8
 
@@ -18,6 +19,10 @@ the same at fit and at prediction:
18
19
 
19
20
  `fit_offsets` computes the offset of every model with cost rows and of every model in the price table once per
20
21
  fit; `meta.json` `price_offsets` stores them, and prediction and the workflow search read them from there.
22
+
23
+ `rate_floors` gives each priced model's cache-read rate in $ per token, the cheapest rate any of its tokens
24
+ bills at (in every row of the packaged table, no nonzero rate is lower). A draw's price per token is held at or
25
+ above it, so its tokens never cost less than its dollars (spec 04 section 3). `meta.json` stores them too.
21
26
  """
22
27
 
23
28
  from __future__ import annotations
@@ -140,3 +145,17 @@ def price_term(offsets: Mapping[str, float] | None, model: str) -> tuple[tuple[s
140
145
  return ()
141
146
  o = offsets.get(model)
142
147
  return ((PRICE_NODE, None, float(o)),) if o else ()
148
+
149
+
150
+ def rate_floors(table) -> dict[str, float]:
151
+ """Model (canonical id) to its cache-read rate in $ per token, for every model with a positive one; empty
152
+ without a table. Two table keys of one model keep the lower rate."""
153
+ out: dict[str, float] = {}
154
+ if table is None:
155
+ return out
156
+ for key, rate in table.rates.items():
157
+ floor = float(rate.get("cache_read") or 0.0) / 1e6
158
+ if floor > 0:
159
+ model = canonical_model_id(key)
160
+ out[model] = min(floor, out.get(model, floor))
161
+ return dict(sorted(out.items()))