feat(watches): anomaly watches through the alerts SPI, contingent pickers #9

Merged
KofTwentyTwo merged 10 commits from feature/watches into develop 2026-10-14 12:09:48 +00:00
KofTwentyTwo commented 2026-10-14 01:34:24 +00:00 (Migrated from github.com)

Summary

Phase (e) of docs/DATA-ANALYSIS.md (§7.6) plus contingent possible values. Stacked on #8 → #6 → #5. Draft until those merge after RC.6.

  • Watches (data_analysis_watch) watch either a saved analysis or a table, filter and metric (COUNT, SUM or AVG of a field, the null share, or the category set) per HOUR, DAY, WEEK or MONTH bucket.
  • Detectors (pure functions):
    • SPIKE / DROP: robust z from the median and MAD of the last k buckets, falling back to relative change when MAD = 0;
    • OUT_OF_RANGE;
    • NEW_CATEGORY;
    • NULL_RATE_CREEP.
      Each writes a plain message ("Orders per day dropped to 12 (usual 40–55).") and a drill on the latest bucket.
  • Evaluator: the hidden scheduled process data_analysis_evaluate_watches (every 15 minutes) evaluates each watch as its owner. Without an owner session, or without access, the watch is skipped with a message and never falls back to a system session.
    • On firing it calls QAlerts.raise, with type data.analysis.anomaly (core doesn't allow underscores in type names), declared as QAlertTypeMetaData.
    • On recovery it calls QAlerts.resolve.
    • Without the Alerts qbit, the watch row is the record.
  • Record actions: Evaluate now, Enable and Disable, as standard streamed-ETL Review/Result lines. POST /watches/{id}/preview returns the series with the baseline band and the verdict (extension 1.3.0).
  • Labels: pivot and crosstab show Yes/No for booleans and human measure labels.
  • Contingent fields (owner requirement):
    • data_analysis_watch.saved_id is filtered by ${input.table_name};
    • every table_name uses core's tables PVS;
    • analyzable_fields and excluded_fields pick from the chosen table's fields through a custom provider, in the qbit-quick-search resolveTable(otherValues) style. Storage stays comma-separated, since core has no multi-select PVS field yet.
    • Each field has a possibleValueSourceDependsOn TODO for when core adds it.

Test plan

  • ./mvnw verify with Docker: green on memory, H2 and Postgres (~285 tests). JaCoCo ~97% instructions / 100% classes.
  • Detectors on hand-made series (MAD = 0, too little history); the evaluator with real rows raises and then resolves through a test AlertSinkInterface; owner session used; a user without READ is skipped with a message
  • Record actions' Review/Result lines; preview endpoint over HTTP
  • One test per contingent pair: saved_id filtered by table, the tables PVS lists the instance tables, the field picker lists only the chosen table's fields

🤖 Generated with Claude Code

## Summary Phase (e) of `docs/DATA-ANALYSIS.md` (§7.6) plus contingent possible values. **Stacked on #8 → #6 → #5.** Draft until those merge after RC.6. - **Watches** (`data_analysis_watch`) watch either a saved analysis or a table, filter and metric (COUNT, SUM or AVG of a field, the null share, or the category set) per HOUR, DAY, WEEK or MONTH bucket. - **Detectors** (pure functions): - SPIKE / DROP: robust z from the median and MAD of the last k buckets, falling back to relative change when MAD = 0; - OUT_OF_RANGE; - NEW_CATEGORY; - NULL_RATE_CREEP. Each writes a plain message ("Orders per day dropped to 12 (usual 40–55).") and a drill on the latest bucket. - **Evaluator:** the hidden scheduled process `data_analysis_evaluate_watches` (every 15 minutes) evaluates each watch **as its owner**. Without an owner session, or without access, the watch is skipped with a message and never falls back to a system session. - On firing it calls `QAlerts.raise`, with type `data.analysis.anomaly` (core doesn't allow underscores in type names), declared as `QAlertTypeMetaData`. - On recovery it calls `QAlerts.resolve`. - Without the Alerts qbit, the watch row is the record. - **Record actions:** Evaluate now, Enable and Disable, as standard streamed-ETL Review/Result lines. `POST /watches/{id}/preview` returns the series with the baseline band and the verdict (extension 1.3.0). - **Labels:** pivot and crosstab show Yes/No for booleans and human measure labels. - **Contingent fields** (owner requirement): - `data_analysis_watch.saved_id` is filtered by `${input.table_name}`; - every `table_name` uses core's `tables` PVS; - `analyzable_fields` and `excluded_fields` pick from the chosen table's fields through a custom provider, in the qbit-quick-search `resolveTable(otherValues)` style. Storage stays comma-separated, since core has no multi-select PVS field yet. - Each field has a `possibleValueSourceDependsOn` TODO for when core adds it. ## Test plan - [x] `./mvnw verify` with Docker: green on memory, H2 and Postgres (~285 tests). JaCoCo ~97% instructions / 100% classes. - [x] Detectors on hand-made series (MAD = 0, too little history); the evaluator with real rows raises and then resolves through a test `AlertSinkInterface`; owner session used; a user without READ is skipped with a message - [x] Record actions' Review/Result lines; preview endpoint over HTTP - [x] One test per contingent pair: saved_id filtered by table, the tables PVS lists the instance tables, the field picker lists only the chosen table's fields 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.