---
name: evolution_timesheets_duration_clamped
description: "timesheets.duration is float(5,4) clamped at 9.9999 — use durations (seconds) for actual hours"
metadata: 
  node_type: memory
  type: project
  originSessionId: 6c3711de-c67f-4c4c-ba73-9526f098f2f6
  modified: 2026-07-20T06:40:35.919Z
---

`timesheets.duration float(5,4)` maxes out at **9.9999**, so any shift ≥10h is silently clamped on write — summing it undercounts actual hours (a project can then show actual < billable, which is nonsense).

**Use the unclamped seconds column instead.** Reliable actual-hours expression (also covers legacy pre-`durations` rows):

```sql
(CASE WHEN timesheets.durations > 0 THEN timesheets.durations ELSE ROUND(timesheets.duration * 3600) END) / 3600
```

Time columns: `durations`=elapsed seconds (int, reliable), `billables`=billable seconds (int), `billable`=billables/3600 hours float(10,4), `duration`=(end−start)/3600 hours float(5,4) **clamped**.

Also quarantine corrupt rows in report SUMs: `end < start OR durations < 0 OR billable < 0` (e.g. dev tenant row 594 has end<start).

**Fixed 2026-07-20** across 8 reports + migration `migrations/timesheets_duration_decimal.sql` (widens duration→decimal(10,4), backfills from durations, skips corrupt rows). Full writeup: `evolution/audits/TIMESHEET_MONTHLY_BILLING_AUDIT.md`. Billable value = `SUM(timesheets_items.qty × jobInventory.price)`; cost = `SUM(timesheets.totalcost)`. See [[evolution_timesheets_invoiced_column]].

**Root cause of the corrupt rows = unguarded `strtotime()`** on the clock-out: a blank/malformed end returns `false`, coerces to 0, writes negative/clamped time (end=0 or INT_MIN -2147483648). Guarded 2026-07-20 in evolution `app/modules/timesheetEntry.php` + `timesheetTimer.php`, and portal `library/manager/TimesheetManager.php` + `api/timesheet.php` (portal client check was bypassable, server had none). Existing bad rows: review/void per-tenant via `migrations/timesheets_corrupt_rows_cleanup.sql` (found in 3 live tenants incl. ESS — a quad-submit on job 34); no recoverable finish time so don't fabricate hours.
