EDPA — Evidence-Driven Proportional Allocation
Deriving capacity from delivery evidence
1. Summary
A person declares capacity for a period. The system identifies work items they demonstrably contributed to. Capacity is ex post proportionally split among relevant items based on Job Size and contribution level.
The result is a report that is a byproduct of delivery, not a separate administrative activity.
The model provides two complementary views of the same data:
- Per-person view: how a person's capacity is distributed among their items → reports, timesheets
- Per-item view: how work on an item is distributed among people → cost allocation, audit per deliverable
2. Terminology
| Term | Definition | Configuration |
|---|---|---|
| Iteration | Delivery cycle. Stories are planned, delivered, and closed. | 1w (AI-native) / 2w (classic) |
| Planning Interval | Planning cycle. Features are planned, coordinated, and evaluated. | 5w (4+1 IP) / 10w (8+2 IP) |
| IP Iteration | Innovation & Planning iteration at the end of PI. | Last iteration in PI |
| Job Size (JS) | Relative size estimate of a work item. Modified Fibonacci per WSJF (SAFe 6). | 1, 2, 3, 5, 8, 13, 20, 40, 100 |
| WSJF | Prioritization score: (BV + TC + RR&OE) / JS | Independent per level |
| CW | Contribution Weight — per-item normalized share of a person on an item (Σ across persons = 1.0 per item). | 0–1.0 |
| Signal Weight | Weight of a single evidence signal type (commit_author, pr_reviewer, issue_comment) aggregated by detect_contributors.py. In the config under the signals.* key. | cw_heuristics.yaml |
| Contribution Score | Σ fired signal weights per (person, item). Input to per-item normalization. In code as contribution_score. | Automatic |
| Derived Hours | Derived hours, model output. | After Iteration Close |
3. Model architecture
3.1 Three separate layers
| Layer | Purpose | Where it lives |
|---|---|---|
| Operational Metadata | Live delivery data | GitHub Issues + Projects |
| Capacity Registry | People capacity, roles, FTE | YAML / JSON in repo |
| Evidence & Reporting | Frozen snapshots, reports, signatures | /snapshots, /reports, /signed |
3.2 Source of truth
GitHub IS source of truth for: issue hierarchy, ownership, status, Job Size, WSJF inputs, review trail, delivery audit trail.
GitHub IS NOT source of truth for: hourly capacity, FTE, derived hours, signature states. These live in the evidence layer.
4. Work item hierarchy
Each level has its own independent Job Size and WSJF. Feature WSJF is not computed from Stories below it.
| Level | Job Size range | GitHub mapping |
|---|---|---|
| Initiative | 1–100 (mod. Fibonacci) | Issue Type: Initiative |
| Epic | 1–100 (mod. Fibonacci) | Issue Type: Epic |
| Feature | 1–100 (mod. Fibonacci) | Issue Type: Feature |
| Story | 1–100 (mod. Fibonacci) | Issue Type: Story |
| Defect | 1–100 (mod. Fibonacci) | Issue Type: Defect |
| Task | — (sub-Story checklist) | Issue Type: Task |
5. Model: Evidence-Driven Proportional Allocation
5.1 Iteration Planning Protocol
Before EDPA derives hours (ex-post), the team must plan the iteration (ex-ante). Planning requires confirmed capacity as input.
- Confirm Capacity — Each team member confirms availability. This is a commitment, not an estimate. External collaborators negotiate allocation explicitly. Result:
availability: confirmedinpeople.yaml. - Adjust per-iteration capacity (optional) — One-off deviations from baseline (PTO, overtime, ramp) are recorded as a per-iteration override in the iteration YAML
people:block — the/edpa:capacity <iteration> --add --person <id> --hours …command. Thecapacity_per_iterationbaseline stays; the engine applies the override and reportscapacity_baseline+capacity_override. Set before closing the iteration (Stage 1 of/edpa:close-iteration). - Calculate Planning Capacity —
Planning_Capacity = Σ Capacity[P, I] × planning_factor. Theplanning_factoris a team-level property (configured per team inpeople.yamlunderteams:). Default: 0.8. - Select Work — Pull stories from the prioritized backlog (WSJF order) until
Σ JobSizeapproaches historical velocity ×planning_factor. Do not plan to 100%. - Buffer — The remaining ~20% absorbs support, maintenance, incidents, and unplanned work. If buffer items generate evidence, EDPA allocates them normally.
- Edge case — If no unplanned work occurs, all capacity goes to planned items. The guarantee
Σ = Capacityholds regardless.
5.2 Inputs
| Input | Source | Example |
|---|---|---|
Capacity[P, I] | Confirmed at Iteration Planning | 40h |
RelevantItems[P, I] | Automatic from GitHub evidence (Story Done + Feature/Epic/Initiative gates) | 6 items across 4 levels |
JobSize[item] | Custom field on issue | Mod. Fibonacci 1–100 |
cw[P, item] | detect_contributors.py — additive aggregation + per-item normalization | 0–1.0 (Σ on item = 1.0) |
5.3 Evidence detection
| GitHub signal | Signal weight | Detection |
|---|---|---|
| Commit author | 4.00 | Commit with S-/F-/E-/I-XXX in branch/message |
| PR reviewer | 2.17 | Submitted PR review (excluding self) |
| Issue / PR comment | 1.46 | Comment (bots excluded) |
/contribute @person weight:X | explicit | Manual additive signal — weight X from the directive |
- Signals aggregate additively into
contribution_scoreper (person, item) — no "highest wins" - Per-item normalization yields the cw share:
cw = contribution_score / Σ_persons contribution_score /contributeis a peer signal — it does not override auto-detection, it adds weight- Default weights above are calibrated by Monte Carlo simulation (1000 synthetic scenarios)
issue_comment and pr_reviewer weights. See auto-calibration.md.5.4 Calculation — single source, sum-and-normalize
cw.
Worked example: Calculator. Full spec: methodology.md § 5.4.
5.5 Mathematical guarantees (two)
5.6 What the engine credits per item type
EDPA works with 6 issue types (S-, F-, E-, I-, D-, T-). Not all of them participate in the hours calculation the same way:
| Type | Evidence detection | Engine credits | Iteration filter |
|---|---|---|---|
Story (S-) | yes | yes, only status==Done | exact match (PI-2026-1.1) |
Feature (F-) | yes | yes, Done + gate transitions | PI match (PI-2026-1.x) |
Epic (E-) | yes | yes, Done + gate transitions | always (cross-PI) |
Initiative (I-) | yes | yes, Done + gate transitions | always (cross-PI) |
Defect (D-) | yes | yes, only status==Done | always (cross-PI) — intentional |
Task (T-) | yes | no — engine does not walk tasks/ | n/a |
T-XXX reference, the evidence is detected, but the engine assigns it no score — the work should be attached to the parent Story.6. Dual-view CW: two questions, one dataset
CW = 0.25 for a reviewer can mean two things. These are two different questions — the model solves both from the same data:
Question: How is person P's capacity distributed among their items?
Question: How is work on item X distributed among people?
| View | Question | Output | Guarantee |
|---|---|---|---|
| Per-person | How many hours did P spend on what? | Report, audit | Σ = capacity |
| Per-item | How many people and hours did item X cost? | Cost allocation | Σ shares = 100% |
7. Cadence configuration
| Cycle | Duration | 1.0 FTE | 0.5 | 0.25 |
|---|---|---|---|---|
| Iteration | 2 weeks | 80h | 40h | 20h |
| PI | 10 weeks | 400h | 200h | 100h |
| Cycle | Duration | 1.0 FTE | 0.5 | 0.25 |
|---|---|---|---|---|
| Iteration | 1 week | 40h | 20h | 10h |
| PI | 5 weeks | 200h | 100h | 50h |
8. Learning loop
- CW: After 2–3 iterations, evaluate the heuristic. Recalibrate signal weights with the
/edpa:calibrateoptimizer (Monte Carlo + coordinate descent, synthetic corpus). - Job Size: Reference Story “3” ≠ Feature “3”. Independent per level.
- AI: You report time for delivery, not minutes of coding. AI → velocity, not report.
9. GitHub implementation
9.1 Native Issue Types & custom fields
EDPA is local-first: the engine reads local git evidence and backlog YAML, and the GitHub layer is optional. If your team uses GitHub, Issue Types are a native GitHub feature at the organization level (not labels, not custom fields). Managed via issue_types.py setup --org <org>. Filter syntax: type:Epic, type:Story etc.
| Issue Type | Description |
|---|---|
| Initiative | Business case, investment proposal |
| Epic | Strategic goal, 6-9 months |
| Feature | Must fit within a Planning Interval |
| Story | Delivered within an iteration |
| Defect | Defect in existing functionality |
| Task | Technical work |
| Custom field | Type | Values |
|---|---|---|
| Job Size | Number | Mod. Fibonacci 1–100 |
| BV / TC / RR&OE | Number | Mod. Fibonacci 1–100 |
| WSJF Score | Number | Auto (Action) |
| Planning Interval | Iteration | 5 or 10 weeks |
| Iteration | Iteration | 1 or 2 weeks |
| Team | Single select | Team values |
| Primary Owner | Assignee | Accountable owner |
9.2 The engine runs locally; the GitHub layer is optional
V2 has no GitHub Actions pipeline. The engine, WSJF computation, iteration/PI close, and velocity all run locally via /edpa:close-iteration and MCP tools. Attribution is handled by a post-commit hook (local_evidence.py) — it emits commit_author for every commit that references an EDPA item, and works offline and on GitLab/Forgejo too. Hooks register into .git/hooks/, or — if the project uses lefthook — via a printed snippet for lefthook.yml.
| Layer | Where it runs | Function |
|---|---|---|
| Engine + calculation | Locally (/edpa:close-iteration) | Score, DerivedHours, snapshot + reports (MD/JSON/XLSX) + per-item |
| Attribution | Local post-commit hook | commit_author from git history for every item-referencing commit |
| Velocity | Locally (MCP edpa_flow_metrics) | Velocity and flow metrics from local data |
| Contribution sync (optional) | GitHub Action — --with-ci | edpa-contribution-sync.yml: after a PR merge, appends pr_reviewer + issue_comment from the PR thread into evidence[] |
--with-ci: only when you have signals that don't live in git history (PR reviews, comments) and want them counted. Single-dev / review-light / off-GitHub teams skip the workflow — local evidence is enough.9.3 Timestamp fields
Every backlog item carries three timestamp fields (written by the engine on status transitions; populated from the issue if the team uses GitHub):
| Field | Source | Usage |
|---|---|---|
created_at | Item creation | Cycle time start, item age |
updated_at | Last item change | Freshness, ordering |
closed_at | Transition to Done | Cycle time end, throughput |
9.4 Flow Metrics MCP tool
The edpa_flow_metrics MCP tool provides work flow analytics based on timestamp fields:
| Metric | Calculation | Usage |
|---|---|---|
| Cycle Time | closed_at - created_at | How long it takes to deliver an item |
| Throughput | Closed items count / iteration | Predictability, capacity planning |
| Item Age | now - created_at (open items) | Detecting stagnating items |
9.5 Branch naming & DoR
CI check blocks PRs without reference to an issue (S-XXX, F-XXX, E-XXX). DoR: Issue Type, Parent, Job Size, BV+TC+RR&OE, Owner.
9b. ID collisions and renumbering (multi-developer setup)
EDPA allocates IDs locally (no central coordinator) — if two developers concurrently create a backlog item from the same starting state of main, both get the same ID (e.g. both S-5). The collision surfaces at merge time of the second PR.
Four defense layers detect the collision as early as possible, plus a semi-automatic recovery tool:
| Layer | When | Tool | What it does |
|---|---|---|---|
| 5 | local git commit | validate_ids.py --staged (pre-commit hook) | Blocks commit if staged backlog has ID inconsistencies (filename ≢ id, missing fields, duplicates) |
| 6 | local git push | validate_ids.py --pre-push (pre-push hook) | Fetch origin, compare local-added IDs against origin/main. Blocks push if ID already exists upstream |
| 7 | PR open/sync (server-side) | .github/workflows/edpa-collision-check.yml | Runs renumber_collisions.py --check. On collision: posts a PR comment with fix instructions + fails the check (PR's merge button stays disabled) |
| Recovery | after conflict | renumber_collisions.py --apply | Renames the local file (S-5.md → S-6.md), rewrites id:, updates parent: refs in dependent items, bumps counter |
Standard recovery flow
Setup for a new project (one-time):
Full guide with decision tree and common collision shapes (single / multi / parent-chain / cascading): docs/dev-collisions.md.
10. Reports and audit
10.1 Pipeline
10.2 Freeze rule
After Iteration Close: snapshot is frozen. Evidence is not overwritten in-place. Every correction is a new revision. Essential for audit defense.
10.3 Audit principle
Provability rests on 5 pillars:
- Local git delivery evidence (commit_author, yaml_edit, gate_events, story_activity)
- Capacity registry (YAML)
- Frozen snapshot (reproducible input)
- Reproducible calculation (Score = JS × CW; CW = additive aggregation + per-item normalization)
- Signed output (BankID, law 21/2020 Sb.)
11. Assumptions and risks
- All items are closed (undelivered items are moved)
- Capacity confirmed at Iteration Planning
- Branch naming enforced (CI check)
- Job Size consistent per level
- CW is calibrated after the first iterations
| Risk | Mitigation |
|---|---|
| Auditor rejects | Methodology + snapshots + BankID |
| CW mismatch | Override + calibration |
| Commit without S-/F-/E-XXX | CI check blocks PR |
| PM/Arch without commits | Comments + /contribute |
| 0 items for a person | Process escalation |
12. Comparison with alternatives
Fixed Split
Pre-defined buckets (e.g., 60% Dev, 20% Arch, 20% QA). Hours are split fixedly by role, regardless of actual contribution. Simple, but inaccurate.
EDPA v2.11.1
Evidence-Driven Proportional Allocation. Hours are derived automatically from local git evidence (commits, yaml edits, status transitions, in-flight Story activity); GitHub Projects + Actions are an optional add-on. Mathematical guarantee: Σ = Capacity.
Manual timesheets
Each team member manually fills in hours. Subjective, administratively demanding, has no per-item view, unauditable without additional evidence.
| Property | Fixed Split | EDPA v2.11.1 | Manual timesheets |
|---|---|---|---|
| Fixed buckets | Yes | No | No |
| Empty levels | Problem | Do not exist | N/A |
| Per-person view | Yes | Yes (primary) | Yes |
| Per-item view | No | Yes (dual-view) | No |
| Cross-functional | Limited | Full | Full |
| Automation | Medium | High | None |
| Math. guarantee | Complex | Native | No |
13. Implementation plan
| Phase | Time | Contents |
|---|---|---|
| Day 1 | 1h | /edpa:setup — vendor the engine into .edpa/engine/, config (edpa.yaml, people.yaml), seed the backlog |
| Day 1 (optional) | 30 min | --with-hooks (git hooks), --with-ci (contribution-sync workflow), --with-rules |
| Iteration 1 | 1–2w | Pilot operation, first reports from /edpa:close-iteration |
| Calibration | 10 min | /edpa:calibrate — recalibrate signal weights after the first iterations |
| Retro PI 1 | 1 day | Cadence, CW accuracy, velocity, dual-view validation |