Skip to content

Commit f09a4b8

Browse files
[O2B-1435] Rework QC flags storage to have nullable bounds (#1871)
* Extract dedicated services for GAQ and QC summaries * Update seeders so run 106 do not have first TF timestamp * WIP * WIP on tests * Finalize service test and fix linter * Fix more tests * Minor refactoring and documentation rewrite * Improve naming * Add tests * Try to fix balloon failing test * Fix linter * Another attempt at fixing balloon tests * Fix imports * Fix missing import * Restrict effective run coverage between 0 and 1 * Add missing coalesce * Fix tests again * Add migration to set null boundaries
1 parent ca2c5cb commit f09a4b8

31 files changed

Lines changed: 894 additions & 600 deletions

lib/database/adapters/QcFlagAdapter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class QcFlagAdapter {
3030
/**
3131
* Converts the given database object to an entity object.
3232
*
33-
* @param {SequelizeQualityControlFlag} databaseObject Object to convert.
33+
* @param {SequelizeQcFlag} databaseObject Object to convert.
3434
* @returns {QcFlag} Converted entity object.
3535
*/
3636
toEntity(databaseObject) {

lib/database/adapters/RunAdapter.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ class RunAdapter {
115115
userIdO2Stop,
116116
startTime,
117117
endTime,
118+
qcTimeStart,
119+
qcTimeEnd,
118120
runDuration,
119121
tags,
120122
updatedAt,
@@ -183,6 +185,8 @@ class RunAdapter {
183185
userIdO2Stop,
184186
startTime,
185187
endTime,
188+
qcTimeStart,
189+
qcTimeEnd,
186190
runDuration,
187191
environmentId,
188192
updatedAt: new Date(updatedAt).getTime(),
@@ -298,6 +302,8 @@ class RunAdapter {
298302
userIdO2Stop: entityObject.userIdO2Stop,
299303
startTime: entityObject.startTime,
300304
endTime: entityObject.endTime,
305+
qcTimeStart: entityObject.qcTimeStart,
306+
qcTimeEnd: entityObject.qcTimeEnd,
301307
environmentId: entityObject.environmentId,
302308
runTypeId: entityObject.runTypeId,
303309
runQuality: entityObject.runQuality,
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'use strict';
2+
3+
/** @type {import('sequelize-cli').Migration} */
4+
module.exports = {
5+
up: async (queryInterface) => queryInterface.sequelize.transaction(async () => {
6+
await queryInterface.sequelize.query(`UPDATE quality_control_flags qcf INNER JOIN runs r ON qcf.run_number = r.run_number
7+
SET qcf.\`from\` = NULL
8+
WHERE r.qc_time_start IS NOT NULL
9+
AND qcf.\`from\` = r.qc_time_start`);
10+
11+
await queryInterface.sequelize.query(`UPDATE quality_control_flag_effective_periods qcfep
12+
INNER JOIN quality_control_flags qcf ON qcfep.flag_id = qcf.id
13+
INNER JOIN runs r ON qcf.run_number = r.run_number
14+
SET qcfep.\`from\` = NULL
15+
WHERE r.qc_time_start IS NOT NULL
16+
AND qcfep.\`from\` = r.qc_time_start`);
17+
18+
await queryInterface.sequelize.query(`UPDATE quality_control_flags qcf INNER JOIN runs r ON qcf.run_number = r.run_number
19+
SET qcf.\`to\` = NULL
20+
WHERE r.qc_time_end IS NOT NULL
21+
AND qcf.\`to\` = r.qc_time_end`);
22+
23+
await queryInterface.sequelize.query(`UPDATE quality_control_flag_effective_periods qcfep
24+
INNER JOIN quality_control_flags qcf ON qcfep.flag_id = qcf.id
25+
INNER JOIN runs r ON qcf.run_number = r.run_number
26+
SET qcfep.\`to\` = NULL
27+
WHERE r.qc_time_end IS NOT NULL
28+
AND qcfep.\`to\` = r.qc_time_end`);
29+
}),
30+
31+
down: async (queryInterface) => queryInterface.sequelize.transaction(async () => {
32+
await queryInterface.sequelize.query(`UPDATE quality_control_flags qcf INNER JOIN runs r ON qcf.run_number = r.run_number
33+
SET qcf.\`from\` = r.qc_time_start
34+
WHERE r.qc_time_start IS NOT NULL
35+
AND qcf.\`from\` IS NULL`);
36+
37+
await queryInterface.sequelize.query(`UPDATE quality_control_flag_effective_periods qcfep
38+
INNER JOIN quality_control_flags qcf ON qcfep.flag_id = qcf.id
39+
INNER JOIN runs r ON qcf.run_number = r.run_number
40+
SET qcfep.\`from\` = r.qc_time_start
41+
WHERE r.qc_time_start IS NOT NULL
42+
AND qcfep.\`from\` IS NULL`);
43+
44+
await queryInterface.sequelize.query(`UPDATE quality_control_flags qcf INNER JOIN runs r ON qcf.run_number = r.run_number
45+
SET qcf.\`to\` = r.qc_time_end
46+
WHERE r.qc_time_end IS NOT NULL
47+
AND qcf.\`to\` IS NULL`);
48+
49+
await queryInterface.sequelize.query(`UPDATE quality_control_flag_effective_periods qcfep
50+
INNER JOIN quality_control_flags qcf ON qcfep.flag_id = qcf.id
51+
INNER JOIN runs r ON qcf.run_number = r.run_number
52+
SET qcfep.\`to\` = r.qc_time_end
53+
WHERE r.qc_time_end IS NOT NULL
54+
AND qcfep.\`to\` IS NULL`);
55+
}),
56+
};

lib/database/models/run.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ module.exports = (sequelize) => {
6565
return (runEndString ? new Date(runEndString) : new Date()).getTime();
6666
},
6767
},
68+
qcTimeStart: {
69+
type: Sequelize.DATE,
70+
},
71+
qcTimeEnd: {
72+
type: Sequelize.DATE,
73+
},
6874
runDuration: {
6975
type: Sequelize.VIRTUAL,
7076
// eslint-disable-next-line require-jsdoc

lib/database/models/typedefs/SequelizeQcFlag.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
*
1717
* @property {number} id
1818
* @property {boolean} deleted
19-
* @property {number} from
20-
* @property {number} to
19+
* @property {number|null} from
20+
* @property {number|null} to
2121
* @property {string} comment
2222
* @property {string} origin
2323
* @property {number} createdById

lib/database/models/typedefs/SequelizeRun.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
* @property {number|null} userIdO2Stop relation to the user id that stopped the run
2626
* @property {number|null} startTime timestamp of the run's start, either trigger start if it exists or o2 start or null
2727
* @property {number|null} endTime timestamp of the run's end, either trigger end if it exists or o2 end or now (always null if start is null)
28+
* @property {number|null} qcTimeStart coalesce of run first TF timestamp, trigger start and run o2 start
29+
* @property {number|null} qcTimeEnd coalesce of run last TF timestamp, trigger stop and run o2 end
2830
* @property {string|null} environmentId
2931
* @property {string} runQuality
3032
* @property {number|null} nDetectors

lib/database/repositories/QcFlagEffectivePeriodRepository.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@ class QcFlagEffectivePeriodRepository extends Repository {
3232
*
3333
* @param {Period} period period which effective periods must overlap with
3434
* @param {number|Date} createdAtUpperLimit upper limit of QC flags creation timestamp which effective periods are to be found
35+
* @param {object} monalisaProduction the scope of the flag
3536
* @param {number} [monalisaProduction.dataPassId] id of data pass, which the QC flag belongs to
3637
* @param {number} [monalisaProduction.simulationPassId] id of simulation pass, which the QC flags belongs to
3738
* @param {number} monalisaProduction.runNumber runNumber of run, which the QC flags belongs to
3839
* @param {number} monalisaProduction.detectorId id of DPL detector, which the QC flags belongs to
3940
* @return {Promise<SequelizeQcFlagEffectivePeriod[]>} effective periods promise
4041
*/
41-
async findOverlappingPeriodsCreatedNotAfter(period, createdAtUpperLimit, { dataPassId, simulationPassId, runNumber, detectorId }) {
42+
async findOverlappingPeriodsCreatedBeforeLimit(period, createdAtUpperLimit, { dataPassId, simulationPassId, runNumber, detectorId }) {
4243
const { to, from } = period;
4344

4445
const flagIncludes = [];

lib/database/repositories/QcFlagRepository.js

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,22 @@ const { models: { QcFlag } } = require('..');
1616
const Repository = require('./Repository');
1717

1818
const GAQ_PERIODS_VIEW = `
19+
SELECT * FROM (
1920
SELECT
2021
data_pass_id,
2122
run_number,
22-
timestamp AS \`from\`,
23-
NTH_VALUE(timestamp, 2) OVER (
24-
PARTITION BY data_pass_id,
25-
run_number
26-
ORDER BY ap.timestamp
27-
ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING
28-
) AS \`to\`
23+
LAG(timestamp) OVER w AS \`from\`,
24+
timestamp AS \`to\`,
25+
LAG(ordering_timestamp) OVER w AS from_ordering_timestamp
2926
FROM (
3027
(
3128
SELECT gaqd.data_pass_id,
3229
gaqd.run_number,
33-
COALESCE(UNIX_TIMESTAMP(qcfep.\`from\`), 0) AS timestamp
30+
COALESCE(qcfep.\`from\`, r.qc_time_start) AS timestamp,
31+
COALESCE(qcfep.\`from\`, r.qc_time_start, '0001-01-01 00:00:00.000') AS ordering_timestamp
3432
FROM quality_control_flag_effective_periods AS qcfep
3533
INNER JOIN quality_control_flags AS qcf ON qcf.id = qcfep.flag_id
34+
INNER JOIN runs AS r ON qcf.run_number = r.run_number
3635
INNER JOIN data_pass_quality_control_flag AS dpqcf ON dpqcf.quality_control_flag_id = qcf.id
3736
-- Only flags of detectors which are defined in global_aggregated_quality_detectors
3837
-- should be taken into account for calculation of gaq_effective_periods
@@ -45,9 +44,11 @@ const GAQ_PERIODS_VIEW = `
4544
(
4645
SELECT gaqd.data_pass_id,
4746
gaqd.run_number,
48-
UNIX_TIMESTAMP(COALESCE(qcfep.\`to\`, NOW())) AS timestamp
47+
COALESCE(qcfep.\`to\`, r.qc_time_end) AS timestamp,
48+
COALESCE(qcfep.\`to\`, r.qc_time_end, NOW()) AS ordering_timestamp
4949
FROM quality_control_flag_effective_periods AS qcfep
5050
INNER JOIN quality_control_flags AS qcf ON qcf.id = qcfep.flag_id
51+
INNER JOIN runs AS r ON qcf.run_number = r.run_number
5152
INNER JOIN data_pass_quality_control_flag AS dpqcf ON dpqcf.quality_control_flag_id = qcf.id
5253
-- Only flags of detectors which are defined in global_aggregated_quality_detectors
5354
-- should be taken into account for calculation of gaq_effective_periods
@@ -56,8 +57,15 @@ const GAQ_PERIODS_VIEW = `
5657
AND gaqd.run_number = qcf.run_number
5758
AND gaqd.detector_id = qcf.detector_id
5859
)
59-
ORDER BY timestamp
60-
) AS ap
60+
ORDER BY ordering_timestamp
61+
) AS ap
62+
WINDOW w AS (
63+
PARTITION BY data_pass_id,
64+
run_number
65+
ORDER BY ap.ordering_timestamp
66+
)
67+
) as gaq_periods_with_last_nullish_row
68+
WHERE gaq_periods_with_last_nullish_row.from_ordering_timestamp IS NOT NULL
6169
`;
6270

6371
/**
@@ -104,8 +112,8 @@ class QcFlagRepository extends Repository {
104112
SELECT
105113
gaq_periods.data_pass_id AS dataPassId,
106114
gaq_periods.run_number AS runNumber,
107-
IF(gaq_periods.\`from\` = 0, null, gaq_periods.\`from\` * 1000) AS \`from\`,
108-
IF(gaq_periods.\`to\` = UNIX_TIMESTAMP(NOW()), null, gaq_periods.\`to\` * 1000) AS \`to\`,
115+
gaq_periods.\`from\` AS \`from\`,
116+
gaq_periods.\`to\` AS \`to\`,
109117
group_concat(qcf.id) AS contributingFlagIds
110118
111119
FROM quality_control_flags AS qcf
@@ -118,8 +126,8 @@ class QcFlagRepository extends Repository {
118126
AND gaqd.run_number = gaq_periods.run_number
119127
AND gaqd.detector_id = qcf.detector_id
120128
AND gaq_periods.run_number = qcf.run_number
121-
AND (qcfep.\`from\` IS NULL OR UNIX_TIMESTAMP(qcfep.\`from\`) <= gaq_periods.\`from\`)
122-
AND (qcfep.\`to\` IS NULL OR gaq_periods.\`to\` <= UNIX_TIMESTAMP(qcfep.\`to\`))
129+
AND (qcfep.\`from\` IS NULL OR qcfep.\`from\` <= gaq_periods.\`from\`)
130+
AND (qcfep.\`to\` IS NULL OR gaq_periods.\`to\` <= qcfep.\`to\`)
123131
124132
WHERE gaq_periods.data_pass_id = ${dataPassId}
125133
${runNumber ? `AND gaq_periods.run_number = ${runNumber}` : ''}
@@ -140,8 +148,8 @@ class QcFlagRepository extends Repository {
140148
}) => ({
141149
dataPassId,
142150
runNumber,
143-
from,
144-
to,
151+
from: from?.getTime(),
152+
to: to?.getTime(),
145153
contributingFlagIds: contributingFlagIds.split(',').map((id) => parseInt(id, 10)),
146154
}));
147155
}
@@ -160,8 +168,8 @@ class QcFlagRepository extends Repository {
160168
SELECT
161169
gaq_periods.data_pass_id AS dataPassId,
162170
gaq_periods.run_number AS runNumber,
163-
IF(gaq_periods.\`from\` = 0, null, gaq_periods.\`from\`) AS \`from\`,
164-
IF(gaq_periods.\`to\` = UNIX_TIMESTAMP(NOW()), null, gaq_periods.\`to\`) AS \`to\`,
171+
gaq_periods.\`from\` AS \`from\`,
172+
gaq_periods.\`to\` AS \`to\`,
165173
SUM(IF(qcft.monte_carlo_reproducible AND :mcReproducibleAsNotBad, false, qcft.bad)) >= 1 AS bad,
166174
SUM(qcft.bad) = SUM(qcft.monte_carlo_reproducible) AND SUM(qcft.monte_carlo_reproducible) AS mcReproducible,
167175
GROUP_CONCAT( DISTINCT qcfv.flag_id ) AS verifiedFlagsList,
@@ -183,8 +191,8 @@ class QcFlagRepository extends Repository {
183191
AND gaqd.run_number = gaq_periods.run_number
184192
AND gaqd.detector_id = qcf.detector_id
185193
AND gaq_periods.run_number = qcf.run_number
186-
AND (qcfep.\`from\` IS NULL OR UNIX_TIMESTAMP(qcfep.\`from\`) <= gaq_periods.\`from\`)
187-
AND (qcfep.\`to\` IS NULL OR gaq_periods.\`to\` <= UNIX_TIMESTAMP(qcfep.\`to\`))
194+
AND (qcfep.\`from\` IS NULL OR qcfep.\`from\` <= gaq_periods.\`from\`)
195+
AND (qcfep.\`to\` IS NULL OR gaq_periods.\`to\` <= qcfep.\`to\`)
188196
189197
GROUP BY
190198
gaq_periods.data_pass_id,
@@ -203,18 +211,16 @@ class QcFlagRepository extends Repository {
203211
GROUP_CONCAT(effectivePeriods.flagsList) AS flagsList,
204212
205213
IF(
206-
run.time_start IS NULL OR run.time_end IS NULL,
214+
run.qc_time_start IS NULL OR run.qc_time_end IS NULL,
207215
IF(
208216
effectivePeriods.\`from\` IS NULL AND effectivePeriods.\`to\` IS NULL,
209217
1,
210218
null
211219
),
212220
SUM(
213-
COALESCE(effectivePeriods.\`to\`, UNIX_TIMESTAMP(run.time_end))
214-
- COALESCE(effectivePeriods.\`from\`, UNIX_TIMESTAMP(run.time_start))
215-
) / (
216-
UNIX_TIMESTAMP(run.time_end) - UNIX_TIMESTAMP(run.time_start)
217-
)
221+
UNIX_TIMESTAMP(COALESCE(effectivePeriods.\`to\`,run.qc_time_end))
222+
- UNIX_TIMESTAMP(COALESCE(effectivePeriods.\`from\`, run.qc_time_start))
223+
) / (UNIX_TIMESTAMP(run.qc_time_end) - UNIX_TIMESTAMP(run.qc_time_start))
218224
) AS effectiveRunCoverage
219225
220226
FROM (${effectivePeriodsWithTypeSubQuery}) AS effectivePeriods
@@ -236,14 +242,20 @@ class QcFlagRepository extends Repository {
236242
mcReproducible,
237243
flagsList,
238244
verifiedFlagsList,
239-
}) => ({
240-
runNumber,
241-
bad,
242-
effectiveRunCoverage: parseFloat(effectiveRunCoverage) || null,
243-
mcReproducible: Boolean(mcReproducible),
244-
flagsIds: [...new Set(flagsList.split(','))],
245-
verifiedFlagsIds: verifiedFlagsList ? [...new Set(verifiedFlagsList.split(','))] : [],
246-
}));
245+
}) => {
246+
if ((effectiveRunCoverage ?? null) != null) {
247+
effectiveRunCoverage = Math.min(1, Math.max(0, parseFloat(effectiveRunCoverage)));
248+
}
249+
250+
return {
251+
runNumber,
252+
bad,
253+
effectiveRunCoverage,
254+
mcReproducible: Boolean(mcReproducible),
255+
flagsIds: [...new Set(flagsList.split(','))],
256+
verifiedFlagsIds: verifiedFlagsList ? [...new Set(verifiedFlagsList.split(','))] : [],
257+
};
258+
});
247259
}
248260

249261
/**

lib/database/seeders/20200713103855-runs.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2509,6 +2509,8 @@ module.exports = {
25092509
{
25102510
id: 100,
25112511
run_number: 100,
2512+
time_o2_end: '2019-08-08 13:00:00',
2513+
time_trg_end: '2019-08-08 14:23:45',
25122514
run_type_id: 12,
25132515
run_quality: 'test',
25142516
n_detectors: 3,
@@ -2661,7 +2663,6 @@ module.exports = {
26612663
time_o2_end: '2019-08-09 14:00:00',
26622664
time_trg_start: '2019-08-08 13:00:00',
26632665
time_trg_end: '2019-08-09 14:00:00',
2664-
first_tf_timestamp: '2019-08-09 13:00:00',
26652666
run_type_id: 12,
26662667
run_quality: 'good',
26672668
n_detectors: 15,
@@ -2737,9 +2738,10 @@ module.exports = {
27372738
id: 108,
27382739
run_number: 108,
27392740
time_o2_start: '2019-08-08 13:00:00',
2740-
time_o2_end: '2019-08-09 14:00:00',
2741+
first_tf_timestamp: '2019-08-08 13:00:00',
27412742
time_trg_start: '2019-08-08 13:00:00',
27422743
time_trg_end: '2019-08-09 14:00:00',
2744+
time_o2_end: '2019-08-09 14:00:00',
27432745
run_type_id: 12,
27442746
run_quality: 'good',
27452747
n_detectors: 15,

lib/database/seeders/20240112102011-data-passes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ module.exports = {
203203
{ data_pass_id: 1, run_number: 106, ready_for_skimming: true },
204204
{ data_pass_id: 1, run_number: 107, ready_for_skimming: false },
205205
{ data_pass_id: 1, run_number: 108, ready_for_skimming: false },
206+
206207
{ data_pass_id: 2, run_number: 1 },
207208
{ data_pass_id: 2, run_number: 2 },
208209
{ data_pass_id: 2, run_number: 55 },
@@ -215,6 +216,7 @@ module.exports = {
215216
{ data_pass_id: 4, run_number: 49 },
216217
{ data_pass_id: 4, run_number: 54 },
217218
{ data_pass_id: 4, run_number: 56 },
219+
{ data_pass_id: 4, run_number: 100 },
218220
{ data_pass_id: 4, run_number: 105 },
219221

220222
{ data_pass_id: 5, run_number: 49 },

0 commit comments

Comments
 (0)