{
  "id": "python-data-script",
  "type": "loadout",
  "title": "Python data script",
  "summary": "Rules for reproducible, single-file Python data scripts with uv and inline dependencies, plus a skill that sanity-checks a dataset before anyone trusts the numbers.",
  "description": "For the scripts that turn a CSV export into a chart, a report or a cleaned file. Agents tend to write\none-off notebooks that only run on their machine and silently drop half the rows. This loadout makes\nevery script runnable with one command, keeps raw data untouched, and adds a sanity-check skill that\nlooks for the classic data mistakes before results get shared.\n",
  "highlights": [
    "Single-file scripts with PEP 723 inline dependencies, run with `uv run`",
    "Raw data is read-only; outputs go to their own folder",
    "Row counts and assumptions logged at each step, so silent data loss shows up",
    "Sanity-check skill: types, nulls, duplicates, ranges, join explosions, date and currency traps"
  ],
  "version": "1.0.0",
  "license": "MIT",
  "author": {
    "name": "Agent Loadout",
    "url": "https://loadout.jnw-it.com"
  },
  "categories": [
    "data"
  ],
  "stacks": [
    "python",
    "uv",
    "polars",
    "pandas"
  ],
  "files": [
    {
      "path": "AGENTS.md",
      "role": "instructions",
      "description": "Python data scripting rules.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": "AGENTS.md",
        "codex": "AGENTS.md",
        "cursor": "AGENTS.md",
        "gemini-cli": "AGENTS.md",
        "copilot": "AGENTS.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/AGENTS.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/python-data-script/latest/AGENTS.md",
      "bytes": 2282,
      "lang": "markdown",
      "content": "# AGENTS.md: Python data scripts\n\n## Folders\n```\ndata/raw/        inputs exactly as received — never modified, often git-ignored\ndata/interim/    intermediate files written by scripts\noutput/          final CSVs, charts, reports\nscripts/         one script per job, numbered if they run in order: 01_clean.py, 02_report.py\n```\n\n## Every script is runnable with one command\n- Use PEP 723 inline metadata so `uv run scripts/01_clean.py` installs what it needs:\n  ```python\n  # /// script\n  # requires-python = \">=3.12\"\n  # dependencies = [\"polars>=1.0\", \"matplotlib\"]\n  # ///\n  ```\n- Add dependencies with `uv add --script scripts/01_clean.py <package>` rather than editing by hand.\n- Paths come from CLI arguments (`argparse`) with sensible defaults, built with `pathlib.Path`. No hard-coded absolute paths.\n- Put the work in `main()` and end with `if __name__ == \"__main__\": main()`.\n\n## Data handling\n- Prefer **polars** for new scripts (fast, strict types); use pandas if the project already does.\n- Read with explicit types for IDs, postcodes and phone numbers (strings, so leading zeros survive) and explicit date parsing.\n- Never overwrite files in `data/raw/`. Write outputs to new files.\n- After each major step (load, filter, join, dedupe), log the row count. A join that changes the row count unexpectedly is a bug until proven otherwise.\n- State assumptions in code comments next to the line that makes them (\"drops refunds: amount < 0\").\n- Money: keep as integer pence/cents or `Decimal`, not floats, when sums must reconcile.\n- Dates: be explicit about time zones and day-first formats (UK data is usually DD/MM/YYYY).\n\n## Output\n- Print a short summary at the end: rows in, rows out, files written.\n- Charts: label axes with units, title says what the chart shows, save as PNG and SVG to `output/`.\n- Do not print or commit personal data. If the data contains names, emails or addresses, keep outputs aggregated unless asked otherwise.\n\n## Style\n- Type hints on functions. Small functions per step (`load`, `clean`, `summarise`), each returning a dataframe.\n- `ruff format` and `ruff check` clean (`uvx ruff check .`).\n\n## Before you say \"done\"\nRun the script end to end on the real input (or a sample), show the summary output, and mention any rows dropped and why.\n"
    },
    {
      "path": "CLAUDE.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Claude Code.",
      "skill": null,
      "agent": "claude-code",
      "install_to": {
        "claude-code": "CLAUDE.md",
        "codex": null,
        "cursor": null,
        "gemini-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/CLAUDE.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/python-data-script/latest/CLAUDE.md",
      "bytes": 374,
      "lang": "markdown",
      "content": "# CLAUDE.md\n\n@AGENTS.md\n\n## Claude Code notes\n- Skills from this loadout live in `.claude/skills/`: `data-sanity-check`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From the Agent Loadout \"Python data script\" v1.0.0 — https://loadout.jnw-it.com/loadouts/python-data-script/ -->\n"
    },
    {
      "path": "GEMINI.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Gemini CLI.",
      "skill": null,
      "agent": "gemini-cli",
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": null,
        "gemini-cli": "GEMINI.md",
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/GEMINI.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/python-data-script/latest/GEMINI.md",
      "bytes": 375,
      "lang": "markdown",
      "content": "# GEMINI.md\n\n@./AGENTS.md\n\n## Gemini CLI notes\n- Skills from this loadout live in `.agents/skills/`: `data-sanity-check`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From the Agent Loadout \"Python data script\" v1.0.0 — https://loadout.jnw-it.com/loadouts/python-data-script/ -->\n"
    },
    {
      "path": ".cursor/rules/python-scripts.mdc",
      "role": "rules",
      "description": "Rule applied to Python files.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": ".cursor/rules/python-scripts.mdc",
        "gemini-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/.cursor/rules/python-scripts.mdc",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/python-data-script/latest/.cursor/rules/python-scripts.mdc",
      "bytes": 593,
      "lang": "markdown",
      "content": "---\ndescription: Python data script conventions\nglobs: \"**/*.py\"\nalwaysApply: false\n---\n\n- Scripts start with a PEP 723 `# /// script` block listing `requires-python` and `dependencies`; run them with `uv run`.\n- `pathlib.Path` for paths, `argparse` for inputs, `main()` guarded by `if __name__ == \"__main__\":`.\n- Never write into `data/raw/`.\n- Log row counts after loads, filters, joins and dedupes.\n- Read identifier-like columns (IDs, postcodes, phone numbers) as strings.\n- No bare `except:`; catch specific exceptions and re-raise with context.\n- Type hints on every function signature.\n"
    },
    {
      "path": "skills/data-sanity-check/SKILL.md",
      "role": "skill",
      "description": "Profiles a dataset and flags data quality problems.",
      "skill": "data-sanity-check",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/data-sanity-check/SKILL.md",
        "codex": ".agents/skills/data-sanity-check/SKILL.md",
        "cursor": ".agents/skills/data-sanity-check/SKILL.md",
        "gemini-cli": ".agents/skills/data-sanity-check/SKILL.md",
        "copilot": ".agents/skills/data-sanity-check/SKILL.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/skills/data-sanity-check/SKILL.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/python-data-script/latest/skills/data-sanity-check/SKILL.md",
      "bytes": 2262,
      "lang": "markdown",
      "content": "---\nname: data-sanity-check\ndescription: Profile a dataset (CSV, Excel, Parquet, JSON) and flag quality problems before analysis. Use when the user shares a data file, asks \"is this data OK?\", or before producing figures someone will rely on.\n---\n\n# Data sanity check\n\nWrite a throwaway script `scripts/00_sanity_check.py` (PEP 723, polars) that reads the file and prints the checks below.\nDo not modify the input file.\n\n## Checks\n1. **Shape:** row and column count. Does it match what the human expects (ask if they know)?\n2. **Types:** each column's inferred type. Flag numbers stored as text (e.g. \"1,234\", \"£12.50\"), dates stored as text,\n   and IDs inferred as numbers (leading zeros lost).\n3. **Nulls:** null count and percentage per column. Flag columns over 5% null and \"null-like\" strings: `\"\"`, `\"N/A\"`, `\"null\"`, `\"-\"`, `\"0\"` in date columns.\n4. **Duplicates:** fully duplicated rows, and duplicates on the column that should be a unique key.\n5. **Ranges:** min/max for numbers and dates. Flag negatives where impossible, dates in the future or before 1990, outliers beyond 3 IQR.\n6. **Categories:** for text columns with fewer than 50 distinct values, list the values with counts. Flag near-duplicates\n   (`\"London\"`, `\"london\"`, `\"London \"`).\n7. **Dates:** check whether day-first or month-first parsing is ambiguous (all days ≤ 12?). Note time zone assumptions.\n8. **Joins (if a second file is involved):** key overlap in both directions, and whether the join is one-to-one,\n   one-to-many or many-to-many. Many-to-many is almost always a mistake.\n9. **Totals:** if there is an amount column, print the grand total so the human can reconcile it with the source system.\n10. **Personal data:** flag columns that look like names, emails, phone numbers or addresses.\n\n## Report\n```\nSanity check — <file> — <rows> rows × <cols> columns\nProblems\n- [High] customer_id inferred as integer — 214 IDs had leading zeros. Read as string.\n- [Medium] order_date: 3.1% null; 12 dates in 2031.\nLooks fine\n- No duplicate rows; amounts total £48,210.55.\nQuestions for you\n- Is order_date day-first? Every day value is ≤ 12, so it's ambiguous.\n```\nLead with problems, keep \"looks fine\" to a line or two, and end with questions only the human can answer.\n"
    }
  ],
  "requires": {
    "tools": [
      "uv",
      "python"
    ],
    "env": [],
    "mcp": []
  },
  "tested": {
    "status": "untested",
    "last_run": null,
    "fixture": null,
    "per_agent": {
      "claude-code": "untested",
      "codex": "untested",
      "cursor": "untested",
      "gemini-cli": "untested",
      "copilot": "untested"
    }
  },
  "security": {
    "scanned": true,
    "scanner": "intake-precheck/1",
    "verdict": "clean",
    "network": true,
    "scripts": [],
    "notes": "Instructions only. `uv run` and `uvx ruff` download packages from PyPI the first time a script or tool runs."
  },
  "featured": false,
  "deprecated": false,
  "last_checked": "2026-10-15",
  "published": "2026-10-15",
  "updated": "2026-10-15",
  "compatibility": {
    "claude-code": "native",
    "codex": "native",
    "cursor": "native",
    "gemini-cli": "native",
    "copilot": "native"
  },
  "counts": {
    "rules": 2,
    "skills": 1,
    "mcp": 0
  },
  "url": "https://loadout.jnw-it.com/loadouts/python-data-script/",
  "api_url": "https://loadout.jnw-it.com/api/loadouts/python-data-script.json",
  "verification": {
    "level": "verified-install",
    "status": "verified",
    "label": "Verified install",
    "verified": true,
    "checked_at": "2026-10-16T22:54:03Z",
    "commit": "ad36e852567aa3c87821d2a9e2fe4052fd983374",
    "content_current": true,
    "agent_tests": "not-run",
    "counts": {
      "pass": 9,
      "fail": 0,
      "skip": 0,
      "not_applicable": 1
    },
    "meaning": "Level 1: automated checks on the files (parse, install paths, MCP config, safety scan, references, size, licence, starter build). No AI agent was run.",
    "checks": [
      {
        "id": "listing",
        "name": "Listing",
        "status": "pass",
        "summary": "Schema valid; 5 files declared, none missing or extra",
        "details": [
          "tested.status: untested (agent tests are separate from Level 1)"
        ]
      },
      {
        "id": "parse",
        "name": "Files parse",
        "status": "pass",
        "summary": "All 5 files parse; front matter valid",
        "details": []
      },
      {
        "id": "adapters",
        "name": "Adapters",
        "status": "pass",
        "summary": "CLAUDE.md and GEMINI.md import AGENTS.md and are up to date",
        "details": [
          "CLAUDE.md: matches the generator",
          "GEMINI.md: matches the generator"
        ]
      },
      {
        "id": "install-paths",
        "name": "Install paths",
        "status": "pass",
        "summary": "Correct paths for all 5 agents; layouts re-discovered",
        "details": [
          "Claude Code: 3 files, 1 skill in .claude/skills/",
          "Codex CLI: 2 files, 1 skill in .agents/skills/",
          "Cursor: 3 files, 1 skill in .agents/skills/, 1 rule",
          "Gemini CLI: 3 files, 1 skill in .agents/skills/",
          "GitHub Copilot: 2 files, 1 skill in .agents/skills/"
        ]
      },
      {
        "id": "mcp",
        "name": "MCP config",
        "status": "n/a",
        "summary": "No MCP servers in this loadout",
        "details": []
      },
      {
        "id": "safety",
        "name": "Safety scan",
        "status": "pass",
        "summary": "Clean: no credentials, risky shell or hidden text",
        "details": [
          "Scanner intake-precheck/1: verdict clean; network yes (listing says yes)"
        ]
      },
      {
        "id": "references",
        "name": "References",
        "status": "pass",
        "summary": "No cross-file references to check",
        "details": []
      },
      {
        "id": "size",
        "name": "Size limits",
        "status": "pass",
        "summary": "5 files, 5.7 KB total; largest AGENTS.md 2.2 KB",
        "details": [
          "Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule"
        ]
      },
      {
        "id": "licence",
        "name": "Licence",
        "status": "pass",
        "summary": "MIT, by Agent Loadout; text in the repository LICENSE",
        "details": []
      },
      {
        "id": "starter-build",
        "name": "Starter build",
        "status": "pass",
        "summary": "PEP 723 polars script in the loadout's layout runs with uv run on sample data, keeps raw data untouched, and is ruff check and ruff format clean",
        "details": [
          "Applied 6 loadout files at every agent's paths",
          "✓ uv run the script: uv run --no-project scripts/01_clean.py",
          "✓ check outputs: python3 check_output.py b0e1dbf3d9f797453d3c401ad412e702cca6f6f5b418867eda311445efa31835",
          "✓ ruff check: uvx ruff@0.17.1 check scripts check_output.py",
          "✓ ruff format --check: uvx ruff@0.17.1 format --check scripts check_output.py",
          "Covers: PEP 723 inline metadata so uv run installs polars itself; argparse + pathlib; main() guard (AGENTS.md and the Cursor rule)",
          "Covers: IDs and postcodes read as strings, UK DD/MM/YYYY dates, integer pence, row counts logged after each step",
          "Covers: data/raw/ is never written to",
          "Covers: Dependencies pinned in time with uv's exclude-newer so the run is repeatable"
        ]
      }
    ]
  },
  "install": {
    "claude-code": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "CLAUDE.md",
          "target": "CLAUDE.md",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/CLAUDE.md",
          "role": "adapter"
        },
        {
          "path": "skills/data-sanity-check/SKILL.md",
          "target": ".claude/skills/data-sanity-check/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/skills/data-sanity-check/SKILL.md",
          "role": "skill"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/python-data-script/claude-code.zip"
    },
    "codex": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/data-sanity-check/SKILL.md",
          "target": ".agents/skills/data-sanity-check/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/skills/data-sanity-check/SKILL.md",
          "role": "skill"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/python-data-script/codex.zip"
    },
    "cursor": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": ".cursor/rules/python-scripts.mdc",
          "target": ".cursor/rules/python-scripts.mdc",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/.cursor/rules/python-scripts.mdc",
          "role": "rules"
        },
        {
          "path": "skills/data-sanity-check/SKILL.md",
          "target": ".agents/skills/data-sanity-check/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/skills/data-sanity-check/SKILL.md",
          "role": "skill"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/python-data-script/cursor.zip"
    },
    "gemini-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "GEMINI.md",
          "target": "GEMINI.md",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/GEMINI.md",
          "role": "adapter"
        },
        {
          "path": "skills/data-sanity-check/SKILL.md",
          "target": ".agents/skills/data-sanity-check/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/skills/data-sanity-check/SKILL.md",
          "role": "skill"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/python-data-script/gemini-cli.zip"
    },
    "copilot": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/data-sanity-check/SKILL.md",
          "target": ".agents/skills/data-sanity-check/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/python-data-script/1.0.0/skills/data-sanity-check/SKILL.md",
          "role": "skill"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/python-data-script/copilot.zip"
    }
  },
  "source_zip_url": "https://loadout.jnw-it.com/download/python-data-script/source.zip"
}