"""Count Pharos target development levels (TDLs) by target family. Python 3.9+, standard library.

Input: the public Pharos GraphQL API (https://pharos-api.ncats.io/graphql), which serves
TCRD through the Pharos database. The first run on 2026-10-23 returned dbVersion
"pharos319"; the Pharos About page reported Pharos 3.19.10 and TCRD 6.13.5 that day.

Run:  python analyze.py
Writes family-tdl-counts.csv and provenance.json next to this script. Each row counts
human targets in one family by Tclin, Tchem, Tbio and Tdark, plus the Tclin share.

Families are the Pharos "IDG Family" facet, which assigns each target to exactly one
family, so the IDG rows sum to the whole target list. Pharos has no protease family;
the protease row uses the "DTO Class" facet value "Protease" (Drug Target Ontology),
which overlaps the Enzyme and Other rows and is not part of that sum.
Outputs are generated by this script, never edited by hand.
"""

import csv
import json
import urllib.request
from datetime import datetime, timezone
from pathlib import Path

ENDPOINT = "https://pharos-api.ncats.io/graphql"
ROOT = Path(__file__).resolve().parent
TDLS = ["Tclin", "Tchem", "Tbio", "Tdark"]
IDG_FAMILIES = [
    "GPCR",
    "oGPCR",
    "Kinase",
    "Ion Channel",
    "Nuclear Receptor",
    "Enzyme",
    "Transporter",
    "Transcription Factor",
    "Epigenetic",
    "TF-Epigenetic",
    "Other",
]


def graphql(query, variables=None):
    body = json.dumps({"query": query, "variables": variables or {}}).encode()
    request = urllib.request.Request(
        ENDPOINT, data=body, headers={"Content-Type": "application/json"}
    )
    with urllib.request.urlopen(request, timeout=180) as response:
        payload = json.load(response)
    if payload.get("errors"):
        raise RuntimeError(payload["errors"])
    return payload["data"]


def tdl_counts(facets):
    data = graphql(
        """query ($filter: IFilter) {
          targets(filter: $filter) {
            count
            facets(include: ["Target Development Level"]) { values(all: true) { name value } }
          }
        }""",
        {"filter": {"facets": facets}} if facets else None,
    )["targets"]
    levels = {v["name"]: v["value"] for v in data["facets"][0]["values"]}
    counts = {tdl: levels.get(tdl, 0) for tdl in TDLS}
    if sum(counts.values()) != data["count"]:
        raise RuntimeError(f"TDL counts do not sum to {data['count']} for {facets}")
    return data["count"], counts


def row(source, family, facets):
    total, counts = tdl_counts(facets)
    share = 100 * counts["Tclin"] / total if total else 0
    return {
        "family_source": source,
        "family": family,
        "targets": total,
        **{tdl.lower(): counts[tdl] for tdl in TDLS},
        "tclin_share_pct": f"{share:.1f}",
    }


def main():
    rows = [
        row("IDG Family", name, [{"facet": "IDG Family", "values": [name]}])
        for name in IDG_FAMILIES
    ]
    rows.append(row("DTO Class", "Protease", [{"facet": "DTO Class", "values": ["Protease"]}]))
    rows.append(row("All targets", "All", []))

    idg = rows[: len(IDG_FAMILIES)]
    for key in ["targets", "tclin"]:
        if sum(r[key] for r in idg) != rows[-1][key]:
            raise RuntimeError(f"IDG Family rows do not sum to the {key} total")

    with open(ROOT / "family-tdl-counts.csv", "w", newline="", encoding="utf-8") as handle:
        writer = csv.DictWriter(handle, fieldnames=list(rows[0]), lineterminator="\n")
        writer.writeheader()
        writer.writerows(rows)

    provenance = {
        "endpoint": ENDPOINT,
        "db_version": graphql("{ dbVersion }")["dbVersion"],
        "about_page_on_2026_10_23": {"pharos": "3.19.10", "tcrd": "6.13.5"},
        "queried_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
        "facets": ["IDG Family", "DTO Class = Protease", "Target Development Level"],
    }
    (ROOT / "provenance.json").write_text(json.dumps(provenance, indent=2) + "\n", encoding="utf-8")
    for r in rows:
        print(f"{r['family']:<22}{r['targets']:>7}{r['tclin']:>6}{r['tclin_share_pct']:>7}%")


if __name__ == "__main__":
    main()
