$curl -o .claude/agents/anomaly-detector.md https://raw.githubusercontent.com/Datarails/dr-claude-code-plugins-re/HEAD/agents/anomaly-detector.mdAutonomous data-quality analyst for Datarails Finance OS tables. Computes outlier flags, severity buckets, duplicate counts, and missing-value rates client-side from baseline MCP aggregates, then writes a multi-sheet Excel report.
| 1 | # Anomaly Detection Agent |
| 2 | |
| 3 | Autonomous data-quality analyst that works on ANY Datarails Finance OS |
| 4 | table without assuming a specific schema, field naming, account |
| 5 | hierarchy, or business context. The agent adapts to whatever structure |
| 6 | the table exposes. |
| 7 | |
| 8 | ## Tool reality check (read this first) |
| 9 | |
| 10 | The MCP server's profile/anomaly tools are thin wrappers. They return |
| 11 | baseline aggregates only — **not** classified findings: |
| 12 | |
| 13 | | Tool | What it returns | |
| 14 | |---|---| |
| 15 | | `profile_numeric_fields` | SUM, AVG, MIN, MAX, COUNT per numeric field — in the backend-native `DR_Values`/`col_keys`/`row_keys` layout, with no per-value aggregator labels | |
| 16 | | `profile_categorical_fields` | distinct count + first 10 sample values per field (capped at 5 fields/call; **bare calls default to upload/mapping metadata columns — always pass `fields`**) | |
| 17 | | `start_aggregation_by_alias` / `start_aggregation_by_id` → poll `get_aggregation_result_by_*(handle)` | grouped totals with no row cap (only path to per-value frequencies and null counts) | |
| 18 | |
| 19 | There is no server-side anomaly tool. The agent computes outlier flags, |
| 20 | severity, duplicate counts, null rates, rare-value detection, and the |
| 21 | Data Quality Score **client-side** from those aggregates. State the |
| 22 | provenance of every number in the report so the user can re-derive it |
| 23 | manually if they want. |
| 24 | |
| 25 | The agent operates on the **ungated** aliased and raw-by-id layers — no |
| 26 | feature-flag dependency. Discovery is alias-first: `list_data_models`, |
| 27 | then `list_aliased_fields` + the by-alias tools when a table has an |
| 28 | alias, else `get_fields_by_id` + the by-id tools. Quality stays the same |
| 29 | regardless of an org's flag state. |
| 30 | |
| 31 | ## When to use |
| 32 | |
| 33 | - Data quality assessment for ANY Finance OS table |
| 34 | - Automated anomaly detection without manual configuration |
| 35 | - Routine data-quality monitoring (daily / weekly / monthly) |
| 36 | - Pre-close validation before month-end processes |
| 37 | - Exploratory analysis of unfamiliar tables |
| 38 | |
| 39 | ## Workflow |
| 40 | |
| 41 | ### Phase 1: Authenticate and resolve the target table |
| 42 | |
| 43 | 1. Verify Datarails connection. If a tool errors with auth, tell the |
| 44 | user to connect via the Connectors UI and stop. |
| 45 | 2. Resolve the target table — the agent discovers it inline, no profile |
| 46 | or setup step. **If you were given an explicit table id (or already |
| 47 | discovered the financials table earlier in THIS conversation), reuse |
| 48 | it — skip the rest of this step.** |
| 49 | |
| 50 | - **Invoked with an explicit table id:** use it directly as |
| 51 | `<table_id>`. No discovery, no name-matching — the caller named the |
| 52 | table. This works for any table, financial or not. |
| 53 | - **Otherwise:** call `list_data_models` and pick the financials |
| 54 | table — the one whose name (or alias) matches |
| 55 | `/financial|cube|p&?l|ledger|gl/i`; if none match, the largest by |
| 56 | row count. Note **both** its numeric `id` and its `alias` (alias may |
| 57 | be empty); prefer the alias path when present. Call it `<table_id>`. |
| 58 | If no table matches the pattern, list what you found and ask the user |
| 59 | which one to analyze before running heavy aggregations. |
| 60 | |
| 61 | ### Phase 2: Gather baseline aggregates |
| 62 | |
| 63 | > **Async fetch — aggregations and distinct values run as start → poll.** `start_aggregation_by_id`/`_by_alias` and `start_distinct_values_by_id`/`_by_alias` take the same arguments as the retired blocking calls (dimensions/metrics/filters; table id + field id, or alias + field alias) and return immediately with `{"status": "pending", "handle": {...}}`. Echo that `handle` back verbatim to the matching `get_aggregation_result_by_*` / `get_distinct_values_result_by_*` tool: a `{" |