tablassert 7.4.13__tar.gz → 7.5.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.
Files changed (62) hide show
  1. {tablassert-7.4.13 → tablassert-7.5.0}/CHANGELOG.md +18 -0
  2. {tablassert-7.4.13 → tablassert-7.5.0}/PKG-INFO +1 -1
  3. {tablassert-7.4.13 → tablassert-7.5.0}/docs/api/lib.md +18 -14
  4. {tablassert-7.4.13 → tablassert-7.5.0}/docs/api/qc.md +6 -6
  5. {tablassert-7.4.13 → tablassert-7.5.0}/docs/api/utils.md +26 -7
  6. {tablassert-7.4.13 → tablassert-7.5.0}/docs/changelog.md +7 -0
  7. {tablassert-7.4.13 → tablassert-7.5.0}/docs/cli.md +2 -2
  8. {tablassert-7.4.13 → tablassert-7.5.0}/docs/configuration/advanced-example.md +19 -21
  9. {tablassert-7.4.13 → tablassert-7.5.0}/docs/configuration/graph.md +16 -16
  10. {tablassert-7.4.13 → tablassert-7.5.0}/docs/configuration/table.md +37 -30
  11. {tablassert-7.4.13 → tablassert-7.5.0}/docs/docker.md +6 -4
  12. {tablassert-7.4.13 → tablassert-7.5.0}/pyproject.toml +2 -2
  13. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/lib.py +22 -7
  14. {tablassert-7.4.13 → tablassert-7.5.0}/tests/test_lib.py +89 -0
  15. {tablassert-7.4.13 → tablassert-7.5.0}/uv.lock +153 -128
  16. {tablassert-7.4.13 → tablassert-7.5.0}/.github/workflows/docker.yml +0 -0
  17. {tablassert-7.4.13 → tablassert-7.5.0}/.github/workflows/docs.yml +0 -0
  18. {tablassert-7.4.13 → tablassert-7.5.0}/.github/workflows/pipy.yml +0 -0
  19. {tablassert-7.4.13 → tablassert-7.5.0}/.gitignore +0 -0
  20. {tablassert-7.4.13 → tablassert-7.5.0}/.pre-commit-config.yaml +0 -0
  21. {tablassert-7.4.13 → tablassert-7.5.0}/AGENTS.md +0 -0
  22. {tablassert-7.4.13 → tablassert-7.5.0}/CITATION.cff +0 -0
  23. {tablassert-7.4.13 → tablassert-7.5.0}/CONTRIBUTING.md +0 -0
  24. {tablassert-7.4.13 → tablassert-7.5.0}/Dockerfile +0 -0
  25. {tablassert-7.4.13 → tablassert-7.5.0}/LICENSE +0 -0
  26. {tablassert-7.4.13 → tablassert-7.5.0}/README.md +0 -0
  27. {tablassert-7.4.13 → tablassert-7.5.0}/docs/api/fullmap.md +0 -0
  28. {tablassert-7.4.13 → tablassert-7.5.0}/docs/datassert.md +0 -0
  29. {tablassert-7.4.13 → tablassert-7.5.0}/docs/examples/tutorial-data.csv +0 -0
  30. {tablassert-7.4.13 → tablassert-7.5.0}/docs/examples/tutorial-graph.yaml +0 -0
  31. {tablassert-7.4.13 → tablassert-7.5.0}/docs/examples/tutorial-table.yaml +0 -0
  32. {tablassert-7.4.13 → tablassert-7.5.0}/docs/examples.md +0 -0
  33. {tablassert-7.4.13 → tablassert-7.5.0}/docs/index.md +0 -0
  34. {tablassert-7.4.13 → tablassert-7.5.0}/docs/installation.md +0 -0
  35. {tablassert-7.4.13 → tablassert-7.5.0}/docs/tutorial.md +0 -0
  36. {tablassert-7.4.13 → tablassert-7.5.0}/llms.txt +0 -0
  37. {tablassert-7.4.13 → tablassert-7.5.0}/mkdocs.yml +0 -0
  38. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/__init__.py +0 -0
  39. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/cli.py +0 -0
  40. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/downloader.py +0 -0
  41. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/enums.py +0 -0
  42. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/fullmap.py +0 -0
  43. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/ingests.py +0 -0
  44. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/log.py +0 -0
  45. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/models.py +0 -0
  46. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/nlp.py +0 -0
  47. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/progress.py +0 -0
  48. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/qc.py +0 -0
  49. {tablassert-7.4.13 → tablassert-7.5.0}/src/tablassert/utils.py +0 -0
  50. {tablassert-7.4.13 → tablassert-7.5.0}/tests/__init__.py +0 -0
  51. {tablassert-7.4.13 → tablassert-7.5.0}/tests/conftest.py +0 -0
  52. {tablassert-7.4.13 → tablassert-7.5.0}/tests/fixtures/invalid_section_missing_source.yaml +0 -0
  53. {tablassert-7.4.13 → tablassert-7.5.0}/tests/fixtures/minimal_section.yaml +0 -0
  54. {tablassert-7.4.13 → tablassert-7.5.0}/tests/fixtures/minimal_section_with_sections.yaml +0 -0
  55. {tablassert-7.4.13 → tablassert-7.5.0}/tests/test_downloader.py +0 -0
  56. {tablassert-7.4.13 → tablassert-7.5.0}/tests/test_enums.py +0 -0
  57. {tablassert-7.4.13 → tablassert-7.5.0}/tests/test_fullmap.py +0 -0
  58. {tablassert-7.4.13 → tablassert-7.5.0}/tests/test_ingests.py +0 -0
  59. {tablassert-7.4.13 → tablassert-7.5.0}/tests/test_models.py +0 -0
  60. {tablassert-7.4.13 → tablassert-7.5.0}/tests/test_nlp.py +0 -0
  61. {tablassert-7.4.13 → tablassert-7.5.0}/tests/test_qc.py +0 -0
  62. {tablassert-7.4.13 → tablassert-7.5.0}/tests/test_utils.py +0 -0
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## 7.5.0 - 2026-07-01
6
+
7
+ ### Changed
8
+ - Publication CURIEs in `compile_subgraph()` (`lib.py`) now use the `PMCID:` namespace prefix for PubMed Central sources. A `repo: PMC` section with `publication: PMC11708054` is emitted as `PMCID:PMC11708054` (previously `PMC:PMC11708054`); non-PMC repos such as `PMID` are unaffected and continue to emit `<repo>:<publication>` (e.g., `PMID:11708054`). The `repository` edge column is unchanged and still records the raw `repo` value. Extracted via a new `publication_curie()` helper.
9
+
10
+ ### Added
11
+ - New `<col> table literal value` edge column for subject, object, and qualifier nodes encoded with `method: column`. Unlike the existing `original <col>` column (which snapshots the value *after* all `fill`/`explode_by`/`regex`/`remove`/`prefix`/`suffix`/`transformations`), `<col> table literal value` captures the pristine source-cell value *before* any transformation. Emitted only for column-encoded nodes; annotations and `method: value` nodes are unaffected. Implemented via a `table_literal` flag on `Tcode.encoding()`, enabled by `Tcode.node()`.
12
+ - Four regression tests in `test_lib.py`: `publication_curie()` for PMC and PMID namespaces, and two `Tcode` tcode-inspection tests covering presence/ordering of the table-literal column for column encodings and its absence for value encodings.
13
+
14
+ ### Documentation
15
+ - Comprehensive accuracy pass across the API, configuration, and Docker documentation, reconciling every page against the current codebase. Highlights: corrected invalid examples that would not load (`syntax: TC2`; `publication` integers and missing `PMC` prefixes; a non-existent `Qualifiers` member; `reindex` placed at section level; a subject missing `method: column`), fixed wrong field types (`rows`/`row_slice`/`taxon` → `PositiveInt`, `remove` → regex patterns), corrected the QC fuzzy thresholds (`fuzz.ratio >= 20 OR partial_token_sort_ratio >= 30`), removed a non-existent `uuid:` prefix from `utils.md` return examples, fixed the `resolve_many()` parameter order and added the original-column-capture and optional QC-audit pipeline steps, corrected graph-config path resolution (CWD, not config-relative) and processing-flow ordering, documented the strict QC GPU no-fallback behavior and the `.cachassert/` working directory, and aligned `Categories` enum member names (`GENE`/`PROTEIN`) and Docker CI triggers with the source.
16
+
17
+ ## 7.4.14 - 2026-06-30
18
+
19
+ ### Changes
20
+ - Extended `sig()` in `lib.py` to select a p-value column by fuzzy matching rather than requiring an exact `"p value"` name. All schema columns whose names contain the substring `"p value"` are now considered candidates; `fuzz.ratio` (rapidfuzz) scores each against the literal `"p value"` and the highest-scoring column is used to compute the `"significant"` output. An exact `"p value"` column scores 100 and is always preferred; columns like `"adjusted p value"` or `"log p value"` are used only when no exact match is present. If no column contains the substring the function continues to emit `"UNSURE"` for all rows.
21
+ - Added five regression tests in `test_lib.py` covering: exact-match preference, non-exact fallback, closest-match selection among multiple non-exact candidates, no-p-value column (UNSURE), and null value handling.
22
+
5
23
  ## 7.4.13 - 2026-06-30
