1. Summary

Time is not measured, it is derived. Nobody logs hours. Nobody fills out timesheets.

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

TermDefinitionConfiguration
IterationDelivery cycle. Stories are planned, delivered, and closed.1w (AI-native) / 2w (classic)
Planning IntervalPlanning cycle. Features are planned, coordinated, and evaluated.5w (4+1 IP) / 10w (8+2 IP)
IP IterationInnovation & 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
WSJFPrioritization score: (BV + TC + RR&OE) / JSIndependent per level
CWContribution Weight — per-item normalized share of a person on an item (Σ across persons = 1.0 per item).0–1.0
Signal WeightWeight 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 HoursDerived hours, model output.After Iteration Close
Signal Weight → Contribution Score (Σ fired signal weights) → CW (per-item normalized share) → Derived Hours.

3. Model architecture

3.1 Three separate layers

LayerPurposeWhere it lives
Operational MetadataLive delivery dataGitHub Issues + Projects
Capacity RegistryPeople capacity, roles, FTEYAML / JSON in repo
Evidence & ReportingFrozen 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

Initiative (entire project, business case) └── Epic (strategic goal, 6-9 months) └── Feature (must fit within a Planning Interval) ├── Story (delivered in an Iteration) │ └── Task (technical work, optional) └── Defect (fix, at the Story level)
Initiative
Epic
Feature
Story
Defect
Task

Each level has its own independent Job Size and WSJF. Feature WSJF is not computed from Stories below it.

LevelJob Size rangeGitHub mapping
Initiative1–100 (mod. Fibonacci)Issue Type: Initiative
Epic1–100 (mod. Fibonacci)Issue Type: Epic
Feature1–100 (mod. Fibonacci)Issue Type: Feature
Story1–100 (mod. Fibonacci)Issue Type: Story
Defect1–100 (mod. Fibonacci)Issue Type: Defect
Task— (sub-Story checklist)Issue Type: Task
Modified Fibonacci scale (1, 2, 3, 5, 8, 13, 20, 40, 100) per WSJF in SAFe 6. No fixed per-level caps — granularity is the team's responsibility during refinement. With AI-assisted work even a Story at JS=20+ can realistically ship within an iteration.
WSJF: (BV + TC + RR&OE) / JS BV = Business Value, TC = Time Criticality, RR&OE = Risk Reduction Computed per level independently.
BV — Business Value
TC — Time Criticality
RR&OE — Risk Reduction & Opportunity Enablement
JS — Job Size

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.

  1. Confirm Capacity — Each team member confirms availability. This is a commitment, not an estimate. External collaborators negotiate allocation explicitly. Result: availability: confirmed in people.yaml.
  2. 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. The capacity_per_iteration baseline stays; the engine applies the override and reports capacity_baseline + capacity_override. Set before closing the iteration (Stage 1 of /edpa:close-iteration).
  3. Calculate Planning CapacityPlanning_Capacity = Σ Capacity[P, I] × planning_factor. The planning_factor is a team-level property (configured per team in people.yaml under teams:). Default: 0.8.
  4. Select Work — Pull stories from the prioritized backlog (WSJF order) until Σ JobSize approaches historical velocity × planning_factor. Do not plan to 100%.
  5. Buffer — The remaining ~20% absorbs support, maintenance, incidents, and unplanned work. If buffer items generate evidence, EDPA allocates them normally.
  6. Edge case — If no unplanned work occurs, all capacity goes to planned items. The guarantee Σ = Capacity holds regardless.
Why 80%? Planning to 100% forces one of three failure modes: undelivered stories, overwork, or scope creep. The 80% heuristic aligns with SAFe load factor, Scrum velocity-based planning, and Kanban WIP limits. Different teams may choose different factors based on their support load and maturity.

5.2 Inputs

InputSourceExample
Capacity[P, I]Confirmed at Iteration Planning40h
RelevantItems[P, I]Automatic from GitHub evidence (Story Done + Feature/Epic/Initiative gates)6 items across 4 levels
JobSize[item]Custom field on issueMod. Fibonacci 1–100
cw[P, item]detect_contributors.py — additive aggregation + per-item normalization0–1.0 (Σ on item = 1.0)

5.3 Evidence detection

