Ir al contenido

ADR-011 — Weekly-Only Hours Computation

Accepted

The Hours Ledger service previously supported three period strategies — WEEKLY, MONTHLY_FIXED (scaling factor 4.0), and MONTHLY_CALENDAR (scaling factor = days_in_month / 7) — plus a separate PeriodType enum on assignments (WEEKLY / MONTHLY) with cross-period scaling logic to bridge mismatches between contracts and assignments.

This created three problems:

  1. Divergent enums: PeriodStrategy (3 values on contracts) and PeriodType (2 values on assignments) were semantically related but structurally different, requiring a scaling bridge in calculate_balance().
  2. Arbitrary monthly definitions: MONTHLY_FIXED assumed 4 weeks/month; MONTHLY_CALENDAR computed actual days/7. Neither matched a real-world standard — they were implementation artifacts.
  3. Conflict with Argentine labor law: The LCT (Ley de Contrato de Trabajo 20.744, Articles 196-201; see [[adr-021-argentine-legal-context|ADR-021]]) defines labor hour obligations in weekly units. Jornada completa = 48h/week. Jornada insalubre = 36h/week. Overtime is computed per week. The legal unit of measurement is the week, not the month.

The “month” in hospital operations (e.g., “anesthesiologists rotate monthly”) is a scheduling pattern, not a unit of hour measurement. Monthly views (budgets, payroll) are aggregations of weekly data, not distinct computation strategies.

Weekly-only computation. All hours computation is weekly. The ledger resolves every reference date to its ISO week (Monday–Sunday) and computes balance against base_weekly_hours directly, with no scaling factor. resolve_period() takes only a reference_date and returns a ComputePeriod(start_date, end_date) — always Monday to Sunday. calculate_balance() sums effective_hours directly with no cross-period scaling bridge.

Period enums and fields removed. The PeriodStrategy enum (was on ContractTemplate) and the PeriodType enum (was on Assignment) are removed, along with their corresponding fields ContractTemplate.period_strategy and Assignment.period_type. These were implementation artifacts of the multi-strategy approach and have no place in a weekly-only model.

Monthly views are read-time aggregations. Monthly views (budgets, payroll reports) are computed at read time by summing N weekly balances for the weeks that fall within a given month. This is a presentation concern, not a computation strategy.

[!note] ContractTemplate was later replaced by tags [[adr-019-tag-based-requirements|ADR-019]] superseded ContractTemplate (and its base_weekly_hours) with the TagCatalog + EmployeeTag model. The weekly-only decision here still holds: the weekly hour pool is now max(0, Σ hours_delta) over active tags, resolved to ISO weeks exactly as described above.

  • The hours computation pipeline is simpler: no branching by strategy, no scaling factors, no cross-period bridge. resolve_period() went from ~30 lines to ~3.
  • ContractTemplate has only two meaningful fields: name and base_weekly_hours. Simpler to create, validate, and reason about.
  • Assignment.effective_hours always means “hours per week for this assignment.” No ambiguity.
  • Monthly aggregation endpoints can be added when the Coverage or Reports module requires them, as pure read-time operations. They are not in MVP scope.
  • Database migration removed both columns. Existing seed data is unaffected (fields were informational, not used in critical paths beyond the now-simplified service).
  • API contract updated: period_strategy removed from contract template endpoints; period.strategy and period.scaling_factor removed from balance response.