6
24
 
7
25
  ### Changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tablassert
3
- Version: 7.4.13
3
+ Version: 7.5.0
4
4
  Summary: Extract knowledge assertions from tabular data into NCATS Translator-compliant KGX NDJSON — declaratively, with entity resolution and quality control built in.
5
5
  Project-URL: Homepage, https://github.com/SkyeAv/Tablassert
6
6
  Project-URL: Source, https://github.com/SkyeAv/Tablassert
@@ -2,7 +2,7 @@
2
2
 
3
3
  The `lib` module exposes `resolve_many()`, a high-level convenience function for resolving an iterable of entity strings to CURIEs without requiring manual LazyFrame construction, NLP preprocessing, or DuckDB shard management.
4
4
 
5
- It wraps the lower-level [`resolve()`](fullmap.md) pipeline — applying `level_one` and `level_two` normalization, opening all 10 DuckDB shard connections, executing entity resolution, and returning results as a plain Python list of row dictionaries.
5
+ It wraps the lower-level [`resolve()`](fullmap.md) pipeline — preserving the original input text, applying `level_one` and `level_two` normalization, opening all 10 DuckDB shard connections, executing entity resolution, optionally running the QC audit (when `qc=True`), and returning results as a plain Python list of row dictionaries.
6
6
 
