{{ fmt(v.lignes) }}
{{ k }} · {{ fmt(v.disparus) }} disparus
maj {{ (''+v.derniere_maj).slice(0,10) }}

Objectif

Compiler différentes sources de « produits » en une seule table produits ayant un identifiant unique dorians_product_id (numérique), avec la même structure. Une seule ligne par DIN, même si le produit existe en plusieurs formats (une seule ligne pour atorvastatine 80, qu'il vienne en 30, 100 ou 500 comprimés).

Sources

PrioritéNomClé nativeUne ligne parConversion → dorians_product_idÉtat
1BDPP — Base de données de produits pharmaceutiques (Santé Canada)DRUG_CODEproduitvaleur numérique du DIN (préfixe 00/01/02)actif
2BDPSNH — Produits de santé naturels homologués (Santé Canada)NPN (licence_number)produitvaleur numérique du NPN (97 % préfixe 80)actif
3RAMQ — Liste des médicamentsCOD_FACT_PRODUformatvaleur numérique (DIN réel ou pseudo-code 99xxxxxx)actif
4AQPP — liste des produitsDINformatvaleur numérique du DINactif
5McKesson — catalogue grossisteitem_numberformatdin, sinon NPN (mckesson_item_number_vs_npn, scraping), sinon gtin_unit ≥ 10⁹, sinon gtin_case ≥ 10⁹, sinon 66 + item sur 6 chiffresactif
RAMQ pseudo-DIN — liste des produitsà venir

Plages de l'identifiant numérique

PlageFamille
< 03 000 000DIN réels (préfixes 00/01/02, zéros de tête perdus — affichés repaddés sur 8 chiffres)
66 000 000 – 66 999 999McKesson sans identifiant : 66 + item_number sur 6 chiffres
77 000 001 +groupes génériques frappés (table groupes_generiques — pas des produits)
80 xxx xxxNPN
88 000 001 +produits personnels variés : 88 + auto-increment sur 6 chiffres (à venir)
99 xxx xxxpseudo-DIN RAMQ (codes de facturation)
≥ 10¹¹UPC/GTIN (produits sans autre identifiant, à venir)

Structure de produits

ColonneTypeNote
dorians_product_idBIGINT PKidentifiant unique numérique du projet
dinINT NULL, UNIQUEDIN bdpp, NPN bdpsnh ou pseudo-DIN ramq ; pour les jointures Santé Canada, filtrer din < 99000000
nomVARCHAR(500)BRAND_NAME / product_name / Marque
groupe_generiqueBIGINT NULLniveau molécule + teneur + forme (les interchangeables) : NO_SEQ_MED RAMQ par din, sinon triplet AQPP apparié à RAMQ, sinon id frappé 77xxxxxx (table groupes_generiques)
formatsJSON NULL[{ format_id, quant_format, unit_format, prix_unitaire }] — enrichis depuis RAMQ puis AQPP par dorians_product_id, peu importe la source de la ligne ; McKesson à venir
actifTINYINT(1)BDPP MARKETED · BDPSNH licence active · RAMQ inscrit · AQPP publié
actif_detailVARCHAR(100)« Commercialisé », statut_f, « Retiré de la liste AQPP (…) », « Disparu de la source (…) », …
sourceVARCHAR(10)bdpp / bdpsnh / ramq / aqpp / mck
updated_atDATETIMEposé uniquement quand la ligne change réellement

Structure de la base

TableRôle
produitsversion finale unifiée
produits_stage_$date_$istaging avant confirmation ($i auto-incrémenté si plusieurs le même jour)
bdpp / bdpsnh / ramq / aqpp / mckessondernière version de chaque source, avec updated_at, vu_le, disparu
bdpp_$date_brut / … / aqpp_$date_brutsnapshot brut de chaque téléchargement (archives, jamais modifiées après exclusions)
aqpp_2026_08_23_fichieramorçage AQPP : copie verbatim du fichier source (grain DIN+Format), lue par aqpp/3_extraire.js --table=…
groupes_generiquesgroupes frappés 77xxxxxx pour les produits AQPP hors RAMQ (durable, jamais purgée)
majs / majs_diffstraçabilité : chaque comparaison est enregistrée, chaque promotion pointée

Mise à jour incrémentielle

Les mises à jour sont incrémentielles, basées sur le téléchargement précédent : un DIN peut disparaître de la source, il ne disparaît jamais des tables. Il est marqué disparu=1 dans la table source et actif=0 / « Disparu de la source » dans produits. Aucun DELETE, jamais, et aucune promotion sans confirmation explicite.

Chaque source suit 6 étapes (dossiers bdpp/, bdpsnh/, ramq/, aqpp/ls du dossier = la checklist) :

1_telecharger.js         télécharge les fichiers bruts dans <source>/temp/$date/
2_verifier_structure.js  structure des fichiers vs définitions — échec = stop, rien en DB
3_extraire.js            extrait tout dans <source>_$date_brut
4_exclure.js             supprime les exclusions manuelles (de la brute seulement)
5_comparer.js            intégrité + diff complet vs table durable → majs / majs_diffs
6_promouvoir.js          CONFIRMATION puis upsert → table durable (disparus marqués)

Puis generer_produits/ : 1_generer_stage.js (fusion par priorité, enrichissements croisés formats et groupe générique) → 2_comparer.js (diff vs produits) → 3_promouvoir.js (confirmation puis upsert).

La checklist détaillée que Claude suit est dans CLAUDE.md à la racine du projet.

Exclusions manuelles

Enregistrements exclus des sources brutes avant unification. Appliquées par 4_exclure.js sur les tables brutes datées uniquement. Modifiables seulement dans <source>/exclusions.js, avec l'accord de Dorian.

{{ e.source.toUpperCase() }} — {{ e.raison }} ({{ e.n }})

Champ {{ e.cle }} · exclu par {{ e.exclu_par }} le {{ e.date }}

{{ e.valeurs.join(', ') }}
{{ fmt(dt.total) }} ligne(s){{ filtresActifs ? ' (filtrées)' : '' }} page {{ dt.page }} / {{ Math.max(1, Math.ceil(dt.total/dt.taille)) }} filtres : taper puis Entrée · tri : cliquer un en-tête · id : cliquer pour la fiche
{{ c.nom }}{{ dir==='asc' ? ' ▲' : ' ▼' }}

Aucun résultat.

Chargement…

Recherche « {{ recherche.q }} » — {{ fmt(recherche.rows.length) }} résultat(s){{ recherche.rows.length===200 ? ' (limité à 200, préciser la recherche)' : '' }}

dorians_product_iddinNomGroupe génériqueActifDétailSource
{{ pad(r.dorians_product_id) }} {{ r.din==null ? '' : pad(r.din) }} {{ r.nom }} {{ r.groupe_generique ?? '' }} {{ r.actif ? 'actif' : 'inactif' }} {{ r.actif_detail }} {{ r.source }}

Aucun produit trouvé.

Historique des comparaisons (majs)

#SourceBrut → cibleAjoutsModifsDisparitionsCrééePromue
{{ m.id }}{{ m.source }} {{ m.table_brut }} → {{ m.table_cible }} {{ fmt(res(m).n_ajouts) }}{{ fmt(res(m).n_modifs) }}{{ fmt(res(m).n_disparitions) }} {{ (''+m.created_at).slice(0,16).replace('T',' ') }} {{ m.promu ? 'promue' : 'non promue' }}

Cliquer sur une ligne pour voir le datatable complet de ses changements.