Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compléter les historiques de Déclarations #1482

Merged
merged 19 commits into from
Jan 28, 2025
Merged
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
2 changes: 1 addition & 1 deletion data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ REF_ICA_FONCTION_INGREDIENT | | | | | |
|ICA_POPULATION_RISQUE_DECLAREE | | |
|ICA_SUBSTANCE_DECLAREE | | |
|ICA_USAGER | | 🕵️anonymisée (contient Foreign Key vers USR, ADM, ETAB) |
|REF_ICA_TYPE_DECLARATION | |Enum ? ou obsolète ? (Art 1(, Art 1-, Simplifiée)) |
|REF_ICA_TYPE_DECLARATION | |Enum ? ou obsolète ? (Art 15, Art 16, Simplifiée)) |
|REF_ICA_TYPE_HERITAGE | | Enum ? (Simplifié ou Nouvelle formule)|
|REF_ICA_TYPE_VERSION_DECLARATION | | |
|REF_ICA_FORME_GALENIQUE | | |
Expand Down
186 changes: 177 additions & 9 deletions data/etl/teleicare_history/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,46 @@
from datetime import date, datetime, timezone

from django.core.exceptions import MultipleObjectsReturned, ValidationError
from django.db import IntegrityError
from django.db import IntegrityError, transaction

from phonenumber_field.phonenumber import PhoneNumber

from data.models import GalenicFormulation, SubstanceUnit
from data.models import (
Condition,
Effect,
GalenicFormulation,
Ingredient,
Microorganism,
Plant,
PlantPart,
Population,
Preparation,
Substance,
SubstanceUnit,
)
from data.models.company import ActivityChoices, Company
from data.models.declaration import Declaration
from data.models.declaration import (
ComputedSubstance,
Declaration,
DeclaredIngredient,
DeclaredMicroorganism,
DeclaredPlant,
)
from data.models.teleicare_history.ica_declaration import (
IcaComplementAlimentaire,
IcaDeclaration,
IcaEffetDeclare,
IcaPopulationCibleDeclaree,
IcaPopulationRisqueDeclaree,
IcaVersionDeclaration,
)
from data.models.teleicare_history.ica_declaration_composition import (
IcaIngredient,
IcaIngredientAutre,
IcaMicroOrganisme,
IcaPreparation,
IcaSubstanceDeclaree,
)
from data.models.teleicare_history.ica_etablissement import IcaEtablissement

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -177,6 +205,144 @@ def convert_str_date(value, aware=False):
8: Declaration.DeclarationStatus.ABANDONED, # 'abandonné'
}

DECLARATION_TYPE_TO_ARTICLE_MAPPING = {
1: Declaration.Article.ARTICLE_15,
2: Declaration.Article.ARTICLE_16,
3: None, # Type "simplifié" dans Teleicare, normalement liées à des modifications de déclarations déjà instruites
}


MANY_TO_MANY_PRODUCT_MODELS_MATCHING = {
"effects": {"teleIcare_model": IcaEffetDeclare, "teleIcare_pk": "objeff_ident", "CA_model": Effect},
"conditions_not_recommended": {
"teleIcare_model": IcaPopulationRisqueDeclaree,
"teleIcare_pk": "poprs_ident",
"CA_model": Condition,
},
"populations": {
"teleIcare_model": IcaPopulationCibleDeclaree,
"teleIcare_pk": "popcbl_ident",
"CA_model": Population,
},
}


def create_declared_plant(declaration, teleIcare_plant, active):
declared_plant = DeclaredPlant(
declaration=declaration,
plant=Plant.objects.get(siccrf_id=teleIcare_plant.plte_ident),
active=active,
quantity=teleIcare_plant.prepa_qte if active else None,
unit=SubstanceUnit.objects.get(siccrf_id=teleIcare_plant.unt_ident) if active else None,
preparation=Preparation.objects.get(siccrf_id=teleIcare_plant.typprep_ident) if active else None,
used_part=PlantPart.objects.get(siccrf_id=teleIcare_plant.pplan_ident) if active else None,
)
return declared_plant


def create_declared_microorganism(declaration, teleIcare_microorganism, active):
declared_microorganism = DeclaredMicroorganism(
declaration=declaration,
microorganism=Microorganism.objects.get(siccrf_id=teleIcare_microorganism.morg_ident),
active=active,
activated=True, # dans TeleIcare, le champ `activated` n'existait pas, les MO inactivés étaient des `ingrédients autres`
strain=teleIcare_microorganism.ingmorg_souche,
quantity=teleIcare_microorganism.ingmorg_quantite_par_djr,
)
return declared_microorganism


def create_declared_ingredient(declaration, teleIcare_ingredient, active):
declared_ingredient = DeclaredIngredient(
declaration=declaration,
ingredient=Ingredient.objects.get(siccrf_id=teleIcare_ingredient.inga_ident),
active=active,
quantity=None, # dans TeleIcare, les ingrédients n'avaient pas de quantité associée
)
return declared_ingredient


