Merge branch 'Symptoms-caused-by-Lots'
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,9 @@
|
|||||||
class PageInitializer {
|
class PageInitializer {
|
||||||
|
|
||||||
static initializePage({ symptom, vaccine }) {
|
static initializePage({ symptom, vaccine, symptomVsSymptomChart }) {
|
||||||
PageInitializer.#configureSymptom(symptom);
|
PageInitializer.#configureSymptom(symptom);
|
||||||
PageInitializer.#configureVaccine(vaccine);
|
PageInitializer.#configureVaccine(vaccine);
|
||||||
|
PageInitializer.#configureSymptomVsSymptomChart(symptomVsSymptomChart);
|
||||||
}
|
}
|
||||||
|
|
||||||
static #configureSymptom({ symptomSelectElement, prrByVaccineTableElement, downloadPrrByVaccineTableButton }) {
|
static #configureSymptom({ symptomSelectElement, prrByVaccineTableElement, downloadPrrByVaccineTableButton }) {
|
||||||
@@ -25,6 +26,12 @@ class PageInitializer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static #configureSymptomVsSymptomChart(symptomVsSymptomChart) {
|
||||||
|
new SymptomVsSymptomChartViewInitializer().configureSymptomVsSymptomChart(
|
||||||
|
symptomVsSymptomChart,
|
||||||
|
PageInitializer.#initializeSelectElement);
|
||||||
|
}
|
||||||
|
|
||||||
static #initializeSelectElement({ selectElement, onValueSelected, minimumInputLength }) {
|
static #initializeSelectElement({ selectElement, onValueSelected, minimumInputLength }) {
|
||||||
selectElement.select2({ minimumInputLength: minimumInputLength });
|
selectElement.select2({ minimumInputLength: minimumInputLength });
|
||||||
selectElement.on(
|
selectElement.on(
|
||||||
|
|||||||
20
docs/SymptomsCausedByCOVIDLots/js/Sets.js
Normal file
20
docs/SymptomsCausedByCOVIDLots/js/Sets.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
class Sets {
|
||||||
|
|
||||||
|
static filterSet(set, predicate) {
|
||||||
|
return new Set([...set].filter(predicate));
|
||||||
|
}
|
||||||
|
|
||||||
|
static union(sets) {
|
||||||
|
return sets.reduce(
|
||||||
|
(union, set) => new Set([...union, ...set]),
|
||||||
|
new Set());
|
||||||
|
}
|
||||||
|
|
||||||
|
static intersection(set1, set2) {
|
||||||
|
return new Set([...set1].filter(x => set2.has(x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static isEmpty(set) {
|
||||||
|
return set.size == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
class SymptomVsSymptomChartDataProvider {
|
||||||
|
|
||||||
|
// FK-TODO: extract utility class and test
|
||||||
|
static retainCommonKeys({ dict1, dict2 }) {
|
||||||
|
const commonKeys = SymptomVsSymptomChartDataProvider.#getCommonKeys(dict1, dict2);
|
||||||
|
return {
|
||||||
|
dict1: SymptomVsSymptomChartDataProvider.#retainKeysOfDict(dict1, commonKeys),
|
||||||
|
dict2: SymptomVsSymptomChartDataProvider.#retainKeysOfDict(dict2, commonKeys)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getChartData({ prrByLotX, prrByLotY }) {
|
||||||
|
const { dict1: prrByLotXCommon, dict2: prrByLotYCommon } =
|
||||||
|
SymptomVsSymptomChartDataProvider.retainCommonKeys(
|
||||||
|
{
|
||||||
|
dict1: prrByLotX,
|
||||||
|
dict2: prrByLotY
|
||||||
|
});
|
||||||
|
const lots = Object.keys(prrByLotXCommon);
|
||||||
|
return {
|
||||||
|
labels: lots,
|
||||||
|
data:
|
||||||
|
lots.map(
|
||||||
|
lot => ({
|
||||||
|
x: prrByLotXCommon[lot],
|
||||||
|
y: prrByLotYCommon[lot]
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static #getCommonKeys(dict1, dict2) {
|
||||||
|
return Sets.intersection(
|
||||||
|
SymptomVsSymptomChartDataProvider.#getKeySet(dict1),
|
||||||
|
SymptomVsSymptomChartDataProvider.#getKeySet(dict2));
|
||||||
|
}
|
||||||
|
|
||||||
|
static #getKeySet(dict) {
|
||||||
|
return new Set(Object.keys(dict));
|
||||||
|
}
|
||||||
|
|
||||||
|
static #retainKeysOfDict(dict, keys2Retain) {
|
||||||
|
const entries2Retain =
|
||||||
|
Object
|
||||||
|
.entries(dict)
|
||||||
|
.filter(([key, _]) => keys2Retain.has(key));
|
||||||
|
return Object.fromEntries(entries2Retain);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
class SymptomVsSymptomChartView {
|
||||||
|
|
||||||
|
#canvas;
|
||||||
|
#chart;
|
||||||
|
|
||||||
|
constructor(canvas) {
|
||||||
|
this.#canvas = canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAndDisplayChart(symptomX, symptomY) {
|
||||||
|
Promise
|
||||||
|
.all([symptomX, symptomY].map(symptom => PrrByVaccineProvider.getPrrByVaccine(symptom)))
|
||||||
|
.then(
|
||||||
|
([prrByLotX, prrByLotY]) => {
|
||||||
|
const { labels, data } = SymptomVsSymptomChartDataProvider.getChartData({ prrByLotX, prrByLotY });
|
||||||
|
this.#displayChart(symptomX, symptomY, labels, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#displayChart(symptomX, symptomY, labels, data) {
|
||||||
|
if (this.#chart != null) {
|
||||||
|
this.#chart.destroy();
|
||||||
|
}
|
||||||
|
this.#chart =
|
||||||
|
new Chart(
|
||||||
|
this.#canvas,
|
||||||
|
this.#getConfig(symptomX, symptomY, labels, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
#getConfig(symptomX, symptomY, labels, data) {
|
||||||
|
return {
|
||||||
|
type: 'scatter',
|
||||||
|
data: this.#getData(labels, data),
|
||||||
|
options: this.#getOptions(symptomX, symptomY)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#getData(labels, data) {
|
||||||
|
return {
|
||||||
|
datasets:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
labels: labels,
|
||||||
|
data: data,
|
||||||
|
backgroundColor: 'rgb(0, 0, 255)'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#getOptions(symptomX, symptomY) {
|
||||||
|
return {
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
type: 'linear',
|
||||||
|
position: 'bottom',
|
||||||
|
title: this.#getAxisTitle(symptomX)
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
title: this.#getAxisTitle(symptomY)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function (context) {
|
||||||
|
return 'Batch: ' + context.dataset.labels[context.dataIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#getAxisTitle(symptom) {
|
||||||
|
return {
|
||||||
|
display: true,
|
||||||
|
text: 'PRR ratio of Batch for ' + symptom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
class SymptomVsSymptomChartViewInitializer {
|
||||||
|
|
||||||
|
#symptomVsSymptomChartView;
|
||||||
|
#symptomX;
|
||||||
|
#symptomY;
|
||||||
|
|
||||||
|
configureSymptomVsSymptomChart(
|
||||||
|
{ symptomSelectXElement, symptomSelectYElement, symptomVsSymptomChartViewElement },
|
||||||
|
initializeSelectElement) {
|
||||||
|
|
||||||
|
this.#symptomVsSymptomChartView = new SymptomVsSymptomChartView(symptomVsSymptomChartViewElement);
|
||||||
|
{
|
||||||
|
initializeSelectElement(
|
||||||
|
{
|
||||||
|
selectElement: symptomSelectXElement,
|
||||||
|
onValueSelected: symptomX => {
|
||||||
|
this.#symptomX = symptomX;
|
||||||
|
this.#loadAndDisplayChart();
|
||||||
|
},
|
||||||
|
minimumInputLength: 4
|
||||||
|
});
|
||||||
|
this.#symptomX = null;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
initializeSelectElement(
|
||||||
|
{
|
||||||
|
selectElement: symptomSelectYElement,
|
||||||
|
onValueSelected: symptomY => {
|
||||||
|
this.#symptomY = symptomY;
|
||||||
|
this.#loadAndDisplayChart();
|
||||||
|
},
|
||||||
|
minimumInputLength: 4
|
||||||
|
});
|
||||||
|
this.#symptomY = null;
|
||||||
|
}
|
||||||
|
this.#loadAndDisplayChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
#loadAndDisplayChart() {
|
||||||
|
if (this.#symptomX != null && this.#symptomY != null) {
|
||||||
|
this.#symptomVsSymptomChartView.loadAndDisplayChart(this.#symptomX, this.#symptomY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
QUnit.module('SymptomVsSymptomChartDataProviderTest', function () {
|
||||||
|
|
||||||
|
QUnit.test.each(
|
||||||
|
'shouldRetainCommonLots',
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
dict1: {
|
||||||
|
"lotX": 1.0
|
||||||
|
},
|
||||||
|
dict2: {
|
||||||
|
"lotY": 2.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dict1: {
|
||||||
|
},
|
||||||
|
dict2: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
dict1: {
|
||||||
|
"lotX": 1.0,
|
||||||
|
"lotCommon": 2.0
|
||||||
|
},
|
||||||
|
dict2: {
|
||||||
|
"lotCommon": 3.0,
|
||||||
|
"lotY": 4.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dict1: {
|
||||||
|
"lotCommon": 2.0
|
||||||
|
},
|
||||||
|
dict2: {
|
||||||
|
"lotCommon": 3.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
(assert, [dicts, dictsHavingCommonKeys]) => {
|
||||||
|
// Given
|
||||||
|
|
||||||
|
// When
|
||||||
|
const dictsHavingCommonKeysActual = SymptomVsSymptomChartDataProvider.retainCommonKeys(dicts);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.deepEqual(dictsHavingCommonKeysActual, dictsHavingCommonKeys);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('shouldProvideChartData', function (assert) {
|
||||||
|
// Given
|
||||||
|
const prrByLotX = {
|
||||||
|
"lotX": 1.0,
|
||||||
|
"lotCommon": 2.0
|
||||||
|
};
|
||||||
|
const prrByLotY = {
|
||||||
|
"lotCommon": 3.0,
|
||||||
|
"lotY": 4.0
|
||||||
|
};
|
||||||
|
|
||||||
|
// When
|
||||||
|
const chartData = SymptomVsSymptomChartDataProvider.getChartData({ prrByLotX, prrByLotY });
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.deepEqual(
|
||||||
|
chartData,
|
||||||
|
{
|
||||||
|
labels: ["lotCommon"],
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
x: 2.0,
|
||||||
|
y: 3.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
38
docs/SymptomsCausedByCOVIDLots/test/index.test.html
Normal file
38
docs/SymptomsCausedByCOVIDLots/test/index.test.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Safety Signals for COVID Batches</title>
|
||||||
|
<script src="../../Utils.js"></script>
|
||||||
|
<script src="../../UIUtils.js"></script>
|
||||||
|
<script src="../../NumberWithBarElementFactory.js"></script>
|
||||||
|
<script src="../js/Sets.js"></script>
|
||||||
|
<script src="../js/PrrByKey2CsvConverter.js"></script>
|
||||||
|
<script src="../js/PageInitializer.js"></script>
|
||||||
|
<script src="../js/PrrByVaccineProvider.js"></script>
|
||||||
|
<script src="../js/PrrByKeyTable.js"></script>
|
||||||
|
<script src="../js/PrrByKeyTableView.js"></script>
|
||||||
|
<script src="../js/PrrByVaccineTableView.js"></script>
|
||||||
|
<script src="../js/PrrBySymptomTableView.js"></script>
|
||||||
|
<script src="../js/SymptomVsSymptomChartView.js"></script>
|
||||||
|
<script src="../js/SymptomVsSymptomChartDataProvider.js"></script>
|
||||||
|
<script src="../js/SymptomVsSymptomChartViewInitializer.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.17.2.css">
|
||||||
|
<script src="https://code.jquery.com/qunit/qunit-2.17.2.js"></script>
|
||||||
|
<script type="text/javascript" src="./jshamcrest.js"></script>
|
||||||
|
<script type="text/javascript" src="./jsmockito-1.0.4.js"></script>
|
||||||
|
<script>
|
||||||
|
JsHamcrest.Integration.QUnit();
|
||||||
|
JsMockito.Integration.QUnit();
|
||||||
|
</script>
|
||||||
|
<script src="./SymptomVsSymptomChartDataProviderTest.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="qunit"></div>
|
||||||
|
<div id="qunit-fixture"></div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
1500
docs/SymptomsCausedByCOVIDLots/test/jshamcrest.js
Normal file
1500
docs/SymptomsCausedByCOVIDLots/test/jshamcrest.js
Normal file
File diff suppressed because it is too large
Load Diff
1019
docs/SymptomsCausedByCOVIDLots/test/jsmockito-1.0.4.js
Normal file
1019
docs/SymptomsCausedByCOVIDLots/test/jsmockito-1.0.4.js
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -438,7 +438,7 @@
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"from SymptomsCausedByVaccines.HtmlUpdater import updateHtmlFile\n",
|
"from SymptomsCausedByVaccines.HtmlUpdater import updateHtmlFile, updateHtmlFile4SymptomsCausedByCOVIDLots\n",
|
||||||
"from SymptomsCausedByVaccines.PrrSeriesFactory import PrrSeriesFactory\n",
|
"from SymptomsCausedByVaccines.PrrSeriesFactory import PrrSeriesFactory\n",
|
||||||
"from SymptomsCausedByVaccines.PrrSeriesTransformer import PrrSeriesTransformer\n",
|
"from SymptomsCausedByVaccines.PrrSeriesTransformer import PrrSeriesTransformer\n",
|
||||||
"from SymptomsCausedByVaccines.ProportionalReportingRatiosPersister import saveProportionalReportingRatios\n",
|
"from SymptomsCausedByVaccines.ProportionalReportingRatiosPersister import saveProportionalReportingRatios\n",
|
||||||
@@ -627,11 +627,10 @@
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"updateHtmlFile(\n",
|
"updateHtmlFile4SymptomsCausedByCOVIDLots(\n",
|
||||||
" symptoms = list(prrByLotAndSymptom.columns),\n",
|
" symptoms = list(prrByLotAndSymptom.columns),\n",
|
||||||
" vaccines = list(prrByLotAndSymptom.index),\n",
|
" batches = list(prrByLotAndSymptom.index),\n",
|
||||||
" htmlFile = \"../docs/SymptomsCausedByCOVIDLots/index.html\",\n",
|
" htmlFile = \"../docs/SymptomsCausedByCOVIDLots/index.html\")"
|
||||||
" defaultSelectVaccineOptionText = 'Select Batch')"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,19 @@ def updateHtmlFile(symptoms, vaccines, htmlFile, defaultSelectVaccineOptionText
|
|||||||
htmlFile = htmlFile,
|
htmlFile = htmlFile,
|
||||||
selectElementId = 'vaccineSelect')
|
selectElementId = 'vaccineSelect')
|
||||||
|
|
||||||
|
def updateHtmlFile4SymptomsCausedByCOVIDLots(symptoms, batches, htmlFile):
|
||||||
|
symptomOptions = getSymptomOptions(symptoms)
|
||||||
|
for selectElementId in ['symptomSelect', 'symptomSelectX', 'symptomSelectY']:
|
||||||
|
_saveOptions(
|
||||||
|
options = symptomOptions,
|
||||||
|
htmlFile = htmlFile,
|
||||||
|
selectElementId = selectElementId)
|
||||||
|
|
||||||
|
_saveOptions(
|
||||||
|
options = getVaccineOptions(batches, 'Select Batch'),
|
||||||
|
htmlFile = htmlFile,
|
||||||
|
selectElementId = 'vaccineSelect')
|
||||||
|
|
||||||
def _saveOptions(options, htmlFile, selectElementId):
|
def _saveOptions(options, htmlFile, selectElementId):
|
||||||
HtmlTransformerUtil().applySoupTransformerToFile(
|
HtmlTransformerUtil().applySoupTransformerToFile(
|
||||||
file=htmlFile,
|
file=htmlFile,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ FK-TODO:
|
|||||||
- Symptomhistogramm
|
- Symptomhistogramm
|
||||||
- http://www.howbadismybatch.info/SymptomsCausedByVaccines/index.html
|
- http://www.howbadismybatch.info/SymptomsCausedByVaccines/index.html
|
||||||
- http://www.howbadismybatch.info/SymptomsCausedByCOVIDLots/index.html
|
- http://www.howbadismybatch.info/SymptomsCausedByCOVIDLots/index.html
|
||||||
|
http://www.howbadismybatch.info/SymptomsCausedByCOVIDLots/test/index.test.html
|
||||||
|
|
||||||
anacron job:
|
anacron job:
|
||||||
sudo cp src/intensivstationen_howbadismybatch.sh /etc/cron.daily/intensivstationen_howbadismybatch
|
sudo cp src/intensivstationen_howbadismybatch.sh /etc/cron.daily/intensivstationen_howbadismybatch
|
||||||
|
|||||||
Reference in New Issue
Block a user