renamed
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking"
|
||||
],
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/semi": ["error"],
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/restrict-template-expressions": "off",
|
||||
"@typescript-eslint/restrict-plus-operands": "off",
|
||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{ "argsIgnorePattern": "^_" }
|
||||
],
|
||||
"no-case-declarations": "off"
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Diagnose } from "../src/types";
|
||||
const data: Diagnose[] = [
|
||||
{
|
||||
code: "M24.2",
|
||||
name: "Disorder of ligament",
|
||||
latin: "Morbositas ligamenti",
|
||||
},
|
||||
{
|
||||
code: "M51.2",
|
||||
name: "Other specified intervertebral disc displacement",
|
||||
latin: "Alia dislocatio disci intervertebralis specificata",
|
||||
},
|
||||
{
|
||||
code: "S03.5",
|
||||
name: "Sprain and strain of joints and ligaments of other and unspecified parts of head",
|
||||
latin:
|
||||
"Distorsio et/sive distensio articulationum et/sive ligamentorum partium aliarum sive non specificatarum capitis",
|
||||
},
|
||||
{
|
||||
code: "J10.1",
|
||||
name: "Influenza with other respiratory manifestations, other influenza virus codeentified",
|
||||
latin:
|
||||
"Influenza cum aliis manifestationibus respiratoriis ab agente virali codeentificato",
|
||||
},
|
||||
{
|
||||
code: "J06.9",
|
||||
name: "Acute upper respiratory infection, unspecified",
|
||||
latin: "Infectio acuta respiratoria superior non specificata",
|
||||
},
|
||||
{
|
||||
code: "Z57.1",
|
||||
name: "Occupational exposure to radiation",
|
||||
},
|
||||
{
|
||||
code: "N30.0",
|
||||
name: "Acute cystitis",
|
||||
latin: "Cystitis acuta",
|
||||
},
|
||||
{
|
||||
code: "H54.7",
|
||||
name: "Unspecified visual loss",
|
||||
latin: "Amblyopia NAS",
|
||||
},
|
||||
{
|
||||
code: "J03.0",
|
||||
name: "Streptococcal tonsillitis",
|
||||
latin: "Tonsillitis (palatina) streptococcica",
|
||||
},
|
||||
{
|
||||
code: "L60.1",
|
||||
name: "Onycholysis",
|
||||
latin: "Onycholysis",
|
||||
},
|
||||
{
|
||||
code: "Z74.3",
|
||||
name: "Need for continuous supervision",
|
||||
},
|
||||
{
|
||||
code: "L20",
|
||||
name: "Atopic dermatitis",
|
||||
latin: "Atopic dermatitis",
|
||||
},
|
||||
{
|
||||
code: "F43.2",
|
||||
name: "Adjustment disorders",
|
||||
latin: "Perturbationes adaptationis",
|
||||
},
|
||||
{
|
||||
code: "S62.5",
|
||||
name: "Fracture of thumb",
|
||||
latin: "Fractura [ossis/ossium] pollicis",
|
||||
},
|
||||
{
|
||||
code: "H35.29",
|
||||
name: "Other proliferative retinopathy",
|
||||
latin: "Alia retinopathia proliferativa",
|
||||
},
|
||||
];
|
||||
|
||||
export default data;
|
||||
@@ -0,0 +1,116 @@
|
||||
import { Patient, Gender } from '../src/types';
|
||||
|
||||
const patients: Patient[] = [
|
||||
{
|
||||
id: 'd2773336-f723-11e9-8f0b-362b9e155667',
|
||||
name: 'John McClane',
|
||||
dateOfBirth: '1986-07-09',
|
||||
ssn: '090786-122X',
|
||||
gender: Gender.Male,
|
||||
occupation: 'New york city cop',
|
||||
entries: [
|
||||
{
|
||||
id: 'd811e46d-70b3-4d90-b090-4535c7cf8fb1',
|
||||
date: '2015-01-02',
|
||||
type: 'Hospital',
|
||||
specialist: 'MD House',
|
||||
diagnosisCodes: ['S62.5'],
|
||||
description:
|
||||
"Healing time appr. 2 weeks. patient doesn't remember how he got the injury.",
|
||||
discharge: {
|
||||
date: '2015-01-16',
|
||||
criteria: 'Thumb has healed.',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'd2773598-f723-11e9-8f0b-362b9e155667',
|
||||
name: 'Martin Riggs',
|
||||
dateOfBirth: '1979-01-30',
|
||||
ssn: '300179-777A',
|
||||
gender: Gender.Male,
|
||||
occupation: 'Cop',
|
||||
entries: [
|
||||
{
|
||||
id: 'fcd59fa6-c4b4-4fec-ac4d-df4fe1f85f62',
|
||||
date: '2019-08-05',
|
||||
type: 'OccupationalHealthcare',
|
||||
specialist: 'MD House',
|
||||
employerName: 'HyPD',
|
||||
diagnosisCodes: ['Z57.1', 'Z74.3', 'M51.2'],
|
||||
description:
|
||||
'Patient mistakenly found himself in a nuclear plant waste site without protection gear. Very minor radiation poisoning. ',
|
||||
sickLeave: {
|
||||
startDate: '2019-08-05',
|
||||
endDate: '2019-08-28',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'd27736ec-f723-11e9-8f0b-362b9e155667',
|
||||
name: 'Hans Gruber',
|
||||
dateOfBirth: '1970-04-25',
|
||||
ssn: '250470-555L',
|
||||
gender: Gender.Other,
|
||||
occupation: 'Technician',
|
||||
entries: [],
|
||||
},
|
||||
{
|
||||
id: 'd2773822-f723-11e9-8f0b-362b9e155667',
|
||||
name: 'Dana Scully',
|
||||
dateOfBirth: '1974-01-05',
|
||||
ssn: '050174-432N',
|
||||
gender: Gender.Female,
|
||||
occupation: 'Forensic Pathologist',
|
||||
entries: [
|
||||
{
|
||||
id: 'b4f4eca1-2aa7-4b13-9a18-4a5535c3c8da',
|
||||
date: '2019-10-20',
|
||||
specialist: 'MD House',
|
||||
type: 'HealthCheck',
|
||||
description: 'Yearly control visit. Cholesterol levels back to normal.',
|
||||
healthCheckRating: 0,
|
||||
},
|
||||
{
|
||||
id: 'fcd59fa6-c4b4-4fec-ac4d-df4fe1f85f62',
|
||||
date: '2019-09-10',
|
||||
specialist: 'MD House',
|
||||
type: 'OccupationalHealthcare',
|
||||
employerName: 'FBI',
|
||||
description: 'Prescriptions renewed.',
|
||||
},
|
||||
{
|
||||
id: '37be178f-a432-4ba4-aac2-f86810e36a15',
|
||||
date: '2018-10-05',
|
||||
specialist: 'MD House',
|
||||
type: 'HealthCheck',
|
||||
description:
|
||||
'Yearly control visit. Due to high cholesterol levels recommended to eat more vegetables.',
|
||||
healthCheckRating: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'd2773c6e-f723-11e9-8f0b-362b9e155667',
|
||||
name: 'Matti Luukkainen',
|
||||
dateOfBirth: '1971-04-09',
|
||||
ssn: '090471-8890',
|
||||
gender: Gender.Male,
|
||||
occupation: 'Digital evangelist',
|
||||
entries: [
|
||||
{
|
||||
id: '54a8746e-34c4-4cf4-bf72-bfecd039be9a',
|
||||
date: '2019-05-01',
|
||||
specialist: 'Dr Byte House',
|
||||
type: 'HealthCheck',
|
||||
description: 'Digital overdose, very bytestatic. Otherwise healthy.',
|
||||
healthCheckRating: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default patients;
|
||||
|
||||
+4996
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"tsc": "tsc",
|
||||
"dev": "ts-node-dev ./src/index.ts",
|
||||
"lint": "eslint --ext .ts ."
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||
"@typescript-eslint/parser": "^5.60.1",
|
||||
"eslint": "^8.43.0",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^5.1.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"uuid": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import express from "express";
|
||||
import diagnoses from "../data/diagnoses";
|
||||
import {
|
||||
addPatient,
|
||||
addEntry,
|
||||
getPatient,
|
||||
getAllPatients,
|
||||
} from "./services/patients";
|
||||
import { toNewPatient,toNewEntry } from "./utils";
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.get("/api/ping", (_req, res) => {
|
||||
res.send("pong");
|
||||
});
|
||||
app.get("/api/diagnoses", (_req, res) => {
|
||||
res.json(diagnoses);
|
||||
});
|
||||
app.get("/api/patients", (_req, res) => {
|
||||
res.json(getAllPatients());
|
||||
});
|
||||
app.get("/api/patients/:id", (req, res) => {
|
||||
res.json(getPatient(req.params.id));
|
||||
});
|
||||
app.post("/api/patients/:id/entries", (req, res) => {
|
||||
const newEntry = toNewEntry(req.body);
|
||||
res.json(addEntry(newEntry, req.params.id));
|
||||
});
|
||||
app.post("/api/patients", (req, res) => {
|
||||
const newPatient = toNewPatient(req.body);
|
||||
res.json(addPatient(newPatient));
|
||||
});
|
||||
|
||||
const PORT = 3001;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running at port${PORT}`);
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import patients from "../../data/patients";
|
||||
import { Patient, NewPatient, NonSensitivePatient, Entry } from "../types";
|
||||
import { v1 as uuid } from "uuid";
|
||||
|
||||
export const getAllPatients = (): NonSensitivePatient[] =>
|
||||
patients.map(({ id, name, dateOfBirth, gender, occupation, entries }) => {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
dateOfBirth,
|
||||
gender,
|
||||
occupation,
|
||||
entries,
|
||||
};
|
||||
});
|
||||
|
||||
export const addPatient = (patient: NewPatient): Patient => {
|
||||
const newPatient = { ...patient, id: uuid() };
|
||||
patients.push(newPatient);
|
||||
return newPatient;
|
||||
};
|
||||
|
||||
export const getPatient = (id: string): NonSensitivePatient => {
|
||||
const patient = patients.find((el) => el.id === id);
|
||||
if (patient)
|
||||
return {
|
||||
id: patient.id,
|
||||
name: patient.name,
|
||||
dateOfBirth: patient.dateOfBirth,
|
||||
gender: patient.gender,
|
||||
occupation: patient.occupation,
|
||||
entries: patient.entries,
|
||||
};
|
||||
throw new Error("patient not found");
|
||||
};
|
||||
|
||||
export const addEntry = (entry:Omit<Entry,'id'>, id: string): NonSensitivePatient => {
|
||||
const patient = patients.find((el) => el.id === id);
|
||||
if (patient) {
|
||||
const newEntry = {...entry,id:uuid()} as Entry
|
||||
const entries = patient.entries.concat(newEntry);
|
||||
const updatedPatient = { ...patient, entries };
|
||||
return {
|
||||
id: updatedPatient.id,
|
||||
name: updatedPatient.name,
|
||||
dateOfBirth: updatedPatient.dateOfBirth,
|
||||
gender: updatedPatient.gender,
|
||||
occupation: updatedPatient.occupation,
|
||||
entries: updatedPatient.entries,
|
||||
};
|
||||
}
|
||||
throw new Error("patient not found");
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
export interface Diagnose {
|
||||
code: string;
|
||||
name: string;
|
||||
latin?: string;
|
||||
}
|
||||
interface HospitalEntry extends BaseEntry {
|
||||
type: "Hospital";
|
||||
diagnosisCodes: string[];
|
||||
discharge: {
|
||||
date: string;
|
||||
criteria: string;
|
||||
};
|
||||
}
|
||||
interface OccupationalHealthcareEntry extends BaseEntry {
|
||||
type: "OccupationalHealthcare";
|
||||
employerName: string;
|
||||
diagnosisCodes?: string[];
|
||||
sickLeave?: {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
};
|
||||
}
|
||||
interface HealthCheck extends BaseEntry {
|
||||
type: "HealthCheck";
|
||||
employerName?: string;
|
||||
healthCheckRating: number;
|
||||
}
|
||||
interface BaseEntry {
|
||||
id: string;
|
||||
date: string;
|
||||
specialist: string;
|
||||
description: string;
|
||||
}
|
||||
export type Entry = HospitalEntry | OccupationalHealthcareEntry | HealthCheck;
|
||||
|
||||
export interface Patient {
|
||||
id: string;
|
||||
dateOfBirth: string;
|
||||
gender: string;
|
||||
occupation: string;
|
||||
name: string;
|
||||
ssn: string;
|
||||
entries: Entry[];
|
||||
}
|
||||
|
||||
export type NonSensitivePatient = Omit<Patient, "ssn">;
|
||||
|
||||
export type NewPatient = Omit<Patient, "id">;
|
||||
|
||||
export enum Gender {
|
||||
Male = "male",
|
||||
Female = "female",
|
||||
Other = "other",
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import { Entry, Gender, NewPatient } from "./types";
|
||||
|
||||
export const toNewPatient = (object: unknown): NewPatient => {
|
||||
if (!object || typeof object !== "object")
|
||||
throw new Error("Incorrect or request body");
|
||||
if (
|
||||
"name" in object &&
|
||||
"gender" in object &&
|
||||
"dateOfBirth" in object &&
|
||||
"occupation" in object &&
|
||||
"ssn" in object
|
||||
) {
|
||||
const newPatient: NewPatient = {
|
||||
name: parseString(object.name, "Incorrect or missing name"),
|
||||
gender: parseGender(object.gender),
|
||||
dateOfBirth: parseDate(object.dateOfBirth),
|
||||
occupation: parseString(object.occupation, "Incorrect or missing ssn"),
|
||||
ssn: parseString(object.name, "Incorrect or missing ssn"),
|
||||
entries: [],
|
||||
};
|
||||
return newPatient;
|
||||
}
|
||||
throw new Error("Incorrect data some fields are missing");
|
||||
};
|
||||
|
||||
const parseDate = (date: unknown): string => {
|
||||
if (!date || !isString(date) || !isDate(date))
|
||||
throw new Error("Incorrect or missing date");
|
||||
return date;
|
||||
};
|
||||
|
||||
const parseGender = (gender: unknown): Gender => {
|
||||
if (!gender || !isString(gender) || !isGender(gender))
|
||||
throw new Error("Incorrect or missing gender");
|
||||
return gender;
|
||||
};
|
||||
|
||||
const parseNumber = (number: unknown, message: string): number => {
|
||||
if (!number || !isNumber(number)) throw new Error(message);
|
||||
return number;
|
||||
};
|
||||
|
||||
const parseString = (name: unknown, message: string): string => {
|
||||
if (!name || !isString(name)) throw new Error(message);
|
||||
return name;
|
||||
};
|
||||
|
||||
const isString = (text: unknown): text is string => {
|
||||
return typeof text === "string" || text instanceof String;
|
||||
};
|
||||
|
||||
const isNumber = (number: unknown): number is number => {
|
||||
return typeof number === "number" || number instanceof Number;
|
||||
};
|
||||
|
||||
const isDate = (date: string): boolean => {
|
||||
return Boolean(Date.parse(date));
|
||||
};
|
||||
|
||||
const isGender = (gender: string): gender is Gender => {
|
||||
return Object.values(Gender)
|
||||
.map((v) => v.toString())
|
||||
.includes(gender);
|
||||
};
|
||||
type UnionOmit<T, K extends string | number | symbol> = T extends unknown
|
||||
? Omit<T, K>
|
||||
: never;
|
||||
|
||||
export const toNewEntry = (object: unknown): UnionOmit<Entry, "id"> => {
|
||||
if (!object || typeof object !== "object") throw new Error("incorrect body");
|
||||
if (
|
||||
"date" in object &&
|
||||
"specialist" in object &&
|
||||
"description" in object &&
|
||||
"type" in object
|
||||
) {
|
||||
console.log(object.type)
|
||||
switch (object.type) {
|
||||
case "Hospital":
|
||||
if (
|
||||
"diagnosisCodes" in object &&
|
||||
"discharge" in object &&
|
||||
typeof object.discharge === "object" &&
|
||||
object.discharge !== null &&
|
||||
"date" in object.discharge &&
|
||||
"criteria" in object.discharge
|
||||
) {
|
||||
let diagnosisCodes = undefined;
|
||||
if (isDiagnosisCodes(object.diagnosisCodes))
|
||||
diagnosisCodes = [...object.diagnosisCodes];
|
||||
else throw new Error("error in diagnoses codes");
|
||||
return {
|
||||
date: parseString(object.date, "Incorrect date"),
|
||||
specialist: parseString(object.specialist, "Incorrect specialist"),
|
||||
description: parseString(
|
||||
object.description,
|
||||
"Incorrect description"
|
||||
),
|
||||
type: object.type,
|
||||
discharge: {
|
||||
date: parseString(object.discharge.date, "error in discharge date"),
|
||||
criteria: parseString(object.discharge.criteria, "error in discharge criteria"),
|
||||
},
|
||||
diagnosisCodes,
|
||||
};
|
||||
}
|
||||
throw new Error(
|
||||
"diagnosisCodes or discharge missing for type Hospital"
|
||||
);
|
||||
case "HealthCheck":
|
||||
if ("healthCheckRating" in object) {
|
||||
let employerName = undefined;
|
||||
if ("employerName" in object && employerName !== "undefined")
|
||||
employerName = object.employerName;
|
||||
return {
|
||||
date: parseString(object.date, "Incorrect date"),
|
||||
specialist: parseString(object.specialist, "Incorrect specialist"),
|
||||
description: parseString(
|
||||
object.description,
|
||||
"Incorrect description"
|
||||
),
|
||||
type: object.type,
|
||||
healthCheckRating: parseNumber(
|
||||
object.healthCheckRating,
|
||||
"Incorrect HealthCheckRating"
|
||||
),
|
||||
employerName: parseString(employerName, "Incorrect employername"),
|
||||
};
|
||||
}
|
||||
throw new Error("healthCheckRating missing for type HealthCheck");
|
||||
|
||||
case "OccupationalHealthcare":
|
||||
if ("employerName" in object) {
|
||||
let diagnosisCodes: undefined | string[] = undefined;
|
||||
let sickLeave: undefined | { startDate: string; endDate: string } =
|
||||
undefined;
|
||||
if (
|
||||
"diagnosisCodes" in object &&
|
||||
object.diagnosisCodes !== undefined
|
||||
) {
|
||||
diagnosisCodes = isDiagnosisCodes(object.diagnosisCodes)
|
||||
? object.diagnosisCodes
|
||||
: undefined;
|
||||
}
|
||||
if (
|
||||
"sickLeave" in object &&
|
||||
object.sickLeave !== undefined &&
|
||||
object.sickLeave !== null &&
|
||||
typeof object.sickLeave === "object" &&
|
||||
"startDate" in object.sickLeave &&
|
||||
"endDate" in object.sickLeave
|
||||
) {
|
||||
sickLeave = {
|
||||
startDate: parseString(object.sickLeave.startDate, ""),
|
||||
endDate: parseString(object.sickLeave.endDate, ""),
|
||||
};
|
||||
}
|
||||
return {
|
||||
date: parseString(object.date, "Incorrect date"),
|
||||
specialist: parseString(object.specialist, "Incorrect specialist"),
|
||||
description: parseString(
|
||||
object.description,
|
||||
"Incorrect description"
|
||||
),
|
||||
type: object.type,
|
||||
...(isDiagnosisCodes(diagnosisCodes) && {diagnosisCodes}),
|
||||
//diagnosisCodes,
|
||||
employerName: parseString(
|
||||
object.employerName,
|
||||
"Incorrect employername"
|
||||
),
|
||||
sickLeave,
|
||||
};
|
||||
}
|
||||
throw new Error("employerName missing in OccupationalHealthcare");
|
||||
default:
|
||||
throw new Error("wrong type");
|
||||
}
|
||||
}
|
||||
throw new Error("erro");
|
||||
};
|
||||
|
||||
const isDiagnosisCodes = (
|
||||
diagnosisCodes: unknown
|
||||
): diagnosisCodes is string[] => {
|
||||
if (diagnosisCodes instanceof Array) {
|
||||
diagnosisCodes.forEach((el) => {
|
||||
if (typeof el === "string") throw new Error("");
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES6",
|
||||
"outDir": "./build/",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user