def create_computed_substance(declaration, teleIcare_substance):
computed_substance = ComputedSubstance(
declaration=declaration,
substance=Substance.objects.get(siccrf_id=teleIcare_substance.sbsact_ident),
quantity=teleIcare_substance.sbsactdecl_quantite_par_djr,
# le champ de TeleIcare 'sbsact_commentaires' n'est pas repris
)
return computed_substance


@transaction.atomic
def add_product_info_from_teleicare_history(declaration, vrsdecl_ident):
"""
Cette function importe les champs ManyToMany des déclarations, relatifs à l'onglet "Produit"
Il est nécessaire que les objets soient enregistrés en base (et aient obtenu un id) grâce à la fonction
`create_declaration_from_teleicare_history` pour updater leurs champs ManyToMany.
"""
# TODO: other_conditions=conditions_not_recommended,
# TODO: other_effects=
for CA_field_name, struct in MANY_TO_MANY_PRODUCT_MODELS_MATCHING.items():
# par exemple Declaration.populations
CA_field = getattr(declaration, CA_field_name)
# TODO: utiliser les dataclass ici
pk_field = struct["teleIcare_pk"]
CA_model = struct["CA_model"]
teleIcare_model = struct["teleIcare_model"]
CA_field.set(
[
CA_model.objects.get(siccrf_id=getattr(TI_object, pk_field))
for TI_object in (teleIcare_model.objects.filter(vrsdecl_ident=vrsdecl_ident))
]
)


@transaction.atomic
def add_composition_from_teleicare_history(declaration, vrsdecl_ident):
"""
Cette function importe les champs ManyToMany des déclarations, relatifs à l'onglet "Composition"
Il est nécessaire que les objets soient enregistrés en base (et aient obtenu un id) grâce à la fonction
`create_declaration_from_teleicare_history` pour updater leurs champs ManyToMany.
"""
bulk_ingredients = {DeclaredPlant: [], DeclaredMicroorganism: [], DeclaredIngredient: [], ComputedSubstance: []}
for ingredient in IcaIngredient.objects.filter(vrsdecl_ident=vrsdecl_ident):
if ingredient.tying_ident == 1:
declared_plant = create_declared_plant(
declaration=declaration,
teleIcare_plant=IcaPreparation.objects.get(
ingr_ident=ingredient.ingr_ident,
),
active=ingredient.fctingr_ident == 1,
)
bulk_ingredients[DeclaredPlant].append(declared_plant)
elif ingredient.tying_ident == 2:
declared_microorganism = create_declared_microorganism(
declaration=declaration,
teleIcare_microorganism=IcaMicroOrganisme.objects.get(
ingr_ident=ingredient.ingr_ident,
),
active=ingredient.fctingr_ident == 1,
)
bulk_ingredients[DeclaredMicroorganism].append(declared_microorganism)
elif ingredient.tying_ident == 3:
declared_ingredient = create_declared_ingredient(
declaration=declaration,
teleIcare_ingredient=IcaIngredientAutre.objects.get(
ingr_ident=ingredient.ingr_ident,
),
active=ingredient.fctingr_ident == 1,
)
bulk_ingredients[DeclaredIngredient].append(declared_ingredient)
# dans TeleIcare les `declared_substances` étaient des ingrédients
# donc on ne rempli pas le champ declaration.declared_substances grâce à l'historique
for teleIcare_substance in IcaSubstanceDeclaree.objects.filter(vrsdecl_ident=vrsdecl_ident):
computed_substance = create_computed_substance(
declaration=declaration, teleIcare_substance=teleIcare_substance
)
bulk_ingredients[ComputedSubstance].append(computed_substance)

for model, bulk_of_objects in bulk_ingredients.items():
model.objects.bulk_create(bulk_of_objects)


