-
-
Notifications
You must be signed in to change notification settings - Fork 0
Analyst Time Tracking
A low-overhead way to record investigation hours per case, designed so analysts enter as little as possible and reports stay correct as cases are reclassified or reassigned.
A time entry stores only:
- case
- analyst
- minutes
- date
- optional task or note reference
The reporting dimensions — customer, sector, incident type — are derived at report time by joining through the case. Analysts never tag them, and reports self-correct when a case is reclassified or reassigned.
Design rule: if a dimension already lives on the case, derive it at report time — don't make the analyst tag it.
Minutes are enforced to positive multiples of 15 by a CHECK(minutes > 0 AND minutes % 15 = 0) constraint on the model (CaseTimeEntry). The check lives on the
ORM __table_args__ because IRIS runs db.create_all() before Alembic, so a
migration-only constraint would be dropped.
Entries can be created, edited, and deleted only while the case is open
(close_date IS NULL). Closing a case locks its time entries; reopening unlocks
them.
A clock icon in the case header opens the time-log modal. The client JS is a static
file (ui/public/assets/js/iris/case.time.js) — no Vite build required to change it.
Gotcha: the case Summary tab hand-rolls its own
{% block javascripts %}instead of including the shared case footer, so any shared case-page JS must be added to that tab explicitly or it silently no-ops only on Summary.
An opt-in "you haven't logged time" nudge is available. It is off by default
(server_settings.time_tracking_nudge_enabled, toggled in /manage/settings).
- A Time tab on the case, and a Total time field on the case-edit modal, broken down by user and by task.
The Metrics tab has a Time tracking section with breakdowns by customer, person, case, sector, and incident type, plus CSV export.
- The card shows averages (total ÷ distinct cases in the bucket) so buckets with more cases aren't overstated; the CSV keeps raw totals (with Cases / Total / Avg columns).
Each user can carry an optional hourly_rate (nullable = unpriced), settable on the
user-edit Billing tab (admin) or self-service on the profile page.
Cost = Σ(analyst_minutes / 60 × rate). It surfaces on the case-edit Time tab (Est. cost badge + Rate/Cost columns) and the case-info read view. Unpriced analysts count as $0 but flag the estimate as partial — the number is never silently understated.
| Method | Path |
|---|---|
GET / POST
|
/api/v2/cases/<cid>/time-entries |
PUT / DELETE
|
/api/v2/cases/<cid>/time-entries/<entry_id> |
GET |
/api/v2/cases/time-nudge |
-
models.py—CaseTimeEntry -
datamgmt/case/case_time_db.py—case_time_summary()(+ cost calc) business/case_time.pybusiness/dashboard_metrics.py::_time_trackingblueprints/rest/v2/cases/time.py