anomaly_score_db
anomaly_match.prediction.anomaly_score_db
¶
SQLite-backed prediction result database with resume support.
Single predictions.db per prediction run, storing (filename, score)
pairs plus a run_metadata table used to validate that a resumed run
targets the same model / normalisation as the original run.
Schema (schema_version=4):
results(id, filename, score):scoreis the 32-bitfloatbit pattern as anINTEGER(0..4294967295) — non-negative floats keep their ordering as unsigned ints, soORDER BY scoreworks. The rowididgives insertion order (INSERT OR REPLACEbumps it), soORDER BY id DESCis "most recently written" with noupdated_atcolumn.run_metadata: flatkey → jsonstore for the schema version and the :meth:AnomalyScoreDB.validate_compatibilityfields.
Opening an older-schema DB raises :class:SchemaVersionError (pre-release
layout, not migrated).
SchemaVersionError
¶
Bases: RuntimeError
Raised when opening a predictions.db written by an older schema.
AnomalyScoreDB(db_path, *, checkpoint_every_n_commits=_DEFAULT_CHECKPOINT_EVERY_N_COMMITS, synchronous_full=False, read_only=False, busy_timeout_ms=_BUSY_TIMEOUT_MS)
¶
SQLite store for prediction results with resume support.
Concurrency contract¶
Uses WAL journal mode so the prediction subprocess (the single
writer) and the UI process (readers) can operate concurrently
without blocking. Implicit transactions use BEGIN IMMEDIATE
(see :class:sqlite3.Connection.isolation_level) — the RESERVED
lock is acquired at BEGIN time, before any work is done, so a
contending writer usually waits out busy_timeout (30 s) or
raises :class:sqlite3.OperationalError cleanly rather than
racing another writer mid-transaction. busy_timeout does not
cover a lock upgrade, though: a connection holding SHARED that
tries to take RESERVED while another already holds it gets
SQLITE_BUSY immediately. So read paths must issue only
SELECT — no temp tables, no DML — otherwise isolation_level
turns a "read" into a write transaction that contends for the single
writer slot (this bit :meth:get_unprocessed, which is why it is
SELECT-only).
Single writer per DB is the current invariant. IMMEDIATE +
busy_timeout is the primitive a future multi-writer
coordinator (issue #322) will extend: either a queue-based single
writer draining per-GPU workers, or per-worker DBs merged via
ATTACH. Either path drops in without schema change.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
db_path
|
str | Path
|
Path to the SQLite database file. Created if missing. |
required |
checkpoint_every_n_commits
|
int
|
Run |
_DEFAULT_CHECKPOINT_EVERY_N_COMMITS
|
synchronous_full
|
bool
|
If |
False
|
read_only
|
bool
|
If |
False
|
busy_timeout_ms
|
int
|
|
_BUSY_TIMEOUT_MS
|
Raises:
| Type | Description |
|---|---|
SchemaVersionError
|
If db_path exists and was written by an incompatible schema version. |
Source code in anomaly_match/prediction/anomaly_score_db.py
path
property
¶
Return the path to the database file.
Returns:
| Type | Description |
|---|---|
Path
|
Database file path. |
store_results(results)
¶
Batch insert or update prediction results.
Scores are rounded to fp32 precision before storage. Existing
rows with the same filename are replaced (INSERT OR REPLACE);
replacement assigns a new rowid so the replaced row moves to the
top of the updated_desc ordering.
The commit also bumps the last_heartbeat metadata key. WAL
growth is bounded passively by PRAGMA wal_autocheckpoint; every
checkpoint_every_n_commits commits an opportunistic
PRAGMA wal_checkpoint(TRUNCATE) additionally tries to reset the
.wal file to zero, which only succeeds when no reader holds it open.
The checkpoint runs outside the transaction — it cannot execute
inside one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
results
|
list[tuple[str, float]]
|
Sequence of |
required |
Raises:
| Type | Description |
|---|---|
DatabaseError
|
If the score write fails for a non-transient reason. A transient WAL-checkpoint lock from a concurrent reader is caught (the WAL stays bounded by autocheckpoint), not raised. |
Source code in anomaly_match/prediction/anomaly_score_db.py
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 | |
record_catalogues(source_to_catalogue)
¶
Record which catalogue file each scored source came from (#506).
Interns each distinct catalogue path into the catalogues table and
points the matching results rows at it via catalogue_idx, so a
later gallery load reads only the catalogue that holds a source instead
of scanning them all. Sources without a results row (not scored)
are silently ignored — only scored sources are ever browsed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_to_catalogue
|
dict[str, str]
|
Map from |
required |
Source code in anomaly_match/prediction/anomaly_score_db.py
get_catalogue_map(source_ids)
¶
Return the recorded catalogue path for each given scored source (#506).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_ids
|
list[str]
|
Source ids to look up. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Map from |
dict[str, str]
|
ids whose |
dict[str, str]
|
full catalogue scan in the loader). An empty input yields an empty |
dict[str, str]
|
map. |
Source code in anomaly_match/prediction/anomaly_score_db.py
set_metadata(key, value)
¶
Store a metadata key-value pair (JSON-serialised).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Metadata key (e.g. |
required |
value
|
Any
|
Arbitrary JSON-serialisable value. |
required |
Source code in anomaly_match/prediction/anomaly_score_db.py
set_metadata_batch(items)
¶
Store multiple metadata key-value pairs in one transaction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
dict[str, Any]
|
Mapping of metadata keys to JSON-serialisable values. |
required |
Source code in anomaly_match/prediction/anomaly_score_db.py
get_results(sort_by='score_desc', limit=100, offset=0)
¶
Query results with sorting and pagination.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sort_by
|
str
|
One of |
'score_desc'
|
limit
|
int
|
Maximum number of rows to return. |
100
|
offset
|
int
|
Number of rows to skip. |
0
|
Returns:
| Type | Description |
|---|---|
list[dict]
|
List of dicts with keys |
Raises:
| Type | Description |
|---|---|
ValueError
|
If sort_by is not a recognised sort mode. |
Source code in anomaly_match/prediction/anomaly_score_db.py
get_top_results(n)
¶
Convenience wrapper: return the n highest-scoring results.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Number of top results to return. |
required |
Returns:
| Type | Description |
|---|---|
list[dict]
|
List of result dicts sorted by descending score. |
Source code in anomaly_match/prediction/anomaly_score_db.py
get_count()
¶
Return total number of stored results.
Returns:
| Type | Description |
|---|---|
int
|
Row count of the results table. |
Source code in anomaly_match/prediction/anomaly_score_db.py
get_result_by_filename(filename)
¶
Look up a single result by its filename.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename
|
str
|
The source filename to look up. |
required |
Returns:
| Type | Description |
|---|---|
dict | None
|
Dict with keys |
dict | None
|
not found. |
Source code in anomaly_match/prediction/anomaly_score_db.py
get_all_scores()
¶
Return every stored score as a float32 numpy array.
Loads the entire score column into memory — not suitable for
databases with more than a few million rows.
Returns:
| Type | Description |
|---|---|
ndarray
|
1-D array of all scores. |
Source code in anomaly_match/prediction/anomaly_score_db.py
get_score_range()
¶
Return (min_score, max_score) across all results.
Returns:
| Type | Description |
|---|---|
tuple[float, float]
|
Tuple of minimum and maximum score values. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the database contains no results. |
Source code in anomaly_match/prediction/anomaly_score_db.py
get_score_histogram(bins=50)
¶
Compute a score histogram entirely in SQL.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bins
|
int
|
Number of histogram bins (default 50). |
50
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
ndarray
|
bin_edges has length |
tuple[ndarray, ndarray]
|
count 0. |
Source code in anomaly_match/prediction/anomaly_score_db.py
is_processed(filename)
¶
Check whether filename already has a stored result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename
|
str
|
Source filename to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in anomaly_match/prediction/anomaly_score_db.py
get_unprocessed(filenames)
¶
Filter filenames to only those without stored results.
SELECT-only (no temp tables); input order and any duplicate entries are preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filenames
|
list[str]
|
Candidate filenames to check. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
Subset of filenames that do not yet appear in the |
list[str]
|
results table. |
Source code in anomaly_match/prediction/anomaly_score_db.py
get_processed_filenames()
¶
Return the set of all processed filenames.
For very large databases this loads all filenames into memory.
Prefer :meth:get_unprocessed for incremental resume checks.
Returns:
| Type | Description |
|---|---|
set[str]
|
Set of filenames with stored results. |
Source code in anomaly_match/prediction/anomaly_score_db.py
validate_compatibility(cfg)
¶
Check whether cfg matches the stored run metadata.
Only keys listed in :data:_COMPAT_KEYS are compared. The
schema-version entry is always present (written on open) but is
not part of the compatibility check — a schema mismatch is
already rejected in :meth:__init__.
cfg is the prediction config; this method calls
:func:build_compat_metadata internally so all DB-related
metadata extraction lives next to the DB. Callers that already
hold a built dict can use :meth:set_metadata_batch directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cfg
|
DotMap
|
Prediction config with |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
str
|
mismatch found, or is empty on success. A fresh DB (no |
tuple[bool, str]
|
compat keys stored yet) is always compatible. |
Source code in anomaly_match/prediction/anomaly_score_db.py
get_metadata()
¶
Return all stored metadata as a dict.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Mapping of metadata keys to their JSON-deserialised values. |
Source code in anomaly_match/prediction/anomaly_score_db.py
get_heartbeat()
¶
Return the unix-epoch timestamp of the last successful commit.
Refreshed by :meth:store_results in the same transaction as
the scores. A stale value (more than a few minutes behind the
wall clock) means the writer subprocess is paused or crashed.
Returns:
| Type | Description |
|---|---|
int | None
|
Integer unix timestamp, or |
int | None
|
since the DB was created. |
Source code in anomaly_match/prediction/anomaly_score_db.py
close()
¶
Close the database connection.
Acquires _close_lock to wait for any in-flight
_safe_execute calls to finish, then nulls _conn so
subsequent reads return []. The actual conn.close()
runs after the lock is released — at that point no thread can
hold a reference to the connection.
On the way out:
PRAGMA wal_checkpoint(TRUNCATE)folds the WAL back into the main file so a reopened DB does not carry a*.walleftover from the last run.PRAGMA optimizelets SQLite regenerate statistics on indexes that have changed since they were last ANALYZEd.
A best-effort wrapper swallows per-pragma errors so a write lock held by something else (or a broken connection) can't block shutdown.
Source code in anomaly_match/prediction/anomaly_score_db.py
compute_model_sha256(path)
¶
Return the hex SHA-256 digest of a model checkpoint file.
Used by both the prediction subprocesses (when writing
model_sha256 into run_metadata) and the UI resume check
(when asking whether an existing DB is compatible with the
currently selected model). Content-hashing sidesteps the
fragility of comparing model_path strings — relative vs
absolute paths, symlinks, and cross-machine moves all collapse
into the same hash.
Streamed in 1 MiB chunks so a multi-GB checkpoint doesn't have to fit in memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Filesystem path to the checkpoint file. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Hex digest string (64 characters). |
Source code in anomaly_match/prediction/anomaly_score_db.py
build_compat_metadata(cfg)
¶
Build the run_metadata dict for a prediction config.
Centralises the model / normalisation fields that are stored in
run_metadata and consulted by
:meth:AnomalyScoreDB.validate_compatibility. model_sha256 (not
model_path) is the identity key the compatibility check uses:
two runs whose cfg.model_path strings differ but whose files
have the same bytes are compatible, and two runs whose strings
match but whose files differ are not.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cfg
|
DotMap
|
Prediction config with |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict[str, Any]
|
Metadata dict suitable for :meth: |
|
and |
dict[str, Any]
|
meth: |