Skip to content

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
  • Loading branch information
imanabu committed Jun 16, 2019
2 parents e403ea0 + 208d3e6 commit be74524
Show file tree
Hide file tree
Showing 20 changed files with 1,247 additions and 3,428 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,7 @@ typings/

# next.js build output
.next
test/300-API.spec.js
routes/api.js
public/jsd/main.js
package-lock.json
10 changes: 10 additions & 0 deletions .idea/dictionaries/manabu.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 0 additions & 22 deletions Confg.js

This file was deleted.

30 changes: 17 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
## Modality Worklist Test Generator and QIDO Server System

Manabu Tokunaga, GitHub: imanabu
Version 0.0.4
Release Date: 20190413
Version 0.0.7
Release Date: 20190616

## New Features and Changes

### 0.0.6 - 0.0.7

* Manually add new patient-studies with your choice of information.
Use this to create a correlated encounter already on the EHR/PACS.

### 0.0.5

* The configuration is now a plain json and stored in config/appConfig.js
* More realistic generation of encounters now. Repeated default /api/studies will only provide
list changes as specified in the configuration. So for example, if you set up 12 encounter per hour,
Expand All @@ -21,9 +28,11 @@ Release Date: 20190413

* URL `limit` and `hourly` parameters will persist for the duration of the server's lifecycle.
Once the `limit` or `hourly` in a query is used that value will persist and will be used
for next request even without the limit and hourly parameter.
for next request even without the limit and hourly parameter *if* `persistConfig` is true.

By default the configuration is set to 96 encounters for 8 hours or 12 new encounters per hour.

* In preparation for public access, rate and quantity limit is now enforceable and configurable.

## Fixed In This Release

Expand All @@ -40,11 +49,7 @@ associated, and serve them up via the DICOM QIDO /studies API. Because I do need
departments with real existing ones at the hospital, you can also configure realistic
clinical department configurations as well.

I am keeping this as simple as possible without a asking you to configure Java or MogoDB or MySQL. I think
this will just work if you have ever worked with Node.js with npm without you needing to fuss about
configurations. All the defaults should be adequate to get you started. The only thing you may not have
done is to code the stuff in TypeScript. I like it. It removes my own coding hassles. But TS now
very popular with React and Angular 2 camps so you are likely exposed to it.
I am keeping this as simple as possible without a asking you to configure Java or MongoDB or MySQL.


## What It Does
Expand Down Expand Up @@ -110,17 +115,16 @@ And by default it should be listening at the Port 3000 of your local system.

`http://localhost:3000/api/studies`

We really do not need any fancy QIDO query param stuff here. So no date range query nor
At this point no date range query nor
element level query is supported. (You are welcome to add those things. Just fork it.)
Go ahead and specify them but they will be ignored.

Only exception to that is that it supports `?limit=number` so that you can
pull 100's and 1000's of entries at a time, and it will be very quick to do so.
In this case the limit is used as a requested number of entry you'd want to generate.
Only exception to that is that it supports `?limit=number` can be used to request the generation quantity.
This is limited to 250 by configuration, but can be changed. 1000s of entries can be generated quickly.

The default is hardwired to 10.

Example with Limit: `http://localhost:3000/api/studies?limit=1000`
Example with Limit: `http://localhost:3000/api/studies?limit=200`

## Configuring Departments and Associated Reasons for Study

Expand Down
6 changes: 5 additions & 1 deletion app.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Application, NextFunction, Request, Response} from "express";
const createError: any = require("http-errors");
import {Application} from "express";
import config = require("./config/appConfig");
import express = require("express");
import slowDown = require("express-slow-down");
import path = require("path");
const cookieParser: any = require("cookie-parser");
const lessMiddleware: any = require("less-middleware");
Expand All @@ -11,6 +12,10 @@ const indexRouter: Application = require("./routes");

const app = express();

const speedLimiter = slowDown(config.speedLimit);

app.enable("trust proxy");
app.use(speedLimiter);
app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
Expand Down
184 changes: 184 additions & 0 deletions client/AddComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import {ClassComponent, RequestOptions} from "mithril";
import {IApiResponse, INewStudy, INewStudyDto} from "../model/dtos";
import m = require("mithril");

