@@ -230,6 +230,7 @@ data "tech_data" source="data/tech.csv" {
230230 set "tech"
231231 set "wind_tech" {
232232 in "tech"
233+ // Spec: §9 — bare RHS is categorical literal, not column lookup.
233234 filter { tech == wind }
234235 }
235236}
@@ -264,6 +265,113 @@ scenario "S1" {
264265 Ok ( ( ) )
265266}
266267
268+ #[ test]
269+ fn semantic_validation_applies_mapped_column_set_filters ( ) -> Result < ( ) , Box < dyn std:: error:: Error > >
270+ {
271+ let root = temp_root ( "semantic-set-filter-mapped-column" ) ?;
272+ fs:: create_dir_all ( root. join ( "data" ) ) ?;
273+ fs:: write (
274+ root. join ( "data" ) . join ( "tech.csv" ) ,
275+ "technology\n wind\n solar\n wind\n " ,
276+ ) ?;
277+
278+ let path = root. join ( "input.kdl" ) ;
279+ let text = r#"
280+ data "tech_data" source="data/tech.csv" {
281+ map "tech" from="technology"
282+
283+ set "tech"
284+ set "wind_tech" {
285+ in "tech"
286+ // Spec: §9 — map applies to lhs, RHS bare token remains literal.
287+ filter { tech == wind }
288+ }
289+ }
290+
291+ model "Dispatch" {
292+ set "tech"
293+
294+ control "x" {
295+ index "tech"
296+ }
297+
298+ minimize "Obj" {
299+ sum(x[tech] for tech in tech)
300+ }
301+ }
302+
303+ scenario "S1" {
304+ use "Dispatch"
305+ }
306+ "# ;
307+
308+ let parsed = parse_program_text ( text, & path) ?;
309+ let semantic = validate_program ( & parsed. program , & path) ?;
310+
311+ let wind_tech = semantic
312+ . set_registry
313+ . get ( "wind_tech" )
314+ . ok_or ( "missing set wind_tech" ) ?;
315+ assert_eq ! ( wind_tech. values, vec![ "wind" . to_string( ) ] ) ;
316+
317+ fs:: remove_dir_all ( & root) ?;
318+ Ok ( ( ) )
319+ }
320+
321+ #[ test]
322+ fn semantic_validation_applies_set_filter_with_parent_alias_and_bare_rhs ( )
323+ -> Result < ( ) , Box < dyn std:: error:: Error > > {
324+ let root = temp_root ( "semantic-subset-filter-parent-bare-rhs" ) ?;
325+ fs:: create_dir_all ( root. join ( "data" ) ) ?;
326+ fs:: write (
327+ root. join ( "data" ) . join ( "assets.csv" ) ,
328+ "asset_name,zone_raw\n A,north\n B,south\n C,north\n " ,
329+ ) ?;
330+
331+ let path = root. join ( "input.kdl" ) ;
332+ let text = r#"
333+ data "assets_data" source="data/assets.csv" {
334+ map "asset_id" from="asset_name"
335+ map "zone" from="zone_raw"
336+
337+ set "asset_id" alias="a"
338+ set "south_assets" {
339+ in "a"
340+ // Spec: §9 — alias resolution and bare RHS semantics together.
341+ filter { zone == south }
342+ }
343+ }
344+
345+ model "Dispatch" {
346+ set "asset_id" alias="a"
347+
348+ control "x" {
349+ index "a"
350+ }
351+
352+ minimize "Obj" {
353+ sum(x[a] for a in asset_id)
354+ }
355+ }
356+
357+ scenario "S1" {
358+ use "Dispatch"
359+ }
360+ "# ;
361+
362+ let parsed = parse_program_text ( text, & path) ?;
363+ let semantic = validate_program ( & parsed. program , & path) ?;
364+
365+ let south_assets = semantic
366+ . set_registry
367+ . get ( "south_assets" )
368+ . ok_or ( "missing set south_assets" ) ?;
369+ assert_eq ! ( south_assets. values, vec![ "B" . to_string( ) ] ) ;
370+
371+ fs:: remove_dir_all ( & root) ?;
372+ Ok ( ( ) )
373+ }
374+
267375#[ test]
268376fn semantic_validation_applies_numeric_set_filters ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
269377 let root = temp_root ( "semantic-set-filter-numeric" ) ?;
0 commit comments