--- name: masthead-data-scans description: Set up a data quality scan end to end — define a metric with the user, materialize it as a BigQuery table in a `masthead_dq` dataset with a scheduled query that keeps it current, give Masthead read access to that dataset only, then validate and create the scan through the Masthead MCP server. Also lists, pauses, updates, and deletes existing scans. Every BigQuery change and every scan change runs only on explicit confirmation. compatibility: Requires the Masthead MCP server at `https://mcp.mastheadata.com/mcp` (US region) and the `bq` CLI signed in to the customer's Google Cloud project, with permission to create datasets, tables, and scheduled queries (BigQuery Data Transfer API). --- # Data Scans ## Purpose Add a data quality scan to Masthead end to end. The user describes a metric; the skill turns it into a query that follows Masthead's contract, materializes the results in a table in a dedicated `masthead_dq` dataset in the user's project, schedules a query that adds each closed period to that table, gives Masthead read access to that dataset only, and creates the scan through the Masthead MCP server. Masthead then reads the table on a schedule, learns the expected range of every metric, and raises a data quality incident when a value falls outside it. Masthead never reads the source tables. The skill also lists, pauses, updates, and deletes existing scans. ## Operating Modes * **Recommendation Mode (Default)**: read-only. Writes the metric SQL, runs the local checks with the user's credentials, and shows the setup plan. Calls only `list_projects` and `list_data_scans` (and `create_data_scan` with `dryRun=true` for a scan table that already exists). No DDL, no `bq` write, no scan change. * **Action Mode**: runs each customer-side step (dataset, table, scheduled refresh, grant), the real scan creation, and every update or delete — **each only after the user's explicit yes for that step**. A yes for one step never covers the next. ## How Masthead runs a data scan * Masthead runs `SELECT * FROM WHERE timestamp >= ` as `masthead-quality-checks@masthead-prod.iam.gserviceaccount.com`, in Masthead's project and at Masthead's cost. The scheduled refresh runs in the user's project. * Every run adds its own time filter, so the table keeps its history; nothing deletes old periods. * Each `(table_reference, rule_name)` pair is one series. Masthead resamples it to the scan frequency, learns its expected range, and flags values outside it as well as periods with no row at all. * The first run starts as soon as the scan is created, backfills the last 14 days, and sends no notifications. Its results appear within a few minutes on the scan's page and the monitored table's page in Masthead. Later anomalies raise data quality incidents, which follow the tenant's alert settings. * Masthead only reads closed periods — the in-progress period (today, for a `DAILY` scan) is never fetched; a period is read on a run after it has closed. `processDelayHours` adds extra wait after that close. **The scheduled refresh must finish within that delay**, or Masthead finds no row for the period and flags it as missing. * Data scans are available in the US region only. ## Scan table contract The scan table has these columns; extra columns are ignored. Examples and the refresh template: [references/table-contract.md](references/table-contract.md). | Column | Type | Rule | | --- | --- | --- | | `table_reference` | `STRING` | `project.dataset.table` of the monitored table the metric describes; incidents attach to it | | `rule_name` | `STRING` | Stable metric name; it becomes the incident's metric and cannot be renamed later | | `timestamp` | `TIMESTAMP` | Start of the period: `TIMESTAMP_TRUNC(, DAY)` or `TIMESTAMP()` | | `value` | `INT64`, `FLOAT64`, `NUMERIC`, `BIGNUMERIC` | The metric. Ratios as percents (0–100), because the learned range is very wide for values under 25 | * One row per `table_reference`, `rule_name`, and period; no NULLs. * **A row for every series in every period**, including zero values. A series that stops getting rows is flagged as missing every period. Never decide which series to keep with a rolling-window filter (for example "spend over the last 30 days ≥ $1"): a series that drops under it vanishes and alerts daily. Filter on something stable, or keep low-value series. * At least 14 days of history when the scan is created. * Aggregated metrics only — never store raw rows. * All source tables in one BigQuery location, the same as the `masthead_dq` dataset. * At most 1,000 series per scan. * Partition the table by `timestamp` (day) and cluster by `table_reference, rule_name`, so Masthead's reads stay cheap. ## Workflow ### Step 0: Preconditions 1. If the `create_data_scan` tool is not available, stop: "Data scans are available in the US region only." Don't look for workarounds. 2. `list_projects`: the project that will hold the scan table must be listed. If it isn't, stop and tell the user to connect that project to Masthead first. 3. `bq ls --project_id=` must succeed. If it doesn't, ask the user to run `gcloud auth login` and retry. 4. `bq ls --transfer_config --transfer_location= --project_id=` must succeed. If it fails because the BigQuery Data Transfer API is off, ask the user to enable it (`gcloud services enable bigquerydatatransfer.googleapis.com --project=`) — an Action Mode step that needs its own yes. If the user already schedules SQL with dbt, Dataform, or Airflow, offer to add the refresh there instead and skip the scheduled query. 5. `list_data_scans`: show the existing scans, so a new name doesn't clash and an existing scan isn't recreated. ### Step 1: Define the metric Agree with the user on: the monitored table or tables, the metric or metrics, the frequency (`HOURLY`, `EVERY_6_HOURS`, `EVERY_12_HOURS`, `DAILY`), the scan name, and the scan table name (snake_case, for example `orders_daily_volume`). Write the **metric SQL** to the contract above, with one placeholder window on the source's time column: `