7
7
  ## resolve_many()
8
8
 
@@ -18,8 +18,8 @@ def resolve_many(
18
18
  taxon: Optional[str] = None,
19
19
  prioritize: Optional[list[Categories]] = None,
20
20
  avoid: Optional[list[Categories]] = None,
21
- column_context: bool = True,
22
21
  qc: bool = False,
22
+ column_context: bool = True,
23
23
  ) -> list[dict[str, Any]]
24
24
  ```
25
25
 
@@ -52,29 +52,29 @@ Each shard contains:
52
52
 
53
53
  Optional NCBI Taxon ID for filtering results to a specific organism.
54
54
 
55
- Example: `"9606"` restricts matches to human-specific entities. When `None`, no taxon filtering is applied and matches from all organisms are returned.
55
+ Example: `"9606"` restricts **gene** matches to human-specific entries; non-gene categories (e.g., diseases, chemicals) are returned regardless of taxon. When `None`, no taxon filtering is applied and matches from all organisms are returned.
56
56
 
57
57
  **`prioritize: Optional[list[Categories]]` (default: `None`)**
58
58
 
59
59
  Optional list of Biolink categories to prefer when multiple matches exist for the same input term. Categories listed here receive higher ranking scores during resolution.
60
60
 
61
- Example: `[Categories.Gene, Categories.Protein]` prefers gene and protein mappings over other categories like diseases or chemicals.
61
+ Example: `[Categories.GENE, Categories.PROTEIN]` prefers gene and protein mappings over other categories like diseases or chemicals.
62
62
 
63
63
  **`avoid: Optional[list[Categories]]` (default: `None`)**
64
64
 
65
65
  Optional list of Biolink categories to exclude from results entirely. Any match belonging to an avoided category is filtered out before ranking.
66
66
 
67
- Example: `[Categories.Gene]` prevents gene mappings from appearing in the output, even if they would otherwise be the best match.
67
+ Example: `[Categories.GENE]` prevents gene mappings from appearing in the output, even if they would otherwise be the best match.
68
68
 
69
69
  **`column_context: bool` (default: `True`)**
70
70
 
71
- Controls category-frequency tie-breaking when multiple matches exist for a term. When `True`, the resolution query adds a category frequency score and prefers the category that appears most frequently across all terms in the batch. When `False`, frequency-based tie-breaking is disabled.
71
+ Controls category-frequency tie-breaking when multiple matches exist for a term. When `True`, the deduplication stage adds a category-frequency score (computed in Polars after the SQL query) and prefers the category that appears most frequently across all matched terms in the batch. When `False`, frequency-based tie-breaking is disabled.
72
72
 
73
73
  This is useful when resolving a column of related entities (e.g., all genes) — the shared context helps disambiguate terms that map to multiple categories.
74
74
 
75
75
  **`qc: bool` (default: `False`)**
76
76
 
77
- When `True`, runs the QC audit stage after entity resolution. The QC pipeline validates mappings through a three-stage audit: exact match, fuzzy matching via rapidfuzz, and BioBERT sentence embeddings with cosine similarity. Requires a QC runtime to be installed (`tablassert[qc]` or `tablassert[qc-cuda]`). The ONNX Runtime provider is auto-detected based on the installed package — CUDA is preferred when `onnxruntime-gpu` is available, otherwise CPU is used.
77
+ When `True`, runs the QC audit stage after entity resolution. The QC pipeline validates mappings through a three-stage audit: exact match, fuzzy matching via rapidfuzz, and BioBERT sentence embeddings with cosine similarity. Mappings that fail all three stages are dropped from the returned list (in addition to the unresolved-entity filtering performed by `resolve()`). Requires a QC runtime to be installed (`tablassert[qc]` or `tablassert[qc-cuda]`). The ONNX Runtime provider is auto-detected based on the installed package — CUDA is preferred when `onnxruntime-gpu` is installed and `CUDAExecutionProvider` is available; if only `onnxruntime` (CPU) is installed, CPU is used. Note: if `onnxruntime-gpu` is installed but `CUDAExecutionProvider` is unavailable, QC raises rather than falling back to CPU.
78
78
 
79
79
  ### Return Value
80
80
 
@@ -101,13 +101,17 @@ Each dictionary contains the following keys (where `{col}` is the value of the `
101
101
 
