From f66bc92a1b38bfb15f66d208d14c7fb0c4bb68b8 Mon Sep 17 00:00:00 2001 From: "Juan E. Arango Ossa" Date: Tue, 19 Dec 2023 13:34:38 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=92=20add=20redoc=20api=20reference?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/redoc-build.yml | 19 ++ mds-foundation-logo.svg | 16 + openapi.yml | 437 ++++++++++++++++++++++++++++ package.json | 3 +- redoc-static.html | 466 ++++++++++++++++++++++++++++++ src/server.ts | 13 +- src/utils/validation.ts | 2 - 7 files changed, 951 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/redoc-build.yml create mode 100644 mds-foundation-logo.svg create mode 100644 openapi.yml create mode 100644 redoc-static.html diff --git a/.github/workflows/redoc-build.yml b/.github/workflows/redoc-build.yml new file mode 100644 index 0000000..4ac5637 --- /dev/null +++ b/.github/workflows/redoc-build.yml @@ -0,0 +1,19 @@ +name: Generate Redoc Documentation + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: 14 + + - name: Generate documentation + run: npx @redocly/cli build-docs openapi.yml \ No newline at end of file diff --git a/mds-foundation-logo.svg b/mds-foundation-logo.svg new file mode 100644 index 0000000..d1c68dd --- /dev/null +++ b/mds-foundation-logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/openapi.yml b/openapi.yml new file mode 100644 index 0000000..cd3c17d --- /dev/null +++ b/openapi.yml @@ -0,0 +1,437 @@ +openapi: 3.0.0 +info: + title: IPSS-M Api Documentation + version: 1.0.0 + x-logo: + url: 'https://raw.githubusercontent.com/papaemmelab/ipssm-api/mds-foundation-logo.svg' + backgroundColor: '#FFFFFF' + altText: 'MDS Foundation' +paths: + /ipssm: + post: + summary: IPSS-M Risk Score Calculation + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/IpssmSchema' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/IpssmResponse' + '500': + description: An error occurred while calculating IPSS-M risk score + /ipssr: + post: + summary: IPSS-R Risk Score Calculation + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/IpssrSchema' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/IpssrResponse' + '500': + description: An error occurred while calculating IPSS-R risk score +components: + schemas: + Clinical: + type: object + required: + - BM_BLAST + - HB + - PLT + properties: + # Clinical + BM_BLAST: + type: number + minimum: 0 + maximum: 30 + description: "Bone marrow blast percentage (%)" + HB: + type: number + minimum: 4 + maximum: 20 + description: "Hemoglobin (g/dL)" + PLT: + type: number + minimum: 0 + maximum: 2000 + description: "Platelet count (x10^9/L)" + IpssrSchema: + allOf: + - $ref: '#/components/schemas/Clinical' + - type: object + required: + - ANC + - CYTO_IPSSR + properties: + ANC: + type: number + minimum: 0 + maximum: 15 + description: "Absolute neutrophil count (x10^9/L)" + AGE: + type: number + minimum: 18 + maximum: 120 + description: "Age (years)" + CYTO_IPSSR: + type: string + enum: ["Very Good", "Good", "Intermediate", "Poor", "Very Poor"] + description: "Cytogenetic risk group" + IpssmSchema: + allOf: + - $ref: '#/components/schemas/Clinical' + - type: object + required: + - CYTO_IPSSR + properties: + # Cytogenetics + del5q: + type: number + enum: [0, 1] + description: "Deletion of chromosome 5q" + default: 0 + del7_7q: + type: number + enum: [0, 1] + description: "Deletion of chromosome 7 or monosomy 7" + default: 0 + del17_17p: + type: number + enum: [0, 1] + description: "Deletion of chromosome 17p" + default: 0 + complex: + type: number + enum: [0, 1] + description: "Complex karyotype (>= 3 abnormalities)" + default: 0 + CYTO_IPSSR: + type: string + enum: ["Very Good", "Good", "Intermediate", "Poor", "Very Poor"] + description: "Cytogenetic risk group" + # Molecular + TP53mut: + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["0", "1", "2 or more"] + description: "Number of TP53 mutations" + default: 0 + TP53maxvaf: + type: number + minimum: 0 + maximum: 100 + description: "Maximum variant allele frequency of TP53 mutation" + TP53loh: + description: "Loss of heterozygosity at TP53 locus" + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + MLL_PTD: + description: "MLL (KMT2A) PTD" + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + FLT3: + description: "FLT3 ITD or TKD" + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + # Main Genes + ASXL1: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + CBL: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + DNMT3A: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + ETV6: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + EZH2: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + IDH2: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + KRAS: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + NPM1: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + NRAS: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + RUNX1: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + SF3B1: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + SRSF2: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + U2AF1: + description: Gene (Individual Weight) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + # Residual Genes + BCOR: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + BCORL1: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + CEBPA: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + ETNK1: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + GATA2: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + GNB1: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + IDH1: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + NF1: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + PHF6: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + PPM1D: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + PRPF8: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + PTPN11: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + SETBP1: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + STAG2: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + WT1: + description: Gene (Number of Residual Mutations) + default: 0 + oneOf: + - type: number + enum: [0, 1] + - type: string + enum: ["NA"] + Risk: + type: object + properties: + riskScore: + type: number + description: IPSS-M Risk Score + riskCat: + type: string + description: IPSS-M Risk Category + enum: ["Very Low", "Low", "Moderate Low", "Moderate High", "High", "Very High"] + IpssmResponse: + type: object + properties: + patient: + $ref: '#/components/schemas/IpssmSchema' + ipssm: + type: object + properties: + means: + description: "IPSS-M risk score (mean if any NA value was provided)" + $ref: '#/components/schemas/Risk' + worst: + description: "IPSS-M risk score for worst case scenario of NA cases" + $ref: '#/components/schemas/Risk' + best: + description: "IPSS-M risk score for best case scenario of NA cases" + $ref: '#/components/schemas/Risk' + IpssrResponse: + type: object + properties: + patient: + $ref: '#/components/schemas/IpssrSchema' + ipssr: + type: object + properties: + IPSSR_CAT: + type: string + enum: ["Very Low", "Low", "Intermediate", "High", "Very High"] + description: "IPSS-R Risk Category" + IPSSR_SCORE: + type: number + description: "IPSS-R Risk Score" + IPSSRA_CAT: + type: string + enum: ["Very Low", "Low", "Intermediate", "High", "Very High"] + description: "IPSS-R Risk Category (Age-Adjusted)" + IPSSRA_SCORE: + type: number + description: "IPSS-R Risk Score (Age-Adjusted)" \ No newline at end of file diff --git a/package.json b/package.json index fcb062e..7b0e783 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "scripts": { "test": "vitest", "build": "tsc", - "serve": "tsc && node dist/server.js" + "serve": "tsc && node dist/server.js", + "docs": "npx @redocly/cli build-docs openapi.yml" }, "repository": { "type": "git", diff --git a/redoc-static.html b/redoc-static.html new file mode 100644 index 0000000..abb93af --- /dev/null +++ b/redoc-static.html @@ -0,0 +1,466 @@ + + + + + + IPSS-M Api Documentation + + + + + + + + + +

