ppuzio commited on
Commit
15eb2b4
·
1 Parent(s): 7ce967b

Release 1.0.2: conservative labelled country-area phone fix

Browse files
Files changed (6) hide show
  1. CHANGELOG.md +14 -0
  2. README.md +14 -11
  3. hybrid.json +9 -9
  4. nergal.py +2 -2
  5. scrub_pii.py +19 -6
  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.1
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.1` (`hybrid.json`, `CHANGELOG.md`)
27
- - **Ground:** `scrub_pii` regex (SHA256 `4dcc441c…`)
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
- | **1.0.1** | **323** | **25** | **98** | **123** | **97.93%** | **95.95%** | Prefix-only glued-email trim |
 
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 | 245 | 73 | 98 | 98.04% | 81.01% |
75
  | GLiNER 2.5-multi zero-shot | naked | 81 | 188 | 970 | 56.98% | 21.22% |
76
- | GLiNER 2.5-multi zero-shot | ∪ regex | 262 | 63 | 1,103 | 82.36% | 85.02% |
77
  | Historical GLiNER email12 | naked | 249 | 70 | 10 | 99.81% | 85.39% |
78
- | Historical GLiNER email12 | ∪ regex | 289 | 51 | 143 | 97.54% | 93.48% |
79
  | XLM-R epoch 5 | naked | 298 | 42 | 57 | 98.96% | 89.38% |
80
- | **NERGAL 1.0.1** | **∪ regex** | **323** | **25** | **123** | **97.93%** | **95.95%** |
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: 144/169 phone, 179/185 other PII. Exact-span precision 87.50%, recall 88.98%, F1 88.24%.
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 (this snapshot) | 323 | 123 | 0 |
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.1`, 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`.
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.1",
5
  "mode": "rules_union",
6
  "epoch": 5,
7
  "seed": 202609160,
8
  "threshold": 0.95,
9
- "rules_sha256": "4dcc441c39b653b1e8360928c9e36f6299c1f17e81e7566dd2d252cc5dbbb03c",
10
  "eval": {
11
  "split": "841-dev",
12
  "gold_entities": 354,
13
- "whole_entities": 323,
14
- "residual_passages": 25,
15
  "union_fp": 123,
16
  "rules_fp": 98,
17
  "character_precision": 0.9793,
18
- "character_recall": 0.9595,
19
- "exact_precision": 0.875,
20
- "exact_recall": 0.8898,
21
- "exact_f1": 0.8824,
22
- "phone_whole": 144,
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.1'
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 = '4dcc441c39b653b1e8360928c9e36f6299c1f17e81e7566dd2d252cc5dbbb03c'
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. Short service numbers
 
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{1,2})-(\d{1,2})\)', r'+\1 \2', s)
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
- short = bool(_SHORT_PHONE_LABEL_RE.search(text[max(0, m.start()-100):m.start()]))
 
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 = '4dcc441c39b653b1e8360928c9e36f6299c1f17e81e7566dd2d252cc5dbbb03c'
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.1')
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)