srmsoumya commited on
Commit
582d1ab
·
1 Parent(s): 3ba9557

Randomize candidate dataset order

Browse files
dataset/README.md CHANGED
@@ -103,7 +103,7 @@ Verify:
103
 
104
  ```bash
105
  modal volume ls gazet-data
106
- # should show: overture/, overture_normalized/, natural_earth_geoparquet/, natural_earth_normalized/
107
  ```
108
 
109
  Skip this step on subsequent runs — the volume persists across runs.
 
103
 
104
  ```bash
105
  modal volume ls gazet-data
106
+ # should show: overture_normalized/, natural_earth_normalized/
107
  ```
108
 
109
  Skip this step on subsequent runs — the volume persists across runs.
dataset/scripts/export_training_data.py CHANGED
@@ -20,6 +20,7 @@ Output layout (all paths relative to dataset/):
20
  output/runs/{run_name}/stats.json
21
  """
22
 
 
23
  import json
24
  import random
25
  import sys
@@ -178,6 +179,23 @@ def _format_sql(sql: str) -> str:
178
  ).strip()
179
 
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  def sample_to_sql_pair(sample: Dict[str, Any]) -> Optional[Dict]:
182
  """Convert a raw sample to a conversational prompt-completion pair for SQL generation."""
183
  sql = sample.get("target", {}).get("sql", "").strip()
@@ -360,7 +378,9 @@ def main(config_path: Optional[Path] = None) -> None:
360
  # Load
361
  print("\nLoading validated samples...")
362
  samples = load_samples(validated_file)
 
363
  print(f" {len(samples):,} samples loaded")
 
364
 
365
  # Split once, reuse for both tasks
366
  print("\nSplitting 80 / 10 / 10 (stratified by task family)...")
 
20
  output/runs/{run_name}/stats.json
21
  """
22
 
23
+ import copy
24
  import json
25
  import random
26
  import sys
 
179
  ).strip()
180
 
181
 
182
+ def _shuffle_candidates_for_export(sample: Dict[str, Any]) -> Dict[str, Any]:
183
+ """Return a copy of sample with candidate row order shuffled deterministically.
184
+
185
+ Candidate IDs remain unchanged; only row order changes. This removes the
186
+ positional shortcut where the true anchor often appears first in the raw
187
+ synthetic samples, while keeping selected_candidates valid.
188
+ """
189
+ shuffled = copy.deepcopy(sample)
190
+ candidates = shuffled.get("candidates", [])
191
+ if len(candidates) <= 1:
192
+ return shuffled
193
+
194
+ rng = random.Random(shuffled.get("id", "sample"))
195
+ rng.shuffle(candidates)
196
+ return shuffled
197
+
198
+
199
  def sample_to_sql_pair(sample: Dict[str, Any]) -> Optional[Dict]:
200
  """Convert a raw sample to a conversational prompt-completion pair for SQL generation."""
201
  sql = sample.get("target", {}).get("sql", "").strip()
 
378
  # Load
379
  print("\nLoading validated samples...")
380
  samples = load_samples(validated_file)
381
+ samples = [_shuffle_candidates_for_export(s) for s in samples]
382
  print(f" {len(samples):,} samples loaded")
383
+ print(" Candidate row order shuffled deterministically for export")
384
 
385
  # Split once, reuse for both tasks
386
  print("\nSplitting 80 / 10 / 10 (stratified by task family)...")
