EDPA Engine — Evidence-Driven Calculation
What this does
Computes derived hours for all team members for a given iteration by
running the deterministic engine script. Per ADR-003 ("heavy compute /
file generation → directly script"), this skill is a thin wrapper: it
resolves the iteration argument, shells out to
.edpa/engine/scripts/engine.py, and interprets the output. It never
re-implements the calculation.
One successful run writes, under .edpa/:
| Artifact |
Path |
| Engine results |
reports/iteration-<ID>/edpa_results.json |
| Excel workbook (Team Summary + Item Costs tabs) |
reports/iteration-<ID>/edpa-results.xlsx |
| Frozen audit snapshot (content-hashed) |
snapshots/<ID>.json |
Arguments
$ARGUMENTS = iteration ID (e.g., "PI-2026-1.3") or "latest" for most recent closed iteration.
Argument resolution (when $ARGUMENTS is empty)
If $ARGUMENTS is empty, blank, or "help":
- Call MCP tool
edpa_iterations (or read .edpa/iterations/*.yaml
directly). PI/iteration timeline data is reconstructed at runtime
from those per-PI and per-iteration YAML files — edpa.yaml no
longer carries pis[].
- Present available iterations with status and dates:
Available iterations:
PI-2026-1.1 [closed] 2026-04-06–2026-04-17
PI-2026-1.2 [closed] 2026-04-20–2026-05-01
PI-2026-1.3 [closed] 2026-05-04–2026-05-15
PI-2026-1.4 [active] 2026-05-18–2026-05-29 <-- suggested
PI-2026-1.5 [planned] 2026-06-01–2026-06-12 (IP)
- Default suggestion: the iteration with
status: active. If none is active, suggest the latest closed.
- Ask user: "Which iteration to compute? [suggested-id]"
- If user confirms or provides an ID, proceed. If
.edpa/config/edpa.yaml does not exist, inform user to run /edpa setup first.
Prerequisites
.edpa/config/people.yaml exists (run /edpa:setup first)
.edpa/config/cw_heuristics.yaml exists (seeded by project_setup.py;
a legacy heuristics.yaml is still accepted as fallback)
- Backlog items carry
js: (Job Size) in their YAML — V2 keeps Job Size
in the backlog files, not in any GitHub field
- The iteration has Done stories/defects/tasks and/or recorded gate
transitions
- Evidence is materialized in the item YAML (see "Where the signals
come from" below)
Run the engine
python3 .edpa/engine/scripts/engine.py --edpa-root .edpa --iteration <iteration-id>
Never hand-compute. Do not load config with inline Python, do not
hand-calculate scores or hours, and do not hand-write
edpa_results.json. The script is the single deterministic
implementation — it enforces the invariants, stamps the methodology
version into results and snapshot, and produces the XLSX + content-hashed
snapshot the audit trail relies on.
Useful variants:
# Setup doctor — what is configured, what is missing (read-only)
python3 .edpa/engine/scripts/engine.py --status
# Worked example with built-in sample data (writes no files)
python3 .edpa/engine/scripts/engine.py --demo
# Explain one person's allocation from already-computed results
python3 .edpa/engine/scripts/engine.py --edpa-root .edpa \
--iteration <iteration-id> --explain <person-id> [--explain-item <item-id>]
--output <path> overrides the results JSON path.
Interpret the output
- Summary table — stdout ends with a per-person summary (capacity,
derived hours, items, invariant status). Relay it to the user.
- Invariants — on a hard invariant failure the engine reports it
and exits 1 (
all_invariants_passed: false in the JSON). Report
which check failed; never "fix" numbers by hand.
- Snapshot line —
Snapshot frozen: … on first freeze;
refreshed (same content, frozen_at updated) on an identical rerun;
new revision (content changed); previous: <ID>.json when inputs
changed. Frozen snapshots are immutable — changed reruns create
<ID>_rev<N>.json instead of overwriting.
Excel export skipped (install openpyxl for XLSX output) —
XLSX needs openpyxl; the JSON results and snapshot are unaffected.
Then:
- Suggest
/edpa:reports <iteration-id> to render per-person timesheets.
- Nothing is auto-committed. The engine only writes files; commit
the generated
reports/ + snapshots/ outputs as part of the
iteration-close batch.
Background — what the script computes
For reference when explaining results (the script does all of this;
you never re-do it):
Pure reader. The engine reads the materialized evidence[] /
contributors[] blocks persisted in each item's YAML. It does not
scan git (or call gh) at compute time — so the report equals the
persisted state, deterministically, on any machine.
CW comes from detect_contributors.py aggregation: additive
signal weights from cw_heuristics.yaml
(contribution_score[P, item] = Σ signal_weight), normalized per
item so Σ_persons cw[*, item] = 1.0. Manual /contribute weights
stack additively. Role labels (owner/key/reviewer/consulted) are
display-time projections, never stored.
Score per person P:
Score[P, done_item] = JobSize[item] × CW[P, item] # Story / Defect / Task at Done
Score[P, gate_event] = JobSize[parent] × gate_weight × CW[P, parent] # Feature/Epic/Initiative transition
Score[P, activity] = JobSize[story] × credit_factor × CW[P, story] # in-flight Story yaml_edit activity
When git history records no transitions and no yaml_edit activity,
only Done-item credit fires — the calculation degenerates gracefully
to Done-only behaviour (the pre-v1.14 --mode selector is gone).
Hours: DerivedHours[P, item] = (Score[P, item] / Σ Score[P, *]) × Capacity[P].
Invariants (hard ones halt the run): Σ DerivedHours[P, *] = Capacity[P] ± 0.01, share ratios sum to 1.0, no negative hours.
Missing Job Size warns and skips the item.
Where the signals come from (not the engine's job)
The post-commit hook (local_evidence.py) materializes commit_author,
/contribute, yaml_edit, and state_transition signals as each commit
lands. To backfill history, commits made with EDPA_NO_LOCAL_EVIDENCE=1,
or signals from another machine, run /edpa:materialize (MCP tool
edpa_materialize, or local_evidence.py --materialize --iteration <id>
/ --all-iterations) — idempotent, deduped by ref. PR-thread signals
(pr_reviewer, issue_comment) are materialized by the optional
edpa-contribution-sync CI workflow. The engine then just reads the
result.
Error handling
- Script errors with
--edpa-root or (--iteration + --capacity + --heuristics) required → always pass --edpa-root .edpa in a V2
project (the flag has no default).
- PI id instead of iteration id (e.g. "PI-2026-1" not "PI-2026-1.3") →
the engine refuses: a PI label would silently drop every item tagged
<pi>.N. Use /edpa:close-pi <PI> for PI rollups.
- No items in iteration → "No closed items found for {iteration}.
Check iteration label."
- Missing Job Size → warn per item, excluded from calculation.
- Person with 0 relevant items → 0h derived (process issue, not math issue).
- Evidence missing / contributors empty → the engine does not scan
git to recover it; run
/edpa:materialize <iteration> to persist the
signals into evidence[], then re-run the engine.
1---2name: engine3description: Run EDPA evidence-driven calculation for an iteration by invoking the vendored engine script (.edpa/engine/scripts/engine.py). The engine reads the materialized evidence[]/contributors[] persisted in each item's YAML (written by the post-commit hook / /edpa:materialize — it does not scan git at compute time), computes CW from cw_heuristics, calculates Score and DerivedHours, validates invariants, and writes results JSON + XLSX + a frozen snapshot. Use when closing an iteration, computing derived hours, or running "EDPA výpočet". Produces the input for the reports skill.4license: MIT5---67# EDPA Engine — Evidence-Driven Calculation89## What this does1011Computes derived hours for all team members for a given iteration by12running the deterministic engine script. Per ADR-003 ("heavy compute /13file generation → directly script"), this skill is a thin wrapper: it14resolves the iteration argument, shells out to15`.edpa/engine/scripts/engine.py`, and interprets the output. It never16re-implements the calculation.1718One successful run writes, under `.edpa/`:1920| Artifact | Path |21|----------|------|22| Engine results | `reports/iteration-<ID>/edpa_results.json` |23| Excel workbook (Team Summary + Item Costs tabs) | `reports/iteration-<ID>/edpa-results.xlsx` |24| Frozen audit snapshot (content-hashed) | `snapshots/<ID>.json` |2526## Arguments2728`$ARGUMENTS` = iteration ID (e.g., "PI-2026-1.3") or "latest" for most recent closed iteration.2930### Argument resolution (when $ARGUMENTS is empty)3132If `$ARGUMENTS` is empty, blank, or "help":33341. Call MCP tool `edpa_iterations` (or read `.edpa/iterations/*.yaml`35 directly). PI/iteration timeline data is reconstructed at runtime36 from those per-PI and per-iteration YAML files — `edpa.yaml` no37 longer carries `pis[]`.382. Present available iterations with status and dates:39 ```40 Available iterations:41 PI-2026-1.1 [closed] 2026-04-06–2026-04-1742 PI-2026-1.2 [closed] 2026-04-20–2026-05-0143 PI-2026-1.3 [closed] 2026-05-04–2026-05-1544 PI-2026-1.4 [active] 2026-05-18–2026-05-29 <-- suggested45 PI-2026-1.5 [planned] 2026-06-01–2026-06-12 (IP)46 ```473. **Default suggestion:** the iteration with `status: active`. If none is active, suggest the latest `closed`.484. Ask user: "Which iteration to compute? [suggested-id]"495. If user confirms or provides an ID, proceed. If `.edpa/config/edpa.yaml` does not exist, inform user to run `/edpa setup` first.5051## Prerequisites5253- `.edpa/config/people.yaml` exists (run /edpa:setup first)54- `.edpa/config/cw_heuristics.yaml` exists (seeded by `project_setup.py`;55 a legacy `heuristics.yaml` is still accepted as fallback)56- Backlog items carry `js:` (Job Size) in their YAML — V2 keeps Job Size57 in the backlog files, not in any GitHub field58- The iteration has Done stories/defects/tasks and/or recorded gate59 transitions60- Evidence is materialized in the item YAML (see "Where the signals61 come from" below)6263## Run the engine6465```bash66python3 .edpa/engine/scripts/engine.py --edpa-root .edpa --iteration <iteration-id>67```6869**Never hand-compute.** Do not load config with inline Python, do not70hand-calculate scores or hours, and do not hand-write71`edpa_results.json`. The script is the single deterministic72implementation — it enforces the invariants, stamps the methodology73version into results and snapshot, and produces the XLSX + content-hashed74snapshot the audit trail relies on.7576Useful variants:7778```bash79# Setup doctor — what is configured, what is missing (read-only)80python3 .edpa/engine/scripts/engine.py --status8182# Worked example with built-in sample data (writes no files)83python3 .edpa/engine/scripts/engine.py --demo8485# Explain one person's allocation from already-computed results86python3 .edpa/engine/scripts/engine.py --edpa-root .edpa \87 --iteration <iteration-id> --explain <person-id> [--explain-item <item-id>]88```8990`--output <path>` overrides the results JSON path.9192## Interpret the output93941. **Summary table** — stdout ends with a per-person summary (capacity,95 derived hours, items, invariant status). Relay it to the user.962. **Invariants** — on a hard invariant failure the engine reports it97 and exits 1 (`all_invariants_passed: false` in the JSON). Report98 which check failed; never "fix" numbers by hand.993. **Snapshot line** — `Snapshot frozen: …` on first freeze;100 `refreshed (same content, frozen_at updated)` on an identical rerun;101 `new revision (content changed); previous: <ID>.json` when inputs102 changed. Frozen snapshots are immutable — changed reruns create103 `<ID>_rev<N>.json` instead of overwriting.1044. **`Excel export skipped (install openpyxl for XLSX output)`** —105 XLSX needs openpyxl; the JSON results and snapshot are unaffected.106107Then:108109- Suggest `/edpa:reports <iteration-id>` to render per-person timesheets.110- **Nothing is auto-committed.** The engine only writes files; commit111 the generated `reports/` + `snapshots/` outputs as part of the112 iteration-close batch.113114## Background — what the script computes115116For reference when explaining results (the script does all of this;117you never re-do it):118119- **Pure reader.** The engine reads the materialized `evidence[]` /120 `contributors[]` blocks persisted in each item's YAML. It does not121 scan git (or call `gh`) at compute time — so the report equals the122 persisted state, deterministically, on any machine.123- **CW** comes from `detect_contributors.py` aggregation: additive124 signal weights from `cw_heuristics.yaml`125 (`contribution_score[P, item] = Σ signal_weight`), normalized per126 item so `Σ_persons cw[*, item] = 1.0`. Manual `/contribute` weights127 stack additively. Role labels (owner/key/reviewer/consulted) are128 display-time projections, never stored.129- **Score** per person P:130131 ```132 Score[P, done_item] = JobSize[item] × CW[P, item] # Story / Defect / Task at Done133 Score[P, gate_event] = JobSize[parent] × gate_weight × CW[P, parent] # Feature/Epic/Initiative transition134 Score[P, activity] = JobSize[story] × credit_factor × CW[P, story] # in-flight Story yaml_edit activity135 ```136137 When git history records no transitions and no yaml_edit activity,138 only Done-item credit fires — the calculation degenerates gracefully139 to Done-only behaviour (the pre-v1.14 `--mode` selector is gone).140- **Hours**: `DerivedHours[P, item] = (Score[P, item] / Σ Score[P, *]) × Capacity[P]`.141- **Invariants** (hard ones halt the run): `Σ DerivedHours[P, *] =142 Capacity[P] ± 0.01`, share ratios sum to 1.0, no negative hours.143 Missing Job Size warns and skips the item.144145### Where the signals come from (not the engine's job)146147The post-commit hook (`local_evidence.py`) materializes `commit_author`,148`/contribute`, `yaml_edit`, and `state_transition` signals as each commit149lands. To backfill history, commits made with `EDPA_NO_LOCAL_EVIDENCE=1`,150or signals from another machine, run `/edpa:materialize` (MCP tool151`edpa_materialize`, or `local_evidence.py --materialize --iteration <id>`152/ `--all-iterations`) — idempotent, deduped by `ref`. PR-thread signals153(`pr_reviewer`, `issue_comment`) are materialized by the optional154`edpa-contribution-sync` CI workflow. The engine then just reads the155result.156157## Error handling158159- Script errors with `--edpa-root or (--iteration + --capacity +160 --heuristics) required` → always pass `--edpa-root .edpa` in a V2161 project (the flag has no default).162- PI id instead of iteration id (e.g. "PI-2026-1" not "PI-2026-1.3") →163 the engine refuses: a PI label would silently drop every item tagged164 `<pi>.N`. Use `/edpa:close-pi <PI>` for PI rollups.165- No items in iteration → "No closed items found for {iteration}.166 Check iteration label."167- Missing Job Size → warn per item, excluded from calculation.168- Person with 0 relevant items → 0h derived (process issue, not math issue).169- Evidence missing / contributors empty → the engine does **not** scan170 git to recover it; run `/edpa:materialize <iteration>` to persist the171 signals into `evidence[]`, then re-run the engine.