From 24b94eeddcf42602d8cece0f32a34a56a648646e Mon Sep 17 00:00:00 2001 From: Arnaud AMBROSELLI Date: Fri, 29 Mar 2024 11:51:47 +0100 Subject: [PATCH] fix: new data aggregation and display --- .../migration.sql | 10 + .../migration.sql | 106 ----- .../migration.sql | 94 ---- .../migration.sql | 33 -- .../migration.sql | 30 +- api-node/prisma/schema.prisma | 81 +--- api-node/src/aggregators/drinking_water.ts | 384 ++++++++++------ api-node/src/getters/drinking_water.ts | 56 ++- api-node/src/index.ts | 30 +- api-node/src/types/api/drinking_water.ts | 175 +++++-- api-node/src/types/api/indicator.ts | 26 +- api-node/src/utils/drinking_water.ts | 431 ++++-------------- api-node/src/utils/notifications/alert.ts | 12 +- 13 files changed, 603 insertions(+), 865 deletions(-) create mode 100644 api-node/prisma/migrations/20240314105019_drinking_water_enable/migration.sql delete mode 100644 api-node/prisma/migrations/20240314204424_drinking_water_parameters/migration.sql delete mode 100644 api-node/prisma/migrations/20240314211347_drinking_water_parameters_synthetic/migration.sql delete mode 100644 api-node/prisma/migrations/20240314211543_drinking_water_parameters_conformity/migration.sql rename api-node/prisma/migrations/{20240314105019_drinking_water => 20240328145103_drinking_water_table}/migration.sql (53%) diff --git a/api-node/prisma/migrations/20240314105019_drinking_water_enable/migration.sql b/api-node/prisma/migrations/20240314105019_drinking_water_enable/migration.sql new file mode 100644 index 00000000..04f18fce --- /dev/null +++ b/api-node/prisma/migrations/20240314105019_drinking_water_enable/migration.sql @@ -0,0 +1,10 @@ +-- AlterEnum +ALTER TYPE "IndicatorsSlugEnum" +ADD + VALUE 'drinking_water'; + +-- AlterTable +ALTER TABLE + "User" +ADD + COLUMN "udi" TEXT; \ No newline at end of file diff --git a/api-node/prisma/migrations/20240314204424_drinking_water_parameters/migration.sql b/api-node/prisma/migrations/20240314204424_drinking_water_parameters/migration.sql deleted file mode 100644 index 35c62373..00000000 --- a/api-node/prisma/migrations/20240314204424_drinking_water_parameters/migration.sql +++ /dev/null @@ -1,106 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `code_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `conclusion_conformite_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `conformite_limites_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `conformite_limites_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `conformite_references_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `conformite_references_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `date_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `hubeau_test_url` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `nom_distributeur` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `nom_moa` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `nom_reseau` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `nom_uge` on the `DrinkingWater` table. All the data in the column will be lost. - -*/ --- DropIndex -DROP INDEX "DrinkingWater_code_prelevement_key"; - --- AlterTable -ALTER TABLE "DrinkingWater" DROP COLUMN "code_prelevement", -DROP COLUMN "conclusion_conformite_prelevement", -DROP COLUMN "conformite_limites_bact_prelevement", -DROP COLUMN "conformite_limites_pc_prelevement", -DROP COLUMN "conformite_references_bact_prelevement", -DROP COLUMN "conformite_references_pc_prelevement", -DROP COLUMN "date_prelevement", -DROP COLUMN "hubeau_test_url", -DROP COLUMN "nom_distributeur", -DROP COLUMN "nom_moa", -DROP COLUMN "nom_reseau", -DROP COLUMN "nom_uge", -ADD COLUMN "ASP_code_prelevement" TEXT, -ADD COLUMN "ASP_conclusion_conformite_prelevement" TEXT, -ADD COLUMN "ASP_conformite_limites_bact_prelevement" TEXT, -ADD COLUMN "ASP_conformite_limites_pc_prelevement" TEXT, -ADD COLUMN "ASP_conformite_references_bact_prelevement" TEXT, -ADD COLUMN "ASP_conformite_references_pc_prelevement" TEXT, -ADD COLUMN "ASP_date_prelevement" TIMESTAMP(3), -ADD COLUMN "ASP_is_conform" BOOLEAN, -ADD COLUMN "ASP_value" TEXT, -ADD COLUMN "COULF_code_prelevement" TEXT, -ADD COLUMN "COULF_conclusion_conformite_prelevement" TEXT, -ADD COLUMN "COULF_conformite_limites_bact_prelevement" TEXT, -ADD COLUMN "COULF_conformite_limites_pc_prelevement" TEXT, -ADD COLUMN "COULF_conformite_references_bact_prelevement" TEXT, -ADD COLUMN "COULF_conformite_references_pc_prelevement" TEXT, -ADD COLUMN "COULF_date_prelevement" TIMESTAMP(3), -ADD COLUMN "COULF_is_conform" BOOLEAN, -ADD COLUMN "COULF_value" TEXT, -ADD COLUMN "COULQ_code_prelevement" TEXT, -ADD COLUMN "COULQ_conclusion_conformite_prelevement" TEXT, -ADD COLUMN "COULQ_conformite_limites_bact_prelevement" TEXT, -ADD COLUMN "COULQ_conformite_limites_pc_prelevement" TEXT, -ADD COLUMN "COULQ_conformite_references_bact_prelevement" TEXT, -ADD COLUMN "COULQ_conformite_references_pc_prelevement" TEXT, -ADD COLUMN "COULQ_date_prelevement" TIMESTAMP(3), -ADD COLUMN "COULQ_is_conform" BOOLEAN, -ADD COLUMN "COULQ_value" TEXT, -ADD COLUMN "ODQ_code_prelevement" TEXT, -ADD COLUMN "ODQ_conclusion_conformite_prelevement" TEXT, -ADD COLUMN "ODQ_conformite_limites_bact_prelevement" TEXT, -ADD COLUMN "ODQ_conformite_limites_pc_prelevement" TEXT, -ADD COLUMN "ODQ_conformite_references_bact_prelevement" TEXT, -ADD COLUMN "ODQ_conformite_references_pc_prelevement" TEXT, -ADD COLUMN "ODQ_date_prelevement" TIMESTAMP(3), -ADD COLUMN "ODQ_is_conform" BOOLEAN, -ADD COLUMN "ODQ_value" TEXT, -ADD COLUMN "PESTOT_code_prelevement" TEXT, -ADD COLUMN "PESTOT_conclusion_conformite_prelevement" TEXT, -ADD COLUMN "PESTOT_conformite_limites_bact_prelevement" TEXT, -ADD COLUMN "PESTOT_conformite_limites_pc_prelevement" TEXT, -ADD COLUMN "PESTOT_conformite_references_bact_prelevement" TEXT, -ADD COLUMN "PESTOT_conformite_references_pc_prelevement" TEXT, -ADD COLUMN "PESTOT_date_prelevement" TIMESTAMP(3), -ADD COLUMN "PESTOT_is_conform" BOOLEAN, -ADD COLUMN "PESTOT_value" TEXT, -ADD COLUMN "PH_code_prelevement" TEXT, -ADD COLUMN "PH_conclusion_conformite_prelevement" TEXT, -ADD COLUMN "PH_conformite_limites_bact_prelevement" TEXT, -ADD COLUMN "PH_conformite_limites_pc_prelevement" TEXT, -ADD COLUMN "PH_conformite_references_bact_prelevement" TEXT, -ADD COLUMN "PH_conformite_references_pc_prelevement" TEXT, -ADD COLUMN "PH_date_prelevement" TIMESTAMP(3), -ADD COLUMN "PH_is_conform" BOOLEAN, -ADD COLUMN "PH_value" TEXT, -ADD COLUMN "SAVQ_code_prelevement" TEXT, -ADD COLUMN "SAVQ_conclusion_conformite_prelevement" TEXT, -ADD COLUMN "SAVQ_conformite_limites_bact_prelevement" TEXT, -ADD COLUMN "SAVQ_conformite_limites_pc_prelevement" TEXT, -ADD COLUMN "SAVQ_conformite_references_bact_prelevement" TEXT, -ADD COLUMN "SAVQ_conformite_references_pc_prelevement" TEXT, -ADD COLUMN "SAVQ_date_prelevement" TIMESTAMP(3), -ADD COLUMN "SAVQ_is_conform" BOOLEAN, -ADD COLUMN "SAVQ_value" TEXT, -ADD COLUMN "TEAU_code_prelevement" TEXT, -ADD COLUMN "TEAU_conclusion_conformite_prelevement" TEXT, -ADD COLUMN "TEAU_conformite_limites_bact_prelevement" TEXT, -ADD COLUMN "TEAU_conformite_limites_pc_prelevement" TEXT, -ADD COLUMN "TEAU_conformite_references_bact_prelevement" TEXT, -ADD COLUMN "TEAU_conformite_references_pc_prelevement" TEXT, -ADD COLUMN "TEAU_date_prelevement" TIMESTAMP(3), -ADD COLUMN "TEAU_is_conform" BOOLEAN, -ADD COLUMN "TEAU_value" TEXT, -ADD COLUMN "hubeau_parameters_url" TEXT; diff --git a/api-node/prisma/migrations/20240314211347_drinking_water_parameters_synthetic/migration.sql b/api-node/prisma/migrations/20240314211347_drinking_water_parameters_synthetic/migration.sql deleted file mode 100644 index c652e2f7..00000000 --- a/api-node/prisma/migrations/20240314211347_drinking_water_parameters_synthetic/migration.sql +++ /dev/null @@ -1,94 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `ASP_conformite_limites_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `ASP_conformite_limites_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `ASP_conformite_references_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `ASP_conformite_references_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `ASP_is_conform` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `COULF_conformite_limites_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `COULF_conformite_limites_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `COULF_conformite_references_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `COULF_conformite_references_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `COULF_is_conform` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `COULQ_conformite_limites_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `COULQ_conformite_limites_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `COULQ_conformite_references_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `COULQ_conformite_references_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `COULQ_is_conform` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `ODQ_conformite_limites_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `ODQ_conformite_limites_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `ODQ_conformite_references_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `ODQ_conformite_references_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `ODQ_is_conform` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `PESTOT_conformite_limites_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `PESTOT_conformite_limites_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `PESTOT_conformite_references_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `PESTOT_conformite_references_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `PESTOT_is_conform` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `PH_conformite_limites_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `PH_conformite_limites_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `PH_conformite_references_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `PH_conformite_references_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `PH_is_conform` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `SAVQ_conformite_limites_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `SAVQ_conformite_limites_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `SAVQ_conformite_references_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `SAVQ_conformite_references_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `SAVQ_is_conform` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `TEAU_conformite_limites_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `TEAU_conformite_limites_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `TEAU_conformite_references_bact_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `TEAU_conformite_references_pc_prelevement` on the `DrinkingWater` table. All the data in the column will be lost. - - You are about to drop the column `TEAU_is_conform` on the `DrinkingWater` table. All the data in the column will be lost. - -*/ --- AlterTable -ALTER TABLE "DrinkingWater" DROP COLUMN "ASP_conformite_limites_bact_prelevement", -DROP COLUMN "ASP_conformite_limites_pc_prelevement", -DROP COLUMN "ASP_conformite_references_bact_prelevement", -DROP COLUMN "ASP_conformite_references_pc_prelevement", -DROP COLUMN "ASP_is_conform", -DROP COLUMN "COULF_conformite_limites_bact_prelevement", -DROP COLUMN "COULF_conformite_limites_pc_prelevement", -DROP COLUMN "COULF_conformite_references_bact_prelevement", -DROP COLUMN "COULF_conformite_references_pc_prelevement", -DROP COLUMN "COULF_is_conform", -DROP COLUMN "COULQ_conformite_limites_bact_prelevement", -DROP COLUMN "COULQ_conformite_limites_pc_prelevement", -DROP COLUMN "COULQ_conformite_references_bact_prelevement", -DROP COLUMN "COULQ_conformite_references_pc_prelevement", -DROP COLUMN "COULQ_is_conform", -DROP COLUMN "ODQ_conformite_limites_bact_prelevement", -DROP COLUMN "ODQ_conformite_limites_pc_prelevement", -DROP COLUMN "ODQ_conformite_references_bact_prelevement", -DROP COLUMN "ODQ_conformite_references_pc_prelevement", -DROP COLUMN "ODQ_is_conform", -DROP COLUMN "PESTOT_conformite_limites_bact_prelevement", -DROP COLUMN "PESTOT_conformite_limites_pc_prelevement", -DROP COLUMN "PESTOT_conformite_references_bact_prelevement", -DROP COLUMN "PESTOT_conformite_references_pc_prelevement", -DROP COLUMN "PESTOT_is_conform", -DROP COLUMN "PH_conformite_limites_bact_prelevement", -DROP COLUMN "PH_conformite_limites_pc_prelevement", -DROP COLUMN "PH_conformite_references_bact_prelevement", -DROP COLUMN "PH_conformite_references_pc_prelevement", -DROP COLUMN "PH_is_conform", -DROP COLUMN "SAVQ_conformite_limites_bact_prelevement", -DROP COLUMN "SAVQ_conformite_limites_pc_prelevement", -DROP COLUMN "SAVQ_conformite_references_bact_prelevement", -DROP COLUMN "SAVQ_conformite_references_pc_prelevement", -DROP COLUMN "SAVQ_is_conform", -DROP COLUMN "TEAU_conformite_limites_bact_prelevement", -DROP COLUMN "TEAU_conformite_limites_pc_prelevement", -DROP COLUMN "TEAU_conformite_references_bact_prelevement", -DROP COLUMN "TEAU_conformite_references_pc_prelevement", -DROP COLUMN "TEAU_is_conform", -ADD COLUMN "ASP_conformity" BOOLEAN, -ADD COLUMN "COULF_conformity" BOOLEAN, -ADD COLUMN "COULQ_conformity" BOOLEAN, -ADD COLUMN "ODQ_conformity" BOOLEAN, -ADD COLUMN "PESTOT_conformity" BOOLEAN, -ADD COLUMN "PH_conformity" BOOLEAN, -ADD COLUMN "SAVQ_conformity" BOOLEAN, -ADD COLUMN "TEAU_conformity" BOOLEAN; diff --git a/api-node/prisma/migrations/20240314211543_drinking_water_parameters_conformity/migration.sql b/api-node/prisma/migrations/20240314211543_drinking_water_parameters_conformity/migration.sql deleted file mode 100644 index fc610130..00000000 --- a/api-node/prisma/migrations/20240314211543_drinking_water_parameters_conformity/migration.sql +++ /dev/null @@ -1,33 +0,0 @@ -/* - Warnings: - - - Added the required column `ASP_conformity` to the `DrinkingWater` table without a default value. This is not possible if the table is not empty. - - Added the required column `COULF_conformity` to the `DrinkingWater` table without a default value. This is not possible if the table is not empty. - - Added the required column `COULQ_conformity` to the `DrinkingWater` table without a default value. This is not possible if the table is not empty. - - Added the required column `ODQ_conformity` to the `DrinkingWater` table without a default value. This is not possible if the table is not empty. - - Added the required column `PESTOT_conformity` to the `DrinkingWater` table without a default value. This is not possible if the table is not empty. - - Added the required column `PH_conformity` to the `DrinkingWater` table without a default value. This is not possible if the table is not empty. - - Added the required column `SAVQ_conformity` to the `DrinkingWater` table without a default value. This is not possible if the table is not empty. - - Added the required column `TEAU_conformity` to the `DrinkingWater` table without a default value. This is not possible if the table is not empty. - -*/ --- CreateEnum -CREATE TYPE "DrinkingWaterPrelevementConformity" AS ENUM ('C', 'D', 'N', 'S'); - --- AlterTable -ALTER TABLE "DrinkingWater" DROP COLUMN "ASP_conformity", -ADD COLUMN "ASP_conformity" "DrinkingWaterPrelevementConformity" NOT NULL, -DROP COLUMN "COULF_conformity", -ADD COLUMN "COULF_conformity" "DrinkingWaterPrelevementConformity" NOT NULL, -DROP COLUMN "COULQ_conformity", -ADD COLUMN "COULQ_conformity" "DrinkingWaterPrelevementConformity" NOT NULL, -DROP COLUMN "ODQ_conformity", -ADD COLUMN "ODQ_conformity" "DrinkingWaterPrelevementConformity" NOT NULL, -DROP COLUMN "PESTOT_conformity", -ADD COLUMN "PESTOT_conformity" "DrinkingWaterPrelevementConformity" NOT NULL, -DROP COLUMN "PH_conformity", -ADD COLUMN "PH_conformity" "DrinkingWaterPrelevementConformity" NOT NULL, -DROP COLUMN "SAVQ_conformity", -ADD COLUMN "SAVQ_conformity" "DrinkingWaterPrelevementConformity" NOT NULL, -DROP COLUMN "TEAU_conformity", -ADD COLUMN "TEAU_conformity" "DrinkingWaterPrelevementConformity" NOT NULL; diff --git a/api-node/prisma/migrations/20240314105019_drinking_water/migration.sql b/api-node/prisma/migrations/20240328145103_drinking_water_table/migration.sql similarity index 53% rename from api-node/prisma/migrations/20240314105019_drinking_water/migration.sql rename to api-node/prisma/migrations/20240328145103_drinking_water_table/migration.sql index 8c1635fe..059fa20d 100644 --- a/api-node/prisma/migrations/20240314105019_drinking_water/migration.sql +++ b/api-node/prisma/migrations/20240328145103_drinking_water_table/migration.sql @@ -1,8 +1,5 @@ --- AlterEnum -ALTER TYPE "IndicatorsSlugEnum" ADD VALUE 'drinking_water'; - --- AlterTable -ALTER TABLE "User" ADD COLUMN "udi" TEXT; +-- CreateEnum +CREATE TYPE "DrinkingWaterPrelevementConformity" AS ENUM ('C', 'D', 'N', 'S'); -- CreateTable CREATE TABLE "DrinkingWater" ( @@ -15,25 +12,18 @@ CREATE TABLE "DrinkingWater" ( "alert_status" "AlertStatusEnum" NOT NULL DEFAULT 'NOT_ALERT_THRESHOLD', "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "code_prelevement" TEXT, - "nom_uge" TEXT, - "nom_distributeur" TEXT, - "nom_moa" TEXT, - "nom_reseau" TEXT, - "date_prelevement" TIMESTAMP(3), + "hubeau_first_url" TEXT, + "last_prelevement_code" TEXT, + "last_prelevement_date" TIMESTAMP(3), "conclusion_conformite_prelevement" TEXT, - "conformite_limites_bact_prelevement" TEXT, - "conformite_limites_pc_prelevement" TEXT, - "conformite_references_bact_prelevement" TEXT, - "conformite_references_pc_prelevement" TEXT, - "hubeau_udi_url" TEXT, - "hubeau_test_url" TEXT, + "conformite_limites_bact_prelevement" "DrinkingWaterPrelevementConformity", + "conformite_limites_pc_prelevement" "DrinkingWaterPrelevementConformity", + "conformite_references_bact_prelevement" "DrinkingWaterPrelevementConformity", + "conformite_references_pc_prelevement" "DrinkingWaterPrelevementConformity", + "all_tests_results" JSONB, CONSTRAINT "DrinkingWater_pkey" PRIMARY KEY ("id") ); --- CreateIndex -CREATE UNIQUE INDEX "DrinkingWater_code_prelevement_key" ON "DrinkingWater"("code_prelevement"); - -- CreateIndex CREATE INDEX "drinking_water_udi_diffusion_date_validity_start" ON "DrinkingWater"("udi", "diffusion_date", "validity_start"); diff --git a/api-node/prisma/schema.prisma b/api-node/prisma/schema.prisma index 7e4bfe5d..bf87dbbf 100644 --- a/api-node/prisma/schema.prisma +++ b/api-node/prisma/schema.prisma @@ -274,70 +274,25 @@ enum DrinkingWaterPrelevementConformity { } model DrinkingWater { - id String @id @default(uuid()) - udi String // example: "076000534" - validity_start DateTime // the date is the first date of the validity period, included - validity_end DateTime // the date is the last date of the validity period, included - diffusion_date DateTime // the latest test date - data_availability DataAvailabilityEnum @default(AVAILABLE) - alert_status AlertStatusEnum @default(NOT_ALERT_THRESHOLD) - created_at DateTime @default(now()) - updated_at DateTime @default(now()) @updatedAt + id String @id @default(uuid()) + udi String // example: "076000534" + validity_start DateTime // the date is the first date of the validity period, included + validity_end DateTime // the date is the last date of the validity period, included + diffusion_date DateTime // the latest test date + data_availability DataAvailabilityEnum @default(AVAILABLE) + alert_status AlertStatusEnum @default(NOT_ALERT_THRESHOLD) + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt // specific fields for this indicator, raw data extracted from the API - // note: we couldn't use Prisma enum here because - // Prisma enum are restricted to: start with letter, no space, etc. - // the API returns numbers, letters, spaces, etc. soo - // we specify out TypeScript types in comment instead - hubeau_udi_url String? // example: "https://hubeau.eaufrance.fr/page/telechargement-donnees" - hubeau_parameters_url String? // example: "https://hubeau.eaufrance.fr/page/telechargement-donnees" - // PH - PH_value String? // PH - PH_conformity DrinkingWaterPrelevementConformity - PH_code_prelevement String? - PH_date_prelevement DateTime? - PH_conclusion_conformite_prelevement String? // example: "Eau conforme, buvez sereinement" - // TEMPÉRATURE DE L'EAU - TEAU_value String? - TEAU_conformity DrinkingWaterPrelevementConformity - TEAU_code_prelevement String? - TEAU_date_prelevement DateTime? - TEAU_conclusion_conformite_prelevement String? // example: "Eau conforme, buvez sereinement" - // TOTAL DES PESTICIDES ANALYSÉS - PESTOT_value String? - PESTOT_conformity DrinkingWaterPrelevementConformity - PESTOT_code_prelevement String? - PESTOT_date_prelevement DateTime? - PESTOT_conclusion_conformite_prelevement String? // example: "Eau conforme, buvez sereinement" - // COLORATION - COULF_value String? - COULF_conformity DrinkingWaterPrelevementConformity - COULF_code_prelevement String? - COULF_date_prelevement DateTime? - COULF_conclusion_conformite_prelevement String? // example: "Eau conforme, buvez sereinement" - // SAVEUR (QUALITATIF) - SAVQ_value String? - SAVQ_conformity DrinkingWaterPrelevementConformity - SAVQ_code_prelevement String? - SAVQ_date_prelevement DateTime? - SAVQ_conclusion_conformite_prelevement String? // example: "Eau conforme, buvez sereinement" - // COULEUR (QUALITATIF) - COULQ_value String? - COULQ_conformity DrinkingWaterPrelevementConformity - COULQ_code_prelevement String? - COULQ_date_prelevement DateTime? - COULQ_conclusion_conformite_prelevement String? // example: "Eau conforme, buvez sereinement" - // ASPECT (QUALITATIF) - ASP_value String? - ASP_conformity DrinkingWaterPrelevementConformity - ASP_code_prelevement String? - ASP_date_prelevement DateTime? - ASP_conclusion_conformite_prelevement String? // example: "Eau conforme, buvez sereinement" - // ODEUR (QUALITATIF) - ODQ_value String? - ODQ_conformity DrinkingWaterPrelevementConformity - ODQ_code_prelevement String? - ODQ_date_prelevement DateTime? - ODQ_conclusion_conformite_prelevement String? // example: "Eau conforme, buvez sereinement" + hubeau_first_url String? // example: "https://hubeau.eaufrance.fr/page/telechargement-donnees" + last_prelevement_code String? + last_prelevement_date DateTime? + conclusion_conformite_prelevement String? + conformite_limites_bact_prelevement DrinkingWaterPrelevementConformity? + conformite_limites_pc_prelevement DrinkingWaterPrelevementConformity? + conformite_references_bact_prelevement DrinkingWaterPrelevementConformity? + conformite_references_pc_prelevement DrinkingWaterPrelevementConformity? + all_tests_results Json? @@index([udi, diffusion_date, validity_start], name: "drinking_water_udi_diffusion_date_validity_start") } diff --git a/api-node/src/aggregators/drinking_water.ts b/api-node/src/aggregators/drinking_water.ts index 987d6f3f..30254d53 100644 --- a/api-node/src/aggregators/drinking_water.ts +++ b/api-node/src/aggregators/drinking_water.ts @@ -5,20 +5,24 @@ import { capture } from '~/third-parties/sentry'; import customParseFormat from 'dayjs/plugin/customParseFormat'; import utc from 'dayjs/plugin/utc'; import { - type HubEAUResponse, - type CodeParametreSISEEaux, - ConformityStatusEnum, + type HubEAUShortResponse, + type ShortPrelevementResult, + type HubEAUResultsParameters, + type ExtendedShortPrelevementResult, + ConformityEnum, } from '~/types/api/drinking_water'; import { AlertStatusEnum, DataAvailabilityEnum, IndicatorsSlugEnum, type User, + Prisma, } from '@prisma/client'; import { sendAlertNotification } from '~/utils/notifications/alert'; import { - getUdiConformityStatus, - mapParameterToRowData, + checkPrelevementConformity, + getWorstConclusion, + getWorstConformity, } from '~/utils/drinking_water'; const fetch = fetchRetry(global.fetch); @@ -29,6 +33,9 @@ dayjs.extend(utc); // https://www.data.gouv.fr/en/datasets/resultats-du-controle-sanitaire-de-leau-distribuee-commune-par-commune/ // https://www.data.gouv.fr/fr/datasets/r/36afc708-42dc-4a89-b039-7fde6bcc83d8 +// documentation about Hub'eau API: +// https://hubeau.eaufrance.fr/api/v1/doc + let now = Date.now(); function logStep(step: string) { console.info( @@ -71,7 +78,15 @@ async function getDrinkingWaterIndicator() { } } -async function fetchDrinkingWaterData(udi: string) { +async function fetchDrinkingWaterData(udi: User['udi']) { + if (!udi) { + return { + data: null, + insertedNewRow: false, + alreadyExistingRow: false, + missingData: false, + }; + } // what does Hub'eau provide? IT's not written in the documentation so we figured this out ourselves // you can put an udi as parameter, you will receive the latest test results for this udi // a test has an id: the `code_prelevement` @@ -79,96 +94,226 @@ async function fetchDrinkingWaterData(udi: string) { // and for each paramter for one given test, the global conclusion are always the same // but each test doesn't have the same parameters tested + // the agreement we have with the Health Autohrity right now is + // - we can't make our own conclusion // so what we need to do is: - // 1. know which parameters we are interested in - // 2. select for the udi and the selected parameters the latest test results + // 1. select all the tests after a date_min_prelevement (1 year ago) + // 2. the latest test will be the one we'll show in the summary + // 3. we'll show all the tests results in the details - // max 20 paramters - const parameters: Array = [ - 'PH', - 'TEAU', - 'PESTOT', - 'COULF', - 'SAVQ', - 'COULQ', - 'ASP', - 'ODQ', - ]; + const hubeEauEndpoint = + 'https://hubeau.eaufrance.fr/api/v1/qualite_eau_potable/resultats_dis'; - const hubEauQuery = { - size: 500, // to mazimize the chance to get the latest test results for all the parameters - code_reseau: udi, - code_parametre_se: parameters.join(','), - } as any; - const hubeauUdiUrl = new URL( - 'https://hubeau.eaufrance.fr/api/v1/qualite_eau_potable/resultats_dis', - ); + // first we check the latest test results for the udi to know if we have it in DB + const hubEauLastTestCheckURL = new URL(hubeEauEndpoint); + const hubeEauLastTestCheckQuery: HubEAUResultsParameters = { + size: 1, + code_reseau: [udi], + fields: ['code_prelevement'], + date_min_prelevement: dayjs().subtract(1, 'year').format('YYYY-MM-DD'), + date_max_prelevement: dayjs().add(1, 'day').format('YYYY-MM-DD'), + }; + + Object.keys(hubeEauLastTestCheckQuery).forEach((key) => { + let value = + hubeEauLastTestCheckQuery[key as keyof typeof hubeEauLastTestCheckQuery]; + if (value) { + hubEauLastTestCheckURL.searchParams.append( + key, + Array.isArray(value) ? value.join(',') : `${value}`, + ); + } + }); + + const hubEauLastTestCheckResponse: HubEAUShortResponse = await fetch( + hubEauLastTestCheckURL.toString(), + { + retryDelay: 1000, + retries: 3, + }, + ).then(async (res) => res.json()); + if (hubEauLastTestCheckResponse.data?.length > 0) { + const hubEauLastTestCheckResuklt = hubEauLastTestCheckResponse.data[0]; + const existingDrinkingWaterRow = await prisma.drinkingWater.findFirst({ + where: { + udi, + last_prelevement_code: hubEauLastTestCheckResuklt.code_prelevement, + }, + }); + if (existingDrinkingWaterRow) { + return { + data: existingDrinkingWaterRow, + insertedNewRow: false, + alreadyExistingRow: true, + missingData: false, + }; + } + } + + // there is new data to fetch ! let's do it + const hubEauURL = new URL(hubeEauEndpoint); + + const hubEauQuery: HubEAUResultsParameters = { + size: 1000, // to mazimize the chance to get the latest test results for all the parameters + code_reseau: [udi], + // code_parametre_se: [ + // 'PH', + // 'TEAU', + // 'PESTOT', + // 'COULF', + // 'SAVQ', + // 'COULQ', + // 'ASP', + // 'ODQ', + // ], + fields: [ + 'date_prelevement', + 'code_prelevement', + 'conclusion_conformite_prelevement', + 'conformite_limites_bact_prelevement', + 'conformite_limites_pc_prelevement', + 'conformite_references_bact_prelevement', + 'conformite_references_pc_prelevement', + ], + date_min_prelevement: dayjs().subtract(1, 'year').format('YYYY-MM-DD'), + date_max_prelevement: dayjs().add(1, 'day').format('YYYY-MM-DD'), + }; Object.keys(hubEauQuery).forEach((key) => { - hubeauUdiUrl.searchParams.append(key, hubEauQuery[key]); + let value = hubEauQuery[key as keyof typeof hubEauQuery]; + if (value) { + hubEauURL.searchParams.append( + key, + Array.isArray(value) ? value.join(',') : `${value}`, + ); + } }); - const hubeau_parameters_url = hubeauUdiUrl.toString(); - const hubeauUdiResponse: HubEAUResponse = await fetch(hubeau_parameters_url, { - retryDelay: 1000, - retries: 3, - }).then(async (res) => res.json()); + const hubeau_first_url = hubEauURL.toString(); - const results = hubeauUdiResponse.data; - const latestTestResult = results[0]; + const prelevements: Array = []; + const prelevementsDone: Record< + ShortPrelevementResult['code_prelevement'], + boolean + > = {}; + let currentPrelevementCode: ShortPrelevementResult['code_prelevement'] = ''; + let currentPrelevementConclusions: ShortPrelevementResult | null = null; + let currentPrelevementParametersCount = 0; - if (!latestTestResult) { - return { - data: null, - insertedNewRow: false, - alreadyExistingRow: false, - missingData: true, - }; + async function getHubeauDataRecursive(hubeau_url: string) { + const hubeauUdiResponse: HubEAUShortResponse = await fetch(hubeau_url, { + retryDelay: 1000, + retries: 3, + }).then(async (res) => res.json()); + if (hubeauUdiResponse.data?.length > 0) { + // let's group by code_prelevement + const hubEAUResults = hubeauUdiResponse.data; + for (const result of hubEAUResults) { + if (result.code_prelevement !== currentPrelevementCode) { + if (currentPrelevementConclusions) { + prelevements.push({ + ...currentPrelevementConclusions, + parameters_count: currentPrelevementParametersCount, + }); + } + currentPrelevementParametersCount = 0; + currentPrelevementConclusions = result; + if (prelevementsDone[result.code_prelevement]) { + capture( + new Error( + '[Hubeau] Response is not sorted by prelevement because prelevement already done', + ), + { + extra: { + prelevementCode: result.code_prelevement, + udi, + hubeau_first_url, + hubeau_url, + prelevementsDone, + }, + }, + ); + } + currentPrelevementCode = result.code_prelevement; + } + if ( + JSON.stringify(currentPrelevementConclusions) !== + JSON.stringify(result) + ) { + capture(new Error('[Hubeau] Conclusion is different for same test'), { + extra: { + result, + udi, + hubeau_first_url, + hubeau_url, + currentPrelevementConclusions, + currentPrelevementCode, + }, + tags: { + udi, + currentPrelevementCode, + }, + }); + if (!currentPrelevementConclusions) continue; + const worstPrelevement: ShortPrelevementResult = { + // order matters + code_prelevement: currentPrelevementCode, + date_prelevement: currentPrelevementConclusions.date_prelevement, + conclusion_conformite_prelevement: getWorstConclusion( + currentPrelevementConclusions.conclusion_conformite_prelevement, + result.conclusion_conformite_prelevement, + ), + conformite_limites_bact_prelevement: getWorstConformity( + currentPrelevementConclusions.conformite_limites_bact_prelevement, + result.conformite_limites_bact_prelevement, + ), + conformite_limites_pc_prelevement: getWorstConformity( + currentPrelevementConclusions.conformite_limites_pc_prelevement, + result.conformite_limites_pc_prelevement, + ), + conformite_references_bact_prelevement: getWorstConformity( + currentPrelevementConclusions.conformite_references_bact_prelevement, + result.conformite_references_bact_prelevement, + ), + conformite_references_pc_prelevement: getWorstConformity( + currentPrelevementConclusions.conformite_references_pc_prelevement, + result.conformite_references_pc_prelevement, + ), + }; + currentPrelevementConclusions = worstPrelevement; + } + currentPrelevementParametersCount++; + } + } + if (hubeauUdiResponse.next) { + getHubeauDataRecursive(hubeauUdiResponse.next); + } else { + if (currentPrelevementConclusions && currentPrelevementParametersCount) + prelevements.push({ + ...currentPrelevementConclusions, + parameters_count: currentPrelevementParametersCount, + }); + } } - const datePrelevement = dayjs(latestTestResult.date_prelevement) - .utc() - .toDate(); + await getHubeauDataRecursive(hubeau_first_url); + // results are sorted by latest date, and one prelevement has one date, so + // results are also sorted by prelevement - const existingPrelevement = await prisma.drinkingWater.findFirst({ - where: { - udi, - diffusion_date: datePrelevement, - }, - }); - if (existingPrelevement) { + if (!prelevements.length) { return { - data: existingPrelevement, + data: null, insertedNewRow: false, - alreadyExistingRow: true, + alreadyExistingRow: false, missingData: false, }; } - const phTestResult = mapParameterToRowData( - results.find((r) => r.code_parametre_se === 'PH'), - ); - const teauTestResult = mapParameterToRowData( - results.find((r) => r.code_parametre_se === 'TEAU'), - ); - const pestotTestResult = mapParameterToRowData( - results.find((r) => r.code_parametre_se === 'PESTOT'), - ); - const coulfTestResult = mapParameterToRowData( - results.find((r) => r.code_parametre_se === 'COULF'), - ); - const savqTestResult = mapParameterToRowData( - results.find((r) => r.code_parametre_se === 'SAVQ'), - ); - const coulqTestResult = mapParameterToRowData( - results.find((r) => r.code_parametre_se === 'COULQ'), - ); - const aspTestResult = mapParameterToRowData( - results.find((r) => r.code_parametre_se === 'ASP'), - ); - const odqTestResult = mapParameterToRowData( - results.find((r) => r.code_parametre_se === 'ODQ'), - ); + const lastPrelevement = prelevements[0]; + + const datePrelevement = dayjs(lastPrelevement.date_prelevement) + .utc() + .toDate(); let newDrinkingWaterRow = await prisma.drinkingWater.create({ data: { @@ -177,73 +322,34 @@ async function fetchDrinkingWaterData(udi: string) { validity_end: dayjs(datePrelevement).add(1, 'year').toDate(), diffusion_date: datePrelevement, data_availability: - hubeauUdiResponse.data.length > 0 + prelevements.length > 0 ? DataAvailabilityEnum.AVAILABLE : DataAvailabilityEnum.NOT_AVAILABLE, - alert_status: AlertStatusEnum.NOT_ALERT_THRESHOLD, - hubeau_parameters_url, - PH_conformity: phTestResult.conformity, - PH_value: phTestResult.value, - PH_code_prelevement: phTestResult.code_prelevement, - PH_date_prelevement: phTestResult.date_prelevement, - PH_conclusion_conformite_prelevement: - phTestResult.conclusion_conformite_prelevement, - TEAU_conformity: teauTestResult.conformity, - TEAU_value: teauTestResult.value, - TEAU_code_prelevement: teauTestResult.code_prelevement, - TEAU_date_prelevement: teauTestResult.date_prelevement, - TEAU_conclusion_conformite_prelevement: - teauTestResult.conclusion_conformite_prelevement, - PESTOT_conformity: pestotTestResult.conformity, - PESTOT_value: pestotTestResult.value, - PESTOT_code_prelevement: pestotTestResult.code_prelevement, - PESTOT_date_prelevement: pestotTestResult.date_prelevement, - PESTOT_conclusion_conformite_prelevement: - pestotTestResult.conclusion_conformite_prelevement, - COULF_conformity: coulfTestResult.conformity, - COULF_value: coulfTestResult.value, - COULF_code_prelevement: coulfTestResult.code_prelevement, - COULF_date_prelevement: coulfTestResult.date_prelevement, - COULF_conclusion_conformite_prelevement: - coulfTestResult.conclusion_conformite_prelevement, - SAVQ_conformity: savqTestResult.conformity, - SAVQ_value: savqTestResult.value, - SAVQ_code_prelevement: savqTestResult.code_prelevement, - SAVQ_date_prelevement: savqTestResult.date_prelevement, - SAVQ_conclusion_conformite_prelevement: - savqTestResult.conclusion_conformite_prelevement, - COULQ_conformity: coulqTestResult.conformity, - COULQ_value: coulqTestResult.value, - COULQ_code_prelevement: coulqTestResult.code_prelevement, - COULQ_date_prelevement: coulqTestResult.date_prelevement, - COULQ_conclusion_conformite_prelevement: - coulqTestResult.conclusion_conformite_prelevement, - ASP_conformity: aspTestResult.conformity, - ASP_value: aspTestResult.value, - ASP_code_prelevement: aspTestResult.code_prelevement, - ASP_date_prelevement: aspTestResult.date_prelevement, - ASP_conclusion_conformite_prelevement: - aspTestResult.conclusion_conformite_prelevement, - ODQ_conformity: odqTestResult.conformity, - ODQ_value: odqTestResult.value, - ODQ_code_prelevement: odqTestResult.code_prelevement, - ODQ_date_prelevement: odqTestResult.date_prelevement, - ODQ_conclusion_conformite_prelevement: - odqTestResult.conclusion_conformite_prelevement, + alert_status: + checkPrelevementConformity(lastPrelevement) === + ConformityEnum.NOT_CONFORM + ? AlertStatusEnum.ALERT_NOTIFICATION_NOT_SENT_YET + : AlertStatusEnum.NOT_ALERT_THRESHOLD, + last_prelevement_code: lastPrelevement.code_prelevement, + last_prelevement_date: datePrelevement, + conclusion_conformite_prelevement: + lastPrelevement.conclusion_conformite_prelevement, + conformite_limites_bact_prelevement: + lastPrelevement.conformite_limites_bact_prelevement, + conformite_limites_pc_prelevement: + lastPrelevement.conformite_limites_pc_prelevement, + conformite_references_bact_prelevement: + lastPrelevement.conformite_references_bact_prelevement, + conformite_references_pc_prelevement: + lastPrelevement.conformite_references_pc_prelevement, + hubeau_first_url, + all_tests_results: prelevements as unknown as Prisma.JsonArray, }, }); if ( - getUdiConformityStatus(newDrinkingWaterRow) === - ConformityStatusEnum.NOT_CONFORM + newDrinkingWaterRow.alert_status === + AlertStatusEnum.ALERT_NOTIFICATION_NOT_SENT_YET ) { - newDrinkingWaterRow = await prisma.drinkingWater.update({ - where: { - id: newDrinkingWaterRow.id, - }, - data: { - alert_status: AlertStatusEnum.ALERT_NOTIFICATION_NOT_SENT_YET, - }, - }); const alertSent = await sendAlertNotification( IndicatorsSlugEnum.drinking_water, newDrinkingWaterRow, diff --git a/api-node/src/getters/drinking_water.ts b/api-node/src/getters/drinking_water.ts index e195a471..aadc8645 100644 --- a/api-node/src/getters/drinking_water.ts +++ b/api-node/src/getters/drinking_water.ts @@ -3,21 +3,25 @@ import fs from 'fs'; import { z } from 'zod'; import dayjs from 'dayjs'; import type { Indicator, IndicatorByPeriod } from '~/types/api/indicator'; +import type { ExtendedShortPrelevementResult } from '~/types/api/drinking_water'; import { IndicatorsSlugEnum, + Prisma, type Municipality, type User, } from '@prisma/client'; import { indicatorsObject } from './indicators_list'; import utc from 'dayjs/plugin/utc'; import quarterOfYear from 'dayjs/plugin/quarterOfYear'; -import { ConformityStatusEnum } from '~/types/api/drinking_water'; +import { + ConformityStatusEnum, + ConformityEnum, +} from '~/types/api/drinking_water'; import { fetchDrinkingWaterData } from '~/aggregators/drinking_water'; import { - getAllConclusions, - getUdiConformityStatus, - getUdiConformityValue, - mapParameterRowDataToIndicatorByPeriodValues, + checkPrelevementConformityChemical, + checkPrelevementConformityBacteriological, + checkPrelevementConformity, } from '~/utils/drinking_water'; dayjs.extend(quarterOfYear); dayjs.extend(utc); @@ -29,7 +33,7 @@ const about_description = fs.readFileSync( async function getDrinkingWaterFromUdi({ udi, - municipality_insee_code, // we keep this municipality_insee_code for now to not break alkl the types around + municipality_insee_code, // we keep this municipality_insee_code for now to not break all the types around in the Indicator response date_UTC_ISO, }: { udi: User['udi']; @@ -63,15 +67,47 @@ async function getDrinkingWaterFromUdi({ if (drinkingWaterResult.missingData || !drinkingWater) { return getDrinkingWaterEmpty(municipality_insee_code); } + const lastPrelevementConformity = checkPrelevementConformity(drinkingWater); + + if (lastPrelevementConformity === ConformityEnum.NOT_TESTED) { + return getDrinkingWaterEmpty(municipality_insee_code); + } const drinkingWaterIndicatorJ0AndJ1: IndicatorByPeriod = { id: drinkingWater.id, summary: { - value: getUdiConformityValue(drinkingWater), - status: getUdiConformityStatus(drinkingWater), - recommendations: getAllConclusions(drinkingWater), + value: { + chemical: checkPrelevementConformityChemical(drinkingWater), + bacteriological: + checkPrelevementConformityBacteriological(drinkingWater), + }, + status: lastPrelevementConformity, + recommendations: [ + drinkingWater.conclusion_conformite_prelevement as string, + ], }, - values: mapParameterRowDataToIndicatorByPeriodValues(drinkingWater), + values: ((drinkingWater.all_tests_results ?? []) as Prisma.JsonArray)?.map( + (jsonValue) => { + const test_result = + jsonValue as unknown as ExtendedShortPrelevementResult; + return { + slug: test_result.code_prelevement, + name: `Test du ${dayjs(test_result.date_prelevement).format( + 'DD MMM YYYY', + )}`, + value: { + chemical: checkPrelevementConformityChemical(test_result), + bacteriological: + checkPrelevementConformityBacteriological(test_result), + }, + drinkingWaterMetadata: { + parameters_count: test_result.parameters_count, + prelevement_code: test_result.code_prelevement, + prelevement_date: test_result.date_prelevement, + }, + }; + }, + ), diffusion_date: dayjs(drinkingWater.diffusion_date).format('YYYY-MM-DD'), validity_start: dayjs(drinkingWater.diffusion_date).format('YYYY-MM-DD'), validity_end: 'N/A', diff --git a/api-node/src/index.ts b/api-node/src/index.ts index 2391e3d0..a164909f 100644 --- a/api-node/src/index.ts +++ b/api-node/src/index.ts @@ -1,5 +1,5 @@ import 'dotenv/config'; -import '~/prisma'; +import prisma from '~/prisma'; import * as Sentry from '@sentry/node'; import express from 'express'; @@ -21,7 +21,35 @@ import feedbackRouter from './controllers/feedback.ts'; import notificationRouter from './controllers/notification.ts'; import udiRouter from './controllers/udi.ts'; +console.log( + (async () => { + const udis = await prisma.udis.findMany({ + select: { + code_udi: true, + }, + }); + for (const [index, { code_udi }] of Object.entries(udis)) { + if (Number(index) < 15427) continue; + console.log( + 'index', + index, + ' of all ', + udis.length, + ' code_udi', + code_udi, + ); + const result = await getDrinkingWaterFromUdi({ + date_UTC_ISO: '2022-03-01T00:00:00.000Z', + municipality_insee_code: '12345', + udi: code_udi, + }); + console.log('result', result?.j0.summary?.recommendations?.[0]); + } + })(), +); + import packageJson from '../package.json'; +import { getDrinkingWaterFromUdi } from './getters/drinking_water.ts'; // Put together a schema const app = express(); diff --git a/api-node/src/types/api/drinking_water.ts b/api-node/src/types/api/drinking_water.ts index b103af25..f9d08084 100644 --- a/api-node/src/types/api/drinking_water.ts +++ b/api-node/src/types/api/drinking_water.ts @@ -1,3 +1,5 @@ +import type { Municipality, User } from '@prisma/client'; + export enum ConformityEnum { CONFORM = 'C', // Conforme CONFORM_DEROGATION = 'D', // Conforme dans le cadre d’une dérogation @@ -19,15 +21,57 @@ export enum ConformityNumberEnum { NOT_TESTED = 0, // Sans objet lorsqu'aucun paramètre microbio n'a été mesuré } -export type CodeParametreSISEEaux = - | 'PH' - | 'TEAU' - | 'PESTOT' - | 'COULF' - | 'SAVQ' - | 'COULQ' - | 'ASP' - | 'ODQ'; +export type CodeParametreSISEEauxTop50 = + | 'A2H' // ATRAZINE-2-HYDROXY + | 'ACRYL' // ACRYLAMIDE + | 'ADET' // ATRAZINE DÉSÉTHYL + | 'ADSP' // ATRAZINE-DÉISOPROPYL + | 'ALTMICR' // ALUMINIUM TOTAL µG/L + | 'ASP' // ASPECT (QUALITATIF) + | 'ATRZ' // ATRAZINE + | 'BRF' // BROMOFORME + | 'BSIR' // BACT. ET SPORES SULFITO-RÉDU./100ML + | 'CA' // CALCIUM + | 'CDT25' // CONDUCTIVITÉ À 25°C + | 'CL' // CHLORURES + | 'CL2COMB' // CHLORE COMBINÉ + | 'CL2LIB' // CHLORE LIBRE + | 'CL2TOT' // CHLORE TOTAL + | 'CLF' // CHLOROFORME + | 'CLO2' // BIOXYDE DE CHLORE MG/L CLO2 + | 'CLVYL' // CHLORURE DE VINYL MONOMÈRE + | 'COT' // CARBONE ORGANIQUE TOTAL + | 'COULF' // COLORATION + | 'COULQ' // COULEUR (QUALITATIF) + | 'CTF' // BACTÉRIES COLIFORMES /100ML-MS + | 'DBRMCL' // CHLORODIBROMOMÉTHANE + | 'DCLMBR' // DICHLOROMONOBROMOMÉTHANE + | 'ECOLI' // ESCHERICHIA COLI /100ML - MF + | 'FET' // FER TOTAL + | 'GT22_68' // BACT. AÉR. REVIVIFIABLES À 22°-68H + | 'GT36_44' // BACT. AÉR. REVIVIFIABLES À 36°-44H + | 'HCO3' // HYDROGÉNOCARBONATES + | 'MG' // MAGNÉSIUM + | 'MN' // MANGANÈSE TOTAL + | 'MTCESA' // ESA METOLACHLORE + | 'NH4' // AMMONIUM (EN NH4) + | 'NO2' // NITRITES (EN NO2) + | 'NO3' // NITRATES (EN NO3) + | 'NO3_NO2' // NITRATES/50 + NITRITES/3 + | 'ODQ' // ODEUR (QUALITATIF) + | 'PESTOT' // TOTAL DES PESTICIDES ANALYSÉS + | 'PH' // PH + | 'SAVQ' // SAVEUR (QUALITATIF) + | 'SO4' // SULFATES + | 'STRF' // ENTÉROCOQUES /100ML-MS + | 'TA' // TITRE ALCALIMÉTRIQUE + | 'TAC' // TITRE ALCALIMÉTRIQUE COMPLET + | 'TAIR' // TEMPÉRATURE DE L'AIR + | 'TEAU' // TEMPÉRATURE DE L'EAU + | 'TEMP_PH' // TEMPÉRATURE DE MESURE DU PH + | 'TH' // TITRE HYDROTIMÉTRIQUE + | 'THM4' // TRIHALOMÉTHANES (4 SUBSTANCES) + | 'TURBNFU'; // TURBIDITÉ NÉPHÉLOMÉTRIQUE NFU export type LibelleParametre = string; @@ -41,51 +85,98 @@ export interface ParameterInDataRow { conclusion_conformite_prelevement: string; // this is what we use for recommandation } +// https://hubeau.eaufrance.fr/page/api-qualite-eau-potable#/qualite_eau_potable/resultats +export interface HubEAUResultsParameters { + borne_inf_resultat?: number; + borne_sup_resultat?: number; + code_commune?: Array; + code_departement?: Array; + code_lieu_analyse?: 'T' | 'L'; + code_parametre?: Array; + code_parametre_cas?: Array; + code_parametre_se?: Array; + code_prelevement?: string; + code_reseau?: Array; + conformite_limites_bact_prelevement?: ConformityEnum; + conformite_limites_pc_prelevement?: ConformityEnum; + conformite_references_bact_prelevement?: ConformityEnum; + conformite_references_pc_prelevement?: ConformityEnum; + date_max_prelevement?: string; // YYYY-MM-DD + date_min_prelevement?: string; // YYYY-MM-DD + fields?: Array; + libelle_parametre?: string; + libelle_parametre_maj?: string; + nom_commune?: Array; + nom_distributeur?: string; + nom_moa?: string; + page?: number; + size?: number; + sort?: 'asc' | 'desc'; +} + export interface PrelevementResult { - // code_departement?: string; - // nom_departement?: string; + code_departement: string; + nom_departement: string; code_prelevement: string; // useful - // code_parametre?: string; - code_parametre_se: CodeParametreSISEEaux; // useful - // code_parametre_cas?: string; + code_parametre: string; + code_parametre_se: CodeParametreSISEEauxTop50; // useful + code_parametre_cas: string; libelle_parametre: LibelleParametre; // useful libelle_parametre_maj: LibelleParametreMaj; // useful - // libelle_parametre_web?: string; - code_type_parametre?: 'O' | 'N'; // O is qualitative, N is quantitative - // code_lieu_analyse?: 'T' | 'L'; // Type de résultat Terrain ou Laboratoire - resultat_alphanumerique?: string; // useful + libelle_parametre_web: string; + code_type_parametre: 'O' | 'N'; // O is qualitative, N is quantitative + code_lieu_analyse: 'T' | 'L'; // Type de résultat Terrain ou Laboratoire + resultat_alphanumerique: string; // useful resultat_numerique: string; // useful - libelle_unite?: string; // useful when code_type_parametre is N (quantitative) - // code_unite?: string; - limite_qualite_parametre?: string; // useful - reference_qualite_parametre?: string; // useful - // code_commune?: string; - // nom_commune?: string; - nom_uge?: string; - nom_distributeur?: string; - nom_moa?: string; + libelle_unite: string; // useful when code_type_parametre is N (quantitative) + code_unite: string; + limite_qualite_parametre: string; // useful + reference_qualite_parametre: string; // useful + code_commune: string; + nom_commune: string; + nom_uge: string; + nom_distributeur: string; + nom_moa: string; date_prelevement: string; // useful - conclusion_conformite_prelevement?: string; // super useful - conformite_limites_bact_prelevement?: ConformityEnum; - conformite_limites_pc_prelevement?: ConformityEnum; - conformite_references_bact_prelevement?: ConformityEnum; - conformite_references_pc_prelevement?: ConformityEnum; - // reference_analyse?: string; - // code_installation_amont?: string; - // nom_installation_amont?: string; - // reseaux?: Array<{ - // code?: string; - // nom?: string; - // debit?: string; - // }>; + conclusion_conformite_prelevement: string; // super useful + conformite_limites_bact_prelevement: ConformityEnum; + conformite_limites_pc_prelevement: ConformityEnum; + conformite_references_bact_prelevement: ConformityEnum; + conformite_references_pc_prelevement: ConformityEnum; + reference_analyse?: string; + code_installation_amont?: string; + nom_installation_amont?: string; + reseaux?: Array<{ + code?: string; + nom?: string; + debit?: string; + }>; } -export interface HubEAUResponse { +export type ShortPrelevementResult = Pick< + PrelevementResult, + | 'code_prelevement' + | 'date_prelevement' + | 'conclusion_conformite_prelevement' + | 'conformite_limites_bact_prelevement' + | 'conformite_limites_pc_prelevement' + | 'conformite_references_bact_prelevement' + | 'conformite_references_pc_prelevement' +>; + +export interface ExtendedShortPrelevementResult extends ShortPrelevementResult { + parameters_count: number; +} + +interface HubEAUResponse { count: number; first: string; // "https://hubeau.eaufrance.fr/api/v1/qualite_eau_potable/resultats_dis?code_reseau=014000659&page=1&size=200", last: string | null; // "https://hubeau.eaufrance.fr/api/v1/qualite_eau_potable/resultats_dis?code_reseau=014000659&page=2&size=200", prev: string | null; next: string | null; // "https://hubeau.eaufrance.fr/api/v1/qualite_eau_potable/resultats_dis?code_reseau=014000659&page=2&size=200", api_version: 'v1'; - data: Array; + data: Array; } + +export type HubEAUCompleteResponse = HubEAUResponse; +export type HubEAUShortResponse = HubEAUResponse; diff --git a/api-node/src/types/api/indicator.ts b/api-node/src/types/api/indicator.ts index 12a3767b..68a4a844 100644 --- a/api-node/src/types/api/indicator.ts +++ b/api-node/src/types/api/indicator.ts @@ -1,4 +1,8 @@ -import type { IndicatorsSlugEnum, Municipality } from '@prisma/client'; +import type { + DrinkingWaterPrelevementConformity, + IndicatorsSlugEnum, + Municipality, +} from '@prisma/client'; export type IndicatorItem = { name: string; @@ -10,13 +14,23 @@ export type IndicatorItem = { export type IndicatorDay = 'j0' | 'j1'; +export type DrinkingWaterValue = { + chemical: DrinkingWaterPrelevementConformity; + bacteriological: DrinkingWaterPrelevementConformity; +}; + +export type DrinkingWaterMetadata = { + parameters_count: number; + prelevement_code: string; + prelevement_date: string; +}; + export type IndicatorByPeriodValue = { slug: string; name: string; - value: number | string; + value: number | string | DrinkingWaterValue; link?: string; // specific for bathing water - isConform?: boolean; // specific for drinking water - datePrelevement?: string; // specific for drinking water + drinkingWater?: DrinkingWaterMetadata; }; export type IndicatorByPeriodValues = Array; @@ -28,8 +42,8 @@ export interface IndicatorByPeriod { created_at: string; updated_at: string; summary: { - value: number | null; - status: string; + value: number | null | DrinkingWaterValue; + status: string | null; status_description?: string; recommendations?: string[]; }; diff --git a/api-node/src/utils/drinking_water.ts b/api-node/src/utils/drinking_water.ts index 493ab335..04dad9cb 100644 --- a/api-node/src/utils/drinking_water.ts +++ b/api-node/src/utils/drinking_water.ts @@ -1,33 +1,35 @@ import { type PrelevementResult, ConformityEnum, - ConformityStatusEnum, - ConformityNumberEnum, - type ParameterInDataRow, } from '~/types/api/drinking_water'; -import type { DrinkingWater } from '@prisma/client'; -import { type IndicatorByPeriodValue } from '~/types/api/indicator'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; dayjs.extend(utc); -// function extractOnlyNumber(str: string): number { -// return parseFloat(str?.replace(/[^0-9,]/g, '').replace(',', '.')); -// } -type PrelevementConformity = Pick< - PrelevementResult, - | 'conclusion_conformite_prelevement' - | 'conformite_limites_bact_prelevement' - | 'conformite_limites_pc_prelevement' - | 'conformite_references_bact_prelevement' - | 'conformite_references_pc_prelevement' ->; +type PrelevementConformity = { + conclusion_conformite_prelevement: string | null; + conformite_limites_bact_prelevement: string | null; + conformite_limites_pc_prelevement: string | null; + conformite_references_bact_prelevement: string | null; + conformite_references_pc_prelevement: string | null; +}; function checkPrelevementConformity( // only conformite_limites_pc_prelevement, conformite_references_pc_prelevement, conformite_limites_bact_prelevement, conformite_references_bact_prelevement are used prelevement?: PrelevementConformity, ): ConformityEnum { if (!prelevement) return ConformityEnum.NOT_TESTED; + if (!prelevement.conclusion_conformite_prelevement) + return ConformityEnum.NOT_TESTED; + if (!prelevement.conformite_limites_pc_prelevement) + return ConformityEnum.NOT_TESTED; + if (!prelevement.conformite_references_pc_prelevement) + return ConformityEnum.NOT_TESTED; + if (!prelevement.conformite_limites_bact_prelevement) + return ConformityEnum.NOT_TESTED; + if (!prelevement.conformite_references_bact_prelevement) + return ConformityEnum.NOT_TESTED; + const conformityLimitChemical = prelevement.conformite_limites_pc_prelevement; const conformityReferenceChemical = prelevement.conformite_references_pc_prelevement; @@ -59,353 +61,96 @@ function checkPrelevementConformity( return ConformityEnum.CONFORM; } -function getUdiConformityStatus(drinkingWater: DrinkingWater) { - let isNotTested = true; - if (drinkingWater.PH_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityStatusEnum.NOT_CONFORM; - } - if (drinkingWater.PH_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.TEAU_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityStatusEnum.NOT_CONFORM; - } - if (drinkingWater.TEAU_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.PESTOT_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityStatusEnum.NOT_CONFORM; - } - if (drinkingWater.PESTOT_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.COULF_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityStatusEnum.NOT_CONFORM; - } - if (drinkingWater.COULF_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.SAVQ_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityStatusEnum.NOT_CONFORM; - } - if (drinkingWater.SAVQ_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.COULQ_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityStatusEnum.NOT_CONFORM; - } - if (drinkingWater.COULQ_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.ASP_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityStatusEnum.NOT_CONFORM; - } - if (drinkingWater.ASP_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.ODQ_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityStatusEnum.NOT_CONFORM; - } - if (drinkingWater.ODQ_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (isNotTested) { - return ConformityStatusEnum.NOT_TESTED; - } - return ConformityStatusEnum.CONFORM; -} +function checkPrelevementConformityChemical( + prelevement?: PrelevementConformity, +): ConformityEnum { + if (!prelevement) return ConformityEnum.NOT_TESTED; + if (!prelevement) return ConformityEnum.NOT_TESTED; + if (!prelevement.conclusion_conformite_prelevement) + return ConformityEnum.NOT_TESTED; + if (!prelevement.conformite_limites_pc_prelevement) + return ConformityEnum.NOT_TESTED; + if (!prelevement.conformite_references_pc_prelevement) + return ConformityEnum.NOT_TESTED; -function getUdiConformityValue(drinkingWater: DrinkingWater) { - let isNotTested = true; - if (drinkingWater.PH_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityNumberEnum.NOT_CONFORM; - } - if (drinkingWater.PH_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.TEAU_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityNumberEnum.NOT_CONFORM; - } - if (drinkingWater.TEAU_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.PESTOT_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityNumberEnum.NOT_CONFORM; - } - if (drinkingWater.PESTOT_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.COULF_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityNumberEnum.NOT_CONFORM; - } - if (drinkingWater.COULF_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.SAVQ_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityNumberEnum.NOT_CONFORM; - } - if (drinkingWater.SAVQ_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.COULQ_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityNumberEnum.NOT_CONFORM; - } - if (drinkingWater.COULQ_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.ASP_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityNumberEnum.NOT_CONFORM; - } - if (drinkingWater.ASP_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; - } - if (drinkingWater.ODQ_conformity === ConformityEnum.NOT_CONFORM) { - return ConformityNumberEnum.NOT_CONFORM; + const conformityLimitChemical = prelevement.conformite_limites_pc_prelevement; + const conformityReferenceChemical = + prelevement.conformite_references_pc_prelevement; + + if (conformityLimitChemical === ConformityEnum.NOT_CONFORM) { + return ConformityEnum.NOT_CONFORM; } - if (drinkingWater.ODQ_conformity !== ConformityEnum.NOT_TESTED) { - isNotTested = false; + if (conformityReferenceChemical === ConformityEnum.NOT_CONFORM) { + return ConformityEnum.NOT_CONFORM; } - if (isNotTested) { - return ConformityNumberEnum.NOT_TESTED; + if ( + conformityLimitChemical === ConformityEnum.NOT_TESTED && + conformityReferenceChemical === ConformityEnum.NOT_TESTED + ) { + return ConformityEnum.NOT_TESTED; } - return ConformityNumberEnum.CONFORM; + return ConformityEnum.CONFORM; } -function mapParameterToRowData( - parameterPrelevement?: PrelevementResult, -): ParameterInDataRow { - // source: https://www.legifrance.gouv.fr/loda/id/JORFTEXT000000465574/ - // but the reality is: we took all the data from - // https://www.data.gouv.fr/en/datasets/resultats-du-controle-sanitaire-de-leau-distribuee-commune-par-commune/ - // we checked for each parameter row the reference_qualite_parametre and limite_qualite_parametre - // and we found that the values are what we have in the source above, and what we used below - if (!parameterPrelevement) { - return { - value: '', - conformity: ConformityEnum.NOT_TESTED, - code_prelevement: '', - date_prelevement: null, - conclusion_conformite_prelevement: '', - }; - } - switch (parameterPrelevement.code_parametre_se) { - case 'PH': { - const max = 9; - const min = 6.5; - const value = parseFloat(parameterPrelevement.resultat_numerique); - return { - value: `${value}`, - conformity: - value >= min && value <= max - ? ConformityEnum.CONFORM - : ConformityEnum.NOT_CONFORM, - code_prelevement: parameterPrelevement.code_prelevement, - date_prelevement: parameterPrelevement.date_prelevement, - conclusion_conformite_prelevement: - parameterPrelevement.conclusion_conformite_prelevement ?? '', - }; - } - case 'TEAU': { - const max = 25; - const value = parseFloat(parameterPrelevement.resultat_numerique); - return { - value: `${value}${parameterPrelevement.libelle_unite}`, - conformity: - value <= max ? ConformityEnum.CONFORM : ConformityEnum.NOT_CONFORM, - code_prelevement: parameterPrelevement.code_prelevement, - date_prelevement: parameterPrelevement.date_prelevement, - conclusion_conformite_prelevement: - parameterPrelevement.conclusion_conformite_prelevement ?? '', - }; - } - case 'PESTOT': { - const max = 0.5; - const value = parseFloat(parameterPrelevement.resultat_numerique); - return { - value: `${value}${parameterPrelevement.libelle_unite}`, - conformity: - value <= max ? ConformityEnum.CONFORM : ConformityEnum.NOT_CONFORM, - code_prelevement: parameterPrelevement.code_prelevement, - date_prelevement: parameterPrelevement.date_prelevement, - conclusion_conformite_prelevement: - parameterPrelevement.conclusion_conformite_prelevement ?? '', - }; - } - case 'COULF': { - const max = 15; - const value = parseFloat(parameterPrelevement.resultat_numerique); - return { - value: `${value}${parameterPrelevement.libelle_unite}`, - conformity: - value <= max ? ConformityEnum.CONFORM : ConformityEnum.NOT_CONFORM, - code_prelevement: parameterPrelevement.code_prelevement, - date_prelevement: parameterPrelevement.date_prelevement, - conclusion_conformite_prelevement: - parameterPrelevement.conclusion_conformite_prelevement ?? '', - }; - } - case 'SAVQ': - case 'COULQ': - case 'ASP': - case 'ODQ': { - return { - value: parameterPrelevement.resultat_alphanumerique ?? '', - conformity: checkPrelevementConformity(parameterPrelevement), - code_prelevement: parameterPrelevement.code_prelevement, - date_prelevement: parameterPrelevement.date_prelevement, - conclusion_conformite_prelevement: - parameterPrelevement.conclusion_conformite_prelevement ?? '', - }; - } - } -} +function checkPrelevementConformityBacteriological( + prelevement?: PrelevementConformity, +): ConformityEnum { + if (!prelevement) return ConformityEnum.NOT_TESTED; + if (!prelevement) return ConformityEnum.NOT_TESTED; + if (!prelevement.conclusion_conformite_prelevement) + return ConformityEnum.NOT_TESTED; + if (!prelevement.conformite_limites_bact_prelevement) + return ConformityEnum.NOT_TESTED; + if (!prelevement.conformite_references_bact_prelevement) + return ConformityEnum.NOT_TESTED; -function mapParameterRowDataToIndicatorByPeriodValues( - drinkingWaterRow: DrinkingWater, -): Array { - // source: https://www.legifrance.gouv.fr/loda/id/JORFTEXT000000465574/ - // but the reality is: we took all the data from - // https://www.data.gouv.fr/en/datasets/resultats-du-controle-sanitaire-de-leau-distribuee-commune-par-commune/ - // we checked for each parameter row the reference_qualite_parametre and limite_qualite_parametre - // and we found that the values are what we have in the source above, and what we used below - return [ - { - slug: 'PH', - name: 'pH', - value: drinkingWaterRow.PH_value ?? '', - isConform: drinkingWaterRow.PH_conformity === ConformityEnum.CONFORM, - datePrelevement: drinkingWaterRow.PH_date_prelevement - ? dayjs(drinkingWaterRow.PH_date_prelevement).format('YYYY-MM-DD') - : '', - }, - { - slug: 'TEAU', - name: 'Température de l’eau', - value: drinkingWaterRow.TEAU_value ?? '', - isConform: drinkingWaterRow.TEAU_conformity === ConformityEnum.CONFORM, - datePrelevement: drinkingWaterRow.TEAU_date_prelevement - ? dayjs(drinkingWaterRow.TEAU_date_prelevement).format('YYYY-MM-DD') - : '', - }, - { - slug: 'PESTOT', - name: 'Pesticides totaux', - value: drinkingWaterRow.PESTOT_value ?? '', - isConform: drinkingWaterRow.PESTOT_conformity === ConformityEnum.CONFORM, - datePrelevement: drinkingWaterRow.PESTOT_date_prelevement - ? dayjs(drinkingWaterRow.PESTOT_date_prelevement).format('YYYY-MM-DD') - : '', - }, - { - slug: 'COULF', - name: 'Couleur', - value: drinkingWaterRow.COULF_value ?? '', - isConform: drinkingWaterRow.COULF_conformity === ConformityEnum.CONFORM, - datePrelevement: drinkingWaterRow.COULF_date_prelevement - ? dayjs(drinkingWaterRow.COULF_date_prelevement).format('YYYY-MM-DD') - : '', - }, - { - slug: 'SAVQ', - name: 'Saveur', - value: drinkingWaterRow.SAVQ_value ?? '', - isConform: drinkingWaterRow.SAVQ_conformity === ConformityEnum.CONFORM, - datePrelevement: drinkingWaterRow.SAVQ_date_prelevement - ? dayjs(drinkingWaterRow.SAVQ_date_prelevement).format('YYYY-MM-DD') - : '', - }, - { - slug: 'COULQ', - name: 'Couleur', - value: drinkingWaterRow.COULQ_value ?? '', - isConform: drinkingWaterRow.COULQ_conformity === ConformityEnum.CONFORM, - datePrelevement: drinkingWaterRow.COULQ_date_prelevement - ? dayjs(drinkingWaterRow.COULQ_date_prelevement).format('YYYY-MM-DD') - : '', - }, - { - slug: 'ASP', - name: 'Aspect', - value: drinkingWaterRow.ASP_value ?? '', - isConform: drinkingWaterRow.ASP_conformity === ConformityEnum.CONFORM, - datePrelevement: drinkingWaterRow.ASP_date_prelevement - ? dayjs(drinkingWaterRow.ASP_date_prelevement).format('YYYY-MM-DD') - : '', - }, - { - slug: 'ODQ', - name: 'Odeur', - value: drinkingWaterRow.ODQ_value ?? '', - isConform: drinkingWaterRow.ODQ_conformity === ConformityEnum.CONFORM, - datePrelevement: drinkingWaterRow.ODQ_date_prelevement - ? dayjs(drinkingWaterRow.ODQ_date_prelevement).format('YYYY-MM-DD') - : '', - }, - ]; -} + const conformityLimitBacteriological = + prelevement.conformite_limites_bact_prelevement; + const conformityReferenceBacteriological = + prelevement.conformite_references_bact_prelevement; -function getAllConclusions(drinkingWater: DrinkingWater): Array { - const allConclusions = []; - if ( - drinkingWater.PH_conformity === ConformityEnum.NOT_CONFORM && - !!drinkingWater.PH_conclusion_conformite_prelevement - ) { - allConclusions.push(drinkingWater.PH_conclusion_conformite_prelevement); - } - if ( - drinkingWater.TEAU_conformity === ConformityEnum.NOT_CONFORM && - !!drinkingWater.TEAU_conclusion_conformite_prelevement - ) { - allConclusions.push(drinkingWater.TEAU_conclusion_conformite_prelevement); - } - if ( - drinkingWater.PESTOT_conformity === ConformityEnum.NOT_CONFORM && - !!drinkingWater.PESTOT_conclusion_conformite_prelevement - ) { - allConclusions.push(drinkingWater.PESTOT_conclusion_conformite_prelevement); - } - if ( - drinkingWater.COULF_conformity === ConformityEnum.NOT_CONFORM && - !!drinkingWater.COULF_conclusion_conformite_prelevement - ) { - allConclusions.push(drinkingWater.COULF_conclusion_conformite_prelevement); + if (conformityLimitBacteriological === ConformityEnum.NOT_CONFORM) { + return ConformityEnum.NOT_CONFORM; } - if ( - drinkingWater.SAVQ_conformity === ConformityEnum.NOT_CONFORM && - !!drinkingWater.SAVQ_conclusion_conformite_prelevement - ) { - allConclusions.push(drinkingWater.SAVQ_conclusion_conformite_prelevement); + if (conformityReferenceBacteriological === ConformityEnum.NOT_CONFORM) { + return ConformityEnum.NOT_CONFORM; } if ( - drinkingWater.COULQ_conformity === ConformityEnum.NOT_CONFORM && - !!drinkingWater.COULQ_conclusion_conformite_prelevement + conformityLimitBacteriological === ConformityEnum.NOT_TESTED && + conformityReferenceBacteriological === ConformityEnum.NOT_TESTED ) { - allConclusions.push(drinkingWater.COULQ_conclusion_conformite_prelevement); + return ConformityEnum.NOT_TESTED; } + return ConformityEnum.CONFORM; +} + +function getWorstConformity( + conformity1: ConformityEnum, + conformity2: ConformityEnum, +): ConformityEnum { if ( - drinkingWater.ASP_conformity === ConformityEnum.NOT_CONFORM && - !!drinkingWater.ASP_conclusion_conformite_prelevement + conformity1 === ConformityEnum.NOT_CONFORM || + conformity2 === ConformityEnum.NOT_CONFORM ) { - allConclusions.push(drinkingWater.ASP_conclusion_conformite_prelevement); + return ConformityEnum.NOT_CONFORM; } if ( - drinkingWater.ODQ_conformity === ConformityEnum.NOT_CONFORM && - !!drinkingWater.ODQ_conclusion_conformite_prelevement + conformity1 === ConformityEnum.NOT_TESTED || + conformity2 === ConformityEnum.NOT_TESTED ) { - allConclusions.push(drinkingWater.ODQ_conclusion_conformite_prelevement); - } - if (!allConclusions.length) { - allConclusions.push( - "Eau d'alimentation conforme aux exigences de qualité en vigueur pour l'ensemble des paramètres mesurés.", - ); + return ConformityEnum.NOT_TESTED; } - return allConclusions; + return ConformityEnum.CONFORM; +} + +function getWorstConclusion(conclusion1: string, conclusion2: string): string { + return conclusion1.length > conclusion2.length ? conclusion1 : conclusion2; } export { - getUdiConformityStatus, - getUdiConformityValue, - mapParameterToRowData, - mapParameterRowDataToIndicatorByPeriodValues, - getAllConclusions, + checkPrelevementConformity, + checkPrelevementConformityChemical, + checkPrelevementConformityBacteriological, + getWorstConformity, + getWorstConclusion, }; diff --git a/api-node/src/utils/notifications/alert.ts b/api-node/src/utils/notifications/alert.ts index a2541206..e46c237a 100644 --- a/api-node/src/utils/notifications/alert.ts +++ b/api-node/src/utils/notifications/alert.ts @@ -31,14 +31,11 @@ import { AlertStatusThresholdEnum } from '~/utils/alert_status'; import { NotificationDotColor } from '~/types/notifications'; import { BathingWaterStatusEnum } from '~/types/api/bathing_water'; import { - ConformityStatusEnum, + ConformityEnum, ConformityNumberEnum, } from '~/types/api/drinking_water'; import { getBathingWaterSummaryValue } from '~/utils/bathing_water/bathing_water'; -import { - getAllConclusions, - getUdiConformityStatus, -} from '~/utils/drinking_water'; +import { checkPrelevementConformity } from '~/utils/drinking_water'; dayjs.extend(utc); @@ -295,8 +292,7 @@ export async function sendAlertNotification( if (indicatorSlug === IndicatorsSlugEnum.drinking_water) { const drinkingWater = indicatorRow as DrinkingWater; const isAlert = - getUdiConformityStatus(drinkingWater) === - ConformityStatusEnum.NOT_CONFORM; + checkPrelevementConformity(drinkingWater) === ConformityEnum.NOT_CONFORM; if (!isAlert) return false; data.drinking_water = { id: drinkingWater.id, @@ -305,7 +301,7 @@ export async function sendAlertNotification( const bathingWaterText = `🚰 Eaux du robinet : Non conforme ${drinkingWaterDotColor}`; rawBody.push(bathingWaterText); data.drinking_water.text = bathingWaterText; - const recommandation = getAllConclusions(drinkingWater)[0]; + const recommandation = drinkingWater.conclusion_conformite_prelevement; if (recommandation) { rawBody.push(recommandation); }