structflo-ner 0.2.2__tar.gz → 0.2.3__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.2.2 → structflo_ner-0.2.3}/.github/workflows/ci.yml +10 -3
- structflo_ner-0.2.3/PKG-INFO +397 -0
- structflo_ner-0.2.3/README.md +384 -0
- structflo_ner-0.2.3/coverage.xml +620 -0
- structflo_ner-0.2.3/images/fast-viz.png +0 -0
- structflo_ner-0.2.3/images/local-gen-pandas.png +0 -0
- structflo_ner-0.2.3/images/local-gen-viz.png +0 -0
- structflo_ner-0.2.3/images/local-tb-viz.png +0 -0
- structflo_ner-0.2.3/images/struct-flo-ner.png +0 -0
- structflo_ner-0.2.3/notebooks/01_quickstart.ipynb +844 -0
- structflo_ner-0.2.3/notebooks/02_fast_ner.ipynb +686 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/pyproject.toml +3 -1
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/__init__.py +4 -7
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/_display.py +1 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/_entities.py +10 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/fast/_loader.py +2 -2
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/fast/_matcher.py +2 -2
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/fast/extractor.py +1 -0
- structflo_ner-0.2.3/structflo/ner/fast/gazetteers/accession_number.yml +47824 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/fast/gazetteers/disease.yml +2 -0
- structflo_ner-0.2.3/structflo/ner/fast/gazetteers/functional_category.yml +46 -0
- structflo_ner-0.2.3/structflo/ner/fast/gazetteers/gene_name.yml +37157 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/fast/gazetteers/product.yml +1 -1
- structflo_ner-0.2.3/structflo/ner/fast/gazetteers/screening_method.yml +131 -0
- structflo_ner-0.2.3/structflo/ner/fast/gazetteers/strain.yml +42 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/tests/test_entities.py +1 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/tests/test_fast.py +1 -4
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/uv.lock +139 -7
- structflo_ner-0.2.2/PKG-INFO +0 -140
- structflo_ner-0.2.2/README.md +0 -126
- structflo_ner-0.2.2/notebooks/01_quickstart.ipynb +0 -262
- structflo_ner-0.2.2/notebooks/02_fast_ner.ipynb +0 -416
- structflo_ner-0.2.2/structflo/ner/fast/gazetteers/accession_number.yml +0 -18
- structflo_ner-0.2.2/structflo/ner/fast/gazetteers/functional_category.yml +0 -27
- structflo_ner-0.2.2/structflo/ner/fast/gazetteers/gene_name.yml +0 -75
- structflo_ner-0.2.2/structflo/ner/fast/gazetteers/screening_method.yml +0 -38
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/.github/workflows/publish.yml +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/.gitignore +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/Makefile +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/_examples.py +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/_mapping.py +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/_prompts.py +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/extractor.py +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/fast/README.md +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/fast/__init__.py +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/fast/_normalize.py +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/fast/gazetteers/compound_name.yml +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/fast/gazetteers/target.yml +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/structflo/ner/profiles.py +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/tests/__init__.py +0 -0
- {structflo_ner-0.2.2 → structflo_ner-0.2.3}/tests/test_extractor.py +0 -0
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
run: uv python install 3.12
|
|
20
20
|
|
|
21
21
|
- name: Install dependencies
|
|
22
|
-
run: uv sync --dev --extra
|
|
22
|
+
run: uv sync --dev --extra dataframe
|
|
23
23
|
|
|
24
24
|
- name: Lint
|
|
25
25
|
run: uv run ruff check structflo/ tests/
|
|
@@ -27,5 +27,12 @@ jobs:
|
|
|
27
27
|
- name: Format
|
|
28
28
|
run: uv run ruff format --check structflo/ tests/
|
|
29
29
|
|
|
30
|
-
- name: Test
|
|
31
|
-
run: uv run pytest -q
|
|
30
|
+
- name: Test with coverage
|
|
31
|
+
run: uv run pytest -q --cov=structflo --cov-report=xml
|
|
32
|
+
|
|
33
|
+
- name: Upload coverage to Codecov
|
|
34
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
35
|
+
uses: codecov/codecov-action@v4
|
|
36
|
+
with:
|
|
37
|
+
files: coverage.xml
|
|
38
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: structflo-ner
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Drug discovery NER wrapper around LangExtract — zero-config entity extraction for chemistry and biology.
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: langextract>=1.1.1
|
|
8
|
+
Requires-Dist: pyyaml>=6.0
|
|
9
|
+
Requires-Dist: rapidfuzz>=3.0
|
|
10
|
+
Provides-Extra: dataframe
|
|
11
|
+
Requires-Dist: pandas>=1.5; extra == 'dataframe'
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
<h1 align="center">structflo.ner</h1>
|
|
16
|
+
<p align="center">
|
|
17
|
+
<img src="./images/struct-flo-ner.png" alt="structflo.ner logo" width="600">
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<a href="https://pepy.tech/projects/structflo-ner"><img src="https://static.pepy.tech/personalized-badge/structflo-ner?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads" alt="PyPI Downloads"></a>
|
|
22
|
+
<a href="https://github.com/structflo/structflo-ner/actions"><img src="https://img.shields.io/github/actions/workflow/status/structflo/structflo-ner/ci.yml?label=tests" alt="Tests"></a>
|
|
23
|
+
<a href="https://codecov.io/gh/structflo/structflo-ner"><img src="https://codecov.io/gh/structflo/structflo-ner/branch/main/graph/badge.svg" alt="Coverage"></a>
|
|
24
|
+
<a href="https://github.com/structflo/structflo-ner/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-green.svg" alt="License"></a>
|
|
25
|
+
<a href="https://www.linkedin.com/in/sidxz/"><img src="https://img.shields.io/badge/LinkedIn-blue?logo=linkedin&logoColor=white" alt="LinkedIn"></a>
|
|
26
|
+
<a href="https://github.com/sidxz/"><img src="https://img.shields.io/badge/GitHub-black?logo=github&logoColor=white" alt="GitHub"></a>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
<p align="center">
|
|
30
|
+
Zero-config Named Entity Recognition for drug discovery, chemistry, and biological sciences.
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
<a href="#installation">Installation</a> •
|
|
37
|
+
<a href="#llm-powered-extraction">LLM Extraction</a> •
|
|
38
|
+
<a href="#fast-dictionary-based-ner">Fast NER</a> •
|
|
39
|
+
<a href="#profiles">Profiles</a> •
|
|
40
|
+
<a href="#visualization">Visualization</a> •
|
|
41
|
+
<a href="#notebooks">Notebooks</a>
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
**structflo.ner** is a lightweight NER library specialized for pharmaceutical and biological sciences. It uses [LangExtract](https://github.com/langextract/langextract) and fuzzy based tools to deliver **zero-configuration** entity extraction.
|
|
47
|
+
|
|
48
|
+
It ships with two extraction engines:
|
|
49
|
+
|
|
50
|
+
| | `NERExtractor` | `FastNERExtractor` |
|
|
51
|
+
| ----------------- | ----------------------------- | ---------------------------------- |
|
|
52
|
+
| Approach | LLM-powered (Gemini, Ollama) | Dictionary-based (YAML gazetteers) |
|
|
53
|
+
| Speed | ~10-60s per abstract | ~0.4-1s per abstract |
|
|
54
|
+
| Novel entities | Discovers new entities | Known terms only |
|
|
55
|
+
| Context awareness | Full contextual understanding | String matching (exact + fuzzy) |
|
|
56
|
+
| Cost | API costs or local GPU | Free (no API calls) |
|
|
57
|
+
| Setup | API key or Ollama | Zero config |
|
|
58
|
+
| Output format | `NERResult` | `NERResult` (identical) |
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install structflo-ner
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# or with uv
|
|
68
|
+
uv add structflo-ner
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Install optional extras as needed:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install "structflo-ner[dataframe]" # pandas DataFrame support
|
|
75
|
+
pip install "structflo-ner[fast]" # fast dictionary-based NER (rapidfuzz)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## LLM-Powered Extraction
|
|
79
|
+
|
|
80
|
+
### Cloud model (Gemini)
|
|
81
|
+
|
|
82
|
+
The default model is `gemini-2.5-flash`. Pass your API key or set the `GEMINI_API_KEY` environment variable.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from structflo.ner import NERExtractor
|
|
86
|
+
|
|
87
|
+
extractor = NERExtractor(api_key="YOUR_GEMINI_KEY")
|
|
88
|
+
|
|
89
|
+
result = extractor.extract(
|
|
90
|
+
"Gefitinib (ZD1839) is a first-generation EGFR tyrosine kinase inhibitor "
|
|
91
|
+
"with IC50 = 0.033 µM, approved for non-small cell lung cancer (NSCLC). "
|
|
92
|
+
"Its SMILES is COc1cc2ncnc(Nc3ccc(F)c(Cl)c3)c2cc1OCCCN1CCOCC1."
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Local models via Ollama
|
|
97
|
+
|
|
98
|
+
Run extraction entirely on your own hardware — no API key needed:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
extractor = NERExtractor(
|
|
102
|
+
model_id="qwen2.5:72b",
|
|
103
|
+
model_url="http://localhost:11434",
|
|
104
|
+
)
|
|
105
|
+
text = ("Gefitinib (ZD1839) is a first-generation EGFR inhibitor with IC50 = 0.033 µM approved for NSCLC."
|
|
106
|
+
"Its SMILES is COc1cc2ncnc(Nc3ccc(F)c(Cl)c3)c2cc1OCCCN1CCOCC1.")
|
|
107
|
+
result = extractor.extract(text)
|
|
108
|
+
result
|
|
109
|
+
```
|
|
110
|
+
Any model served by [Ollama](https://ollama.com/) works gemma, llama, mistral, qwen, deepseek, etc.
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
Render results as color-coded, interactive HTML directly in Jupyter notebooks:
|
|
114
|
+

|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
To get a PANDAS dataframe.
|
|
118
|
+
```python
|
|
119
|
+
result.to_dataframe()
|
|
120
|
+
```
|
|
121
|
+

|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
### TB specific extractor pass in the profile=TB
|
|
125
|
+
```python
|
|
126
|
+
from structflo.ner import NERExtractor, TB
|
|
127
|
+
|
|
128
|
+
extractor = NERExtractor(
|
|
129
|
+
model_id="qwen2.5:72b",
|
|
130
|
+
model_url="http://localhost:11434",
|
|
131
|
+
profile=TB,
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
```python
|
|
135
|
+
text = (
|
|
136
|
+
"Bedaquiline (TMC207) is a diarylquinoline that inhibits the "
|
|
137
|
+
"mycobacterial ATP synthase subunit c encoded by atpE (Rv1305). "
|
|
138
|
+
"It shows potent activity against Mycobacterium tuberculosis "
|
|
139
|
+
"including MDR-TB and XDR-TB. This compound was identified through "
|
|
140
|
+
"whole-cell screening and targets the energy metabolism pathway."
|
|
141
|
+
)
|
|
142
|
+
result = extractor.extract(text)
|
|
143
|
+
result
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+

|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
# Flat list of all entities
|
|
150
|
+
for entity in result.all_entities():
|
|
151
|
+
print(f"{entity.entity_type:20s} | {entity.text}")
|
|
152
|
+
```
|
|
153
|
+
```
|
|
154
|
+
compound_name | Bedaquiline
|
|
155
|
+
compound_name | TMC207
|
|
156
|
+
target | ATP synthase subunit c
|
|
157
|
+
disease | MDR-TB
|
|
158
|
+
disease | XDR-TB
|
|
159
|
+
accession_number | Rv1305
|
|
160
|
+
functional_category | energy metabolism pathway
|
|
161
|
+
screening_method | whole-cell screening
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Batch extraction
|
|
165
|
+
|
|
166
|
+
Pass a list of texts to extract from multiple documents at once:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
texts = [
|
|
170
|
+
"Imatinib inhibits BCR-ABL with IC50 = 0.6 µM in CML.",
|
|
171
|
+
"Trastuzumab targets HER2 in breast cancer patients.",
|
|
172
|
+
"Remdesivir (GS-5734) is an antiviral with EC50 = 0.77 µM against SARS-CoV-2.",
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
results = extractor.extract(texts)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
--- Text 1 ---
|
|
180
|
+
compound_name | Imatinib
|
|
181
|
+
target | BCR-ABL
|
|
182
|
+
disease | CML
|
|
183
|
+
bioactivity | IC50 = 0.6 µM
|
|
184
|
+
|
|
185
|
+
--- Text 2 ---
|
|
186
|
+
compound_name | Trastuzumab
|
|
187
|
+
target | HER2
|
|
188
|
+
disease | breast cancer
|
|
189
|
+
|
|
190
|
+
--- Text 3 ---
|
|
191
|
+
compound_name | Remdesivir
|
|
192
|
+
compound_name | GS-5734
|
|
193
|
+
disease | SARS-CoV-2
|
|
194
|
+
bioactivity | EC50 = 0.77 µM
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Fast Dictionary-Based NER (Mode 2)
|
|
198
|
+
|
|
199
|
+
`FastNERExtractor` uses curated YAML gazetteers with a three-phase matching strategy for deterministic, high-speed extraction when LLMs are not available.
|
|
200
|
+
These run extremely fast, however they are fuzzy based matches to predefined patterns and so it does not understands context.
|
|
201
|
+
```python
|
|
202
|
+
from structflo.ner.fast import FastNERExtractor
|
|
203
|
+
|
|
204
|
+
fast = FastNERExtractor()
|
|
205
|
+
|
|
206
|
+
text = (
|
|
207
|
+
"Bedaquiline (TMC207) is a diarylquinoline that inhibits the "
|
|
208
|
+
"mycobacterial ATP synthase subunit c encoded by atpE (Rv1305). "
|
|
209
|
+
"It shows potent activity against Mycobacterium tuberculosis "
|
|
210
|
+
"including MDR-TB and XDR-TB. This compound was identified through "
|
|
211
|
+
"whole-cell screening and targets the energy metabolism pathway."
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
result = fast.extract(text)
|
|
215
|
+
result
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+

|
|
219
|
+
|
|
220
|
+
### How matching works
|
|
221
|
+
|
|
222
|
+
| Phase | Method | What it catches |
|
|
223
|
+
| ----- | ------------------ | --------------------------------------------------------------------------------------------------------- |
|
|
224
|
+
| 1 | **Exact match** | Case-sensitive and normalized dictionary lookups with word-boundary enforcement |
|
|
225
|
+
| 1b | **Regex patterns** | Auto-derived patterns from accession number seeds (Rv tags, UniProt, PDB, etc.) |
|
|
226
|
+
| 2 | **Fuzzy match** | Typos and minor variants via [rapidfuzz](https://github.com/rapidfuzz/rapidfuzz) (configurable threshold) |
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
# Fuzzy matching catches typos
|
|
230
|
+
result = fast.extract("Bedaquilne showed activity against TB")
|
|
231
|
+
# "Bedaquilne" -> canonical: "Bedaquiline" (method: fuzzy)
|
|
232
|
+
|
|
233
|
+
# Disable fuzzy matching for strict mode
|
|
234
|
+
strict = FastNERExtractor(fuzzy_threshold=0)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Built-in gazetteers
|
|
238
|
+
|
|
239
|
+
The fast extractor ships with curated gazetteers for TB drug discovery:
|
|
240
|
+
|
|
241
|
+
| Gazetteer | Examples |
|
|
242
|
+
| --------------------- | ---------------------------------------------- |
|
|
243
|
+
| `accession_number` | Rv1305, B586_RS00005 |
|
|
244
|
+
| `gene_name` | atpE, InhA, DprE1 |
|
|
245
|
+
| `screening_method` | whole-cell screening, fragment-based screening |
|
|
246
|
+
| `target` | InhA, DprE1, MmpL3 |
|
|
247
|
+
| `compound_name` | Bedaquiline, Delamanid, Pretomanid |
|
|
248
|
+
| `functional_category` | DNA replication, cell wall biosynthesis |
|
|
249
|
+
| `strain` | M. tuberculosis H37Rv |
|
|
250
|
+
| `product` | enoyl-ACP reductase, ATP synthase subunit c |
|
|
251
|
+
| `disease` | TB, MDR-TB, XDR-TB |
|
|
252
|
+
|
|
253
|
+
### Custom gazetteers
|
|
254
|
+
|
|
255
|
+
Extend the built-in dictionaries with your own terms:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
custom = FastNERExtractor(
|
|
259
|
+
extra_gazetteers={
|
|
260
|
+
"target": ["MyNovelTarget", "KinaseX"],
|
|
261
|
+
"compound_name": ["CompoundABC"],
|
|
262
|
+
}
|
|
263
|
+
)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Or drop a new YAML file into the gazetteers directory — the filename (without `.yml`) maps to an entity type.
|
|
267
|
+
|
|
268
|
+
### Performance
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
Single abstract: ~393 ms
|
|
272
|
+
8 abstracts: ~862 ms
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Profiles
|
|
276
|
+
|
|
277
|
+
Profiles control which entity types are extracted. Use them to focus the model on specific categories.
|
|
278
|
+
|
|
279
|
+
### Built-in profiles
|
|
280
|
+
|
|
281
|
+
| Profile | Entity classes |
|
|
282
|
+
| ---------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
283
|
+
| `FULL` (default) | compounds, targets, diseases, bioactivities, assays, mechanisms |
|
|
284
|
+
| `CHEMISTRY` | compound names, SMILES, CAS numbers, molecular formulas |
|
|
285
|
+
| `BIOLOGY` | targets, gene names, protein names |
|
|
286
|
+
| `BIOACTIVITY` | bioactivity measurements, assays |
|
|
287
|
+
| `DISEASE` | diseases and clinical indications |
|
|
288
|
+
| `TB` | TB drug discovery (compounds, targets, diseases, accessions, strains, screening methods, functional categories) |
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
from structflo.ner import NERExtractor, CHEMISTRY
|
|
292
|
+
|
|
293
|
+
extractor = NERExtractor(api_key="YOUR_GEMINI_KEY")
|
|
294
|
+
result = extractor.extract(text, profile=CHEMISTRY)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Merging profiles
|
|
298
|
+
|
|
299
|
+
Combine multiple profiles for broader extraction:
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from structflo.ner import CHEMISTRY, BIOLOGY
|
|
303
|
+
|
|
304
|
+
combined = CHEMISTRY.merge(BIOLOGY)
|
|
305
|
+
result = extractor.extract(text, profile=combined)
|
|
306
|
+
# Profile: chemistry+biology
|
|
307
|
+
# Entity classes: compound_name, smiles, cas_number, molecular_formula, target, gene_name, protein_name
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Custom profiles
|
|
311
|
+
|
|
312
|
+
Define your own extraction schema:
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
from structflo.ner import NERExtractor, EntityProfile
|
|
316
|
+
|
|
317
|
+
my_profile = EntityProfile(
|
|
318
|
+
name="kinase_inhibitors",
|
|
319
|
+
entity_classes=["compound_name", "smiles", "target", "bioactivity"],
|
|
320
|
+
prompt="Extract kinase inhibitor names, SMILES, targets, and potency values.",
|
|
321
|
+
examples=my_examples,
|
|
322
|
+
)
|
|
323
|
+
result = extractor.extract(text, profile=my_profile)
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Working with Results
|
|
327
|
+
|
|
328
|
+
Both extractors return identical `NERResult` objects:
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
# Typed entity lists
|
|
332
|
+
result.compounds # [ChemicalEntity(...)]
|
|
333
|
+
result.targets # [TargetEntity(...)]
|
|
334
|
+
result.diseases # [DiseaseEntity(...)]
|
|
335
|
+
result.bioactivities # [BioactivityEntity(...)]
|
|
336
|
+
result.assays # [...]
|
|
337
|
+
result.mechanisms # [...]
|
|
338
|
+
result.accessions # [AccessionEntity(...)]
|
|
339
|
+
|
|
340
|
+
# Flat list of all entities
|
|
341
|
+
result.all_entities()
|
|
342
|
+
|
|
343
|
+
# Export to pandas DataFrame
|
|
344
|
+
df = result.to_dataframe()
|
|
345
|
+
|
|
346
|
+
# Serialize to dict (JSON-friendly)
|
|
347
|
+
result.to_dict()
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
## Notebooks
|
|
352
|
+
|
|
353
|
+
Explore worked examples in the [`notebooks/`](notebooks/) directory:
|
|
354
|
+
|
|
355
|
+
| Notebook | Description |
|
|
356
|
+
| ---------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
357
|
+
| [01_quickstart.ipynb](notebooks/01_quickstart.ipynb) | End-to-end extraction with cloud and local models, profiles, batch extraction |
|
|
358
|
+
| [02_fast_ner.ipynb](notebooks/02_fast_ner.ipynb) | Fast dictionary-based NER — matching strategies, custom gazetteers, performance |
|
|
359
|
+
|
|
360
|
+
## Contributing
|
|
361
|
+
|
|
362
|
+
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
# clone and install dev dependencies
|
|
366
|
+
git clone https://github.com/structflo/structflo-ner.git
|
|
367
|
+
cd structflo-ner
|
|
368
|
+
pip install -e ".[dataframe]" --group dev
|
|
369
|
+
|
|
370
|
+
# run tests
|
|
371
|
+
pytest
|
|
372
|
+
|
|
373
|
+
# lint
|
|
374
|
+
ruff check .
|
|
375
|
+
ruff format .
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Citation
|
|
379
|
+
|
|
380
|
+
If you use structflo.ner in your research, please cite:
|
|
381
|
+
|
|
382
|
+
<details>
|
|
383
|
+
<summary>BibTeX</summary>
|
|
384
|
+
|
|
385
|
+
```bibtex
|
|
386
|
+
@software{structflo_ner,
|
|
387
|
+
title = {structflo.ner: Zero-config NER for Drug Discovery},
|
|
388
|
+
url = {https://github.com/structflo/structflo-ner},
|
|
389
|
+
year = {2026}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
</details>
|
|
394
|
+
|
|
395
|
+
## License
|
|
396
|
+
|
|
397
|
+
This project is licensed under the [Apache License 2.0](LICENSE).
|