Skip to content

Commit

Permalink
Automatically send gene sample results (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicou authored Jun 21, 2024
1 parent 46988c9 commit 3c0aefc
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 42 deletions.
9 changes: 9 additions & 0 deletions db/data/gene-samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Gene samples

This directory contains a script to convert the gene sample PDF names to a hash of the person id. This is done so that the players can't guess the gene sample file names.

1. Download the gene sample PDFs from Google Drive and place them in this directory
2. Make sure that files.csv is up to date, as it maps the file names to person id's
3. Make sure you are in this directory and run `./convert.sh`
4. The script will create a new directory called `processed` that contains the PDFs where the file name is now the first 8 characters of the sha1 hash of the person id
5. The contents of `processed` can be uploaded to the server to be served under `/gene-samples/`
25 changes: 25 additions & 0 deletions db/data/gene-samples/convert.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash

set -euo pipefail

input="files.csv"

# Function to generate a short hash from an ID
generate_hash() {
echo -n "$1" | sha1sum | cut -c1-8
}

mkdir -p ./processed
rm -f ./processed/*.pdf

while IFS=',' read -r id file; do
# Skip the header line
if [ "$id" != "id" ]; then
# Remove leading ./ from the filename if present
filename="${file#./}"
hash=$(generate_hash "$id")
# hash="$id"
cp "$filename" "./processed/${hash}.pdf"
fi
done < "$input"

105 changes: 105 additions & 0 deletions db/data/gene-samples/files.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
id,file
20077,./Gene test - Amir Bolton image.pdf
20107,./Gene test - Jonah Malone image.pdf
20011,./Gene test - Terran Wells image.pdf
20022,./Gene test - Jardan image.pdf
20066,./Gene test - Lane Hayakawa image.pdf
20087,./Gene test - Beva Drugov image.pdf
20040,./Gene test - Deron Richard image.pdf
20026,./Gene test - Naethan image.pdf
20073,./Gene test - Torrey Watson image.pdf
20386,./Gene test - The Guardian image.pdf
20130,./Gene test - Anix image.pdf
20021,./Gene test - Alia Swanson image.pdf
20096,./Gene test - Dayle Rogers image.pdf
20085,./Gene test - Abe Arima image.pdf
20004,./Gene test - Dallan Jordan image.pdf
20025,./Gene test - Jaeco image.pdf
20052,./Gene test - Glen Hollow image.pdf
20012,./Gene test - Blake Ishimoto image.pdf
20090,./Gene test - Harper Ellis image.pdf
20005,./Gene test - Yera Romero image.pdf
20060,./Gene test - Nicol Wells image.pdf
20108,./Gene test - Malak Fukui image.pdf
20094,./Gene test - Hayden Carson image.pdf
20089,./Gene test - Fenix Ellis image.pdf
20083,./Gene test - Tan Ellis image.pdf
20124,./Gene test - Jovian Aurelios Cauruleos image.pdf
20015,./Gene test - Gallan Reid image.pdf
20086,./Gene test - Aeran Lester image.pdf
20001,./Gene test - Vane Hodge image.pdf
20039,./Gene test - Noe Walker image.pdf
20121,./Gene test - Pax Houghton image.pdf
20059,./Gene test - Ballard Case image.pdf
20051,./Gene test - Roan Rowen image.pdf
20006,./Gene test - Kai Rogers image.pdf
20132,./Gene test - Nayel image.pdf
20110,./Gene test - Jose Cain image.pdf
20044,./Gene test - Jill Montoya image.pdf
20007,./Gene test - Cal Allen image.pdf
20131,./Gene test - Keana image.pdf
20120,./Gene test - Remi Sharp image.pdf
20023,./Gene test - Zaera image.pdf
20104,./Gene test - Espen Nakahara image.pdf
20043,./Gene test - Gail Wells image.pdf
20058,./Gene test - Hali Okuma image.pdf
20084,./Gene test - Arlyn Booth image.pdf
20055,./Gene test - Julia Aurelios Cauruleos image.pdf
20134,./Gene test - Tarai image.pdf
20013,./Gene test - Devyn Pearson image.pdf
20003,./Gene test - Jin Komatsu image.pdf
20119,./Gene test - Yuri Mills image.pdf
20126,./Gene test - Gene Hawkins image.pdf
20123,./Gene test - Lee Savage image.pdf
20014,./Gene test - Evin Reid image.pdf
20082,./Gene test - Eva Ellis image.pdf
20098,./Gene test - Briana Chambers image.pdf
20103,./Gene test - Valerian Fukui image.pdf
20101,./Gene test - Osha Green image.pdf
20095,./Gene test - Han Barnes image.pdf
20074,./Gene test - Nikita Watson image.pdf
20106,./Gene test - Nico Lawrence image.pdf
20088,./Gene test - Fran Abrankowich image.pdf
20099,./Gene test - Heath Steele image.pdf
20020,./Gene test - Xavier Blake image.pdf
20133,./Gene test - Saria image.pdf
20063,./Gene test - Eli Booth image.pdf
20045,./Gene test - Isha Hayakawa image.pdf
20097,./Gene test - Mel McBride image.pdf
20019,./Gene test - Malak Kovalenko image.pdf
20117,./Gene test - Leigh Kent image.pdf
20017,./Gene test - Skye Duran image.pdf
20116,./Gene test - Nolan Hunter image.pdf
20061,./Gene test - Flann Hollow image.pdf
20037,./Gene test - Hale Green image.pdf
20018,./Gene test - Zyra Lee image.pdf
20028,./Gene test - Briya image.pdf
20100,./Gene test - Caden Andrews image.pdf
20102,./Gene test - Lynn Ryan image.pdf
20041,./Gene test - Nickie Ramirez image.pdf
20125,./Gene test - Avery Higashi image.pdf
20128,./Gene test - Aedan image.pdf
20070,./Gene test - Jodey Agaki image.pdf
20008,./Gene test - Idris McBride image.pdf
20042,./Gene test - Lowan Romero image.pdf
20024,./Gene test - Mael image.pdf
20002,./Gene test - Lex Peters image.pdf
20057,./Gene test - Eugenie Russell image.pdf
20112,./Gene test - Zeya Cook image.pdf
20127,./Gene test - Harley Carroll image.pdf
20092,./Gene test - Hedly Walker image.pdf
20062,./Gene test - Tristan Fukui image.pdf
20016,./Gene test - Tyler Carrillo image.pdf
20118,./Gene test - Lonnie Gordon image.pdf
20081,./Gene test - Oriel Cook image.pdf
20113,./Gene test - Karin Alexandrov image.pdf
20111,./Gene test - Ziva Callahan image.pdf
20114,./Gene test - Leone Mills image.pdf
20064,./Gene test - Ismy Arima image.pdf
20091,./Gene test - Gale Chapman image.pdf
20038,./Gene test - Ashlin Hall image.pdf
20129,./Gene test - Taelor image.pdf
20010,./Gene test - Remy Hall image.pdf
20093,./Gene test - Taren Yates image.pdf
20115,./Gene test - Kerrie Ray image.pdf
20050,./Gene test - Gaylen Russell image.pdf
108 changes: 67 additions & 41 deletions src/routes/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { logger } from '@/logger';
import { NotFound } from 'http-errors';
import { get } from 'lodash';
import Bookshelf from 'bookshelf';
import { getBloodTestResultText } from '@/utils/blood-test-results';
import { getBloodTestResultText, getGeneSampleResultText } from '@/utils/medical-test-results';
import { getScienceAnalysisTime, addOperationResultsToArtifactEntry } from '@/utils/science';
import { getPath } from "@/store/store";
import { getPath } from '@/store/store';
import moment from 'moment';
import { Stores } from '@/store/types';
import { saveBlob } from '@/rules/helpers';
Expand Down Expand Up @@ -57,6 +57,7 @@ async function processXrayOperation(operationResult: Bookshelf.Model<unknown>) {

const addOperationResultToMedicalEntry = async (operationResult: Bookshelf.Model<unknown>) => {
const isBloodSample = operationResult.get('additional_type') === 'BLOOD_SAMPLE';
const isGeneSample = operationResult.get('additional_type') === 'GENE_SAMPLE';
const isAnalysed = operationResult.get('is_analysed');
const isComplete = operationResult.get('is_complete');
if (isBloodSample && isAnalysed && !isComplete) {
Expand All @@ -68,6 +69,15 @@ const addOperationResultToMedicalEntry = async (operationResult: Bookshelf.Model
`Added blood test results to ${person.get('full_name')} (${person.get('id')}), marking the operation as complete`
);
await operationResult.save({ is_complete: true }, { method: 'update', patch: true });
} else if (isGeneSample && isAnalysed && !isComplete) {
const person = await new Person().where({ bio_id: operationResult.get('bio_id') }).fetch();
const geneTestResult = getGeneSampleResultText(person.get('id'));
const entry = new Entry();
await entry.save({ added_by: EVA_ID, entry: geneTestResult, person_id: person.get('id'), type: 'MEDICAL' });
logger.success(
`Added gene test results to ${person.get('full_name')} (${person.get('id')}), marking the operation as complete`
);
await operationResult.save({ is_complete: true }, { method: 'update', patch: true });
}
};

Expand Down Expand Up @@ -100,7 +110,7 @@ async function scheduleAddOperationResultToArtifactEntry(operationResult: Booksh
operation_result_id: operationResult.get('id'),
started_at: Date.now(),
},
]
],
});
}

Expand All @@ -112,12 +122,15 @@ async function scheduleAddOperationResultToArtifactEntry(operationResult: Booksh
* @param {boolean} include_complete.query - True if completed results should be included, defaults to false
* @returns {Array.<OperationResult>} 200 - List of all OperationResult models
*/
router.get('/', handleAsyncErrors(async (req: Request, res: Response) => {
const shouldContainRelations = get(req, 'query.relations') === 'true';
const include_complete = get(req, 'query.include_complete') === 'true';
const where = include_complete ? {} : { is_complete: false };
res.json(await OperationResult.forge().where(where)[shouldContainRelations ? 'fetchAllWithRelated' : 'fetchAll']());
}));
router.get(
'/',
handleAsyncErrors(async (req: Request, res: Response) => {
const shouldContainRelations = get(req, 'query.relations') === 'true';
const include_complete = get(req, 'query.include_complete') === 'true';
const where = include_complete ? {} : { is_complete: false };
res.json(await OperationResult.forge().where(where)[shouldContainRelations ? 'fetchAllWithRelated' : 'fetchAll']());
})
);

/**
* Get a single operation by operation id
Expand All @@ -128,13 +141,17 @@ router.get('/', handleAsyncErrors(async (req: Request, res: Response) => {
* @returns {Error} 404 - OperationResult not found
* @returns {OperationResult.model} 200 - OperationResult model
*/
router.get('/:id', handleAsyncErrors(async (req: Request, res: Response) => {
const shouldContainRelations = get(req, 'query.relations') === 'true';
const operationResult = await OperationResult
.forge({ id: req.params.id })[shouldContainRelations ? 'fetchWithRelated' : 'fetch']();
if (!operationResult) throw new NotFound('OperationResult not found');
res.json(operationResult);
}));
router.get(
'/:id',
handleAsyncErrors(async (req: Request, res: Response) => {
const shouldContainRelations = get(req, 'query.relations') === 'true';
const operationResult = await OperationResult.forge({ id: req.params.id })[
shouldContainRelations ? 'fetchWithRelated' : 'fetch'
]();
if (!operationResult) throw new NotFound('OperationResult not found');
res.json(operationResult);
})
);

/**
* Insert a new operation result
Expand All @@ -144,27 +161,33 @@ router.get('/:id', handleAsyncErrors(async (req: Request, res: Response) => {
* @param {OperationResult.model} operationresult.body.required - OperationResult model
* @returns {OperationResult.model} 200 - Inserted OperationResult model
*/
router.post('/', handleAsyncErrors(async (req: Request, res: Response) => {
const operationResult = await OperationResult.forge().save({
...req.body,
is_analysed: true, // All operations are now analysed by default and results get posted automatically
}, { method: 'insert' });
router.post(
'/',
handleAsyncErrors(async (req: Request, res: Response) => {
const operationResult = await OperationResult.forge().save(
{
...req.body,
is_analysed: true, // All operations are now analysed by default and results get posted automatically
},
{ method: 'insert' }
);

const operationResultType = operationResult.get('type');
const operationResultType = operationResult.get('type');

if (operationResultType === 'MEDIC') {
// Automatically post blood test results, in case this is a blood sample
await addOperationResultToMedicalEntry(operationResult);
}
if (operationResultType === 'MEDIC') {
// Automatically post blood test results, in case this is a blood sample
await addOperationResultToMedicalEntry(operationResult);
}

if (operationResultType === 'SCIENCE') {
// Automatically post artifact test results if available
await scheduleAddOperationResultToArtifactEntry(operationResult);
}
if (operationResultType === 'SCIENCE') {
// Automatically post artifact test results if available
await scheduleAddOperationResultToArtifactEntry(operationResult);
}

await processXrayOperation(operationResult);
res.json(operationResult);
}));
await processXrayOperation(operationResult);
res.json(operationResult);
})
);

/**
* Update an operation result by id
Expand All @@ -175,13 +198,16 @@ router.post('/', handleAsyncErrors(async (req: Request, res: Response) => {
* @param {OperationResult.model} operationresult.body.required - OperationResult model new values
* @returns {OperationResult.model} 200 - Updated OperationResult model
*/
router.put('/:id', handleAsyncErrors(async (req: Request, res: Response) => {
const operationResult = await OperationResult.forge({ id: req.params.id }).fetch();
if (!operationResult) throw new NotFound('OperationResult not found');
await operationResult.save(req.body, { method: 'update', patch: true });
await addOperationResultToMedicalEntry(operationResult);
await processXrayOperation(operationResult);
res.json(operationResult);
}));
router.put(
'/:id',
handleAsyncErrors(async (req: Request, res: Response) => {
const operationResult = await OperationResult.forge({ id: req.params.id }).fetch();
if (!operationResult) throw new NotFound('OperationResult not found');
await operationResult.save(req.body, { method: 'update', patch: true });
await addOperationResultToMedicalEntry(operationResult);
await processXrayOperation(operationResult);
res.json(operationResult);
})
);

export default router;
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
import crypto from 'crypto';

const GENE_SAMPLE_BASE_PATH = '/gene-samples';

export function getGeneSampleFilename(personId: string) {
const hash = crypto.createHash('sha1').update(personId.toString()).digest('hex');
const shortHash = hash.substring(0, 8);
return `${GENE_SAMPLE_BASE_PATH}/${shortHash}.pdf`;
}

function getHemoglobinStatus(hemoglobinStr: string) {
const hemoglobin = parseFloat(hemoglobinStr);
if (isNaN(hemoglobin)) return null;
Expand Down Expand Up @@ -28,7 +38,8 @@ function getKaliumStatus(kaliumStr: string) {
const kalium = parseFloat(kaliumStr);
if (isNaN(kalium)) return null;

if (kalium < 3.5) return 'Hypokalemia (too little kalium in the system), feeling tired, leg cramps, weakness, and constipation. Risk of an abnormal heart rhythm. Can cause cardiac arrest.';
if (kalium < 3.5)
return 'Hypokalemia (too little kalium in the system), feeling tired, leg cramps, weakness, and constipation. Risk of an abnormal heart rhythm. Can cause cardiac arrest.';
if (kalium > 5.1) return 'Dehydrated';
return null;
}
Expand Down Expand Up @@ -88,3 +99,8 @@ export function getBloodTestResultText(resultsModel: any) {
Details: ${resultsModel.get('details') || 'None'}`;
}

export function getGeneSampleResultText(personId: string) {
const filename = getGeneSampleFilename(personId);
return `**Gene sample results:** <a href="${filename}" target="_blank">Click here to view the results</a>`;
}

0 comments on commit 3c0aefc

Please sign in to comment.