File size: 3,168 Bytes
8ce1e37
fb8c550
 
8ce1e37
 
fb8c550
 
8ce1e37
 
 
 
 
 
fb8c550
 
 
 
 
 
 
 
 
 
 
 
 
8ce1e37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bde6aea
 
 
 
8ce1e37
bde6aea
 
 
 
 
 
 
 
 
 
8ce1e37
 
 
bde6aea
 
 
 
 
 
8ce1e37
 
 
 
fb8c550
8ce1e37
 
 
fb8c550
 
 
 
 
 
8ce1e37
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import httpx
import json
import hashlib
import logging

from app.services.cache import cache_get, cache_set

logger = logging.getLogger(__name__)

ANALYSIS_BASE = "https://reactome.org/AnalysisService"


async def run_enrichment(identifiers: list[str]) -> dict | None:
    raw = json.dumps(sorted(identifiers), sort_keys=True)
    key_hash = hashlib.sha256(raw.encode()).hexdigest()[:16]
    cache_key = f"enrichment:{key_hash}"

    cached = cache_get(cache_key)
    if cached is not None:
        try:
            result = json.loads(cached)
            if isinstance(result, dict):
                result["from_cache"] = True
            return result
        except (json.JSONDecodeError, TypeError):
            pass
    try:
        body = "\n".join(identifiers)
        async with httpx.AsyncClient(timeout=30) as client:
            resp = await client.post(
                f"{ANALYSIS_BASE}/identifiers/projection",
                content=body,
                headers={"Content-Type": "text/plain"},
                params={"pageSize": "20", "page": "1"},
            )
            if resp.status_code != 200:
                logger.warning(f"Reactome Analysis Service returned {resp.status_code}")
                return None

            data = resp.json()
            token = data.get("summary", {}).get("token", "")
            if not token:
                logger.warning("No analysis token returned from Reactome")
                return None

            # The projection response already carries the enriched pathways;
            # the separate /token/{token}/pathways call is unnecessary (and
            # currently 404s for tokens returned with a trailing %3D).
            pathways_data = data
            pathways = []
            for item in pathways_data.get("pathways", []):
                species = item.get("species", {})
                species_name = (
                    species.get("name", "")
                    if isinstance(species, dict)
                    else (species or "")
                )
                entities = item.get("entities", {})
                found = entities.get("found", 0)
                total = entities.get("total", 0)
                pathways.append({
                    "stId": item.get("stId", ""),
                    "name": item.get("name", ""),
                    "species": species_name,
                    "entitiesFound": found,
                    "entitiesTotal": total,
                    "geneRatio": round(found / total, 4) if total else 0.0,
                    "entitiesFDR": entities.get("fdr", 1.0),
                    "entitiesPValue": entities.get("pValue", 1.0),
                })

            pathways.sort(key=lambda p: p["entitiesFDR"])

            result = {
                "token": token,
                "pathways": pathways,
            }
            try:
                cache_set(cache_key, json.dumps(result), ttl=86400)
            except (TypeError, ValueError):
                pass
            result["from_cache"] = False
            return result
    except Exception as e:
        logger.warning(f"Pathway enrichment failed: {e}")
        return None