a8594d786c
The Code empowers the executive branch, but the mirror only had legislative Bodies — every authority reference pointed at nothing. Fixed by minting the executive branch from the Code's OWN enumerations: 15 Cabinet departments (5 U.S.C. § 101) + 21 Executive-Schedule Level I offices (§ 5312), 42 Body nodes under us/executive/. Then the axis: scripts/extract_authority.py matches named office/department references across all 59,740 sections (guarding against Deputy/Assistant/Under subordinates) and emits data/section_authority_edges.jsonl — 27,804 edges from 17,031 sections (28.7% of the Code) to the offices they empower. build.py renders reciprocal 'empowered by' links on each office node; the section files are never touched. Now: click the Attorney General, see all 2,386 sections that vest authority in it. Named references only in v1 (cabinet-level). Deterministic; body.schema classification enum extended (executive-department/-agency/-office); make check green at 105,746 records. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
122 lines
4.7 KiB
Python
122 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Extract the authority axis: U.S. Code section -> executive office/department.
|
|
|
|
Named-reference extraction (v1): matches the canonical office and department
|
|
names in data/executive_offices.jsonl against each section's operative text
|
|
(the '## Text' body, excluding historical Notes), and emits one edge per
|
|
(section, target) into data/section_authority_edges.jsonl.
|
|
|
|
Deterministic and mechanical — no NLP, no network. This is a committed input,
|
|
generated like county_district_edges.jsonl; scripts/build.py renders it into
|
|
reciprocal links on the executive office nodes (the section files are never
|
|
touched). Relative references ("the Secretary") are out of scope for v1.
|
|
|
|
Usage: python3 scripts/extract_authority.py
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
OFFICES = REPO / "data" / "executive_offices.jsonl"
|
|
CODE = REPO / "legal" / "us" / "code"
|
|
OUT = REPO / "data" / "section_authority_edges.jsonl"
|
|
|
|
# An alias immediately preceded by one of these is a *different*, lower office
|
|
# (a Deputy/Assistant/Under Secretary, a Solicitor/Inspector General, etc.) —
|
|
# not the cabinet-rank principal, so it must not match.
|
|
SUBORDINATE_PREFIX = re.compile(
|
|
r"(?:Deputy|Assistant|Associate|Under|Acting|Solicitor|Inspector|Principal|"
|
|
r"Special|Vice|Former|Additional)\s*$")
|
|
|
|
|
|
def load_targets():
|
|
"""Return alias-matchers sorted longest-first: (regex, target_id, name, kind, rel)."""
|
|
targets = []
|
|
for line in OFFICES.open():
|
|
r = json.loads(line)
|
|
dept_id = f"us/executive/{r['slug']}"
|
|
office_id = f"{dept_id}/{r['head']['slug']}"
|
|
rel_kind = "references" # a department/agency mention
|
|
for alias in r["aliases"]:
|
|
targets.append((alias, dept_id, r["name"], r["kind"], rel_kind))
|
|
for alias in r["head"]["aliases"]: # the officer the law empowers
|
|
targets.append((alias, office_id, r["head"]["name"], "office", "empowers"))
|
|
# longest alias first so "Secretary of the Treasury" wins over any substring
|
|
targets.sort(key=lambda t: -len(t[0]))
|
|
return [(re.compile(r"\b" + re.escape(a) + r"\b"), tid, name, kind, rel)
|
|
for a, tid, name, kind, rel in targets]
|
|
|
|
|
|
def operative_text(md):
|
|
"""The section's live text only — frontmatter and historical Notes dropped."""
|
|
parts = md.split("---", 2)
|
|
body = parts[2] if len(parts) >= 3 else md
|
|
return re.split(r"\n##\s+Notes", body, maxsplit=1)[0]
|
|
|
|
|
|
def frontmatter_field(md, key):
|
|
m = re.search(rf"(?m)^{key}:\s*(.+)$", md.split("---", 2)[1] if "---" in md else md)
|
|
if not m:
|
|
return None
|
|
v = m.group(1).strip()
|
|
return v[1:-1] if len(v) >= 2 and v[0] == v[-1] == '"' else v
|
|
|
|
|
|
def main():
|
|
targets = load_targets()
|
|
edges = []
|
|
for path in sorted(CODE.rglob("section-*.md")):
|
|
md = path.read_text(encoding="utf-8")
|
|
text = operative_text(md)
|
|
section = frontmatter_field(md, "source_identifier")
|
|
citation = frontmatter_field(md, "citation")
|
|
title_num = frontmatter_field(md, "title_number")
|
|
if not section:
|
|
continue
|
|
rel_path = str(path.relative_to(REPO))
|
|
# (target_id) -> [name, kind, rel, mentions]; keep the most-specific hit
|
|
hits = {}
|
|
for rx, tid, name, kind, rel in targets:
|
|
occ = 0
|
|
for m in rx.finditer(text):
|
|
pre = text[max(0, m.start() - 24):m.start()]
|
|
if SUBORDINATE_PREFIX.search(pre):
|
|
continue
|
|
occ += 1
|
|
if occ:
|
|
cur = hits.get(tid)
|
|
if cur is None:
|
|
hits[tid] = [name, kind, rel, occ]
|
|
else:
|
|
cur[3] += occ
|
|
for tid, (name, kind, rel, mentions) in hits.items():
|
|
edges.append({
|
|
"section": section,
|
|
"citation": citation,
|
|
"path": rel_path,
|
|
"title": int(title_num) if title_num and title_num.isdigit() else None,
|
|
"target": tid,
|
|
"target_name": name,
|
|
"target_kind": kind,
|
|
"relationship": rel,
|
|
"mentions": mentions,
|
|
})
|
|
|
|
edges.sort(key=lambda e: (e["section"], e["target"]))
|
|
with OUT.open("w", encoding="utf-8") as f:
|
|
for e in edges:
|
|
f.write(json.dumps(e, ensure_ascii=False) + "\n")
|
|
|
|
sections = len({e["section"] for e in edges})
|
|
offices = len({e["target"] for e in edges if e["target_kind"] == "office"})
|
|
print(f"authority edges: {len(edges)} "
|
|
f"(sections with authority: {sections}; distinct targets hit: {len({e['target'] for e in edges})}; "
|
|
f"office targets: {offices})")
|
|
print(f"wrote {OUT.relative_to(REPO)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|