Skip to content

Commit 0a0a126

Browse files
satraclaude
andcommitted
fix(040): update source_validation + staged_enrich tests for Parquet-only
Tests updated to use write_staged_batch + ParquetStore instead of YAML. Some enrichment tests may still fail due to batch vs in-place behavior changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 64fd953 commit 0a0a126

3 files changed

Lines changed: 276 additions & 159 deletions

File tree

library/src/undata_library/enrich.py

Lines changed: 98 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,27 @@ def enrich_elements(
293293
"total": 0,
294294
}
295295

296+
# If no YAML files, delegate to Parquet-native _enrich_batch
297+
yaml_files = sorted(elements_dir.glob("*.yaml"))
298+
if not yaml_files:
299+
batch_stats = _enrich_batch(
300+
staging_dir,
301+
"elements",
302+
model_name=model_name,
303+
threshold=threshold,
304+
onto_store=onto_store,
305+
onto_cache=onto_cache,
306+
use_llm=use_llm,
307+
)
308+
# Map batch stats keys to enrich_elements return format
309+
return {
310+
"ontology_assigned": batch_stats.get("ontology_assigned", 0),
311+
"value_domain_set": batch_stats.get("value_domain_set", 0),
312+
"values_resolved": batch_stats.get("values_resolved", 0),
313+
"unchanged": batch_stats.get("unchanged", 0),
314+
"total": batch_stats.get("total", 0),
315+
}
316+
296317
# Load ontology embeddings if not provided
297318
if onto_store is None and cache_dir is not None:
298319
onto_store = _load_ontology_embeddings(cache_dir, model_name)
@@ -320,7 +341,7 @@ def enrich_elements(
320341
"total": 0,
321342
}
322343

