Skip to content

Audit trail

session.store_backend is the session's SQLite store. The methods below read its audit rows and its flattened instance table. They are documented but internal (see API stability): the words in the rows are frozen, and these methods that return them may change in a 1.x release. What each action_type means is on Analytics & Reporting.

SqliteStore

The session's SQLite store and pixel sidecar: session.store_backend.

It holds the Patient -> Study -> Series -> Instance hierarchy, the append-only <name>_pixels.bin sidecar, and the audit log, which a background thread writes.

get_audit_summary()

Returns an aggregated summary of actions from the audit log.

Reads through the flush_audit_queue barrier, so every row enqueued before the call is counted.

Returns:

Type Description
Dict[str, int]

e.g., {'REMEDIATION_REPLACE': 1200, 'EXPORT': 1}

get_audit_errors()

Every ERROR and WARNING audit row, oldest first.

Flushes the audit queue first, so every row enqueued before the call is included.

Returns:

Type Description
List[tuple]

(timestamp, action_type, details) per row; empty if the query fails.

get_audit_losses()

Retrieves every DATA_LOSS entry, with the scope it was recorded under.

Separate from get_audit_errors because the scopes grade differently: a loss scoped PRIVATE or SIGNAL takes validation_status to REVIEW_REQUIRED; one scoped STANDARD leaves it at PASS. Every loss is reported under "3.1 Data Loss", never under "Exceptions & Errors".

A row whose loss_scope is NULL predates the column and cannot be graded; it is reported and left at PASS.

Returns:

Type Description
List[tuple]

(timestamp, entity_uid, details, loss_scope)

get_audit_declines()

Every REMEDIATION_DECLINED entry: a value the sweep left behind.

A scan gap says an element could not be read; this says an element was read, a remediation was proposed for it, and the remediation did not run, so the value is still in the graph and will reach the exported file. The reason is in details.

Flushes first, like every other audit reader, so a row still in the queue is counted.

Returns:

Type Description
List[tuple]

(timestamp, entity_uid, details)

get_audit_scan_gaps()

Every SCAN_GAP entry: an element the PHI scan could not open.

Separate from get_audit_losses because it is a different claim. A loss says an element was dropped at ingest and cannot reach the output; this says an element was kept whole and the scan could not read what is inside it.

The row states ingest-time knowledge only. Whether the element reaches the exported file is decided later, by remove_private_tags, and generate_report resolves that against the object graph.

No loss_scope column: only an odd-group tag reaches the parse gate, so these are private by construction. element_tag is selected instead, and is NULL for a row written before that column existed.

Returns:

Type Description
List[tuple]

(timestamp, entity_uid, details, element_tag)

get_audit_drops()

How many audit rows were dropped by a failed batch write.

The rows themselves are unrecoverable: a failed batch write loses them. A non-zero count means the audit table under-states what happened, and generate_report grades it like an exception.

Flushes first, like every other audit reader, so a row still in the queue is counted if its write fails.

Returns:

Type Description
int

Rows dropped over this store object's lifetime.

get_flattened_instances(patient_ids=None, instance_uids=None, page_size=_FLATTENED_PAGE_SIZE)

An iterator of one flat dictionary per stored instance.

For streaming exports or analysis without loading the entire graph into RAM. The rows come back one page at a time, and no database handle is held between pages, so an iterator left part-consumed holds no lock and no read snapshot. Two consequences:

  • Iteration is not one snapshot. Each page is its own query, so writes that land between pages are visible and rows deleted between pages are not returned.
  • Order is by instances.id.

Parameters:

Name Type Description Default
patient_ids Iterable[str]

Restrict the rows to these Patient IDs, read as every patient_ids in the package is read. None means every patient in the store. An empty iterable matches nobody: it is a filter that selected nothing, not an absent filter. An iterator is read once, at the call. An ID no patient holds is not counted here, unlike the Session methods: the pages are separate reads, so there is no single moment at which an ID is or is not held.

None
instance_uids Iterable[str]

Restrict the rows to these SOP Instance UIDs. Same rules: None is no filter, an empty iterable matches nobody. Both filters together intersect.

None
page_size int

Rows per page, defaulting to 500. Trades resident memory against the number of queries. Must be an int >= 1.

_FLATTENED_PAGE_SIZE

Returns:

Type Description
Iterator[dict]

One dict per instance, keyed patient_id, patient_name, study_instance_uid, study_date, series_instance_uid, modality, series_number, manufacturer, model_name, device_serial_number, sop_instance_uid, sop_class_uid, instance_number, file_path, pixel_offset, pixel_length, compress_alg and attributes_json, with the values as stored.

Raises:

Type Description
ValueError

If page_size is not a whole number >= 1, at the call, not on the first next().

TypeError

If either filter is a bare str, bytes-like, not iterable, or holds an element that is not a str (named by position and type, never by value), at the call. page_size is checked first.