feat(data): dataset ingestion + detection evaluation (#35) [WIP] - #146
Draft
Jovonni wants to merge 5 commits into
Draft
feat(data): dataset ingestion + detection evaluation (#35) [WIP]#146Jovonni wants to merge 5 commits into
Jovonni wants to merge 5 commits into
Conversation
) First slice of the Data Ingestion & Testing work, on existing rails: - core/services/detection_eval.py: pure, dependency-light entity-level scoring (precision/recall/F1/FPR + per-scenario recall) shaped for ExperimentRun.metrics - anomalies read-by-run: AnomalyRepository.list_all(run_id=...), a run_id query param on GET /api/v1/anomalies, and query_anomalies(run_id=) in the SDK — the read path evaluation needs to score a single run's output - tests: 10 unit tests for the scorer (no infra) + a repo test for the run_id filter
Replace the hard toy_1-only whitelist in ingest_all_datasets with a 'looks like a dataset' check (directory with >=1 log-type subdir). toy_1 behavior is unchanged; additional datasets now fan out to their <name>_<logtype> Spark tables + openuba-<logtype>-<name> ES indices with no code change. Junk/hidden dirs and stray files are still skipped. Tests: dataset-scan unit tests (temp tree, mocked ingest — no infra).
- POST /api/v1/evaluate/run: reads a run's anomalies (by run_id) and scores them with the shared core.services.detection_eval scorer; returns JSONB- friendly metrics ready for ExperimentRun.metrics - SDK: openuba.evaluate_run(run_id, malicious_entities, ...) + query_anomalies now accepts run_id - tests: API test (seed run+anomalies, POST, check metrics) + 3 SDK unit tests
- inject an admin caller (override get_current_user) so the evaluation API test exercises the real require_permission path and returns 200 - pass the required slug to ModelRepository.create in the new tests - docs/DATASETS_AND_EVALUATION.md: adding datasets, ground truth, and evaluating a run (precision/recall/f1) via the SDK, reusing experiments
This branch touches sdk/, which triggers the workspace image build; it fails generating pyspark metadata without modern build tooling. Same fix already validated on the alerts branch.
There was a problem hiding this comment.
Pull request overview
Adds end-to-end support for evaluating a model run’s anomaly detections against ground-truth entity labels (issue #35), while expanding dataset ingestion to automatically pick up additional captures under test_datasets/ without code changes.
Changes:
- Introduces a dependency-light, entity-level detection scoring service (precision/recall/F1/FPR + per-scenario recall) and corresponding unit tests.
- Adds run-scoped anomaly listing across repository/API/SDK, plus a new
/api/v1/evaluate/runendpoint and SDK surface to score a run. - Broadens dataset ingestion discovery from a
toy_1whitelist to “looks like a dataset” directory scanning, with docs and tests.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk/tests/test_evaluate.py | Adds SDK tests for evaluate_run payload + run_id anomaly scoping. |
| sdk/src/openuba/client.py | Extends anomaly query with run_id and adds evaluate_run SDK method. |
| sdk/src/openuba/init.py | Exposes evaluate_run and run_id-scoped query_anomalies at module level. |
| docs/DATASETS_AND_EVALUATION.md | Documents dataset layout, ingestion behavior, and run evaluation workflow. |
| docker/workspace/Dockerfile | Upgrades build tooling to support sdist/isolated-build installs. |
| core/tests/test_services/test_detection_eval.py | Unit tests for entity-level detection evaluation math. |
| core/tests/test_services/test_data_ingestion_scan.py | Tests dataset directory scanning behavior for ingestion fan-out. |
| core/tests/test_repositories.py | Adds repository test for AnomalyRepository.list_all(run_id=...). |
| core/tests/test_api_routers/test_evaluation.py | API tests for POST /api/v1/evaluate/run. |
| core/services/detection_eval.py | New scoring implementation for entity-level metrics. |
| core/services/data_ingestion.py | Relaxes dataset ingestion from strict whitelist to dataset-shape discovery. |
| core/repositories/anomaly_repository.py | Adds run_id filter to anomaly listing. |
| core/fastapi_app.py | Registers the new evaluation router. |
| core/api_routers/evaluation.py | New evaluation endpoint that reads a run’s anomalies and scores them. |
| core/api_routers/anomalies.py | Adds run_id query parameter to anomalies listing endpoint. |
| .gitignore | Ignores .venv/. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+610
to
+612
| def query_anomalies(self, entity_id=None, model_id=None, run_id=None, | ||
| min_risk=None, max_risk=None, limit=1000): | ||
| '''query anomalies from the platform (optionally scoped to one run)''' |
Comment on lines
+269
to
272
| def query_anomalies(entity_id=None, model_id=None, run_id=None, min_risk=None, | ||
| max_risk=None, limit=1000): | ||
| """Query anomalies from the platform.""" | ||
| """Query anomalies from the platform (optionally scoped to one run).""" | ||
| return _get_client().query_anomalies(entity_id=entity_id, model_id=model_id, |
Comment on lines
+45
to
+65
| repo = AnomalyRepository(db) | ||
| rows = repo.list_all(run_id=body.run_id, limit=100000) | ||
| anomalies = [ | ||
| { | ||
| "entity_id": r.entity_id, | ||
| "risk_score": float(r.risk_score) if r.risk_score is not None else 0.0, | ||
| "anomaly_type": r.anomaly_type, | ||
| } | ||
| for r in rows | ||
| ] | ||
| result = evaluate_detections( | ||
| anomalies=anomalies, | ||
| malicious_entities=body.malicious_entities, | ||
| all_entities=body.all_entities, | ||
| threshold=body.threshold, | ||
| scenarios=body.scenarios, | ||
| ) | ||
| logger.info( | ||
| f"evaluated run {body.run_id}: precision={result.precision} " | ||
| f"recall={result.recall} over {len(anomalies)} anomalies" | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft / WIP for #35 (Data Ingestion & Testing). Building this on OpenUBA's existing rails — deliberately not inventing a parallel workflow.
Background
#35 asked for sample data + a way to test detections. The thread's real blocker (anupamme): the data has no labels, so you can't evaluate whether a model works. This delivers the missing evaluation capability and makes additional datasets first-class.
Design was hardened by auditing the code first, which corrected several early assumptions: models featurize raw logs themselves (no pre-aggregation), the canonical run path is ES/Spark (not local_csv), ground truth should reuse
UserFeedback, and metrics belong inExperimentRun.metrics— so this reuses those instead of rebuilding them.Phase A (this PR so far) — all tested
core/services/detection_eval.py): pure, dependency-light entity-level precision/recall/F1/FPR (+ per-scenario recall), shaped forExperimentRun.metrics.AnomalyRepository.list_all(run_id=…), arun_idparam onGET /api/v1/anomalies, andquery_anomalies(run_id=)in the SDK (the read evaluation needs).POST /api/v1/evaluate/run(reads a run's anomalies, scores them) andopenuba.evaluate_run(run_id, malicious_entities, …).toy_1-only whitelist iningest_all_datasetswith a "looks like a dataset" check — additional captures undertest_datasets/now fan out to their Spark tables + ES indices with no code change (toy_1 unchanged).docs/DATASETS_AND_EVALUATION.md.Tests (18, green locally; run in CI)
Phase B (follow-up in this branch)
test_datasets/with injected known-bad entities + aground_truth.json.Datasetrows (none are seeded today) so shipped datasets show on/datasets.09_model_pipeline.ipynbwith an eval cell) and a CLIdatasetgroup.Also fixes two real bugs found while mapping:
jobs.pylocal_csv missingfile_name, and the deadsource_groupLocalPandasLoaderimport (to be addressed in Phase B).Closes #35