tablassert 14.0.0__tar.gz → 15.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tablassert-14.0.0 → tablassert-15.0.0}/PKG-INFO +1 -1
- {tablassert-14.0.0 → tablassert-15.0.0}/pyproject.toml +1 -1
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/agent.py +6 -12
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/biolink.py +4 -9
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/lib.py +24 -4
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/models.py +41 -1
- {tablassert-14.0.0 → tablassert-15.0.0}/LICENSE +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/README.md +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/rust/Cargo.lock +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/rust/Cargo.toml +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/rust/examples/count_tables.rs +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/rust/src/fullmap.rs +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/rust/src/json.rs +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/rust/src/lib.rs +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/rust/src/ndjson.rs +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/rust/src/uuid.rs +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/rust/tests/build_golden.rs +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/rust/tests/common/mod.rs +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/rust/tests/extract_prebuilt.rs +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/__init__.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/_lazy.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/cli.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/coerce.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/enums.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/errors.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/extras.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/fullmap.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/graph_target.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/ingests.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/log.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/nlp.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/progress.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/qc.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/rig.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/rs.pyi +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/study.py +0 -0
- {tablassert-14.0.0 → tablassert-15.0.0}/src/tablassert/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tablassert"
|
|
3
|
-
version = "
|
|
3
|
+
version = "15.0.0"
|
|
4
4
|
description = "Extract knowledge assertions from tabular data into NCATS Translator-compliant KGX NDJSON — declaratively, with entity resolution and quality control built in."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Skye Lane Goetz", email = "sgoetz@isbscience.org" }
|
|
@@ -1299,11 +1299,11 @@ def _biolink_report(nodes: Path, edges: Path) -> dict[str, object]:
|
|
|
1299
1299
|
Wraps ``biolink.validate_kgx`` (the same check ``tablassert validate-kgx`` runs) into the
|
|
1300
1300
|
flat, JSON-safe keys ``build_and_audit`` returns, plus the ``_notes`` list the caller
|
|
1301
1301
|
folds into its own. ``biolink_valid_pct`` excludes the known-pending fields Tablassert
|
|
1302
|
-
emits on purpose (
|
|
1303
|
-
|
|
1304
|
-
``effect_type`` when biolink-model 4.4.4 shipped them as real
|
|
1305
|
-
scored number reflects the agent's decisions rather than a
|
|
1306
|
-
``biolink_valid_pct_strict`` keeps that gap visible.
|
|
1302
|
+
emits on purpose (the KGX denormalized carryovers -- the set is derived, and emptied
|
|
1303
|
+
itself of ``approval_ids`` when the curated pass-through override was removed and of
|
|
1304
|
+
``effect_size`` / ``effect_type`` when biolink-model 4.4.4 shipped them as real
|
|
1305
|
+
Association slots) so the scored number reflects the agent's decisions rather than a
|
|
1306
|
+
deliberate gap; ``biolink_valid_pct_strict`` keeps that gap visible.
|
|
1307
1307
|
|
|
1308
1308
|
Never raises: an unreadable or unparseable artifact degrades to ``None`` metrics and a
|
|
1309
1309
|
note, exactly like the coverage measurement above it.
|
|
@@ -2349,19 +2349,13 @@ qualifier and evidence slot the specific class declared. build_and_audit reports
|
|
|
2349
2349
|
`effect_type`), `q value` / `padj` -> `adjusted_p_value`. Names nothing claims
|
|
2350
2350
|
(`fold_change` alone, `z_score`, `lfsr`, `standard_error`, free-form notes) are folded
|
|
2351
2351
|
into `supporting_text`. Prefer `p_value`, `adjusted_p_value`, `effect_size`,
|
|
2352
|
-
`effect_type`, `has_evidence`.
|
|
2353
|
-
translator-ingest pass-through: keep the pipe-joined value as a scalar and do not add
|
|
2354
|
-
`split_by`.
|
|
2352
|
+
`effect_type`, `has_evidence`.
|
|
2355
2353
|
- MULTIVALUED slots (`has_evidence` and friends) take a real JSON array, never a joined string:
|
|
2356
2354
|
`split_by` is the ONLY multivalued encoding — there is no literal-list method. INSPECT the
|
|
2357
2355
|
column's cells first (read_table shows them); the separator they ACTUALLY use — `|`, `,`, or
|
|
2358
2356
|
`;` — is the one you declare: `{method: column, encoding: <letter>, split_by: "<separator>"}`.
|
|
2359
2357
|
A SINGLE-value cell gets NO `split_by`: its scalar wraps into a one-element array, the correct
|
|
2360
2358
|
shape. Cells that DO join multiple values but OMIT `split_by` ship as one unusable joined blob.
|
|
2361
|
-
- `approval_ids` is a deliberate translator-ingest pass-through extra, EXEMPT from the
|
|
2362
|
-
validity score: a `biolink_valid_pct` below 1.0 is never caused by it. (`effect_size` /
|
|
2363
|
-
`effect_type` were exempt only until biolink-model 4.4.4 shipped them as real Association
|
|
2364
|
-
slots; they now validate like any other slot.)
|
|
2365
2359
|
- QUALIFIERS: enum-ranged qualifiers take a literal TOKEN, never a CURIE
|
|
2366
2360
|
(`object_direction_qualifier: increased`, not a UMLS id), and `species_context_qualifier` is
|
|
2367
2361
|
disabled — never author it as a qualifier or annotation.
|
|
@@ -396,11 +396,6 @@ def resolve_association_class(category: str, predicate: str) -> type[Any]:
|
|
|
396
396
|
# - ``taxon`` -- a node property; no species-context edge is synthesized from it.
|
|
397
397
|
TABLASERT_EDGE_EXTRAS: frozenset[str] = frozenset(
|
|
398
398
|
[
|
|
399
|
-
# FDA application numbers from translator-ingests (DAKP precedent): the ingest
|
|
400
|
-
# emits them as a pipe-joined scalar (e.g. ``"011111|022222"``) with no
|
|
401
|
-
# ``split_by``, so the curated extra carries the scalar to the final edge
|
|
402
|
-
# verbatim instead of folding it into ``supporting_text``.
|
|
403
|
-
"approval_ids",
|
|
404
399
|
"broad_synonym",
|
|
405
400
|
"equivalent_identifiers",
|
|
406
401
|
"evidence_direction",
|
|
@@ -574,9 +569,9 @@ against the resolved class is done by ``lib.prune_to_class()``.
|
|
|
574
569
|
KNOWN_PENDING_EDGE_FIELDS: frozenset[str] = TABLASERT_EDGE_EXTRAS - frozenset(_association_model_fields())
|
|
575
570
|
"""Curated edge extras the installed Biolink Model does not (yet) declare on any association.
|
|
576
571
|
|
|
577
|
-
Tablassert emits these deliberately --
|
|
578
|
-
|
|
579
|
-
|
|
572
|
+
Tablassert emits these deliberately -- the KGX denormalized carryovers (``synonym``,
|
|
573
|
+
``xref``, ``relation``, ...) -- so a Biolink class rejects them as ``extra_forbidden`` even
|
|
574
|
+
though the build is behaving as designed. The
|
|
580
575
|
statistical ``effect_size`` / ``effect_type`` pair is no longer pending since
|
|
581
576
|
biolink-model 4.4.4 shipped PR #1774.
|
|
582
577
|
:func:`is_pending_problem` uses this set
|
|
@@ -765,7 +760,7 @@ def validate_kgx(nodes_path: Path, edges_path: Path, limit: int = 20) -> dict[st
|
|
|
765
760
|
Two pass rates are reported. ``valid`` is strict and drives ``ok`` (the CLI's
|
|
766
761
|
non-zero exit). ``valid_excluding_pending`` additionally counts records whose *every*
|
|
767
762
|
failure is a :func:`is_pending_problem` -- the score to optimize against, so a
|
|
768
|
-
deliberate gap like the
|
|
763
|
+
deliberate gap like the KGX denormalized carryovers is not mistaken for a
|
|
769
764
|
malformed record. The two converge as future model releases absorb curated extras.
|
|
770
765
|
|
|
771
766
|
Args:
|
|
@@ -222,7 +222,7 @@ def predicate_options(subject_category: str, object_category: str) -> frozenset[
|
|
|
222
222
|
return legal_predicates(derived_edge_category(subject_category, object_category))
|
|
223
223
|
|
|
224
224
|
|
|
225
|
-
def edge_category(lf: pl.LazyFrame, predicate: str | None = None) -> pl.LazyFrame:
|
|
225
|
+
def edge_category(lf: pl.LazyFrame, predicate: str | None = None, category_override: dict[str, str] | None = None) -> pl.LazyFrame:
|
|
226
226
|
"""Add the derived ``category`` column using native polars replace operations.
|
|
227
227
|
|
|
228
228
|
The ``(subject role, object role)`` lookup alone routinely produces a category
|
|
@@ -233,10 +233,18 @@ def edge_category(lf: pl.LazyFrame, predicate: str | None = None) -> pl.LazyFram
|
|
|
233
233
|
by :func:`biolink.resolve_association_class`, which walks up the association
|
|
234
234
|
hierarchy only as far as the predicate requires.
|
|
235
235
|
|
|
236
|
+
``category_override`` pins the association class per *object* category (the
|
|
237
|
+
config's ``statement.category_override``): a row whose raw object category --
|
|
238
|
+
before the role rollup that merges e.g. ``Disease`` and ``PhenotypicFeature``
|
|
239
|
+
into one pair key -- appears in the map carries the pinned class instead of the
|
|
240
|
+
pair-derived one. Pinned classes pass through the same predicate reconciliation.
|
|
241
|
+
|
|
236
242
|
Args:
|
|
237
243
|
lf: Source LazyFrame with ``subject category`` and ``object category``
|
|
238
244
|
columns (biolink-prefixed).
|
|
239
245
|
predicate: Section predicate CURIE used to reconcile the derived category.
|
|
246
|
+
category_override: ``{object category (bare name): biolink: CURIE}`` map
|
|
247
|
+
applied per row in place of the pair lookup.
|
|
240
248
|
|
|
241
249
|
Returns:
|
|
242
250
|
LazyFrame with a new list-typed ``category`` column containing the
|
|
@@ -252,12 +260,20 @@ def edge_category(lf: pl.LazyFrame, predicate: str | None = None) -> pl.LazyFram
|
|
|
252
260
|
# small raw-category -> resolved-category remap resolved once at plan time.
|
|
253
261
|
edge_lookup = {k: f"biolink:{resolve_association_class(v, predicate).__name__}" for k, v in edge_lookup.items()}
|
|
254
262
|
default = f"biolink:{resolve_association_class(default, predicate).__name__}"
|
|
263
|
+
if category_override:
|
|
264
|
+
category_override = {k: f"biolink:{resolve_association_class(v, predicate).__name__}" for k, v in category_override.items()}
|
|
255
265
|
names: list[str] = lf.collect_schema().names()
|
|
256
266
|
subject_col: str = "subject_category" if "subject_category" in names else "subject category"
|
|
257
267
|
object_col: str = "object_category" if "object_category" in names else "object category"
|
|
258
268
|
sr: pl.Expr = pl.col(subject_col).str.replace("biolink:", "").replace(cat_role).fill_null("")
|
|
259
269
|
or_: pl.Expr = pl.col(object_col).str.replace("biolink:", "").replace(cat_role).fill_null("")
|
|
260
|
-
|
|
270
|
+
derived: pl.Expr = pl.concat_str([sr, pl.lit("|"), or_]).replace_strict(edge_lookup, default=default)
|
|
271
|
+
if category_override:
|
|
272
|
+
# Key on the RAW object category: the role rollup above collapses exactly the
|
|
273
|
+
# distinction (Disease vs PhenotypicFeature) the override exists to preserve.
|
|
274
|
+
hit: pl.Expr = pl.col(object_col).str.replace("biolink:", "").replace_strict(category_override, default=None, return_dtype=pl.String)
|
|
275
|
+
derived = pl.when(hit.is_not_null()).then(hit).otherwise(derived)
|
|
276
|
+
return lf.with_columns(pl.concat_list(derived).alias("category"))
|
|
261
277
|
|
|
262
278
|
|
|
263
279
|
def prune_to_class(lf: pl.LazyFrame) -> pl.LazyFrame:
|
|
@@ -1177,7 +1193,7 @@ class Tcode(Section):
|
|
|
1177
1193
|
[
|
|
1178
1194
|
op
|
|
1179
1195
|
for x in self.annotations
|
|
1180
|
-
for op in [*self.encoding(x, x.annotation
|
|
1196
|
+
for op in [*self.encoding(x, x.annotation), *([(split_list, (x.annotation, x.split_by))] if x.split_by else [])]
|
|
1181
1197
|
]
|
|
1182
1198
|
if self.annotations
|
|
1183
1199
|
else None,
|
|
@@ -1297,9 +1313,13 @@ class Tcode(Section):
|
|
|
1297
1313
|
study_name: str | None = sheet if sheet is not None else Path(self.source.local).name
|
|
1298
1314
|
if study_name == study_id:
|
|
1299
1315
|
study_name = None
|
|
1316
|
+
# `use_enum_values` stores the override's enum keys/values as plain strings.
|
|
1317
|
+
category_override: dict[str, str] | None = (
|
|
1318
|
+
{obj: f"biolink:{pinned}" for obj, pinned in self.statement.category_override.items()} if self.statement.category_override else None
|
|
1319
|
+
)
|
|
1300
1320
|
return [
|
|
1301
1321
|
(value, ("predicate", "biolink:" + self.statement.predicate)),
|
|
1302
|
-
(edge_category, ("biolink:" + self.statement.predicate,)),
|
|
1322
|
+
(edge_category, ("biolink:" + self.statement.predicate, category_override)),
|
|
1303
1323
|
(value, ("knowledge_level", knowledge_level)),
|
|
1304
1324
|
(value, ("agent_type", agent_type)),
|
|
1305
1325
|
# Retrieval provenance lives only in the nested `sources` list (Biolink
|
|
@@ -17,9 +17,11 @@ from tablassert.biolink import (
|
|
|
17
17
|
UNSATISFIABLE_EDGE_FIELDS,
|
|
18
18
|
AgentTypes,
|
|
19
19
|
Categories,
|
|
20
|
+
EdgeCategories,
|
|
20
21
|
KnowledgeLevels,
|
|
21
22
|
Predicates,
|
|
22
23
|
Qualifiers,
|
|
24
|
+
resolve_association_class,
|
|
23
25
|
)
|
|
24
26
|
from tablassert.coerce import coerced_target
|
|
25
27
|
from tablassert.enums import (
|
|
@@ -435,6 +437,14 @@ class Statement(TablaBase):
|
|
|
435
437
|
object: NodeEncoding = Field(..., description="Object node encoding and mapping configuration.")
|
|
436
438
|
predicate: Predicates = Field(Predicates.RELATED_TO, description="Predicate connecting subject and object nodes.")
|
|
437
439
|
qualifiers: list[Qualifier] | None = Field(None, description="Optional qualifier nodes attached to the statement.")
|
|
440
|
+
category_override: dict[Categories, EdgeCategories] | None = Field(
|
|
441
|
+
None,
|
|
442
|
+
description=(
|
|
443
|
+
"Optional per-object-category association-class override: maps a resolved object category (bare name, e.g. `Disease`) "
|
|
444
|
+
"to the association category its rows carry (e.g. `EntityToDiseaseAssociation`), used in place of the derived "
|
|
445
|
+
"(subject, object) pair lookup. Rows whose object category is absent from the map derive as before."
|
|
446
|
+
),
|
|
447
|
+
)
|
|
438
448
|
|
|
439
449
|
@model_validator(mode="after")
|
|
440
450
|
def reject_duplicate_qualifiers(self: Self) -> Self:
|
|
@@ -458,6 +468,30 @@ class Statement(TablaBase):
|
|
|
458
468
|
seen.add(key)
|
|
459
469
|
return self
|
|
460
470
|
|
|
471
|
+
@model_validator(mode="after")
|
|
472
|
+
def warn_when_an_override_is_demoted_by_the_predicate(self: Self) -> Self:
|
|
473
|
+
"""Warn when a pinned association class cannot carry the section predicate.
|
|
474
|
+
|
|
475
|
+
A ``category_override`` value still passes through
|
|
476
|
+
:func:`biolink.resolve_association_class` at build time: when the pinned class
|
|
477
|
+
restricts its ``predicate`` slot and the section predicate is not in it, the
|
|
478
|
+
emitted category silently walks up to an ancestor -- taking every subclass-only
|
|
479
|
+
slot the author pinned the class *for* with it. The value is never lost (the
|
|
480
|
+
reconciliation keeps the edge valid), so this is a warning, not an error.
|
|
481
|
+
"""
|
|
482
|
+
for obj_category, pinned in (self.category_override or {}).items():
|
|
483
|
+
# `use_enum_values` stores both sides as plain strings.
|
|
484
|
+
resolved: str = resolve_association_class(f"biolink:{pinned}", f"biolink:{self.predicate}").__name__
|
|
485
|
+
if resolved != pinned:
|
|
486
|
+
warnings.warn(
|
|
487
|
+
f"category_override pins `{pinned}` for `{obj_category}` objects, but that class does not accept "
|
|
488
|
+
f"predicate `{self.predicate}`, so those rows are emitted as `{resolved}` instead -- any slots only "
|
|
489
|
+
f"`{pinned}` declares are pruned off the edge.",
|
|
490
|
+
BiolinkRelocationWarning,
|
|
491
|
+
stacklevel=2,
|
|
492
|
+
)
|
|
493
|
+
return self
|
|
494
|
+
|
|
461
495
|
|
|
462
496
|
def validate_infores_curie(value: str, code: TablassertErrorCodes) -> str:
|
|
463
497
|
"""Validate an ``infores:`` CURIE used for Biolink knowledge-source fields."""
|
|
@@ -674,7 +708,13 @@ class Annotation(Encoding):
|
|
|
674
708
|
@field_validator("annotation", mode="after")
|
|
675
709
|
@classmethod
|
|
676
710
|
def clean_annotation(cls, annotation: str) -> str:
|
|
677
|
-
|
|
711
|
+
cleaned: str = annotation.strip()
|
|
712
|
+
lowered: str = cleaned.lower()
|
|
713
|
+
# Allow-listed slots may carry uppercase (Biolink's ``FDA_regulatory_approvals``):
|
|
714
|
+
# any casing the author declares canonicalizes onto the allow-listed spelling
|
|
715
|
+
# verbatim, so the emitted edge field preserves its case exactly instead of being
|
|
716
|
+
# lowercased into an unknown name.
|
|
717
|
+
return next((field for field in ALLOWED_EDGE_FIELDS if field.lower() == lowered), lowered)
|
|
678
718
|
|
|
679
719
|
@model_validator(mode="after")
|
|
680
720
|
def reject_disabled_annotations(self: Self) -> Self:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|