GitHub signalSignal weightDetection
Commit author4.00Commit with S-/F-/E-/I-XXX in branch/message
PR reviewer2.17Submitted PR review (excluding self)
Issue / PR comment1.46Comment (bots excluded)
/contribute @person weight:XexplicitManual additive signal — weight X from the directive
  • Signals aggregate additively into contribution_score per (person, item) — no "highest wins"
  • Per-item normalization yields the cw share: cw = contribution_score / Σ_persons contribution_score
  • /contribute is a peer signal — it does not override auto-detection, it adds weight
  • Default weights above are calibrated by Monte Carlo simulation (1000 synthetic scenarios)
No per-role corrections. CW is computed purely via additive signal aggregation and per-item normalization (see § 5.4 below). Strategic-role bias is handled by calibrating signal weights against ground truth — if BO/PM/Arch are under-credited, the calibrator increases issue_comment and pr_reviewer weights. See auto-calibration.md.

5.4 Calculation — single source, sum-and-normalize

contribution_score[P, item] = Σ signal_weight × signal_fired(P, item) cw[P, item] = contribution_score[P, item] / Σpersons contribution_score[*, item] score[P, item] = JobSize[item] × cw[P, item] ratio[P, item] = score[P, item] / Σitems_of_P score DerivedHours[P, item] = ratio[P, item] × Capacity[P, I]
CW is a per-item share (Σ across persons = 1.0 per item), not an absolute role-coupled value. Signals aggregate additively, normalize per item, and the engine consumes ready-made cw.
Worked example: Calculator. Full spec: methodology.md § 5.4.

5.5 Mathematical guarantees (two)

1. Per-item: Σpersons cw[*, item] = 1.0 2. Per-person: Σitems DerivedHours[P, *] = Capacity[P, I] Both hold structurally — by construction of the sum-and-normalize aggregation and per-person ratio normalization across iteration items. Engine validates them at runtime and refuses to write the snapshot on violation.

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:

TypeEvidence detectionEngine creditsIteration filter
Story (S-)yesyes, only status==Doneexact match (PI-2026-1.1)
Feature (F-)yesyes, Done + gate transitionsPI match (PI-2026-1.x)
Epic (E-)yesyes, Done + gate transitionsalways (cross-PI)
Initiative (I-)yesyes, Done + gate transitionsalways (cross-PI)
Defect (D-)yesyes, only status==Donealways (cross-PI) — intentional
Task (T-)yesno — engine does not walk tasks/n/a
Cross-PI defects are intentional. A defect may not get fixed within the iteration where it was opened — it can spill across a PI boundary. The engine therefore credits it whenever it transitions to Done, with no origin filter. This captures the actual lifetime of the defect as evidence.
Tasks are not deliverables in the EDPA model. Tasks are intended as sub-Story breakdown (checklist on an issue), not standalone items for hour calculation. If a team commits with 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:

Per-person normalization

Question: How is person P's capacity distributed among their items?

DerivedHours[P, item] = (Score[P, item] / Σ Score[P, *]) × Capacity[P, I] Guarantee: Σ DerivedHours[P, *] = Capacity

Output: report per person for audit

Per-item normalization

Question: How is work on item X distributed among people?

ItemShare[P, item] = DerivedHours[P, item] / Σ DerivedHours[*, item] Guarantee: Σ shares per item = 100 %

Output: cost card per deliverable

ViewQuestionOutputGuarantee
Per-personHow many hours did P spend on what?Report, auditΣ = capacity
Per-itemHow many people and hours did item X cost?Cost allocationΣ shares = 100%

7. Cadence configuration

Variant A: Classic (2/10)
CycleDuration1.0 FTE0.50.25
Iteration2 weeks80h40h20h
PI10 weeks400h200h100h
Variant B: AI-Native (1/5)
CycleDuration1.0 FTE0.50.25
Iteration1 week40h20h10h
PI5 weeks200h100h50h
Recommendation: start with A. After the first PI, evaluate transition to B based on velocity and lead time data.

8. Learning loop

