From 558f259da916d24f8d397eb58695dd3fa2398875 Mon Sep 17 00:00:00 2001 From: quill Date: Sun, 1 Nov 2026 21:51:57 +0000 Subject: [PATCH] quill: add csv_summary.py artifact with sample and verifier --- wizard-work/quill/csv_summary.py | 51 +++++++++++++++++++++++++++++ wizard-work/quill/sample.csv | 6 ++++ wizard-work/quill/verify_summary.py | 34 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100755 wizard-work/quill/csv_summary.py create mode 100644 wizard-work/quill/sample.csv create mode 100644 wizard-work/quill/verify_summary.py diff --git a/wizard-work/quill/csv_summary.py b/wizard-work/quill/csv_summary.py new file mode 100755 index 0000000..9448757 --- /dev/null +++ b/wizard-work/quill/csv_summary.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +"""csv_summary.py — Read a CSV from stdin or a file argument and emit JSON summary statistics.""" + +import sys +import json +import csv +from collections import defaultdict +from statistics import mean, median + +def summarize_csv(path=None): + fh = open(path, newline='') if path else sys.stdin + reader = csv.DictReader(fh) + rows = list(reader) + fh.close() if path else None + + if not rows: + return {"rows": 0, "columns": [], "numeric": {}} + + columns = reader.fieldnames or [] + numeric = {} + for col in columns: + values = [] + for row in rows: + try: + values.append(float(row[col])) + except (ValueError, TypeError): + pass + if values: + numeric[col] = { + "count": len(values), + "sum": round(sum(values), 4), + "mean": round(mean(values), 4), + "median": round(median(values), 4), + "min": round(min(values), 4), + "max": round(max(values), 4), + } + + return { + "rows": len(rows), + "columns": columns, + "numeric": numeric, + } + +def main(): + path = sys.argv[1] if len(sys.argv) > 1 else None + result = summarize_csv(path) + json.dump(result, sys.stdout, indent=2) + sys.stdout.write("\n") + +if __name__ == "__main__": + main() diff --git a/wizard-work/quill/sample.csv b/wizard-work/quill/sample.csv new file mode 100644 index 0000000..595e94f --- /dev/null +++ b/wizard-work/quill/sample.csv @@ -0,0 +1,6 @@ +name,age,salary,active +Alice,30,50000,true +Bob,25,45000,false +Charlie,35,70000,true +Diana,28,52000,true +Eve,32,48000,false diff --git a/wizard-work/quill/verify_summary.py b/wizard-work/quill/verify_summary.py new file mode 100644 index 0000000..af83879 --- /dev/null +++ b/wizard-work/quill/verify_summary.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""verify_summary.py — Assert csv_summary.py output matches expected values.""" +import sys +import json + +expected_rows = 5 +expected_columns = ["name", "age", "salary", "active"] +expected_age = { + "count": 5, + "sum": 150.0, + "mean": 30.0, + "median": 30.0, + "min": 25.0, + "max": 35.0, +} +expected_salary = { + "count": 5, + "sum": 265000.0, + "mean": 53000.0, + "median": 50000.0, + "min": 45000.0, + "max": 70000.0, +} + +data = json.load(sys.stdin) +assert data["rows"] == expected_rows, f"rows: {data['rows']}" +assert data["columns"] == expected_columns, f"columns: {data['columns']}" +age = data["numeric"]["age"] +for k, v in expected_age.items(): + assert age[k] == v, f"age.{k}: {age[k]} != {v}" +sal = data["numeric"]["salary"] +for k, v in expected_salary.items(): + assert sal[k] == v, f"salary.{k}: {sal[k]} != {v}" +print("VERIFIED")