osint-investigation

Follow the money via public records and sanctions data.

  • osint
  • investigation
  • public-records
  • sec
  • sanctions
  • corporate-registry
  • property
  • courts
  • due-diligence
  • journalism

Declared platforms: linux · macos · windows

Install
npx skills add 'https://github.com/NousResearch/hermes-agent/tree/main/optional-skills/research/osint-investigation'
Download bundle ↓
main · 24fd22bScanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗
View on GitHub
← Back to SKILL.md
#!/usr/bin/env python3"""Build a structured findings.json with evidence chains (stdlib-only). Aggregates cross_links.csv (entity_resolution output) and an optionaltiming.json (timing_analysis output) into a single evidence-chain document. Output structure:    {      "metadata": {...},      "findings": [        {          "id": "F0001",          "title": "...",          "severity": "HIGH|MEDIUM|LOW",          "confidence": "high|medium|low",          "summary": "...",          "evidence": [            {"source": "cross_links.csv", "row": 12, "fields": {...}},            ...          ],          "sources": ["cross_links.csv", "timing.json"]        }      ]    } Every finding traces to specific source rows. No naked claims."""from __future__ import annotations import argparseimport csvimport jsonfrom collections import defaultdictfrom pathlib import Path CONFIDENCE_ORDER = {"high": 0, "medium": 1, "low": 2}SEVERITY_ORDER = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}  def _read_cross_links(path: str) -> list[dict[str, str]]:    with open(path, newline="", encoding="utf-8") as fh:        return list(csv.DictReader(fh))  def build_findings(    cross_links_path: str,    timing_path: str | None = None,    out_path: str = "findings.json",    bundled_threshold: int = 3,) -> dict:    findings: list[dict] = []    next_id = 1     # 1. Match-based findings, grouped by (left_normalized, right_normalized).    matches = _read_cross_links(cross_links_path)    grouped: dict[tuple[str, str], list[dict[str, str]]] = defaultdict(list)    for i, row in enumerate(matches):        row["__row__"] = str(i)        grouped[(row.get("left_normalized", ""), row.get("right_normalized", ""))].append(row)     for (left_norm, right_norm), rows in grouped.items():        if not left_norm or not right_norm:            continue        # Use the highest-confidence match for the finding's overall confidence.        best = min(rows, key=lambda r: CONFIDENCE_ORDER.get(r.get("confidence", "low"), 2))        finding_id = f"F{next_id:04d}"        next_id += 1        evidence = [            {                "source": "cross_links.csv",                "row": int(r["__row__"]),                "fields": {                    "match_type": r.get("match_type", ""),                    "confidence": r.get("confidence", ""),                    "left_name": r.get("left_name", ""),                    "right_name": r.get("right_name", ""),                    "overlap_ratio": r.get("overlap_ratio", ""),                    "shared_tokens": r.get("shared_tokens", ""),                },            }            for r in rows        ]        findings.append(            {                "id": finding_id,                "title": f"Entity match: {best.get('left_name', '')} ↔ {best.get('right_name', '')}",                "severity": "MEDIUM" if best.get("confidence") == "high" else "LOW",                "confidence": best.get("confidence", "low"),                "summary": (                    f"{len(rows)} cross-link record(s) tie "                    f"'{best.get('left_name', '')}' to "                    f"'{best.get('right_name', '')}' "                    f"(best tier: {best.get('match_type', '')})."                ),                "evidence": evidence,                "sources": ["cross_links.csv"],            }        )     # 2. Bundled-donations findings (if cross_links carries donor↔candidate pattern).    #    Heuristic: many distinct left names sharing the same right name.    by_right: dict[str, set[str]] = defaultdict(set)    by_right_rows: dict[str, list[dict[str, str]]] = defaultdict(list)    for r in matches:        right = r.get("right_normalized", "")        left_raw = r.get("left_name", "").strip()        if right and left_raw:            by_right[right].add(left_raw)            by_right_rows[right].append(r)    for right_norm, lefts in by_right.items():        if len(lefts) < bundled_threshold:            continue        rows = by_right_rows[right_norm]        right_raw = rows[0].get("right_name", "")        findings.append(            {                "id": f"F{next_id:04d}",                "title": f"Bundled cross-links: {len(lefts)} distinct left entities ↔ '{right_raw}'",                "severity": "HIGH",                "confidence": "medium",                "summary": (                    f"{len(lefts)} distinct left-side entities link to "                    f"'{right_raw}'. Pattern suggests coordinated relationship "                    f"(e.g. bundled donations, multi-vendor employer)."                ),                "evidence": [                    {                        "source": "cross_links.csv",                        "row": int(r.get("__row__", "0")),                        "fields": {                            "left_name": r.get("left_name", ""),                            "match_type": r.get("match_type", ""),                        },                    }                    for r in rows                ],                "sources": ["cross_links.csv"],            }        )        next_id += 1     # 3. Timing-based findings.    if timing_path and Path(timing_path).exists():        timing = json.loads(Path(timing_path).read_text(encoding="utf-8"))        for r in timing.get("results", []):            if not r.get("significant"):                continue            findings.append(                {                    "id": f"F{next_id:04d}",                    "title": (                        f"Donation timing significantly clusters near awards: "                        f"{r['donor']} ↔ {r['recipient']}"                    ),                    "severity": "HIGH" if r["p_value"] < 0.01 else "MEDIUM",                    "confidence": "medium",                    "summary": (                        f"Mean nearest-award distance {r['observed_mean_days']} days "                        f"(null {r['null_mean_days']} days). p={r['p_value']}, "                        f"effect size {r['effect_size_sd']} SD. "                        f"{r['n_donations']} donations, {r['n_award_dates']} awards."                    ),                    "evidence": [                        {                            "source": "timing.json",                            "row": None,                            "fields": r,                        }                    ],                    "sources": ["timing.json"],                }            )            next_id += 1     # Sort: severity → confidence → id.    findings.sort(        key=lambda f: (            SEVERITY_ORDER.get(f["severity"], 3),            CONFIDENCE_ORDER.get(f["confidence"], 3),            f["id"],        )    )     payload = {        "metadata": {            "n_findings": len(findings),            "cross_links_path": cross_links_path,            "timing_path": timing_path,            "bundled_threshold": bundled_threshold,        },        "findings": findings,    }    Path(out_path).write_text(json.dumps(payload, indent=2), encoding="utf-8")    return payload  def main() -> int:    p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)    p.add_argument("--cross-links", required=True)    p.add_argument("--timing", help="Optional timing.json from timing_analysis.py")    p.add_argument("--out", default="findings.json")    p.add_argument(        "--bundled-threshold",        type=int,        default=3,        help="Minimum distinct left entities to flag as bundled (default 3)",    )    a = p.parse_args()     payload = build_findings(        cross_links_path=a.cross_links,        timing_path=a.timing,        out_path=a.out,        bundled_threshold=a.bundled_threshold,    )    print(f"Wrote {payload['metadata']['n_findings']} findings to {a.out}")    return 0  if __name__ == "__main__":    raise SystemExit(main())