def create_declaration_from_teleicare_history():
"""
Expand Down Expand Up @@ -248,12 +414,8 @@ def create_declaration_from_teleicare_history():
minimum_duration=latest_ica_version_declaration.vrsdecl_durabilite,
instructions=latest_ica_version_declaration.vrsdecl_mode_emploi or "",
warning=latest_ica_version_declaration.vrsdecl_mise_en_garde or "",
# TODO: ces champs proviennent de tables pas encore importées
# populations=
# conditions_not_recommended=
# other_conditions=
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ces changements couvrent aussi other_conditions ?

Copy link
Collaborator Author

@pletelli pletelli Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pas encore, je le ferais dans une future PR

# effects=
# other_effects=
calculated_article=DECLARATION_TYPE_TO_ARTICLE_MAPPING[latest_ica_declaration.tydcl_ident],
# TODO: ces champs sont à importer
# address=
# postal_code=
# city=
Expand All @@ -266,6 +428,12 @@ def create_declaration_from_teleicare_history():
try:
with suppress_autotime(declaration, ["creation_date", "modification_date"]):
declaration.save()
add_product_info_from_teleicare_history(
declaration, latest_ica_version_declaration.vrsdecl_ident
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

J'ai du mal à comprendre ce code. Je pense que c'est surtout car je n'ai pas les infos sur ica/vrs, population cible vs population... mais j'ai une question et une suggestion:
Question: est-ce qu'il existe une risque de MultipleObjectsReturned pour les gets ?

Suggestion:

cible_populations = Ica...Declaree.objects.filter(...)
populations = [Population.objects.get(...) for population in cible_populations]
declaration.populations.set(populations)

et pour les conditions aussi. Après, je sais pas si cible_populations est le bon nom de variable

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ça a été factorisé ailleurs, l291

add_composition_from_teleicare_history(
declaration, latest_ica_version_declaration.vrsdecl_ident
)
nb_created_declarations += 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

je me demande si c'est mieux de mettre ça au dessus de ligne 278 si jamais il y a une erreur pas attendu avec les sets ?

except IntegrityError:
# cette Déclaration a déjà été créée
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pour confirmer : si la déclaration est déjà créée, on veut pas MAJ les populations ni les conditions ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oui et non.
Pour le moment cet import est plutôt pensé comme un import One-Shot.
Et même si je vais l'effectuer 2 fois, je pensais d'abord supprimer toutes les déclarations historiques (car elles ne peuvent pour le moment pas avoir été modifiées) pour les réimporter entièrement.
Mais c'est vrai que je pourrais envisager de juste mettre à jour des champs ManyToMany

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je dirais de faire au plus simple s'il n'y a pas de changement possible

Expand Down
43 changes: 43 additions & 0 deletions data/etl/teleicare_history/sql/company_table_creation.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
-- Le but de ce fichier est de permettre la recréation des tables Teleicare correspondant
-- au modèle de Declaration Compl'Alim


-- int int smallint -> integer
-- vachar -> text
-- bit -> boolean
-- datetime -> text (pour une conversion ensuite)

CREATE TABLE ICA_ETABLISSEMENT (
ETAB_IDENT integer PRIMARY KEY,
COG_IDENT integer NULL,
ETAB_IDENT_PARENT integer NULL,
PAYS_IDENT integer NOT NULL,
ETAB_SIRET text NULL,
ETAB_NUMERO_TVA_INTRA text NULL,
ETAB_RAISON_SOCIALE text NOT NULL,
ETAB_ENSEIGNE text NULL,
ETAB_ADRE_VILLE text NULL,
ETAB_ADRE_CP text NULL,
ETAB_ADRE_VOIE text NULL,
ETAB_ADRE_COMP text NULL,
ETAB_ADRE_COMP2 text NULL,
ETAB_ADRE_DIST text NULL,
ETAB_TELEPHONE text NULL,
ETAB_FAX text NULL,
ETAB_COURRIEL text NULL,
ETAB_SITE_INTERNET text NULL,
ETAB_ICA_FACONNIER boolean NULL,
ETAB_ICA_FABRICANT boolean NULL,
ETAB_ICA_CONSEIL boolean NULL,
ETAB_ICA_IMPORTATEUR boolean NULL,
ETAB_ICA_INTRODUCTEUR boolean NULL,
ETAB_ICA_DISTRIBUTEUR boolean NULL,
ETAB_ICA_ENSEIGNE text NULL,
ETAB_ADRE_REGION text NULL,
ETAB_DT_AJOUT_IDENT_PARENT text NULL,
ETAB_NUM_ADH_TELE_PROC text NULL,
ETAB_COMMENTAIRE_IDENT_PARENT text NULL,
ETAB_NOM_DOMAINE text NULL,
ETAB_DATE_ADHESION text NULL,
ETAB_NB_COMPTE_AUTORISE integer NOT NULL
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
CREATE TABLE ICA_INGREDIENT (
INGR_IDENT integer PRIMARY KEY,
VRSDECL_IDENT integer NOT NULL,
FCTINGR_IDENT integer NOT NULL,
TYING_IDENT integer NOT NULL,
INGR_COMMENTAIRES text NULL
);




CREATE TABLE ICA_MICRO_ORGANISME (
INGR_IDENT integer PRIMARY KEY,
MORG_IDENT integer NOT NULL,
INGMORG_SOUCHE text NULL,
INGMORG_QUANTITE_PAR_DJR bigint NULL
);



CREATE TABLE ICA_INGREDIENT_AUTRE (
INGR_IDENT integer PRIMARY KEY,
INGA_IDENT integer NOT NULL
);



CREATE TABLE ICA_PREPARATION (
INGR_IDENT integer PRIMARY KEY,
PLTE_IDENT integer NOT NULL,
PPLAN_IDENT integer NULL,
UNT_IDENT integer NULL,
TYPPREP_IDENT integer NULL,
PREPA_QTE float NULL
);


CREATE TABLE ICA_SUBSTANCE_DECLAREE(
VRSDECL_IDENT integer NOT NULL,
SBSACT_IDENT integer NOT NULL,
SBSACT_COMMENTAIRES text NULL,
SBSACTDECL_QUANTITE_PAR_DJR float NULL,
PRIMARY KEY (VRSDECL_IDENT, SBSACT_IDENT)
);
Loading
Loading