flywire-coding-cortex 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.
@@ -0,0 +1,210 @@
1
+ """Build curated circuit.json from Codex CSVs or Zenodo Feathers."""
2
+ from __future__ import annotations
3
+
4
+ import csv
5
+ import gzip
6
+ import json
7
+ from collections import Counter, defaultdict
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ CORE_TYPES = {
12
+ "LC4": "lc4",
13
+ "LPLC2": "lplc2",
14
+ "DNp01": "gf",
15
+ "DNa02": "dna02",
16
+ "DNa01": "dna01",
17
+ "DNp09": "dnp09",
18
+ "DNg11": "dng11",
19
+ "MDN": "mdn",
20
+ "DNp02": "escw",
21
+ "DNp04": "escw",
22
+ "DNp11": "escw",
23
+ }
24
+ NT_SIGN = {"ACH": 1.0, "GABA": -1.0, "GLUT": -1.0, "DA": 0.5, "SER": 0.5, "OCT": 0.5}
25
+ MAX_PARTNERS = 330
26
+
27
+
28
+ def build_from_codex_dir(raw: Path, out: Path) -> dict[str, Any]:
29
+ def rows(name: str):
30
+ with gzip.open(raw / name, "rt", encoding="utf-8", errors="replace") as f:
31
+ r = csv.reader(f)
32
+ next(r)
33
+ yield from r
34
+
35
+ core: dict[str, str] = {}
36
+ type_of: dict[str, str] = {}
37
+ counts: dict[str, int] = defaultdict(int)
38
+ for row in rows("consolidated_cell_types.csv.gz"):
39
+ rid, ptype = row[0], row[1].strip()
40
+ role = CORE_TYPES.get(ptype)
41
+ if role:
42
+ core[rid] = role
43
+ type_of[rid] = ptype
44
+ counts[ptype] += 1
45
+ print("core populations:", dict(counts))
46
+ if not counts.get("LC4") or not counts.get("LPLC2") or not counts.get("DNp01"):
47
+ raise SystemExit("FATAL: missing a core population — check type names")
48
+
49
+ klass: dict[str, tuple[str, str]] = {}
50
+ for row in rows("classification.csv.gz"):
51
+ klass[row[0]] = (row[2], row[6])
52
+
53
+ pos: dict[str, tuple[float, float, float]] = {}
54
+ for row in rows("coordinates.csv.gz"):
55
+ rid = row[0]
56
+ if rid in pos:
57
+ continue
58
+ p = row[1].strip("[]").split()
59
+ if len(p) == 3:
60
+ pos[rid] = (float(p[0]), float(p[1]), float(p[2]))
61
+
62
+ partner_strength: dict[str, int] = defaultdict(int)
63
+ strength_by_role: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
64
+ for row in rows("connections.csv.gz"):
65
+ pre, post, syn = row[0], row[1], int(row[3])
66
+ pre_core, post_core = pre in core, post in core
67
+ if pre_core and not post_core:
68
+ partner_strength[post] += syn
69
+ strength_by_role[core[pre]][post] += syn
70
+ elif post_core and not pre_core:
71
+ partner_strength[pre] += syn
72
+ strength_by_role[core[post]][pre] += syn
73
+
74
+ usable = lambda r: r in pos and r in klass
75
+ ranked = [rid for rid, _ in sorted(partner_strength.items(), key=lambda kv: -kv[1]) if usable(rid)]
76
+ partners: list[str] = []
77
+ seen: set[str] = set()
78
+
79
+ def take(cands: list[str], k: int) -> None:
80
+ n = 0
81
+ for r in cands:
82
+ if r in seen or not usable(r):
83
+ continue
84
+ seen.add(r)
85
+ partners.append(r)
86
+ n += 1
87
+ if n == k:
88
+ break
89
+
90
+ for role in ("gf", "dna01", "dna02", "dnp09", "dng11", "mdn", "escw"):
91
+ take([r for r, _ in sorted(strength_by_role[role].items(), key=lambda kv: -kv[1])], 10)
92
+ take([r for r in ranked if klass[r][0] == "ascending"], 24)
93
+ take([r for r in ranked if klass[r][0] == "sensory"], 16)
94
+ take(ranked, MAX_PARTNERS - len(partners))
95
+ print("partner super_classes:", dict(Counter(klass[r][0] for r in partners)))
96
+
97
+ members = list(core.keys()) + partners
98
+ member_idx = {rid: i for i, rid in enumerate(members)}
99
+ edges: list[list[float]] = []
100
+ nt_missing = 0
101
+ for row in rows("connections.csv.gz"):
102
+ pre, post = row[0], row[1]
103
+ i, j = member_idx.get(pre), member_idx.get(post)
104
+ if i is None or j is None:
105
+ continue
106
+ syn, nt = int(row[3]), row[4].strip().upper()
107
+ sign = NT_SIGN.get(nt)
108
+ if sign is None:
109
+ sign, nt_missing = 1.0, nt_missing + 1
110
+ edges.append([i, j, round(syn * sign, 1)])
111
+ print(f"circuit edges: {len(edges)} (unknown nt on {nt_missing})")
112
+
113
+ xs = [p[0] for p in pos.values()]
114
+ ys = [p[1] for p in pos.values()]
115
+ zs = [p[2] for p in pos.values()]
116
+ cx, cy, cz = (min(xs) + max(xs)) / 2, (min(ys) + max(ys)) / 2, (min(zs) + max(zs)) / 2
117
+ scale = 20.0 / max(max(xs) - min(xs), max(ys) - min(ys), max(zs) - min(zs))
118
+
119
+ def norm(p: tuple[float, float, float]) -> list[float]:
120
+ return [
121
+ round((p[0] - cx) * scale, 3),
122
+ round(-(p[1] - cy) * scale, 3),
123
+ round(-(p[2] - cz) * scale, 3),
124
+ ]
125
+
126
+ neurons = []
127
+ for rid in members:
128
+ sc, side = klass.get(rid, ("", ""))
129
+ p = norm(pos[rid]) if rid in pos else [0.0, 0.0, 0.0]
130
+ neurons.append(
131
+ {
132
+ "id": rid,
133
+ "type": type_of.get(rid, sc or "?"),
134
+ "role": core.get(rid, "other"),
135
+ "side": side,
136
+ "pos": p,
137
+ }
138
+ )
139
+ circuit = {
140
+ "neurons": neurons,
141
+ "edges": edges,
142
+ "source": "FlyWire Codex FAFB v783 connections.csv (syn>=5, signed by nt_type)",
143
+ }
144
+ out.parent.mkdir(parents=True, exist_ok=True)
145
+ out.write_text(json.dumps(circuit, separators=(",", ":")) + "\n", encoding="utf-8")
146
+ print(f"Wrote {out}: {len(neurons)} neurons, {len(edges)} edges")
147
+ return circuit
148
+
149
+
150
+ def build_from_feather_dir(raw: Path, out: Path) -> dict[str, Any]:
151
+ """Build a curated circuit using proofread_connections Feather + cell types if present.
152
+
153
+ Falls back to requiring Codex cell-type CSVs in the same raw dir (downloaded
154
+ automatically alongside Feathers when missing).
155
+ """
156
+ import pandas as pd
157
+
158
+ conn_path = raw / "proofread_connections_783.feather"
159
+ if not conn_path.exists():
160
+ raise FileNotFoundError(f"Missing {conn_path}")
161
+
162
+ # Need cell types — pull Codex types if absent
163
+ types_gz = raw / "consolidated_cell_types.csv.gz"
164
+ class_gz = raw / "classification.csv.gz"
165
+ coords_gz = raw / "coordinates.csv.gz"
166
+ if not types_gz.exists():
167
+ from .download import download_file, load_manifest
168
+
169
+ base = load_manifest()["profiles"]["codex"]["base_url"].rstrip("/")
170
+ for name in (
171
+ "consolidated_cell_types.csv.gz",
172
+ "classification.csv.gz",
173
+ "coordinates.csv.gz",
174
+ "connections.csv.gz",
175
+ ):
176
+ dest = raw / name
177
+ if not dest.exists():
178
+ download_file(f"{base}/{name}", dest, label=name)
179
+
180
+ # Prefer Codex thresholded connections for role ETL consistency when present;
181
+ # otherwise synthesize a connections.csv.gz-like stream from Feather.
182
+ if (raw / "connections.csv.gz").exists():
183
+ return build_from_codex_dir(raw, out)
184
+
185
+ df = pd.read_feather(conn_path)
186
+ # Normalize column names across releases
187
+ cols = {c.lower(): c for c in df.columns}
188
+ pre_c = cols.get("pre_root_id") or cols.get("pre") or cols.get("from") or list(df.columns)[0]
189
+ post_c = cols.get("post_root_id") or cols.get("post") or cols.get("to") or list(df.columns)[1]
190
+ syn_c = cols.get("syn_count") or cols.get("synapse_count") or cols.get("count")
191
+ if syn_c is None:
192
+ for c in df.columns:
193
+ if "syn" in c.lower() or "count" in c.lower():
194
+ syn_c = c
195
+ break
196
+ if syn_c is None:
197
+ raise SystemExit(f"Cannot find synapse count column in {list(df.columns)}")
198
+
199
+ tmp_conn = raw / "connections.csv.gz"
200
+ subset = df[[pre_c, post_c, syn_c]].copy()
201
+ subset.columns = ["pre", "post", "syn"]
202
+ with gzip.open(tmp_conn, "wt", encoding="utf-8") as f:
203
+ w = csv.writer(f)
204
+ w.writerow(["pre_root_id", "post_root_id", "neuropil", "syn_count", "nt_type"])
205
+ for pre, post, syn in subset.itertuples(index=False, name=None):
206
+ syn_i = int(syn)
207
+ if syn_i < 5:
208
+ continue
209
+ w.writerow([str(pre), str(post), "", syn_i, "ACH"])
210
+ return build_from_codex_dir(raw, out)
@@ -0,0 +1,128 @@
1
+ """Download profile assets into the user cache."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import shutil
6
+ import urllib.request
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from ..paths import bundled_manifest, bundled_seed_circuit, profile_dir
11
+
12
+
13
+ def load_manifest() -> dict[str, Any]:
14
+ return json.loads(bundled_manifest().read_text(encoding="utf-8"))
15
+
16
+
17
+ def list_profiles() -> dict[str, Any]:
18
+ return load_manifest()["profiles"]
19
+
20
+
21
+ def download_file(url: str, dest: Path, *, label: str = "") -> None:
22
+ dest.parent.mkdir(parents=True, exist_ok=True)
23
+ tmp = dest.with_suffix(dest.suffix + ".part")
24
+ print(f"Downloading {label or dest.name} …")
25
+ urllib.request.urlretrieve(url, tmp) # noqa: S310 — curated manifest URLs
26
+ tmp.replace(dest)
27
+ print(f" -> {dest} ({dest.stat().st_size:,} bytes)")
28
+
29
+
30
+ def fetch_seed(*, force: bool = False) -> Path:
31
+ dest = profile_dir("seed") / "circuit.json"
32
+ if dest.exists() and not force:
33
+ return dest
34
+ seed = bundled_seed_circuit()
35
+ if seed is None:
36
+ raise FileNotFoundError(
37
+ "circuit.seed.json missing. Build it with: flywire-cortex fetch --profile codex"
38
+ )
39
+ shutil.copy2(seed, dest)
40
+ meta = {
41
+ "profile": "seed",
42
+ "source": "bundled circuit.seed.json",
43
+ "neurons": _count_neurons(dest),
44
+ }
45
+ (profile_dir("seed") / "meta.json").write_text(json.dumps(meta, indent=2) + "\n", encoding="utf-8")
46
+ return dest
47
+
48
+
49
+ def fetch_codex(*, force: bool = False) -> Path:
50
+ from .build_circuit import build_from_codex_dir
51
+
52
+ raw = profile_dir("codex") / "raw"
53
+ circuit = profile_dir("codex") / "circuit.json"
54
+ if circuit.exists() and not force:
55
+ return circuit
56
+ manifest = load_manifest()["profiles"]["codex"]
57
+ base = manifest["base_url"].rstrip("/")
58
+ raw.mkdir(parents=True, exist_ok=True)
59
+ for f in manifest["files"]:
60
+ name = f["name"]
61
+ dest = raw / name
62
+ if dest.exists() and not force:
63
+ continue
64
+ download_file(f"{base}/{f['path']}", dest, label=name)
65
+ build_from_codex_dir(raw, circuit)
66
+ meta = {
67
+ "profile": "codex",
68
+ "source": base,
69
+ "neurons": _count_neurons(circuit),
70
+ }
71
+ (profile_dir("codex") / "meta.json").write_text(json.dumps(meta, indent=2) + "\n", encoding="utf-8")
72
+ # also refresh bundled seed for packaging workflows
73
+ seed_out = bundled_seed_circuit()
74
+ if seed_out is None:
75
+ seed_out = Path(__file__).resolve().parents[3] / "data" / "circuit.seed.json"
76
+ seed_out.parent.mkdir(parents=True, exist_ok=True)
77
+ shutil.copy2(circuit, seed_out)
78
+ assets = Path(__file__).resolve().parents[1] / "assets" / "circuit.seed.json"
79
+ assets.parent.mkdir(parents=True, exist_ok=True)
80
+ shutil.copy2(circuit, assets)
81
+ return circuit
82
+
83
+
84
+ def fetch_full(*, force: bool = False, include_synapses: bool = False, yes: bool = False) -> Path:
85
+ if not yes:
86
+ raise SystemExit(
87
+ "Profile 'full' downloads ~10.6 GB. Re-run with --yes to confirm:\n"
88
+ " flywire-cortex fetch --profile full --yes\n"
89
+ "Optional largest synapse table: add --include-synapses"
90
+ )
91
+ try:
92
+ import pandas as pd # noqa: F401
93
+ import pyarrow # noqa: F401
94
+ except ImportError as exc:
95
+ raise SystemExit(
96
+ "Profile 'full' needs: pip install 'flywire-coding-cortex[full]'"
97
+ ) from exc
98
+
99
+ from .build_circuit import build_from_feather_dir
100
+
101
+ raw = profile_dir("full") / "raw"
102
+ circuit = profile_dir("full") / "circuit.json"
103
+ if circuit.exists() and not force:
104
+ return circuit
105
+ manifest = load_manifest()["profiles"]["full"]
106
+ raw.mkdir(parents=True, exist_ok=True)
107
+ for f in manifest["files"]:
108
+ if f.get("optional") and not include_synapses:
109
+ print(f"Skipping optional {f['name']} (pass --include-synapses to fetch)")
110
+ continue
111
+ dest = raw / f["name"]
112
+ if dest.exists() and not force:
113
+ continue
114
+ download_file(f["url"], dest, label=f"{f['name']} (~{f.get('approx_bytes', 0):,} B)")
115
+ build_from_feather_dir(raw, circuit)
116
+ meta = {
117
+ "profile": "full",
118
+ "source": manifest.get("record_url"),
119
+ "neurons": _count_neurons(circuit),
120
+ "include_synapses": include_synapses,
121
+ }
122
+ (profile_dir("full") / "meta.json").write_text(json.dumps(meta, indent=2) + "\n", encoding="utf-8")
123
+ return circuit
124
+
125
+
126
+ def _count_neurons(path: Path) -> int:
127
+ data = json.loads(path.read_text(encoding="utf-8"))
128
+ return len(data.get("neurons", []))
@@ -0,0 +1,74 @@
1
+ """Copy bundled skill into Cursor / OpenClaw / Claude skill directories."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import shutil
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from .paths import bundled_skill_dir, home
10
+
11
+
12
+ def _cursor_skills() -> Path:
13
+ return Path.home() / ".cursor" / "skills" / "flywire-coding-cortex"
14
+
15
+
16
+ def _openclaw_skills() -> list[Path]:
17
+ h = Path.home()
18
+ return [
19
+ h / ".openclaw" / "workspace" / "skills" / "flywire-coding-cortex",
20
+ h / ".openclaw" / "skills" / "flywire-coding-cortex",
21
+ ]
22
+
23
+
24
+ def _claude_skills() -> Path:
25
+ return Path.home() / ".claude" / "skills" / "flywire-coding-cortex"
26
+
27
+
28
+ def _copy_skill(dest: Path) -> str:
29
+ src = bundled_skill_dir()
30
+ dest.parent.mkdir(parents=True, exist_ok=True)
31
+ if dest.exists():
32
+ shutil.rmtree(dest)
33
+ shutil.copytree(src, dest)
34
+ return str(dest)
35
+
36
+
37
+ def mcp_snippet() -> dict[str, Any]:
38
+ return {
39
+ "mcpServers": {
40
+ "flywire-coding-cortex": {
41
+ "command": "flywire-cortex",
42
+ "args": ["mcp"],
43
+ }
44
+ }
45
+ }
46
+
47
+
48
+ def install_all(targets: list[str] | None = None) -> dict[str, Any]:
49
+ targets = targets or ["cursor", "openclaw", "claude"]
50
+ result: dict[str, Any] = {"installed": [], "skipped": [], "mcp": mcp_snippet(), "home": str(home())}
51
+ if "cursor" in targets:
52
+ result["installed"].append({"target": "cursor", "path": _copy_skill(_cursor_skills())})
53
+ if "openclaw" in targets:
54
+ # prefer workspace path; also try top-level skills
55
+ paths = _openclaw_skills()
56
+ primary = paths[0]
57
+ result["installed"].append({"target": "openclaw", "path": _copy_skill(primary)})
58
+ # second path as optional mirror if parent exists
59
+ if paths[1].parent.exists() or paths[1].parent.parent.exists():
60
+ try:
61
+ paths[1].parent.mkdir(parents=True, exist_ok=True)
62
+ result["installed"].append({"target": "openclaw-alt", "path": _copy_skill(paths[1])})
63
+ except OSError as exc:
64
+ result["skipped"].append({"target": "openclaw-alt", "error": str(exc)})
65
+ if "claude" in targets:
66
+ result["installed"].append({"target": "claude", "path": _copy_skill(_claude_skills())})
67
+ tip = home() / "mcp.snippet.json"
68
+ tip.write_text(json.dumps(mcp_snippet(), indent=2) + "\n", encoding="utf-8")
69
+ result["mcp_snippet_file"] = str(tip)
70
+ result["hint"] = (
71
+ "Add the mcp snippet to Cursor MCP settings, then restart the agent session. "
72
+ "Ensure `flywire-cortex` is on PATH (pip/uv install)."
73
+ )
74
+ return result