tablassert 7.4.14__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.
- {tablassert-7.4.14 → tablassert-7.5.0}/CHANGELOG.md +12 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/PKG-INFO +1 -1
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/api/lib.md +18 -14
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/api/qc.md +6 -6
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/api/utils.md +26 -7
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/cli.md +2 -2
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/configuration/advanced-example.md +19 -21
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/configuration/graph.md +16 -16
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/configuration/table.md +37 -30
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/docker.md +6 -4
- {tablassert-7.4.14 → tablassert-7.5.0}/pyproject.toml +1 -1
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/lib.py +15 -4
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/test_lib.py +53 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/uv.lock +1 -1
- {tablassert-7.4.14 → tablassert-7.5.0}/.github/workflows/docker.yml +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/.github/workflows/docs.yml +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/.github/workflows/pipy.yml +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/.gitignore +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/.pre-commit-config.yaml +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/AGENTS.md +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/CITATION.cff +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/CONTRIBUTING.md +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/Dockerfile +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/LICENSE +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/README.md +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/api/fullmap.md +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/changelog.md +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/datassert.md +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/examples/tutorial-data.csv +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/examples/tutorial-graph.yaml +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/examples/tutorial-table.yaml +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/examples.md +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/index.md +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/installation.md +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/docs/tutorial.md +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/llms.txt +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/mkdocs.yml +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/__init__.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/cli.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/downloader.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/enums.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/fullmap.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/ingests.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/log.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/models.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/nlp.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/progress.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/qc.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/src/tablassert/utils.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/__init__.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/conftest.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/fixtures/invalid_section_missing_source.yaml +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/fixtures/minimal_section.yaml +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/fixtures/minimal_section_with_sections.yaml +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/test_downloader.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/test_enums.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/test_fullmap.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/test_ingests.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/test_models.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/test_nlp.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/test_qc.py +0 -0
- {tablassert-7.4.14 → tablassert-7.5.0}/tests/test_utils.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
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
|
+
|
|
5
17
|
## 7.4.14 - 2026-06-30
|
|
6
18
|
|
|
7
19
|
### Changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tablassert
|
|
3
|
-
Version: 7.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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. **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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** |
|
|
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
|
|
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
|
-
**
|
|
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) >=
|
|
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 =
|
|
120
|
-
similarity = cosine_similarity(
|
|
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
|
|
23
|
-
- `"edges"`
|
|
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: "
|
|
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) # "
|
|
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
|
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
42
|
+
# Strip non-value text in place (rows are not dropped)
|
|
43
43
|
remove:
|
|
44
|
-
- "^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:
|
|
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). **
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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:
|
|
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`
|
|
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:
|
|
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.
|
|
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
|
|
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
|
|
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. **
|
|
143
|
-
|
|
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
|
-
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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/
|
|
177
|
-
pmc_db: /local_raid1/sgoetz/DBSTORE/
|
|
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`.
|
|
@@ -84,7 +84,7 @@ template:
|
|
|
84
84
|
syntax: TC3
|
|
85
85
|
|
|
86
86
|
sections:
|
|
87
|
-
- syntax:
|
|
87
|
+
- syntax: GC2 # Valid values are TC3 (default) and GC2; overriding is not recommended
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
### Use Cases
|
|
@@ -100,7 +100,7 @@ template:
|
|
|
100
100
|
```yaml
|
|
101
101
|
template:
|
|
102
102
|
source: {kind: excel, local: data.xlsx, url: https://example.com/data.xlsx}
|
|
103
|
-
provenance: {repo: PMC, publication:
|
|
103
|
+
provenance: {repo: PMC, publication: "PMC123", contributors: [{name: Example User, date: 27 JAN 2026}]}
|
|
104
104
|
|
|
105
105
|
sections:
|
|
106
106
|
- statement: {predicate: treats}
|
|
@@ -111,7 +111,7 @@ sections:
|
|
|
111
111
|
```yaml
|
|
112
112
|
template:
|
|
113
113
|
source: {kind: text, local: data.csv, url: https://example.com/data.csv}
|
|
114
|
-
provenance: {repo: PMID, publication: 456, contributors: [{name: Example User, date: 27 JAN 2026}]}
|
|
114
|
+
provenance: {repo: PMID, publication: "456", contributors: [{name: Example User, date: 27 JAN 2026}]}
|
|
115
115
|
statement:
|
|
116
116
|
subject: {method: column, encoding: A}
|
|
117
117
|
|
|
@@ -141,8 +141,8 @@ Defines the data file location and format.
|
|
|
141
141
|
| `local` | Path | Yes | Local file path for caching |
|
|
142
142
|
| `url` | URL | Yes | Download URL (HTTP/HTTPS). Validated as reachable at parse time. |
|
|
143
143
|
| `sheet` | String | No | Sheet name. Defaults to `"Sheet1"`. |
|
|
144
|
-
| `row_slice` | List[
|
|
145
|
-
| `rows` | List[
|
|
144
|
+
| `row_slice` | List[PositiveInt\|"auto"] | No | Two-value zero-based crop bounds: `[start, stop]`. Each value may be a positive integer or `"auto"`. Mutually exclusive with `rows`. |
|
|
145
|
+
| `rows` | List[PositiveInt] | No | Zero-based row indices to keep after any `row_slice` crop. Mutually exclusive with `row_slice`. |
|
|
146
146
|
| `reindex` | List[Reindex] | No | Conditional row filtering |
|
|
147
147
|
|
|
148
148
|
**Example:**
|
|
@@ -165,8 +165,8 @@ source:
|
|
|
165
165
|
| `local` | Path | Yes | Local file path for caching |
|
|
166
166
|
| `url` | URL | Yes | Download URL. Validated as reachable at parse time. |
|
|
167
167
|
| `delimiter` | String | No | Field delimiter. Defaults to `","`. |
|
|
168
|
-
| `row_slice` | List[
|
|
169
|
-
| `rows` | List[
|
|
168
|
+
| `row_slice` | List[PositiveInt\|"auto"] | No | Two-value zero-based crop bounds: `[start, stop]`. Each value may be a positive integer or `"auto"`. Mutually exclusive with `rows`. |
|
|
169
|
+
| `rows` | List[PositiveInt] | No | Zero-based row indices to keep after any `row_slice` crop. Mutually exclusive with `row_slice`. |
|
|
170
170
|
| `reindex` | List[Reindex] | No | Conditional filtering |
|
|
171
171
|
|
|
172
172
|
**Example:**
|
|
@@ -185,11 +185,11 @@ source:
|
|
|
185
185
|
|
|
186
186
|
Filter rows based on column values.
|
|
187
187
|
|
|
188
|
-
| Field | Type | Description |
|
|
189
|
-
|
|
190
|
-
| `column` | String | Source column letters to evaluate (`A`-`ZZZ`) |
|
|
191
|
-
| `comparison` | String | Operator. Defaults to `"ne"`; allowed values are `"eq"`, `"ne"`, `"lt"`, `"le"`, `"gt"`, `"ge"`. |
|
|
192
|
-
| `comparator` | String\|Int\|Float | Value to compare against. Must be a string for `"eq"`/`"ne"`, or a number for `"lt"`/`"le"`/`"gt"`/`"ge"`. |
|
|
188
|
+
| Field | Type | Required | Description |
|
|
189
|
+
|-------|------|----------|-------------|
|
|
190
|
+
| `column` | String | Yes | Source column letters to evaluate; constrained to `^[A-Z]{1,3}$` (`A`-`ZZZ`). |
|
|
191
|
+
| `comparison` | String | No | Operator. Defaults to `"ne"`; allowed values are `"eq"`, `"ne"`, `"lt"`, `"le"`, `"gt"`, `"ge"`. |
|
|
192
|
+
| `comparator` | String\|Int\|Float | Yes | Value to compare against. Must be a string for `"eq"`/`"ne"`, or a number for `"lt"`/`"le"`/`"gt"`/`"ge"`. |
|
|
193
193
|
|
|
194
194
|
**Example:**
|
|
195
195
|
```yaml
|
|
@@ -232,12 +232,12 @@ Defines how to extract and resolve entities.
|
|
|
232
232
|
|-------|------|----------|-------------|
|
|
233
233
|
| `method` | String | No | `"value"` (literal) or `"column"` (source column letters). Defaults to `"value"`. |
|
|
234
234
|
| `encoding` | String\|Int\|Float | Yes | Literal value or source column letters, depending on `method` |
|
|
235
|
-
| `taxon` |
|
|
235
|
+
| `taxon` | PositiveInt | No | NCBI Taxon ID for filtering (e.g., `9606` for human) |
|
|
236
236
|
| `prioritize` | List[String] | No | Preferred Biolink categories (must be valid `Categories` enum values such as `Gene`, `Protein`) |
|
|
237
237
|
| `avoid` | List[String] | No | Excluded Biolink categories (must be valid `Categories` enum values) |
|
|
238
238
|
| `regex` | List[Regex] | No | Pattern replacements |
|
|
239
239
|
| `fill` | String | No | Null-filling strategy: `"forward"`, `"backward"`, `"min"`, `"max"`, `"mean"`, `"zero"`, `"one"` |
|
|
240
|
-
| `remove` | List[String] | No |
|
|
240
|
+
| `remove` | List[Int\|Float\|String] | No | Regex patterns to remove (replaced with empty string) |
|
|
241
241
|
| `prefix` | String | No | Add prefix to values |
|
|
242
242
|
| `suffix` | String | No | Add suffix to values |
|
|
243
243
|
| `explode_by` | String | No | Delimiter to split multi-value cells |
|
|
@@ -314,7 +314,7 @@ Prevents misclassifying organism names as genes.
|
|
|
314
314
|
|
|
315
315
|
#### Text Transformations
|
|
316
316
|
|
|
317
|
-
**`regex: list[{pattern, replacement}]`** - Pattern-based replacements
|
|
317
|
+
**`regex: list[{pattern: Int|Float|String, replacement: Int|Float|String}]`** - Pattern-based replacements
|
|
318
318
|
|
|
319
319
|
```yaml
|
|
320
320
|
subject:
|
|
@@ -328,18 +328,18 @@ subject:
|
|
|
328
328
|
|
|
329
329
|
Executed in order.
|
|
330
330
|
|
|
331
|
-
> **Regex dialect:** Patterns are passed directly to Polars `str.replace_all()`, which uses the Rust [`regex`](https://docs.rs/regex/) crate. Only features supported by that engine work — in particular, **
|
|
331
|
+
> **Regex dialect:** Patterns are passed directly to Polars `str.replace_all()`, which uses the Rust [`regex`](https://docs.rs/regex/) crate. Only features supported by that engine work — in particular, **backreferences (`\1`, `\2`, …) and lookarounds (`(?=...)`, `(?<=...)`, `(?!...)`, `(?<!...)`) are not supported** and will raise an error at parse time. Plain groups `(...)` and non-capturing groups `(?:...)` *are* supported. Stick to character classes, anchors (`^`, `$`), quantifiers, alternation (`a|b`), and grouping if needed. If a transformation is too complex to express, prefer chaining several simple substitutions or capturing the residual context in a `miscellaneous notes` annotation instead.
|
|
332
332
|
|
|
333
|
-
**`remove: list[
|
|
333
|
+
**`remove: list[regex]`** - Regex patterns to remove
|
|
334
334
|
|
|
335
335
|
```yaml
|
|
336
336
|
subject:
|
|
337
337
|
encoding: A
|
|
338
338
|
remove:
|
|
339
|
-
- "^NA " #
|
|
339
|
+
- "^NA " # Strip leading "NA " prefix from cell text
|
|
340
340
|
```
|
|
341
341
|
|
|
342
|
-
Same regex constraints apply as the `regex` field — Polars-compatible patterns only, no
|
|
342
|
+
Each entry is applied as a regex replace-with-empty-string on the cell text in place (rows are not dropped). Same regex constraints apply as the `regex` field — Polars-compatible patterns only, no backreferences or lookarounds.
|
|
343
343
|
|
|
344
344
|
**`prefix` / `suffix`** - Add text
|
|
345
345
|
|
|
@@ -349,6 +349,13 @@ object:
|
|
|
349
349
|
prefix: "CUSTOM:" # "123" → "CUSTOM:123"
|
|
350
350
|
```
|
|
351
351
|
|
|
352
|
+
**Output columns: `original <col>` vs `<col> table literal value`** - For every subject/object/qualifier node, the pipeline snapshots the cell value into two edge columns at different stages:
|
|
353
|
+
|
|
354
|
+
- `<col> table literal value` - the **pristine source-cell value**, captured immediately after the column is read and *before* any `fill`, `explode_by`, `regex`, `remove`, `prefix`, `suffix`, or `transformations`. Emitted only when `method: column` (a `method: value` node has no table source).
|
|
355
|
+
- `original <col>` - the **fully-transformed value**, captured *after* all of the above, i.e. the same text that is then normalized and resolved to a CURIE. Always present for subject/object/qualifier nodes.
|
|
356
|
+
|
|
357
|
+
Example: with `method: column`, `encoding: A`, `remove: ["^NA "]` over a cell `"NA BRCA1"`, `subject table literal value` is `"NA BRCA1"` while `original subject` is `"BRCA1"`. Annotations never emit a table-literal column.
|
|
358
|
+
|
|
352
359
|
#### Null Handling
|
|
353
360
|
|
|
354
361
|
**`fill: string`** - Fill null values using a strategy
|
|
@@ -400,10 +407,10 @@ Use the `"values"` token to reference column values in transformations.
|
|
|
400
407
|
|
|
401
408
|
Add context to edges (anatomical location, species, etc.).
|
|
402
409
|
|
|
403
|
-
| Field | Type | Description |
|
|
404
|
-
|
|
405
|
-
| `qualifier` | String | Biolink qualifier from the `Qualifiers` enum (e.g., `"species_context_qualifier"`) |
|
|
406
|
-
| (inherits NodeEncoding) | | All NodeEncoding fields available |
|
|
410
|
+
| Field | Type | Required | Description |
|
|
411
|
+
|-------|------|----------|-------------|
|
|
412
|
+
| `qualifier` | String | Yes | Biolink qualifier from the `Qualifiers` enum (e.g., `"species_context_qualifier"`) |
|
|
413
|
+
| (inherits NodeEncoding) | | | All NodeEncoding fields available |
|
|
407
414
|
|
|
408
415
|
**Example:**
|
|
409
416
|
```yaml
|
|
@@ -420,7 +427,7 @@ Required metadata about data source.
|
|
|
420
427
|
| Field | Type | Required | Description |
|
|
421
428
|
|-------|------|----------|-------------|
|
|
422
429
|
| `repo` | String | No | Repository. Defaults to `"PMC"`; allowed values are `"PMC"`, `"PMID"`. |
|
|
423
|
-
| `publication` | String | Yes | Repository-local identifier
|
|
430
|
+
| `publication` | String | Yes | Repository-local identifier emitted as a CURIE. For `repo: PMC` the value **must** start with `PMC` followed by digits (e.g., `"PMC11708054"`, `"PMC123"`) and is emitted under the `PMCID:` namespace as `PMCID:PMC...` (previously `PMC:PMC...`); for `repo: PMID` it is emitted as `PMID:<publication>` (e.g., `"11708054"` → `PMID:11708054`). The `repository` output column still records the raw `repo` value (`PMC`/`PMID`). |
|
|
424
431
|
| `contributors` | List[Contributor] | Yes | Curation information |
|
|
425
432
|
|
|
426
433
|
**Contributor fields:**
|
|
@@ -437,7 +444,7 @@ Required metadata about data source.
|
|
|
437
444
|
```yaml
|
|
438
445
|
provenance:
|
|
439
446
|
repo: PMC
|
|
440
|
-
publication:
|
|
447
|
+
publication: "PMC11708054"
|
|
441
448
|
contributors:
|
|
442
449
|
- kind: curation
|
|
443
450
|
name: Skye Lane Goetz
|
|
@@ -452,10 +459,10 @@ provenance:
|
|
|
452
459
|
|
|
453
460
|
Optional edge attributes (statistical metadata, notes, etc.).
|
|
454
461
|
|
|
455
|
-
| Field | Type | Description |
|
|
456
|
-
|
|
457
|
-
| `annotation` | String | Attribute name (e.g., `"p value"`, `"sample size"`). Underscores are automatically replaced with spaces at parse time. |
|
|
458
|
-
| (inherits Encoding) | | All Encoding fields available (method, encoding, regex, etc.) |
|
|
462
|
+
| Field | Type | Required | Description |
|
|
463
|
+
|-------|------|----------|-------------|
|
|
464
|
+
| `annotation` | String | Yes | Attribute name (e.g., `"p value"`, `"sample size"`). Underscores are automatically replaced with spaces and the result is trimmed of leading/trailing whitespace at parse time. |
|
|
465
|
+
| (inherits Encoding) | | | All Encoding fields available (method, encoding, regex, etc.) |
|
|
459
466
|
|
|
460
467
|
**Example:**
|
|
461
468
|
```yaml
|
|
@@ -510,7 +517,7 @@ template:
|
|
|
510
517
|
|
|
511
518
|
provenance:
|
|
512
519
|
repo: PMID
|
|
513
|
-
publication: 12345678
|
|
520
|
+
publication: "12345678"
|
|
514
521
|
contributors:
|
|
515
522
|
- kind: curation
|
|
516
523
|
name: Example User
|
|
@@ -10,7 +10,7 @@ The image is based on `python:3.14-slim` with the Tablassert CLI as the entrypoi
|
|
|
10
10
|
docker pull ghcr.io/skyeav/tablassert:latest
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Version-pinned tags match the git tag (e.g., `ghcr.io/skyeav/tablassert:v7.
|
|
13
|
+
Version-pinned tags match the git tag (e.g., `ghcr.io/skyeav/tablassert:v7.5.0`).
|
|
14
14
|
|
|
15
15
|
## Quick Start
|
|
16
16
|
|
|
@@ -49,7 +49,7 @@ docker run --rm \
|
|
|
49
49
|
|
|
50
50
|
All dependencies ship in the base install, so the Docker image includes:
|
|
51
51
|
|
|
52
|
-
- **Quality control** — The QC pipeline in `src/tablassert/qc.py` runs a three-stage audit: exact match, then fuzzy matching via rapidfuzz (
|
|
52
|
+
- **Quality control** — The QC pipeline in `src/tablassert/qc.py` runs a three-stage audit: exact match, then fuzzy matching via rapidfuzz (`fuzz.ratio` >= 20 or `partial_token_sort_ratio` >= 30), then BioBERT sentence embeddings with cosine similarity (threshold >= 0.2). The ONNX model is cached in `.onnxassert/` (line 24).
|
|
53
53
|
- **Web downloads** — `src/tablassert/downloader.py` uses Playwright to download remote files with retry logic.
|
|
54
54
|
- **Legacy Excel** — `modernize_xls()` in `src/tablassert/downloader.py` converts `.xls` files using pyexcel.
|
|
55
55
|
|
|
@@ -61,7 +61,8 @@ Mount these volumes to persist data across container runs:
|
|
|
61
61
|
|---|---|---|
|
|
62
62
|
| `.storassert/` | `src/tablassert/utils.py:17` — `STORE` | Intermediate Parquet storage for compiled subgraphs |
|
|
63
63
|
| `.logassert/` | `src/tablassert/log.py` | Loguru log files with 100 MB rotation |
|
|
64
|
-
| `.onnxassert/` | `src/tablassert/qc.py:
|
|
64
|
+
| `.onnxassert/` | `src/tablassert/qc.py:24` — `MODEL` | Cached ONNX/BioBERT model |
|
|
65
|
+
| `.cachassert/` | `src/tablassert/models.py:76` — `CACHE` | diskcache store for URL-validation memoization |
|
|
65
66
|
|
|
66
67
|
Example:
|
|
67
68
|
|
|
@@ -72,6 +73,7 @@ docker run --rm \
|
|
|
72
73
|
-v ./.storassert:/app/.storassert \
|
|
73
74
|
-v ./.logassert:/app/.logassert \
|
|
74
75
|
-v ./.onnxassert:/app/.onnxassert \
|
|
76
|
+
-v ./.cachassert:/app/.cachassert \
|
|
75
77
|
-w /app \
|
|
76
78
|
ghcr.io/skyeav/tablassert:latest \
|
|
77
79
|
build /data/graph-config.yaml
|
|
@@ -87,4 +89,4 @@ docker run --rm \
|
|
|
87
89
|
|
|
88
90
|
## CI/CD Integration
|
|
89
91
|
|
|
90
|
-
Images are built by `.github/workflows/docker.yml`, which triggers on
|
|
92
|
+
Images are built by `.github/workflows/docker.yml`, which triggers when the `Auto Tag Versions` workflow completes on `main` (also runnable manually via `workflow_dispatch`). Tags match the repository version tag (e.g., `v7.5.0`).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tablassert"
|
|
3
|
-
version = "7.
|
|
3
|
+
version = "7.5.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" }
|
|
@@ -14,7 +14,7 @@ import lazy_loader as Lazy
|
|
|
14
14
|
from pydantic import Field, NonNegativeInt, PositiveInt
|
|
15
15
|
|
|
16
16
|
from tablassert.downloader import from_url
|
|
17
|
-
from tablassert.enums import Categories, EncodingMethods, Files, Tokens
|
|
17
|
+
from tablassert.enums import Categories, EncodingMethods, Files, Repositories, Tokens
|
|
18
18
|
from tablassert.fullmap import SHARDS, resolve
|
|
19
19
|
from tablassert.log import cat
|
|
20
20
|
from tablassert.models import Encoding, NodeEncoding, Section
|
|
@@ -279,11 +279,14 @@ class Tcode(Section):
|
|
|
279
279
|
log: bool = Field(False)
|
|
280
280
|
qc: bool = Field(False)
|
|
281
281
|
|
|
282
|
-
def encoding(self: Self, x: Encoding, col: str) -> list[Any]:
|
|
282
|
+
def encoding(self: Self, x: Encoding, col: str, table_literal: bool = False) -> list[Any]:
|
|
283
283
|
# ? Collect Helper For Encoding Classes
|
|
284
284
|
return [
|
|
285
285
|
(value, (col, x.encoding)) if eq(x.method, EncodingMethods.VALUE) else None,
|
|
286
286
|
(column, (col, idxname(x.encoding))) if eq(x.method, EncodingMethods.COLUMN) else None,
|
|
287
|
+
(column, (add(col, " table literal value"), col))
|
|
288
|
+
if (table_literal and eq(x.method, EncodingMethods.COLUMN))
|
|
289
|
+
else None,
|
|
287
290
|
(fill, (col, x.fill)) if x.fill else None,
|
|
288
291
|
(explode, (col, x.explode_by)) if x.explode_by else None,
|
|
289
292
|
[(regex, (col, r.pattern, r.replacement)) for r in x.regex] if x.regex else None,
|
|
@@ -295,7 +298,7 @@ class Tcode(Section):
|
|
|
295
298
|
|
|
296
299
|
def node(self: Self, x: NodeEncoding, col: str, conns: list[object]) -> list[Any]:
|
|
297
300
|
# ? Collect Helper For NodeEncoding Classes
|
|
298
|
-
encoding: list[Any] = self.encoding(x, col)
|
|
301
|
+
encoding: list[Any] = self.encoding(x, col, table_literal=True)
|
|
299
302
|
node: list[Any] = [
|
|
300
303
|
(column, (add("original ", col), col)),
|
|
301
304
|
(level_one, (col,)),
|
|
@@ -355,10 +358,11 @@ class Tcode(Section):
|
|
|
355
358
|
(value, ("section number", self.number)),
|
|
356
359
|
(value, ("status", self.status)),
|
|
357
360
|
(value, ("repository", self.provenance.repo)),
|
|
358
|
-
(value, ("publication", (self.provenance.repo
|
|
361
|
+
(value, ("publication", publication_curie(self.provenance.repo, self.provenance.publication))),
|
|
359
362
|
(contributor_values, ("contributors", self.provenance.contributors)),
|
|
360
363
|
(value, ("url", str(self.source.url))),
|
|
361
364
|
(value, ("section hash", self.store.stem)),
|
|
365
|
+
(value, ("sheet name", self.source.sheet)) if eq(self.source.kind, Files.EXCEL) else None, # pyright: ignore
|
|
362
366
|
(with_mesh, (pubmed_db, self.provenance.publication)) if pubmed_db else None,
|
|
363
367
|
(with_captions, (pmc_db, self.provenance.publication, str(self.source.url))) if pmc_db else None,
|
|
364
368
|
(sig, ()),
|
|
@@ -391,6 +395,13 @@ def normalize(
|
|
|
391
395
|
return nodes, edges_out
|
|
392
396
|
|
|
393
397
|
|
|
398
|
+
def publication_curie(repo: str, publication: str) -> str:
|
|
399
|
+
# ? Builds The Publication CURIE; PMCID Namespace For PubMed Central
|
|
400
|
+
if eq(repo, Repositories.PUBMED_CENTRAL):
|
|
401
|
+
return add("PMCID:", publication)
|
|
402
|
+
return add(repo, add(":", publication))
|
|
403
|
+
|
|
404
|
+
|
|
394
405
|
def publications(
|
|
395
406
|
edges: pl.LazyFrame, names: list[str] = ["id", "name", "first author", "journal", "year published"]
|
|
396
407
|
) -> tuple[pl.LazyFrame, pl.LazyFrame]:
|
|
@@ -182,6 +182,59 @@ def test_tcode_collect_enables_qc_logging(fixtures_path: Path) -> None:
|
|
|
182
182
|
assert qc_ops[1][1] == ("object", "sectionhash", "minimal_section.yaml", "passed", True)
|
|
183
183
|
|
|
184
184
|
|
|
185
|
+
# ? publication_curie Uses PMCID Namespace For PubMed Central
|
|
186
|
+
def test_publication_curie_pmc() -> None:
|
|
187
|
+
assert lib.publication_curie("PMC", "PMC1234567") == "PMCID:PMC1234567"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ? publication_curie Uses Repo Namespace For Non PMC Repositories
|
|
191
|
+
def test_publication_curie_pubmed() -> None:
|
|
192
|
+
assert lib.publication_curie("PMID", "11708054") == "PMID:11708054"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# ? Tcode Captures Table Literal Value Before Regex For Column Encoded Nodes
|
|
196
|
+
def test_tcode_table_literal_value_before_regex_for_columns(fixtures_path: Path) -> None:
|
|
197
|
+
data: Any = from_yaml(fixtures_path / "minimal_section.yaml")
|
|
198
|
+
store: Path = Path("/tmp/sectionhash.parquet")
|
|
199
|
+
data["statement"]["subject"] = {
|
|
200
|
+
"method": "column",
|
|
201
|
+
"encoding": "A",
|
|
202
|
+
"regex": [{"pattern": "\\s+", "replacement": " "}],
|
|
203
|
+
}
|
|
204
|
+
data["statement"]["object"] = {"method": "column", "encoding": "B"}
|
|
205
|
+
|
|
206
|
+
tcode_model: Tcode = Tcode.model_validate( # pyright: ignore
|
|
207
|
+
{**data, "number": 7, "config": fixtures_path / "minimal_section.yaml", "store": store}
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
collected: list[tuple[Any, tuple[Any]]] = tcode_model.collect([], None, None) # pyright: ignore
|
|
211
|
+
targets: list[str] = [op[1][0] for op in collected if op[0].__name__ == "column" and len(op[1]) > 1]
|
|
212
|
+
assert "subject table literal value" in targets
|
|
213
|
+
assert "object table literal value" in targets
|
|
214
|
+
|
|
215
|
+
lit_idx: int = next(
|
|
216
|
+
i for i, op in enumerate(collected) if len(op[1]) > 0 and op[1][0] == "subject table literal value"
|
|
217
|
+
)
|
|
218
|
+
regex_idx: int = next(
|
|
219
|
+
i for i, op in enumerate(collected) if op[0].__name__ == "regex" and len(op[1]) > 0 and op[1][0] == "subject"
|
|
220
|
+
)
|
|
221
|
+
assert lit_idx < regex_idx
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# ? Tcode Omits Table Literal Value For Value Encoded Nodes
|
|
225
|
+
def test_tcode_table_literal_value_absent_for_value_encoding(fixtures_path: Path) -> None:
|
|
226
|
+
data: Any = from_yaml(fixtures_path / "minimal_section.yaml")
|
|
227
|
+
store: Path = Path("/tmp/sectionhash.parquet")
|
|
228
|
+
tcode_model: Tcode = Tcode.model_validate( # pyright: ignore
|
|
229
|
+
{**data, "number": 7, "config": fixtures_path / "minimal_section.yaml", "store": store}
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
collected: list[tuple[Any, tuple[Any]]] = tcode_model.collect([], None, None) # pyright: ignore
|
|
233
|
+
targets: list[str] = [op[1][0] for op in collected if op[0].__name__ == "column" and len(op[1]) > 1]
|
|
234
|
+
assert "subject table literal value" not in targets
|
|
235
|
+
assert "object table literal value" not in targets
|
|
236
|
+
|
|
237
|
+
|
|
185
238
|
# ? resolve_many Skips QC When Disabled
|
|
186
239
|
def test_resolve_many_skips_qc(monkeypatch: Any, tmp_path: Path) -> None:
|
|
187
240
|
calls: list[tuple[Any, ...]] = []
|
|
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
|
|
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
|