Merge branch 'Symptoms-caused-by-Lots'

This commit is contained in:
frankknoll
2023-11-09 12:23:27 +01:00
14 changed files with 29365 additions and 17643 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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(

View 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
]
});
});
});

View 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>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -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')"
] ]
}, },
{ {

View File

@@ -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,

View File

@@ -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