mikeboone Claude Sonnet 4.6 commited on
Commit
e92f76b
·
1 Parent(s): 368ee54

fix: stop all-rows-fail on numeric column type mismatch (22018)

Browse files

Root cause: Snowflake DESCRIBE TABLE returns FIXED(38,0) for INT columns
and REAL for FLOAT columns, but convert_value only checked for NUMBER/INT/FLOAT.
So string values (even valid ones like "0.035") passed through uncoerced to
Snowflake, which rejected them with 22018 on every row.

Three-part fix:
1. generator.py entity check: store float(entity_value) not the raw string,
and strip currency/unit chars before parsing. Add FIXED/REAL/DOUBLE/MONEY
to the is_numeric_col check.
2. legitdata_bridge.py convert_value: add FIXED/REAL/DOUBLE/MONEY to numeric
type check, and strip non-numeric chars from strings before float().
3. generic.py generate_key + generator.py _infer_strategy: add same types
so integer PKs and inferred strategies use numeric returns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

legitdata_bridge.py CHANGED
@@ -232,12 +232,18 @@ class KeyPairSnowflakeWriter:
232
  if isinstance(value, str) and len(value) > max_len:
233
  value = value[:max_len]
234
 
235
- # Clamp numbers to fit DECIMAL precision
236
- elif 'NUMBER' in data_type or 'DECIMAL' in data_type or 'NUMERIC' in data_type or 'INT' in data_type or 'FLOAT' in data_type:
237
- # If a string landed in a numeric column (AI misclassification), coerce or null it
 
 
 
 
238
  if isinstance(value, str):
 
 
239
  try:
240
- value = float(value)
241
  except (ValueError, TypeError):
242
  return None # Can't coerce — use NULL rather than crash
243
  precision = info.get('precision', 38)
 
232
  if isinstance(value, str) and len(value) > max_len:
233
  value = value[:max_len]
234
 
235
+ # Clamp numbers to fit DECIMAL precision.
236
+ # Includes Snowflake's internal DESCRIBE TABLE names: FIXED (integers), REAL (floats).
237
+ elif any(t in data_type for t in (
238
+ 'NUMBER', 'DECIMAL', 'NUMERIC', 'INT', 'FLOAT',
239
+ 'FIXED', 'REAL', 'DOUBLE', 'MONEY',
240
+ )):
241
+ # If a string landed in a numeric column, strip formatting and coerce
242
  if isinstance(value, str):
243
+ import re as _re
244
+ cleaned = _re.sub(r'[^\d.\-+eE]', '', value.replace(',', ''))
245
  try:
246
+ value = float(cleaned) if cleaned else None
247
  except (ValueError, TypeError):
248
  return None # Can't coerce — use NULL rather than crash
249
  precision = info.get('precision', 38)
legitdata_project/legitdata/generator.py CHANGED
@@ -935,16 +935,21 @@ class LegitGenerator:
935
  entity_value = entity[col_name]
936
  # Check if column is numeric but AI gave us text
937
  data_type_upper = (column.data_type or '').upper()
938
- is_numeric_col = any(t in data_type_upper for t in ('INT', 'NUMBER', 'NUMERIC', 'DECIMAL', 'BIGINT', 'SMALLINT', 'FLOAT'))
939
-
 
 
 
940
  if is_numeric_col:
941
- # Validate that the value is actually numeric
 
 
 
942
  try:
943
- float(entity_value)
944
- row[col_name] = entity_value
945
  continue
946
  except (ValueError, TypeError):
947
- # AI gave us text for a numeric column - fall through to generic generation
948
  if i == 0:
949
  print(f" [WARN] {col_name}: AI gave '{entity_value}' for numeric column, using inferred instead")
950
  pass # Fall through to generic generation below
@@ -1649,8 +1654,8 @@ class LegitGenerator:
1649
  if semantic_strategy:
1650
  return semantic_strategy
1651
 
1652
- # Check if data type is numeric
1653
- is_numeric = any(t in data_type for t in ('INT', 'NUMBER', 'NUMERIC', 'DECIMAL', 'BIGINT', 'SMALLINT'))
1654
 
1655
  # Key columns (CustomerKey, ProductKey, etc.)
1656
  if 'key' in col_lower and 'keyboard' not in col_lower:
 
935
  entity_value = entity[col_name]
936
  # Check if column is numeric but AI gave us text
937
  data_type_upper = (column.data_type or '').upper()
938
+ is_numeric_col = any(t in data_type_upper for t in (
939
+ 'INT', 'NUMBER', 'NUMERIC', 'DECIMAL', 'BIGINT', 'SMALLINT',
940
+ 'FLOAT', 'REAL', 'DOUBLE', 'FIXED', 'MONEY',
941
+ ))
942
+
943
  if is_numeric_col:
944
+ # Coerce to float and store as Python numeric, not string.
945
+ # Strip currency symbols / units before trying.
946
+ import re as _re
947
+ cleaned = _re.sub(r'[^\d.\-+eE]', '', str(entity_value).replace(',', ''))
948
  try:
949
+ row[col_name] = float(cleaned)
 
950
  continue
951
  except (ValueError, TypeError):
952
+ # AI gave us unparseable text fall through to generic
953
  if i == 0:
954
  print(f" [WARN] {col_name}: AI gave '{entity_value}' for numeric column, using inferred instead")
955
  pass # Fall through to generic generation below
 
1654
  if semantic_strategy:
1655
  return semantic_strategy
1656
 
1657
+ # Check if data type is numeric (includes Snowflake internal names FIXED/REAL)
1658
+ is_numeric = any(t in data_type for t in ('INT', 'NUMBER', 'NUMERIC', 'DECIMAL', 'BIGINT', 'SMALLINT', 'FLOAT', 'REAL', 'DOUBLE', 'FIXED', 'MONEY'))
1659
 
1660
  # Key columns (CustomerKey, ProductKey, etc.)
1661
  if 'key' in col_lower and 'keyboard' not in col_lower:
legitdata_project/legitdata/sourcer/generic.py CHANGED
@@ -189,7 +189,7 @@ class GenericSourcer:
189
  # Check if column expects a numeric type
190
  if data_type:
191
  data_type_upper = data_type.upper()
192
- if any(t in data_type_upper for t in ('INT', 'NUMBER', 'NUMERIC', 'DECIMAL', 'BIGINT', 'SMALLINT')):
193
  return self._key_counters[prefix]
194
 
195
  return f"{prefix}_{self._key_counters[prefix]:05d}"
 
189
  # Check if column expects a numeric type
190
  if data_type:
191
  data_type_upper = data_type.upper()
192
+ if any(t in data_type_upper for t in ('INT', 'NUMBER', 'NUMERIC', 'DECIMAL', 'BIGINT', 'SMALLINT', 'FIXED', 'REAL', 'DOUBLE')):
193
  return self._key_counters[prefix]
194
 
195
  return f"{prefix}_{self._key_counters[prefix]:05d}"