loopmath 0.2.0__tar.gz → 0.2.2__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.
- {loopmath-0.2.0 → loopmath-0.2.2}/PKG-INFO +1 -1
- {loopmath-0.2.0 → loopmath-0.2.2}/pyproject.toml +1 -1
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/__init__.py +1 -1
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/design.py +45 -10
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/fit.py +59 -1
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/forest.py +1 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/lookahead.py +3 -3
- loopmath-0.2.2/src/loopmath/belief/pricing.py +142 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/state.py +28 -9
- loopmath-0.2.2/src/loopmath/builder/__init__.py +1 -0
- loopmath-0.2.2/src/loopmath/builder/cli.py +34 -0
- loopmath-0.2.2/src/loopmath/builder/context.py +353 -0
- loopmath-0.2.2/src/loopmath/builder/predict.py +306 -0
- loopmath-0.2.2/src/loopmath/builder/server.py +171 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/cli_registry.py +8 -3
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/benchmarks.toml +465 -3
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/build.py +25 -1
- loopmath-0.2.2/src/loopmath/priors/bundle/README.md +3 -0
- loopmath-0.2.2/src/loopmath/priors/bundle/lanes.jsonl.gz +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/bundle/manifest.json +65 -1
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/commands.py +3 -3
- loopmath-0.2.2/src/loopmath/priors/lanes.py +267 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/reduce.py +4 -2
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/registry.py +8 -5
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/recommend/commands.py +72 -22
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/recommend/curve.py +86 -5
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/recommend/engine.py +216 -38
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/recommend/message.py +101 -14
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/recommend/search.py +10 -6
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/skill/skills/loopmath/SKILL.md +1 -1
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/skill/skills/loopmath-import-runs/SKILL.md +5 -4
- loopmath-0.2.2/src/loopmath/skill/skills/loopmath-plan-task/SKILL.md +119 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/skill/skills/loopmath-update-fit/SKILL.md +6 -3
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/skill/skills/reference.md +20 -4
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/commands.py +38 -3
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/config.py +21 -4
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/base.css +7 -0
- loopmath-0.2.2/src/loopmath/views/assets/builder.css +303 -0
- loopmath-0.2.2/src/loopmath/views/assets/builder.html +70 -0
- loopmath-0.2.2/src/loopmath/views/assets/builder.js +1314 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/common.js +85 -1
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/estimates.js +11 -6
- loopmath-0.2.2/src/loopmath/views/assets/plans.css +39 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/plans.js +249 -52
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/results.js +44 -16
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/runs.js +1 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/viz.css +14 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/viz.js +46 -21
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/plans.py +3 -1
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/posterior.py +219 -20
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath.egg-info/PKG-INFO +1 -1
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath.egg-info/SOURCES.txt +12 -0
- loopmath-0.2.2/tests/test_cli_help_lists.py +40 -0
- loopmath-0.2.0/src/loopmath/priors/bundle/README.md +0 -1
- loopmath-0.2.0/src/loopmath/skill/skills/loopmath-plan-task/SKILL.md +0 -95
- loopmath-0.2.0/src/loopmath/views/assets/plans.css +0 -9
- {loopmath-0.2.0 → loopmath-0.2.2}/LICENSE +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/README.md +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/setup.cfg +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/spec/ocp-v0.2.schema.json +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/spec/ocp-v0.schema.json +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/spec/ocp_conformance.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/__main__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapter_host.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/atrium.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/base.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/bb.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/bb_support.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/omp.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/omp_facts.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/omp_links.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/omp_parse.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/omp_types.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/opencode.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/opencode_store.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/orca.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/otel_genai.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/otel_genai_graph.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/otel_genai_parse.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/otel_genai_types.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/otel_genai_usage.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/paseo.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/pi.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/pi_support.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/adapters/registry.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/block.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/check_pymc.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/commands.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/compose.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/features.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/gaussian.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/outcome.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/belief/priors.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/cli.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/cli_graph.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/cli_support.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/e0/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/e0/arms.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/e0/cli_e0.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/e0/estimate.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/e0/estimate_rework.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/e0/figures.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/e0/io.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/e0/verb.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/explain.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/fit.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/fit_assembly.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/fit_bayes.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/fit_masks.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/fit_predict.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/fit_pricing.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/fit_transfer.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/gaps.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/gitwalk.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/grade.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/grade_support.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/artifact_git.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/artifact_join.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/artifact_kinds.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/artifacts.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/bashparse.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/bashread_ops.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/bashwrite_ops.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/bashwrites.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/cache.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/codexio.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/codexio_parse.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/codexio_scan.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/dataset.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/dataset_build.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/dataset_core.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/dataset_edges.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/dataset_nodes.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/extract.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/html_common.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/html_costcurve.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/html_css.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/html_data.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/html_force.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/html_render.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/html_swimlanes.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_cli.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_common.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_parse.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_pricing.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_prompt.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_prompt_build.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_prompt_dispatch.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_prompt_evidence.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_prompt_node.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_prompt_records.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_prompt_result.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_prompt_session.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_prompt_tools.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_prompt_validation.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_prompt_workflow.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_report.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labeler_score.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/labels.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/launch.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/launch_origin.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/ocp.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/ocp_emit.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/ocp_support.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/render.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/runfile.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/runfile_export.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/runfile_finish.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/scan.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/schema.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/graph/token_completeness.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ingest/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ingest/base.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ingest/claude_code.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ingest/claude_code_support.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ingest/codex.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ingest/codex_support.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ingest/ocp.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ingest/ocp_analysis.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ingest/ocp_artifact.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ingest/ocp_common.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ingest/ocp_convert.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ingest/ocp_projection.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/logmatch/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/logmatch/artifacts.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/logmatch/clip.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/logmatch/costs.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/logmatch/match.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/logmatch/settle.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/logmatch/tariff.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ocp/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ocp/canonical.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ocp/commands.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ocp/conformance.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ocp/contractv3.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ocp/emit.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ocp/migrate.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ocp/schema/ocp-v0.2.schema.json +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ocp/schema/ocp-v0.3.schema.json +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ocp/schema/ocp-v0.schema.json +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/ocp/version.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/onboard/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/onboard/commands.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/onboard/history.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/onboard/label.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/onboard/usual.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/output.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/pool.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/price.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/price_validation.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/prices.toml +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/benchmarks.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/bundle/e0.jsonl.gz +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/bundle/rq1.jsonl.gz +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/bundle/sweep.jsonl.gz +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/e0.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/history.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/ocpdoc.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/rq1.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/show.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/sweep.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/priors/validate.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/receipts.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/recommend/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/recommend/backlog.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/recommend/gain.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/recommend/receipts.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/recommend/stats.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/recommend/storeread.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/report/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/report/html.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/report/terminal.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/report/terminal_exclusions.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/report/terminal_format.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/report/terminal_read.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/research_defaults.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/research_paths.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/scoring.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/scoring_comparisons.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/scoring_predictive.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/share/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/share/commands.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/share/export.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/share/import_.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/skill/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/skill/commands.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/skill/doctor.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/skill/install.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/skill/skills/loopmath-onboard/SKILL.md +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/skill/skills/loopmath-record-run/SKILL.md +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/budget.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/finish.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/fitjob.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/home.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/ids.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/lock.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/record.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/report.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/runs.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/store/status.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/surface.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/surface_bootstrap.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/surface_estimation.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/surface_frame.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/surface_presentation.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/surface_support.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/taskmodel.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/types.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/README.md +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/estimates.css +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/graph.css +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/graph.js +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/assets/results.css +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/common.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/views/runs.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/__init__.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/candidates.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/catalog/best_of_n.toml +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/catalog/implement_review.toml +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/catalog/plan_implement.toml +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/catalog/plan_implement_review.toml +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/catalog/solo.toml +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/catalog/swarm.toml +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/commands.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/diff.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/format.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/graphview.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/ids.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/infer.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/models.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/ocp.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath/workflows/shapes.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath.egg-info/dependency_links.txt +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath.egg-info/entry_points.txt +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath.egg-info/requires.txt +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/src/loopmath.egg-info/top_level.txt +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_adapter_atrium.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_adapter_bb.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_adapter_omp.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_adapter_opencode.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_adapter_opencode_output_tokens.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_adapter_orca.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_adapter_otel_genai.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_adapter_paseo.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_adapter_services.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_adapters.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_adapters_pi.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_arms.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_cli_graph.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_cli_labeler.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_cli_main_module.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_cli_plan_hidden.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_cli_prices.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_cli_startup.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_color_proof.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_duplicate_guards.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_estimate.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_explain.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_fit.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_gaps.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_gitwalk.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_grade.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_artifact_recording.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_artifacts.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_bashwrites.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_cache.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_codexio.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_dataset.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_extract.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_glue.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_html.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_labeler.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_labeler_evidence_matrix.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_launch.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_ocp.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_render.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_runfile.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_graph_schema.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_home_guard.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_html.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_ingest.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_ingest_claude_code.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_ingest_codex.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_ingest_ocp.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_io.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_not_run_summary.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_ocp_conformance.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_ocp_golden.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_ocp_open_enums.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_ocp_versioning.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_output_html_path.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_packaging_metadata.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_packaging_smoke.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_pool.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_preview_script.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_price.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_receipts.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_release_paths.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_runfile_privacy_n1b.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_scoring.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_split_rebindings.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_surface.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_taskmodel_features.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_terminal.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_types_control.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_types_piece_prediction.py +0 -0
- {loopmath-0.2.0 → loopmath-0.2.2}/tests/test_v01_scaffold.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "loopmath"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
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"
|
|
@@ -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
|
|
|
@@ -35,6 +36,11 @@ HEURISTIC_COST_WEIGHT = 0.7 # spec 04 section 2 and D27: allocated (heuristic)
|
|
|
35
36
|
USER_SOURCES = ("live", "backlog", "history", "user", "orchestrator", "onboard", "habit", "")
|
|
36
37
|
HORIZON_NODES = ("horizon:timebox", "horizon:log2h") # fixed terms of a timeboxed run (spec 04 section 1)
|
|
37
38
|
RQ1_EXT = "dev.loopmath.rq1" # loopmath-exp's ext key; its horizon_s is the fallback for older RQ1 runs
|
|
39
|
+
# 0.2.1 (F5, spec 04 section 1): in the cost and tokens rows of a timeboxed task the terms a timebox caps (effort
|
|
40
|
+
# and shape) enter at this value, not 1. Set from the RQ1 2 h runs: open-ended effort terms gave max over low x2.64
|
|
41
|
+
# (solo luna) and x2.23 (solo sol) against x1.34 and x1.27 observed; ln 1.34 / ln 2.64 = ln 1.27 / ln 2.23 = 0.30.
|
|
42
|
+
TIMEBOX_EFFORT_COST = 0.3
|
|
43
|
+
TIMEBOX_LEVELS = ("effort:", "family_effort:", "topology:", "position:")
|
|
38
44
|
|
|
39
45
|
|
|
40
46
|
def other_design(meta: dict) -> str | None:
|
|
@@ -304,22 +310,45 @@ def structure(config: Configuration) -> Structure:
|
|
|
304
310
|
|
|
305
311
|
# ---------------------------------------------------------------- rows
|
|
306
312
|
|
|
313
|
+
def effort_weight(task: Task, timebox_effort: float | None = None) -> float:
|
|
314
|
+
"""The value of the timebox-capped terms (TIMEBOX_LEVELS) in a cost or tokens row of `task`: `timebox_effort`
|
|
315
|
+
(default TIMEBOX_EFFORT_COST) when the task has a horizon, else 1 (spec 04 section 1, effort and shape under
|
|
316
|
+
a timebox)."""
|
|
317
|
+
if task_horizon(task) is None:
|
|
318
|
+
return 1.0
|
|
319
|
+
return float(TIMEBOX_EFFORT_COST if timebox_effort is None else timebox_effort)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@lru_cache(maxsize=65536)
|
|
323
|
+
def _effort_scaled(terms: tuple[Term, ...], weight: float) -> tuple[Term, ...]:
|
|
324
|
+
return tuple((n, p, v * weight) if n.startswith(TIMEBOX_LEVELS) else (n, p, v) for n, p, v in terms)
|
|
325
|
+
|
|
326
|
+
|
|
307
327
|
def cost_rest(st: Structure, piece: str, k: int, setting: Setting | None = None, *,
|
|
308
|
-
source: str | None = None
|
|
328
|
+
source: str | None = None, effort: float = 1.0,
|
|
329
|
+
prices: Mapping[str, float] | None = None) -> tuple[Term, ...]:
|
|
309
330
|
"""Cost and tokens rows without the task part: setting, role, topology, position, round, control,
|
|
310
331
|
and with a `source` the position x source node `psrc:<shape>#<pos>|<source>` and the family x source
|
|
311
|
-
node `fsrc:<family>|<source>` (spec 04 section 1).
|
|
332
|
+
node `fsrc:<family>|<source>` (spec 04 section 1). `effort` is the value of the terms a timebox caps, the
|
|
333
|
+
effort and shape (topology, position) terms (`effort_weight`: below 1 under a timebox); the role, psrc and
|
|
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."""
|
|
312
337
|
s = setting or st.settings[piece]
|
|
313
338
|
h, m, e = _s(s)
|
|
314
339
|
shape = st.shape or st.workflow_id
|
|
315
340
|
position = f"{shape}#{st.pieces.index(piece)}"
|
|
316
|
-
|
|
317
|
-
|
|
341
|
+
st_terms = setting_terms(h, m, e)
|
|
342
|
+
shape_terms = ((f"topology:{shape}", None, 1.0), (f"position:{position}", f"topology:{shape}", 1.0))
|
|
343
|
+
if effort != 1.0:
|
|
344
|
+
st_terms, shape_terms = _effort_scaled(st_terms, float(effort)), _effort_scaled(shape_terms, float(effort))
|
|
345
|
+
terms = st_terms + role_terms(st.roles[piece], m) + shape_terms
|
|
318
346
|
if source is not None:
|
|
319
347
|
fam = model_path(m)[1]
|
|
320
348
|
terms += ((f"psrc:{position}|{source}", f"position:{position}", 1.0),
|
|
321
349
|
(f"fsrc:{fam}|{source}", f"family:{fam}", 1.0))
|
|
322
|
-
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))
|
|
323
352
|
|
|
324
353
|
|
|
325
354
|
def gate_rest(st: Structure, gate: GateInfo, k: int) -> tuple[Term, ...]:
|
|
@@ -345,7 +374,8 @@ def run_rest(st: Structure) -> tuple[Term, ...]:
|
|
|
345
374
|
|
|
346
375
|
def cost_row(task: Task, source: str, st: Structure, piece: str, k: int,
|
|
347
376
|
setting: Setting | None = None, features: FeatureSet | None = None) -> list[Term]:
|
|
348
|
-
return task_terms(task, source, features) + list(cost_rest(st, piece, k, setting, source=source
|
|
377
|
+
return task_terms(task, source, features) + list(cost_rest(st, piece, k, setting, source=source,
|
|
378
|
+
effort=effort_weight(task)))
|
|
349
379
|
|
|
350
380
|
|
|
351
381
|
def gate_row(task: Task, source: str, st: Structure, gate: GateInfo, k: int,
|
|
@@ -396,6 +426,7 @@ class AttemptObs:
|
|
|
396
426
|
weight: float
|
|
397
427
|
setting: Setting
|
|
398
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)
|
|
399
430
|
|
|
400
431
|
|
|
401
432
|
@dataclass
|
|
@@ -433,8 +464,10 @@ def source_label(doc: dict) -> str:
|
|
|
433
464
|
ext = run.get("ext") or {}
|
|
434
465
|
share = ext.get("dev.loopmath.share")
|
|
435
466
|
if isinstance(share, dict):
|
|
436
|
-
|
|
437
|
-
|
|
467
|
+
# the importer writes `source` and `task.org` as `shared:<org_hash>` already: never prefix twice
|
|
468
|
+
org = (share.get("source") or share.get("org") or share.get("org_hash") or (run.get("task") or {}).get("org")
|
|
469
|
+
or "shared")
|
|
470
|
+
return "shared:" + str(org).removeprefix("shared:")
|
|
438
471
|
src = (run.get("task") or {}).get("source")
|
|
439
472
|
kind = src.get("kind") if isinstance(src, dict) else src
|
|
440
473
|
kind = str(kind or "").strip().lower()
|
|
@@ -736,7 +769,9 @@ def parse_run(doc: dict, *, now: datetime | None = None, rule: AcceptanceRule |
|
|
|
736
769
|
if weight <= 0 or ((usd is None or usd <= 0) and not tokens):
|
|
737
770
|
dropped["attempt without usable cost"] = dropped.get("attempt without usable cost", 0) + 1
|
|
738
771
|
continue
|
|
739
|
-
|
|
772
|
+
streams = tuple(_ocp_fields(cost)[name] for name in _OCP_TOKEN_FIELDS)
|
|
773
|
+
attempts.append(AttemptObs(piece, k, usd if usd and usd > 0 else None, tokens, weight, setting, repriced,
|
|
774
|
+
streams))
|
|
740
775
|
|
|
741
776
|
gates = _gate_observations(doc, st, by_piece_round)
|
|
742
777
|
use_rule = rule or rule_from_doc(doc)
|
|
@@ -29,12 +29,14 @@ import numpy as np
|
|
|
29
29
|
from scipy import sparse
|
|
30
30
|
|
|
31
31
|
from ..taskmodel import FeatureConfigError, FeatureSet
|
|
32
|
+
from . import design as design_rows
|
|
32
33
|
from . import priors as prior_data
|
|
33
34
|
from .design import (DESIGN_VERSION, Term, Unusable, cost_row, gate_row, parse_run, run_row, structure,
|
|
34
35
|
task_features, task_terms)
|
|
35
36
|
from .features import FeatureTally, add_horizon, drop_features, horizon_nodes, horizon_specs
|
|
36
37
|
from .block import leaf_split
|
|
37
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
|
|
38
40
|
from .gaussian import N_DRAWS, Factors, GaussianHead, HeadFit, LogisticHead, Prior
|
|
39
41
|
from .state import SCORE_CLAMP, inverse_transform, transform # noqa: F401 (re-exported)
|
|
40
42
|
|
|
@@ -162,6 +164,7 @@ class HeadRows:
|
|
|
162
164
|
sources: list[str] = field(default_factory=list)
|
|
163
165
|
types: list[str] = field(default_factory=list)
|
|
164
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)
|
|
165
168
|
|
|
166
169
|
def add(self, terms, y, w, run, source, ttype):
|
|
167
170
|
self.rows.append(terms)
|
|
@@ -212,6 +215,8 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
|
|
|
212
215
|
stored: set[str] = set() # run ids of the user's store, whose copy wins
|
|
213
216
|
overlap: Counter = Counter()
|
|
214
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))
|
|
215
220
|
for item in docs:
|
|
216
221
|
origin, doc = item if isinstance(item, tuple) else (None, item)
|
|
217
222
|
if origin is not None and _excluded(origin, without):
|
|
@@ -257,6 +262,10 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
|
|
|
257
262
|
if a.usd:
|
|
258
263
|
heads["cost"].add(terms, math.log(a.usd), a.weight, pr.run_id, pr.source, ttype)
|
|
259
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
|
|
260
269
|
if a.tokens:
|
|
261
270
|
heads["tokens"].add(terms, math.log(a.tokens), a.weight, pr.run_id, pr.source, ttype)
|
|
262
271
|
for gi, k, passed in pr.gates:
|
|
@@ -288,10 +297,34 @@ def collect_rows(docs: Iterable[dict | tuple[str | None, dict]], *, without: tup
|
|
|
288
297
|
coding = tally.horizon_coding() # the horizon terms need every run's horizon first (spec 04 section 1)
|
|
289
298
|
for hr in heads.values():
|
|
290
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)
|
|
291
301
|
return (heads, score_info, dict(dropped), dict(runs_by_source), {c: dict(n) for c, n in config_support.items()},
|
|
292
302
|
dict(checks), dict(overlap), dict(labels), tally)
|
|
293
303
|
|
|
294
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
|
+
|
|
295
328
|
# ---------------------------------------------------------------- matrices and priors
|
|
296
329
|
|
|
297
330
|
def build_matrix(rows: list[list[Term]], forest: Forest, extra_nodes: Iterable[tuple[str, str | None]] = ()):
|
|
@@ -362,9 +395,27 @@ def _support(hr: HeadRows, X: sparse.csr_matrix, node_ids: list[str]) -> dict[st
|
|
|
362
395
|
|
|
363
396
|
|
|
364
397
|
def seed_for(fit_id: str, head: str) -> int:
|
|
398
|
+
"""A head's seed from the fit's `seed_key` (its fit id before 0.2.1)."""
|
|
365
399
|
return int.from_bytes(hashlib.sha256(f"{fit_id}/{head}".encode()).digest()[:4], "little")
|
|
366
400
|
|
|
367
401
|
|
|
402
|
+
def input_key(heads_rows: dict[str, HeadRows], specs: list, settings: dict) -> str:
|
|
403
|
+
"""The fit's `seed_key` (spec 04 section 3): the sha256 of the design version, the settings that change the
|
|
404
|
+
posterior, the prior factors and every head's rows (terms, response, weight, run, source), each head's rows
|
|
405
|
+
sorted, so the same data and settings seed the same draws whatever the store's reading order."""
|
|
406
|
+
h = hashlib.sha256()
|
|
407
|
+
h.update(repr((DESIGN_VERSION, design_rows.TIMEBOX_EFFORT_COST, design_rows.TIMEBOX_LEVELS,
|
|
408
|
+
sorted(settings.items()))).encode())
|
|
409
|
+
for spec in specs:
|
|
410
|
+
h.update(repr((spec.head, spec.terms, spec.mean, spec.var)).encode())
|
|
411
|
+
for name in sorted(heads_rows):
|
|
412
|
+
hr = heads_rows[name]
|
|
413
|
+
h.update(f"\n{name}:{len(hr.rows)}".encode())
|
|
414
|
+
for line in sorted(repr((t, y, w, r, src)) for t, y, w, r, src in zip(hr.rows, hr.y, hr.w, hr.runs, hr.sources)):
|
|
415
|
+
h.update(line.encode())
|
|
416
|
+
return h.hexdigest()
|
|
417
|
+
|
|
418
|
+
|
|
368
419
|
def fit_head(hr: HeadRows, forest: Forest, factor_specs: list, *, eb: bool = True) -> dict:
|
|
369
420
|
"""Fit one head; returns arrays and metadata for the state files."""
|
|
370
421
|
extra = [(n, p) for spec in factor_specs for n, p, _ in spec.terms]
|
|
@@ -502,6 +553,8 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
|
|
|
502
553
|
specs = []
|
|
503
554
|
if not no_prior and "benchmark" not in without:
|
|
504
555
|
specs = prior_data.benchmark_factors(benchmarks, weight=weight)
|
|
556
|
+
seed_key = input_key(heads_rows, specs, {"no_prior": no_prior, "without": sorted(without), "eb": eb,
|
|
557
|
+
"benchmark_prior_weight": weight})
|
|
505
558
|
forest = Forest()
|
|
506
559
|
fitted = {}
|
|
507
560
|
for name, hr in heads_rows.items():
|
|
@@ -511,6 +564,8 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
|
|
|
511
564
|
nodes = horizon_nodes(hr.rows)
|
|
512
565
|
if nodes: # the horizon priors, --no-prior included (spec 04 section 1)
|
|
513
566
|
head_specs += horizon_specs(name, hr.kind, nodes)
|
|
567
|
+
if name == "cost": # the price offset (spec 04 section 2)
|
|
568
|
+
head_specs += price_spec(hr)
|
|
514
569
|
fitted[name] = fit_head(hr, forest, head_specs, eb=eb)
|
|
515
570
|
fit_id = new_fit_id(fits, now)
|
|
516
571
|
partial = fits / f"{fit_id}.partial"
|
|
@@ -546,6 +601,8 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
|
|
|
546
601
|
_atomic_json(partial / "design.json", design)
|
|
547
602
|
meta = {
|
|
548
603
|
"fit": fit_id, "created_at": now.isoformat(timespec="seconds"), "code_version": _code_version(),
|
|
604
|
+
"seed_key": seed_key, "timebox_effort": design_rows.TIMEBOX_EFFORT_COST,
|
|
605
|
+
"timebox_terms": list(design_rows.TIMEBOX_LEVELS),
|
|
549
606
|
"options": {"no_prior": no_prior, "without": list(without), "full": full, "eb": eb,
|
|
550
607
|
"benchmark_prior_weight": weight},
|
|
551
608
|
"runs_by_source": runs_by_source,
|
|
@@ -562,13 +619,14 @@ def fit(home: Path, *, no_prior: bool = False, without: tuple[str, ...] = (), fu
|
|
|
562
619
|
"center": h["center"], "scale": h["scale"], "baseline": h["baseline"],
|
|
563
620
|
"iterations": h["fit"].iterations, "optimizer": h["fit"].info,
|
|
564
621
|
"log_evidence": _finite(h["fit"].log_evidence), "factors": h["factors"],
|
|
565
|
-
"seed": seed_for(
|
|
622
|
+
"seed": seed_for(seed_key, name)}
|
|
566
623
|
for name, h in fitted.items()},
|
|
567
624
|
"scores": score_info,
|
|
568
625
|
"draws": N_DRAWS,
|
|
569
626
|
"design_version": DESIGN_VERSION,
|
|
570
627
|
"features": {**tally.report, **({"error": features_error} if features_error else {})},
|
|
571
628
|
"horizons": tally.horizon_report(),
|
|
629
|
+
"price_offsets": heads_rows["cost"].prices,
|
|
572
630
|
}
|
|
573
631
|
if full:
|
|
574
632
|
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
|
|
@@ -165,7 +165,7 @@ def _prepare(state: FitState, task: Task, configs: list[Configuration], rule: Ac
|
|
|
165
165
|
tt = state.task_terms(task)
|
|
166
166
|
cost_rows, coef, owner_r, owner_c, run_rows, plans = [], [], [], [], [], []
|
|
167
167
|
for ci, (cfg, (_, d)) in enumerate(zip(configs, out)):
|
|
168
|
-
plan = state._plan(cfg)
|
|
168
|
+
plan = state._plan(cfg, state.effort_for(task))
|
|
169
169
|
plans.append(plan)
|
|
170
170
|
st = plan["st"]
|
|
171
171
|
reach = d["reach"] # E[r(k)] with the unseen task effect on the gates integrated out
|
|
@@ -193,7 +193,7 @@ def _prepare(state: FitState, task: Task, configs: list[Configuration], rule: Ac
|
|
|
193
193
|
def _explore_cost_obs(state: FitState, task: Task, config: Configuration) -> tuple[list[tuple], int]:
|
|
194
194
|
"""Composite observation: the mean of the round-1 cost rows of the configuration's pieces,
|
|
195
195
|
and the number of pieces (the observation's noise variance is sigma^2 / n)."""
|
|
196
|
-
plan = state._plan(config)
|
|
196
|
+
plan = state._plan(config, state.effort_for(task))
|
|
197
197
|
st = plan["st"]
|
|
198
198
|
tt = state.task_terms(task)
|
|
199
199
|
acc: dict[str, list] = {}
|
|
@@ -385,7 +385,7 @@ def _condition_head(head: HeadState, terms: Sequence[tuple], tau2: float) -> Hea
|
|
|
385
385
|
draws = mean[:, None] + U @ Z
|
|
386
386
|
meta = dict(head.meta) or {"engine": head.engine, "kind": head.kind, "sigma": head.sigma}
|
|
387
387
|
out = HeadState(head.name, meta, head.nodes + new_nodes, None, head.fit_id, mean=mean, U=U, draws=draws,
|
|
388
|
-
unit=Z)
|
|
388
|
+
unit=Z, seed_key=head.seed_key)
|
|
389
389
|
out.fitted = head.fitted
|
|
390
390
|
out.phi = dict(head.phi)
|
|
391
391
|
return out
|
|
@@ -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 ()
|
|
@@ -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
|
|
@@ -152,7 +152,7 @@ class HeadState:
|
|
|
152
152
|
`loader(part)` returns a stored array or None when the fit did not store it."""
|
|
153
153
|
|
|
154
154
|
def __init__(self, name: str, meta: dict, nodes: Sequence[str], loader: Callable[[str], np.ndarray] | None,
|
|
155
|
-
fit_id: str, *, mean=None, U=None, draws=None, unit=None):
|
|
155
|
+
fit_id: str, *, mean=None, U=None, draws=None, unit=None, seed_key: str | None = None):
|
|
156
156
|
base = BASE_HEADS.get(name, ("gaussian", "score"))
|
|
157
157
|
self.name = name
|
|
158
158
|
self.engine = meta.get("engine") or base[0]
|
|
@@ -165,6 +165,7 @@ class HeadState:
|
|
|
165
165
|
self.seed = int(meta.get("seed", 0))
|
|
166
166
|
self.meta = meta
|
|
167
167
|
self.fit_id = fit_id
|
|
168
|
+
self.seed_key = seed_key or fit_id # spec 04 section 3: the fit's input hash; the fit id before 0.2.1
|
|
168
169
|
self.nodes = list(nodes)
|
|
169
170
|
self.index = {n: j for j, n in enumerate(self.nodes)}
|
|
170
171
|
self._loader = loader
|
|
@@ -252,7 +253,7 @@ class HeadState:
|
|
|
252
253
|
"""Standard normal draws of an unseen node, fixed by the fit, the head and the node id."""
|
|
253
254
|
z = self._virtual.get(node)
|
|
254
255
|
if z is None:
|
|
255
|
-
z = np.random.default_rng(_seed(self.
|
|
256
|
+
z = np.random.default_rng(_seed(self.seed_key, self.name, node)).standard_normal(N_DRAWS)
|
|
256
257
|
self._virtual[node] = z
|
|
257
258
|
return z
|
|
258
259
|
|
|
@@ -332,16 +333,23 @@ class FitState:
|
|
|
332
333
|
self._design = design
|
|
333
334
|
self._npz = None
|
|
334
335
|
self._cache: dict = {}
|
|
335
|
-
|
|
336
|
+
# spec 04 section 3: every draw is seeded from the fit's input hash, so identical fits give identical
|
|
337
|
+
# output; fits before 0.2.1 have no `seed_key` and keep their fit id
|
|
338
|
+
self.seed_key: str = self.meta.get("seed_key") or self.fit_id
|
|
339
|
+
self.sim_seed = _seed(self.seed_key, "simulate")
|
|
340
|
+
# spec 04 section 1: the value of the timebox-capped terms (effort, shape) in a timeboxed task's cost rows
|
|
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 {})
|
|
336
344
|
if heads is None:
|
|
337
345
|
heads = {}
|
|
338
346
|
for name, hmeta in (self.meta.get("heads") or {}).items():
|
|
339
347
|
key = name.replace(":", "@")
|
|
340
348
|
ids = [str(x) for x in self._array(f"{key}__ids")]
|
|
341
|
-
heads[name] = HeadState(name, hmeta, ids, self._loader(key), self.fit_id)
|
|
349
|
+
heads[name] = HeadState(name, hmeta, ids, self._loader(key), self.fit_id, seed_key=self.seed_key)
|
|
342
350
|
for name in BASE_HEADS:
|
|
343
351
|
if name not in heads:
|
|
344
|
-
heads[name] = HeadState(name, {}, [], None, self.fit_id)
|
|
352
|
+
heads[name] = HeadState(name, {}, [], None, self.fit_id, seed_key=self.seed_key)
|
|
345
353
|
self.heads = heads
|
|
346
354
|
# the fit's feature declarations (the built-ins for fits before 0.2), so predictions read
|
|
347
355
|
# features the way the fit did
|
|
@@ -397,13 +405,23 @@ class FitState:
|
|
|
397
405
|
def _row(self, head: HeadState, rest: tuple) -> Row:
|
|
398
406
|
return self._cached(head, [rest])[0]
|
|
399
407
|
|
|
400
|
-
def
|
|
408
|
+
def effort_for(self, task: Task) -> float:
|
|
409
|
+
"""The value of the timebox-capped terms (effort and shape, `design.TIMEBOX_LEVELS`) in the task's cost
|
|
410
|
+
rows: the fit's `timebox_effort` when the task's resolved horizon (given, else recorded) is a timebox the
|
|
411
|
+
fit uses (it has timeboxed runs), else 1 (spec 04 section 1)."""
|
|
412
|
+
if self.timebox_effort == 1.0:
|
|
413
|
+
return 1.0
|
|
414
|
+
return self.timebox_effort if self.resolve_task(task)[1]["horizon"]["used"] else 1.0
|
|
415
|
+
|
|
416
|
+
def _plan(self, config: Configuration, effort: float = 1.0) -> dict:
|
|
417
|
+
"""The configuration's rows without the task part; `effort` from `effort_for(task)`."""
|
|
401
418
|
st = structure(config)
|
|
402
419
|
cost = {}
|
|
403
420
|
for piece in st.pieces:
|
|
404
421
|
rounds = st.k_max if st.piece_loop.get(piece) is not None else 1
|
|
405
422
|
for k in range(1, rounds + 1):
|
|
406
|
-
cost[(piece, k)] = cost_rest(st, piece, k, source=PREDICT_SOURCE
|
|
423
|
+
cost[(piece, k)] = cost_rest(st, piece, k, source=PREDICT_SOURCE, effort=effort,
|
|
424
|
+
prices=self.price_offsets)
|
|
407
425
|
gate = {}
|
|
408
426
|
for gi, g in enumerate(st.gates):
|
|
409
427
|
for k in range(1, (st.k_max if st.gate_loop.get(gi) is not None else 1) + 1):
|
|
@@ -790,7 +808,8 @@ class FitState:
|
|
|
790
808
|
|
|
791
809
|
def _predict_many(self, task: Task, configs: Sequence[Configuration], rule: AcceptanceRule | None,
|
|
792
810
|
rescue_usd: float | None) -> list[tuple[Prediction, dict]]:
|
|
793
|
-
|
|
811
|
+
effort = self.effort_for(task)
|
|
812
|
+
plans = [self._plan(c, effort) for c in configs]
|
|
794
813
|
tparts = self._task_parts(task)
|
|
795
814
|
self._warm(plans)
|
|
796
815
|
return [self._predict(task, p, tparts, rule, rescue_usd) for p in plans]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""`loopmath builder`: the workflow builder (P8), a local page over the fitted model (spec 02, `builder`)."""
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""The `loopmath builder` parser, added by `cli_registry.register` (spec 02, `builder`).
|
|
2
|
+
|
|
3
|
+
It takes the task and rule flags of `recommend`, from the registry's own helpers, and imports nothing
|
|
4
|
+
heavy: the handler is `builder.server:serve`, loaded only when the command runs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def add(sub) -> None:
|
|
11
|
+
from ..cli_registry import FIT_HELP, _common, _lazy, _task_args
|
|
12
|
+
|
|
13
|
+
p = sub.add_parser("builder", help="build your own workflow on a local page and see its estimates change",
|
|
14
|
+
description="Serves the workflow builder on 127.0.0.1: pick a shape, change models, efforts, "
|
|
15
|
+
"widths and pieces, and see the estimated numbers for the task, the same ones "
|
|
16
|
+
"recommend gives. Ctrl+C stops it.")
|
|
17
|
+
_task_args(p)
|
|
18
|
+
rule = p.add_mutually_exclusive_group()
|
|
19
|
+
rule.add_argument("--rule", default=None, metavar="RULE", help="a named acceptance rule from config")
|
|
20
|
+
rule.add_argument("--target", default=None, metavar="NAME>=X", help="score-target rule, quoted: e.g. 'heldout_perf>=2400'")
|
|
21
|
+
p.add_argument("--goal", default=None, choices=["default", "p50", "p70", "p80", "p90", "p95", "p99"],
|
|
22
|
+
help="the pick to aim for, as for recommend (default: config goal)")
|
|
23
|
+
p.add_argument("--usual", default=None, metavar="CFG", help="a configuration id to treat as the usual one, as for recommend")
|
|
24
|
+
p.add_argument("--workflow", action="append", default=[], metavar="FILE.toml", help="also consider this workflow (repeatable)")
|
|
25
|
+
p.add_argument("--models", default=None, metavar="M,M,...", help="restrict candidate models")
|
|
26
|
+
p.add_argument("--fit", default=None, metavar="ID", help=FIT_HELP)
|
|
27
|
+
p.add_argument("--rec", default=None, metavar="REC", help="start from a saved recommendation: its task, rule and fit "
|
|
28
|
+
"(task flags given as well override its task)")
|
|
29
|
+
p.add_argument("--start", default=None, metavar="CFG", help="open this configuration first: a cfg_ id from the "
|
|
30
|
+
"recommendation, an earlier one or your runs")
|
|
31
|
+
p.add_argument("--port", type=int, default=0, metavar="N", help="port on 127.0.0.1 (default 0: a free port)")
|
|
32
|
+
p.add_argument("--no-open", action="store_true", help="print the URL without opening the browser")
|
|
33
|
+
_common(p, json_flag=False)
|
|
34
|
+
p.set_defaults(func=_lazy("loopmath.builder.server:serve"))
|