{
  "markdown": "# andon\n\n<!-- mcp-name: io.github.gulmezeren2-byte/andon -->\n\n**Stop the line when the numbers don't add up.**\n\n[![CI](https://github.com/gulmezeren2-byte/andon/actions/workflows/ci.yml/badge.svg)](https://github.com/gulmezeren2-byte/andon/actions/workflows/ci.yml)\n[![PyPI](https://img.shields.io/pypi/v/andon-verify)](https://pypi.org/project/andon-verify/)\n[![Python](https://img.shields.io/badge/python-3.10%2B-blue)](pyproject.toml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n\nandon re-checks the numbers in finished analysis — the report your AI agent just drafted,\nthe workbook a colleague \"quickly updated\" — against the data they came from and against\nthemselves. It does this with arithmetic, not with another LLM: reconciliation,\ninternal consistency, schema contracts and Excel workbook integrity, written down as a\nsmall YAML spec and enforced with exit codes.\n\n![andon stopping the line on a sabotaged quarterly report](docs/demo.svg)\n\nThat screenshot is real output. The example it runs on is in\n[`examples/quarterly-report/`](examples/quarterly-report/), staged by a script that\nplants the defects I keep meeting in real reporting work: a count taken from a stale\nsnapshot, a revenue total typed over by hand, shares that sum to 101.2, a total row\nnobody updated after a data refresh, a `#REF!`, and freight numbers stored as text.\n\n## Why this exists\n\nI'm an industrial engineer. I build and run operations reporting — delivery KPIs,\nforecast accuracy, inventory analytics — and over the last two years an increasing share\nof the first drafts around me has been written by AI agents. They are fast, tireless,\nand confidently wrong in ways a tired human is not: the filter that silently dropped\ncancelled orders, the percentage column that almost sums to 100, the total row that\nsurvived three edits of its parts.\n\nThe common answer is to ask a second model to review the first one. I think that is the\nwrong tool. Whether 539 rows really sum to 257,060.48 is not a matter of opinion, and no\namount of model capability makes an opinion the right instrument for it.\n\nManufacturing solved this problem decades ago. On a Toyota line, any worker who spots a\ndefect pulls a cord — the *andon* — and the line stops until the problem is understood.\nThe machine equivalent, *jidoka*, is a machine that stops itself when it detects an\nabnormal condition. This tool is that cord for spreadsheets and reports: a small,\ndeterministic gate between \"the analysis is written\" and \"the analysis is sent.\"\n\n## The iron rules\n\nandon's behavior is easier to trust because it is constrained. These rules are enforced\nin code, not just promised here:\n\n1. **Only arithmetic can fail the build.** Heuristic checks (distribution shifts,\n   plausibility bounds) can raise a REVIEW flag; the engine will not let them FAIL, even\n   if a buggy check tries.\n2. **No silent blessings.** Every report ends with what was read, what was skipped, and\n   which worksheets were never touched (the report calls this the honesty block). A\n   PASS covers the listed assertions and nothing else.\n3. **A check that can't run is a finding, not a pass.** Missing file, unreadable range,\n   text in a numeric column — the run continues, the check is recorded as ERROR, and the\n   exit code is non-zero. \"Not verified\" must never be readable as \"fine.\"\n4. **Read-only by construction.** There is no code path that writes to your data.\n\n## Install\n\n```\npip install andon-verify\n```\n\nThe PyPI distribution is named `andon-verify` (the bare `andon` name was already\ntaken); the command and the import stay `andon` — `andon run ...`, `import andon`.\nFrom source: `pip install git+https://github.com/gulmezeren2-byte/andon`.\n\n## Quick start\n\nPoint andon at data and claims:\n\n```yaml\n# andon.yaml\nversion: 1\n\nsources:\n  orders: data/orders.csv\n  report: out/weekly.xlsx#Summary\n\nchecks:\n  - name: no dropped orders\n    reconcile.row_count:\n      left:  { source: orders, where: \"status != 'cancelled'\" }\n      right: { source: report, cell: B4 }\n\n  - name: revenue adds up\n    reconcile.sum:\n      column: revenue\n      left:  { source: orders, where: \"status != 'cancelled'\" }\n      right: { source: report, cell: B6 }\n      tolerance: 0.5%\n\n  - name: totals row is honest\n    internal.total_row:\n      source: report\n      parts: B10:B21\n      total: B22\n      tolerance: 0.01\n\n  - name: workbook is mechanically sound\n    excel.integrity:\n      source: report\n```\n\n```\nandon run andon.yaml            # human-readable verdict\nandon run andon.yaml --json     # full machine-readable report\nandon inspect out/weekly.xlsx   # integrity-scan a workbook, no spec needed\nandon init                      # write a commented starter spec\n```\n\nOr try the sabotaged example in this repo:\n\n```\ngit clone https://github.com/gulmezeren2-byte/andon\ncd andon/examples/quarterly-report\nandon run andon.yaml\n```\n\n## What it checks\n\n| Family | Checks | Question it answers | Can FAIL? |\n|---|---|---|---|\n| `reconcile` | `row_count`, `sum`, `aggregate`, `group_sum`, `keys` | Does the report agree with the data it came from? | yes |\n| `internal` | `total_row`, `percent_sum`, `recompute` | Does the report agree with itself? | yes |\n| `schema` | `columns`, `unique`, `not_null`, `allowed_values`, `date_continuity` | Is the data shaped the way everyone assumes? | yes |\n| `excel` | `integrity` | Is the workbook mechanically sound? (`#REF!`, values typed over formulas, numbers stored as text — including the `1.234,56` flavor — hidden rows, external links) | on error cells |\n| `plausibility` | `bounds`, `new_categories`, `mean_shift` | Should a human look at this before anyone trusts it? | no — REVIEW at most |\n\nFull parameter reference with examples: [`docs/checks.md`](docs/checks.md).\n\n## Exit codes and CI\n\nExit codes are a contract:\n\n| code | meaning |\n|---|---|\n| 0 | every check passed |\n| 1 | at least one FAIL (with `--strict`: also on REVIEW/ERROR/nothing-ran) |\n| 2 | no failures, but REVIEW flags were raised |\n| 3 | nothing was verified — a check could not run, or every check was skipped |\n| 4 | the spec itself is broken |\n\nAs a GitHub Action — one line, and the verdict lands in your job summary:\n\n```yaml\n- uses: gulmezeren2-byte/andon@v1\n  with:\n    spec: reports/andon.yaml\n    args: \"--strict\"\n```\n\nOr plainly, in any runner:\n\n```yaml\n- run: pip install andon-verify\n- run: andon run reports/andon.yaml --strict --md verdict.md   # --md → a PR-comment-ready verdict\n```\n\n## As a pre-commit hook\n\nStop a commit before a broken report leaves your machine:\n\n```yaml\n# .pre-commit-config.yaml\nrepos:\n  - repo: https://github.com/gulmezeren2-byte/andon\n    rev: v0.6.2\n    hooks:\n      - id: andon\n        args: [\"reports/andon.yaml\", \"--strict\"]\n```\n\n## Using andon with AI agents\n\nandon is built to be *driven by* agents, not to contain one:\n\n- `--json` emits the full report with stable field names; the exit code alone is enough\n  for a go/no-go decision.\n- Error messages name the sources, columns and sheets involved, so an agent can repair\n  its own spec instead of guessing (\"Column 'Revenue' not found. Columns are: region,\n  share_pct, revenue\").\n- [`skills/verify-with-andon/`](skills/verify-with-andon/) ships a skill for Claude\n  Code and compatible harnesses that teaches an agent the discipline: after drafting any\n  analysis, write the spec, run andon, and report the verdict — including the rule that\n  loosening a tolerance to make a check pass must be declared, never silent.\n- **MCP server.** `pip install 'andon-verify[mcp]'` and run `andon-mcp` to expose three\n  tools to any MCP-speaking runtime: `run` (execute a spec), `inspect` (integrity-scan a\n  workbook with no spec), and `diff` (classify what changed between two versions). The\n  agent gets the same structured verdict a human gets — not prose it has to parse back.\n\n```jsonc\n// e.g. Claude Desktop / Claude Code mcp config\n{ \"mcpServers\": { \"andon\": { \"command\": \"andon-mcp\" } } }\n```\n\nNo local Python? The [`Dockerfile`](Dockerfile) builds the same server:\n`docker build -t andon . && docker run --rm -i -v \"$PWD:/work:ro\" -w /work andon`.\n\nMy working rule: the agent that wrote the analysis also writes the spec, and neither is\nfinished until `andon run` exits 0 — or a human has signed off on every flag it raised.\n\n## What andon is not\n\n- **Not a data-quality platform.** [Great Expectations](https://github.com/great-expectations/great_expectations)\n  and [pandera](https://github.com/unionai-oss/pandera) validate data *inside pipelines*,\n  in code, usually against a warehouse. andon verifies *claims in finished artifacts* —\n  the report against its source — and treats Excel as a first-class citizen, because\n  that is where analysis actually lives in most companies.\n- **Not an LLM evaluator.** It doesn't score model outputs; it re-derives numbers.\n- **Not a replacement for reading the report.** It removes a class of mechanical error\n  so human review can spend itself on judgment.\n\n## Limitations, honestly\n\n- **Formula cells need cached values.** andon reads the value Excel last calculated. A\n  workbook produced by a library and never opened in Excel/LibreOffice carries no cached\n  values for its formulas; andon refuses to guess and reports exactly that.\n- **`where` filters are pandas `query()` expressions.** They are expressive, which means\n  a spec can encode the same mistakes as any query. Specs are code — review them like code.\n- **Heuristic checks have false positives by design.** That is why they cannot fail a\n  build.\n- **Scale is untested beyond mid-size files.** Everyday operational workbooks and CSVs\n  (hundreds of thousands of rows) are fine; nobody has benchmarked it against 10 GB of\n  parquet. If you do, tell me what broke.\n- Parquet sources need `pip install 'andon-verify[parquet]'`.\n\n## CSV encoding, delimiter, and how numbers are punctuated\n\nA CSV source is read as utf-8 by default, and Excel's BOM is stripped so it can't poison\nthe first column name. When a file isn't utf-8 — Turkish exports out of Excel are often\n`;`-separated, encoded cp1254, and write amounts as `1.234,56` — give the source as a\nmapping instead of a bare path:\n\n```yaml\nsources:\n  sales:\n    path: data/satis.csv\n    encoding: cp1254      # default: utf-8-sig\n    delimiter: \";\"        # default: \",\"\n    decimal: \",\"          # default: \".\"\n    thousands: \".\"        # default: none\n```\n\nGet the encoding wrong and andon says so, naming the likely culprit — it does not\nsilently mojibake your column names and then \"verify\" them.\n\nThe last two matter more than they look. Without them `1.234,56` is read as **text**, and\na column of text is a column a numeric check cannot examine. andon will tell you that\nrather than pass — see below — but declaring the punctuation is what makes the column\ncheckable in the first place.\n\n## What a check does when it can't read the data\n\nEvery report ends with the same line: *a PASS is not an opinion about the analysis; it is\narithmetic about these claims.* That obliges the checks to distinguish two things a\ncareless implementation runs together:\n\n- **\"I compared them and they were fine\"** — a PASS.\n- **\"I had nothing to compare\"** — never a PASS.\n\nSo a plausibility check that finds no numeric values says so, instead of reporting that\nzero values fell outside the bounds:\n\n```\nREVIEW  bounds  no numeric values in tutar; nothing was compared against the bounds\n```\n\nAnd when only part of a column is readable, the verdict carries the denominator, because\na statement about the half that parsed is not a statement about the data you handed over:\n\n```\nREVIEW  bounds  tutar stays within bounds, but only read 2 of 4 value(s) of tutar\n```\n\nEvery such check reports `values_checked`, `values_total` and `values_skipped` in its\nevidence, so the coverage is machine-readable too. Values that pandas turns into `NaN` at\nread time — `n/a`, `-`, `#N/A` — are counted as skipped like anything else; they are\ninvisible to a naive coercion count, which is exactly how they used to go unnoticed.\n\n## Verify against a warehouse query (DuckDB)\n\nA source can be a SQL query instead of a file — so you can reconcile a report against\nthe same data your BI tool reads, not just against a CSV. With\n`pip install 'andon-verify[duckdb]'`, prefix a source with `duckdb:` and DuckDB runs it\n(it reads CSV, parquet, JSON and `.duckdb` files inside the query; relative paths resolve\nagainst the spec):\n\n```yaml\nsources:\n  # the report's headline number\n  report: out/q2.xlsx#Summary\n  # the same number, straight from the raw data via SQL\n  truth: \"duckdb:SELECT SUM(revenue) AS rev FROM 'data/orders.parquet' WHERE status='shipped'\"\n\nchecks:\n  - name: revenue matches the warehouse\n    reconcile.sum:\n      column: rev\n      left:  { source: truth }\n      right: { source: report, cell: B6 }\n      tolerance: 0.5%\n```\n\n## Diff two workbook versions\n\n\"Someone edited the workbook — what actually changed?\" `git diff` on an .xlsx is noise,\nand the tools that compare spreadsheets show every changed cell flat. `andon diff`\nclassifies each change instead, so a new `#REF!` doesn't hide behind reformatted dates:\n\n```\nandon diff last-week.xlsx this-week.xlsx\nandon diff v1.xlsx v2.xlsx --tolerance 0.5%   # hide numeric moves below 0.5%\nandon diff v1.xlsx v2.xlsx --json             # machine-readable\n```\n\n```\ncell        change      before → after\nSummary!B9  new_error   43.8 → #REF!\nSummary!B4  numeric     261,687.57 → 266,687.57  (+5000, +1.91%)\n```\n\nA new error cell is called out on its own; numeric moves come with a delta and a percent.\nExit codes: 0 = nothing meaningful changed, 1 = a new error appeared (or, with `--strict`,\nany change), 2 = changes but no new error.\n\n## Roadmap\n\nShipped since 0.1: a [GitHub Action](#exit-codes-and-ci) and pre-commit hook, DuckDB\nsources, `andon diff`, an [MCP server](#using-andon-with-ai-agents), CSV encoding/delimiter\ncontrols, and JSON/JSONL sources.\n\nNear-term, in order:\n\n- Row-level diff for data sheets, not just cell-by-cell\n- A `--watch` mode: re-run a spec when its sources change on disk\n\nNot planned: dashboards, scheduled runners, LLM-powered anything inside the verifier.\nThe verifier stays deterministic; that is the point.\n\n## How this project is built\n\nI design the checks, decide the semantics and review every line; I use AI agents\n(Claude Code) heavily for implementation speed, and the commit trailers say so. If that\nbothers you, read `tests/` first: the suite builds real CSV and XLSX fixtures, no\nmocks, and it is the contract. Tests don't care who typed them.\n\n## Related\n\n- **[opsaudit](https://github.com/gulmezeren2-byte/opsaudit)** — the same instinct, one level up: operations analytics that audits its own numbers instead of trusting them. `andon` checks a finished report against its sources; `opsaudit` computes the metrics with the honesty block built in.\n\nMore tools by [Eren Gülmez](https://github.com/gulmezeren2-byte?tab=repositories).\n\n## License\n\n[MIT](LICENSE) — Mehmet Eren Gülmez\n",
  "bytes": 14998,
  "sha": "e0b53260a40fae0a733470638693bd7ec511162b3fa843190a95e157c558eeec",
  "repo_slug": "gulmezeren2-byte/andon",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_gulmezeren2_byte_andon_6e68f88e/readme"
}