Skip to content

Analyst Time Tracking

zach115th edited this page Jul 20, 2026 · 1 revision

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.

Model

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.

15-minute increments

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.

Edit locking

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.

Logging time (UI)

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.

Optional nudge

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).

Reporting

Per-case

  • A Time tab on the case, and a Total time field on the case-edit modal, broken down by user and by task.

Cross-case (dashboard)

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).

Estimated case cost

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.

REST endpoints

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

Key files

  • models.pyCaseTimeEntry
  • datamgmt/case/case_time_db.pycase_time_summary() (+ cost calc)
  • business/case_time.py
  • business/dashboard_metrics.py::_time_tracking
  • blueprints/rest/v2/cases/time.py

Clone this wiki locally