export class AddComponent implements ClassComponent {

constructor() {}

private ent: INewStudy = {} as INewStudy;
private _status: string = "";
private set status(v: string) {
this._status = v;
m.redraw();
}

private get status() {
return this._status;
}

oninit = () => {
this.ent.studyDate = new Date();
};

public view = () => {
const my = this;

const dateParse = (v: string) => {
try {
let nd = new Date(v);
if (nd.toLocaleDateString().indexOf("Invalid") === 0) {
my.status = `Please correct the date ${v}`;
return undefined;
} else {
return nd;
}
} catch (why) {
my.status = `Bad date string ${v} ${why}`;
}
};

const oninput = (e: Event) => {
const t = e.currentTarget as HTMLInputElement;
const v = t.value;
switch (t.id) {
case "acc":
my.ent.accession = v ? v : "";
break;
case "dob":
const dp = dateParse(v);
my.ent.dob = dp;
break;
case "gender":
my.ent.gender = (v === "F" || v === "M" || v === "O") ? v : "O";
break;
case "mrn":
my.ent.mrn = v ? v : "";
break;
case "modality":
my.ent.modality = v ? v : "";
break;
case "name":
if (v.indexOf("^") >= 0) {
my.ent.patientName = v ? v.toUpperCase() : "";
} else {
my.status = "Name must be in the DICOM/HL7 Format: SMITH^JOHN^A";
}
break;
case "reason":
my.ent.reason = v ? v : "";
break;
case "sd":
const sd = dateParse(v);
my.ent.studyDate = sd;
break;
case "studyuid":
my.ent.studyUid = v ? v : "";
break;
}
};

const onCreate = () => {
const my = this;
let opt = {} as RequestOptions<IApiResponse<any>>;
opt.method = "POST";
let data: INewStudyDto = {} as INewStudyDto;
opt.data = data;
const ent = my.ent;

data.accession = ent.accession;
data.gender = ent.gender ? ent.gender : "O";
data.mrn = ent.mrn ? ent.mrn : "";
data.modality = ent.modality ? ent.modality : "VL";
data.patientName = ent.patientName ? ent.patientName : "";
data.reason = ent.reason ? ent.reason : "";
data.studyUid = ent.studyUid ? ent.studyUid : "";

if (!ent.dob) {
my.status = "DOB cannot be left empty";
return;
}

data.dob = ent.dob ? ent.dob.toISOString() : "";
data.studyDate = ent.studyDate? ent.studyDate!.toISOString(): "";

m.request("api/study/add", opt)
.then((res) => {
my.status = res.message;
return res;
})
.catch((err: Error) => {
my.status = err.message;
return err;
});
};

return m("",
m("h4", my.status),
m("",
m("label.lbl-input[for=mrn])", "MRN: "),
m("input[id=mrn][type=text]", {
onchange: oninput,
placeholder: "Optional",
value: my.ent.mrn })),
m("",
m("label.lbl-input[for=name])","Name: "),
m("input[id=name][type=text]", {
onchange: oninput,
placeholder: "Temple^Zen^M^Jr.",
value: my.ent.patientName})),
m("",
m("label.lbl-input[for=gender])", "Gender: "
),
m("input.lbl-input[id=gender][type=text]", {
onchange: oninput,
placeholder: "F, M or O",
value: my.ent.gender})),
m("",
m("label.lbl-input[for=dob])", "DOB: "),
m("input[id=dob][type=text]", {
onchange: oninput,
placeholder: "Like 2/29/2000",
value: my.ent.dob ? my.ent.dob.toLocaleDateString(): ""})),
m("",
m("label.lbl-input[for=sd])", "Study Date: "),
m("input[id=sd][type=text]", {
onchange: oninput,
value: my.ent.studyDate ? my.ent.studyDate.toLocaleDateString() : ""
})),
m("",
m("label.lbl-input[for=acc])", "Accession: "),
m("input[id=acc][type=text]", {
onchange: oninput,
placeholder: "Optional",
value: my.ent.accession })),
m("",
m("label.lbl-input[for=modality])", "Modality: "),
m("input[id=modality][type=text]", {
onchange: oninput,
placeholder: "CT, MR, XR, VL...",
value: my.ent.modality })),
m("",
m("label.lbl-input[for=reason])", "Reason: "),
m("input[id=reason][type=text]", {
onchange: oninput,
placeholder: "Optional",
style: "width:60%",
value: my.ent.reason })),
m("",
m("label.lbl-input[for=studyuid])", "StudyUID: "),
m("input[id=studyuid][type=text]", {
onchange: oninput,
placeholder: "Critical! Leave blank unless you know what to do.",
style: "width:60%",
value: my.ent.studyUid })),
m("",
m("button.btn-md.btn-success",
{
onclick: onCreate
},
"Create")
)
);
}
}
Loading

0 comments on commit be74524

Please sign in to comment.