structflo-ner 0.5.0__tar.gz → 0.6.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.
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/PKG-INFO +15 -1
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/README.md +14 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/__init__.py +1 -1
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/_examples.py +126 -5
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/_prompts.py +47 -15
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/README.md +1 -1
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/_loader.py +7 -2
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/_matcher.py +3 -2
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/tests/test_extractor.py +15 -1
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/tests/test_fast.py +14 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/uv.lock +3 -3
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/.github/workflows/ci.yml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/.github/workflows/publish.yml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/.gitignore +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/Makefile +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/coverage.xml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/images/fast-viz.png +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/images/local-gen-pandas.png +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/images/local-gen-viz.png +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/images/local-tb-viz.png +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/images/ner_visualization.gif +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/images/struct-flo-ner.png +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/notebooks/01_quickstart.ipynb +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/notebooks/02_fast_ner.ipynb +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/pyproject.toml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/_display.py +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/_entities.py +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/_mapping.py +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/extractor.py +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/__init__.py +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/_normalize.py +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/extractor.py +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/gazetteers/accession_number.yml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/gazetteers/compound_name.yml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/gazetteers/disease.yml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/gazetteers/functional_category.yml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/gazetteers/gene_name.yml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/gazetteers/product.yml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/gazetteers/screening_method.yml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/gazetteers/strain.yml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/gazetteers/target.yml +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/profiles.py +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/tests/__init__.py +0 -0
- {structflo_ner-0.5.0 → structflo_ner-0.6.0}/tests/test_entities.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: structflo-ner
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Drug discovery NER wrapper around LangExtract — zero-config entity extraction for chemistry and biology.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -347,6 +347,20 @@ df = result.to_dataframe()
|
|
|
347
347
|
result.to_dict()
|
|
348
348
|
```
|
|
349
349
|
|
|
350
|
+
Each bioactivity carries its measurement in `attributes`:
|
|
351
|
+
|
|
352
|
+
| Attribute | Example | Meaning |
|
|
353
|
+
| --------------- | --------------------------- | ---------------------------------------------------------- |
|
|
354
|
+
| `value` | `>20.0` | number as reported, qualifier kept |
|
|
355
|
+
| `unit` | `µM` | unit as written |
|
|
356
|
+
| `assay_type` | `CC50` | the endpoint (IC50, MIC90, GI50, ED90, ...) |
|
|
357
|
+
| `assay` | `HepG2 MTT` | assay, cell line or read-out the value was measured in |
|
|
358
|
+
| `compound_name` | `8t` | compound the value belongs to |
|
|
359
|
+
| `strain` | `H37Rv` | organism or strain (`TB` profile only) |
|
|
360
|
+
|
|
361
|
+
An attribute the text does not state comes back as the string `"None"` with
|
|
362
|
+
providers that enforce a strict schema (OpenAI), or is absent otherwise.
|
|
363
|
+
|
|
350
364
|
|
|
351
365
|
## Notebooks
|
|
352
366
|
|
|
@@ -334,6 +334,20 @@ df = result.to_dataframe()
|
|
|
334
334
|
result.to_dict()
|
|
335
335
|
```
|
|
336
336
|
|
|
337
|
+
Each bioactivity carries its measurement in `attributes`:
|
|
338
|
+
|
|
339
|
+
| Attribute | Example | Meaning |
|
|
340
|
+
| --------------- | --------------------------- | ---------------------------------------------------------- |
|
|
341
|
+
| `value` | `>20.0` | number as reported, qualifier kept |
|
|
342
|
+
| `unit` | `µM` | unit as written |
|
|
343
|
+
| `assay_type` | `CC50` | the endpoint (IC50, MIC90, GI50, ED90, ...) |
|
|
344
|
+
| `assay` | `HepG2 MTT` | assay, cell line or read-out the value was measured in |
|
|
345
|
+
| `compound_name` | `8t` | compound the value belongs to |
|
|
346
|
+
| `strain` | `H37Rv` | organism or strain (`TB` profile only) |
|
|
347
|
+
|
|
348
|
+
An attribute the text does not state comes back as the string `"None"` with
|
|
349
|
+
providers that enforce a strict schema (OpenAI), or is absent otherwise.
|
|
350
|
+
|
|
337
351
|
|
|
338
352
|
## Notebooks
|
|
339
353
|
|
|
@@ -158,8 +158,8 @@ BIOLOGY_EXAMPLES: list[lx.data.ExampleData] = [
|
|
|
158
158
|
|
|
159
159
|
_BIOACTIVITY_EXAMPLE_1 = lx.data.ExampleData(
|
|
160
160
|
text=(
|
|
161
|
-
"Compound 7 inhibited EGFR with an IC50 of 2.3 nM in a cell-free
|
|
162
|
-
"and showed an EC50 of 45 nM in A549 (human lung adenocarcinoma) cell proliferation assay. "
|
|
161
|
+
"Compound 7 (CHEMBL5171042) inhibited EGFR with an IC50 of 2.3 nM in a cell-free "
|
|
162
|
+
"enzymatic assay and showed an EC50 of 45 nM in A549 (human lung adenocarcinoma) cell proliferation assay. "
|
|
163
163
|
"Selectivity over ERBB2 was >100-fold (Ki = 0.8 nM vs 95 nM)."
|
|
164
164
|
),
|
|
165
165
|
extractions=[
|
|
@@ -170,7 +170,8 @@ _BIOACTIVITY_EXAMPLE_1 = lx.data.ExampleData(
|
|
|
170
170
|
"value": "2.3",
|
|
171
171
|
"unit": "nM",
|
|
172
172
|
"assay_type": "IC50",
|
|
173
|
-
"compound_name": "
|
|
173
|
+
"compound_name": "CHEMBL5171042",
|
|
174
|
+
"assay": "cell-free enzymatic assay",
|
|
174
175
|
},
|
|
175
176
|
),
|
|
176
177
|
lx.data.Extraction(
|
|
@@ -185,7 +186,8 @@ _BIOACTIVITY_EXAMPLE_1 = lx.data.ExampleData(
|
|
|
185
186
|
"value": "45",
|
|
186
187
|
"unit": "nM",
|
|
187
188
|
"assay_type": "EC50",
|
|
188
|
-
"compound_name": "
|
|
189
|
+
"compound_name": "CHEMBL5171042",
|
|
190
|
+
"assay": "A549 cell proliferation assay",
|
|
189
191
|
},
|
|
190
192
|
),
|
|
191
193
|
lx.data.Extraction(
|
|
@@ -204,7 +206,7 @@ _BIOACTIVITY_EXAMPLE_1 = lx.data.ExampleData(
|
|
|
204
206
|
"value": "0.8",
|
|
205
207
|
"unit": "nM",
|
|
206
208
|
"assay_type": "Ki",
|
|
207
|
-
"compound_name": "
|
|
209
|
+
"compound_name": "CHEMBL5171042",
|
|
208
210
|
},
|
|
209
211
|
),
|
|
210
212
|
],
|
|
@@ -337,6 +339,7 @@ _FULL_EXAMPLE_1 = lx.data.ExampleData(
|
|
|
337
339
|
"unit": "µM",
|
|
338
340
|
"assay_type": "IC50",
|
|
339
341
|
"compound_name": "Gefitinib",
|
|
342
|
+
"assay": "cell-free biochemical assay",
|
|
340
343
|
},
|
|
341
344
|
),
|
|
342
345
|
lx.data.Extraction(
|
|
@@ -357,6 +360,7 @@ _FULL_EXAMPLE_1 = lx.data.ExampleData(
|
|
|
357
360
|
"unit": "µM",
|
|
358
361
|
"assay_type": "IC50",
|
|
359
362
|
"compound_name": "Gefitinib",
|
|
363
|
+
"assay": "A431 cells",
|
|
360
364
|
},
|
|
361
365
|
),
|
|
362
366
|
lx.data.Extraction(
|
|
@@ -449,6 +453,7 @@ _TB_EXAMPLE_1 = lx.data.ExampleData(
|
|
|
449
453
|
"assay_type": "MIC",
|
|
450
454
|
"strain": "H37Rv",
|
|
451
455
|
"compound_name": "Bedaquiline",
|
|
456
|
+
"assay": "MABA",
|
|
452
457
|
},
|
|
453
458
|
),
|
|
454
459
|
lx.data.Extraction(
|
|
@@ -619,6 +624,7 @@ _TB_EXAMPLE_3 = lx.data.ExampleData(
|
|
|
619
624
|
"unit": "nM",
|
|
620
625
|
"assay_type": "IC50",
|
|
621
626
|
"compound_name": "Compound 14a",
|
|
627
|
+
"assay": "biochemical assay",
|
|
622
628
|
},
|
|
623
629
|
),
|
|
624
630
|
lx.data.Extraction(
|
|
@@ -643,6 +649,7 @@ _TB_EXAMPLE_3 = lx.data.ExampleData(
|
|
|
643
649
|
"assay_type": "MIC90",
|
|
644
650
|
"strain": "H37Rv",
|
|
645
651
|
"compound_name": "Compound 14a",
|
|
652
|
+
"assay": "REMA",
|
|
646
653
|
},
|
|
647
654
|
),
|
|
648
655
|
lx.data.Extraction(
|
|
@@ -658,6 +665,7 @@ _TB_EXAMPLE_3 = lx.data.ExampleData(
|
|
|
658
665
|
"unit": "uM",
|
|
659
666
|
"assay_type": "EC50",
|
|
660
667
|
"compound_name": "Compound 14a",
|
|
668
|
+
"assay": "THP-1 macrophage infection assay",
|
|
661
669
|
},
|
|
662
670
|
),
|
|
663
671
|
lx.data.Extraction(
|
|
@@ -673,6 +681,7 @@ _TB_EXAMPLE_3 = lx.data.ExampleData(
|
|
|
673
681
|
"unit": "uM",
|
|
674
682
|
"assay_type": "MIC",
|
|
675
683
|
"compound_name": "Compound 14a",
|
|
684
|
+
"assay": "LORA",
|
|
676
685
|
},
|
|
677
686
|
),
|
|
678
687
|
lx.data.Extraction(
|
|
@@ -688,6 +697,7 @@ _TB_EXAMPLE_3 = lx.data.ExampleData(
|
|
|
688
697
|
"unit": "uM",
|
|
689
698
|
"assay_type": "CC50",
|
|
690
699
|
"compound_name": "Compound 14a",
|
|
700
|
+
"assay": "HepG2 cells",
|
|
691
701
|
},
|
|
692
702
|
),
|
|
693
703
|
],
|
|
@@ -860,12 +870,123 @@ _TB_EXAMPLE_5 = lx.data.ExampleData(
|
|
|
860
870
|
],
|
|
861
871
|
)
|
|
862
872
|
|
|
873
|
+
# Table as Docling emits it: the column header names the assay, the endpoint
|
|
874
|
+
# and unit come from the footnote, and every cell is its own bioactivity. The
|
|
875
|
+
# ID column, not the deck-local row label, names the compound.
|
|
876
|
+
_TB_EXAMPLE_6 = lx.data.ExampleData(
|
|
877
|
+
text=(
|
|
878
|
+
"Table 2. Whole-cell activity\n\n"
|
|
879
|
+
"| Cpd | ID | MABA | THP-1 | Vero |\n"
|
|
880
|
+
"| --- | --- | --- | --- | --- |\n"
|
|
881
|
+
"| 21a | SACC-4412 | 0.12 | 0.9 | >50 |\n"
|
|
882
|
+
"| 21b | CHEMBL5290347 | <0.03 | 2.4* | 18.5 |\n\n"
|
|
883
|
+
"MABA: MIC90 (µM) against M. tuberculosis H37Rv. THP-1: intracellular EC90 (µM). "
|
|
884
|
+
"Vero: CC50 (µM). *Single determination."
|
|
885
|
+
),
|
|
886
|
+
extractions=[
|
|
887
|
+
lx.data.Extraction(
|
|
888
|
+
extraction_class="assay",
|
|
889
|
+
extraction_text="MABA",
|
|
890
|
+
attributes={"assay_format": "whole-cell", "strain": "H37Rv"},
|
|
891
|
+
),
|
|
892
|
+
lx.data.Extraction(
|
|
893
|
+
extraction_class="assay",
|
|
894
|
+
extraction_text="THP-1",
|
|
895
|
+
attributes={"cell_line": "THP-1", "assay_format": "intracellular"},
|
|
896
|
+
),
|
|
897
|
+
lx.data.Extraction(
|
|
898
|
+
extraction_class="assay",
|
|
899
|
+
extraction_text="Vero",
|
|
900
|
+
attributes={"cell_line": "Vero", "assay_format": "cytotoxicity"},
|
|
901
|
+
),
|
|
902
|
+
lx.data.Extraction(
|
|
903
|
+
extraction_class="compound_name",
|
|
904
|
+
extraction_text="SACC-4412",
|
|
905
|
+
attributes={"synonyms": "21a"},
|
|
906
|
+
),
|
|
907
|
+
lx.data.Extraction(
|
|
908
|
+
extraction_class="bioactivity",
|
|
909
|
+
extraction_text="0.12",
|
|
910
|
+
attributes={
|
|
911
|
+
"value": "0.12",
|
|
912
|
+
"unit": "µM",
|
|
913
|
+
"assay_type": "MIC90",
|
|
914
|
+
"strain": "H37Rv",
|
|
915
|
+
"compound_name": "SACC-4412",
|
|
916
|
+
"assay": "MABA",
|
|
917
|
+
},
|
|
918
|
+
),
|
|
919
|
+
lx.data.Extraction(
|
|
920
|
+
extraction_class="bioactivity",
|
|
921
|
+
extraction_text="0.9",
|
|
922
|
+
attributes={
|
|
923
|
+
"value": "0.9",
|
|
924
|
+
"unit": "µM",
|
|
925
|
+
"assay_type": "EC90",
|
|
926
|
+
"compound_name": "SACC-4412",
|
|
927
|
+
"assay": "THP-1",
|
|
928
|
+
},
|
|
929
|
+
),
|
|
930
|
+
lx.data.Extraction(
|
|
931
|
+
extraction_class="bioactivity",
|
|
932
|
+
extraction_text=">50",
|
|
933
|
+
attributes={
|
|
934
|
+
"value": ">50",
|
|
935
|
+
"unit": "µM",
|
|
936
|
+
"assay_type": "CC50",
|
|
937
|
+
"compound_name": "SACC-4412",
|
|
938
|
+
"assay": "Vero",
|
|
939
|
+
},
|
|
940
|
+
),
|
|
941
|
+
lx.data.Extraction(
|
|
942
|
+
extraction_class="compound_name",
|
|
943
|
+
extraction_text="CHEMBL5290347",
|
|
944
|
+
attributes={"synonyms": "21b"},
|
|
945
|
+
),
|
|
946
|
+
lx.data.Extraction(
|
|
947
|
+
extraction_class="bioactivity",
|
|
948
|
+
extraction_text="<0.03",
|
|
949
|
+
attributes={
|
|
950
|
+
"value": "<0.03",
|
|
951
|
+
"unit": "µM",
|
|
952
|
+
"assay_type": "MIC90",
|
|
953
|
+
"strain": "H37Rv",
|
|
954
|
+
"compound_name": "CHEMBL5290347",
|
|
955
|
+
"assay": "MABA",
|
|
956
|
+
},
|
|
957
|
+
),
|
|
958
|
+
lx.data.Extraction(
|
|
959
|
+
extraction_class="bioactivity",
|
|
960
|
+
extraction_text="2.4",
|
|
961
|
+
attributes={
|
|
962
|
+
"value": "2.4",
|
|
963
|
+
"unit": "µM",
|
|
964
|
+
"assay_type": "EC90",
|
|
965
|
+
"compound_name": "CHEMBL5290347",
|
|
966
|
+
"assay": "THP-1",
|
|
967
|
+
},
|
|
968
|
+
),
|
|
969
|
+
lx.data.Extraction(
|
|
970
|
+
extraction_class="bioactivity",
|
|
971
|
+
extraction_text="18.5",
|
|
972
|
+
attributes={
|
|
973
|
+
"value": "18.5",
|
|
974
|
+
"unit": "µM",
|
|
975
|
+
"assay_type": "CC50",
|
|
976
|
+
"compound_name": "CHEMBL5290347",
|
|
977
|
+
"assay": "Vero",
|
|
978
|
+
},
|
|
979
|
+
),
|
|
980
|
+
],
|
|
981
|
+
)
|
|
982
|
+
|
|
863
983
|
TB_EXAMPLES: list[lx.data.ExampleData] = [
|
|
864
984
|
_TB_EXAMPLE_1,
|
|
865
985
|
_TB_EXAMPLE_2,
|
|
866
986
|
_TB_EXAMPLE_3,
|
|
867
987
|
_TB_EXAMPLE_4,
|
|
868
988
|
_TB_EXAMPLE_5,
|
|
989
|
+
_TB_EXAMPLE_6,
|
|
869
990
|
]
|
|
870
991
|
|
|
871
992
|
TB_CHEMISTRY_EXAMPLES: list[lx.data.ExampleData] = [
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
"""Prompt strings for each built-in EntityProfile."""
|
|
2
2
|
|
|
3
|
+
# Shared by every prompt that extracts bioactivity values. The attribute names
|
|
4
|
+
# must match the keys used in the few-shot examples: langextract derives the
|
|
5
|
+
# output schema from those keys, not from this text.
|
|
6
|
+
_BIOACTIVITY_ATTRIBUTES = (
|
|
7
|
+
"For each bioactivity value capture:\n"
|
|
8
|
+
"- value: the number as reported, keeping qualifiers ('>20.0', '<1.0') and dropping "
|
|
9
|
+
"footnote markers ('0.3*' → '0.3').\n"
|
|
10
|
+
"- unit: as written (nM, µM, µg/mL, mg/kg, %, h).\n"
|
|
11
|
+
"- assay_type: the endpoint only, in plain characters (CC₅₀ → CC50). Common endpoints: "
|
|
12
|
+
"IC50, IC90, EC50, EC90, pIC50, Ki, Kd, '% inhibition at <concentration>'; "
|
|
13
|
+
"MIC, MIC50, MIC90, MIC99, MBC, log10 CFU reduction; CC50, TC50, SI (selectivity index); "
|
|
14
|
+
"GI50, TGI, LC50; DC50, Dmax (degraders); ED50, ED90, %TGI, T/C (in vivo); "
|
|
15
|
+
"PRR (parasite reduction ratio), parasite clearance half-life.\n"
|
|
16
|
+
"- assay: the assay, cell line or read-out the value was measured in, e.g. 'MABA', "
|
|
17
|
+
"'LORA', 'HepG2 MTT', 'THP-1 macrophage infection assay', 'asexual blood-stage', "
|
|
18
|
+
"'liver-stage', 'gametocyte', 'NCI-60', 'hERG', 'P. berghei 4-day suppressive test'. "
|
|
19
|
+
"null when no assay is stated.\n"
|
|
20
|
+
"- compound_name: the compound the value belongs to, by its name or identifier "
|
|
21
|
+
"(e.g. 'bedaquiline', 'BTZ043', 'CHEMBL1234', 'SACC-3000'); a document-local label "
|
|
22
|
+
"('7a', 'Compound 9b') only when nothing else names the compound.\n"
|
|
23
|
+
)
|
|
24
|
+
_BIOACTIVITY_TABLES = (
|
|
25
|
+
"In a data table, extract every cell value as its own bioactivity. An ID or registry "
|
|
26
|
+
"column, when present, supplies the compound_name and the row label is only its synonym; "
|
|
27
|
+
"otherwise the row label is the compound_name. A column header naming an assay or cell "
|
|
28
|
+
"line is the assay, and the endpoint and unit often come from the caption or a footnote "
|
|
29
|
+
"('CC50 in µM')."
|
|
30
|
+
)
|
|
31
|
+
|
|
3
32
|
CHEMISTRY_PROMPT = (
|
|
4
33
|
"Extract chemical entities from the text. "
|
|
5
34
|
"Include: compound names (generic names, IUPAC names, code names like 'Compound 5b', "
|
|
@@ -17,14 +46,11 @@ BIOLOGY_PROMPT = (
|
|
|
17
46
|
)
|
|
18
47
|
|
|
19
48
|
BIOACTIVITY_PROMPT = (
|
|
20
|
-
"Extract bioactivity measurements and assay data from the text
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"'CHEMBL1234', 'SACC-3000') — that the measurement applies to, as a 'compound_name' attribute, "
|
|
26
|
-
"if it can be identified from the surrounding context. "
|
|
27
|
-
"Also extract assay descriptions: cell lines used (e.g. 'HeLa', 'A549'), assay formats "
|
|
49
|
+
"Extract bioactivity measurements and assay data from the text: potency, selectivity, "
|
|
50
|
+
"cytotoxicity and efficacy values.\n"
|
|
51
|
+
+ _BIOACTIVITY_ATTRIBUTES
|
|
52
|
+
+ _BIOACTIVITY_TABLES
|
|
53
|
+
+ "\nAlso extract assay descriptions: cell lines used (e.g. 'HeLa', 'A549'), assay formats "
|
|
28
54
|
"(e.g. 'cell viability', 'binding assay', 'enzymatic assay'), and organisms."
|
|
29
55
|
)
|
|
30
56
|
|
|
@@ -43,22 +69,23 @@ FULL_PROMPT = (
|
|
|
43
69
|
"- Chemical entities: compound names (generic, IUPAC, code names, brand names), "
|
|
44
70
|
"SMILES strings (only if explicitly written), CAS numbers, molecular formulas.\n"
|
|
45
71
|
"- Biological targets: protein names, gene names, receptor names, enzyme names, pathways.\n"
|
|
46
|
-
"- Bioactivity data:
|
|
47
|
-
"
|
|
48
|
-
"(captured as a 'compound_name' attribute).\n"
|
|
72
|
+
"- Bioactivity data: potency, selectivity, cytotoxicity and efficacy values "
|
|
73
|
+
"(attributes below).\n"
|
|
49
74
|
"- Assay information: cell lines, assay formats, experimental organisms.\n"
|
|
50
75
|
"- Diseases and indications: cancer types, disease names, therapeutic areas.\n"
|
|
51
76
|
"- Mechanisms of action: binding modes, inhibition types, selectivity descriptions.\n"
|
|
52
|
-
|
|
77
|
+
+ _BIOACTIVITY_ATTRIBUTES
|
|
78
|
+
+ _BIOACTIVITY_TABLES
|
|
79
|
+
+ "\nExtract only what is explicitly stated; do not infer or generate values."
|
|
53
80
|
)
|
|
54
81
|
|
|
55
82
|
# ── Tuberculosis early drug discovery prompts ──────────────────────────
|
|
56
83
|
|
|
57
84
|
TB_PROMPT = (
|
|
58
85
|
"Extract drug discovery entities from this tuberculosis research text.\n\n"
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
"
|
|
86
|
+
+ _BIOACTIVITY_ATTRIBUTES
|
|
87
|
+
+ "- strain: the organism or strain tested (H37Rv, Erdman, Pf3D7, Dd2, K1), "
|
|
88
|
+
"null when not stated.\n" + _BIOACTIVITY_TABLES + "\n\n"
|
|
62
89
|
"DISAMBIGUATION RULES:\n"
|
|
63
90
|
"- Mycobacterial proteins (e.g. ClpC1, DprE1, InhA, AtpE, MmpL3, QcrB) "
|
|
64
91
|
"are biological targets, NOT compounds.\n"
|
|
@@ -67,6 +94,11 @@ TB_PROMPT = (
|
|
|
67
94
|
"- Compound registry and programme identifiers (CHEMBL4521987, ZINC000012345678, "
|
|
68
95
|
"DB00945, SACC-3060, TBDA-01187, GSK3036656) are compound_name; only Rv locus "
|
|
69
96
|
"tags, UniProt IDs, PDB codes and RefSeq IDs are accession_number.\n"
|
|
97
|
+
"- A compound with both a document-local label (a number with an optional letter: 7a, "
|
|
98
|
+
"12, 'Compound 9b') and a longer identifier (registry ID; programme, partner or vendor "
|
|
99
|
+
"code: CHEMBL…, SACC-…, a ChemBridge number) is extracted once, as the identifier, with "
|
|
100
|
+
"the label in synonyms; its bioactivities use the identifier as compound_name. A label "
|
|
101
|
+
"is the compound_name only when the document gives nothing else.\n"
|
|
70
102
|
"- Enzyme descriptions like 'enoyl-ACP reductase' are product, not target.\n"
|
|
71
103
|
"- 'cell wall', 'lipid metabolism' are functional_category, not mechanism_of_action.\n"
|
|
72
104
|
"- 'fragment screening', 'biochemical assay' are screening_method, not assay.\n"
|
|
@@ -66,7 +66,7 @@ so a deployment's own gazetteer decides which ones apply:
|
|
|
66
66
|
| Seed | Auto-derived Pattern | Matches |
|
|
67
67
|
|---|---|---|
|
|
68
68
|
| `Rv0005` | `Rv\d{4}[c]?` | All Rv locus tags |
|
|
69
|
-
| `MT0005` | `MT\w
|
|
69
|
+
| `MT0005` | `MT[A-Z]{0,2}\d{2,}\w*` | Mycobrowser IDs (not MTT, MTX) |
|
|
70
70
|
| `P9WGR1` | `[OPQ][0-9][A-Z0-9]{3}[0-9]` | UniProt accessions |
|
|
71
71
|
| `4TZK` | `(?=[A-Z0-9]{0,3}[A-Z])[1-9][A-Z0-9]{3}` | PDB codes |
|
|
72
72
|
| `WP_003407354` | `WP_\d+` | NCBI RefSeq proteins |
|
|
@@ -89,8 +89,13 @@ _COMPOUND_ID_PATTERNS: list[IdPattern] = [
|
|
|
89
89
|
_ACCESSION_PATTERNS: list[tuple[re.Pattern[str], re.Pattern[str], str]] = [
|
|
90
90
|
# Rv locus tags: Rv0005, Rv3854c
|
|
91
91
|
(re.compile(r"^Rv\d{4}[c]?$"), re.compile(r"\bRv\d{4}[c]?\b"), "Rv locus tag"),
|
|
92
|
-
# Mycobrowser MT IDs: MT0005,
|
|
93
|
-
(
|
|
92
|
+
# Mycobrowser MT IDs: MT0005, MT18B_0001, MTB000001. The digit run keeps
|
|
93
|
+
# MTT (viability assay), MTX and MTD from matching.
|
|
94
|
+
(
|
|
95
|
+
re.compile(r"^MT[A-Z]{0,2}\d{2,}\w*$"),
|
|
96
|
+
re.compile(r"\bMT[A-Z]{0,2}\d{2,}\w*\b"),
|
|
97
|
+
"Mycobrowser ID",
|
|
98
|
+
),
|
|
94
99
|
# UniProt accessions: P9WGR1, O53617
|
|
95
100
|
(
|
|
96
101
|
re.compile(r"^[OPQ][0-9][A-Z0-9]{3}[0-9]$"),
|
|
@@ -275,8 +275,9 @@ def _build_position_map(original: str, normalized: str) -> list[int]:
|
|
|
275
275
|
|
|
276
276
|
# Advance original pointer to find the matching character
|
|
277
277
|
while orig_idx < len(original):
|
|
278
|
-
|
|
279
|
-
|
|
278
|
+
# normalize() the char too: a newline became " " and an en dash "-",
|
|
279
|
+
# and comparing raw chars would desync every position after them.
|
|
280
|
+
if (normalize(original[orig_idx]) or " ") == norm_char:
|
|
280
281
|
position_map.append(orig_idx)
|
|
281
282
|
orig_idx += 1
|
|
282
283
|
break
|
|
@@ -7,6 +7,7 @@ from unittest.mock import MagicMock
|
|
|
7
7
|
import langextract as lx
|
|
8
8
|
|
|
9
9
|
from structflo.ner import (
|
|
10
|
+
BIOACTIVITY,
|
|
10
11
|
BIOLOGY,
|
|
11
12
|
CHEMISTRY,
|
|
12
13
|
FULL,
|
|
@@ -284,8 +285,21 @@ class TestTBProfile:
|
|
|
284
285
|
for cls in TB_BIOLOGY.entity_classes:
|
|
285
286
|
assert cls in TB.entity_classes
|
|
286
287
|
|
|
288
|
+
def test_bioactivity_schema_has_assay(self):
|
|
289
|
+
"""langextract builds the output schema from example attribute keys, so an
|
|
290
|
+
`assay` slot exists only if some bioactivity example carries one."""
|
|
291
|
+
for profile in (TB, BIOACTIVITY, FULL):
|
|
292
|
+
keys = {
|
|
293
|
+
k
|
|
294
|
+
for ex in profile.examples
|
|
295
|
+
for e in ex.extractions
|
|
296
|
+
if e.extraction_class == "bioactivity"
|
|
297
|
+
for k in (e.attributes or {})
|
|
298
|
+
}
|
|
299
|
+
assert {"value", "unit", "assay_type", "compound_name", "assay"} <= keys, profile.name
|
|
300
|
+
|
|
287
301
|
def test_tb_examples_count(self):
|
|
288
|
-
assert len(TB.examples) ==
|
|
302
|
+
assert len(TB.examples) == 6
|
|
289
303
|
assert len(TB_CHEMISTRY.examples) == 2
|
|
290
304
|
assert len(TB_BIOLOGY.examples) == 2
|
|
291
305
|
|
|
@@ -180,6 +180,13 @@ class TestDeriveIdPatterns:
|
|
|
180
180
|
assert not pdb.regex.search("in 2019 the trial")
|
|
181
181
|
assert not pdb.regex.search("n = 1234")
|
|
182
182
|
|
|
183
|
+
def test_mycobrowser_pattern_needs_locus_digits(self):
|
|
184
|
+
"""MTT (viability assay), MTX and MTD are not Mycobrowser loci."""
|
|
185
|
+
mt = next(p for p in _accessions(["MT18B_0001"]) if p.description == "Mycobrowser ID")
|
|
186
|
+
for locus in ["MT0005", "MT18B_0001", "MTB000001"]:
|
|
187
|
+
assert mt.regex.fullmatch(locus), locus
|
|
188
|
+
assert not mt.regex.search("HepG2 MTT, MTX and MTD read-outs")
|
|
189
|
+
|
|
183
190
|
def test_cas_check_digit_rejects_dates(self):
|
|
184
191
|
cas = next(p for p in derive_id_patterns({}) if p.description == "CAS number")
|
|
185
192
|
assert cas.validate("50-78-2") # aspirin
|
|
@@ -246,6 +253,13 @@ class TestGazetteerMatcher:
|
|
|
246
253
|
m = inha_matches[0]
|
|
247
254
|
assert text[m.char_start : m.char_end] == "InhA"
|
|
248
255
|
|
|
256
|
+
def test_char_offsets_survive_newlines(self):
|
|
257
|
+
"""normalize() folds a newline into a space; offsets after it must not drift."""
|
|
258
|
+
matcher = self._simple_matcher(fuzzy_threshold=0)
|
|
259
|
+
text = "Targets:\nINHA and\n\nDPRE1."
|
|
260
|
+
found = {m.canonical: text[m.char_start : m.char_end] for m in matcher.match(text)}
|
|
261
|
+
assert found == {"InhA": "INHA", "DprE1": "DPRE1"}
|
|
262
|
+
|
|
249
263
|
def test_regex_accession_matching(self):
|
|
250
264
|
matcher = GazetteerMatcher(
|
|
251
265
|
gazetteers={"target": ["InhA"]},
|
|
@@ -917,7 +917,7 @@ wheels = [
|
|
|
917
917
|
|
|
918
918
|
[[package]]
|
|
919
919
|
name = "langextract"
|
|
920
|
-
version = "1.
|
|
920
|
+
version = "1.7.0"
|
|
921
921
|
source = { registry = "https://pypi.org/simple" }
|
|
922
922
|
dependencies = [
|
|
923
923
|
{ name = "absl-py" },
|
|
@@ -940,9 +940,9 @@ dependencies = [
|
|
|
940
940
|
{ name = "tqdm" },
|
|
941
941
|
{ name = "typing-extensions" },
|
|
942
942
|
]
|
|
943
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
943
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e3/e7/6cf877cdd3d5d3a479c0688ff2b63c665c4319ae35ba00734c9fef36a1d2/langextract-1.7.0.tar.gz", hash = "sha256:cb6dc515583b0fdc0f9eff9dce61b2a3c7c84b0668add4dd4e7be364f4c188ba", size = 147514, upload-time = "2026-09-13T18:58:06.376Z" }
|
|
944
944
|
wheels = [
|
|
945
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
945
|
+
{ url = "https://files.pythonhosted.org/packages/ae/ec/2883a93872701045971ea77e30434d72556a557f188df575b154d179854c/langextract-1.7.0-py3-none-any.whl", hash = "sha256:908f8ec696ab13578cdc3862efcf1a4fda929ae426410d99e86977d50e52136d", size = 154297, upload-time = "2026-09-13T18:58:04.896Z" },
|
|
946
946
|
]
|
|
947
947
|
|
|
948
948
|
[[package]]
|
|
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
|
{structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/gazetteers/accession_number.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/gazetteers/functional_category.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{structflo_ner-0.5.0 → structflo_ner-0.6.0}/structflo/ner/fast/gazetteers/screening_method.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|