Skip to content

Evidence Inventory

zach115th edited this page Jul 17, 2026 · 3 revisions

Evidence Inventory

Overview

The Inventory tab on /dashboard tracks the physical drives that hold digital evidence. A drive is the barcoded physical object; evidence items (CaseReceivedFile rows) are the logical files on it.

Key design decisions:

  • One drive → one current case (but reusable across a wipe-and-rotate lifecycle)
  • Wipe keeps evidence records (history must survive) — only the drive link is cleared
  • Lookup and management are open to any logged-in user (evidence-locker workflow)

Drive lifecycle

available ──► in_use ──► wiped ──► available   (rotate back into circulation)
                │
                └──► retired                   (permanently decommissioned)
  • available — in the evidence locker, not assigned to a case
  • in_use — assigned to an active case; date_assigned is stamped
  • wiped — sanitised and ready to reuse; date_wiped stamped, date_assigned cleared
  • retired — decommissioned; no further use

Status visualization

The summary card at the top of the Inventory tab shows a status donut chart + 4 stat tiles:

  • Available#2dce89 (green)
  • In use#5e72e4 (indigo)
  • Wiped#8898aa (grey)
  • Retired#4a5568 (dark grey)

Center overlay shows the total drive count. The card is hidden when no drives are registered.

If a data retention policy is configured and any in-use drives are overdue, an amber alert banner appears below the tiles:

"X drive(s) have exceeded the configured retention period — consider wiping and rotating them."

Data retention policy

Set at /manage/settingsStorage tab:

  • Retention period (months) — blank = no policy (no overdue indicators anywhere)
  • When set, _is_overdue(drive, retention_months, case_close_date) returns True when:
    • status == 'in_use'
    • The associated case has a close_date (open cases are never flagged overdue)
    • (utcnow - close_date).days > retention_months * 30

The clock starts at case closure, not at drive assignment — the intent is "how long has this drive been sitting around after the investigation finished."

Two UI indicators when a drive is overdue:

  1. Amber ⚠ Overdue badge inline next to the status chip in the drive table row
  2. The amber alert banner in the summary card

No indicator appears when the policy is NULL.

Barcode lookup

The Scan / enter barcode card at the top of the Inventory tab resolves a barcode to its drive, location, current case, and linked evidence items:

GET /api/v2/dashboard/inventory/lookup?barcode=<value>

Matching is exact first, then case-insensitive fallback.

Barcode auto-link in the Register Evidence modal

When typing a barcode in the evidence modal and tabbing away, the field fires a lookup automatically. If a drive is found:

  • The "On physical drive" picker auto-selects that drive
  • A green chip → <label> · <location> appears
  • An inline location editor is revealed — update the drive's location without leaving the modal

If no match: amber chip (non-blocking — the barcode may be new and not yet registered in Inventory).

Registering a drive

Click Register drive on the Inventory tab. Required field: Barcode (must be unique). Optional fields: Label, Serial number, Physical location, Capacity, Notes, Created by.

POST /api/v2/dashboard/inventory/drives
{
  "barcode": "EV-2026-0042",
  "label": "Seagate 4TB Evidence Drive",
  "serial_number": "ST4000LM024-12345",
  "physical_location": "Locker A / Shelf 3",
  "capacity": "4TB",
  "created_by": "Zachary Carter"
}

Editing a drive

Sparse update — only fields present in the body are touched:

PUT /api/v2/dashboard/inventory/drives/<id>
{
  "physical_location": "Locker B / Shelf 1"
}

Assigning a new case_id automatically flips status → in_use and stamps date_assigned (unless an explicit status is provided).

Wipe and rotate

POST /api/v2/dashboard/inventory/drives/<id>/wipe

Effect:

  • All case_received_file.drive_id FKs pointing to this drive → NULL (evidence rows kept)
  • drive.case_idNULL
  • drive.statusavailable
  • drive.date_wiped stamped, drive.date_assigned cleared

The drive is now available for the next case. Evidence history is preserved.

Linking evidence to a drive

Two paths:

  1. Barcode field in the Register / Edit Evidence modal — typing a barcode that matches a drive auto-links drive_id on save (_reconcile_evidence_drive in case_rfiles_db.py)
  2. "On physical drive" picker — explicit select2 dropdown; barcode is backfilled from the chosen drive if the barcode field is empty

barcode ↔ drive_id reconciliation rules (in priority order):

  1. Explicit drive_id always wins
  2. Typed barcode matching a drive → auto-link drive_id
  3. drive_id set + barcode empty → backfill barcode from the drive

Retroactive backfill: scripts/backfill_evidence_drive_links.py (dry-run default, --apply to commit).

Sortable column headers

All seven data columns in the Drive inventory table are clickable sort headers: Bar code / Label / Status / Physical location / Case / Capacity / Added

  • First click on a column → A→Z (ascending, ▲)
  • Click the same column again → Z→A (descending, ▼)
  • Click a different column → resets direction to A→Z on that column

Active sort column gets a violet ▲/▼ indicator; all other columns show a neutral ⇅.

Capacity sorts numericallyiris_inv_capacity_bytes() parses "4TB", "500GB", "256MB", etc. into byte values so "1 TB" correctly sorts after "500 GB" (plain string sort would invert them).

Case column sorts by the underlying case_id integer regardless of display name. Drives with no associated case sort last regardless of direction.

Sort state is in-memory only — clicking Refresh re-fetches from the server and resets to natural insertion order.

Adding a drive field

Six-touch pattern:

  1. Model column on EvidenceDrive in models/models.py
  2. Guarded Alembic migration (_table_has_column check)
  3. create_drive / update_drive loop + _serialize_drive in inventory_db.py
  4. Both REST endpoints (POST create + PUT update, body.get(...))
  5. Modal field + clear/populate/save JS in index.html (Inventory pane)
  6. Update the lookup result card if the field belongs there

No bundle rebuild needed — all inline JS + server-side. docker restart iriswebapp_app applies the migration and flushes the Jinja template cache.

API endpoints

Method Path Description
GET /api/v2/dashboard/inventory/drives List all drives (includes overdue: bool when policy is set)
POST /api/v2/dashboard/inventory/drives Register a new drive
PUT /api/v2/dashboard/inventory/drives/<id> Update drive (sparse)
DELETE /api/v2/dashboard/inventory/drives/<id> Delete drive (evidence rows survive, drive_id → NULL)
POST /api/v2/dashboard/inventory/drives/<id>/wipe Wipe and return to circulation
GET /api/v2/dashboard/inventory/lookup?barcode= Resolve barcode → drive + case + evidence items

GET drives response includes overdue: bool on each drive object when a retention policy is configured. Clients that display a status should check this field.

Scripts

Script Purpose
backfill_evidence_drive_links.py Retroactively link evidence rows to drives via barcode matching (--apply to commit)

Planned / Backlog

Drive capacity planning — order-more indicator. A planned addition to the Inventory tab summary card that surfaces a drive-ordering recommendation based on current inventory vs projected demand.

How it works:

  • Average case intake — rolling window of Cases.open_date over recent months
  • Effective 30-day supply — available drives + in-use drives whose case closed within the retention window (wipe-eligible within 30 days)
  • Runway = effective supply ÷ avg cases/month
  • When runway drops below the configured threshold (default 2 months), the card shows an order recommendation with a suggested quantity

No new tables or migrations needed — all required data is already fetched in list_drives().

Configuration lives alongside retention_months in /manage/settings → Storage tab.

Clone this wiki locally