Spaces:
Sleeping
Sleeping
| """ | |
| AJCC Anatomic Staging API (Simplified) | |
| -------------------------------------- | |
| Single-file FastAPI app that exposes TNM -> Stage mapping for: | |
| 1) Breast Cancer β AJCC 8 (Anatomic) | |
| 2) Lung Cancer (NSCLC) β AJCC 8 | |
| 3) Colon / Rectal Cancer β AJCC 8 | |
| 4) Stomach (Gastric Cancer) β AJCC 8 | |
| 5) Pancreatic Cancer β AJCC 8 | |
| 6) Liver Cancer (Hepatocellular Carcinoma) β AJCC 8 | |
| 7) Melanoma β AJCC 8 | |
| 8) Prostate Cancer β (Anatomic logic only) | |
| 9) Kidney (Renal Cell Carcinoma) | |
| 10) Bladder Cancer β AJCC 8 | |
| Plus a general fallback AJCC rule for any other / unknown cancer types. | |
| This is a simplified logic based on the mapping you provided, | |
| NOT a full official AJCC implementation. | |
| """ | |
| from typing import Optional, Any, Dict, List | |
| from fastapi import FastAPI | |
| from pydantic import BaseModel | |
| # --------------------------------------------------------------------- | |
| # Core TNM staging logic (previously in ajcc_staging.py) | |
| # --------------------------------------------------------------------- | |
| def normalize_tnm(value: Optional[str]) -> Optional[str]: | |
| if value is None: | |
| return None | |
| value = value.strip() | |
| if not value: | |
| return None | |
| return value.upper() | |
| def stage_cancer(cancer_type: str, T: str, N: str, M: str) -> str: | |
| """ | |
| Main entry point. | |
| cancer_type: string key identifying the cancer | |
| T, N, M: TNM strings like 'T1', 'T2B', 'N1', 'N1MI', 'M1A', etc. | |
| Returns: stage string like 'Stage IIA', 'Stage IIIC', 'Stage IVB', etc. | |
| """ | |
| cancer_type = cancer_type.strip().lower() | |
| T = normalize_tnm(T) | |
| N = normalize_tnm(N) | |
| M = normalize_tnm(M) | |
| if cancer_type == "breast": | |
| return stage_breast(T, N, M) | |
| elif cancer_type in ("lung", "nsclc", "non-small cell lung", "non small cell lung"): | |
| return stage_nsclc(T, N, M) | |
| elif cancer_type in ("colon", "rectal", "colorectal"): | |
| return stage_colorectal(T, N, M) | |
| elif cancer_type in ("stomach", "gastric"): | |
| return stage_stomach(T, N, M) | |
| elif cancer_type in ("pancreas", "pancreatic"): | |
| return stage_pancreas(T, N, M) | |
| elif cancer_type in ("liver", "hcc", "hepatocellular"): | |
| return stage_liver(T, N, M) | |
| elif cancer_type == "melanoma": | |
| return stage_melanoma(T, N, M) | |
| elif cancer_type == "prostate": | |
| return stage_prostate(T, N, M) | |
| elif cancer_type in ("kidney", "renal"): | |
| return stage_kidney(T, N, M) | |
| elif cancer_type == "bladder": | |
| return stage_bladder(T, N, M) | |
| else: | |
| return stage_general(T, N, M) | |
| # --------------------------------------------------------------------- | |
| # General fallback AJCC rule (for unknown cancer types) | |
| # --------------------------------------------------------------------- | |
| def stage_general(T: str, N: str, M: str) -> str: | |
| """ | |
| General AJCC mapping: | |
| IF T = Tis AND N = N0 AND M = M0 -> Stage 0 | |
| IF (T = T1 OR T = T2) AND N = N0 AND M = M0 -> Stage I | |
| IF (T = T3 AND N = N0 AND M = M0) | |
| OR ((T = T1 OR T = T2) AND N = N1 AND M = M0) -> Stage II | |
| IF ((T = T3 OR T = T4) AND (N = N1 OR N = N2 OR N = N3) AND M = M0) | |
| OR ((T = T1 OR T = T2 OR T = T3 OR T = T4) | |
| AND (N = N2 OR N = N3) AND M = M0) -> Stage III | |
| IF M = M1 -> Stage IV | |
| """ | |
| # Stage IV | |
| if M == "M1": | |
| return "Stage IV" | |
| # Stage 0 | |
| if T == "TIS" and N == "N0" and M == "M0": | |
| return "Stage 0" | |
| # Stage I | |
| if T in ("T1", "T2") and N == "N0" and M == "M0": | |
| return "Stage I" | |
| # Stage II | |
| if (T == "T3" and N == "N0" and M == "M0") or ( | |
| T in ("T1", "T2") and N == "N1" and M == "M0" | |
| ): | |
| return "Stage II" | |
| # Stage III | |
| if ( | |
| (T in ("T3", "T4") and N in ("N1", "N2", "N3") and M == "M0") | |
| or (T in ("T1", "T2", "T3", "T4") and N in ("N2", "N3") and M == "M0") | |
| ): | |
| return "Stage III" | |
| return "Stage not mapped (general)" | |
| # --------------------------------------------------------------------- | |
| # 1) Breast Cancer β AJCC 8 (Anatomic) | |
| # --------------------------------------------------------------------- | |
| def stage_breast(T: str, N: str, M: str) -> str: | |
| # Stage IV first | |
| if M == "M1": | |
| return "Stage IV" | |
| # Stage 0 | |
| if T == "TIS" and N == "N0" and M == "M0": | |
| return "Stage 0" | |
| # Stage IA | |
| if T == "T1" and N == "N0" and M == "M0": | |
| return "Stage IA" | |
| # Stage IB | |
| if (T in ("T0", "T1")) and N == "N1MI" and M == "M0": | |
| return "Stage IB" | |
| # Stage IIA | |
| if ( | |
| ((T in ("T0", "T1")) and N == "N1" and M == "M0") | |
| or (T == "T2" and N == "N0" and M == "M0") | |
| ): | |
| return "Stage IIA" | |
| # Stage IIB | |
| if ( | |
| (T == "T2" and N == "N1" and M == "M0") | |
| or (T == "T3" and N == "N0" and M == "M0") | |
| ): | |
| return "Stage IIB" | |
| # Stage IIIA | |
| if ( | |
| (T in ("T0", "T1", "T2") and N == "N2" and M == "M0") | |
| or (T == "T3" and N in ("N1", "N2") and M == "M0") | |
| ): | |
| return "Stage IIIA" | |
| # Stage IIIB | |
| if T == "T4" and M == "M0": | |
| return "Stage IIIB" | |
| # Stage IIIC | |
| if N == "N3" and M == "M0": | |
| return "Stage IIIC" | |
| return "Stage not mapped (breast)" | |
| # --------------------------------------------------------------------- | |
| # 2) Lung Cancer (NSCLC) β AJCC 8 | |
| # --------------------------------------------------------------------- | |
| def stage_nsclc(T: str, N: str, M: str) -> str: | |
| # Stage IV | |
| if M in ("M1A", "M1B"): | |
| return "Stage IVA" | |
| if M == "M1C": | |
| return "Stage IVB" | |
| if M and M != "M0": | |
| # any other M1 pattern | |
| return "Stage IV" | |
| # Stage 0 | |
| if T == "TIS" and N == "N0" and M == "M0": | |
| return "Stage 0" | |
| # Stage IA1 | |
| if T in ("T1A", "T1A(MI)", "T1AMI") and N == "N0" and M == "M0": | |
| return "Stage IA1" | |
| # Stage IA2 | |
| if T == "T1B" and N == "N0" and M == "M0": | |
| return "Stage IA2" | |
| # Stage IA3 | |
| if T == "T1C" and N == "N0" and M == "M0": | |
| return "Stage IA3" | |
| # Stage IB | |
| if T == "T2A" and N == "N0" and M == "M0": | |
| return "Stage IB" | |
| # Stage IIA | |
| if T == "T2B" and N == "N0" and M == "M0": | |
| return "Stage IIA" | |
| # Stage IIB | |
| if (T == "T3" and N == "N0" and M == "M0") or ( | |
| T in ("T1", "T2") and N == "N1" and M == "M0" | |
| ): | |
| return "Stage IIB" | |
| # Stage IIIA | |
| if ( | |
| T in ("T1", "T2") and N == "N2" and M == "M0" | |
| ) or (T == "T3" and N in ("N1", "N2") and M == "M0"): | |
| return "Stage IIIA" | |
| # Stage IIIB | |
| if ( | |
| T in ("T3", "T4") and N == "N2" and M == "M0" | |
| ) or (T in ("T1", "T2") and N == "N3" and M == "M0"): | |
| return "Stage IIIB" | |
| # Stage IIIC | |
| if T in ("T3", "T4") and N == "N3" and M == "M0": | |
| return "Stage IIIC" | |
| return "Stage not mapped (NSCLC)" | |
| # --------------------------------------------------------------------- | |
| # 3) Colon / Rectal Cancer β AJCC 8 | |
| # --------------------------------------------------------------------- | |
| def stage_colorectal(T: str, N: str, M: str) -> str: | |
| # Stage IV | |
| if M == "M1A": | |
| return "Stage IVA" | |
| if M == "M1B": | |
| return "Stage IVB" | |
| if M == "M1C": | |
| return "Stage IVC" | |
| if M and M != "M0": | |
| return "Stage IV" | |
| # Stage 0 | |
| if T == "TIS" and N == "N0" and M == "M0": | |
| return "Stage 0" | |
| # Stage I | |
| if T in ("T1", "T2") and N == "N0" and M == "M0": | |
| return "Stage I" | |
| # Stage II | |
| if T == "T3" and N == "N0" and M == "M0": | |
| return "Stage IIA" | |
| if T == "T4A" and N == "N0" and M == "M0": | |
| return "Stage IIB" | |
| if T == "T4B" and N == "N0" and M == "M0": | |
| return "Stage IIC" | |
| # Stage IIIA | |
| if ( | |
| T in ("T1", "T2") and N in ("N1", "N1C") and M == "M0" | |
| ) or (T == "T1" and N == "N2A" and M == "M0"): | |
| return "Stage IIIA" | |
| # Stage IIIB | |
| if ( | |
| (T in ("T3", "T4A") and N in ("N1", "N1C") and M == "M0") | |
| or (T in ("T2", "T3") and N == "N2A" and M == "M0") | |
| or (T in ("T1", "T2") and N == "N2B" and M == "M0") | |
| ): | |
| return "Stage IIIB" | |
| # Stage IIIC | |
| if (T in ("T4A", "T4B") and N in ("N2", "N2A", "N2B") and M == "M0"): | |
| return "Stage IIIC" | |
| return "Stage not mapped (colorectal)" | |
| # --------------------------------------------------------------------- | |
| # 4) Stomach (Gastric Cancer) β AJCC 8 | |
| # --------------------------------------------------------------------- | |
| def stage_stomach(T: str, N: str, M: str) -> str: | |
| # Stage IV | |
| if M and M != "M0": | |
| return "Stage IV" | |
| # Stage I | |
| if (T == "T1" and N in ("N0", "N1")) or (T == "T2" and N == "N0"): | |
| return "Stage I" | |
| # Stage II | |
| if (T == "T1" and N == "N2") or (T == "T2" and N == "N1") or ( | |
| T == "T3" and N == "N0" | |
| ): | |
| return "Stage II" | |
| # Stage III | |
| if (T == "T2" and N == "N2") or (T == "T3" and N in ("N1", "N2")) or ( | |
| T == "T4A" and N in ("N0", "N1", "N2") | |
| ) or (T == "T4B" and N in ("N0", "N1")): | |
| return "Stage III" | |
| return "Stage not mapped (stomach)" | |
| # --------------------------------------------------------------------- | |
| # 5) Pancreatic Cancer β AJCC 8 | |
| # --------------------------------------------------------------------- | |
| def stage_pancreas(T: str, N: str, M: str) -> str: | |
| # Stage IV | |
| if M and M != "M0": | |
| return "Stage IV" | |
| # Stage I | |
| if T in ("T1", "T2") and N == "N0" and M == "M0": | |
| return "Stage I" | |
| # Stage II | |
| if (T == "T3" and N == "N0" and M == "M0") or ( | |
| T in ("T1", "T2", "T3") and N == "N1" and M == "M0" | |
| ): | |
| return "Stage II" | |
| # Stage III | |
| # mapping given: T4 (unresectable arterial invasion) OR N2 (β₯4 nodes), with M0 | |
| if (T == "T4" and M == "M0") or (N == "N2" and M == "M0"): | |
| return "Stage III" | |
| return "Stage not mapped (pancreas)" | |
| # --------------------------------------------------------------------- | |
| # 6) Liver Cancer (Hepatocellular Carcinoma) β AJCC 8 | |
| # --------------------------------------------------------------------- | |
| def stage_liver(T: str, N: str, M: str) -> str: | |
| # Stage IV subdivided: | |
| if M and M != "M0": | |
| return "Stage IVB" | |
| if N == "N1" and M == "M0": | |
| return "Stage IVA" | |
| # Stage I | |
| if T == "T1" and N == "N0" and M == "M0": | |
| return "Stage I" | |
| # Stage II | |
| if T == "T2" and N == "N0" and M == "M0": | |
| return "Stage II" | |
| # Stage III | |
| if (T == "T3" and N == "N0" and M == "M0") or ( | |
| T == "T4" and N == "N0" and M == "M0" | |
| ): | |
| return "Stage III" | |
| return "Stage not mapped (liver)" | |
| # --------------------------------------------------------------------- | |
| # 7) Melanoma β AJCC 8 | |
| # --------------------------------------------------------------------- | |
| def stage_melanoma(T: str, N: str, M: str) -> str: | |
| # Stage 0 | |
| if T == "TIS" and N == "N0" and M == "M0": | |
| return "Stage 0" | |
| # Stage IV | |
| if M in ("M1A", "M1B", "M1C", "M1D") or (M and M != "M0"): | |
| if M == "M1A": | |
| return "Stage IV (M1a)" | |
| if M == "M1B": | |
| return "Stage IV (M1b)" | |
| if M == "M1C": | |
| return "Stage IV (M1c)" | |
| if M == "M1D": | |
| return "Stage IV (M1d)" | |
| return "Stage IV" | |
| # Stage I | |
| if T == "T1A" and N == "N0" and M == "M0": | |
| return "Stage IA" | |
| if T in ("T1B", "T2A") and N == "N0" and M == "M0": | |
| return "Stage IB" | |
| # Stage II | |
| if T in ("T2B", "T3A") and N == "N0" and M == "M0": | |
| return "Stage IIA" | |
| if T in ("T3B", "T4A") and N == "N0" and M == "M0": | |
| return "Stage IIB" | |
| if T == "T4B" and N == "N0" and M == "M0": | |
| return "Stage IIC" | |
| # Stage III | |
| if N in ("N1", "N2", "N3") and M == "M0": | |
| return "Stage III" | |
| return "Stage not mapped (melanoma)" | |
| # --------------------------------------------------------------------- | |
| # 8) Prostate Cancer β Anatomic Logic | |
| # --------------------------------------------------------------------- | |
| def stage_prostate(T: str, N: str, M: str) -> str: | |
| # Stage IV | |
| if M and M != "M0": | |
| return "Stage IVB" | |
| if N == "N1" and M == "M0": | |
| return "Stage IVA" | |
| # Stage I | |
| if T in ("T1", "T2A") and N == "N0" and M == "M0": | |
| return "Stage I" | |
| # Stage II (no A/B/C separation here β depends on grade/PSA in real AJCC) | |
| if T in ("T2B", "T2C") and N == "N0" and M == "M0": | |
| return "Stage II" | |
| # Stage IIIA | |
| if T == "T3A" and N == "N0" and M == "M0": | |
| return "Stage IIIA" | |
| # Stage IIIB | |
| if T in ("T3B", "T4") and N == "N0" and M == "M0": | |
| return "Stage IIIB" | |
| return "Stage not mapped (prostate)" | |
| # --------------------------------------------------------------------- | |
| # 9) Kidney (Renal Cell Carcinoma) | |
| # --------------------------------------------------------------------- | |
| def stage_kidney(T: str, N: str, M: str) -> str: | |
| # Stage IV | |
| if T == "T4" and M == "M0": | |
| return "Stage IV" | |
| if M and M != "M0": | |
| return "Stage IV" | |
| # Stage I | |
| if T == "T1" and N == "N0" and M == "M0": | |
| return "Stage I" | |
| # Stage II | |
| if T == "T2" and N == "N0" and M == "M0": | |
| return "Stage II" | |
| # Stage III | |
| if (T == "T3" and N == "N0" and M == "M0") or (N == "N1" and M == "M0"): | |
| return "Stage III" | |
| return "Stage not mapped (kidney)" | |
| # --------------------------------------------------------------------- | |
| # 10) Bladder Cancer β AJCC 8 | |
| # --------------------------------------------------------------------- | |
| def stage_bladder(T: str, N: str, M: str) -> str: | |
| # Stage IV | |
| if T == "T4B" and M == "M0": | |
| return "Stage IV" | |
| if N in ("N1", "N2", "N3") and M == "M0": | |
| return "Stage IV" | |
| if M and M != "M0": | |
| return "Stage IV" | |
| # Stage 0 | |
| if T in ("TA", "TIS") and N == "N0" and M == "M0": | |
| return "Stage 0" | |
| # Stage I | |
| if T == "T1" and N == "N0" and M == "M0": | |
| return "Stage I" | |
| # Stage II | |
| if T == "T2" and N == "N0" and M == "M0": | |
| return "Stage II" | |
| # Stage III | |
| if T in ("T3", "T4A") and N == "N0" and M == "M0": | |
| return "Stage III" | |
| return "Stage not mapped (bladder)" | |
| # --------------------------------------------------------------------- | |
| # FastAPI Layer | |
| # --------------------------------------------------------------------- | |
| app = FastAPI( | |
| title="AJCC TNM Staging API", | |
| description=( | |
| "Simplified AJCC anatomic staging API for multiple cancer types.\n" | |
| "Not a full official AJCC implementation; uses custom mapping logic." | |
| ), | |
| version="1.0.0", | |
| ) | |
| class StagingRequest(BaseModel): | |
| cancer_type: str | |
| T: str | |
| N: str | |
| M: str | |
| class StagingResponse(BaseModel): | |
| cancer_type: str | |
| T: str | |
| N: str | |
| M: str | |
| stage: str | |
| def health_check(): | |
| return {"status": "ok"} | |
| def get_stage(body: StagingRequest): | |
| """ | |
| Return AJCC stage group based on cancer type and TNM. | |
| """ | |
| stage = stage_cancer( | |
| cancer_type=body.cancer_type, | |
| T=body.T, | |
| N=body.N, | |
| M=body.M, | |
| ) | |
| return StagingResponse( | |
| cancer_type=body.cancer_type, | |
| T=body.T, | |
| N=body.N, | |
| M=body.M, | |
| stage=stage, | |
| ) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run("ajcc_api:app", host="0.0.0.0", port=8000, reload=True) | |