102
102
  1. **Series construction** — Wraps the input iterable in a `pl.Series` with the given column name, then converts to a single-column `pl.LazyFrame`.
103
103
 
104
- 2. **NLP normalization** — Applies `level_one()` (whitespace stripping + lowercasing) and `level_two()` (non-word character removal via `\W+`) to produce the two normalized columns required by `resolve()`.
104
+ 2. **Original column capture** — Copies the raw input column into `original {col}` via `column(lf, add("original ", col), col)` so the pre-normalization text is preserved in the output.
105
+
106
+ 3. **NLP normalization** — Applies `level_one()` (whitespace stripping + lowercasing) and `level_two()` (non-word character removal via `\W+`) to produce the two normalized columns required by `resolve()`.
107
+
108
+ 4. **DuckDB connection management** — Opens all 10 shard connections inside a `contextlib.ExitStack`, ensuring every connection is properly closed when resolution completes or if an error occurs.
105
109
 
106
- 3. **DuckDB connection management** — Opens all 10 shard connections inside a `contextlib.ExitStack`, ensuring every connection is properly closed when resolution completes or if an error occurs.
110
+ 5. **Entity resolution** — Delegates to `fullmap.resolve()` which queries the sharded DuckDB database, ranks matches by category priority, preferred-name exactness, NLP level, and category frequency, then deduplicates to one CURIE per input string.
107
111
 
108
- 4. **Entity resolution** — Delegates to `fullmap.resolve()` which queries the sharded DuckDB database, ranks matches by category priority, preferred-name exactness, NLP level, and category frequency, then deduplicates to one CURIE per input string.
112
+ 6. **QC audit (optional)** — When `qc=True`, runs `fullmap_audit()` on the resolved LazyFrame. Rows that fail all three audit stages are dropped from the result.
109
113
 
110
- 5. **Collection and conversion** — Collects the lazy result into an eager `pl.DataFrame` and converts to a list of row dictionaries via `to_dicts()`.
114
+ 7. **Collection and conversion** — Collects the lazy result into an eager `pl.DataFrame` and converts to a list of row dictionaries via `to_dicts()`.
111
115
 
112
116
  ### Example Usage
113
117
 
@@ -126,7 +130,7 @@ result: list[dict[str, Any]] = resolve_many(
126
130
  entities=["TP53", "BRCA1", "EGFR", "KRAS"],
127
131
  datassert=datassert,
128
132
  taxon="9606",
129
- prioritize=[Categories.Gene],
133
+ prioritize=[Categories.GENE],
130
134
  )
131
135
 
132
136
  # result[0] → {"original gene": "TP53", "gene": "HGNC:11998", "gene name": "TP53", ...}
@@ -147,7 +151,7 @@ result: list[dict[str, Any]] = resolve_many(
147
151
  col="disease",
148
152
  entities=["diabetes mellitus", "breast cancer", "alzheimer disease"],
149
153
  datassert=datassert,
150
- avoid=[Categories.Gene, Categories.Protein],
154
+ avoid=[Categories.GENE, Categories.PROTEIN],
151
155
  )
152
156
 
153
157
  # result[0] → {"original disease": "diabetes mellitus", "disease": "MONDO:0005015", ...}
@@ -206,7 +210,7 @@ for row in result:
206
210
  | **Connections** | Managed internally via `ExitStack` | Must be opened externally |
207
211
  | **Output** | `list[dict[str, Any]]` | `pl.LazyFrame` |
208
212
  | **Logging** | Uses default (`log=True`) | Configurable |
209
- | **Context params** | Not exposed (`section_hash`, `config_file`, `tag`) | Fully configurable |
213
+ | **Context params** | `column_context` exposed; `section_hash`, `config_file`, `tag` not exposed | Fully configurable |
210
214
  | **Use case** | Standalone batch lookups, scripting, notebooks | Internal pipeline integration |
211
215
 
