annotuneagent 0.1.0__py3-none-any.whl

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 (38) hide show
  1. annotuneagent/__init__.py +68 -0
  2. annotuneagent/__main__.py +6 -0
  3. annotuneagent/_version.py +19 -0
  4. annotuneagent/annotate.py +93 -0
  5. annotuneagent/arbitrate.py +141 -0
  6. annotuneagent/cli.py +366 -0
  7. annotuneagent/decide.py +725 -0
  8. annotuneagent/evaluate.py +121 -0
  9. annotuneagent/evidence.py +606 -0
  10. annotuneagent/granularity/__init__.py +28 -0
  11. annotuneagent/granularity/cl_dag.py +179 -0
  12. annotuneagent/granularity/ontology_map.py +108 -0
  13. annotuneagent/granularity/relations.py +96 -0
  14. annotuneagent/hypotheses.py +38 -0
  15. annotuneagent/local_lora.py +706 -0
  16. annotuneagent/marker_plot.py +172 -0
  17. annotuneagent/markers.py +206 -0
  18. annotuneagent/markers.tsv +378 -0
  19. annotuneagent/mltools.py +128 -0
  20. annotuneagent/ontology/cl_isa.json +1 -0
  21. annotuneagent/ontology/granularity_policy.yaml +8 -0
  22. annotuneagent/ontology/ontology_map.jsonl +198 -0
  23. annotuneagent/prepare.py +1385 -0
  24. annotuneagent/py.typed +0 -0
  25. annotuneagent/read.py +76 -0
  26. annotuneagent/tokenizer/LICENSE +202 -0
  27. annotuneagent/tokenizer/merges.txt +247587 -0
  28. annotuneagent/tokenizer/tokenizer.json +495920 -0
  29. annotuneagent/tokenizer/tokenizer_config.json +305 -0
  30. annotuneagent/tokenizer/vocab.json +248046 -0
  31. annotuneagent/votes.py +83 -0
  32. annotuneagent/workflow.py +2037 -0
  33. annotuneagent-0.1.0.dist-info/METADATA +208 -0
  34. annotuneagent-0.1.0.dist-info/RECORD +38 -0
  35. annotuneagent-0.1.0.dist-info/WHEEL +5 -0
  36. annotuneagent-0.1.0.dist-info/entry_points.txt +2 -0
  37. annotuneagent-0.1.0.dist-info/licenses/LICENSE +201 -0
  38. annotuneagent-0.1.0.dist-info/top_level.txt +1 -0
annotuneagent/votes.py ADDED
@@ -0,0 +1,83 @@
1
+ # Copyright 2026 Zhao Li (李钊)
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """Agree a cluster label from several views of the same evidence.
5
+
6
+ A view is one name or nothing. Three names that share a parent are enough to
7
+ write that parent. Three that name the same child write the child. One
8
+ dissenting view does not block. Two coarse names with no three-vote parent
9
+ stay unresolved.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from collections import Counter
15
+
16
+ from .evidence import NEGATIVE_MIN_LOGFC, NEGATIVE_PRESENT_PCT
17
+ from .hypotheses import Hypothesis
18
+
19
+ VIEWS = ("panel", "genes", "subcluster", "neighbors")
20
+
21
+
22
+ def panel_vote(record: dict) -> str | None:
23
+ """Top dictionary type when its positives are high and negatives are not."""
24
+ if record.get("status") == "label" and record.get("cell_type"):
25
+ return str(record["cell_type"])
26
+ ranking = list(record.get("ranking") or [])
27
+ if not ranking or float(ranking[0]["score"]) <= 0:
28
+ return None
29
+ for gene in (record.get("evidence") or {}).get("negative") or []:
30
+ if (
31
+ gene.get("pct_in", 0) >= NEGATIVE_PRESENT_PCT
32
+ and gene.get("logfc", 0) >= NEGATIVE_MIN_LOGFC
33
+ ):
34
+ return None
35
+ return str(ranking[0]["name"])
36
+
37
+
38
+ def gene_vote(hypotheses: list[Hypothesis], logfc: dict[str, float]) -> str | None:
39
+ """Dictionary type whose measured positive markers are relatively high."""
40
+ scored: list[tuple[int, float, str]] = []
41
+ for item in hypotheses:
42
+ present = [gene for gene in item.positive if gene in logfc]
43
+ high = [gene for gene in present if logfc[gene] > 0]
44
+ if not high:
45
+ continue
46
+ if len(present) >= 2 and len(high) < 2:
47
+ continue
48
+ scored.append((len(high), sum(logfc[gene] for gene in high), item.name))
49
+ if not scored:
50
+ return None
51
+ scored.sort(key=lambda item: (-item[0], -item[1], item[2]))
52
+ if len(scored) > 1 and scored[0][:2] == scored[1][:2]:
53
+ return None
54
+ return scored[0][2]
55
+
56
+
57
+ def subcluster_vote(children: list[dict]) -> str | None:
58
+ """Name shared by every labeled child. A split casts no single vote."""
59
+ if not children or any(row.get("status") != "label" for row in children):
60
+ return None
61
+ names = {str(row["cell_type"]) for row in children}
62
+ if len(names) == 1:
63
+ return next(iter(names))
64
+ return None
65
+
66
+
67
+ def agreed_name(votes: dict[str, str | None], parents: dict[str, str]) -> str | None:
68
+ """Parent first, then a child when three votes name that child."""
69
+ named = [name for name in votes.values() if name]
70
+ if len(named) < 3:
71
+ return None
72
+
73
+ def coarse(name: str) -> str:
74
+ return parents.get(name, name)
75
+
76
+ parent, parent_votes = Counter(coarse(name) for name in named).most_common(1)[0]
77
+ if parent_votes < 3:
78
+ return None
79
+ fine_counts = Counter(name for name in named if coarse(name) == parent)
80
+ fine, fine_votes = fine_counts.most_common(1)[0]
81
+ if fine_votes >= 3:
82
+ return fine
83
+ return parent