IPSS-M Api Documentation (1.0.0)

Download OpenAPI specification:Download

IPSS-M Risk Score Calculation

Request Body schema: application/json
required
BM_BLAST
required
number [ 0 .. 30 ]

Bone marrow blast percentage (%)

+
HB
required
number [ 4 .. 20 ]

Hemoglobin (g/dL)

+
PLT
required
number [ 0 .. 2000 ]

Platelet count (x10^9/L)

+
del5q
number
Default: 0
Enum: 0 1

Deletion of chromosome 5q

+
del7_7q
number
Default: 0
Enum: 0 1

Deletion of chromosome 7 or monosomy 7

+
del17_17p
number
Default: 0
Enum: 0 1

Deletion of chromosome 17p

+
complex
number
Default: 0
Enum: 0 1

Complex karyotype (>= 3 abnormalities)

+
CYTO_IPSSR
required
string
Enum: "Very Good" "Good" "Intermediate" "Poor" "Very Poor"

Cytogenetic risk group

+
number or string
Default: 0

Number of TP53 mutations

+
TP53maxvaf
number [ 0 .. 100 ]

Maximum variant allele frequency of TP53 mutation

+
number or string
Default: 0

Loss of heterozygosity at TP53 locus

+
number or string
Default: 0

MLL (KMT2A) PTD

+
number or string
Default: 0

FLT3 ITD or TKD

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Individual Weight)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+
number or string
Default: 0

Gene (Number of Residual Mutations)

+

Responses

