agentsclimarketplace

Validate

Skill sdhilip200/api-warehouse/skills/validate

Point a coding agent at any API's docs → client-ready assessment, sample data, and raw data landed in your warehouse (BigQuery/Snowflake/Postgres/Azure/files). A Claude Code plugin. Raw-landing only, security-first, validated.

Install
npx -y skills add sdhilip200/api-warehouse --skill validate

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use whenever the user wants to validate or reconcile loaded data against the source API, check row counts or control totals, verify a load was complete, or confirm that warehouse data matches what the API returned — even if they don't say "validate". Trigger phrases include: "did everything land?", "check the counts", "reconcile the table", "verify the load", "compare API vs warehouse", "are the rows correct?", or any request to confirm that a loaded table matches its source.

SKILL.md

6.9 KB, as published. Nobody here has run it

validate — Control-Total Reconciliation

Overview

Re-pull a bounded sample from the source API, profile both the sample and the loaded warehouse table with profile_records, reconcile the two profiles with reconcile, and write a validation.html report via render_validation.

Validation is best-effort. When the API exposes no total-count endpoint, or when column types differ between source and destination, individual checks are marked skipped rather than fabricated. Never claim an exact match that was not actually computed — see ../../references/anti-slop.md for the honesty standard this report must meet.

Pre-requisites: endpoints.json produced by assess must exist; the warehouse table loaded by land must be queryable.

Check MEMORY.md before starting — it records API-specific quirks discovered in prior runs (e.g., endpoints that expose no total count, pagination edge cases).


Step 1 — Confirm What to Validate

Ask the user:

Which endpoint / table should we validate? Please provide:

  1. The endpoint name (from endpoints.json) to re-pull from the source API.
  2. The warehouse table name (e.g. raw.posts) to compare against.
  3. A row limit for the source re-pull (default: 1000 rows).

Wait for the user's answer before continuing.


Step 2 — Re-pull a Bounded Source Sample

Read endpoints.json and locate the target endpoint by iterating over spec["resources"]. Fetch up to the agreed row limit using the same auth as land:

import json, os, requests

with open("endpoints.json") as f:
    spec = json.load(f)

endpoint = next(ep for ep in spec["resources"] if ep["name"] == TARGET_ENDPOINT)
url = spec["base_url"].rstrip("/") + endpoint["path"]
headers = {}
if spec["auth"]["type"] == "bearer":
    token_env = spec["auth"]["token_env"]
    headers["Authorization"] = f"Bearer {os.environ[token_env]}"

source_records = []
params = {"per_page": 100}
while url and len(source_records) < ROW_LIMIT:
    resp = requests.get(url, headers=headers, params=params, timeout=30)
    resp.raise_for_status()
    page = resp.json()
    rows = page if isinstance(page, list) else page.get(endpoint["name"], page.get("data", page.get("results", [])))
    source_records.extend(rows)
    url = (page.get("links") or {}).get("next") or (page.get("pagination") or {}).get("next_url")
    params = {}

source_records = source_records[:ROW_LIMIT]
print(f"Source sample: {len(source_records)} records")

If the API returns no usable total-count header or next-page link, note this. The row-count check will be skipped — record the quirk in MEMORY.md for future runs.


Step 3 — Query the Warehouse Table

Pull the same number of rows from the loaded table:

# DuckDB example (pandas-free — uses fetchall + con.description)
import duckdb
con = duckdb.connect("warehouse.duckdb")
rows = con.execute(f"SELECT * FROM {WAREHOUSE_TABLE} LIMIT {ROW_LIMIT}").fetchall()
cols = [c[0] for c in con.description]
loaded_records = [dict(zip(cols, r)) for r in rows]
print(f"Loaded sample: {len(loaded_records)} records")

Adapt to BigQuery / Snowflake / Postgres using the destination's native client.


Step 4 — Profile Both Sides

Note: dlt snake-cases camelCase field names on load (e.g. userIduser_id). Normalize source record keys before profiling so columns align and are not spuriously marked skipped.

from api_warehouse.profile import profile_records
from api_warehouse.normalize import normalize_record_keys

source_profile = profile_records(normalize_record_keys(source_records))
loaded_profile = profile_records(loaded_records)

print("Source profile:", source_profile["row_count"], "rows,", len(source_profile["columns"]), "columns")
print("Loaded profile:", loaded_profile["row_count"], "rows,", len(loaded_profile["columns"]), "columns")

profile_records returns {"row_count": int, "columns": {name: {type, null_count, ...}}}. Numeric columns get sum/min/max; timestamp columns get min/max; text columns get distinct_count and top-5 values.


Step 5 — Reconcile

from api_warehouse.reconcile import reconcile

result = reconcile(source_profile, loaded_profile)

passed  = sum(1 for c in result["checks"] if c["status"] == "pass")
failed  = sum(1 for c in result["checks"] if c["status"] == "fail")
skipped = sum(1 for c in result["checks"] if c["status"] == "skipped")
print(f"Reconciliation: {passed} passed, {failed} failed, {skipped} skipped")

Checks are marked skipped when a stat is unavailable on either side, column types differ, or no comparable statistic exists for a column type. skipped means the check could not be computed, not that data is correct or incorrect. Do not treat it as a failure.


Step 6 — Render the Validation Report

from api_warehouse.report import render_validation

html = render_validation(result)
with open("validation.html", "w") as f:
    f.write(html)
print("Report written to validation.html")

render_validation accepts the dict returned by reconcile and writes a self-contained HTML file with pass/fail/skipped rows colour-coded.


Step 7 — Self-Check (Evals)

Before presenting results to the user, run the eval loop defined in ../../references/running-evals.md using the checks in EVALS.md. Spin up a grader agent with a clean context, pass it EVALS.md and validation.html, and iterate until every check is pass or skipped (or 5 rounds have elapsed). Report any remaining failures plainly. On platforms without subagents (e.g. Codex), run the same checklist inline in a fresh reasoning pass instead — see ../../references/running-evals.md.


Step 8 — Present Results

Tell the user the reconciliation summary:

Validation complete.

  • X checks passed
  • Y checks failed
  • Z checks skipped (stat unavailable or types differ)

Report saved to validation.html. Open it in a browser to inspect each check.

If checks failed: a row-count mismatch suggests an incomplete load or a pagination gap. A numeric sum mismatch points to a transformation or rounding difference. A timestamp range mismatch usually means timezone normalisation differs between source and warehouse.

If checks are skipped: the comparison was not possible for those fields — data is neither confirmed correct nor incorrect. Widening the sample or querying a full-table count (if the source API supports it) may resolve some skips.

Do not claim completeness when checks are skipped.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.