212
216
  `resolve_many()` is designed for ad-hoc and programmatic use — scripts, notebooks, and one-off lookups. For pipeline integration where you need full control over logging, context metadata, and lazy evaluation, use `resolve()` directly.
@@ -2,7 +2,7 @@
2
2
 
3
3
  The `qc` module validates entity resolution mappings through a multi-stage pipeline: exact matching, fuzzy matching, and BERT semantic similarity.
4
4
 
5
- QC runtime support is optional. Install `tablassert[qc]` for CPU inference or `tablassert[qc-cuda]` for CUDA inference on GPU 0.
5
+ QC runtime support is optional. Install `tablassert[qc]` for CPU inference or `tablassert[qc-cuda]` for CUDA inference on GPU 0. **Strict GPU behavior:** if `onnxruntime-gpu` is installed but `CUDAExecutionProvider` is not available (e.g., a broken CUDA/cuDNN environment), `fullmap_audit()` raises a `RuntimeError` rather than silently falling back to CPU — install `tablassert[qc]` for CPU inference or fix the CUDA environment.
6
6
 
7
7
  ## fullmap_audit()
8
8
 
@@ -84,7 +84,7 @@ original == preferred_name
84
84
 
85
85
  **Performance:** O(1) string comparison
86
86
 
87
- Before fuzzy matching, the function also applies rule-based pass-through checks for known safe patterns (for example CHEBI/PR/UniProtKB CURIE families and selected exception prefixes).
87
+ Before fuzzy matching, the function also applies rule-based pass-through checks: (1) the resolved CURIE matches an exempt prefix (`CHEBI`, `PR`, `UniProtKB`, `NCBIGene`, `UMLS`, `UNII`, `PUBCHEM`, `MONDO`); (2) the original text contains `:` (looks like a CURIE); (3) the preferred name matches an exception prefix (`^LOC` or `^si:`).
88
88
 
89
89
  #### Stage 2: Fuzzy Matching
90
90
 
@@ -94,11 +94,11 @@ Two fuzzy matching algorithms:
94
94
  1. **Ratio:** Overall string similarity
95
95
  2. **Partial token sort ratio:** Combined token/subsequence matching
96
96
 
97
- **Threshold:** 20% similarity
97
+ **Thresholds:** `fuzz.ratio` >= 20 OR `fuzz.partial_token_sort_ratio` >= 30
98
98
 
99
99
  ```python
100
100
  fuzz.ratio(original, preferred) >= 20
101
- or fuzz.partial_token_sort_ratio(original, preferred) >= 20
101
+ or fuzz.partial_token_sort_ratio(original, preferred) >= 30
102
102
  ```
103
103
 
104
104
  **Example passes:**
@@ -116,8 +116,8 @@ or fuzz.partial_token_sort_ratio(original, preferred) >= 20
116
116
  3. **Accept** if similarity >= 0.2 (20%)
117
117
 
118
118
  ```python
119
- embeddings = BIOBERT.encode([original, preferred])
120
- similarity = cosine_similarity([embeddings[0]], [embeddings[1]])[0][0]
119
+ embeddings = get_biobert(provider).encode(originals + preferreds)
120
+ similarity = cosine_similarity(embeddings[:n], embeddings[n:]).diagonal()
121
121
  return similarity >= 0.2
122
122
  ```
123
123
 
@@ -19,10 +19,8 @@ def namespace_uuid(domain: Any, *values: list[Any]) -> str
19
19
  Domain string used to create the namespace UUID.
20
20
 
21
21
  Converted to string internally. Common domains:
22
- - `"TABLASSERT"` - Default domain used for knowledge graph edge IDs
23
- - `"edges"` - Optional custom domain for edge IDs
24
- - `"nodes"` - For custom node IDs
25
- - `"tablassert"` - For application-specific IDs
22
+ - `"TABLASSERT"` - Default domain used for KGX edge IDs (the value used by `label_edge`)
23
+ - Other domain strings (e.g., `"edges"`, `"nodes"`) may be passed for custom namespaces, though only `"TABLASSERT"` is used internally.
26
24
 
27
25
  **`*values: list[Any]`**
28
26
 
@@ -118,7 +116,7 @@ edge_id = namespace_uuid(
118
116
  "MONDO:0005148", # Type 2 diabetes
119
117
  "PMC11708054" # Publication
120
118
  )
121
- # Returns: "uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
119
+ # Returns: "2cfea591-0f8f-33af-a7df-03da531d3359"
122
120
  ```
123
121
 
124
122
  **Benefits:**
@@ -145,7 +143,7 @@ from tablassert.utils import namespace_uuid
145
143
 
146
144
  # Generate edge ID
147
145
  edge_id = namespace_uuid("edges", "subject", "predicate", "object")
148
- print(edge_id) # "uuid:12345678-1234-1234-1234-123456789abc"
146
+ print(edge_id) # "12345678-1234-1234-1234-123456789abc"
149
147
  ```