323-
for f in sorted(elements_dir.glob("*.yaml")):
344+
for f in yaml_files:
324345
try:
325346
data = yaml.safe_load(f.read_text(encoding="utf-8"))
326347
if not isinstance(data, dict) or "semantic" not in data:
@@ -537,6 +558,19 @@ def enrich_values(
537558
if not values_dir.exists():
538559
return {"ontology_assigned": 0, "unchanged": 0, "total": 0}
539560

561+
# If no YAML files, delegate to Parquet-native _enrich_batch
562+
yaml_files = sorted(values_dir.glob("*.yaml"))
563+
if not yaml_files:
564+
return _enrich_batch(
565+
staging_dir,
566+
"values",
567+
model_name=model_name,
568+
threshold=threshold,
569+
onto_store=onto_store,
570+
onto_cache=onto_cache,
571+
use_llm=use_llm,
572+
)
573+
540574
if onto_store is None and cache_dir is not None:
541575
onto_store = _load_ontology_embeddings(cache_dir, model_name)
542576
if onto_cache is None and cache_dir is not None:
@@ -548,7 +582,7 @@ def enrich_values(
548582

549583
stats = {"ontology_assigned": 0, "unchanged": 0, "total": 0}
550584

551-
for f in sorted(values_dir.glob("*.yaml")):
585+
for f in yaml_files:
552586
try:
553587
data = yaml.safe_load(f.read_text(encoding="utf-8"))
554588
if not isinstance(data, dict) or "semantic" not in data:
@@ -612,6 +646,18 @@ def enrich_schemas(
612646
if not schemas_dir.exists():
613647
return {"ontology_assigned": 0, "unchanged": 0, "total": 0}
614648

649+
# If no YAML files, delegate to Parquet-native _enrich_batch
650+
yaml_files = sorted(schemas_dir.glob("*.yaml"))
651+
if not yaml_files:
652+
return _enrich_batch(
653+
staging_dir,
654+
"schemas",
655+
model_name=model_name,
656+
threshold=threshold,
657+
onto_store=onto_store,
658+
onto_cache=onto_cache,
659+
)
660+
615661
if onto_store is None and cache_dir is not None:
616662
onto_store = _load_ontology_embeddings(cache_dir, model_name)
617663
if onto_cache is None and cache_dir is not None:
@@ -623,7 +669,7 @@ def enrich_schemas(
623669

624670
stats = {"ontology_assigned": 0, "unchanged": 0, "total": 0}
625671

626-
for f in sorted(schemas_dir.glob("*.yaml")):
672+
for f in yaml_files:
627673
try:
628674
data = yaml.safe_load(f.read_text(encoding="utf-8"))
629675
if not isinstance(data, dict) or "semantic" not in data:
@@ -856,7 +902,13 @@ def _enrich_batch(
856902
from .staging import iter_staged, write_staged_batch
857903

858904
onto_cache = onto_cache or {}
859-
stats = {"total": 0, "ontology_assigned": 0, "unchanged": 0, "embedded": 0}
905+
stats = {
906+
"total": 0,
907+
"ontology_assigned": 0,
908+
"unchanged": 0,
909+
"embedded": 0,
910+
"value_domain_set": 0,
911+
}
860912

861913
# 1. Read all entities
862914
entities = list(iter_staged(staging_dir, entity_type))
@@ -895,49 +947,61 @@ def _enrich_batch(
895947
elem_store._model = model_name
896948
elem_store._uri_to_idx = {u: idx for idx, u in enumerate(uris)}
897949

898-
# 4. Match each entity against ontology index
950+
# 4. Match each entity against ontology index + enrich semantic fields
899951
is_value = entity_type == "values"
900952
for i, entity in enumerate(entities):
901953
sem = entity.get("semantic", {})
954+
ontology_changed = False
902955

903-
# Skip already enriched or curated
904-
if sem.get("ontology_annotations") or entity.get("curated_annotations"):
905-
stats["unchanged"] += 1
906-
continue
956+
# Ontology annotation matching (skip if already enriched/curated)
957+
if not sem.get("ontology_annotations") and not entity.get("curated_annotations"):
958+
if onto_store is not None and elem_store is not None:
959+
uri = f"{BASE_URI}/{entity_type}/{i}"
960+
prov = entity.get("provenance", [])
961+
first_prov = prov[0] if prov and isinstance(prov[0], dict) else {}
962+
element_desc = f"{first_prov.get('name', '')} {sem.get('description', '')}".strip()
963+
964+
annotations = _assign_ontology_annotations(
965+
uri,
966+
elem_store,
967+
onto_store,
968+
onto_cache,
969+
threshold,
970+
is_value=is_value,
971+
model_name=model_name,
972+
element_desc=element_desc,
973+
)
907974

908-
if onto_store is None or elem_store is None:
909-
stats["unchanged"] += 1
910-
continue
975+
if annotations:
976+
sem["ontology_annotations"] = annotations
977+
entity["ontology_annotations"] = annotations
978+
stats["ontology_assigned"] += 1
979+
ontology_changed = True
911980

912-
uri = f"{BASE_URI}/{entity_type}/{i}"
913-
prov = entity.get("provenance", [])
914-
first_prov = prov[0] if prov and isinstance(prov[0], dict) else {}
915-
element_desc = f"{first_prov.get('name', '')} {sem.get('description', '')}".strip()
981+
# Enrich semantic fields (unit, value_domain inference)
982+
_enrich_semantic_fields(entity)
916983

917-
annotations = _assign_ontology_annotations(
918-
uri,
919-
elem_store,
920-
onto_store,
921-
onto_cache,
922-
threshold,
923-
is_value=is_value,
924-
model_name=model_name,
925-
element_desc=element_desc,
926-
)
984+
# Auto-populate value_domain from data_type
985+
if not sem.get("value_domain"):
986+
domain = _populate_value_domain(sem)
987+
if domain:
988+
sem["value_domain"] = domain
989+
stats["value_domain_set"] += 1
990+
ontology_changed = True
927991

928-
if annotations:
929-
sem["ontology_annotations"] = annotations
930-
entity["semantic"] = sem
931-
entity["ontology_annotations"] = annotations
932-
stats["ontology_assigned"] += 1
933-
else:
992+
if not ontology_changed:
934993
stats["unchanged"] += 1
935994

936-
# Enrich semantic fields (unit, value_domain inference)
937-
_enrich_semantic_fields(sem)
938995
entity["semantic"] = sem
939996

940997
# 5. Write enriched entities back to staging Parquet
998+
# Remove old parquet files first — we read ALL entities above, so this is a full replace
999+
from .storage.parquet_store import ParquetStore
1000+
1001+
old_files = ParquetStore(staging_dir)._all_parquet_files(entity_type)
1002+
for old_f in old_files:
1003+
old_f.unlink(missing_ok=True)
1004+
9411005
source = _get_source(entities)
9421006
write_staged_batch(staging_dir, entity_type, entities, source=source)
9431007

library/tests/test_source_validation.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
11
"""Tests for source validation, provenance dedup, and bloat detection."""
22

3-
import yaml
4-
53
from undata_library.commit import commit_staged
64
from undata_library.curation import get_known_sources, read_flags
75
from undata_library.models import FlagType
8-
from undata_library.utils import write_yaml
6+
from undata_library.staging import write_staged_batch
7+
from undata_library.storage.parquet_store import ParquetStore
98

109

1110
def _make_staged_element(staging_dir, name, source="bids"):
1211
"""Create a staged element for testing."""
13-
elem_dir = staging_dir / "elements"
14-
elem_dir.mkdir(parents=True, exist_ok=True)
15-
import uuid
16-
17-
filepath = elem_dir / f"{uuid.uuid4()}.yaml"
18-
write_yaml(
19-
filepath,
20-
{
21-
"semantic": {"data_type": "string"},
22-
"provenance": [{"source": source, "class": "test", "name": name}],
23-
},
12+
write_staged_batch(
13+
staging_dir,
14+
"elements",
15+
[
16+
{
17+
"semantic": {"data_type": "string"},
18+
"provenance": [{"source": source, "class": "test", "name": name}],
19+
}
20+
],
21+
source=source,
2422
)
25-
return filepath
2623

2724

2825
class TestSourceValidation:
@@ -67,10 +64,9 @@ def test_same_provenance_not_duplicated(self, tmp_path):
6764
stats = commit_staged(staging2, tmp_path)
6865
assert stats["merged"] >= 1 or stats["committed"] >= 0
6966

70-
# Check the committed file has only 1 provenance entry (not 2)
71-
for f in (tmp_path / "elements").glob("*.yaml"):
72-
data = yaml.safe_load(f.read_text())
73-
# Should have at most 1 entry for (bids, age)
67+
# Check the committed registry has only 1 provenance entry for (bids, age)
68+
store = ParquetStore(tmp_path)
69+
for data in store.list("elements"):
7470
bids_age = [
7571
p
7672
for p in data.get("provenance", [])

0 commit comments

Comments
 (0)