cometric 0.1.0__py3-none-any.whl
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.
- cometric/__init__.py +50 -0
- cometric/cli.py +210 -0
- cometric/core/__init__.py +0 -0
- cometric/core/api.py +835 -0
- cometric/core/engine.py +1386 -0
- cometric/core/enums.py +273 -0
- cometric/core/fingerprint.py +91 -0
- cometric/core/issues.py +68 -0
- cometric/core/ledger.py +165 -0
- cometric/core/models.py +450 -0
- cometric/core/money.py +110 -0
- cometric/core/packs.py +446 -0
- cometric/core/periods.py +102 -0
- cometric/core/predicates.py +139 -0
- cometric/core/quantities.py +138 -0
- cometric/core/schema.py +97 -0
- cometric/core/templates.py +338 -0
- cometric/core/tiers.py +78 -0
- cometric/core/validation.py +738 -0
- cometric/examples/case_a.json +293 -0
- cometric/examples/case_b.json +212 -0
- cometric/examples/case_c.json +233 -0
- cometric/examples/case_d.json +253 -0
- cometric/examples/case_e.json +258 -0
- cometric/examples/case_f.json +298 -0
- cometric/examples/case_g.json +243 -0
- cometric/examples/case_h.json +271 -0
- cometric/examples/spec_example.json +134 -0
- cometric/mcp/__init__.py +0 -0
- cometric/mcp/__main__.py +3 -0
- cometric/mcp/server.py +470 -0
- cometric/packs/alsp/concepts/provider_types.yaml +154 -0
- cometric/packs/alsp/concepts/service_categories.yaml +132 -0
- cometric/packs/alsp/concepts/unit_templates.yaml +132 -0
- cometric/packs/alsp/coverage.yaml +17 -0
- cometric/packs/alsp/evidence/claims.yaml +446 -0
- cometric/packs/alsp/evidence/sources.yaml +545 -0
- cometric/packs/alsp/overlays/levers.yaml +82 -0
- cometric/packs/alsp/overlays/mechanisms.yaml +19 -0
- cometric/packs/alsp/overlays/metrics.yaml +15 -0
- cometric/packs/alsp/overlays/pricing_models.yaml +186 -0
- cometric/packs/alsp/pack.yaml +9 -0
- cometric/packs/alsp/rules/rules.yaml +303 -0
- cometric/packs/base/concepts/automation_structures.yaml +207 -0
- cometric/packs/base/concepts/billing_bases.yaml +239 -0
- cometric/packs/base/concepts/cost_categories.yaml +494 -0
- cometric/packs/base/concepts/dimensions.yaml +404 -0
- cometric/packs/base/concepts/fee_components.yaml +105 -0
- cometric/packs/base/concepts/layers.yaml +146 -0
- cometric/packs/base/concepts/levers.yaml +1449 -0
- cometric/packs/base/concepts/mechanisms.yaml +295 -0
- cometric/packs/base/concepts/metrics.yaml +1149 -0
- cometric/packs/base/concepts/negotiation_exchanges.yaml +226 -0
- cometric/packs/base/concepts/offers.yaml +88 -0
- cometric/packs/base/concepts/pricing_models.yaml +821 -0
- cometric/packs/base/concepts/schema_map.yaml +1100 -0
- cometric/packs/base/concepts/selection_framework.yaml +215 -0
- cometric/packs/base/concepts/selection_heuristics.yaml +285 -0
- cometric/packs/base/coverage.yaml +33 -0
- cometric/packs/base/evidence/claims.yaml +442 -0
- cometric/packs/base/evidence/sources.yaml +293 -0
- cometric/packs/base/excluded.yaml +42 -0
- cometric/packs/base/pack.yaml +10 -0
- cometric/packs/base/rules/rules.yaml +521 -0
- cometric/packs/consulting/concepts/consulting_concepts.yaml +82 -0
- cometric/packs/consulting/concepts/provider_types.yaml +35 -0
- cometric/packs/consulting/concepts/service_categories.yaml +41 -0
- cometric/packs/consulting/concepts/unit_templates.yaml +36 -0
- cometric/packs/consulting/coverage.yaml +17 -0
- cometric/packs/consulting/overlays/pricing_models.yaml +29 -0
- cometric/packs/consulting/pack.yaml +11 -0
- cometric/packs/consulting/rules/rules.yaml +45 -0
- cometric/packs/consulting/templates/capped_tm.json +132 -0
- cometric/packs/consulting/templates/fixed_fee_plus_success.json +163 -0
- cometric/packs/consulting/templates/phased_fixed_fee.json +133 -0
- cometric/packs/consulting/templates/retainer_hours_drawdown.json +144 -0
- cometric/packs/consulting/templates/role_based_tm.json +123 -0
- cometric/py.typed +0 -0
- cometric-0.1.0.dist-info/METADATA +137 -0
- cometric-0.1.0.dist-info/RECORD +83 -0
- cometric-0.1.0.dist-info/WHEEL +4 -0
- cometric-0.1.0.dist-info/entry_points.txt +2 -0
- cometric-0.1.0.dist-info/licenses/LICENSE +202 -0
cometric/__init__.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Cometric: Commercial Intelligence Engine.
|
|
2
|
+
|
|
3
|
+
Model commercial arrangements, simulate engagement economics, and search for structures that
|
|
4
|
+
balance profitability, risk, and client value. All arithmetic is deterministic decimal code.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .core.api import (
|
|
8
|
+
break_even,
|
|
9
|
+
calculate_periods,
|
|
10
|
+
check_commercial_constraints,
|
|
11
|
+
compare_arrangements,
|
|
12
|
+
derive_risk_exposure,
|
|
13
|
+
explain_fee,
|
|
14
|
+
price_floor,
|
|
15
|
+
search_structures,
|
|
16
|
+
)
|
|
17
|
+
from .core.engine import ENGINE_VERSION
|
|
18
|
+
from .core.models import SCHEMA_VERSION
|
|
19
|
+
from .core.packs import describe_pack, list_packs, list_vocabulary, lookup_concept
|
|
20
|
+
from .core.schema import describe_schema
|
|
21
|
+
from .core.templates import get_template, list_templates
|
|
22
|
+
from .core.validation import validate_engagement
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
26
|
+
|
|
27
|
+
__version__ = version("cometric")
|
|
28
|
+
except PackageNotFoundError: # running from a source tree without installation
|
|
29
|
+
__version__ = "0.0.0"
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"ENGINE_VERSION",
|
|
33
|
+
"SCHEMA_VERSION",
|
|
34
|
+
"break_even",
|
|
35
|
+
"calculate_periods",
|
|
36
|
+
"check_commercial_constraints",
|
|
37
|
+
"compare_arrangements",
|
|
38
|
+
"derive_risk_exposure",
|
|
39
|
+
"describe_pack",
|
|
40
|
+
"describe_schema",
|
|
41
|
+
"explain_fee",
|
|
42
|
+
"get_template",
|
|
43
|
+
"list_packs",
|
|
44
|
+
"list_templates",
|
|
45
|
+
"list_vocabulary",
|
|
46
|
+
"lookup_concept",
|
|
47
|
+
"price_floor",
|
|
48
|
+
"search_structures",
|
|
49
|
+
"validate_engagement",
|
|
50
|
+
]
|
cometric/cli.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Command-line interface. Every command prints JSON; inputs are JSON or YAML files.
|
|
2
|
+
|
|
3
|
+
A file may hold a bare object, or a casebook fixture with `engagement` and `scenarios` keys.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
|
|
16
|
+
from . import __version__
|
|
17
|
+
from .core import api, packs, schema, templates
|
|
18
|
+
from .core.validation import validate_engagement
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _read(path: str) -> Any:
|
|
22
|
+
text = Path(path).read_text()
|
|
23
|
+
return yaml.safe_load(text) if path.endswith((".yaml", ".yml")) else json.loads(text)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _engagement(path: str) -> Any:
|
|
27
|
+
doc = _read(path)
|
|
28
|
+
return doc["engagement"] if isinstance(doc, dict) and "engagement" in doc else doc
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _scenarios(path: str | None, fallback: str, ids: list[str] | None = None) -> list[Any]:
|
|
32
|
+
doc = _read(path or fallback)
|
|
33
|
+
if isinstance(doc, dict) and "scenarios" in doc:
|
|
34
|
+
found = doc["scenarios"]
|
|
35
|
+
elif isinstance(doc, list):
|
|
36
|
+
found = doc
|
|
37
|
+
else:
|
|
38
|
+
found = [doc]
|
|
39
|
+
if ids:
|
|
40
|
+
found = [s for s in found if s.get("id") in ids]
|
|
41
|
+
return found
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _policy(value: str | None, engagement_file: str) -> Any:
|
|
45
|
+
if value is None:
|
|
46
|
+
return None
|
|
47
|
+
if Path(value).exists():
|
|
48
|
+
return _read(value)
|
|
49
|
+
doc = _read(engagement_file)
|
|
50
|
+
if isinstance(doc, dict) and value in (doc.get("cost_policies") or {}):
|
|
51
|
+
return doc["cost_policies"][value]
|
|
52
|
+
return value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _print(obj: Any) -> None:
|
|
56
|
+
json.dump(obj, sys.stdout, indent=2, default=str)
|
|
57
|
+
sys.stdout.write("\n")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def main(argv: list[str] | None = None) -> int:
|
|
61
|
+
p = argparse.ArgumentParser(prog="cometric", description="Commercial Intelligence Engine")
|
|
62
|
+
p.add_argument("--version", action="version", version=__version__)
|
|
63
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
64
|
+
|
|
65
|
+
v = sub.add_parser("validate", help="validate an engagement")
|
|
66
|
+
v.add_argument("engagement")
|
|
67
|
+
|
|
68
|
+
c = sub.add_parser("calculate", help="monthly fees, costs, and metrics")
|
|
69
|
+
c.add_argument("engagement")
|
|
70
|
+
c.add_argument("--scenarios", help="file with scenario(s); defaults to the engagement file")
|
|
71
|
+
c.add_argument("--scenario", action="append", help="scenario id to run (repeatable)")
|
|
72
|
+
c.add_argument("--cost-policy", help="file, a policy name inside a fixture, or 'reference'")
|
|
73
|
+
c.add_argument("--detail", default="summary", choices=["summary", "periods", "line_items"])
|
|
74
|
+
|
|
75
|
+
e = sub.add_parser("explain", help="line-item trace for one period")
|
|
76
|
+
e.add_argument("engagement")
|
|
77
|
+
e.add_argument("--period", required=True)
|
|
78
|
+
e.add_argument("--scenarios")
|
|
79
|
+
e.add_argument("--scenario")
|
|
80
|
+
e.add_argument("--component")
|
|
81
|
+
e.add_argument("--cost-policy")
|
|
82
|
+
|
|
83
|
+
cmp_ = sub.add_parser("compare", help="compare engagements across the same scenarios")
|
|
84
|
+
cmp_.add_argument("engagements", nargs="+")
|
|
85
|
+
cmp_.add_argument("--scenarios", required=True)
|
|
86
|
+
cmp_.add_argument("--cost-policy", required=True)
|
|
87
|
+
|
|
88
|
+
s = sub.add_parser("search", help="structure search from a search_spec file")
|
|
89
|
+
s.add_argument("spec")
|
|
90
|
+
|
|
91
|
+
f = sub.add_parser("floor", help="price floor at each scenario volume")
|
|
92
|
+
f.add_argument("engagement")
|
|
93
|
+
f.add_argument("--scenarios")
|
|
94
|
+
f.add_argument("--cost-policy", required=True)
|
|
95
|
+
f.add_argument("--target-margin", required=True)
|
|
96
|
+
f.add_argument("--risk-premium", default="0")
|
|
97
|
+
|
|
98
|
+
b = sub.add_parser("break-even", help="driver volume at zero contribution")
|
|
99
|
+
b.add_argument("engagement")
|
|
100
|
+
b.add_argument("--driver", required=True)
|
|
101
|
+
b.add_argument("--scenarios")
|
|
102
|
+
b.add_argument("--scenario")
|
|
103
|
+
b.add_argument("--cost-policy", required=True)
|
|
104
|
+
|
|
105
|
+
sch = sub.add_parser("schema", help="JSON Schema for a model")
|
|
106
|
+
sch.add_argument("model", choices=schema.model_names())
|
|
107
|
+
|
|
108
|
+
sub.add_parser("packs", help="list installed packs")
|
|
109
|
+
pv = sub.add_parser("validate-pack", help="validate a pack's data files")
|
|
110
|
+
pv.add_argument("pack")
|
|
111
|
+
lk = sub.add_parser("lookup", help="look up a concept by id or text")
|
|
112
|
+
lk.add_argument("query")
|
|
113
|
+
lk.add_argument("--pack")
|
|
114
|
+
|
|
115
|
+
sub.add_parser("templates", help="list templates")
|
|
116
|
+
t = sub.add_parser("template", help="print a template")
|
|
117
|
+
t.add_argument("template_id")
|
|
118
|
+
|
|
119
|
+
m = sub.add_parser("mcp", help="run the MCP server")
|
|
120
|
+
m.add_argument("--transport", default="stdio", choices=["stdio", "http"])
|
|
121
|
+
m.add_argument("--host", default="127.0.0.1")
|
|
122
|
+
m.add_argument("--port", type=int, default=8765)
|
|
123
|
+
|
|
124
|
+
a = p.parse_args(argv)
|
|
125
|
+
|
|
126
|
+
if a.cmd == "validate":
|
|
127
|
+
out = validate_engagement(_engagement(a.engagement))
|
|
128
|
+
_print(out)
|
|
129
|
+
return 0 if out["valid"] else 1
|
|
130
|
+
if a.cmd == "calculate":
|
|
131
|
+
eng = _engagement(a.engagement)
|
|
132
|
+
results = [
|
|
133
|
+
api.calculate_periods(eng, sc, _policy(a.cost_policy, a.engagement), a.detail)
|
|
134
|
+
for sc in _scenarios(a.scenarios, a.engagement, a.scenario)
|
|
135
|
+
]
|
|
136
|
+
_print(results[0] if len(results) == 1 else results)
|
|
137
|
+
return 0 if all(r["status"] in ("ok", "infeasible") for r in results) else 1
|
|
138
|
+
if a.cmd == "explain":
|
|
139
|
+
sc = _scenarios(a.scenarios, a.engagement, [a.scenario] if a.scenario else None)[0]
|
|
140
|
+
_print(
|
|
141
|
+
api.explain_fee(
|
|
142
|
+
_engagement(a.engagement),
|
|
143
|
+
sc,
|
|
144
|
+
_policy(a.cost_policy, a.engagement),
|
|
145
|
+
a.period,
|
|
146
|
+
a.component,
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
return 0
|
|
150
|
+
if a.cmd == "compare":
|
|
151
|
+
cands = [{"id": Path(x).stem, "engagement": _engagement(x)} for x in a.engagements]
|
|
152
|
+
_print(
|
|
153
|
+
api.compare_arrangements(
|
|
154
|
+
cands,
|
|
155
|
+
_scenarios(a.scenarios, a.scenarios),
|
|
156
|
+
_policy(a.cost_policy, a.engagements[0]),
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
return 0
|
|
160
|
+
if a.cmd == "search":
|
|
161
|
+
_print(api.search_structures(_read(a.spec)))
|
|
162
|
+
return 0
|
|
163
|
+
if a.cmd == "floor":
|
|
164
|
+
_print(
|
|
165
|
+
api.price_floor(
|
|
166
|
+
_engagement(a.engagement),
|
|
167
|
+
_scenarios(a.scenarios, a.engagement),
|
|
168
|
+
_policy(a.cost_policy, a.engagement),
|
|
169
|
+
a.target_margin,
|
|
170
|
+
a.risk_premium,
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
return 0
|
|
174
|
+
if a.cmd == "break-even":
|
|
175
|
+
sc = _scenarios(a.scenarios, a.engagement, [a.scenario] if a.scenario else None)[0]
|
|
176
|
+
_print(
|
|
177
|
+
api.break_even(
|
|
178
|
+
_engagement(a.engagement), sc, _policy(a.cost_policy, a.engagement), a.driver
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
return 0
|
|
182
|
+
if a.cmd == "schema":
|
|
183
|
+
_print(schema.describe_schema(a.model))
|
|
184
|
+
return 0
|
|
185
|
+
if a.cmd == "packs":
|
|
186
|
+
_print(packs.list_packs())
|
|
187
|
+
return 0
|
|
188
|
+
if a.cmd == "validate-pack":
|
|
189
|
+
problems = packs.validate_pack(a.pack)
|
|
190
|
+
_print({"pack": a.pack, "valid": not problems, "problems": problems})
|
|
191
|
+
return 0 if not problems else 1
|
|
192
|
+
if a.cmd == "lookup":
|
|
193
|
+
_print(packs.lookup_concept(a.query, a.pack))
|
|
194
|
+
return 0
|
|
195
|
+
if a.cmd == "templates":
|
|
196
|
+
_print(templates.list_templates())
|
|
197
|
+
return 0
|
|
198
|
+
if a.cmd == "template":
|
|
199
|
+
_print(templates.get_template(a.template_id))
|
|
200
|
+
return 0
|
|
201
|
+
if a.cmd == "mcp":
|
|
202
|
+
from .mcp.server import main as mcp_main
|
|
203
|
+
|
|
204
|
+
mcp_main("stdio" if a.transport == "stdio" else "http", a.host, a.port)
|
|
205
|
+
return 0
|
|
206
|
+
return 2
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
if __name__ == "__main__":
|
|
210
|
+
raise SystemExit(main())
|
|
File without changes
|