150
148
 
151
149
  **With qualifiers:**
@@ -183,12 +181,33 @@ Suitable for millions of ID generations per second.
183
181
  **`basespace(domain)`** - Creates namespace UUID from domain
184
182
 
185
183
  ```python
184
+ @cache
186
185
  def basespace(domain: str) -> UUID:
187
186
  namespace = UUID("00000000-0000-0000-0000-000000000000")
188
187
  return uuid3(namespace, domain)
189
188
  ```
190
189
 
191
- Used internally by `namespace_uuid()`.
190
+ Used internally by `namespace_uuid()`. Results are memoized per domain.
191
+
192
+ ## mkhash()
193
+
194
+ Generates an `xxhash` 64-bit digest (hex string, 16 characters) for arbitrary input.
195
+
196
+ ```python
197
+ def mkhash(x: Any) -> str
198
+ ```
199
+
200
+ Used for compact identifier hashing (e.g., config/file identifiers in the CLI). Returns the `xxh64` hex digest of the input converted to a string.
201
+
202
+ ## samphash()
203
+
204
+ Hashes a sampled `polars.DataFrame` for deterministic temp-file naming.
205
+
206
+ ```python
207
+ def samphash(df: pl.DataFrame, n: int = 20) -> str
208
+ ```
209
+
210
+ Samples up to `n` rows (default `20`) from `df`, hashes the sampled bytes via `mkhash()`, and returns the digest string.
192
211
 
193
212
  ## Next Steps
194
213
 
