From 91337d9d8fed1ae88e3844733582be732ff75ba5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 19 Jul 2026 01:59:39 +0000 Subject: [PATCH] fix: post-#21 merge hygiene for schema + blocks_from_analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the duplicate analysis-plan allOf column-required rule left by the late #21 merge, and teach blocks_from_analysis to render histogram, stacked_bar, and scatter_chart so suggest→render stays in lockstep. Co-authored-by: moonlight-lupin --- .claude-plugin/plugin.json | 2 +- CHANGELOG.md | 14 ++++++++++++++ schemas/analysis-plan.schema.json | 2 +- skills/data-visualise/scripts/viz.py | 15 +++++++++++++++ tests/test_engine.py | 6 ++++++ 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index cff9755..7bca23e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "data-toolkit", - "version": "0.8.1", + "version": "0.8.2", "description": "Extract, tidy, reconcile, analyse, visualise and convert messy data into clean, validated, audit-ready tables and insight briefs \u2014 computed by a deterministic engine that runs on your machine, with no third-party services. From Phronesis Applied.", "author": { "name": "Phronesis Applied", diff --git a/CHANGELOG.md b/CHANGELOG.md index a9e50e2..6fbd170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.8.2 — 2026-07-19 + +**Post-#21 merge hygiene** — two leftovers from landing the analyse plan-surface +PR after the viz/filter work: + +- **`analysis-plan.schema.json`**: drop the duplicate `allOf` rule that required + `column` for `numeric_summary` / `outliers_iqr` / `currency_mix` a second time + (the #21 rule that also covers `distribution` remains). +- **`blocks_from_analysis`**: render `histogram`, `stacked_bar`, and + `scatter_chart`. `suggest_blocks_from_analysis` already proposed the first two + for `distribution` / `pivot`; the convenience helper raised + `unsupported block type` instead of drawing them. Agent runtime was unaffected + (`suggest` → `_viz_block`). + ## 0.8.1 — 2026-07-19 **Follow-ups to the viz/filter PR (#25)** — closing the plan-surface and handoff diff --git a/schemas/analysis-plan.schema.json b/schemas/analysis-plan.schema.json index 0e9fdf2..5bd13bc 100644 --- a/schemas/analysis-plan.schema.json +++ b/schemas/analysis-plan.schema.json @@ -1 +1 @@ -{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://data-toolkit.local/schemas/analysis-plan.schema.json","title":"Data Analyse operations","type":"array","items":{"type":"object","required":["op"],"properties":{"op":{"enum":["numeric_summary","outliers_iqr","breakdown","period_series","ageing","currency_mix","concentration","pivot","distribution","trend","percentile","cohort","correlation_matrix","rolling","gini","seasonality","join_on","compare_series","filter_rows"]},"name":{"type":"string"},"column":{"type":"string","minLength":1},"by":{"type":"string","minLength":1},"value":{"type":"string"},"top":{"type":"integer","minimum":1},"top_n":{"type":"integer","minimum":1},"k":{"type":["number","string"]},"cap":{"type":"integer","minimum":1},"date_col":{"type":"string","minLength":1},"grain":{"enum":["month","quarter","year"]},"dayfirst":{"type":"boolean"},"as_of":{"type":"string","minLength":1},"buckets":{"type":"array","minItems":1,"items":{"type":"integer","minimum":0}},"rows_col":{"type":"string","minLength":1},"cols_col":{"type":"string","minLength":1},"aggfunc":{"enum":["sum","count","mean"]},"q":{"oneOf":[{"type":"number","minimum":0,"maximum":1},{"type":"array","minItems":1,"items":{"type":"number","minimum":0,"maximum":1}}]},"id_col":{"type":"string","minLength":1},"columns":{"type":"array","minItems":2,"items":{"type":"string","minLength":1}},"window":{"type":"integer","minimum":1},"func":{"enum":["mean","sum","median"]},"on":{"oneOf":[{"type":"string","minLength":1},{"type":"array","minItems":1,"items":{"type":"string","minLength":1}}]},"how":{"enum":["inner","left"]},"a_value":{"type":"string","minLength":1},"b_value":{"type":"string","minLength":1},"a_label":{"type":"string","minLength":1},"b_label":{"type":"string","minLength":1},"left":{"type":"object","required":["date_col"],"properties":{"date_col":{"type":"string","minLength":1},"value":{"type":"string"},"dayfirst":{"type":"boolean"}},"additionalProperties":false},"right":{"type":"object","required":["date_col"],"properties":{"date_col":{"type":"string","minLength":1},"value":{"type":"string"},"dayfirst":{"type":"boolean"}},"additionalProperties":false},"filters":{"type":"array","minItems":1,"items":{"type":"object","required":["op"],"properties":{"col":{"type":"string","minLength":1},"column":{"type":"string","minLength":1},"op":{"type":"string"},"value":{},"values":{"type":"array"},"lo":{},"hi":{}},"additionalProperties":false}}},"allOf":[{"if":{"properties":{"op":{"enum":["numeric_summary","outliers_iqr","currency_mix","distribution"]}},"required":["op"]},"then":{"required":["column"]}},{"if":{"properties":{"op":{"const":"breakdown"}},"required":["op"]},"then":{"required":["by"]}},{"if":{"properties":{"op":{"const":"period_series"}},"required":["op"]},"then":{"required":["date_col"]}},{"if":{"properties":{"op":{"const":"ageing"}},"required":["op"]},"then":{"required":["date_col","as_of"]}},{"if":{"properties":{"op":{"enum":["concentration","gini"]}},"required":["op"]},"then":{"anyOf":[{"required":["column"]},{"required":["by"]}]}},{"if":{"properties":{"op":{"const":"pivot"}},"required":["op"]},"then":{"required":["rows_col","cols_col"]}},{"if":{"properties":{"op":{"enum":["trend","rolling","seasonality"]}},"required":["op"]},"then":{"required":["date_col"]}},{"if":{"properties":{"op":{"const":"rolling"}},"required":["op"]},"then":{"required":["window"]}},{"if":{"properties":{"op":{"const":"seasonality"}},"required":["op"]},"then":{"properties":{"grain":{"enum":["month","quarter"]}}}},{"if":{"properties":{"op":{"const":"percentile"}},"required":["op"]},"then":{"required":["column","q"]}},{"if":{"properties":{"op":{"const":"cohort"}},"required":["op"]},"then":{"required":["id_col","date_col"]}},{"if":{"properties":{"op":{"const":"correlation_matrix"}},"required":["op"]},"then":{"required":["columns"]}},{"if":{"properties":{"op":{"const":"join_on"}},"required":["op"]},"then":{"required":["on"]}},{"if":{"properties":{"op":{"const":"compare_series"}},"required":["op"]},"then":{"anyOf":[{"required":["date_col","a_value","b_value"]},{"required":["left","right"]}]}},{"if":{"properties":{"op":{"enum":["numeric_summary","outliers_iqr","currency_mix"]}},"required":["op"]},"then":{"required":["column"]}},{"if":{"properties":{"op":{"const":"filter_rows"}},"required":["op"]},"then":{"required":["filters"]}}],"additionalProperties":false}} \ No newline at end of file +{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://data-toolkit.local/schemas/analysis-plan.schema.json","title":"Data Analyse operations","type":"array","items":{"type":"object","required":["op"],"properties":{"op":{"enum":["numeric_summary","outliers_iqr","breakdown","period_series","ageing","currency_mix","concentration","pivot","distribution","trend","percentile","cohort","correlation_matrix","rolling","gini","seasonality","join_on","compare_series","filter_rows"]},"name":{"type":"string"},"column":{"type":"string","minLength":1},"by":{"type":"string","minLength":1},"value":{"type":"string"},"top":{"type":"integer","minimum":1},"top_n":{"type":"integer","minimum":1},"k":{"type":["number","string"]},"cap":{"type":"integer","minimum":1},"date_col":{"type":"string","minLength":1},"grain":{"enum":["month","quarter","year"]},"dayfirst":{"type":"boolean"},"as_of":{"type":"string","minLength":1},"buckets":{"type":"array","minItems":1,"items":{"type":"integer","minimum":0}},"rows_col":{"type":"string","minLength":1},"cols_col":{"type":"string","minLength":1},"aggfunc":{"enum":["sum","count","mean"]},"q":{"oneOf":[{"type":"number","minimum":0,"maximum":1},{"type":"array","minItems":1,"items":{"type":"number","minimum":0,"maximum":1}}]},"id_col":{"type":"string","minLength":1},"columns":{"type":"array","minItems":2,"items":{"type":"string","minLength":1}},"window":{"type":"integer","minimum":1},"func":{"enum":["mean","sum","median"]},"on":{"oneOf":[{"type":"string","minLength":1},{"type":"array","minItems":1,"items":{"type":"string","minLength":1}}]},"how":{"enum":["inner","left"]},"a_value":{"type":"string","minLength":1},"b_value":{"type":"string","minLength":1},"a_label":{"type":"string","minLength":1},"b_label":{"type":"string","minLength":1},"left":{"type":"object","required":["date_col"],"properties":{"date_col":{"type":"string","minLength":1},"value":{"type":"string"},"dayfirst":{"type":"boolean"}},"additionalProperties":false},"right":{"type":"object","required":["date_col"],"properties":{"date_col":{"type":"string","minLength":1},"value":{"type":"string"},"dayfirst":{"type":"boolean"}},"additionalProperties":false},"filters":{"type":"array","minItems":1,"items":{"type":"object","required":["op"],"properties":{"col":{"type":"string","minLength":1},"column":{"type":"string","minLength":1},"op":{"type":"string"},"value":{},"values":{"type":"array"},"lo":{},"hi":{}},"additionalProperties":false}}},"allOf":[{"if":{"properties":{"op":{"enum":["numeric_summary","outliers_iqr","currency_mix","distribution"]}},"required":["op"]},"then":{"required":["column"]}},{"if":{"properties":{"op":{"const":"breakdown"}},"required":["op"]},"then":{"required":["by"]}},{"if":{"properties":{"op":{"const":"period_series"}},"required":["op"]},"then":{"required":["date_col"]}},{"if":{"properties":{"op":{"const":"ageing"}},"required":["op"]},"then":{"required":["date_col","as_of"]}},{"if":{"properties":{"op":{"enum":["concentration","gini"]}},"required":["op"]},"then":{"anyOf":[{"required":["column"]},{"required":["by"]}]}},{"if":{"properties":{"op":{"const":"pivot"}},"required":["op"]},"then":{"required":["rows_col","cols_col"]}},{"if":{"properties":{"op":{"enum":["trend","rolling","seasonality"]}},"required":["op"]},"then":{"required":["date_col"]}},{"if":{"properties":{"op":{"const":"rolling"}},"required":["op"]},"then":{"required":["window"]}},{"if":{"properties":{"op":{"const":"seasonality"}},"required":["op"]},"then":{"properties":{"grain":{"enum":["month","quarter"]}}}},{"if":{"properties":{"op":{"const":"percentile"}},"required":["op"]},"then":{"required":["column","q"]}},{"if":{"properties":{"op":{"const":"cohort"}},"required":["op"]},"then":{"required":["id_col","date_col"]}},{"if":{"properties":{"op":{"const":"correlation_matrix"}},"required":["op"]},"then":{"required":["columns"]}},{"if":{"properties":{"op":{"const":"join_on"}},"required":["op"]},"then":{"required":["on"]}},{"if":{"properties":{"op":{"const":"compare_series"}},"required":["op"]},"then":{"anyOf":[{"required":["date_col","a_value","b_value"]},{"required":["left","right"]}]}},{"if":{"properties":{"op":{"const":"filter_rows"}},"required":["op"]},"then":{"required":["filters"]}}],"additionalProperties":false}} diff --git a/skills/data-visualise/scripts/viz.py b/skills/data-visualise/scripts/viz.py index d79fad1..2f23bef 100644 --- a/skills/data-visualise/scripts/viz.py +++ b/skills/data-visualise/scripts/viz.py @@ -1864,6 +1864,21 @@ def _render(spec: dict) -> str: show_last=spec.get("show_last", True), unit=spec.get("unit", "")) if kind == "waterfall": return waterfall(spec.get("steps", []), title=spec.get("title"), unit=spec.get("unit", "")) + if kind == "scatter_chart": + return scatter_chart(spec.get("x", []), spec.get("y", []), + title=spec.get("title"), + x_label=spec.get("x_label"), y_label=spec.get("y_label"), + unit_x=spec.get("unit_x", ""), + unit_y=spec.get("unit_y", spec.get("unit", "")), + labels=spec.get("labels"), + trend_line=spec.get("trend_line", False)) + if kind == "histogram": + return histogram(spec.get("values", []), bins=spec.get("bins", 10), + title=spec.get("title"), unit=spec.get("unit", "")) + if kind == "stacked_bar": + return stacked_bar(spec.get("data", {}), title=spec.get("title"), + unit=spec.get("unit", ""), + legend=spec.get("legend", True)) if kind == "section": return section(spec.get("title", ""), *[_render(c) for c in spec.get("blocks", [])]) if kind == "grid": diff --git a/tests/test_engine.py b/tests/test_engine.py index e1fbbce..f70f627 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -746,6 +746,10 @@ def test_suggest_blocks_proposes_stacked_bar_for_pivot(): block_types = [b["type"] for b in specs[0]["blocks"]] assert "heatmap" in block_types assert "stacked_bar" in block_types, f"expected stacked_bar, got {block_types}" + # suggest proposes it; blocks_from_analysis must actually render it (merge + # follow-up: the convenience helper lagged _viz_block / suggest). + html = viz.blocks_from_analysis(analysis) + assert any("