Instructions to use SlayerLab/NERGAL with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use SlayerLab/NERGAL with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("token-classification", model="SlayerLab/NERGAL")# Load model directly from transformers import AutoTokenizer, AutoModelForTokenClassification tokenizer = AutoTokenizer.from_pretrained("SlayerLab/NERGAL") model = AutoModelForTokenClassification.from_pretrained("SlayerLab/NERGAL", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Release 1.0.2: conservative labelled country-area phone fix
Browse files- CHANGELOG.md +14 -0
- README.md +14 -11
- hybrid.json +9 -9
- nergal.py +2 -2
- scrub_pii.py +19 -6
- test_nergal.py +2 -2
CHANGELOG.md
CHANGED
|
@@ -8,6 +8,20 @@ Semver for this island:
|
|
| 8 |
|
| 9 |
Accuracy is 841-dev, union at 0.95, 354 gold spans. A version that changes those numbers must update `hybrid.json` `eval` and the tables below.
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
## 1.0.1
|
| 12 |
|
| 13 |
Prefix-only glued-email trim. Cluster-gated title-case 5–11 letter prefixes are dropped from the redaction when the remainder is already a lowercase-local email. Same epoch-5 weights. Lost gold 0. New clean-passage damage 0.
|
|
|
|
| 8 |
|
| 9 |
Accuracy is 841-dev, union at 0.95, 354 gold spans. A version that changes those numbers must update `hybrid.json` `eval` and the tables below.
|
| 10 |
|
| 11 |
+
## 1.0.2
|
| 12 |
+
|
| 13 |
+
Fix immediately labelled hyphenated country-area phones with one-digit country codes or wider area codes. Numeric continuations, slash lists, weak contact cues, and nearby title prose still abstain. Same weights, threshold, and API.
|
| 14 |
+
|
| 15 |
+
841-dev: rules recover 14 more whole spans; the union recovers one (323 → 324), reducing residual passages 25 → 24. Rules FP remain 98; union FP remain 123. Lost gold, new false characters, and new clean-passage damage are all zero. Rules SHA `3016ae5b…`.
|
| 16 |
+
|
| 17 |
+
Exact-span scores are recomputed from deduplicated raw rule/model span triples: precision 86.34%, recall 89.27%, F1 87.78%. The 1.0.1 card's precision/F1 were stale after the prefix trim changed exact rule/model duplicate counts; its character metrics were correct.
|
| 18 |
+
|
| 19 |
+
| Version | Whole /354 | Residual | Rules FP | Union FP | Char P | Char R | What changed |
|
| 20 |
+
|---|---:|---:|---:|---:|---:|---:|---|
|
| 21 |
+
| 1.0.0 | 323 | 25 | 133 | 133 | 97.76% | 95.95% | First Hub snapshot |
|
| 22 |
+
| 1.0.1 | 323 | 25 | 98 | 123 | 97.93% | 95.95% | Prefix-only glued-email trim |
|
| 23 |
+
| 1.0.2 | 324 | 24 | 98 | 123 | 97.93% | 96.12% | Labelled country-area phone fix |
|
| 24 |
+
|
| 25 |
## 1.0.1
|
| 26 |
|
| 27 |
Prefix-only glued-email trim. Cluster-gated title-case 5–11 letter prefixes are dropped from the redaction when the remainder is already a lowercase-local email. Same epoch-5 weights. Lost gold 0. New clean-passage damage 0.
|
README.md
CHANGED
|
@@ -13,7 +13,7 @@ tags:
|
|
| 13 |
- hybrid
|
| 14 |
---
|
| 15 |
|
| 16 |
-
# NERGAL 1.0.
|
| 17 |
|
| 18 |
**Named Entity Recognition with Grounded Additive Labels**
|
| 19 |
|
|
@@ -23,8 +23,8 @@ SlayerLab hybrid PII cleaner for Polish. Not a chat model. Not a drop-in `pipeli
|
|
| 23 |
|
| 24 |
Python rules do the identifiers they can prove. A transformer NER head adds phone and other PII spans the regex misses. The cleaner **unions** the two on the original text, then replaces hits with `[Telefon]` or `[PII]`.
|
| 25 |
|
| 26 |
-
- **Version:** `1.0.
|
| 27 |
-
- **Ground:** `scrub_pii` regex (SHA256 `
|
| 28 |
- **Additive labels:** XLM-RoBERTa-large token classifier, BIO tags `phone` / `pii`, threshold 0.95
|
| 29 |
- **This snapshot:** seed `202609160`, **epoch 5** of a seven-epoch schedule
|
| 30 |
|
|
@@ -35,7 +35,8 @@ Python rules do the identifiers they can prove. A transformer NER head adds phon
|
|
| 35 |
| Version | Whole /354 | Residual | Rules FP | Union FP | Char P | Char R | What changed |
|
| 36 |
|---|---:|---:|---:|---:|---:|---:|---|
|
| 37 |
| 1.0.0 | 323 | 25 | 133 | 133 | 97.76% | 95.95% | First Hub snapshot |
|
| 38 |
-
|
|
|
|
|
| 39 |
|
| 40 |
## 841-dev
|
| 41 |
|
|
@@ -71,23 +72,25 @@ Same 841-dev split, threshold **0.95**. **Naked** is the transformer alone. **
|
|
| 71 |
|
| 72 |
| System | Mode | Whole /354 | Residual | False chars | Char P | Char R |
|
| 73 |
|---|---|---:|---:|---:|---:|---:|
|
| 74 |
-
| Regex (`scrub_pii`) | rules |
|
| 75 |
| GLiNER 2.5-multi zero-shot | naked | 81 | 188 | 970 | 56.98% | 21.22% |
|
| 76 |
-
| GLiNER 2.5-multi zero-shot | ∪ regex |
|
| 77 |
| Historical GLiNER email12 | naked | 249 | 70 | 10 | 99.81% | 85.39% |
|
| 78 |
-
| Historical GLiNER email12 | ∪ regex |
|
| 79 |
| XLM-R epoch 5 | naked | 298 | 42 | 57 | 98.96% | 89.38% |
|
| 80 |
-
| **NERGAL 1.0.
|
| 81 |
|
| 82 |
-
Zero-shot [GLiNER 2.5](https://huggingface.co/fastino/gliner2.5-multi-v1) is not competitive here, especially on non-phone PII (7/185 whole vs 160 naked / 179 union). Fine-tuned historical GLiNER is the precise naked baseline (10 false characters) and still trails XLM-R on coverage. NERGAL is XLM-R epoch 5 plus the regex:
|
| 83 |
|
| 84 |
Trained GLiNER, HerBERT-large, and XLM-R-large were also compared on this split (plot above). GLiNER’s 0.50 coverage lead is the precision collapse in that figure; no GLiNER or HerBERT operating point passed the content-preservation gate.
|
| 85 |
|
| 86 |
## Extra seeds
|
| 87 |
|
|
|
|
|
|
|
| 88 |
| Seed | Whole /354 | False chars | New false vs historical union |
|
| 89 |
|---|---:|---:|---:|
|
| 90 |
-
| 202609160 (
|
| 91 |
| 202609161 | 322 | 134 | 1 |
|
| 92 |
| 202609162 | 316 | 151 | 18 |
|
| 93 |
|
|
@@ -110,6 +113,6 @@ nergal = Nergal.from_pretrained(root, local_files_only=True)
|
|
| 110 |
masked, counts = nergal.scrub(text)
|
| 111 |
```
|
| 112 |
|
| 113 |
-
`hybrid.json` records version `1.0.
|
| 114 |
|
| 115 |
Base weights: [`FacebookAI/xlm-roberta-large`](https://huggingface.co/FacebookAI/xlm-roberta-large) revision `c23d21b0620b635a76227c604d44e43a9f0ee389` (MIT).
|
|
|
|
| 13 |
- hybrid
|
| 14 |
---
|
| 15 |
|
| 16 |
+
# NERGAL 1.0.2
|
| 17 |
|
| 18 |
**Named Entity Recognition with Grounded Additive Labels**
|
| 19 |
|
|
|
|
| 23 |
|
| 24 |
Python rules do the identifiers they can prove. A transformer NER head adds phone and other PII spans the regex misses. The cleaner **unions** the two on the original text, then replaces hits with `[Telefon]` or `[PII]`.
|
| 25 |
|
| 26 |
+
- **Version:** `1.0.2` (`hybrid.json`, `CHANGELOG.md`)
|
| 27 |
+
- **Ground:** `scrub_pii` regex (SHA256 `3016ae5b…`)
|
| 28 |
- **Additive labels:** XLM-RoBERTa-large token classifier, BIO tags `phone` / `pii`, threshold 0.95
|
| 29 |
- **This snapshot:** seed `202609160`, **epoch 5** of a seven-epoch schedule
|
| 30 |
|
|
|
|
| 35 |
| Version | Whole /354 | Residual | Rules FP | Union FP | Char P | Char R | What changed |
|
| 36 |
|---|---:|---:|---:|---:|---:|---:|---|
|
| 37 |
| 1.0.0 | 323 | 25 | 133 | 133 | 97.76% | 95.95% | First Hub snapshot |
|
| 38 |
+
| 1.0.1 | 323 | 25 | 98 | 123 | 97.93% | 95.95% | Prefix-only glued-email trim |
|
| 39 |
+
| **1.0.2** | **324** | **24** | **98** | **123** | **97.93%** | **96.12%** | Labelled country-area phone fix |
|
| 40 |
|
| 41 |
## 841-dev
|
| 42 |
|
|
|
|
| 72 |
|
| 73 |
| System | Mode | Whole /354 | Residual | False chars | Char P | Char R |
|
| 74 |
|---|---|---:|---:|---:|---:|---:|
|
| 75 |
+
| Regex (`scrub_pii`) | rules | 259 | 70 | 98 | 98.13% | 85.04% |
|
| 76 |
| GLiNER 2.5-multi zero-shot | naked | 81 | 188 | 970 | 56.98% | 21.22% |
|
| 77 |
+
| GLiNER 2.5-multi zero-shot | ∪ regex | 273 | 61 | 1,068 | 83.33% | 88.16% |
|
| 78 |
| Historical GLiNER email12 | naked | 249 | 70 | 10 | 99.81% | 85.39% |
|
| 79 |
+
| Historical GLiNER email12 | ∪ regex | 290 | 50 | 108 | 98.13% | 93.76% |
|
| 80 |
| XLM-R epoch 5 | naked | 298 | 42 | 57 | 98.96% | 89.38% |
|
| 81 |
+
| **NERGAL 1.0.2** | **∪ regex** | 324 | 24 | 123 | 97.93% | 96.12% |
|
| 82 |
|
| 83 |
+
Zero-shot [GLiNER 2.5](https://huggingface.co/fastino/gliner2.5-multi-v1) is not competitive here, especially on non-phone PII (7/185 whole vs 160 naked / 179 union). Fine-tuned historical GLiNER is the precise naked baseline (10 false characters) and still trails XLM-R on coverage. NERGAL is XLM-R epoch 5 plus the regex: 145/169 phone, 179/185 other PII. Exact-span precision 86.34%, recall 89.27%, F1 87.78%.
|
| 84 |
|
| 85 |
Trained GLiNER, HerBERT-large, and XLM-R-large were also compared on this split (plot above). GLiNER’s 0.50 coverage lead is the precision collapse in that figure; no GLiNER or HerBERT operating point passed the content-preservation gate.
|
| 86 |
|
| 87 |
## Extra seeds
|
| 88 |
|
| 89 |
+
Historical seed-comparison results, before the 1.0.2 parser fix.
|
| 90 |
+
|
| 91 |
| Seed | Whole /354 | False chars | New false vs historical union |
|
| 92 |
|---|---:|---:|---:|
|
| 93 |
+
| 202609160 (selected weights) | 323 | 123 | 0 |
|
| 94 |
| 202609161 | 322 | 134 | 1 |
|
| 95 |
| 202609162 | 316 | 151 | 18 |
|
| 96 |
|
|
|
|
| 113 |
masked, counts = nergal.scrub(text)
|
| 114 |
```
|
| 115 |
|
| 116 |
+
`hybrid.json` records version `1.0.2`, threshold 0.95, gap ids `250002` / `250003`, and the 841-dev `eval` block. `test_nergal.py` is synthetic (no corpus text). From this snapshot: `python -m unittest test_nergal`.
|
| 117 |
|
| 118 |
Base weights: [`FacebookAI/xlm-roberta-large`](https://huggingface.co/FacebookAI/xlm-roberta-large) revision `c23d21b0620b635a76227c604d44e43a9f0ee389` (MIT).
|
hybrid.json
CHANGED
|
@@ -1,25 +1,25 @@
|
|
| 1 |
{
|
| 2 |
"full_name": "Named Entity Recognition with Grounded Additive Labels",
|
| 3 |
"hub_id": "SlayerLab/NERGAL",
|
| 4 |
-
"version": "1.0.
|
| 5 |
"mode": "rules_union",
|
| 6 |
"epoch": 5,
|
| 7 |
"seed": 202609160,
|
| 8 |
"threshold": 0.95,
|
| 9 |
-
"rules_sha256": "
|
| 10 |
"eval": {
|
| 11 |
"split": "841-dev",
|
| 12 |
"gold_entities": 354,
|
| 13 |
-
"whole_entities":
|
| 14 |
-
"residual_passages":
|
| 15 |
"union_fp": 123,
|
| 16 |
"rules_fp": 98,
|
| 17 |
"character_precision": 0.9793,
|
| 18 |
-
"character_recall": 0.
|
| 19 |
-
"exact_precision": 0.
|
| 20 |
-
"exact_recall": 0.
|
| 21 |
-
"exact_f1": 0.
|
| 22 |
-
"phone_whole":
|
| 23 |
"phone_gold": 169,
|
| 24 |
"pii_whole": 179,
|
| 25 |
"pii_gold": 185
|
|
|
|
| 1 |
{
|
| 2 |
"full_name": "Named Entity Recognition with Grounded Additive Labels",
|
| 3 |
"hub_id": "SlayerLab/NERGAL",
|
| 4 |
+
"version": "1.0.2",
|
| 5 |
"mode": "rules_union",
|
| 6 |
"epoch": 5,
|
| 7 |
"seed": 202609160,
|
| 8 |
"threshold": 0.95,
|
| 9 |
+
"rules_sha256": "3016ae5bd403ff997458f9dd74bad8c6ed1388eb83dadc1b31cdb182f9ed607f",
|
| 10 |
"eval": {
|
| 11 |
"split": "841-dev",
|
| 12 |
"gold_entities": 354,
|
| 13 |
+
"whole_entities": 324,
|
| 14 |
+
"residual_passages": 24,
|
| 15 |
"union_fp": 123,
|
| 16 |
"rules_fp": 98,
|
| 17 |
"character_precision": 0.9793,
|
| 18 |
+
"character_recall": 0.9612,
|
| 19 |
+
"exact_precision": 0.8634,
|
| 20 |
+
"exact_recall": 0.8927,
|
| 21 |
+
"exact_f1": 0.8778,
|
| 22 |
+
"phone_whole": 145,
|
| 23 |
"phone_gold": 169,
|
| 24 |
"pii_whole": 179,
|
| 25 |
"pii_gold": 185
|
nergal.py
CHANGED
|
@@ -17,13 +17,13 @@ import scrub_pii
|
|
| 17 |
from scrub_pii import PHONE_TAG, PII_TAG
|
| 18 |
|
| 19 |
HUB_ID = 'SlayerLab/NERGAL'
|
| 20 |
-
VERSION = '1.0.
|
| 21 |
GAPS = ['[PII_SPACE]', '[PII_BREAK]']
|
| 22 |
GAP_IDS = [250002, 250003]
|
| 23 |
BIO_LABELS = ['O', 'B-phone', 'I-phone', 'B-pii', 'I-pii']
|
| 24 |
LABELS = ['phone', 'pii']
|
| 25 |
THRESHOLD = 0.95
|
| 26 |
-
RULES_SHA = '
|
| 27 |
|
| 28 |
|
| 29 |
def sha(path):
|
|
|
|
| 17 |
from scrub_pii import PHONE_TAG, PII_TAG
|
| 18 |
|
| 19 |
HUB_ID = 'SlayerLab/NERGAL'
|
| 20 |
+
VERSION = '1.0.2'
|
| 21 |
GAPS = ['[PII_SPACE]', '[PII_BREAK]']
|
| 22 |
GAP_IDS = [250002, 250003]
|
| 23 |
BIO_LABELS = ['O', 'B-phone', 'I-phone', 'B-pii', 'I-pii']
|
| 24 |
LABELS = ['phone', 'pii']
|
| 25 |
THRESHOLD = 0.95
|
| 26 |
+
RULES_SHA = '3016ae5bd403ff997458f9dd74bad8c6ed1388eb83dadc1b31cdb182f9ed607f'
|
| 27 |
|
| 28 |
|
| 29 |
def sha(path):
|
scrub_pii.py
CHANGED
|
@@ -21,7 +21,8 @@ unrelated lines end the list. Bounded staff/address-directory evidence also cove
|
|
| 21 |
formatted phone fields. Labelled full-number ranges retain shared prefixes.
|
| 22 |
Phones require a nearby contact cue, a Polish +48/0048 prefix, or explicit
|
| 23 |
international country/trunk notation such as +CC (0). With a cue, the EUR-Lex
|
| 24 |
-
"(32-2) 299 11 11" country-area form counts as international.
|
|
|
|
| 25 |
need strong labels; 116xxx numbers also accept nearby telephone prose. Unlabelled
|
| 26 |
domestic numbers are left for audit because table cells have the same shapes.
|
| 27 |
Flattened tables can glue labels to both neighbours ("Mödlingtel.: … 38112faks:");
|
|
@@ -139,7 +140,7 @@ _GLUED_PHONE_LABEL = r"(?:tel\.|telefon|t[ée]l[ée]phone|t[ée]l[ée]copieur|(?
|
|
| 139 |
_PHONE_END = r"(?:(?!\w)|(?=" + _GLUED_PHONE_LABEL + r"|(?:e-?mail|t[ée]lex)[ \t]*:))"
|
| 140 |
_PHONE_RE = re.compile(
|
| 141 |
_VANITY_PHONE_RE.pattern + r"|(?:(?<!\w)|(?<=telefon)|(?<=tel)|(?<=faks)|(?<=fax))(?:\(?\+[ \t]*)?"
|
| 142 |
-
r"(?:\((?:0|0?\d{2,4}(?:-\d{1,2})?)\)" + _SEP + r")?"
|
| 143 |
r"\d(?:" + _SEP + r"\d){4,}" + _PHONE_END, re.I,
|
| 144 |
)
|
| 145 |
_SHORT_PHONE_LABEL_RE = re.compile(
|
|
@@ -179,6 +180,7 @@ _PHONE_HEADING_RE = re.compile(
|
|
| 179 |
r"(?:tj\.|nr\.|[^\d\n.!?;\[\]]){0,75}:?[ \t]*(?:\n[ \t]*){1,4}\Z", re.I)
|
| 180 |
_CONTACT_EXCLUDE_RE = re.compile(r"\b(?:spraw\w*|kod\w*|kwot\w*|statystyk\w*|taryf\w*)\b", re.I)
|
| 181 |
_PHONE_LINE_RE = re.compile(r"(?<!\w)\d{2,3}(?:[ \t-]\d{2,4}){2,3}(?!\w)")
|
|
|
|
| 182 |
_STAFF_ROLE_RE = re.compile(
|
| 183 |
r"\b(?:inspektor|referent|koordynator|psycholog|księgowość|księgowy|księgowa|"
|
| 184 |
r"pracownicy[ \t]+socjalni|łowczy|podłowczy)\b", re.I)
|
|
@@ -484,7 +486,7 @@ def _phone_ok(raw: str, short: bool = False) -> bool:
|
|
| 484 |
if short and re.match(r'^\(0?\d{2,4}(?:-\d{1,2})?\)', s):
|
| 485 |
return 8 <= len(d) <= 15
|
| 486 |
# EUR-Lex puts country and area code in parentheses: "(32-2) 299 11 11" is +32 2 299 11 11.
|
| 487 |
-
s = re.sub(r'^\(([1-9]\d{
|
| 488 |
if s.startswith(('+', '00')):
|
| 489 |
international = _digits(s.replace('(0)', ''))
|
| 490 |
if s.startswith('00'):
|
|
@@ -595,10 +597,21 @@ def _replace_phones(text: str, *, mask=_tag) -> tuple[str, int]:
|
|
| 595 |
out.append(text[pos:line_end+1])
|
| 596 |
pos = line_end+1
|
| 597 |
continue
|
| 598 |
-
|
|
|
|
| 599 |
short = short or (re.fullmatch(r'116[ \t]?\d{3}', raw)
|
| 600 |
and _phone_context(text, m.start(), raw))
|
| 601 |
short = short or _phone_list_context(text, m.start())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 602 |
# A bare five-digit continuation followed by a place/name can be a
|
| 603 |
# postal address. It needs its own phone label to override that ambiguity.
|
| 604 |
postal = re.fullmatch(r'\d{5}', raw) and re.match(r'[ \t]+[^\W\d_]', text[end:])
|
|
@@ -617,8 +630,8 @@ def _replace_phones(text: str, *, mask=_tag) -> tuple[str, int]:
|
|
| 617 |
text[last_phone_end:m.start()], re.I)
|
| 618 |
and _pl_national_ok(_digits(raw))):
|
| 619 |
short = True
|
| 620 |
-
if (short or _phone_context(text, m.start(), raw)
|
| 621 |
-
or _directory_context(text, m.start(), line_end if line_end >= 0 else end)):
|
| 622 |
if (_is_amount(text, m.start(), end)
|
| 623 |
and re.fullmatch(r'\d{1,3}(?:\.[ \t]*\d{3})+', raw)):
|
| 624 |
out.extend((text[pos:m.start()], raw))
|
|
|
|
| 21 |
formatted phone fields. Labelled full-number ranges retain shared prefixes.
|
| 22 |
Phones require a nearby contact cue, a Polish +48/0048 prefix, or explicit
|
| 23 |
international country/trunk notation such as +CC (0). With a cue, the EUR-Lex
|
| 24 |
+
"(32-2) 299 11 11" country-area form counts as international. Strong labels also
|
| 25 |
+
admit one-digit country codes and wider hyphenated area codes. Short service numbers
|
| 26 |
need strong labels; 116xxx numbers also accept nearby telephone prose. Unlabelled
|
| 27 |
domestic numbers are left for audit because table cells have the same shapes.
|
| 28 |
Flattened tables can glue labels to both neighbours ("Mödlingtel.: … 38112faks:");
|
|
|
|
| 140 |
_PHONE_END = r"(?:(?!\w)|(?=" + _GLUED_PHONE_LABEL + r"|(?:e-?mail|t[ée]lex)[ \t]*:))"
|
| 141 |
_PHONE_RE = re.compile(
|
| 142 |
_VANITY_PHONE_RE.pattern + r"|(?:(?<!\w)|(?<=telefon)|(?<=tel)|(?<=faks)|(?<=fax))(?:\(?\+[ \t]*)?"
|
| 143 |
+
r"(?:\((?:0|[1-9]\d{0,2}-\d{1,4}|0?\d{2,4}(?:-\d{1,2})?)\)" + _SEP + r")?"
|
| 144 |
r"\d(?:" + _SEP + r"\d){4,}" + _PHONE_END, re.I,
|
| 145 |
)
|
| 146 |
_SHORT_PHONE_LABEL_RE = re.compile(
|
|
|
|
| 180 |
r"(?:tj\.|nr\.|[^\d\n.!?;\[\]]){0,75}:?[ \t]*(?:\n[ \t]*){1,4}\Z", re.I)
|
| 181 |
_CONTACT_EXCLUDE_RE = re.compile(r"\b(?:spraw\w*|kod\w*|kwot\w*|statystyk\w*|taryf\w*)\b", re.I)
|
| 182 |
_PHONE_LINE_RE = re.compile(r"(?<!\w)\d{2,3}(?:[ \t-]\d{2,4}){2,3}(?!\w)")
|
| 183 |
+
_PHONE_TITLE_RE = re.compile(r"\b(?:film\w*|serial\w*|tytuł\w*|zatytułowan\w*)\b", re.I)
|
| 184 |
_STAFF_ROLE_RE = re.compile(
|
| 185 |
r"\b(?:inspektor|referent|koordynator|psycholog|księgowość|księgowy|księgowa|"
|
| 186 |
r"pracownicy[ \t]+socjalni|łowczy|podłowczy)\b", re.I)
|
|
|
|
| 486 |
if short and re.match(r'^\(0?\d{2,4}(?:-\d{1,2})?\)', s):
|
| 487 |
return 8 <= len(d) <= 15
|
| 488 |
# EUR-Lex puts country and area code in parentheses: "(32-2) 299 11 11" is +32 2 299 11 11.
|
| 489 |
+
s = re.sub(r'^\(([1-9]\d{0,2})-(\d{1,4})\)', r'+\1 \2', s)
|
| 490 |
if s.startswith(('+', '00')):
|
| 491 |
international = _digits(s.replace('(0)', ''))
|
| 492 |
if s.startswith('00'):
|
|
|
|
| 597 |
out.append(text[pos:line_end+1])
|
| 598 |
pos = line_end+1
|
| 599 |
continue
|
| 600 |
+
immediate_phone_label = bool(_SHORT_PHONE_LABEL_RE.search(text[max(0, m.start()-100):m.start()]))
|
| 601 |
+
short = immediate_phone_label
|
| 602 |
short = short or (re.fullmatch(r'116[ \t]?\d{3}', raw)
|
| 603 |
and _phone_context(text, m.start(), raw))
|
| 604 |
short = short or _phone_list_context(text, m.start())
|
| 605 |
+
novel_country_area = (bool(re.match(r'^\([1-9]\d{0,2}-\d{1,4}\)', raw))
|
| 606 |
+
and not re.match(r'^\(0?\d{2,4}(?:-\d{1,2})?\)', raw))
|
| 607 |
+
# Broadened country-area forms require an immediate strong label and
|
| 608 |
+
# reject numeric continuations, slash lists, and nearby title prose.
|
| 609 |
+
novel_country_area_ok = not novel_country_area or (
|
| 610 |
+
immediate_phone_label
|
| 611 |
+
and not re.search(r'\r?\n[ \t]*\d|/', raw)
|
| 612 |
+
and not re.match(r'[ \t]*(?:\r?\n[ \t]*\d|/)', text[end:])
|
| 613 |
+
and not _PHONE_TITLE_RE.search(text[max(0, m.start()-100):m.start()])
|
| 614 |
+
)
|
| 615 |
# A bare five-digit continuation followed by a place/name can be a
|
| 616 |
# postal address. It needs its own phone label to override that ambiguity.
|
| 617 |
postal = re.fullmatch(r'\d{5}', raw) and re.match(r'[ \t]+[^\W\d_]', text[end:])
|
|
|
|
| 630 |
text[last_phone_end:m.start()], re.I)
|
| 631 |
and _pl_national_ok(_digits(raw))):
|
| 632 |
short = True
|
| 633 |
+
if (novel_country_area_ok and (short or _phone_context(text, m.start(), raw)
|
| 634 |
+
or _directory_context(text, m.start(), line_end if line_end >= 0 else end))):
|
| 635 |
if (_is_amount(text, m.start(), end)
|
| 636 |
and re.fullmatch(r'\d{1,3}(?:\.[ \t]*\d{3})+', raw)):
|
| 637 |
out.extend((text[pos:m.start()], raw))
|
test_nergal.py
CHANGED
|
@@ -5,7 +5,7 @@ import unittest
|
|
| 5 |
from pathlib import Path
|
| 6 |
|
| 7 |
HERE = Path(__file__).resolve().parent
|
| 8 |
-
RULES_SHA = '
|
| 9 |
|
| 10 |
|
| 11 |
class NergalTests(unittest.TestCase):
|
|
@@ -13,7 +13,7 @@ class NergalTests(unittest.TestCase):
|
|
| 13 |
from nergal import GAP_IDS, GAPS, HUB_ID, RULES_SHA as PINNED, THRESHOLD, VERSION
|
| 14 |
card = json.loads((HERE / 'hybrid.json').read_text())
|
| 15 |
self.assertEqual(HUB_ID, 'SlayerLab/NERGAL')
|
| 16 |
-
self.assertEqual(VERSION, '1.0.
|
| 17 |
self.assertEqual(card['version'], VERSION)
|
| 18 |
self.assertEqual(card['eval']['union_fp'], 123)
|
| 19 |
self.assertEqual(card['eval']['rules_fp'], 98)
|
|
|
|
| 5 |
from pathlib import Path
|
| 6 |
|
| 7 |
HERE = Path(__file__).resolve().parent
|
| 8 |
+
RULES_SHA = '3016ae5bd403ff997458f9dd74bad8c6ed1388eb83dadc1b31cdb182f9ed607f'
|
| 9 |
|
| 10 |
|
| 11 |
class NergalTests(unittest.TestCase):
|
|
|
|
| 13 |
from nergal import GAP_IDS, GAPS, HUB_ID, RULES_SHA as PINNED, THRESHOLD, VERSION
|
| 14 |
card = json.loads((HERE / 'hybrid.json').read_text())
|
| 15 |
self.assertEqual(HUB_ID, 'SlayerLab/NERGAL')
|
| 16 |
+
self.assertEqual(VERSION, '1.0.2')
|
| 17 |
self.assertEqual(card['version'], VERSION)
|
| 18 |
self.assertEqual(card['eval']['union_fp'], 123)
|
| 19 |
self.assertEqual(card['eval']['rules_fp'], 98)
|