Request samples

Content type
application/json
{
  • "BM_BLAST": 30,
  • "HB": 4,
  • "PLT": 2000,
  • "del5q": 0,
  • "del7_7q": 0,
  • "del17_17p": 0,
  • "complex": 0,
  • "CYTO_IPSSR": "Very Good",
  • "TP53mut": 0,
  • "TP53maxvaf": 100,
  • "TP53loh": 0,
  • "MLL_PTD": 0,
  • "FLT3": 0,
  • "ASXL1": 0,
  • "CBL": 0,
  • "DNMT3A": 0,
  • "ETV6": 0,
  • "EZH2": 0,
  • "IDH2": 0,
  • "KRAS": 0,
  • "NPM1": 0,
  • "NRAS": 0,
  • "RUNX1": 0,
  • "SF3B1": 0,
  • "SRSF2": 0,
  • "U2AF1": 0,
  • "BCOR": 0,
  • "BCORL1": 0,
  • "CEBPA": 0,
  • "ETNK1": 0,
  • "GATA2": 0,
  • "GNB1": 0,
  • "IDH1": 0,
  • "NF1": 0,
  • "PHF6": 0,
  • "PPM1D": 0,
  • "PRPF8": 0,
  • "PTPN11": 0,
  • "SETBP1": 0,
  • "STAG2": 0,
  • "WT1": 0
}

Response samples

Content type
application/json
{
  • "patient": {
    },
  • "ipssm": {
    }
}

IPSS-R Risk Score Calculation

Request Body schema: application/json
required
BM_BLAST
required
number [ 0 .. 30 ]

Bone marrow blast percentage (%)

+
HB
required
number [ 4 .. 20 ]

Hemoglobin (g/dL)

+
PLT
required
number [ 0 .. 2000 ]

Platelet count (x10^9/L)

+
ANC
required
number [ 0 .. 15 ]

Absolute neutrophil count (x10^9/L)

+
AGE
number [ 18 .. 120 ]

Age (years)

+
CYTO_IPSSR
required
string
Enum: "Very Good" "Good" "Intermediate" "Poor" "Very Poor"

Cytogenetic risk group

+

Responses

Request samples

Content type
application/json
{
  • "BM_BLAST": 30,
  • "HB": 4,
  • "PLT": 2000,
  • "ANC": 15,
  • "AGE": 18,
  • "CYTO_IPSSR": "Very Good"
}

Response samples

Content type
application/json
{
  • "patient": {
    },
  • "ipssr": {
    }
}
+ + + + diff --git a/src/server.ts b/src/server.ts index 72fd68d..4ac0ad4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,4 +1,5 @@ import express, { Request, Response } from 'express' +import path from 'path' import { annotateFile, ipssm, ipssr } from './index' import { validatePatientForIpssm, validatePatientForIpssr } from './utils/validation' import serverless from 'serverless-http' @@ -15,6 +16,11 @@ app.use((req, _res, next) => { next() }) +// Serve the redoc swagger documentation +app.get('/', (_req, res) => { + res.sendFile(path.join(__dirname, '..', 'redoc-static.html')); +}); + // Endpoint for Ipssm app.post('/ipssm', validatePatientForIpssm, async (req: Request, res: Response) => { const patient = req.body @@ -37,7 +43,6 @@ app.post('/ipssm', validatePatientForIpssm, async (req: Request, res: Response) // Endpoint for Ipssr app.post('/ipssr', validatePatientForIpssr, async (req: Request, res: Response) => { const patient = req.body - try { const ipssrResult = ipssr({ hb: patient.HB, @@ -47,7 +52,11 @@ app.post('/ipssr', validatePatientForIpssr, async (req: Request, res: Response) cytoIpssr: patient.CYTO_IPSSR, age: patient.AGE, }) - res.json(ipssrResult) + const response = { + patient: patient, + ipssr: ipssrResult, + } + res.json(response) } catch (error) { console.log((error as Error).stack) res.status(500).json({ diff --git a/src/utils/validation.ts b/src/utils/validation.ts index db54249..3dd115b 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -77,8 +77,6 @@ export const validatePatientForIpssr = (req: Request, res: Response, next: NextF export const validatePatientForIpssm = (req: Request, res: Response, next: NextFunction) => { const { value, error } = patientForIpssmSchema.validate(req.body, { abortEarly: false }) - console.log(value) - console.log(error) req.body = value handleResponse(error, res, next) }