Skip to content

Commit

Permalink
Merge pull request #157 from dali-lab/dev
Browse files Browse the repository at this point in the history
Feat/prepare histogram data
  • Loading branch information
wu-ciesielska authored Feb 10, 2025
2 parents e853962 + ff3883d commit ca16189
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 2 deletions.
10 changes: 10 additions & 0 deletions docs/HISTOGRAM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Histogram Operations

## `GET /histogram`

Returns the histogram data stored in the database.

## `POST /histogram/update`

Expects authorization header with Bearer token.
If no histogram data is stored in the database, creates one. If there is previous data, updates the existing data with the results of latest calculations.
3 changes: 2 additions & 1 deletion docs/ROUTES.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Route Documentation

- [Blog](./BLOG.md)
- [Healthcheck](./HEALTHCHECK.md)
- [Histogram](./HISTOGRAM.md)
- [Summarized County Data](./SUMMARIZED-COUNTY.md)
- [Summarized Ranger District Data](./SUMMARIZED-RD.md)
- [Unsummarized Trapping Data](./UNSUMMARIZED.md)
- [Users](./USERS.md)
- [Blog](./BLOG.md)
1 change: 1 addition & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const COLLECTION_NAMES = {
unsummarized: 'unsummarizedtrappings',
users: 'users',
blogPost: 'blogs',
histogram: 'histogram',
};

/**
Expand Down
33 changes: 33 additions & 0 deletions src/controllers/histogram.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { COLLECTION_NAMES, RESPONSE_CODES } from '../constants';
import { queryFetch } from '../utils';
import { saveHistogramData, computeHistogramData } from '../utils/histogram-service';

/**
* @description retrieves histogram data object
* @returns {Promise<HistogramData>} promise that resolves to histogram data object or error
*/
export const getHistogramData = async () => {
try {
const histogramData = await queryFetch(COLLECTION_NAMES.histogram);
return { ...RESPONSE_CODES.SUCCESS, data: histogramData[0] };
} catch (error) {
console.error(error);
return error;
}
};

/**
* @description recalculates and saves histogram data object to the database
* @returns {Promise<HistogramData>} promise that resolves to histogram data object or error
*/
export const updateHistogramData = async () => {
try {
const histogramData = await computeHistogramData();

const savedHistogramData = await saveHistogramData(histogramData);
return { ...RESPONSE_CODES.SUCCESS, data: savedHistogramData };
} catch (error) {
console.error(error);
return error;
}
};
3 changes: 2 additions & 1 deletion src/controllers/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable import/prefer-default-export */
import * as User from './user';
import * as Blog from './blog';
import * as Histogram from './histogram';

export { User, Blog };
export { User, Blog, Histogram };
39 changes: 39 additions & 0 deletions src/routers/histogram.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Router } from 'express';
import {
generateResponse,
RESPONSE_TYPES,
RESPONSE_CODES,
} from '../constants';
import { Histogram } from '../controllers';
import { updateHistogramData } from '../controllers/histogram';
import { requireAuth } from '../middleware';

const histogramRouter = Router();

// returns histogram data
histogramRouter.route('/').get(async (_req, res) => {
try {
const data = await Histogram.getHistogramData();

res.send(generateResponse(RESPONSE_TYPES.SUCCESS, data.data));
} catch (error) {
res
.status(RESPONSE_CODES.INTERNAL_ERROR.status)
.send(generateResponse(RESPONSE_TYPES.INTERNAL_ERROR, error));
}
});

// calculates histogram data
histogramRouter.route('/update').post([requireAuth], async (req, res) => {
try {
const data = await updateHistogramData();

res.send(generateResponse(RESPONSE_TYPES.SUCCESS, data));
} catch (error) {
res
.status(RESPONSE_CODES.INTERNAL_ERROR.status)
.send(generateResponse(RESPONSE_TYPES.INTERNAL_ERROR, error));
}
});

export default histogramRouter;
2 changes: 2 additions & 0 deletions src/routers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import summarizedRangerDistrict from './summarized-ranger-district';
import unsummarized from './unsummarized';
import user from './user';
import blog from './blog';
import histogram from './histogram';

export default {
healthcheck,
Expand All @@ -12,4 +13,5 @@ export default {
'unsummarized-trapping': unsummarized,
user,
blog,
histogram,
};
108 changes: 108 additions & 0 deletions src/utils/histogram-service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { COLLECTION_NAMES, RESPONSE_CODES } from '../constants';
import { specifiedQueryFetch } from './query-fetch';

// x axis of the histogram - predicted probability of an outbreak
export const probRanges = [
{ min: 0, max: 0.025 },
{ min: 0.025, max: 0.05 },
{ min: 0.05, max: 0.15 },
{ min: 0.15, max: 0.25 },
{ min: 0.25, max: 0.4 },
{ min: 0.4, max: 0.6 },
{ min: 0.6, max: 0.8 },
{ min: 0.8, max: 1 },
];

// y axis of the histogram - actual number of spots observed
const spotsRanges = [
{ min: 0, max: 0 },
{ min: 1, max: 9 },
{ min: 10, max: 19 },
{ min: 20, max: 49 },
{ min: 50, max: 99 },
{ min: 100, max: 249 },
{ min: 250, max: Infinity },
];

const categorizeRecords = (records) => {
const frequencyArray = probRanges.map(({ min, max }) => {
const rangeLabel = `${min === 0 ? '0' : min}-${max}`;
const rangeRecords = records.filter((record) => {
return record.probSpotsGT50 > min && record.probSpotsGT50 <= max;
});

// eslint-disable-next-line no-shadow
const data = spotsRanges.map(({ min, max }) => {
return rangeRecords.filter((record) => {
return record.spotst0 >= min && record.spotst0 <= max;
}).length;
});

return {
range: rangeLabel,
frequency: rangeRecords.length,
withBorder: false,
data,
};
});

return frequencyArray;
};
function validateRecords(records) {
return records.filter((record) => {
return (
record.probSpotsGT50 !== null
&& record.probSpotsGT50 !== undefined
&& record.spotst0 !== null
&& record.spotst0 !== undefined
);
});
}

export const computeHistogramData = async () => {
try {
const counties = await specifiedQueryFetch(
COLLECTION_NAMES.summarizedCounty,
{
hasPredictionAndOutcome: 1,
},
);

const rangerDistricts = await specifiedQueryFetch(
COLLECTION_NAMES.summarizedRangerDistrict,
{
hasPredictionAndOutcome: 1,
},
);

const data = [...counties, ...rangerDistricts];
const validRecords = validateRecords(data);
const frequency = validRecords.length;
const frequencyArray = categorizeRecords(validRecords);

return { ...RESPONSE_CODES.SUCCESS, frequency, frequencyArray };
} catch (e) {
console.error(e);
return e;
}
};

export const saveHistogramData = async (histogramData) => {
const cursor = global.connection.collection('histogram');

const { frequency, frequencyArray } = histogramData;

const savedData = await cursor.updateOne(
{ _id: 'chartData' },
{
$set: {
timestamp: new Date(),
frequencyArray,
frequency,
},
},
{ upsert: true },
);

return savedData;
};

0 comments on commit ca16189

Please sign in to comment.