Skip to content

Commit b3fde9f

Browse files
committed
A
2 parents 0613f66 + 8393baa commit b3fde9f

7 files changed

Lines changed: 192 additions & 18 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: release
22
permissions:
3-
contents: read
3+
contents: write
44
packages: write
55
actions: write
66
on:

lib/database/adapters/RunAdapter.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ class RunAdapter {
287287
entityObject.muInelasticInteractionRate = null;
288288
}
289289

290+
entityObject.collidingBunchesCount = lhcFill
291+
? lhcFill.collidingBunchesCount ?? extractNumberOfCollidingLhcBunchCrossings(lhcFill.fillingSchemeName)
292+
: null;
293+
290294
const adaptedQcFlags = qcFlags ? qcFlags.map(this.qcFlagAdapter.toEntity) : [];
291295
entityObject.qcFlags = adaptedQcFlags.reduce((acc, qcFlag) => {
292296
acc[qcFlag.dplDetectorId] = acc[qcFlag.dplDetectorId] ?? [];

lib/domain/dtos/filters/RunFilterDto.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,24 @@ exports.RunFilterDto = Joi.object({
117117
),
118118
mcReproducibleAsNotBad: Joi.boolean().optional(),
119119
}),
120+
121+
detectorsQc: Joi.object()
122+
.pattern(
123+
Joi.string().regex(/^_\d+$/), // Detector id with '_' prefix
124+
Joi.object({ notBadFraction: FloatComparisonDto }),
125+
)
126+
.keys({
127+
mcReproducibleAsNotBad: Joi.boolean().optional(),
128+
})
129+
.custom((detectorsQcObj, helpers) => {
130+
const [{ dataPassIds, simulationPassIds, lhcPeriodIds }] = helpers.state.ancestors;
131+
const runsCollectionFilters = [dataPassIds, simulationPassIds, lhcPeriodIds].filter(({ length } = {}) => length >= 1);
132+
133+
if (runsCollectionFilters.length !== 1 || runsCollectionFilters[0].length !== 1) {
134+
return helpers.message('Filtering by detector not-bad fraction is allowed only with exactly one of: ' +
135+
'dataPassIds, simulationPassIds, lhcPeriodIds with exactly one ID.');
136+
}
137+
138+
return detectorsQcObj;
139+
}),
120140
});

