beefcot 1.0.3__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {beefcot-1.0.3 → beefcot-1.1.0}/.gitignore +3 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/PKG-INFO +13 -1
- {beefcot-1.0.3 → beefcot-1.1.0}/README.pypi.md +8 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/pyproject.toml +4 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/__init__.py +2 -2
- beefcot-1.1.0/src/beefcot/__main__.py +5 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/checks/figures.py +1 -1
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/cli.py +137 -3
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/cli_presentation.py +30 -1
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/cutting_plan/economics.py +227 -35
- beefcot-1.1.0/src/beefcot/cutting_plan/eligibility.py +158 -0
- beefcot-1.1.0/src/beefcot/cutting_plan/receipt.py +123 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/cutting_plan/run.py +164 -24
- beefcot-1.1.0/src/beefcot/cutting_plan/solver.py +179 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model/learners.py +10 -1
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model/transforms.py +14 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model_format/blocks.py +12 -2
- beefcot-1.1.0/src/beefcot/project.py +202 -0
- beefcot-1.1.0/src/beefcot/project_registry.py +150 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/registry.py +1 -1
- beefcot-1.1.0/src/beefcot/regression/assembly.py +397 -0
- beefcot-1.1.0/src/beefcot/regression/assembly_network.py +61 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/exhaustive.py +146 -37
- beefcot-1.1.0/src/beefcot/regression/exhaustive_parallel.py +110 -0
- beefcot-1.1.0/src/beefcot/regression/exhaustive_storage.py +182 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/run.py +6 -1
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/spec.py +35 -6
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/reports/run.py +46 -5
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/runtime/schemas/model-3.schema.json +3 -1
- beefcot-1.1.0/src/beefcot/service/__init__.py +5 -0
- beefcot-1.1.0/src/beefcot/service/app.py +780 -0
- beefcot-1.1.0/src/beefcot/service/auth.py +219 -0
- beefcot-1.1.0/src/beefcot/service/errors.py +19 -0
- beefcot-1.1.0/src/beefcot/service/history.py +250 -0
- beefcot-1.1.0/src/beefcot/service/job_process.py +90 -0
- beefcot-1.1.0/src/beefcot/service/model_evidence.py +26 -0
- beefcot-1.1.0/src/beefcot/service/plan_analysis.py +246 -0
- beefcot-1.1.0/src/beefcot/service/plan_comparison.py +133 -0
- beefcot-1.1.0/src/beefcot/service/plan_editor.py +237 -0
- beefcot-1.1.0/src/beefcot/service/plan_evidence.py +162 -0
- beefcot-1.1.0/src/beefcot/service/plan_export.py +122 -0
- beefcot-1.1.0/src/beefcot/service/plan_figures.py +41 -0
- beefcot-1.1.0/src/beefcot/service/plan_inputs.py +313 -0
- beefcot-1.1.0/src/beefcot/service/plans.py +179 -0
- beefcot-1.1.0/src/beefcot/service/projects.py +63 -0
- beefcot-1.1.0/src/beefcot/service/purchase_ceiling.py +73 -0
- beefcot-1.1.0/src/beefcot/service/runner.py +110 -0
- beefcot-1.1.0/src/beefcot/service/saved_plan.py +203 -0
- beefcot-1.1.0/src/beefcot/service/server.py +23 -0
- beefcot-1.1.0/src/beefcot/service/studies.py +206 -0
- beefcot-1.1.0/src/beefcot/service/views.py +509 -0
- beefcot-1.0.3/src/beefcot/cutting_plan/solver.py +0 -104
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/anomaly.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/chain.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/checks/__init__.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/checks/anomaly.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/checks/anomaly_figures.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/checks/bins.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/checks/cut_tree.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/checks/forest.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/checks/report.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/checks/review.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/checks/run.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/checks/spec.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/checks/table.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/cutting_plan/__init__.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/decomposition/__init__.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/decomposition/analysis.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/decomposition/fits.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/decomposition/report.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/decomposition/run.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/decomposition/sources.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/decomposition/spec.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/diagnostics/__init__.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/diagnostics/analysis.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/diagnostics/methods.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/diagnostics/plots.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/diagnostics/report.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/diagnostics/run.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/diagnostics/sources.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/diagnostics/spec.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model/__init__.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model/expression.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model/functional.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model/learner_registry.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model/rows.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model/runtime.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model/topology.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model/types.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model_format/__init__.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model_format/card.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model_format/documents.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model_format/export.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model_format/fitted.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model_format/library.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/model_format/schema.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/prediction/__init__.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/prediction/run.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/producers/__init__.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/producers/network.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/producers/rf/__init__.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/producers/rf/loop.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/producers/rf/relevance.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/__init__.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/anomaly.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/candidates.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/factory.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/mapping_review.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/mapping_space.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/provenance.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/rerf.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/rerf_checkpoint.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/rerf_controller.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/rerf_effort.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/rerf_equivalence.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/rerf_policy.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/rerf_selection.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/rerf_settings.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/rerf_warm_start.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/residual.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/rf_expression.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/regression/table.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/reports/__init__.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/reports/metrics.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/reports/plots.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/reports/regression_space.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/reports/style.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/resources/cut_tree.default.json +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/resources/rerf_space.default.yaml +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/runtime/__init__.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/runtime/expression_ast.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/runtime/loader.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/runtime/model.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/runtime/schemas/checks-3.schema.json +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/runtime/schemas/predictors-3.schema.json +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/runtime/schemas/request-3.schema.json +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/runtime/schemas/result-3.schema.json +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/workspace.py +0 -0
- {beefcot-1.0.3 → beefcot-1.1.0}/src/beefcot/yamlio.py +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: beefcot
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Carcass yield models and cutting plans: one YAML in, one delivered model out
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
|
+
Requires-Dist: fastapi>=0.115
|
|
7
|
+
Requires-Dist: httpx>=0.27
|
|
6
8
|
Requires-Dist: jarvisplot>=2.1
|
|
7
9
|
Requires-Dist: numpy>=2.5
|
|
8
10
|
Requires-Dist: pandas>=3.0
|
|
@@ -10,6 +12,8 @@ Requires-Dist: pytest>=8
|
|
|
10
12
|
Requires-Dist: pyyaml>=6.0
|
|
11
13
|
Requires-Dist: scikit-learn>=1.9
|
|
12
14
|
Requires-Dist: scipy>=1.18
|
|
15
|
+
Requires-Dist: threadpoolctl>=3.6
|
|
16
|
+
Requires-Dist: uvicorn>=0.32
|
|
13
17
|
Requires-Dist: xgboost>=3.3
|
|
14
18
|
Description-Content-Type: text/markdown
|
|
15
19
|
|
|
@@ -23,8 +27,16 @@ python3 -m pip install beefcot
|
|
|
23
27
|
beefcot --help
|
|
24
28
|
beefcot validate /path/to/job.yaml
|
|
25
29
|
beefcot fit /path/to/job.yaml
|
|
30
|
+
beefcot project create HERD
|
|
31
|
+
beefcot project list
|
|
26
32
|
```
|
|
27
33
|
|
|
34
|
+
`beefcot project create` writes one project directory. The machine list
|
|
35
|
+
defaults to `~/.beefcot/projects.json`. Pass `--projects` or set
|
|
36
|
+
`BEEFCOT_PROJECTS` to use another file. `beefcot serve` reads that list and
|
|
37
|
+
opens a loopback workbench. The HTML pages stay with the source checkout and
|
|
38
|
+
are not part of this package.
|
|
39
|
+
|
|
28
40
|
Each fit starts from a `kind: regression_tree` YAML declaring its input table,
|
|
29
41
|
feature and target columns, cut-tree mapping, split and regression space.
|
|
30
42
|
Available methods are profile, RF, DNN, ReRF and exhaustive search. Structural
|
|
@@ -8,8 +8,16 @@ python3 -m pip install beefcot
|
|
|
8
8
|
beefcot --help
|
|
9
9
|
beefcot validate /path/to/job.yaml
|
|
10
10
|
beefcot fit /path/to/job.yaml
|
|
11
|
+
beefcot project create HERD
|
|
12
|
+
beefcot project list
|
|
11
13
|
```
|
|
12
14
|
|
|
15
|
+
`beefcot project create` writes one project directory. The machine list
|
|
16
|
+
defaults to `~/.beefcot/projects.json`. Pass `--projects` or set
|
|
17
|
+
`BEEFCOT_PROJECTS` to use another file. `beefcot serve` reads that list and
|
|
18
|
+
opens a loopback workbench. The HTML pages stay with the source checkout and
|
|
19
|
+
are not part of this package.
|
|
20
|
+
|
|
13
21
|
Each fit starts from a `kind: regression_tree` YAML declaring its input table,
|
|
14
22
|
feature and target columns, cut-tree mapping, split and regression space.
|
|
15
23
|
Available methods are profile, RF, DNN, ReRF and exhaustive search. Structural
|
|
@@ -16,10 +16,14 @@ dependencies = [
|
|
|
16
16
|
"pandas>=3.0",
|
|
17
17
|
"scipy>=1.18",
|
|
18
18
|
"scikit-learn>=1.9",
|
|
19
|
+
"threadpoolctl>=3.6",
|
|
19
20
|
"xgboost>=3.3",
|
|
20
21
|
"PyYAML>=6.0",
|
|
21
22
|
"JarvisPLOT>=2.1",
|
|
22
23
|
"pytest>=8",
|
|
24
|
+
"fastapi>=0.115",
|
|
25
|
+
"uvicorn>=0.32",
|
|
26
|
+
"httpx>=0.27",
|
|
23
27
|
]
|
|
24
28
|
|
|
25
29
|
[project.scripts]
|
|
@@ -6,7 +6,7 @@ Every job is one YAML and one verb, the same in Python as on the command line (`
|
|
|
6
6
|
|
|
7
7
|
beefcot.fit("config/regression/murdoch_rf.yaml") # kind: regression_tree -> a run with a model
|
|
8
8
|
beefcot.predict("config/prediction/murdoch_rf_241.yaml") # kind: prediction -> one table
|
|
9
|
-
beefcot.plan("config/plan/
|
|
9
|
+
beefcot.plan("config/plan/breakdown.yaml") # kind: cutting_plan -> a plan
|
|
10
10
|
beefcot.check("config/checks/murdoch_core3_bins.yaml") # kind: bin_variance -> a data check
|
|
11
11
|
beefcot.decompose("config/decomposition/murdoch_rf.yaml") # kind: error_decomposition -> noise / bias² / variance
|
|
12
12
|
beefcot.run("config/pipelines/murdoch_smoke.yaml") # kind: pipeline -> several, in order
|
|
@@ -24,7 +24,7 @@ from __future__ import annotations
|
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
from typing import Any
|
|
26
26
|
|
|
27
|
-
__version__ = "1.0
|
|
27
|
+
__version__ = "1.1.0" # the one place it is written; CHANGELOG.md records each version
|
|
28
28
|
|
|
29
29
|
__all__ = ["__version__", "fit", "predict", "plan", "check", "decompose", "run", "load_model"]
|
|
30
30
|
|
|
@@ -79,7 +79,7 @@ def plot_config(
|
|
|
79
79
|
"DataSet": [{"name": "animals", "path": "../animals.csv", "type": "csv"},
|
|
80
80
|
{"name": "cells", "path": "../cells.csv", "type": "csv"}],
|
|
81
81
|
"Figures": figures,
|
|
82
|
-
"output": {"dir": ".", "formats": ["png"]},
|
|
82
|
+
"output": {"dir": ".", "formats": ["svg", "png"]},
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
|
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
beefcot fit config/regression/murdoch_rf.yaml # kind: regression_tree -> a run with a model
|
|
4
4
|
beefcot predict config/prediction/murdoch_rf_241.yaml # kind: prediction -> one table
|
|
5
|
-
beefcot plan config/plan/
|
|
5
|
+
beefcot plan config/plan/breakdown.yaml # kind: cutting_plan -> a plan
|
|
6
6
|
beefcot check config/checks/murdoch_core3_bins.yaml # kind: bin_variance -> a data check
|
|
7
7
|
beefcot decompose config/decomposition/murdoch_rf.yaml # kind: error_decomposition -> noise / bias² / variance
|
|
8
8
|
beefcot run config/pipelines/murdoch_smoke.yaml # kind: pipeline -> several, in order
|
|
9
9
|
beefcot validate <any YAML> # read and check it, compute nothing
|
|
10
10
|
beefcot models list | show ID | verify ID | import PATH | export ID OUT
|
|
11
|
+
beefcot serve # loopback workbench; --design draws the column grid
|
|
12
|
+
beefcot user add NAME # create a workbench login
|
|
13
|
+
beefcot project create PATH # a standard project and an empty model library
|
|
14
|
+
beefcot project validate [PATH] # check that project; compute nothing
|
|
15
|
+
beefcot project list # projects recorded on this machine
|
|
16
|
+
beefcot project add PATH # record an existing project
|
|
17
|
+
beefcot project remove ID # drop it from the list; keep the directory
|
|
11
18
|
beefcot kinds | version
|
|
12
19
|
beefcot -V | -v | --version # package version, no job required
|
|
13
20
|
|
|
@@ -35,6 +42,33 @@ PRODUCING = ("fit", "predict", "plan", "check", "decompose", "run")
|
|
|
35
42
|
WITH_LIBRARY = ("predict", "plan", "run") # the jobs that may look a model up by id
|
|
36
43
|
|
|
37
44
|
|
|
45
|
+
def _user(args: argparse.Namespace) -> int:
|
|
46
|
+
"""Create or list workbench accounts. The password is read from the terminal."""
|
|
47
|
+
import getpass
|
|
48
|
+
|
|
49
|
+
from beefcot.service.auth import Accounts, AuthError
|
|
50
|
+
|
|
51
|
+
root = Path(args.root).expanduser() if args.root else Path.cwd()
|
|
52
|
+
accounts = Accounts(root / "config" / "auth" / "users.yaml")
|
|
53
|
+
try:
|
|
54
|
+
if args.user_command == "list":
|
|
55
|
+
for name in accounts.names():
|
|
56
|
+
print(name)
|
|
57
|
+
return 0
|
|
58
|
+
if not sys.stdin.isatty():
|
|
59
|
+
raise AuthError(422, "INVALID_PASSWORD", "run beefcot user add in a terminal so the password is not saved in a command")
|
|
60
|
+
password = getpass.getpass("Password: ")
|
|
61
|
+
repeated = getpass.getpass("Repeat password: ")
|
|
62
|
+
if password != repeated:
|
|
63
|
+
raise AuthError(422, "INVALID_PASSWORD", "the two passwords did not match")
|
|
64
|
+
accounts.add(args.name, password)
|
|
65
|
+
except AuthError as failure:
|
|
66
|
+
print(failure.message, file=sys.stderr)
|
|
67
|
+
return 1
|
|
68
|
+
print(f"added {args.name}")
|
|
69
|
+
return 0
|
|
70
|
+
|
|
71
|
+
|
|
38
72
|
def _produce(args: argparse.Namespace) -> dict[str, Any]:
|
|
39
73
|
import beefcot
|
|
40
74
|
from beefcot import registry
|
|
@@ -135,6 +169,26 @@ def _models(args: argparse.Namespace) -> dict[str, Any]:
|
|
|
135
169
|
return {"ok": True, "id": args.id, "written": str(written)}
|
|
136
170
|
|
|
137
171
|
|
|
172
|
+
def _project(args: argparse.Namespace) -> dict[str, Any]:
|
|
173
|
+
"""Create or check a project, or change the machine list. None of these fit a model."""
|
|
174
|
+
from beefcot.project import create, validate
|
|
175
|
+
from beefcot.project_registry import add, describe, location, remove
|
|
176
|
+
|
|
177
|
+
registry = location(getattr(args, "projects", None))
|
|
178
|
+
if args.project_command == "create":
|
|
179
|
+
report = create(args.path)
|
|
180
|
+
report.update(add(registry, report["project"]))
|
|
181
|
+
report["registry"] = str(registry)
|
|
182
|
+
return report
|
|
183
|
+
if args.project_command == "add":
|
|
184
|
+
return {"ok": True, "registry": str(registry), **add(registry, args.path)}
|
|
185
|
+
if args.project_command == "remove":
|
|
186
|
+
return {"ok": True, "registry": str(registry), **remove(registry, args.id)}
|
|
187
|
+
if args.project_command == "list":
|
|
188
|
+
return {"ok": True, "registry": str(registry), "projects": describe(registry)}
|
|
189
|
+
return validate(args.path or Path.cwd())
|
|
190
|
+
|
|
191
|
+
|
|
138
192
|
def _kinds() -> dict[str, Any]:
|
|
139
193
|
from beefcot import registry
|
|
140
194
|
|
|
@@ -146,9 +200,10 @@ def _parser(presentation: Terminal | None = None) -> argparse.ArgumentParser:
|
|
|
146
200
|
parser = HelpParser(prog="beefcot", presentation=presentation,
|
|
147
201
|
description="Define a job in YAML. Validate it, run it, inspect the delivered results.",
|
|
148
202
|
help_panels=[('Regression & data', ('validate', 'fit', 'predict', 'check', 'decompose')),
|
|
149
|
-
('Planning & pipelines', ('plan', 'run')),
|
|
203
|
+
('Planning & pipelines', ('plan', 'run', 'serve', 'user', 'project')),
|
|
150
204
|
('Models & reference', ('models', 'kinds', 'version'))],
|
|
151
205
|
examples=[('Validate', 'beefcot validate path/to/job.yaml'),
|
|
206
|
+
('New project', 'beefcot project create HERD'),
|
|
152
207
|
('Fit', 'beefcot fit config/regression/murdoch_core3_rf.yaml'),
|
|
153
208
|
('Command help', 'beefcot fit --help')])
|
|
154
209
|
parser.add_argument('-V', '-v', '--version', action=VersionAction,
|
|
@@ -186,6 +241,63 @@ def _parser(presentation: Terminal | None = None) -> argparse.ArgumentParser:
|
|
|
186
241
|
command(name, helps[name], library=name in WITH_LIBRARY, quiet=True).add_argument("config", metavar="YAML", help="job configuration; paths inside it are relative to this YAML")
|
|
187
242
|
command("validate", "validate a YAML and its paths without running the job", library=True).add_argument(
|
|
188
243
|
"config", metavar="YAML", help="the configuration to validate")
|
|
244
|
+
serve = sub.add_parser("serve", help="serve the local cutting-plan workbench on 127.0.0.1",
|
|
245
|
+
description="Serve the local cutting-plan workbench. The host is loopback only.",
|
|
246
|
+
examples=[("Open", "beefcot serve"), ("Grid", "beefcot serve --design")])
|
|
247
|
+
presentation_options(serve)
|
|
248
|
+
serve.add_argument("--port", type=int, default=8765, help="loopback port (default: 8765)")
|
|
249
|
+
serve.add_argument("--root", default=None, help="project directory that contains config/ (default: the current directory)")
|
|
250
|
+
serve.add_argument("--design", action="store_true", help="draw the 12-column reference grid in magenta")
|
|
251
|
+
serve.add_argument("--projects", default=None, help="the machine project list (default: ~/.beefcot/projects.json)")
|
|
252
|
+
|
|
253
|
+
account = sub.add_parser("user", help="add or list workbench logins",
|
|
254
|
+
description="Add a workbench login. The password is typed, not passed as an argument. "
|
|
255
|
+
"The web page cannot register a user.",
|
|
256
|
+
examples=[("Add", "beefcot user add NAME"), ("List", "beefcot user list")])
|
|
257
|
+
presentation_options(account)
|
|
258
|
+
account_commands = account.add_subparsers(dest="user_command", required=True)
|
|
259
|
+
added = account_commands.add_parser("add", help="create one user in config/auth/users.yaml")
|
|
260
|
+
presentation_options(added)
|
|
261
|
+
added.add_argument("name", help="the name typed on the login page")
|
|
262
|
+
added.add_argument("--root", default=None, help="project directory that contains config/ (default: the current directory)")
|
|
263
|
+
listed = account_commands.add_parser("list", help="list user names")
|
|
264
|
+
presentation_options(listed)
|
|
265
|
+
listed.add_argument("--root", default=None, help="project directory that contains config/ (default: the current directory)")
|
|
266
|
+
|
|
267
|
+
project = sub.add_parser(
|
|
268
|
+
"project",
|
|
269
|
+
help="create or validate a project directory",
|
|
270
|
+
description="A project holds tables, job YAML and a model library. The package stays separate.",
|
|
271
|
+
examples=[("Create", "beefcot project create HERD"),
|
|
272
|
+
("Validate", "beefcot project validate HERD"),
|
|
273
|
+
("Register", "beefcot project add HERD"),
|
|
274
|
+
("On this machine", "beefcot project list")],
|
|
275
|
+
)
|
|
276
|
+
presentation_options(project)
|
|
277
|
+
project_commands = project.add_subparsers(dest="project_command", required=True)
|
|
278
|
+
|
|
279
|
+
def project_command(name: str, help_text: str, description: str) -> argparse.ArgumentParser:
|
|
280
|
+
made = project_commands.add_parser(name, help=help_text, description=description)
|
|
281
|
+
presentation_options(made)
|
|
282
|
+
made.add_argument("--projects", default=None, help="the machine project list (default: ~/.beefcot/projects.json)")
|
|
283
|
+
return made
|
|
284
|
+
|
|
285
|
+
created = project_command("create", "write a standard project and an empty model library",
|
|
286
|
+
"Write a standard project and an empty model library. "
|
|
287
|
+
"The directory must be missing or empty. No table is copied. "
|
|
288
|
+
"The new project is recorded in the machine list.")
|
|
289
|
+
created.add_argument("path", help="the new project directory")
|
|
290
|
+
checked = project_command("validate", "check a project directory and compute nothing",
|
|
291
|
+
"Check a project directory and compute nothing.")
|
|
292
|
+
checked.add_argument("path", nargs="?", default=None, help="the project directory (default: the current directory)")
|
|
293
|
+
project_command("list", "list the projects recorded on this machine",
|
|
294
|
+
"List the projects recorded on this machine.")
|
|
295
|
+
added = project_command("add", "record an existing project directory",
|
|
296
|
+
"Record an existing project directory. The directory is not copied.")
|
|
297
|
+
added.add_argument("path", help="the project directory")
|
|
298
|
+
removed = project_command("remove", "drop a project from the machine list",
|
|
299
|
+
"Drop a project from the machine list. The directory is kept.")
|
|
300
|
+
removed.add_argument("id", help="the project id")
|
|
189
301
|
|
|
190
302
|
models = sub.add_parser("models", help="inspect, verify and move delivered models",
|
|
191
303
|
description="Inspect, verify and move delivered models.",
|
|
@@ -224,6 +336,21 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
224
336
|
parser.print_help()
|
|
225
337
|
return 0
|
|
226
338
|
args = parser.parse_args(raw)
|
|
339
|
+
if args.command == "serve":
|
|
340
|
+
from beefcot.service.errors import ServiceError
|
|
341
|
+
from beefcot.service.server import serve
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
serve(Path(args.root).expanduser() if args.root else Path.cwd(), args.port,
|
|
345
|
+
design=args.design, projects=args.projects)
|
|
346
|
+
except ServiceError as failure:
|
|
347
|
+
print(failure.message, file=sys.stderr)
|
|
348
|
+
return 1
|
|
349
|
+
except KeyboardInterrupt:
|
|
350
|
+
return 130
|
|
351
|
+
return 0
|
|
352
|
+
if args.command == "user":
|
|
353
|
+
return _user(args)
|
|
227
354
|
ui.as_json = getattr(args, 'json', False)
|
|
228
355
|
ui.no_color = getattr(args, 'no_color', False)
|
|
229
356
|
quiet = getattr(args, 'quiet', False)
|
|
@@ -241,6 +368,8 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
241
368
|
result = _produce(args)
|
|
242
369
|
elif args.command == "validate":
|
|
243
370
|
result = _validate(args)
|
|
371
|
+
elif args.command == "project":
|
|
372
|
+
result = _project(args)
|
|
244
373
|
elif args.command == "models":
|
|
245
374
|
result = _models(args)
|
|
246
375
|
elif args.command == "kinds":
|
|
@@ -254,7 +383,12 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
254
383
|
result = {"ok": False, "error": "Interrupted by user."}
|
|
255
384
|
except Exception as failure: # one structured failure, without a traceback
|
|
256
385
|
result = {"ok": False, "error": f"{type(failure).__name__}: {failure}"}
|
|
257
|
-
|
|
386
|
+
if args.command == "models":
|
|
387
|
+
label = f"models {args.models_command}"
|
|
388
|
+
elif args.command == "project":
|
|
389
|
+
label = f"project {args.project_command}"
|
|
390
|
+
else:
|
|
391
|
+
label = args.command
|
|
258
392
|
ui.result(result, label, time.monotonic() - started)
|
|
259
393
|
return 130 if interrupted else 0 if result["ok"] else 1
|
|
260
394
|
|
|
@@ -159,6 +159,33 @@ class Terminal:
|
|
|
159
159
|
return
|
|
160
160
|
if 'kinds' in result:
|
|
161
161
|
rows = [(item['kind'], f"{item['command']}\n{item['does']}") for item in result['kinds']]
|
|
162
|
+
elif 'project' in result and 'workspace' in result:
|
|
163
|
+
rows = []
|
|
164
|
+
if result.get('id'):
|
|
165
|
+
rows.append(('Id', result['id']))
|
|
166
|
+
if result.get('name'):
|
|
167
|
+
rows.append(('Name', result['name']))
|
|
168
|
+
rows += [('Project', result['project']), ('Workspace', result['workspace'] or 'Missing')]
|
|
169
|
+
rows.append(('Libraries', '\n'.join(result.get('models') or []) or 'None'))
|
|
170
|
+
rows.append(('Config', ', '.join(result.get('config') or []) or 'None'))
|
|
171
|
+
if result.get('registry'):
|
|
172
|
+
rows.append(('Registry', result['registry']))
|
|
173
|
+
if elapsed is not None:
|
|
174
|
+
rows.append(('Elapsed', f'{elapsed:.2f}s'))
|
|
175
|
+
elif result.get('registry') and isinstance(result.get('projects'), list):
|
|
176
|
+
rows = [('Registry', result['registry'])]
|
|
177
|
+
if not result['projects']:
|
|
178
|
+
rows.append(('Projects', 'None'))
|
|
179
|
+
for item in result['projects']:
|
|
180
|
+
state = 'ready' if item.get('ready') else 'not ready'
|
|
181
|
+
rows.append((item['id'], f"{item.get('name') or item['id']}\n{item.get('path', '')}\n{state}"))
|
|
182
|
+
elif result.get('registry') and result.get('id'):
|
|
183
|
+
rows = [('Id', result['id'])]
|
|
184
|
+
if result.get('name'):
|
|
185
|
+
rows.append(('Name', result['name']))
|
|
186
|
+
if result.get('path'):
|
|
187
|
+
rows.append(('Path', result['path']))
|
|
188
|
+
rows.append(('Registry', result['registry']))
|
|
162
189
|
elif isinstance(result.get('models'), list) and 'libraries' in result:
|
|
163
190
|
rows = [('Library', place) for place in result['libraries']]
|
|
164
191
|
rows += [(f'{index:02d}', f"{item['id']}\n{item['producer']} / {item['products']} products / {item['created_at'][:10]}")
|
|
@@ -195,7 +222,9 @@ class Terminal:
|
|
|
195
222
|
rows.append(('Report', str(Path(output) / 'report.html')))
|
|
196
223
|
if elapsed is not None:
|
|
197
224
|
rows.append(('Elapsed', f'{elapsed:.2f}s'))
|
|
198
|
-
titles = {'validate': 'VALID YAML', 'kinds': 'YAML KINDS',
|
|
225
|
+
titles = {'validate': 'VALID YAML', 'kinds': 'YAML KINDS',
|
|
226
|
+
'project list': 'PROJECTS', 'project add': 'PROJECT ADDED', 'project remove': 'PROJECT REMOVED',
|
|
227
|
+
'models list': 'MODEL LIBRARY',
|
|
199
228
|
'models show': 'MODEL DETAILS', 'models verify': 'MODEL VERIFIED',
|
|
200
229
|
'models import': 'MODEL IMPORTED', 'models export': 'MODEL EXPORTED'}
|
|
201
230
|
self.write(self.panel(titles.get(command, f'{command.upper()} COMPLETE'), rows, color='32'), self.out)
|