Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions confiture-rest-api/scripts/yearly-stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/** Calculate some yearly stats. */
import { Audit, AuditType, PrismaClient } from "@prisma/client";
import { AuditService } from "../src/audits/audit.service";

const EMAIL_BLACKLIST = [
"etienne-dupont@example.com",
"test@test.fr",
"test@test.com",
"test@gmail.com",
"toto@toto.fr",
"benoit.dequick@modernisation.gouv.fr",
"adrien.muzyczka@modernisation.gouv.fr",
"adrien.muzyczka@gmail.com"
];

// regexes to filter out emails with aliases
const EMAIL_BLACKLIST_REGEXES = EMAIL_BLACKLIST.map((email) => {
const [username, domain] = email.split("@");
return new RegExp(`${username}(\\+.+)?${domain}`);
});

const filterTestAudits = (audit: Pick<Audit, "auditorEmail">): boolean => !EMAIL_BLACKLIST_REGEXES.some((regex) => regex.test(audit.auditorEmail));

async function getStatsOnDateInterval(prisma: PrismaClient, startDate: Date, endDate: Date) {
const createdAudits2024 = (await prisma.audit.findMany({
where: {
auditorEmail: {
notIn: EMAIL_BLACKLIST
},
creationDate: {
gte: startDate,
lt: endDate
}
},
select: {
auditorEmail: true
}
})).filter(filterTestAudits).length;

const createdFullAudits2024 = (await prisma.audit.findMany({
where: {
auditType: AuditType.FULL,
auditorEmail: {
notIn: EMAIL_BLACKLIST
},
creationDate: {
gte: startDate,
lt: endDate
}
},
select: {
auditorEmail: true
}
})).filter(filterTestAudits).length;

const finishedFullAudits2024 = (await prisma.audit.findMany({
where: {
auditType: AuditType.FULL,
auditorEmail: {
notIn: EMAIL_BLACKLIST
},
publicationDate: {
gte: startDate,
lt: endDate
}
},
select: {
auditorEmail: true
}
})).filter(filterTestAudits).length;

const auditsWithStatements2024 = (await prisma.audit.findMany({
where: {
auditorEmail: {
notIn: EMAIL_BLACKLIST
},
publicationDate: {
gte: startDate,
lt: endDate
},
initiator: {
not: null
}
},
include: {
pages: {
include: {
results: true
}
},
transverseElementsPage: {
include: { results: true }
}
}
})).filter(filterTestAudits);

const filledStatements2024 = auditsWithStatements2024.length;

const auditRates = auditsWithStatements2024.map(audit => {
const results = [audit.pages.map(p => p.results), audit.transverseElementsPage.results].flat(2);
const { accessibilityRate } = AuditService.groupResultsByStatus(results, audit.transverseElementsPageId);
return accessibilityRate;
});

const compliants2024 = auditRates.filter(r => r >= 100).length;
const partiallyCompliants2024 = auditRates.filter(r => r >= 50 && r < 100).length;
const notCompliant2024 = auditRates.filter(r => r < 50).length;

return {
createdAudits2024,
createdFullAudits2024,
finishedFullAudits2024,
filledStatements2024,
compliants2024,
partiallyCompliants2024,
notCompliant2024
};
}

async function main() {
const prisma = new PrismaClient();

const stats2024 = await getStatsOnDateInterval(prisma, new Date("2024-01-01"), new Date("2025-01-01"));
const stats2025 = await getStatsOnDateInterval(prisma, new Date("2025-01-01"), new Date("2026-01-01"));

console.log(JSON.stringify({ stats2024, stats2025 }, null, 4));
}

main();
121 changes: 70 additions & 51 deletions confiture-rest-api/src/audits/audit.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -944,59 +944,16 @@ export class AuditService {
})
]).then(results => results.flat());

const groupedCriteria = results.reduce<Record<string, CriterionResult[]>>(
(acc, c) => {
const key = `${c.topic}.${c.criterium}`;
if (acc[key]) {
acc[key].push(c);
} else {
acc[key] = [c];
}
return acc;
},
{}
);

const applicableCriteria = Object.values(groupedCriteria).filter(
(criteria) => criteria.some((c) => isCompliant(c) || isNotCompliant(c))
);

const compliantCriteria = applicableCriteria.filter((criteria) => {
// remove untested transverse criterion
const withoutUntestedTrans = criteria.filter(
(c) =>
!isTransverse(c, audit.transverseElementsPageId) || !isNotTested(c)
);

return (
withoutUntestedTrans.some((c) => isCompliant(c)) &&
withoutUntestedTrans.every((c) => isCompliant(c) || isNotApplicable(c))
);
});

const notCompliantCriteria = applicableCriteria.filter((criteria) => {
return criteria.some((c) => isNotCompliant(c));
});

const notApplicableCriteria = Object.values(groupedCriteria).filter(
(criteria) => {
// remove untested transverse criterion
const withoutUntestedTrans = criteria.filter(
(c) =>
!isTransverse(c, audit.transverseElementsPageId) || !isNotTested(c)
);

return withoutUntestedTrans.every((c) => isNotApplicable(c));
}
);

const accessibilityRate =
Math.round(
(compliantCriteria.length / applicableCriteria.length) * 100
) || 0;

const totalCriteriaCount = CRITERIA_BY_AUDIT_TYPE[audit.auditType].length;

const {
compliantCriteria,
notCompliantCriteria,
applicableCriteria,
notApplicableCriteria,
accessibilityRate
} = AuditService.groupResultsByStatus(results, audit.transverseElementsPageId);

const report: AuditReportDto = {
consultUniqueId: audit.consultUniqueId,

Expand Down Expand Up @@ -1196,6 +1153,68 @@ export class AuditService {
return report;
}

public static groupResultsByStatus(results: CriterionResult[], transversePageId: number) {
const groupedCriteria = results.reduce<Record<string, CriterionResult[]>>(
(acc, c) => {
const key = `${c.topic}.${c.criterium}`;
if (acc[key]) {
acc[key].push(c);
} else {
acc[key] = [c];
}
return acc;
},
{}
);

const applicableCriteria = Object.values(groupedCriteria).filter(
(criteria) => criteria.some((c) => isCompliant(c) || isNotCompliant(c))
);

const compliantCriteria = applicableCriteria.filter((criteria) => {
// remove untested transverse criterion
const withoutUntestedTrans = criteria.filter(
(c) =>
!isTransverse(c, transversePageId) || !isNotTested(c)
);

return (
withoutUntestedTrans.some((c) => isCompliant(c)) &&
withoutUntestedTrans.every((c) => isCompliant(c) || isNotApplicable(c))
);
});

const notCompliantCriteria = applicableCriteria.filter((criteria) => {
return criteria.some((c) => isNotCompliant(c));
});

const notApplicableCriteria = Object.values(groupedCriteria).filter(
(criteria) => {
// remove untested transverse criterion
const withoutUntestedTrans = criteria.filter(
(c) =>
!isTransverse(c, transversePageId) || !isNotTested(c)
);

return withoutUntestedTrans.every((c) => isNotApplicable(c));
}
);

const accessibilityRate =
Math.round(
(compliantCriteria.length / applicableCriteria.length) * 100
) || 0;

return {
groupedCriteria,
applicableCriteria,
compliantCriteria,
notCompliantCriteria,
notApplicableCriteria,
accessibilityRate
};
}

async isAuditComplete(uniqueId: string): Promise<boolean> {
const audit = await this.findAuditWithEditUniqueId(uniqueId, {
pages: true
Expand Down