consequence-ledger 0.3.0__tar.gz → 0.3.1__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.
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/PKG-INFO +8 -6
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/README.md +7 -5
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/consequence_ledger.egg-info/PKG-INFO +8 -6
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/consequence_ledger.egg-info/SOURCES.txt +1 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/__init__.py +1 -1
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/cli.py +12 -2
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/report.py +67 -1
- consequence_ledger-0.3.1/tests/test_report_segments.py +73 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/LICENSE +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/consequence_ledger.egg-info/dependency_links.txt +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/consequence_ledger.egg-info/entry_points.txt +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/consequence_ledger.egg-info/top_level.txt +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/__main__.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/audit.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/db.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/demo.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/estimate.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/gitio.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/incidents.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/label.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/pagerduty.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/plan.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/policy.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/segments.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/sentry.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/squash.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/ledger/status.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/pyproject.toml +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/setup.cfg +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_audit.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_cache.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_cli.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_crossfit.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_degenerate.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_demo.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_errors.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_estimate.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_filters.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_incidents.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_journey.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_label.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_pagerduty.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_parallel.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_plan.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_policy.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_release.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_robust.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_security.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_segments.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_sentry.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_squash.py +0 -0
- {consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/tests/test_status.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: consequence-ledger
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Measure what AI coding decisions actually cause: randomized routing, delayed outcome labels, causal estimates.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/sujan-mitta/consequence-ledger
|
|
@@ -38,12 +38,14 @@ It is the 90-day MVP from the "AI elephant in the room" research: decision loggi
|
|
|
38
38
|
Python 3.9+ and git 2.28+. There are no dependencies.
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
|
|
42
|
-
cd consequence-ledger
|
|
43
|
-
pip install . # gives you the `ledger` command; or skip this and use `python -m ledger` from the folder
|
|
41
|
+
pip install consequence-ledger # gives you the `ledger` command
|
|
44
42
|
ledger --version
|
|
45
43
|
```
|
|
46
44
|
|
|
45
|
+
New to it? [docs/QUICKSTART.md](docs/QUICKSTART.md) takes you from install to a first report on your own repo in about five minutes,
|
|
46
|
+
and [builder.html](builder.html) is a form that writes the command for you. To work from source instead:
|
|
47
|
+
`git clone https://github.com/sujan-mitta/consequence-ledger.git`, then `pip install .` inside the folder.
|
|
48
|
+
|
|
47
49
|
## Quick start
|
|
48
50
|
|
|
49
51
|
```bash
|
|
@@ -103,7 +105,7 @@ python -m ledger recommend --stratum difficulty # and what routing by th
|
|
|
103
105
|
| `estimate` | Naive, IPW, self-normalised IPW, direct method and doubly robust estimates per arm. Bootstrap and influence-function CIs, effective sample size, minimum propensity, and a check that unmerged work is balanced across arms. `--cluster developer` resamples whole clusters when outcomes are correlated. |
|
|
104
106
|
| `breakdown` | Which tool is better for which kind of task (`--stratum difficulty`). Raw and shrunk effects per stratum with Bonferroni-adjusted p-values, so a lucky stratum is not mistaken for a pattern. |
|
|
105
107
|
| `recommend` | What routing each task type to its best tool would be worth. The tool is chosen on one half of your decisions and judged on the other, so the gain is not inflated; it also shows the gain from simply using the best tool. |
|
|
106
|
-
| `report` | A self-contained HTML report (light and dark). |
|
|
108
|
+
| `report` | A self-contained HTML report (light and dark). Includes the `breakdown` and `recommend` tables once you have run them. |
|
|
107
109
|
| `audit` | Per-tool baseline from history, with Wilson CIs and size-adjusted rates. |
|
|
108
110
|
| `demo` | Synthetic end-to-end run that checks labels against an oracle. |
|
|
109
111
|
|
|
@@ -169,7 +171,7 @@ Full details are in [research/FINDINGS.md](research/FINDINGS.md):
|
|
|
169
171
|
```
|
|
170
172
|
ledger/ the package: policy (routing), label + squash (outcomes), estimate, audit, plan, status,
|
|
171
173
|
incidents + sentry + pagerduty, report, demo, db, gitio, cli
|
|
172
|
-
tests/
|
|
174
|
+
tests/ 236 unittest cases: real git repos in temp dirs, local fake servers, estimator Monte Carlo checks
|
|
173
175
|
tools/ coverage_lite.py, dependency-free line coverage
|
|
174
176
|
docs/ METHODOLOGY.md
|
|
175
177
|
research/ simulate.py (coverage, power, noise, design, peeking studies), results/, FINDINGS.md
|
|
@@ -11,12 +11,14 @@ It is the 90-day MVP from the "AI elephant in the room" research: decision loggi
|
|
|
11
11
|
Python 3.9+ and git 2.28+. There are no dependencies.
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
|
|
15
|
-
cd consequence-ledger
|
|
16
|
-
pip install . # gives you the `ledger` command; or skip this and use `python -m ledger` from the folder
|
|
14
|
+
pip install consequence-ledger # gives you the `ledger` command
|
|
17
15
|
ledger --version
|
|
18
16
|
```
|
|
19
17
|
|
|
18
|
+
New to it? [docs/QUICKSTART.md](docs/QUICKSTART.md) takes you from install to a first report on your own repo in about five minutes,
|
|
19
|
+
and [builder.html](builder.html) is a form that writes the command for you. To work from source instead:
|
|
20
|
+
`git clone https://github.com/sujan-mitta/consequence-ledger.git`, then `pip install .` inside the folder.
|
|
21
|
+
|
|
20
22
|
## Quick start
|
|
21
23
|
|
|
22
24
|
```bash
|
|
@@ -76,7 +78,7 @@ python -m ledger recommend --stratum difficulty # and what routing by th
|
|
|
76
78
|
| `estimate` | Naive, IPW, self-normalised IPW, direct method and doubly robust estimates per arm. Bootstrap and influence-function CIs, effective sample size, minimum propensity, and a check that unmerged work is balanced across arms. `--cluster developer` resamples whole clusters when outcomes are correlated. |
|
|
77
79
|
| `breakdown` | Which tool is better for which kind of task (`--stratum difficulty`). Raw and shrunk effects per stratum with Bonferroni-adjusted p-values, so a lucky stratum is not mistaken for a pattern. |
|
|
78
80
|
| `recommend` | What routing each task type to its best tool would be worth. The tool is chosen on one half of your decisions and judged on the other, so the gain is not inflated; it also shows the gain from simply using the best tool. |
|
|
79
|
-
| `report` | A self-contained HTML report (light and dark). |
|
|
81
|
+
| `report` | A self-contained HTML report (light and dark). Includes the `breakdown` and `recommend` tables once you have run them. |
|
|
80
82
|
| `audit` | Per-tool baseline from history, with Wilson CIs and size-adjusted rates. |
|
|
81
83
|
| `demo` | Synthetic end-to-end run that checks labels against an oracle. |
|
|
82
84
|
|
|
@@ -142,7 +144,7 @@ Full details are in [research/FINDINGS.md](research/FINDINGS.md):
|
|
|
142
144
|
```
|
|
143
145
|
ledger/ the package: policy (routing), label + squash (outcomes), estimate, audit, plan, status,
|
|
144
146
|
incidents + sentry + pagerduty, report, demo, db, gitio, cli
|
|
145
|
-
tests/
|
|
147
|
+
tests/ 236 unittest cases: real git repos in temp dirs, local fake servers, estimator Monte Carlo checks
|
|
146
148
|
tools/ coverage_lite.py, dependency-free line coverage
|
|
147
149
|
docs/ METHODOLOGY.md
|
|
148
150
|
research/ simulate.py (coverage, power, noise, design, peeking studies), results/, FINDINGS.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: consequence-ledger
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Measure what AI coding decisions actually cause: randomized routing, delayed outcome labels, causal estimates.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/sujan-mitta/consequence-ledger
|
|
@@ -38,12 +38,14 @@ It is the 90-day MVP from the "AI elephant in the room" research: decision loggi
|
|
|
38
38
|
Python 3.9+ and git 2.28+. There are no dependencies.
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
|
|
42
|
-
cd consequence-ledger
|
|
43
|
-
pip install . # gives you the `ledger` command; or skip this and use `python -m ledger` from the folder
|
|
41
|
+
pip install consequence-ledger # gives you the `ledger` command
|
|
44
42
|
ledger --version
|
|
45
43
|
```
|
|
46
44
|
|
|
45
|
+
New to it? [docs/QUICKSTART.md](docs/QUICKSTART.md) takes you from install to a first report on your own repo in about five minutes,
|
|
46
|
+
and [builder.html](builder.html) is a form that writes the command for you. To work from source instead:
|
|
47
|
+
`git clone https://github.com/sujan-mitta/consequence-ledger.git`, then `pip install .` inside the folder.
|
|
48
|
+
|
|
47
49
|
## Quick start
|
|
48
50
|
|
|
49
51
|
```bash
|
|
@@ -103,7 +105,7 @@ python -m ledger recommend --stratum difficulty # and what routing by th
|
|
|
103
105
|
| `estimate` | Naive, IPW, self-normalised IPW, direct method and doubly robust estimates per arm. Bootstrap and influence-function CIs, effective sample size, minimum propensity, and a check that unmerged work is balanced across arms. `--cluster developer` resamples whole clusters when outcomes are correlated. |
|
|
104
106
|
| `breakdown` | Which tool is better for which kind of task (`--stratum difficulty`). Raw and shrunk effects per stratum with Bonferroni-adjusted p-values, so a lucky stratum is not mistaken for a pattern. |
|
|
105
107
|
| `recommend` | What routing each task type to its best tool would be worth. The tool is chosen on one half of your decisions and judged on the other, so the gain is not inflated; it also shows the gain from simply using the best tool. |
|
|
106
|
-
| `report` | A self-contained HTML report (light and dark). |
|
|
108
|
+
| `report` | A self-contained HTML report (light and dark). Includes the `breakdown` and `recommend` tables once you have run them. |
|
|
107
109
|
| `audit` | Per-tool baseline from history, with Wilson CIs and size-adjusted rates. |
|
|
108
110
|
| `demo` | Synthetic end-to-end run that checks labels against an oracle. |
|
|
109
111
|
|
|
@@ -169,7 +171,7 @@ Full details are in [research/FINDINGS.md](research/FINDINGS.md):
|
|
|
169
171
|
```
|
|
170
172
|
ledger/ the package: policy (routing), label + squash (outcomes), estimate, audit, plan, status,
|
|
171
173
|
incidents + sentry + pagerduty, report, demo, db, gitio, cli
|
|
172
|
-
tests/
|
|
174
|
+
tests/ 236 unittest cases: real git repos in temp dirs, local fake servers, estimator Monte Carlo checks
|
|
173
175
|
tools/ coverage_lite.py, dependency-free line coverage
|
|
174
176
|
docs/ METHODOLOGY.md
|
|
175
177
|
research/ simulate.py (coverage, power, noise, design, peeking studies), results/, FINDINGS.md
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Consequence Ledger: measure what AI coding decisions actually cause."""
|
|
2
|
-
__version__ = "0.3.
|
|
2
|
+
__version__ = "0.3.1"
|
|
@@ -4,7 +4,8 @@ import datetime as dt
|
|
|
4
4
|
import json
|
|
5
5
|
import sys
|
|
6
6
|
import tempfile
|
|
7
|
-
from contextlib import closing
|
|
7
|
+
from contextlib import closing, redirect_stdout
|
|
8
|
+
from io import StringIO
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
from . import __version__
|
|
@@ -298,9 +299,13 @@ def run_report(repo, out=None, primary="dr", title="Consequence Ledger"):
|
|
|
298
299
|
stats = json.loads((d / "label.json").read_text())
|
|
299
300
|
truth = json.loads((d / "truth.json").read_text()) if (d / "truth.json").exists() else None
|
|
300
301
|
validation = json.loads((d / "validation.json").read_text()) if (d / "validation.json").exists() else None
|
|
302
|
+
extra = {}
|
|
303
|
+
for key, name in (("breakdown", "breakdown.json"), ("recommendation", "recommend.json")):
|
|
304
|
+
if (d / name).exists():
|
|
305
|
+
extra[key] = json.loads((d / name).read_text())
|
|
301
306
|
out = Path(out or d / "report.html")
|
|
302
307
|
out.write_text(report_mod.render(est, stats, title, primary, truth, {"repo": Path(repo).name},
|
|
303
|
-
validation), encoding="utf-8")
|
|
308
|
+
validation, **extra), encoding="utf-8")
|
|
304
309
|
return out
|
|
305
310
|
|
|
306
311
|
|
|
@@ -468,6 +473,11 @@ def cmd_demo(args):
|
|
|
468
473
|
print(f"labeler vs oracle: precision {v['precision']:.2f}, recall {v['recall']:.2f} "
|
|
469
474
|
f"(tp {v['tp']}, fp {v['fp']}, fn {v['fn']})")
|
|
470
475
|
print("DR on oracle labels: " + ", ".join(f"{a} {e['value'] * 100:.1f}%" for a, e in v["oracle_dr"].items()))
|
|
476
|
+
seg = argparse.Namespace(repo=repo, stratum="stratum", cluster=None, baseline=None, clip=None,
|
|
477
|
+
folds=DEFAULT_FOLDS, json=False)
|
|
478
|
+
with redirect_stdout(StringIO()):
|
|
479
|
+
cmd_breakdown(seg)
|
|
480
|
+
cmd_recommend(seg)
|
|
471
481
|
print(f"\nreport: {run_report(repo, title='Consequence Ledger demo')}")
|
|
472
482
|
|
|
473
483
|
|
|
@@ -241,6 +241,60 @@ def early_card(est, label_stats):
|
|
|
241
241
|
f'<tbody>{rows}</tbody></table></div><p class="note">{trust}</p></div>')
|
|
242
242
|
|
|
243
243
|
|
|
244
|
+
def _pp(x):
|
|
245
|
+
return f"{x * 100:+.1f} pp"
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def breakdown_card(bd, baseline, stratum, stale=""):
|
|
249
|
+
if not bd:
|
|
250
|
+
return ""
|
|
251
|
+
parts = []
|
|
252
|
+
for arm, d in bd.items():
|
|
253
|
+
rows = "".join(
|
|
254
|
+
f"<tr><td>{html.escape(str(x['stratum']))}</td><td>{x['n']}</td>"
|
|
255
|
+
f"<td>{_pp(x['effect'])} <span class=\"ci\">[{_pp(x['raw_lo'])}, {_pp(x['raw_hi'])}]</span></td>"
|
|
256
|
+
f"<td>{_pp(x['shrunk'])} <span class=\"ci\">[{_pp(x['shrunk_lo'])}, {_pp(x['shrunk_hi'])}]</span></td>"
|
|
257
|
+
f"<td>{x['p_adjusted']:.3f}</td></tr>"
|
|
258
|
+
for x in sorted(d["strata"], key=lambda x: x["shrunk"]))
|
|
259
|
+
verdict = ("At least one stratum differs from the rest, even after allowing for how many were examined."
|
|
260
|
+
if d["heterogeneous"] else
|
|
261
|
+
"No stratum stands out once you allow for how many were examined.")
|
|
262
|
+
parts.append(
|
|
263
|
+
f"<p><b>{html.escape(arm)}</b> against {html.escape(baseline)}: overall {_pp(d['overall'])}, "
|
|
264
|
+
f"spread between strata {d['tau'] * 100:.1f} pp. {verdict}</p>"
|
|
265
|
+
'<div class="table"><table><thead><tr><th>Stratum</th><th>Decisions</th><th>Raw effect [95% CI]</th>'
|
|
266
|
+
'<th>Shrunk effect [95% CI]</th><th>Adjusted p</th></tr></thead>'
|
|
267
|
+
f"<tbody>{rows}</tbody></table></div>")
|
|
268
|
+
return (f"<h2>Which tool for which kind of task</h2><div class=\"card\">{''.join(parts)}"
|
|
269
|
+
f"<p class=\"note\">Effects are changes in the bad-outcome rate, by <code>{html.escape(stratum)}</code>. "
|
|
270
|
+
"Shrunk effects pull noisy strata toward the overall effect; adjusted p-values allow for looking at "
|
|
271
|
+
f"every stratum. Negative is better than the baseline.{stale}</p></div>")
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def recommend_card(rec, stratum, stale=""):
|
|
275
|
+
if not rec:
|
|
276
|
+
return ""
|
|
277
|
+
if rec["worth_it"]:
|
|
278
|
+
verdict = ("The interval is above zero: shifting routing toward these choices is likely to help. "
|
|
279
|
+
"Keep epsilon at 0.2 or more so later estimates stay valid.")
|
|
280
|
+
elif rec["best_single_lo"] > 0:
|
|
281
|
+
verdict = ("Any gain comes from one tool being better overall, not from matching tools to task types.")
|
|
282
|
+
else:
|
|
283
|
+
verdict = "The interval includes zero: no evidence yet that routing by task type beats what you did."
|
|
284
|
+
stable = ", ".join(f"{html.escape(str(s))} → {html.escape(a)}" for s, a in sorted(rec["stable_policy"].items()))
|
|
285
|
+
unstable = ", ".join(html.escape(str(s)) for s in rec["unstable_strata"])
|
|
286
|
+
return (
|
|
287
|
+
'<h2>What routing by task type is worth</h2><div class="card">'
|
|
288
|
+
f"<p>{rec['current_rate'] * 100:.1f}% of decisions went bad. Sending each <code>{html.escape(stratum)}</code> to its "
|
|
289
|
+
f"best tool, chosen on one half of the decisions and judged on the other, would have changed that by "
|
|
290
|
+
f"<b>{_pp(-rec['gain'])}</b> <span class=\"ci\">[{_pp(-rec['gain_hi'])}, {_pp(-rec['gain_lo'])}]</span>. "
|
|
291
|
+
f"Always using the single best tool: <b>{_pp(-rec['best_single_gain'])}</b> "
|
|
292
|
+
f"<span class=\"ci\">[{_pp(-rec['best_single_hi'])}, {_pp(-rec['best_single_lo'])}]</span>.</p>"
|
|
293
|
+
+ (f"<p>Choices that held in both halves: {stable}.</p>" if stable else "")
|
|
294
|
+
+ (f"<p>Changed with the data (no real preference): {unstable}.</p>" if unstable else "")
|
|
295
|
+
+ f"<p class=\"note\">{verdict}{stale}</p></div>")
|
|
296
|
+
|
|
297
|
+
|
|
244
298
|
def incident_rows(links):
|
|
245
299
|
names = {"suspect": "Suspect commit in the export", "files_off": "Files only, not linked (default)",
|
|
246
300
|
"files_latest": "Files only, latest matching deploy", "files_window": "Files only, every match in 14 days"}
|
|
@@ -268,7 +322,8 @@ def validation_card(v):
|
|
|
268
322
|
f'{incident_table}</div>')
|
|
269
323
|
|
|
270
324
|
|
|
271
|
-
def render(est, label_stats, title="Consequence Ledger", primary="dr", truth=None, meta=None, validation=None
|
|
325
|
+
def render(est, label_stats, title="Consequence Ledger", primary="dr", truth=None, meta=None, validation=None,
|
|
326
|
+
breakdown=None, recommendation=None):
|
|
272
327
|
meta = meta or {}
|
|
273
328
|
as_of = dt.datetime.fromtimestamp(label_stats["as_of"], dt.timezone.utc).strftime("%Y-%m-%d")
|
|
274
329
|
rows = []
|
|
@@ -299,6 +354,16 @@ def render(est, label_stats, title="Consequence Ledger", primary="dr", truth=Non
|
|
|
299
354
|
+ ('<span><i class="dot" style="background:var(--truth)"></i>True rate (synthetic demo)</span>' if truth else "")
|
|
300
355
|
+ "</div>")
|
|
301
356
|
source = html.escape(meta.get("repo", ""))
|
|
357
|
+
stale = ""
|
|
358
|
+
for part in (breakdown, recommendation):
|
|
359
|
+
if part and part.get("n") not in (None, est["n"]):
|
|
360
|
+
stale = (f" These tables were computed from {part['n']} decisions, the estimate above from {est['n']}; "
|
|
361
|
+
"rerun <code>ledger breakdown</code> and <code>ledger recommend</code> to refresh them.")
|
|
362
|
+
segments = ""
|
|
363
|
+
if breakdown:
|
|
364
|
+
segments += breakdown_card(breakdown["arms"], breakdown["baseline"], breakdown["stratum"], stale)
|
|
365
|
+
if recommendation:
|
|
366
|
+
segments += recommend_card(recommendation, recommendation["stratum"], stale)
|
|
302
367
|
clusters = est.get("clusters", est["n"])
|
|
303
368
|
cluster_note = f", resampling {clusters} clusters of related decisions" if clusters < est["n"] else ""
|
|
304
369
|
return f"""<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
@@ -315,6 +380,7 @@ def render(est, label_stats, title="Consequence Ledger", primary="dr", truth=Non
|
|
|
315
380
|
<div class="card table"><table><thead><tr><th>Arm</th><th>Decisions</th><th>Bad</th><th>Naive</th><th>IPW</th><th>SNIPW</th>
|
|
316
381
|
<th>{NAMES[primary]} [95% CI]</th><th>vs baseline</th><th>ESS</th>{truth_head}</tr></thead><tbody>{''.join(rows)}</tbody></table></div>
|
|
317
382
|
{early_card(est, label_stats)}
|
|
383
|
+
{segments}
|
|
318
384
|
{validation_card(validation)}
|
|
319
385
|
<h2>How to read this</h2>
|
|
320
386
|
<div class="card note">
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""The report shows the task-type tables, and only honestly."""
|
|
2
|
+
import random
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from ledger.estimate import estimate
|
|
6
|
+
from ledger.report import breakdown_card, recommend_card, render
|
|
7
|
+
from ledger.segments import breakdown, recommend
|
|
8
|
+
from test_degenerate import STATS, has_bad_token
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_rows(n=400, seed=3):
|
|
12
|
+
rng = random.Random(seed)
|
|
13
|
+
out = []
|
|
14
|
+
for i in range(n):
|
|
15
|
+
arm = "A" if rng.random() < 0.5 else "B"
|
|
16
|
+
stratum = rng.choice(["easy", "hard"])
|
|
17
|
+
bad = rng.random() < (0.08 if arm == "A" else 0.14)
|
|
18
|
+
out.append({"arm": arm, "y": int(bad), "p": {"A": 0.5, "B": 0.5}, "s": stratum, "c": f"c{i}"})
|
|
19
|
+
return out
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SegmentsInReportTests(unittest.TestCase):
|
|
23
|
+
def setUp(self):
|
|
24
|
+
self.rows = make_rows()
|
|
25
|
+
self.est = estimate(self.rows, arms=["A", "B"], baseline="A", boot=20)
|
|
26
|
+
self.bd = {"baseline": "A", "stratum": "difficulty", "n": len(self.rows),
|
|
27
|
+
"arms": breakdown(self.rows, ["A", "B"], "A", None, 5)}
|
|
28
|
+
self.rec = dict(recommend(self.rows, ["A", "B"], "A", None, 5), baseline="A", stratum="difficulty")
|
|
29
|
+
|
|
30
|
+
def test_tables_appear_when_given(self):
|
|
31
|
+
page = render(self.est, STATS, breakdown=self.bd, recommendation=self.rec)
|
|
32
|
+
self.assertIn("Which tool for which kind of task", page)
|
|
33
|
+
self.assertIn("What routing by task type is worth", page)
|
|
34
|
+
self.assertIn("easy", page)
|
|
35
|
+
self.assertIn("hard", page)
|
|
36
|
+
self.assertFalse(has_bad_token(page))
|
|
37
|
+
|
|
38
|
+
def test_report_without_them_is_unchanged(self):
|
|
39
|
+
page = render(self.est, STATS)
|
|
40
|
+
self.assertNotIn("Which tool for which kind of task", page)
|
|
41
|
+
self.assertNotIn("What routing by task type", page)
|
|
42
|
+
|
|
43
|
+
def test_stale_tables_are_flagged(self):
|
|
44
|
+
old = dict(self.bd, n=len(self.rows) - 50)
|
|
45
|
+
page = render(self.est, STATS, breakdown=old)
|
|
46
|
+
self.assertIn("rerun", page)
|
|
47
|
+
fresh = render(self.est, STATS, breakdown=self.bd)
|
|
48
|
+
self.assertNotIn("rerun", fresh)
|
|
49
|
+
|
|
50
|
+
def test_stratum_and_arm_names_are_escaped(self):
|
|
51
|
+
bd = {"baseline": "A", "stratum": "<b>x</b>", "n": 1, "arms": {"<i>B</i>": {
|
|
52
|
+
"overall": 0.0, "tau": 0.0, "heterogeneous": False,
|
|
53
|
+
"strata": [{"stratum": "<script>alert(1)</script>", "n": 5, "effect": 0.01, "se": 0.02, "raw_lo": -0.03,
|
|
54
|
+
"raw_hi": 0.05, "shrunk": 0.0, "shrunk_lo": -0.02, "shrunk_hi": 0.02, "p_adjusted": 1.0}]}}}
|
|
55
|
+
page = breakdown_card(bd["arms"], "A", bd["stratum"])
|
|
56
|
+
self.assertNotIn("<script>", page)
|
|
57
|
+
self.assertNotIn("<i>B</i>", page)
|
|
58
|
+
|
|
59
|
+
def test_empty_inputs_render_nothing(self):
|
|
60
|
+
self.assertEqual(breakdown_card({}, "A", "s"), "")
|
|
61
|
+
self.assertEqual(recommend_card(None, "s"), "")
|
|
62
|
+
|
|
63
|
+
def test_each_verdict_wording(self):
|
|
64
|
+
base = {"n": 10, "current_rate": 0.1, "gain": 0.0, "gain_lo": -0.01, "gain_hi": 0.01,
|
|
65
|
+
"best_single_gain": 0.0, "best_single_lo": -0.01, "best_single_hi": 0.01,
|
|
66
|
+
"stable_policy": {}, "unstable_strata": [], "worth_it": False}
|
|
67
|
+
self.assertIn("includes zero", recommend_card(base, "s"))
|
|
68
|
+
self.assertIn("one tool being better overall", recommend_card(dict(base, best_single_lo=0.01), "s"))
|
|
69
|
+
self.assertIn("likely to help", recommend_card(dict(base, worth_it=True), "s"))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
{consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/consequence_ledger.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{consequence_ledger-0.3.0 → consequence_ledger-0.3.1}/consequence_ledger.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|