@@ -4,6 +4,13 @@ The canonical release history lives in the repository root at [`CHANGELOG.md`](h
4
4
 
5
5
  ## Current Release Notes
6
6
 
7
+ ## 7.4.14 - 2026-06-30
8
+
9
+ ### Changes
10
+
11
+ - Extended `sig()` in `lib.py` to select a p-value column by fuzzy matching rather than requiring an exact `"p value"` name. All schema columns whose names contain the substring `"p value"` are now considered candidates; `fuzz.ratio` (rapidfuzz) scores each against the literal `"p value"` and the highest-scoring column is used to compute the `"significant"` output. An exact `"p value"` column scores 100 and is always preferred; columns like `"adjusted p value"` or `"log p value"` are used only when no exact match is present. If no column contains the substring the function continues to emit `"UNSURE"` for all rows.
12
+ - Added five regression tests in `test_lib.py` covering: exact-match preference, non-exact fallback, closest-match selection among multiple non-exact candidates, no-p-value column (UNSURE), and null value handling.
13
+
7
14
  ## 7.4.13 - 2026-06-30
8
15
 
9
16
  ### Changes
@@ -26,7 +26,7 @@ Prints the installed Tablassert version to stdout and exits. This is a flag on t
26
26
 
27
27
  ## build
28
28
 
29
- Build A KGX Compliant Knowledge Graph From A Graph Configuration File
29
+ Build a knowledge graph from a YAML configuration file.
30
30
 
31
31
  ### Synopsis
32
32
 
@@ -70,7 +70,7 @@ See [Graph Configuration](configuration/graph.md) for details on the YAML schema
70
70
 
71
71
  ## validate
72
72
 
73
- Verify The Syntax Of A Declarative Table Configuration File
73
+ Validate section syntax from a YAML configuration file.
74
74
 
75
75
  ### Synopsis
76
76
 
@@ -23,7 +23,7 @@ template:
23
23
  local: ./DATALAKE/ALAM.XLSX
24
24
  url: https://pmc.ncbi.nlm.nih.gov/articles/instance/11708054/bin/mbio.01679-24-s0006.xlsx
25
25
  row_slice:
26
- - 2 # Skip first row (header)
26
+ - 2 # Skip the first two rows (title + header)
27
27
  - auto # Read to end
28
28
  sheet: all correlations
29
29
 
@@ -39,9 +39,9 @@ template:
39
39
  avoid:
40
40
  - Gene
41
41
 
42
- # Remove invalid entries
42
+ # Strip non-value text in place (rows are not dropped)
43
43
  remove:
44
- - "^NA " # Rows starting with "NA "
44
+ - "^NA " # Cells starting with "NA "
45
45
 
46
46
  # Clean taxonomic names with regex
47
47
  regex:
@@ -66,7 +66,7 @@ template:
66
66
  # Provenance: Publication and curation info
67
67
  provenance:
68
68
  repo: PMC
69
- publication: 11708054
69
+ publication: PMC11708054
70
70
  contributors:
71
71
  - kind: curation
72
72
  name: Skye Lane Goetz
@@ -147,7 +147,7 @@ The subject field uses three regex transformations in sequence:
147
147
  ```
148
148
  `"Lactobacillus sp"` → `"Lactobacillus sp. "`
149
149
 
150
- > **Regex constraint:** Each `pattern` is handed to Polars `str.replace_all()` (Rust `regex` crate). **Capturing groups (`(...)` / `\1`) and lookarounds (`(?=...)`, `(?<=...)`, `(?!...)`, `(?<!...)`) are not allowed** and will fail validation. Express transformations as a sequence of simple anchored / character-class substitutions instead — the pipeline above is a deliberate three-step chain because no single capturing-group pattern is permitted. If the transformation can't be expressed without those features, capture the leftover context in a `miscellaneous notes` annotation rather than fighting the regex engine.
150
+ > **Regex constraint:** Each `pattern` is handed to Polars `str.replace_all()` (Rust `regex` crate). **Backreferences (`\1`, `\2`, ) and lookarounds (`(?=...)`, `(?<=...)`, `(?!...)`, `(?<!...)`) are not allowed** and will fail validation. Plain groups `(...)` and non-capturing groups `(?:...)` *are* supported. Express transformations as a sequence of simple anchored / character-class substitutions where possible — the pipeline above is a deliberate three-step chain. If the transformation can't be expressed without those unsupported features, capture the leftover context in a `miscellaneous notes` annotation rather than fighting the regex engine.
151
151
 
152
152
  ### Taxonomic Filtering
153
153
 
@@ -226,6 +226,7 @@ template:
226
226
 
227
227
  statement:
228
228
  subject:
229
+ method: column
229
230
  encoding: A
230
231
  prioritize: [OrganismTaxon]
231
232
  avoid: [Gene]
@@ -239,18 +240,20 @@ sections:
239
240
  # Section 1: Positive correlations
240
241
  - statement:
241
242
  predicate: positively_correlated_with
242
- reindex:
243
- - column: B # Correlation coefficient
244
- comparison: gt
245
- comparator: 0
243
+ source:
244
+ reindex:
245
+ - column: B # Correlation coefficient
246
+ comparison: gt
247
+ comparator: 0
246
248
 
247
249
  # Section 2: Negative correlations
248
250
  - statement:
249
251
  predicate: negatively_correlated_with
250
- reindex:
251
- - column: B
252
- comparison: lt
253
- comparator: 0
252
+ source:
253
+ reindex:
254
+ - column: B
255
+ comparison: lt
256
+ comparator: 0
254
257
  ```
255
258
 
256
259
  This produces two sets of edges from one table:
@@ -296,14 +299,9 @@ template:
296
299
  - pattern: _
297
300
  replacement: ' ' # "Lactobacillus_rhamnosus" → "Lactobacillus rhamnosus"
298
301
 
299
- qualifiers:
300
- - qualifier: p value
301
- method: column
302
- encoding: E
303
-
304
302
  provenance:
305
303
  repo: PMC
306
- publication: 12345678
304
+ publication: PMC12345678
307
305
  contributors:
308
306
  - kind: curation
309
307
  name: Skye Lane Goetz
@@ -324,7 +322,7 @@ template:
324
322
 
325
323
  **Both nodes from columns:** Setting `method: column` on both subject and object means both undergo entity resolution via `resolve()`. Each gets its own `prioritize` list to guide disambiguation.
326
324
 
327
- **`remove` vs `regex`:** `remove` filters out entire rows matching a pattern before resolution. `regex` transforms the column value in-place before resolution.
325
+ **`remove` vs `regex`:** Both transform cell text in place before resolution (neither drops rows). `remove` strips each listed regex pattern by replacing it with an empty string; `regex` applies an ordered `pattern`→`replacement` substitution list.
328
326
 
329
327
  ---
330
328
 
@@ -364,7 +362,7 @@ template:
364
362
 
365
363
  provenance:
366
364
  repo: PMC
367
- publication: 87654321
365
+ publication: PMC87654321
368
366
  contributors:
369
367
  - kind: curation
370
368
  name: Skye Lane Goetz
@@ -16,7 +16,6 @@ A graph configuration file specifies:
16
16
 
17
17
  | Field | Type | Description |
18
18
  |-------|------|-------------|
19
- | `syntax` | String | Configuration version (must be `"GC2"`) |
20
19
  | `name` | String | Knowledge graph name (used in output filename) |
21
20
  | `version` | String | Knowledge graph version (used in output filename) |
22
21
  | `tables` | List[Path] | Paths to table configuration YAML files |
@@ -26,6 +25,7 @@ A graph configuration file specifies:
26
25
 
27
26
  | Field | Type | Description |
28
27
  |-------|------|-------------|
28
+ | `syntax` | String | Configuration version. Defaults to `"GC2"` (also accepts `"TC3"`); overriding is not recommended. |
29
29
  | `pubmed_db` | Path | Path to SQLite PubMed metadata database |
30
30
  | `pmc_db` | Path | Path to SQLite PMC figure captions database |
31
31
  | `log` | Boolean | Log unmatched entities and audit details during graph builds |
@@ -35,7 +35,7 @@ A graph configuration file specifies:
35
35
 
36
36
  **`syntax: "GC2"`**
37
37
 
38
- Configuration syntax version. Must be `"GC2"`.
38
+ Configuration syntax version. Defaults to `"GC2"` (the type also accepts `"TC3"`); overriding the default is not recommended.
39
39
 
40
40
  **`name: string`**
41
41
 
@@ -61,7 +61,7 @@ This field only controls whether QC runs. Install `tablassert[qc]` or `tablasser
61
61
 
62
62
  **`tables: list[path]`**
63
63
 
64
- List of table configuration file paths. Can be absolute or relative to graph config location.
64
+ List of table configuration file paths. Can be absolute or relative to the current working directory (paths are resolved against the process CWD, not the graph-config file location).
65
65
 
66
66
  Each table config defines:
67
67
  - Data source (Excel/CSV/TSV)
@@ -101,8 +101,7 @@ When provided, this is used when provenance specifies PMC publications.
101
101
 
102
102
  Paths can be:
103
103
  - **Absolute:** `/home/user/data/datassert`
104
- - **Relative to graph config:** `./tables/table1.yaml`
105
- - **Relative to current directory:** `../configs/table.yaml`
104
+ - **Relative to the current working directory:** `./tables/table1.yaml` (note: paths are resolved against the process CWD, not the graph-config file location — there is no config-relative resolver)
106
105
 
107
106
  ## Minimal Example
108
107
 
@@ -139,16 +138,17 @@ pmc_db: /databases/PMCSuppCaptions.db
139
138
  When you run `tablassert build graph.yaml`:
140
139
 
141
140
  1. **Load graph configuration** - Parse YAML, validate schema
142
- 2. **For each table in `tables`:**
143
- - Load table configuration
141
+ 2. **Load table configurations** - Parse each YAML in `tables`
142
+ 3. **Extract sections** - Expand templates into per-section `Tcode` instances
143
+ 4. **Collect instructions (per section):**
144
144
  - Download source file (if URL specified)
145
- - Apply transformations
146
- - Resolve entities using `datassert`
147
- - Validate with QC pipeline when `qc: true`
148
- - Create subgraph parquet file
149
- 3. **Aggregate subgraphs** - Merge all parquet files
150
- 4. **Add provenance (optional)** - Query `pubmed_db` and `pmc_db` for metadata when configured
151
- 5. **Export NDJSON** - Generate `{name}_{version}.nodes.ndjson` and `.edges.ndjson`
145
+ - Apply transformations and resolve entities using `datassert`
146
+ - Validate with the QC audit when `qc: true`
147
+ - Enrich with provenance: query `pubmed_db` (MeSH) and `pmc_db` (captions) when configured
148
+ 5. **Build subgraphs** - Compile each section's resolved data into a parquet file
149
+ 6. **Compile graph** - Aggregate all subgraph parquets and export `{name}_{version}.nodes.ndjson` / `.edges.ndjson`
150
+
151
+ > Note: provenance enrichment (`with_mesh`/`with_captions`) and QC both run during the per-section Collect Instructions stage, *before* subgraphs are built — not as a separate post-aggregation step.
152
152
 
153
153
  ## Output Files
154
154
 
@@ -173,8 +173,8 @@ version: UNSTABLE
173
173
  tables:
174
174
  - /local_raid1/sgoetz/STORE/CONFIG/TABLASSERT/TABLE/V6/ALAMV6.yaml
175
175
  datassert: /local_raid1/sgoetz/CODE/DATASSERT/datassert
176
- pubmed_db: /local_raid1/sgoetz/DBSTORE/local_raid1/sgoetz/DBSTORE/PUBMED/PubMed.db
177
- pmc_db: /local_raid1/sgoetz/DBSTORE/local_raid1/sgoetz/DBSTORE/CAPTIONS/PMCSuppCaptions.db
176
+ pubmed_db: /local_raid1/sgoetz/DBSTORE/PUBMED/PubMed.db
177
+ pmc_db: /local_raid1/sgoetz/DBSTORE/CAPTIONS/PMCSuppCaptions.db
178
178
  ```
179
179
 
180
180
  This processes a single table configuration (ALAMV6.yaml) into a knowledge graph named `MULTIOMICS_KG_UNSTABLE`.