target-revenue/scripts/effort_calculator_cli.py
tegwick 9566e16a97 Implement Development Effort Calculator (WP-0010-T02)
src/target_revenue/effort_calculator.py implements Candidate A
(labor-cost-anchored, accepted T01): commit-timestamp session-gap
clustering for human interaction time, workplan/task-volume counts via
direct workplans/ directory parsing (no state-hub dependency, works
uniformly on any repo using this repo's own convention), file/line
counts with generated/vendored-path exclusion, and caller-supplied
token-cost pricing. estimate_target_basis() combines these and returns
a derivation dict (every input shown) plus a warnings list - never a
black-box dollar figure.

1-day manual-work floor, as requested: any raw commit-clustered
estimate below 1.0 day is floored and flagged with a warning that this
is very likely a measurement gap (commit-clustering is a floor
estimate by design) that should usually be compensated for by manual
override, not trusted at face value. A second, independent
sanity-check warning fires when finished-workplan/task volume is
substantial but the time estimate is still low, even above the floor -
demonstrated live against target-revenue's own history (7 finished
workplans, 57 tasks correctly flagged a 2.38-day estimate as
under-counted).

scripts/effort_calculator_cli.py: CLI wrapper printing JSON, following
the same offline-first, no-Phase-declaration pattern as
scripts/trf_onboard.py. tests/test_effort_calculator.py (15
deterministic tests, throwaway git repos/tmp_path fixtures) covers
commit clustering, workplan/task parsing, size-metric exclusion,
token-cost pricing, the floor-and-warning behavior, the sanity-check
warning, and an end-to-end smoke test. No new hard dependency.
2026-07-30 13:16:44 +02:00

57 lines
2.1 KiB
Python

#!/usr/bin/env python3
"""CLI wrapper for target_revenue.effort_calculator (WP-0010-T02).
Prints a proposed `phase.target_basis` estimate for a repo, with full
derivation and warnings, as JSON. Token counts are caller-supplied (e.g.
pulled from the state hub's `get_token_summary` beforehand) rather than
fetched here — this script has no MCP client of its own, matching the
offline-first design of the rest of this library.
Does not select a Target Multiple and does not register anything with
the hosted Trust Service — this is an estimation aid, not a Phase
declaration tool (that remains `scripts/trf_onboard.py`).
"""
from __future__ import annotations
import argparse
import dataclasses
import json
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "src"))
from target_revenue import effort_calculator as ec # noqa: E402
def main(argv: list[str] | None = None) -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("repo_path", help="path to the repo to estimate effort for")
parser.add_argument("--daily-rate", type=float, required=True)
parser.add_argument("--tokens-in", type=int, default=0)
parser.add_argument("--tokens-out", type=int, default=0)
parser.add_argument("--model", default="default")
parser.add_argument("--session-gap-hours", type=float, default=ec.DEFAULT_SESSION_GAP_HOURS)
parser.add_argument("--hours-per-day", type=float, default=ec.DEFAULT_HOURS_PER_DAY)
args = parser.parse_args(argv)
result = ec.calculate_target_basis(
args.repo_path,
daily_rate=args.daily_rate,
tokens_in=args.tokens_in,
tokens_out=args.tokens_out,
model=args.model,
session_gap_hours=args.session_gap_hours,
hours_per_day=args.hours_per_day,
)
output = dataclasses.asdict(result)
print(json.dumps(output, indent=2))
if result.warnings:
print(f"\n{len(result.warnings)} warning(s) — see 'warnings' above.", file=sys.stderr)
if __name__ == "__main__":
main()