Velocity tracking
Story_Velocity = Σ JS of closed Stories / iteration Feature_Velocity = Σ JS of closed Features / PI Accuracy = Actual / Planned × 100 %
Calibration
  • CW: After 2–3 iterations, evaluate the heuristic. Recalibrate signal weights with the /edpa:calibrate optimizer (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 TypeDescription
InitiativeBusiness case, investment proposal
EpicStrategic goal, 6-9 months
FeatureMust fit within a Planning Interval
StoryDelivered within an iteration
DefectDefect in existing functionality
TaskTechnical work
Enabler is a label classification (Business vs Enabler), not an Issue Type. An Epic can be labeled “Enabler” to mark it as an Enabler Epic (SAFe).
Custom fieldTypeValues
Job SizeNumberMod. Fibonacci 1–100
BV / TC / RR&OENumberMod. Fibonacci 1–100
WSJF ScoreNumberAuto (Action)
Planning IntervalIteration5 or 10 weeks
IterationIteration1 or 2 weeks
TeamSingle selectTeam values
Primary OwnerAssigneeAccountable owner
What not to keep as a GitHub field: Capacity, Derived Hours, FTE, signature state → belong to the evidence layer.

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.

LayerWhere it runsFunction
Engine + calculationLocally (/edpa:close-iteration)Score, DerivedHours, snapshot + reports (MD/JSON/XLSX) + per-item
AttributionLocal post-commit hookcommit_author from git history for every item-referencing commit
VelocityLocally (MCP edpa_flow_metrics)Velocity and flow metrics from local data
Contribution sync (optional)GitHub Action — --with-ciedpa-contribution-sync.yml: after a PR merge, appends pr_reviewer + issue_comment from the PR thread into evidence[]
When to enable --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):

FieldSourceUsage
created_atItem creationCycle time start, item age
updated_atLast item changeFreshness, ordering
closed_atTransition to DoneCycle time end, throughput
These fields feed the flow metrics (§ 9.4). No bidirectional sync and no conflict resolution — the single source of truth is the local YAML in the repo.

9.4 Flow Metrics MCP tool

The edpa_flow_metrics MCP tool provides work flow analytics based on timestamp fields:

MetricCalculationUsage
Cycle Timeclosed_at - created_atHow long it takes to deliver an item
ThroughputClosed items count / iterationPredictability, capacity planning
Item Agenow - created_at (open items)Detecting stagnating items

9.5 Branch naming & DoR

feature/S-200-omop-parser defect/D-215-upload-validation feature/F-102-anon-engine

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:

LayerWhenToolWhat 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

git fetch origin python3 .edpa/engine/scripts/renumber_collisions.py --apply # Detected 1 collision: S-5 → S-6 git add . && git commit -m "renumber(S-5→S-6): collision with main" git merge origin/main # resolve id_counters.yaml conflict (take MAX value) git push

Setup for a new project (one-time):

python3 .edpa/engine/scripts/project_setup.py --with-hooks cp .edpa/engine/templates/github-workflows/edpa-collision-check.yml \ .github/workflows/

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

/.edpa/snapshots/ iteration-PI-2026-1.3.json ← frozen snapshot /.edpa/reports/ iteration-PI-2026-1.3/ report-urbanek.md ← human-readable report report-urbanek.json ← machine data summary.xlsx ← summary Excel item-costs.xlsx ← per-item view /signed/ PI-2026-1.3-urbanek.pdf ← BankID signed

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:

  1. Local git delivery evidence (commit_author, yaml_edit, gate_events, story_activity)
  2. Capacity registry (YAML)
  3. Frozen snapshot (reproducible input)
  4. Reproducible calculation (Score = JS × CW; CW = additive aggregation + per-item normalization)
  5. Signed output (BankID, law 21/2020 Sb.)

11. Assumptions and risks

Assumptions
  • 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
Risks
RiskMitigation
Auditor rejectsMethodology + snapshots + BankID
CW mismatchOverride + calibration
Commit without S-/F-/E-XXXCI check blocks PR
PM/Arch without commitsComments + /contribute
0 items for a personProcess 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.

PropertyFixed SplitEDPA v2.11.1Manual timesheets
Fixed bucketsYesNoNo
Empty levelsProblemDo not existN/A
Per-person viewYesYes (primary)Yes
Per-item viewNoYes (dual-view)No
Cross-functionalLimitedFullFull
AutomationMediumHighNone
Math. guaranteeComplexNativeNo

13. Implementation plan

PhaseTimeContents
Day 11h/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 11–2wPilot operation, first reports from /edpa:close-iteration
Calibration10 min/edpa:calibrate — recalibrate signal weights after the first iterations
Retro PI 11 dayCadence, CW accuracy, velocity, dual-view validation