dataset/scripts/generate_samples.py CHANGED
@@ -416,6 +416,35 @@ def _merge_candidate_lists(
416
  return merged
417
 
418
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  def build_candidate_list(
420
  con: duckdb.DuckDBPyConnection,
421
  anchor_id: str,
@@ -1008,19 +1037,29 @@ def generate_template_based_sample(
1008
  )
1009
 
1010
  elif template.template_id in ("contain_multi_01", "contain_multi_02", "contain_multi_03"):
1011
- # country IN clause — 2 or 3 anchors, each contributes its country code
 
 
1012
  num_a = 3 if template.template_id == "contain_multi_02" else 2
 
 
 
 
 
 
 
 
 
1013
  anchors = [
1014
- sample_random_entity(
1015
- con,
1016
- tables['divisions_area_inventory'],
1017
- 'divisions_area',
1018
- subtypes={'country'},
1019
- )
1020
- for _ in range(num_a)
 
1021
  ]
1022
- if any(a is None for a in anchors):
1023
- return None
1024
 
1025
  countries = [a.get('country') or 'US' for a in anchors]
1026
  target_subtype = template.target_subtype or 'region'
@@ -1039,7 +1078,10 @@ def generate_template_based_sample(
1039
  num_candidates=per_anchor, difficulty="medium")
1040
  for a in anchors
1041
  ]
1042
- candidates = _merge_candidate_lists(*cands, max_total=num_a * per_anchor)
 
 
 
1043
 
1044
  q_kwargs = dict(target_subtype=target_subtype)
1045
  for i, a in enumerate(anchors, 1):
@@ -1629,7 +1671,12 @@ def generate_template_based_sample(
1629
  template.anchor_source == "mixed" and template.num_anchors == 2
1630
  )
1631
 
1632
- if template.family in _multi_anchor_families and template.num_anchors >= 2:
 
 
 
 
 
1633
  anchor_ids: set = set()
1634
  for var in ("anchor1", "anchor2", "anchor3"):
1635
  obj = locals().get(var)
@@ -1641,11 +1688,6 @@ def generate_template_based_sample(
1641
  anchor_ids.add(a.get("id", ""))
1642
  selected_candidate_ids = [c.candidate_id for c in candidates if c.id in anchor_ids]
1643
 
1644
- elif _is_mixed_two_anchor:
1645
- # partial_05 / diff_02: anchor (division) + clip_feature (natural_earth)
1646
- mixed_ids = {anchor.get("id", ""), clip_feature.get("id", "")}
1647
- selected_candidate_ids = [c.candidate_id for c in candidates if c.id in mixed_ids]
1648
-
1649
  else:
1650
  anchor_id_to_find = (
1651
  anchor.get('anchor_id')
 
416
  return merged
417
 
418
 
419
+ def _dedupe_country_candidates(
420
+ candidates: List[Candidate],
421
+ max_total: Optional[int] = None,
422
+ ) -> List[Candidate]:
423
+ """Deduplicate country candidates by country code, preserving first match.
424
+
425
+ This is useful for templates whose SQL uses ``country IN (...)`` rather
426
+ than candidate IDs. Overture can contain multiple country-level rows for
427
+ the same ISO code, which weakens grounding if they all remain in the list.
428
+ """
429
+ deduped: List[Candidate] = []
430
+ seen_keys: set[tuple[str, str]] = set()
431
+ for cand in candidates:
432
+ if cand.subtype == "country" and cand.country:
433
+ key = ("country", cand.country)
434
+ else:
435
+ key = ("id", cand.id)
436
+ if key in seen_keys:
437
+ continue
438
+ deduped.append(cand)
439
+ seen_keys.add(key)
440
+ if max_total is not None and len(deduped) >= max_total:
441
+ break
442
+
443
+ for i, cand in enumerate(deduped, 1):
444
+ cand.candidate_id = f"c{i}"
445
+ return deduped
446
+
447
+
448
  def build_candidate_list(
449
  con: duckdb.DuckDBPyConnection,
450
  anchor_id: str,
 
1037
  )
1038
 
1039
  elif template.template_id in ("contain_multi_01", "contain_multi_02", "contain_multi_03"):
1040
+ # country IN clause — 2 or 3 anchors, each contributes its country code.
1041
+ # Sample unique countries so the query actually teaches a multi-country
1042
+ # pattern rather than repeating the same ISO code multiple times.
1043
  num_a = 3 if template.template_id == "contain_multi_02" else 2
1044
+ country_inventory = tables['divisions_area_inventory']
1045
+ country_inventory = country_inventory[
1046
+ (country_inventory['subtype'] == 'country')
1047
+ & country_inventory['country'].notna()
1048
+ ].drop_duplicates(subset=['country'])
1049
+ if len(country_inventory) < num_a:
1050
+ return None
1051
+
1052
+ sampled = country_inventory.sample(n=num_a, replace=False)
1053
  anchors = [
1054
+ {
1055
+ 'id': row['id'],
1056
+ 'name': row['name'],
1057
+ 'subtype': row.get('subtype'),
1058
+ 'country': row.get('country'),
1059
+ 'source': 'divisions_area',
1060
+ }
1061
+ for _, row in sampled.iterrows()
1062
  ]
 
 
1063
 
1064
  countries = [a.get('country') or 'US' for a in anchors]
1065
  target_subtype = template.target_subtype or 'region'
 
1078
  num_candidates=per_anchor, difficulty="medium")
1079
  for a in anchors
1080
  ]
1081
+ candidates = _dedupe_country_candidates(
1082
+ _merge_candidate_lists(*cands, max_total=num_a * per_anchor),
1083
+ max_total=num_a * per_anchor,
1084
+ )
1085
 
1086
  q_kwargs = dict(target_subtype=target_subtype)
1087
  for i, a in enumerate(anchors, 1):
 
1671
  template.anchor_source == "mixed" and template.num_anchors == 2
1672
  )
1673
 
1674
+ if _is_mixed_two_anchor:
1675
+ # partial_05 / diff_02: anchor (division) + clip_feature (natural_earth)
1676
+ mixed_ids = {anchor.get("id", ""), clip_feature.get("id", "")}
1677
+ selected_candidate_ids = [c.candidate_id for c in candidates if c.id in mixed_ids]
1678
+
1679
+ elif template.family in _multi_anchor_families and template.num_anchors >= 2:
1680
  anchor_ids: set = set()
1681
  for var in ("anchor1", "anchor2", "anchor3"):
1682
  obj = locals().get(var)
 
1688
  anchor_ids.add(a.get("id", ""))
1689
  selected_candidate_ids = [c.candidate_id for c in candidates if c.id in anchor_ids]
1690
 
 
 
 
 
 
1691
  else:
1692
  anchor_id_to_find = (
1693
  anchor.get('anchor_id')