pyannotators-entityfishing 1.6.67__tar.gz → 1.6.71__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.
Files changed (24) hide show
  1. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/PKG-INFO +17 -7
  2. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/README.md +16 -6
  3. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/src/pyannotators_entityfishing/__init__.py +1 -1
  4. pyannotators_entityfishing-1.6.71/tools/el_jev.py +118 -0
  5. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/.claude/skills/ef-mapping/SKILL.md +0 -0
  6. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/.gitignore +0 -0
  7. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/.gitmodules +0 -0
  8. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/.python-version +0 -0
  9. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/Taskfile.yml +0 -0
  10. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/pyproject.toml +0 -0
  11. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/src/pyannotators_entityfishing/ef_client.py +0 -0
  12. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/src/pyannotators_entityfishing/entityfishing.py +0 -0
  13. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/.claude-plugin/marketplace.json +0 -0
  14. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/.claude-plugin/plugin.json +0 -0
  15. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/HOWTO.md +0 -0
  16. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/README.md +0 -0
  17. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/resources/Stages.yml +0 -0
  18. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/resources/Taskfile.yml +0 -0
  19. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/skills/new-plugin/SKILL.md +0 -0
  20. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/skills/new-plugin/references/types.md +0 -0
  21. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/tools/check-marketplace.py +0 -0
  22. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/vars/pythonPipeline.groovy +0 -0
  23. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/tools/eval_mappings.py +0 -0
  24. {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/tools/wdfilter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyannotators-entityfishing
3
- Version: 1.6.67
3
+ Version: 1.6.71
4
4
  Summary: Annotator based on entity-fishing
5
5
  Project-URL: Homepage, https://github.com/oterrier/pyannotators_entityfishing/
6
6
  Author-email: Olivier Terrier <olivier.terrier@kairntech.com>
@@ -89,24 +89,34 @@ for ann in docs[0].annotations:
89
89
  The build is driven by [Task](https://taskfile.dev) and [uv](https://docs.astral.sh/uv/),
90
90
  with the shared stages coming from the `python-archetype` submodule.
91
91
 
92
- ### Prerequisites
92
+ ### Getting started
93
93
 
94
- **Task is the only manual prerequisite.** An archetype cannot bootstrap itself: uv and the
95
- Python interpreter install themselves on demand (every task that runs uv depends on an
96
- internal `install-python` task), but the thing that runs them does not.
94
+ The stages live in a Git submodule, so **clone with `--recurse-submodules`**:
97
95
 
98
96
  ```bash
97
+ git clone --recurse-submodules git@bitbucket.org:kairntech/pyannotators_entityfishing.git
98
+ cd pyannotators_entityfishing
99
99
  sh -c "$(curl -sSL https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
100
+ task
100
101
  ```
101
102
 
102
- Make sure `~/.local/bin` is on your `PATH` — that is where `task` and `uv` both land.
103
+ Already cloned without it? The submodule directory is empty, and `task` fails on:
104
+
105
+ ```
106
+ task: No Taskfile found at ".../submodules/python-archetype/resources/Taskfile.yml"
107
+ ```
103
108
 
104
- Then fetch the shared stages:
109
+ which means exactly that, and nothing worse:
105
110
 
106
111
  ```bash
107
112
  git submodule update --init
108
113
  ```
109
114
 
115
+ **Task is the only manual prerequisite.** An archetype cannot bootstrap itself: uv and the
116
+ Python interpreter install themselves on demand (every task that runs uv depends on an
117
+ internal `install-python` task), but the thing that runs them does not. Make sure
118
+ `~/.local/bin` is on your `PATH` — that is where `task` and `uv` both land.
119
+
110
120
  ### Running the pipeline
111
121
 
112
122
  ```bash
@@ -41,24 +41,34 @@ for ann in docs[0].annotations:
41
41
  The build is driven by [Task](https://taskfile.dev) and [uv](https://docs.astral.sh/uv/),
42
42
  with the shared stages coming from the `python-archetype` submodule.
43
43
 
44
- ### Prerequisites
44
+ ### Getting started
45
45
 
46
- **Task is the only manual prerequisite.** An archetype cannot bootstrap itself: uv and the
47
- Python interpreter install themselves on demand (every task that runs uv depends on an
48
- internal `install-python` task), but the thing that runs them does not.
46
+ The stages live in a Git submodule, so **clone with `--recurse-submodules`**:
49
47
 
50
48
  ```bash
49
+ git clone --recurse-submodules git@bitbucket.org:kairntech/pyannotators_entityfishing.git
50
+ cd pyannotators_entityfishing
51
51
  sh -c "$(curl -sSL https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
52
+ task
52
53
  ```
53
54
 
54
- Make sure `~/.local/bin` is on your `PATH` — that is where `task` and `uv` both land.
55
+ Already cloned without it? The submodule directory is empty, and `task` fails on:
56
+
57
+ ```
58
+ task: No Taskfile found at ".../submodules/python-archetype/resources/Taskfile.yml"
59
+ ```
55
60
 
56
- Then fetch the shared stages:
61
+ which means exactly that, and nothing worse:
57
62
 
58
63
  ```bash
59
64
  git submodule update --init
60
65
  ```
61
66
 
67
+ **Task is the only manual prerequisite.** An archetype cannot bootstrap itself: uv and the
68
+ Python interpreter install themselves on demand (every task that runs uv depends on an
69
+ internal `install-python` task), but the thing that runs them does not. Make sure
70
+ `~/.local/bin` is on your `PATH` — that is where `task` and `uv` both land.
71
+
62
72
  ### Running the pipeline
63
73
 
64
74
  ```bash
@@ -1,3 +1,3 @@
1
1
  """Annotator based on entity-fishing"""
2
2
 
3
- __version__ = "1.6.67"
3
+ __version__ = "1.6.71"
@@ -0,0 +1,118 @@
1
+ """Prototype : NER amont -> candidats entity-fishing -> désambiguïsation Jev.
2
+
3
+ Clé lue dans JEV_API_KEY (comme tests/.env) ou TYPESAFE_API_KEY ; sans clé, affiche la
4
+ requête Jev au lieu de l'envoyer. Voir docs/entity-fishing-disambiguation-candidates_fr.md.
5
+
6
+ .venv/bin/python tools/el_jev.py "Macron à Washington" "Macron:PERSON,Washington:LOCATION" fr
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import re
12
+ import urllib.parse
13
+ import urllib.request
14
+ from pathlib import Path
15
+
16
+ import mongoquery
17
+
18
+ EF = "https://entityfishing.kairntech.com/service"
19
+ API_KEY = os.getenv("JEV_API_KEY") or os.getenv("TYPESAFE_API_KEY")
20
+ JEV = "https://api.typesafe.ai/v1/systemone"
21
+ TOP_K = 8
22
+ NONE = "NONE"
23
+
24
+ # Filtres de type : les mappings mongoquery de l'annotateur (tests/data_v2/<Label>.json),
25
+ # appliqués au JSON de /kb/concept. Un type NER sans mapping (EVENT) n'est pas filtré.
26
+ MAPPINGS = Path(__file__).resolve().parent.parent / "tests" / "data_v2"
27
+ NER_TO_LABEL = {"PERSON": "Person", "LOCATION": "Location", "ORGANIZATION": "Organization"}
28
+ FILTERS = {ner: mongoquery.Query(json.loads((MAPPINGS / f"{label}.json").read_text())) for ner, label in NER_TO_LABEL.items()}
29
+
30
+
31
+ def get(url):
32
+ with urllib.request.urlopen(url, timeout=60) as r:
33
+ return json.load(r)
34
+
35
+
36
+ def senses(term, lang="en"):
37
+ return get(f"{EF}/kb/term/{urllib.parse.quote(term)}?lang={lang}").get("senses", [])
38
+
39
+
40
+ def concept(pageid, lang="en"):
41
+ return get(f"{EF}/kb/concept/{pageid}?lang={lang}")
42
+
43
+
44
+ def first_sentence(wikitext):
45
+ t = re.sub(r"\[\[(?:[^|\]]*\|)?([^\]]*)\]\]", r"\1", wikitext)
46
+ t = re.sub(r"'''?|\{\{[^}]*\}\}|\([^)]*\)", "", t)
47
+ return re.split(r"(?<=\.)\s", re.sub(r"\s+", " ", t).strip())[0]
48
+
49
+
50
+ def candidates(mention, ner_type, lang="en"):
51
+ out = []
52
+ for s in senses(mention, lang)[:TOP_K]:
53
+ c = concept(s["pageid"], lang)
54
+ st = c.get("statements", [])
55
+ if ner_type in FILTERS and not FILTERS[ner_type].match(c):
56
+ continue
57
+ facts = {}
58
+ for x in st:
59
+ if x["propertyId"] in ("P31", "P106", "P27", "P641", "P585") and x.get("valueName"):
60
+ facts.setdefault(x["propertyName"], []).append(x["valueName"])
61
+ if x["propertyId"] == "P569":
62
+ facts["date of birth"] = [x["value"]["time"][1:11]]
63
+ defs = c.get("definitions") or [{}]
64
+ out.append(
65
+ {
66
+ "qid": c["wikidataId"],
67
+ "label": s["preferred"],
68
+ "prior": s["prob_c"],
69
+ "description": first_sentence(defs[0].get("definition", "")),
70
+ "facts": {k: v[:3] for k, v in facts.items()},
71
+ }
72
+ )
73
+ return out
74
+
75
+
76
+ def jev_request(text, mentions):
77
+ """Une Choice par mention, toutes dans le même appel (évaluées en parallèle)."""
78
+ questions = {}
79
+ for i, m in enumerate(mentions):
80
+ criteria = {c["qid"]: {"name": c["label"], "description": c["description"], "facts": c["facts"]} for c in m["candidates"]}
81
+ criteria[NONE] = "None of the listed entities is the one meant by the mention"
82
+ questions[f"m{i}"] = {
83
+ "type": "choice",
84
+ "instructions": {
85
+ "mention": {"surface": m["surface"], "ner_type": m["type"]},
86
+ "question": "In `text`, which knowledge-base entity does `mention.surface` refer to?",
87
+ },
88
+ "criteria": criteria,
89
+ }
90
+ return {"model": "jev-latest", "state": {"text": text}, "questions": questions}
91
+
92
+
93
+ def call_jev(payload):
94
+ req = urllib.request.Request(JEV, data=json.dumps(payload).encode(), headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"})
95
+ with urllib.request.urlopen(req, timeout=60) as r:
96
+ return json.load(r)
97
+
98
+
99
+ if __name__ == "__main__":
100
+ import sys
101
+
102
+ text = sys.argv[1] if len(sys.argv) > 1 else "Ronaldo at the World Cup"
103
+ spec = sys.argv[2] if len(sys.argv) > 2 else "Ronaldo:PERSON,World Cup:EVENT"
104
+ lang = sys.argv[3] if len(sys.argv) > 3 else "en"
105
+ ner = [m.rsplit(":", 1) for m in spec.split(",")]
106
+ mentions = [{"surface": s, "type": t, "candidates": candidates(s, t, lang)} for s, t in ner]
107
+ payload = jev_request(text, mentions)
108
+ if not API_KEY:
109
+ print(json.dumps(payload, indent=1, ensure_ascii=False))
110
+ sys.exit()
111
+ answers = call_jev(payload)["answers"]
112
+ for i, m in enumerate(mentions):
113
+ a = answers[f"m{i}"]
114
+ prior = {c["qid"]: c["prior"] for c in m["candidates"]}
115
+ print(f"{m['surface']} -> {a['choice']} (conf {a['confidence']:.2f})")
116
+ for qid, p in sorted(a["probabilities"].items(), key=lambda kv: -kv[1]):
117
+ label = {c["qid"]: c["label"] for c in m["candidates"]}.get(qid, "")
118
+ print(f" {qid:10} jev={p:.3f} prior={prior.get(qid, 0):.3f} {label}")