A lightweight Python harness for claim-first, audited analytical charts.
Charts should satisfy analytical contracts, not just render.
chart-contract helps analysts and AI agents produce claim-first charts that can be audited before sharing. A chart should not just render; it should declare what it claims, what data supports it, and what could mislead the reader. Charts are reasoning artifacts, not decorations.
Stop beautiful AI charts from smuggling weak claims.
AI-generated charts can look plausible while hiding weak claims, missing units, missing sources, misleading visual forms, or causal overreach. chart-contract makes those assumptions explicit before the chart is shared.
The audit also catches single-point trend claims: a directional trend needs at least two observations, enforced by data.trend.min_points.
It is a chart-contract harness:
claim -> data contract -> visual choice -> audit -> render
It is intentionally not a full visualization library, not a dashboard tool, and not an AI chart generator.
| Bad chart | Corrected chart |
|---|---|
The bad chart renders, but the audit flags the weak claim, missing source, generic title, and arc/category issue. The corrected chart switches to a sorted bar chart that is easier to compare. It keeps the claim narrower, with source and unit metadata visible. The caveat keeps the interpretation descriptive rather than causal.
Outputs from the hero demo:
- Corrected Vega-Lite spec
- Audit report, Markdown
- Audit report, JSON
- Bad chart SVG
- Corrected chart SVG
See docs/AUDIT_RULES.md for the rule reference behind these findings.
The hero demo includes lightweight rendered charts alongside the audit artifacts so the first impression is inspectable without extra tooling.
| Tool | What it does |
|---|---|
| Altair / Vega-Lite | Renders charts. |
| Great Expectations | Validates data expectations. |
| Dashboard QA checklists | Depend on humans remembering the steps. |
chart-contract |
Audits claim + data + visual choice + provenance before sharing. |
chart-contract complements these tools rather than replacing them.
python -m pip install -e ".[dev]"
python examples/bad_to_good_chart.py
pytestUse the CLI as the front door for agent-gated chart checks:
chart-contract audit spec examples/traps/causal_claim_missing_caveat.vl.json \
--data examples/traps/causal_claim_missing_caveat.csv \
--claim "$(cat examples/traps/causal_claim_missing_caveat.claim.txt)"Representative output:
Verdict: REVIEW
Summary: PASS=5 WARN=1 FAIL=0
Findings:
- WARN claim.causal_support: Claim uses causal language without a caveat or causal evidence metadata. Suggestion: Add a caveat or spec['usermeta']['causal_evidence']=True when justified.
Exit behavior:
READYexits0.REVIEWexits0by default and1when--warnings-as-errorsis set.BLOCKexits1.--fail-on READY|REVIEW|BLOCKis also available for explicit thresholds;READYis mechanically valid but means the command will fail for any verdict.
For more agent-facing guidance on v0.2.0, see docs/AGENT_INTEGRATION.md, the runnable CLI traps in examples/traps/README.md, and the older Python probes in examples/quality_traps.py. Chart.from_prompt() is intentionally not part of the release.
The repo leads with a risky chart example because that is the point of the package: catch weak analytical defaults before a chart gets shared.
import pandas as pd
from chart_contract import Chart, audit_spec
df = pd.DataFrame(
{
"segment": ["SMB", "Enterprise", "Mid-Market", "Public", "Startup", "Partners", "Other"],
"conversion_rate": [0.09, 0.18, 0.14, 0.12, 0.16, 0.11, 0.08],
}
)
bad_vega_lite_spec = {
"mark": {"type": "arc", "innerRadius": 40},
"title": "Chart",
"encoding": {
"theta": {"field": "conversion_rate", "type": "quantitative"},
"color": {"field": "segment", "type": "nominal"},
},
}
findings = audit_spec(
spec=bad_vega_lite_spec,
data=df,
claim="The onboarding launch caused conversion lift across customer segments.",
)
corrected = Chart.rank(
data=df.sort_values("conversion_rate", ascending=False),
x="segment",
y="conversion_rate",
claim="Enterprise and startup segments show the highest observed conversion rates.",
source="synthetic.segment_conversion",
unit="conversion rate",
caveat="Observational segment summary; not causal proof for the onboarding launch.",
)Representative audit output from python examples/bad_to_good_chart.py:
PASS contract.claim.present
WARN contract.source.present
WARN labels.title.quality
WARN claim.causal_support
FAIL visual.arc.category_count
PASS readability.color.category_count
Run the full hero demo with:
python examples/bad_to_good_chart.pyIt audits the risky pie-like chart, prints PASS/WARN/FAIL findings, and writes a corrected Vega-Lite spec to examples/output/corrected_chart.vl.json.
import pandas as pd
from chart_contract import Chart
df = pd.DataFrame(
{
"week": ["2026-05-01", "2026-05-08", "2026-05-15"],
"conversion_rate": [0.12, 0.14, 0.16],
}
)
chart = Chart.trend(
data=df,
x="week",
y="conversion_rate",
claim="Conversion improved after onboarding launch",
source="warehouse.funnel_events",
unit="conversion rate",
event={"x": "2026-05-08", "label": "Onboarding launch"},
caveat="Observational trend; not causal proof.",
)
report = chart.audit()
spec = chart.to_vega_lite()
altair_chart = chart.to_altair()For a minimal pre-share gate, inspect report.verdict before you send the chart onward: READY means the audit found only PASS checks, REVIEW means warnings need human judgment, and BLOCK means at least one failure should stop sharing until fixed. report.verdict is the authoritative gate field; report.passed only means there are no FAIL findings, so a REVIEW report can still have passed=True.
Supported Python API front-door intents:
Chart.trend()Chart.rank()Chart.compare()Chart.histogram()Chart.boxplot()Chart.violin()- experimental
audit_spec()
- Not a full visualization library
- Not a dashboard tool
- Not an AI chart generator
- No automatic chart correction
- Claim first
- Data contract before rendering
- Audit before sharing
- Visual integrity over decoration
- Provenance and caveats visible
The audit layer uses Tufte-inspired visual integrity checks. It does not claim to be Tufte-compliant or Tufte-certified.
Use distribution intents when the claim is about spread, shape, outliers, or typical values rather than a trend or rank.
- Histograms are good for one metric's distribution.
- Boxplots are good for robust summaries and outliers.
- Violin plots are good for shape, but they need more observations to stay readable.
Run python examples/distribution_charts.py to write:
examples/output/histogram_chart.vl.jsonexamples/output/boxplot_chart.vl.jsonexamples/output/violin_chart.vl.json
These charts stay inside the same claim-first audit flow and are not a full visualization library.
This repo was built using ai-engineering-skills and is intended as the analytical-integrity proof artifact companion to context-to-action-skills.
See the agent workflow case study and build manifest for the proof trail.
See the suite map for how chart-contract, ai-engineering-skills, and context-to-action-skills fit together as one story.