Skip to content

Commit 81940f0

Browse files
committed
refactor: make the gridded subset explicit about the spec
subset_gridded_to_2d and _spec_field_references now take a kind: Literal['vega-lite', 'deckgl'] instead of sniffing both grammars at once. The Vega-Lite path reads encoding field values and the deck.gl path reads @@= accessor expressions, and each agent passes its own kind.
1 parent 64355da commit 81940f0

4 files changed

Lines changed: 25 additions & 19 deletions

File tree

lumen/ai/agents/deck_gl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ async def respond(
300300
# DeckGL renders a static layer and cannot page a dimension, so once
301301
# the spec has picked its axes, collapse every gridded dim it does not
302302
# reference to a single slice (else a gridded source blows the row cap).
303-
pipeline = subset_gridded_to_2d(pipeline, full_dict.get("spec", {}))
303+
pipeline = subset_gridded_to_2d(pipeline, full_dict.get("spec", {}), "deckgl")
304304

305305
# Create view and output
306306
view = self.view_type(pipeline=pipeline, **full_dict)

lumen/ai/agents/vega_lite.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ async def respond(
630630
# Vega-Lite cannot page a dimension, so once the spec has picked its
631631
# axes, collapse every gridded dim it does not reference to a single
632632
# slice (keeps e.g. a lon/time Hovmoller, drops an unused time on a map).
633-
pipeline = subset_gridded_to_2d(pipeline, full_dict.get("spec", {}))
633+
pipeline = subset_gridded_to_2d(pipeline, full_dict.get("spec", {}), "vega-lite")
634634

635635
# Step 2: Show complete plot immediately
636636
view = self.view_type(pipeline=pipeline, **full_dict)

lumen/ai/utils.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1532,20 +1532,23 @@ def _is_regular(values) -> bool:
15321532
}
15331533

15341534

1535-
def _spec_field_references(spec: Any) -> set[str]:
1536-
"""Column names a generated view spec references: Vega-Lite encoding
1537-
``field`` values and deck.gl ``@@=`` accessor identifiers. Lets the
1538-
gridded subset keep the dims a spec actually plots (an axis, color, ...)
1539-
and collapse the rest, without hard-coding which dims are spatial.
1535+
def _spec_field_references(
1536+
spec: Any, kind: Literal['vega-lite', 'deckgl']
1537+
) -> set[str]:
1538+
"""Column names a generated view ``spec`` references, so the gridded subset
1539+
can keep the dims a spec actually plots (an axis, color, ...) and collapse
1540+
the rest. The two grammars reference columns differently, so the caller
1541+
states which it is: a Vega-Lite spec names columns in encoding ``field``
1542+
values, a deck.gl spec in ``@@=`` accessor expressions.
15401543
"""
15411544
refs: set[str] = set()
15421545

15431546
def walk(node):
15441547
if isinstance(node, dict):
15451548
for key, val in node.items():
1546-
if key == 'field' and isinstance(val, str):
1549+
if kind == 'vega-lite' and key == 'field' and isinstance(val, str):
15471550
refs.add(val)
1548-
elif isinstance(val, str) and val.startswith('@@='):
1551+
elif kind == 'deckgl' and isinstance(val, str) and val.startswith('@@='):
15491552
refs.update(re.findall(r'[A-Za-z_]\w*', val))
15501553
else:
15511554
walk(val)
@@ -1557,10 +1560,13 @@ def walk(node):
15571560
return refs
15581561

15591562

1560-
def subset_gridded_to_2d(pipeline: Pipeline, spec: Any) -> Pipeline:
1563+
def subset_gridded_to_2d(
1564+
pipeline: Pipeline, spec: Any, kind: Literal['vega-lite', 'deckgl']
1565+
) -> Pipeline:
15611566
"""Pin every gridded dimension the view ``spec`` does not reference to its
15621567
first value, so a view that cannot page a dimension (VegaLite, DeckGL)
1563-
renders a single 2D slice instead of the full grid.
1568+
renders a single 2D slice instead of the full grid. ``kind`` selects the
1569+
spec grammar (see :func:`_spec_field_references`).
15641570
15651571
Runs after the spec is generated, so the dims the model chose for x/y/color
15661572
are kept -- a lon/time Hovmoller works -- and only the leftover dims (e.g.
@@ -1571,7 +1577,7 @@ def subset_gridded_to_2d(pipeline: Pipeline, spec: Any) -> Pipeline:
15711577
if not md:
15721578
return pipeline
15731579
ds = pipeline.source.dataset
1574-
referenced = _spec_field_references(spec)
1580+
referenced = _spec_field_references(spec, kind)
15751581
collapse = [dim for dim in ds.dims if dim not in referenced]
15761582
if not collapse:
15771583
return pipeline

lumen/tests/ai/test_gridded.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -240,41 +240,41 @@ def test_gridded_metadata_stays_agnostic_about_spatial_dims(simple_dataset_3d_pi
240240

241241
def test_spec_field_references_vegalite_and_deckgl():
242242
"""Field references are pulled from Vega-Lite encoding fields and deck.gl
243-
``@@=`` accessors alike."""
243+
``@@=`` accessors, each under its own explicit ``kind``."""
244244
vega = {"encoding": {"x": {"field": "lon"}, "y": {"field": "time"},
245245
"color": {"field": "air"}}}
246-
assert _spec_field_references(vega) == {"lon", "time", "air"}
246+
assert _spec_field_references(vega, "vega-lite") == {"lon", "time", "air"}
247247
deck = {"layers": [{"getPosition": "@@=[lon, lat]", "getElevation": "@@=air"}]}
248-
assert {"lon", "lat", "air"} <= _spec_field_references(deck)
248+
assert {"lon", "lat", "air"} <= _spec_field_references(deck, "deckgl")
249249

250250

251251
def test_subset_collapses_dims_absent_from_spec(simple_dataset_3d_pipeline):
252252
"""Dims the spec does not reference (here time on a lon/lat map) collapse to
253253
a single slice; the spec drives the subset, not a name guess."""
254254
spec = {"encoding": {"x": {"field": "lon"}, "y": {"field": "lat"}}}
255255
n_full = len(simple_dataset_3d_pipeline.data)
256-
sub = subset_gridded_to_2d(simple_dataset_3d_pipeline, spec)
256+
sub = subset_gridded_to_2d(simple_dataset_3d_pipeline, spec, "vega-lite")
257257
assert len(sub.data) < n_full
258258
assert sub.data["time"].nunique() == 1
259259

260260

261261
def test_subset_keeps_dims_the_spec_uses(simple_dataset_3d_pipeline):
262262
"""A lon/time spec (Hovmoller) keeps time as an axis; lat collapses instead."""
263263
spec = {"encoding": {"x": {"field": "lon"}, "y": {"field": "time"}}}
264-
sub = subset_gridded_to_2d(simple_dataset_3d_pipeline, spec)
264+
sub = subset_gridded_to_2d(simple_dataset_3d_pipeline, spec, "vega-lite")
265265
assert sub.data["time"].nunique() > 1
266266
assert sub.data["lat"].nunique() == 1
267267

268268

269269
def test_subset_noop_when_spec_uses_all_dims(xarray_pipeline):
270270
"""When the spec references every dim, the pipeline is returned unchanged."""
271271
spec = {"encoding": {"x": {"field": "lon"}, "y": {"field": "lat"}}}
272-
assert subset_gridded_to_2d(xarray_pipeline, spec) is xarray_pipeline
272+
assert subset_gridded_to_2d(xarray_pipeline, spec, "vega-lite") is xarray_pipeline
273273

274274

275275
def test_subset_noop_for_tabular(tabular_pipeline):
276276
"""A non-xarray pipeline is returned unchanged."""
277-
assert subset_gridded_to_2d(tabular_pipeline, {}) is tabular_pipeline
277+
assert subset_gridded_to_2d(tabular_pipeline, {}, "deckgl") is tabular_pipeline
278278

279279

280280
def test_hvplot_prompt_no_gridded_rules_for_tabular():

0 commit comments

Comments
 (0)