lib/server/services/qualityControlFlag/QcFlagSummaryService.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,15 @@ class QcFlagSummaryService {
7878
* @param {number} [scope.dataPassId] data pass id - exclusive with other options
7979
* @param {number} [scope.simulationPassId] simulation pass id - exclusive with other options
8080
* @param {number} [scope.lhcPeriodId] id of LHC Period - exclusive with other options
81+
* @param {number[]} [scope.dplDetectorIds] ids of dpl detectors
8182
* @param {object} [options] additional options
8283
* @param {boolean} [options.mcReproducibleAsNotBad = false] if set to true, `Limited Acceptance MC Reproducible` flag type is treated as
8384
* good one
8485
* @return {Promise<QcSummary>} summary
8586
*/
86-
async getSummary({ dataPassId, simulationPassId, lhcPeriodId }, { mcReproducibleAsNotBad = false } = {}) {
87-
if (Boolean(dataPassId) + Boolean(simulationPassId) + Boolean(lhcPeriodId) > 1) {
88-
throw new BadParameterError('`dataPassId`, `simulationPassId` and `lhcPeriodId` are exclusive options');
87+
async getSummary({ dataPassId, simulationPassId, lhcPeriodId, dplDetectorIds }, { mcReproducibleAsNotBad = false } = {}) {
88+
if (Boolean(dataPassId) + Boolean(simulationPassId) + Boolean(lhcPeriodId) !== 1) {
89+
throw new BadParameterError('One and exactly one of `dataPassId`, `simulationPassId` and `lhcPeriodId` is required');
8990
}
9091

9192
const queryBuilder = dataSource.createQueryBuilder()
@@ -107,6 +108,10 @@ class QcFlagSummaryService {
107108
.include({ association: 'run', attributes: [], where: { lhcPeriodId } });
108109
}
109110

111+
if (dplDetectorIds) {
112+
queryBuilder.where('detectorId').oneOf(...dplDetectorIds);
113+
}
114+
110115
queryBuilder
111116
.include({ association: 'effectivePeriods', attributes: [], required: true })
112117
.include({ association: 'flagType', attributes: [] })

lib/usecases/run/GetAllRunsUseCase.js

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const { EorReasonRepository } = require('../../database/repositories');
2121
const { PhysicalConstant } = require('../../domain/enums/PhysicalConstant');
2222
const { BadParameterError } = require('../../server/errors/BadParameterError');
2323
const { gaqService } = require('../../server/services/qualityControlFlag/GaqService.js');
24+
const { qcFlagSummaryService } = require('../../server/services/qualityControlFlag/QcFlagSummaryService.js');
2425

2526
/**
2627
* GetAllRunsUseCase
@@ -77,6 +78,7 @@ class GetAllRunsUseCase {
7778
inelasticInteractionRateAtMid,
7879
inelasticInteractionRateAtEnd,
7980
gaq,
81+
detectorsQc,
8082
} = filter;
8183

8284
if (runNumbers) {
@@ -368,6 +370,21 @@ class GetAllRunsUseCase {
368370
filteringQueryBuilder.where('runNumber').oneOf(...runNumbers);
369371
}
370372

373+
const badEffectiveRunCoverageComparison = (badEffectiveRunCoverage, operator, value) => {
374+
switch (operator) {
375+
case '<':
376+
return 1 - badEffectiveRunCoverage < value;
377+
case '<=':
378+
return 1 - badEffectiveRunCoverage <= value;
379+
case '=':
380+
return 1 - badEffectiveRunCoverage === value;
381+
case '>':
382+
return 1 - badEffectiveRunCoverage > value;
383+
case '>=':
384+
return 1 - badEffectiveRunCoverage >= value;
385+
}
386+
};
387+
371388
if (gaq) {
372389
if ((dataPassIds ?? []).length !== 1) {
373390
throw new BadParameterError('Filtering by GAQ is enabled only when filtering with one dataPassId');
@@ -379,21 +396,46 @@ class GetAllRunsUseCase {
379396
if (gaq.notBadFraction) {
380397
const { operator, limit: value } = gaq.notBadFraction;
381398
const runNumbers = Object.entries(gaqSummary)
382-
.filter(([_, { badEffectiveRunCoverage }]) => {
383-
switch (operator) {
384-
case '<':
385-
return 1 - badEffectiveRunCoverage < value;
386-
case '<=':
387-
return 1 - badEffectiveRunCoverage <= value;
388-
case '=':
389-
return 1 - badEffectiveRunCoverage === value;
390-
case '>':
391-
return 1 - badEffectiveRunCoverage > value;
392-
case '>=':
393-
return 1 - badEffectiveRunCoverage >= value;
394-
}
399+
.filter(([_, { badEffectiveRunCoverage }]) =>
400+
badEffectiveRunCoverageComparison(badEffectiveRunCoverage, operator, value))
401+
.map(([runNumber]) => runNumber);
402+
filteringQueryBuilder.where('runNumber').oneOf(...runNumbers);
403+
}
404+
}
405+
406+
if (detectorsQc) {
407+
const [dataPassId] = dataPassIds ?? [];
408+
const [simulationPassId] = simulationPassIds ?? [];
409+
const [lhcPeriodId] = lhcPeriodIds ?? [];
410+
const { mcReproducibleAsNotBad } = detectorsQc;
411+
delete detectorsQc.mcReproducibleAsNotBad;
412+
413+
const dplDetectorIds = Object.keys(detectorsQc).map((id) => parseInt(id.slice(1), 10));
414+
if (dplDetectorIds.length > 0) {
415+
const qcSummary = await qcFlagSummaryService.getSummary(
416+
{
417+
dataPassId,
418+
simulationPassId,
419+
lhcPeriodId,
420+
dplDetectorIds,
421+
},
422+
{ mcReproducibleAsNotBad },
423+
);
424+
425+
const runNumbers = Object.entries(qcSummary)
426+
.filter(([_, runSummary]) => {
427+
const mask = Object.entries(detectorsQc).map(([prefixedDetectorId, { notBadFraction: { operator, limit } }]) => {
428+
const dplDetectorId = parseInt(prefixedDetectorId.slice(1), 10);
429+
if (!(dplDetectorId in runSummary)) {
430+
return false;
431+
}
432+
return badEffectiveRunCoverageComparison(runSummary[dplDetectorId].badEffectiveRunCoverage, operator, limit);
433+
});
434+
435+
return mask.every((valid) => valid);
395436
})
396437
.map(([runNumber]) => runNumber);
438+
397439
filteringQueryBuilder.where('runNumber').oneOf(...runNumbers);
398440
}
399441
}
@@ -411,6 +453,19 @@ class GetAllRunsUseCase {
411453
filteringQueryBuilder.where('runNumber').oneOf(...runNumbers);
412454
}
413455

456+
if (lhcPeriodIds) {
457+
const runNumbers = (await RunRepository.findAll({
458+
attributes: ['runNumber'],
459+
raw: true,
460+
include: {
461+
association: 'lhcPeriods',
462+
attributes: [],
463+
where: { id: { [Op.in]: lhcPeriodIds } },
464+
},
465+
})).map(({ runNumber }) => runNumber);
466+
filteringQueryBuilder.where('runNumber').oneOf(...runNumbers);
467+
}
468+
414469
if (tags?.values?.length) {
415470
if (tags.operation === 'and') {
416471
const runsWithExpectedTags = await RunRepository.findAll({

test/api/runs.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,42 @@ module.exports = () => {
423423
}
424424
});
425425

426+
it('should successfully filter by detectors notBadFraction', async () => {
427+
const dataPassId = 1;
428+
{
429+
const response = await request(server).get(`/api/runs?filter[dataPassIds][]=${dataPassId}`
430+
+ '&filter[detectorsQc][_1][notBadFraction][operator]=>&filter[detectorsQc][_1][notBadFraction][limit]=0.7');
431+
432+
expect(response.status).to.equal(200);
433+
const { data: runs } = response.body;
434+
435+
expect(runs).to.be.an('array');
436+
expect(runs.map(({ runNumber }) => runNumber)).to.have.all.members([107]);
437+
}
438+
{
439+
const response = await request(server).get(`/api/runs?filter[dataPassIds][]=${dataPassId}`
440+
+ '&filter[detectorsQc][_1][notBadFraction][operator]=<&filter[detectorsQc][_1][notBadFraction][limit]=0.9&filter[detectorsQc][mcReproducibleAsNotBad]=true');
441+
442+
expect(response.status).to.equal(200);
443+
const { data: runs } = response.body;
444+
445+
expect(runs).to.be.an('array');
446+
expect(runs.map(({ runNumber }) => runNumber)).to.have.all.members([106]);
447+
}
448+
{
449+
const response = await request(server).get(`/api/runs?filter[dataPassIds][]=${dataPassId}`
450+
+ '&filter[detectorsQc][_1][notBadFraction][operator]=<&filter[detectorsQc][_1][notBadFraction][limit]=0.7'
451+
+ '&filter[detectorsQc][_16][notBadFraction][operator]=>&filter[detectorsQc][_16][notBadFraction][limit]=0.9'
452+
);
453+
454+
expect(response.status).to.equal(200);
455+
const { data: runs } = response.body;
456+
457+
expect(runs).to.be.an('array');
458+
expect(runs.map(({ runNumber }) => runNumber)).to.have.all.members([106]);
459+
}
460+
});
461+
426462
it('should return http status 400 if updatedAt from larger than to', async () => {
427463
const timeNow = Date.now();
428464
const response =

test/lib/usecases/run/GetAllRunsUseCase.test.js

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,8 +797,62 @@ module.exports = () => {
797797
expect(runs).to.have.lengthOf(0);
798798
}
799799
});
800+
801+
it('should successfully filter by detectors notBadFraction', async () => {
802+
const dataPassIds = [1];
803+
{
804+
const { runs } = await new GetAllRunsUseCase().execute({
805+
query: {
806+
filter: {
807+
dataPassIds,
808+
detectorsQc: { '_1': { notBadFraction: { operator: '<', limit: 0.7 } } },
809+
},
810+
},
811+
});
812+
expect(runs).to.be.an('array');
813+
expect(runs.map(({ runNumber }) => runNumber)).to.have.all.members([106]);
814+
}
815+
{
816+
const { runs } = await new GetAllRunsUseCase().execute({
817+
query: {
818+
filter: {
819+
dataPassIds,
820+
detectorsQc: { '_1': { notBadFraction: { operator: '<', limit: 0.8 } } },
821+
},
822+
},
823+
});
824+
expect(runs.map(({ runNumber }) => runNumber)).to.have.all.members([107, 106]);
825+
}
826+
827+
{
828+
const { runs } = await new GetAllRunsUseCase().execute({
829+
query: {
830+
filter: {
831+
dataPassIds,
832+
detectorsQc: { '_1': { notBadFraction: { operator: '<', limit: 0.9 } }, mcReproducibleAsNotBad: true },
833+
},
834+
},
835+
});
836+
expect(runs.map(({ runNumber }) => runNumber)).to.have.all.members([106]);
837+
}
838+
839+
{
840+
const { runs } = await new GetAllRunsUseCase().execute({
841+
query: {
842+
filter: {
843+
dataPassIds,
844+
detectorsQc: {
845+
'_2': { notBadFraction: { operator: '>', limit: 0.8 } },
846+
'_1': { notBadFraction: {operator: '<', limit: 0.8 } },
847+
},
848+
},
849+
},
850+
});
851+
expect(runs.map(({ runNumber }) => runNumber)).to.have.all.members([107]);
852+
}
853+
});
800854

801-
it('should successfully handle query including QC flags', async () => {
855+
it('should successfully handle query including QC flags', async () => {
802856
{
803857
await assert.rejects(() => new GetAllRunsUseCase().execute({
804858
query: {

0 commit comments

Comments
 (0)