@tekkare/romulus 0.1.251 → 0.1.252
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/layer/app/app.config.ts +6 -0
- package/layer/app/components/OipPubmedSummary.vue +223 -0
- package/layer/app/components/OipResourceDrawer.vue +331 -0
- package/layer/app/components/OipResourceDrawerLink.vue +40 -0
- package/layer/app/components/OipResourceField.vue +41 -0
- package/layer/app/components/OipResourceGenericCard.vue +54 -0
- package/layer/app/components/OipResourceSource.vue +50 -0
- package/layer/app/components/OipResourceTags.vue +65 -0
- package/layer/app/components/OipResourceView.vue +107 -0
- package/layer/app/components/oip-drugs/fiches/AssociationFiche.vue +775 -0
- package/layer/app/components/oip-drugs/fiches/BrandFiche.vue +1111 -0
- package/layer/app/components/oip-drugs/fiches/IndicationFiche.vue +182 -0
- package/layer/app/components/oip-drugs/fiches/JoFiche.vue +101 -0
- package/layer/app/components/oip-drugs/fiches/PndsFiche.vue +279 -0
- package/layer/app/components/oip-drugs/heart/JoCard.vue +314 -0
- package/layer/app/components/oip-drugs/heart/TextHighlight.vue +326 -0
- package/layer/app/components/oip-drugs/heart/ansm/BrandCard.vue +187 -0
- package/layer/app/components/oip-drugs/heart/ansm/CisDetail.vue +363 -0
- package/layer/app/components/oip-drugs/heart/ansm/CisTable.vue +167 -0
- package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyCard.vue +373 -0
- package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyFiche.vue +372 -0
- package/layer/app/components/oip-drugs/heart/associations/TranscriptionCard.vue +55 -0
- package/layer/app/components/oip-drugs/heart/elastic/SourceContainerElastic.vue +103 -0
- package/layer/app/components/oip-drugs/heart/france/BrandGroupCard.vue +450 -0
- package/layer/app/components/oip-drugs/heart/mitm/Card.vue +199 -0
- package/layer/app/components/oip-drugs/heart/mitm/Source.vue +23 -0
- package/layer/app/components/oip-drugs/heart/mitm/details/Overview.vue +381 -0
- package/layer/app/components/oip-drugs/heart/put/Card.vue +212 -0
- package/layer/app/components/oip-drugs/heart/results/AccesCard.vue +696 -0
- package/layer/app/components/oip-drugs/heart/results/AlertCard.vue +342 -0
- package/layer/app/components/oip-drugs/heart/results/AsmrCursor.vue +229 -0
- package/layer/app/components/oip-drugs/heart/results/EvalCard.vue +370 -0
- package/layer/app/components/oip-drugs/heart/results/IndicCard.vue +743 -0
- package/layer/app/components/oip-drugs/heart/results/MatchExplain.vue +243 -0
- package/layer/app/components/oip-drugs/heart/results/PrefRows.vue +105 -0
- package/layer/app/components/oip-drugs/heart/results/details/HasHistory.vue +487 -0
- package/layer/app/components/oip-drugs/heart/results/details/InternationalCountryCard.vue +461 -0
- package/layer/app/components/oip-drugs/heart/results/details/InternationalOverview.vue +200 -0
- package/layer/app/components/oip-drugs/heart/results/details/InternationalPriceDrawer.vue +647 -0
- package/layer/app/components/oip-drugs/heart/results/details/Monograph.vue +128 -0
- package/layer/app/components/oip-drugs/heart/results/details/OpinionDrawer.vue +535 -0
- package/layer/app/components/oip-drugs/heart/results/details/PresentationDrawer.vue +738 -0
- package/layer/app/components/oip-drugs/heart/results/details/Price.vue +227 -0
- package/layer/app/components/oip-drugs/heart/results/details/PriceOverview.vue +1261 -0
- package/layer/app/components/oip-drugs/heart/results/details/RecordDrawer.vue +494 -0
- package/layer/app/components/oip-drugs/heart/results/details/RecordList.vue +426 -0
- package/layer/app/components/oip-drugs/heart/results/les/Card.vue +340 -0
- package/layer/app/components/oip-drugs/heart/results/les/Drawer.vue +67 -0
- package/layer/app/components/oip-drugs/heart/results/les/IndicationDetail.vue +149 -0
- package/layer/app/components/oip-drugs/heart/results/les/Source.vue +20 -0
- package/layer/app/components/oip-drugs/heart/results/les/SourceTag.vue +39 -0
- package/layer/app/components/oip-drugs/heart/searchPage/DynamicSource.vue +29 -0
- package/layer/app/components/oip-drugs/medicam/market/Chart.vue +121 -0
- package/layer/app/components/oip-drugs/outpatient/ProductEvolution.vue +236 -0
- package/layer/app/components/oip-drugs/resources/Ansm.vue +75 -0
- package/layer/app/components/oip-drugs/resources/Association.vue +30 -0
- package/layer/app/components/oip-drugs/resources/Brand.vue +65 -0
- package/layer/app/components/oip-drugs/resources/Contribution.vue +216 -0
- package/layer/app/components/oip-drugs/resources/Hospital.vue +151 -0
- package/layer/app/components/oip-drugs/resources/Indication.vue +54 -0
- package/layer/app/components/oip-drugs/resources/Jo.vue +57 -0
- package/layer/app/components/oip-drugs/resources/Medicam.vue +140 -0
- package/layer/app/components/oip-drugs/resources/Mitm.vue +84 -0
- package/layer/app/components/oip-drugs/resources/NationalAssembly.vue +93 -0
- package/layer/app/components/oip-drugs/resources/Outpatient.vue +97 -0
- package/layer/app/components/oip-drugs/resources/Pnds.vue +24 -0
- package/layer/app/components/oip-drugs/resources/Price.vue +106 -0
- package/layer/app/components/oip-drugs/resources/Put.vue +143 -0
- package/layer/app/components/oip-science/appel-a-projet/Cards.vue +162 -0
- package/layer/app/components/oip-science/clinical-trials/Cards.vue +382 -0
- package/layer/app/components/oip-science/clinical-trials/CardsGlobal.vue +273 -0
- package/layer/app/components/oip-science/clinical-trials/LinkedList.vue +366 -0
- package/layer/app/components/oip-science/clinical-trials/PairBadge.vue +25 -0
- package/layer/app/components/oip-science/clinical-trials/PairBody.vue +182 -0
- package/layer/app/components/oip-science/clinical-trials/SitesDrawer.vue +95 -0
- package/layer/app/components/oip-science/common/Form.vue +73 -0
- package/layer/app/components/oip-science/common/MatchAvgTag.vue +283 -0
- package/layer/app/components/oip-science/common/NoData.vue +16 -0
- package/layer/app/components/oip-science/common/ScoreBadge.vue +35 -0
- package/layer/app/components/oip-science/common/Source.vue +31 -0
- package/layer/app/components/oip-science/common/TableTags.vue +279 -0
- package/layer/app/components/oip-science/common/Timeline.vue +104 -0
- package/layer/app/components/oip-science/common/ToolTips.vue +89 -0
- package/layer/app/components/oip-science/cross/publicationPubmed.vue +484 -0
- package/layer/app/components/oip-science/crossData/CrossDataLocations.vue +71 -0
- package/layer/app/components/oip-science/ctgov/Investigators.vue +148 -0
- package/layer/app/components/oip-science/ctgov/Locations.vue +102 -0
- package/layer/app/components/oip-science/ctgov/Publications.vue +52 -0
- package/layer/app/components/oip-science/ctgov/Sponsors.vue +66 -0
- package/layer/app/components/oip-science/ctgov/StudyDescription.vue +1136 -0
- package/layer/app/components/oip-science/ctgov-centers/ClinicalTrials.vue +40 -0
- package/layer/app/components/oip-science/ctgov-centers/Publications.vue +50 -0
- package/layer/app/components/oip-science/ctgov-investigators/ClinicalTrials.vue +43 -0
- package/layer/app/components/oip-science/ctgov-investigators/Informations.vue +171 -0
- package/layer/app/components/oip-science/ctgov-investigators/Publications.vue +44 -0
- package/layer/app/components/oip-science/elastic/DateRangeFilter.vue +209 -0
- package/layer/app/components/oip-science/elastic/Filter.vue +719 -0
- package/layer/app/components/oip-science/elastic/MatchExplain.vue +152 -0
- package/layer/app/components/oip-science/establishments/Cards.vue +183 -0
- package/layer/app/components/oip-science/establishments/ClinicalTrials.vue +62 -0
- package/layer/app/components/oip-science/establishments/Hcps.vue +227 -0
- package/layer/app/components/oip-science/establishments/Informations.vue +263 -0
- package/layer/app/components/oip-science/establishments/LinkedList.vue +153 -0
- package/layer/app/components/oip-science/establishments/MatchedLabels.vue +194 -0
- package/layer/app/components/oip-science/establishments/PublicFinance.vue +62 -0
- package/layer/app/components/oip-science/euct/Investigators.vue +125 -0
- package/layer/app/components/oip-science/euct/Locations.vue +102 -0
- package/layer/app/components/oip-science/euct/Products.vue +101 -0
- package/layer/app/components/oip-science/euct/Publications.vue +89 -0
- package/layer/app/components/oip-science/euct/Sponsors.vue +86 -0
- package/layer/app/components/oip-science/euct/StudyDescription.vue +1075 -0
- package/layer/app/components/oip-science/euct-centers/ClinicalTrials.vue +40 -0
- package/layer/app/components/oip-science/euct-centers/Publications.vue +55 -0
- package/layer/app/components/oip-science/euct-investigators/ClinicalTrials.vue +41 -0
- package/layer/app/components/oip-science/euct-investigators/Informations.vue +171 -0
- package/layer/app/components/oip-science/euct-investigators/Publications.vue +51 -0
- package/layer/app/components/oip-science/hcps/AppelsAProjet.vue +62 -0
- package/layer/app/components/oip-science/hcps/Cards.vue +237 -0
- package/layer/app/components/oip-science/hcps/ClinicalTrials.vue +78 -0
- package/layer/app/components/oip-science/hcps/Informations.vue +296 -0
- package/layer/app/components/oip-science/hcps/LinkedList.vue +153 -0
- package/layer/app/components/oip-science/hcps/MatchedLabels.vue +207 -0
- package/layer/app/components/oip-science/hcps/Pubmed.vue +128 -0
- package/layer/app/components/oip-science/local/SliderTabs.vue +99 -0
- package/layer/app/components/oip-science/local/SliderTabsFull.vue +135 -0
- package/layer/app/components/oip-science/pubmed/Authors.vue +128 -0
- package/layer/app/components/oip-science/pubmed/Cards.vue +417 -0
- package/layer/app/components/oip-science/pubmed/ClinicalTrials.vue +71 -0
- package/layer/app/components/oip-science/resources/Phrc.vue +39 -0
- package/layer/app/composables/useOipChapterScroll.ts +43 -0
- package/layer/app/composables/useOipResourceHooks.ts +27 -0
- package/layer/app/composables/useOipResourceNav.ts +66 -0
- package/layer/app/composables/useOipResourceViews.ts +117 -0
- package/layer/app/composables/useOipResources.ts +83 -0
- package/layer/app/oip-drugs/PORTING.md +120 -0
- package/layer/app/oip-drugs/composables/useAssociationDetail.ts +220 -0
- package/layer/app/oip-drugs/composables/useAssociationExport.ts +63 -0
- package/layer/app/oip-drugs/composables/useAssociationLookup.ts +105 -0
- package/layer/app/oip-drugs/composables/useCardPrefOptions.ts +201 -0
- package/layer/app/oip-drugs/composables/useCurrency.ts +121 -0
- package/layer/app/oip-drugs/composables/useDirectusSources.ts +224 -0
- package/layer/app/oip-drugs/composables/useHeartBrandMatches.ts +112 -0
- package/layer/app/oip-drugs/composables/useHeartDetailSearches.ts +479 -0
- package/layer/app/oip-drugs/composables/useHeartIntlApprovals.ts +661 -0
- package/layer/app/oip-drugs/composables/useHeartLesIndication.ts +168 -0
- package/layer/app/oip-drugs/composables/useHeartMatchExplain.ts +231 -0
- package/layer/app/oip-drugs/composables/useHeartReports.ts +239 -0
- package/layer/app/oip-drugs/composables/useHeartSearchFields.ts +233 -0
- package/layer/app/oip-drugs/composables/useMergeByKey.ts +23 -0
- package/layer/app/oip-drugs/composables/useOipDrugsElastic.ts +621 -0
- package/layer/app/oip-drugs/composables/usePageEmpty.ts +58 -0
- package/layer/app/oip-drugs/composables/v-useSalesSource.ts +64 -0
- package/layer/app/oip-drugs/drugs.css +500 -0
- package/layer/app/oip-drugs/esIndices.ts +322 -0
- package/layer/app/oip-drugs/kinds.ts +264 -0
- package/layer/app/oip-drugs/locales/en.json +1571 -0
- package/layer/app/oip-drugs/locales/fr.json +1571 -0
- package/layer/app/oip-drugs/monographs/blincyto-has.vue +289 -0
- package/layer/app/oip-drugs/monographs/darzalex-has.vue +276 -0
- package/layer/app/oip-drugs/monographs/dupixent-has.vue +216 -0
- package/layer/app/oip-drugs/monographs/enhertu-has.vue +280 -0
- package/layer/app/oip-drugs/monographs/has-sein-2023.vue +1649 -0
- package/layer/app/oip-drugs/monographs/ibrance-has.vue +277 -0
- package/layer/app/oip-drugs/monographs/imfinzi-has.vue +293 -0
- package/layer/app/oip-drugs/monographs/keytruda-has.vue +334 -0
- package/layer/app/oip-drugs/monographs/kisqali-has.vue +201 -0
- package/layer/app/oip-drugs/monographs/opdivo-has.vue +231 -0
- package/layer/app/oip-drugs/monographs/ozempic-has.vue +186 -0
- package/layer/app/oip-drugs/monographs/tecentriq-has.vue +312 -0
- package/layer/app/oip-drugs/monographs/yescarta-has.vue +272 -0
- package/layer/app/oip-drugs/types/elastic.ts +113 -0
- package/layer/app/oip-drugs/types/es/agencies.ts +152 -0
- package/layer/app/oip-drugs/types/es/directory.ts +54 -0
- package/layer/app/oip-drugs/types/es/docByIndex.ts +151 -0
- package/layer/app/oip-drugs/types/es/openmedic.ts +67 -0
- package/layer/app/oip-drugs/types/es/outpatient.ts +71 -0
- package/layer/app/oip-drugs/types/es/portal.ts +130 -0
- package/layer/app/oip-drugs/types/es/retail.ts +107 -0
- package/layer/app/oip-drugs/types/es/statistics.ts +29 -0
- package/layer/app/oip-drugs/types/heart/CurrencyRate.ts +16 -0
- package/layer/app/oip-drugs/types/heart/ansm.ts +106 -0
- package/layer/app/oip-drugs/types/heart/tag.ts +29 -0
- package/layer/app/oip-drugs/utils/associationFields.ts +118 -0
- package/layer/app/oip-drugs/utils/contributions.ts +36 -0
- package/layer/app/oip-drugs/utils/countryMapping.ts +387 -0
- package/layer/app/oip-drugs/utils/esHighlight.ts +21 -0
- package/layer/app/oip-drugs/utils/exportSheets.ts +57 -0
- package/layer/app/oip-drugs/utils/heartIntlPrice.ts +228 -0
- package/layer/app/oip-drugs/utils/heartStyleAndFormat.ts +262 -0
- package/layer/app/oip-drugs/utils/heartStyleAnsm.ts +71 -0
- package/layer/app/oip-drugs/utils/latestDate.ts +31 -0
- package/layer/app/oip-drugs/utils/p-heartCardTags.ts +53 -0
- package/layer/app/oip-drugs/utils/priceExport.ts +269 -0
- package/layer/app/oip-drugs/utils/q-hasRatings.ts +43 -0
- package/layer/app/oip-drugs/utils/s-openCard.ts +20 -0
- package/layer/app/oip-drugs/utils/sourceUuids.ts +155 -0
- package/layer/app/oip-drugs/utils/styleAndFormat.ts +40 -0
- package/layer/app/oip-drugs/utils/v-salesSeries.ts +156 -0
- package/layer/app/oip-drugs/utils/v-scopeTags.ts +23 -0
- package/layer/app/oip-science/PORTING.md +93 -0
- package/layer/app/oip-science/assets/smartSelection.json +428 -0
- package/layer/app/oip-science/composables/useAuthorMatching.ts +342 -0
- package/layer/app/oip-science/composables/useCTCenter.ts +107 -0
- package/layer/app/oip-science/composables/useCTInvestigators.ts +107 -0
- package/layer/app/oip-science/composables/useEstablishmentCrossData.ts +139 -0
- package/layer/app/oip-science/composables/useEstablishmentMatching.ts +176 -0
- package/layer/app/oip-science/composables/useEuct.ts +418 -0
- package/layer/app/oip-science/composables/useEuctCenter.ts +107 -0
- package/layer/app/oip-science/composables/useEuctInvestigators.ts +107 -0
- package/layer/app/oip-science/composables/useGlobalClinicalTrials.ts +624 -0
- package/layer/app/oip-science/composables/useLinkedTrials.ts +83 -0
- package/layer/app/oip-science/composables/useOipElastic.ts +658 -0
- package/layer/app/oip-science/composables/useOipSearchExplain.ts +166 -0
- package/layer/app/oip-science/composables/usePubmed.ts +487 -0
- package/layer/app/oip-science/composables/useSearchFields.ts +153 -0
- package/layer/app/oip-science/indexConfig.json +794 -0
- package/layer/app/oip-science/locales/en.json +483 -0
- package/layer/app/oip-science/locales/fr.json +483 -0
- package/layer/app/oip-science/science.css +1182 -0
- package/layer/app/oip-science/utils/a-openCard.ts +23 -0
- package/layer/app/oip-science/utils/b-openCard.ts +35 -0
- package/layer/app/oip-science/utils/c-filterContext.ts +81 -0
- package/layer/app/oip-science/utils/f-types.ts +241 -0
- package/layer/app/oip-science/utils/frDiseaseTerms.ts +269 -0
- package/layer/app/oip-science/utils/matchMethod.ts +53 -0
- package/layer/app/oip-science/utils/sliderTabs.ts +13 -0
- package/layer/app/oip-science/utils/sources.ts +15 -0
- package/layer/app/oip-science/utils/styleAndFormat.ts +608 -0
- package/layer/app/oip-science/utils/toPresets.ts +7 -0
- package/layer/app/oip-science/utils/valueTable.ts +126 -0
- package/layer/app/plugins/oip-science-i18n.ts +17 -0
- package/layer/app/utils/oipFicheRoutes.ts +118 -0
- package/layer/app/utils/oipResources.ts +440 -0
- package/layer/nuxt.config.ts +22 -2
- package/layer/public/reports/blincyto-has.html +292 -0
- package/layer/public/reports/blincyto-has.pdf +0 -0
- package/layer/public/reports/darzalex-has.html +279 -0
- package/layer/public/reports/darzalex-has.pdf +0 -0
- package/layer/public/reports/dupixent-has.html +217 -0
- package/layer/public/reports/dupixent-has.pdf +0 -0
- package/layer/public/reports/enhertu-has.html +283 -0
- package/layer/public/reports/enhertu-has.pdf +0 -0
- package/layer/public/reports/has-sein-2023.html +1639 -0
- package/layer/public/reports/ibrance-has.html +280 -0
- package/layer/public/reports/ibrance-has.pdf +0 -0
- package/layer/public/reports/imfinzi-has.html +297 -0
- package/layer/public/reports/imfinzi-has.pdf +0 -0
- package/layer/public/reports/keytruda-has.html +337 -0
- package/layer/public/reports/keytruda-has.pdf +0 -0
- package/layer/public/reports/kisqali-has.html +202 -0
- package/layer/public/reports/kisqali-has.pdf +0 -0
- package/layer/public/reports/opdivo-has.html +232 -0
- package/layer/public/reports/opdivo-has.pdf +0 -0
- package/layer/public/reports/ozempic-has.html +188 -0
- package/layer/public/reports/ozempic-has.pdf +0 -0
- package/layer/public/reports/tecentriq-has.html +315 -0
- package/layer/public/reports/tecentriq-has.pdf +0 -0
- package/layer/public/reports/yescarta-has.html +275 -0
- package/layer/public/reports/yescarta-has.pdf +0 -0
- package/layer/server/api/oip/es.post.ts +99 -0
- package/layer/server/api/oip/pdf.get.ts +70 -0
- package/layer/server/api/oip/reports.get.ts +98 -0
- package/layer/server/api/oip/resource-types.get.ts +22 -0
- package/package.json +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* De l'ecriture d'un nom d'association a sa fiche (portage de
|
|
3
|
+
* useAssociationLookup de fr_drugs_app).
|
|
4
|
+
*
|
|
5
|
+
* Les contributions patients des avis HAS nomment l'association qui les signe
|
|
6
|
+
* (`association_label`, et son regroupement `association_label_regroup`), mais
|
|
7
|
+
* ces noms ne sont pas ceux de l'annuaire : sur les 156 associations qui
|
|
8
|
+
* contribuent, 43 portent dans les avis un nom que l'annuaire n'a pas comme
|
|
9
|
+
* `_id`. Pointer la fiche avec le nom de l'avis menait donc une fois sur
|
|
10
|
+
* quatre a un « Association introuvable ».
|
|
11
|
+
*
|
|
12
|
+
* L'annuaire, lui, liste toutes les ecritures connues d'une association dans
|
|
13
|
+
* `association_label`, separees par `; ` : ce sont elles qui font le pont.
|
|
14
|
+
* C'est la reciproque de `useAssociationDetail`.
|
|
15
|
+
*
|
|
16
|
+
* Les libelles de l'annuaire sont des `keyword` sans sous-champ analyse : un
|
|
17
|
+
* nom noye dans la liste des variantes ne se trouve donc pas par `match`,
|
|
18
|
+
* d'ou la regexp qui borne le nom aux separateurs.
|
|
19
|
+
*/
|
|
20
|
+
import { ES_INDEX } from "../esIndices";
|
|
21
|
+
import { ASSOCIATION_FIELDS } from "../utils/associationFields";
|
|
22
|
+
import useOipDrugsElastic from "./useOipDrugsElastic";
|
|
23
|
+
|
|
24
|
+
/** Caracteres que la syntaxe des regexp Elasticsearch se reserve. */
|
|
25
|
+
const REGEXP_SPECIAL = /([.?+*|{}[\]()"\\#@&<>~])/g;
|
|
26
|
+
|
|
27
|
+
const escapeRegexp = (value: string) => value.replace(REGEXP_SPECIAL, "\\$1");
|
|
28
|
+
|
|
29
|
+
const key = (name: unknown) => String(name ?? "").trim().toLowerCase();
|
|
30
|
+
|
|
31
|
+
export function useAssociationLookup() {
|
|
32
|
+
const { queryCustomElastic } = useOipDrugsElastic();
|
|
33
|
+
|
|
34
|
+
/** Ecriture d'un nom (en minuscules) vers l'`_id` de la fiche. */
|
|
35
|
+
const ids = ref<Record<string, string>>({});
|
|
36
|
+
|
|
37
|
+
/** Noms deja demandes, resolus ou non : une page ne les redemande pas. */
|
|
38
|
+
const asked = new Set<string>();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resout un paquet de noms en une requete. Appelable a chaque arrivee de
|
|
42
|
+
* donnees : seuls les noms encore inconnus repartent.
|
|
43
|
+
*/
|
|
44
|
+
async function load(names: unknown[]) {
|
|
45
|
+
const wanted = [...new Set(names.map((name) => String(name ?? "").trim()))]
|
|
46
|
+
.filter((name) => name !== "" && !asked.has(key(name)));
|
|
47
|
+
if (wanted.length === 0) return;
|
|
48
|
+
wanted.forEach((name) => asked.add(key(name)));
|
|
49
|
+
|
|
50
|
+
// Trois chemins pour un meme nom : tel quel dans le nom regroupe de
|
|
51
|
+
// l'annuaire, a la casse pres, ou parmi ses variantes.
|
|
52
|
+
const should: any[] = [{ terms: { [ASSOCIATION_FIELDS.label]: wanted } }];
|
|
53
|
+
wanted.forEach((name) => {
|
|
54
|
+
const escaped = escapeRegexp(name);
|
|
55
|
+
should.push({
|
|
56
|
+
regexp: { [ASSOCIATION_FIELDS.label]: { value: escaped, case_insensitive: true } },
|
|
57
|
+
});
|
|
58
|
+
should.push({
|
|
59
|
+
regexp: {
|
|
60
|
+
[ASSOCIATION_FIELDS.labelVariants]: {
|
|
61
|
+
value: `(.*; )?${escaped}(; .*)?`,
|
|
62
|
+
case_insensitive: true,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const res: any = await queryCustomElastic({
|
|
69
|
+
index: ES_INDEX.ASSOCIATION_DIRECTORY,
|
|
70
|
+
customQuery: {
|
|
71
|
+
size: 200,
|
|
72
|
+
_source: [ASSOCIATION_FIELDS.label, ASSOCIATION_FIELDS.labelVariants],
|
|
73
|
+
query: { bool: { should, minimum_should_match: 1 } },
|
|
74
|
+
} as any,
|
|
75
|
+
allAnswer: true,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const found: Record<string, string> = {};
|
|
79
|
+
(res?.hits?.hits ?? []).forEach((hit: any) => {
|
|
80
|
+
const source = hit?._source ?? {};
|
|
81
|
+
const writings = [
|
|
82
|
+
source[ASSOCIATION_FIELDS.label] ?? hit._id,
|
|
83
|
+
...String(source[ASSOCIATION_FIELDS.labelVariants] ?? "").split(";"),
|
|
84
|
+
];
|
|
85
|
+
writings.forEach((writing) => {
|
|
86
|
+
const name = key(writing);
|
|
87
|
+
// Premiere fiche gagnante : une meme variante peut figurer dans deux
|
|
88
|
+
// fiches, et rien dans l'annuaire ne dit laquelle prime.
|
|
89
|
+
if (name && !(name in found) && !(name in ids.value)) found[name] = hit._id;
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
ids.value = { ...ids.value, ...found };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** La fiche qui reconnait l'une de ces ecritures, s'il y en a une. */
|
|
96
|
+
function idFor(...names: unknown[]): string | null {
|
|
97
|
+
for (const name of names) {
|
|
98
|
+
const id = ids.value[key(name)];
|
|
99
|
+
if (id) return id;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { ids, load, idFor };
|
|
105
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { HeartTagColor } from "../types/heart/tag";
|
|
2
|
+
/**
|
|
3
|
+
* Catalogue des options du panneau « Personnaliser », par famille de page.
|
|
4
|
+
*
|
|
5
|
+
* Une option = une rangee facultative sur la carte de resultat. Ce fichier dit
|
|
6
|
+
* ce qu'on peut demander (libelle, icone, teinte des etiquettes) ; ce sont les
|
|
7
|
+
* cartes qui savent ou lire la valeur, et HeartResultsPrefRows qui la dessine.
|
|
8
|
+
* L'etat arrive en prop `prefs` (cf. defaultCardPrefs).
|
|
9
|
+
*
|
|
10
|
+
* N'entrent ici que des champs absents du corps de la carte : on ne propose pas
|
|
11
|
+
* d'afficher ce qui est deja affiche. Une option sans donnee sur un resultat ne
|
|
12
|
+
* fait pas de rangee vide, la carte la saute.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Familles de page (ex-store `cardPrefs` de fr_drugs_app). Le choix de
|
|
17
|
+
* l'utilisateur vivait dans ce store, garde en localStorage ; dans le layer
|
|
18
|
+
* il arrive en prop `prefs` (cle d'option → affichee), par defaut
|
|
19
|
+
* `defaultCardPrefs()` : tout masque, comme dans fr_drugs_app.
|
|
20
|
+
*/
|
|
21
|
+
export type CardPrefScope = "has" | "alert" | "ansm" | "les" | "mitm" | "pnds" | "put";
|
|
22
|
+
|
|
23
|
+
/** Cle d'option → affichee. Absent = masque. */
|
|
24
|
+
export type CardPrefs = Record<string, boolean>;
|
|
25
|
+
|
|
26
|
+
/** Les valeurs par defaut de fr_drugs_app : rien de coche. */
|
|
27
|
+
export function defaultCardPrefs(): CardPrefs {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type CardPrefOption = {
|
|
32
|
+
key: string;
|
|
33
|
+
fr: string;
|
|
34
|
+
en: string;
|
|
35
|
+
/** Nom d'icone Phosphor, sans le prefixe « Ph ». */
|
|
36
|
+
icon: string;
|
|
37
|
+
/** Teinte des etiquettes de la rangee (variantes RmTag). */
|
|
38
|
+
variant?: HeartTagColor;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type CardPrefGroup = {
|
|
42
|
+
fr: string;
|
|
43
|
+
en: string;
|
|
44
|
+
options: CardPrefOption[];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const CARD_PREF_GROUPS: Record<CardPrefScope, CardPrefGroup[]> = {
|
|
48
|
+
// Avis HAS (onglets de la page Recherche) : carte de marque.
|
|
49
|
+
has: [
|
|
50
|
+
{
|
|
51
|
+
fr: "Tags de la marque",
|
|
52
|
+
en: "Brand tags",
|
|
53
|
+
options: [
|
|
54
|
+
{ key: "rare", fr: "Maladies rares", en: "Rare diseases", icon: "Virus" },
|
|
55
|
+
{ key: "oncoIcd10", fr: "Oncologie CIM-10", en: "Oncology ICD-10", icon: "ListNumbers", variant: "gray" },
|
|
56
|
+
{ key: "oncoTerms", fr: "Termes oncologie", en: "Oncology terms", icon: "Stethoscope", variant: "gray" },
|
|
57
|
+
{ key: "genes", fr: "Gènes", en: "Genes", icon: "Dna", variant: "violet" },
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
fr: "Dernier avis",
|
|
62
|
+
en: "Latest opinion",
|
|
63
|
+
options: [
|
|
64
|
+
{ key: "smr", fr: "SMR / ASMR", en: "SMR / ASMR", icon: "Scales", variant: "indigo" },
|
|
65
|
+
{ key: "theme", fr: "Thème", en: "Theme", icon: "Tag", variant: "sky" },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
|
|
70
|
+
// Alertes de securite ANSM : carte de marque alertee. Les tags viennent de
|
|
71
|
+
// la marque, comme sur les avis HAS (meme index).
|
|
72
|
+
alert: [
|
|
73
|
+
{
|
|
74
|
+
fr: "Tags de la marque",
|
|
75
|
+
en: "Brand tags",
|
|
76
|
+
options: [
|
|
77
|
+
{ key: "rare", fr: "Maladies rares", en: "Rare diseases", icon: "Virus" },
|
|
78
|
+
{ key: "oncoIcd10", fr: "Oncologie CIM-10", en: "Oncology ICD-10", icon: "ListNumbers", variant: "gray" },
|
|
79
|
+
{ key: "oncoTerms", fr: "Termes oncologie", en: "Oncology terms", icon: "Stethoscope", variant: "gray" },
|
|
80
|
+
{ key: "genes", fr: "Gènes", en: "Genes", icon: "Dna", variant: "violet" },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
fr: "Alerte",
|
|
85
|
+
en: "Alert",
|
|
86
|
+
options: [
|
|
87
|
+
{ key: "cis", fr: "Codes CIS", en: "CIS codes", icon: "Barcode", variant: "gray" },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
|
|
92
|
+
// Repertoire ANSM : le tableau des CIS d'une marque. Ici une option n'ajoute
|
|
93
|
+
// pas une rangee sous la carte mais une colonne au tableau, les champs
|
|
94
|
+
// vivant sur la specialite et non sur la marque.
|
|
95
|
+
ansm: [
|
|
96
|
+
{
|
|
97
|
+
fr: "Colonnes du tableau",
|
|
98
|
+
en: "Table columns",
|
|
99
|
+
options: [
|
|
100
|
+
{ key: "form", fr: "Forme pharmaceutique", en: "Pharmaceutical form", icon: "Pill" },
|
|
101
|
+
{ key: "route", fr: "Voie d'administration", en: "Route of administration", icon: "Syringe" },
|
|
102
|
+
{ key: "procedure", fr: "Type de procédure", en: "Procedure type", icon: "Stamp" },
|
|
103
|
+
{ key: "generic", fr: "Groupe générique", en: "Generic group", icon: "CopySimple" },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
|
|
108
|
+
// Referentiel LES (JO) : carte d'indication.
|
|
109
|
+
les: [
|
|
110
|
+
{
|
|
111
|
+
fr: "Tags de l'indication",
|
|
112
|
+
en: "Indication tags",
|
|
113
|
+
options: [
|
|
114
|
+
{ key: "rare", fr: "Maladies rares", en: "Rare diseases", icon: "Virus" },
|
|
115
|
+
{ key: "oncoIcd10", fr: "Oncologie CIM-10", en: "Oncology ICD-10", icon: "ListNumbers", variant: "gray" },
|
|
116
|
+
{ key: "oncoTerms", fr: "Termes oncologie", en: "Oncology terms", icon: "Stethoscope", variant: "gray" },
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
fr: "Inscription",
|
|
121
|
+
en: "Listing",
|
|
122
|
+
options: [
|
|
123
|
+
{ key: "statut", fr: "Statut d'inscription", en: "Listing status", icon: "Certificate", variant: "sky" },
|
|
124
|
+
{ key: "endDate", fr: "Fin de prise en charge", en: "End of coverage", icon: "CalendarX", variant: "amber" },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
fr: "Produit",
|
|
129
|
+
en: "Product",
|
|
130
|
+
options: [
|
|
131
|
+
{ key: "drugType", fr: "Type de produit", en: "Product type", icon: "Pill", variant: "indigo" },
|
|
132
|
+
{ key: "ucd", fr: "Codes UCD", en: "UCD codes", icon: "Barcode", variant: "gray" },
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
|
|
137
|
+
// Medicaments d'interet therapeutique majeur : carte CIS.
|
|
138
|
+
mitm: [
|
|
139
|
+
{
|
|
140
|
+
fr: "Dernier avis",
|
|
141
|
+
en: "Latest opinion",
|
|
142
|
+
options: [
|
|
143
|
+
{ key: "hasReason", fr: "Motif de l'avis", en: "Opinion reason", icon: "Scales", variant: "indigo" },
|
|
144
|
+
{ key: "hasCode", fr: "Référence HAS", en: "HAS reference", icon: "Hash", variant: "gray" },
|
|
145
|
+
{ key: "opinionCount", fr: "Nombre d'avis", en: "Opinion count", icon: "Files", variant: "sky" },
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
fr: "Produit",
|
|
150
|
+
en: "Product",
|
|
151
|
+
options: [
|
|
152
|
+
{ key: "atc4", fr: "Classe ATC4", en: "ATC4 class", icon: "Tag", variant: "gray" },
|
|
153
|
+
{ key: "sameMolecule", fr: "Marques de la molécule", en: "Brands of the molecule", icon: "Pill", variant: "violet" },
|
|
154
|
+
{ key: "prices", fr: "Prix et remboursement", en: "Price and reimbursement", icon: "Money", variant: "emerald" },
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
|
|
159
|
+
// PNDS : carte de pathologie.
|
|
160
|
+
pnds: [
|
|
161
|
+
{
|
|
162
|
+
fr: "Tags de la pathologie",
|
|
163
|
+
en: "Pathology tags",
|
|
164
|
+
options: [
|
|
165
|
+
{ key: "rare", fr: "Maladies rares", en: "Rare diseases", icon: "Virus" },
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
fr: "Protocole",
|
|
170
|
+
en: "Protocol",
|
|
171
|
+
options: [
|
|
172
|
+
{ key: "docs", fr: "Documents", en: "Documents", icon: "Files", variant: "gray" },
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
|
|
177
|
+
// P.U.T. : carte de protocole d'utilisation therapeutique.
|
|
178
|
+
put: [
|
|
179
|
+
{
|
|
180
|
+
fr: "Produit",
|
|
181
|
+
en: "Product",
|
|
182
|
+
options: [
|
|
183
|
+
{ key: "brand", fr: "Marque", en: "Brand", icon: "Pill", variant: "indigo" },
|
|
184
|
+
{ key: "dci", fr: "DCI", en: "INN", icon: "Flask", variant: "violet" },
|
|
185
|
+
{ key: "therapeuticArea", fr: "Aire thérapeutique", en: "Therapeutic area", icon: "Stethoscope", variant: "sky" },
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
fr: "Protocole",
|
|
190
|
+
en: "Protocol",
|
|
191
|
+
options: [
|
|
192
|
+
{ key: "docs", fr: "Documents", en: "Documents", icon: "Files", variant: "gray" },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/** Les options d'une page, a plat et dans l'ordre d'affichage. */
|
|
199
|
+
export function cardPrefOptions(scope: CardPrefScope): CardPrefOption[] {
|
|
200
|
+
return (CARD_PREF_GROUPS[scope] ?? []).flatMap((group) => group.options);
|
|
201
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { ES_INDEX } from "../esIndices";
|
|
2
|
+
import type { CurrencyRateSource } from "../types/heart/CurrencyRate";
|
|
3
|
+
import useOipDrugsElastic from "./useOipDrugsElastic";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Portage du store `CurrencyStore` de fr_drugs_app (useCurrencyStore), avec
|
|
7
|
+
* le chargement que faisait useHeartCurrency dans les layouts de l'app : les
|
|
8
|
+
* taux de change sont une donnee de reference, lue une fois par session dans
|
|
9
|
+
* l'index CURRENCY_RATE et partagee par toutes les fiches.
|
|
10
|
+
*
|
|
11
|
+
* Memes lecteurs que le store (`getCurrency`, `getCurrencyByCountryCode`...).
|
|
12
|
+
* `currencies` est une ref (le store la deballait) : `currencies.value`.
|
|
13
|
+
* `load()` est appele a la creation ; un composant qui a besoin des taux
|
|
14
|
+
* avant de rendre peut l'attendre (`await useCurrency().load()`).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
let pending: Promise<void> | null = null;
|
|
18
|
+
|
|
19
|
+
export function useCurrency() {
|
|
20
|
+
const currencies = useState<CurrencyRateSource[] | null>("oip-drugs:currencies", () => null);
|
|
21
|
+
|
|
22
|
+
function setCurrency(list: CurrencyRateSource[]) {
|
|
23
|
+
currencies.value = list;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function load(): Promise<void> {
|
|
27
|
+
if (currencies.value) return Promise.resolve();
|
|
28
|
+
pending ??= useOipDrugsElastic()
|
|
29
|
+
.queryCustomElastic({
|
|
30
|
+
index: ES_INDEX.CURRENCY_RATE,
|
|
31
|
+
customQuery: {
|
|
32
|
+
from: 0,
|
|
33
|
+
query: {
|
|
34
|
+
bool: {
|
|
35
|
+
must: [{ match_all: {} }],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
size: 20,
|
|
39
|
+
track_total_hits: true,
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
.then((res: any) => {
|
|
43
|
+
if (res?.results?.length) setCurrency(res.results);
|
|
44
|
+
})
|
|
45
|
+
.finally(() => {
|
|
46
|
+
pending = null;
|
|
47
|
+
});
|
|
48
|
+
return pending;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getCurrency(currency: string): CurrencyRateSource | undefined {
|
|
52
|
+
return currencies.value?.find((x) => x.country_code === currency);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Équivalence entre devises et codes ISO des pays
|
|
56
|
+
function getCurrencyToCountryMapping() {
|
|
57
|
+
return {
|
|
58
|
+
"USD": "US", // United States (pas dans la liste mais USD principal)
|
|
59
|
+
"EUR": ["DE", "ES", "FR", "IT"], // Germany, Spain, France, Italy
|
|
60
|
+
"CHF": "CH", // Switzerland
|
|
61
|
+
"BRL": "BR", // Brazil
|
|
62
|
+
"SEK": "SE", // Sweden (pas dans la liste mais SEK principal)
|
|
63
|
+
"GBP": "GB", // United Kingdom
|
|
64
|
+
"NOK": "NO", // Norway
|
|
65
|
+
"AUD": "AU", // Australia
|
|
66
|
+
"PLN": "PL", // Poland
|
|
67
|
+
"JPY": "JP", // Japan
|
|
68
|
+
"DKK": "DK", // Denmark
|
|
69
|
+
"KRW": "KR", // South Korea
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Fonction inverse : obtenir la devise à partir du code pays
|
|
74
|
+
function getCountryToCurrencyMapping() {
|
|
75
|
+
return {
|
|
76
|
+
"AU": "AUD", // Australia
|
|
77
|
+
"BR": "BRL", // Brazil
|
|
78
|
+
"CH": "CHF", // Switzerland
|
|
79
|
+
"DE": "EUR", // Germany
|
|
80
|
+
"DK": "DKK", // Denmark
|
|
81
|
+
"ES": "EUR", // Spain
|
|
82
|
+
"FR": "EUR", // France
|
|
83
|
+
"GB": "GBP", // United Kingdom
|
|
84
|
+
"IT": "EUR", // Italy
|
|
85
|
+
"JP": "JPY", // Japan
|
|
86
|
+
"KR": "KRW", // South Korea
|
|
87
|
+
"NO": "NOK", // Norway
|
|
88
|
+
"PL": "PLN", // Poland
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Obtenir la devise d'un pays spécifique
|
|
93
|
+
function getCurrencyByCountryCode(countryCode: string): string | null {
|
|
94
|
+
const mapping = getCountryToCurrencyMapping();
|
|
95
|
+
return mapping[countryCode as keyof typeof mapping] || null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Obtenir les pays qui utilisent une devise spécifique
|
|
99
|
+
function getCountriesByCurrency(currency: string): string | string[] | null {
|
|
100
|
+
const mapping = getCurrencyToCountryMapping();
|
|
101
|
+
return mapping[currency as keyof typeof mapping] || null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (import.meta.client) load();
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
currencies,
|
|
108
|
+
load,
|
|
109
|
+
setCurrency,
|
|
110
|
+
getCurrency,
|
|
111
|
+
getCurrencyToCountryMapping,
|
|
112
|
+
getCountryToCurrencyMapping,
|
|
113
|
+
getCurrencyByCountryCode,
|
|
114
|
+
getCountriesByCurrency,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Le nom du store d'origine, pour les portages qui l'appelaient. */
|
|
119
|
+
export const useCurrencyStore = useCurrency;
|
|
120
|
+
|
|
121
|
+
export default useCurrency;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDirectusSources - le referentiel Directus des sources citees par l'app.
|
|
3
|
+
*
|
|
4
|
+
* Une source s'affiche a deux endroits : le panneau « Sources » du rail et le
|
|
5
|
+
* pied de chaque carte (« Source : X, verifie le Y »). Les deux doivent lire
|
|
6
|
+
* le meme referentiel, sinon la meme source porte deux dates differentes selon
|
|
7
|
+
* l'endroit ou on la lit. Le panneau lisait deja Directus ; les cartes
|
|
8
|
+
* lisaient une agregation Elasticsearch sur le dernier millesime de la donnee,
|
|
9
|
+
* ce qui faisait passer « 2025 » (millesime) pour une date de verification.
|
|
10
|
+
*
|
|
11
|
+
* Tout passe donc par ici : un uuid par jeu, une requete groupee vers
|
|
12
|
+
* /api/oip/sources (route du layer Romulus, qui proxie Directus cote serveur), un cache partage.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { RmExportSource } from "@tekkare/romulus/utils/export";
|
|
16
|
+
|
|
17
|
+
/** uuid Directus de chaque jeu cite par l'app, par cle de jeu. */
|
|
18
|
+
export const SOURCE_UUIDS = {
|
|
19
|
+
medicam: "71949060-2e0f-4493-beab-6c8200dbec21",
|
|
20
|
+
openmedic: "3d52dae5-7f70-4700-8633-10dd0a30eb26",
|
|
21
|
+
phmev: "7184cd9a-9f31-4f4b-99de-0d284f04d974",
|
|
22
|
+
sus: "cf928a21-47a3-4722-923a-0d8642dc900c",
|
|
23
|
+
retrocession: "ba08baa4-d29b-44c3-bd8a-dff04737f525",
|
|
24
|
+
price: "a4d3d627-97fe-4a8e-ac1c-f926e2d3f566",
|
|
25
|
+
has: "6f0bfe9a-bc58-4feb-9e9c-f37a0ac9ceba",
|
|
26
|
+
nct: "224f4621-f6e7-4635-a7d9-371e212d5ca6",
|
|
27
|
+
euct: "a81861dc-3a42-4019-a93b-e0e83edcaf41",
|
|
28
|
+
finess: "545ba237-b92d-47fc-b336-25972873d0b2",
|
|
29
|
+
rpps: "fee124b8-7cb7-41a0-825b-6a70ce975834",
|
|
30
|
+
jca: "4a311879-080e-4c26-bfc2-5c3a80f9758b",
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
export type SourceKey = keyof typeof SOURCE_UUIDS;
|
|
34
|
+
|
|
35
|
+
/** Ce qu'une carte a besoin de dire : qui produit la donnee, et depuis quand. */
|
|
36
|
+
export interface SourceMeta {
|
|
37
|
+
/** « Assurance Maladie (Open Medic) » : le producteur, puis le jeu. */
|
|
38
|
+
name: string;
|
|
39
|
+
/** Date de notre dernier chargement, mise au format de la langue lue. */
|
|
40
|
+
verified: string;
|
|
41
|
+
/**
|
|
42
|
+
* Le surnom Directus du jeu (« DE - PRICE TRACKER », « AEMPS »). Court, il
|
|
43
|
+
* nomme la source la ou « Producteur (jeu) » deborde, par exemple quand une
|
|
44
|
+
* carte en cite deux.
|
|
45
|
+
*/
|
|
46
|
+
nickname?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface DirectusSource {
|
|
50
|
+
source_uuid: string;
|
|
51
|
+
Nickname?: string | null;
|
|
52
|
+
latest_download_date?: string | null;
|
|
53
|
+
translations?: { languages_code?: string; source_name?: string | null }[] | null;
|
|
54
|
+
provider?: {
|
|
55
|
+
translations?: { languages_code?: string; provider_name?: string | null }[] | null;
|
|
56
|
+
} | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Tant que Directus n'a pas repondu, le pied de carte reste muet plutot que faux. */
|
|
60
|
+
export const PENDING = "…";
|
|
61
|
+
|
|
62
|
+
function pickTranslation(
|
|
63
|
+
rows: { languages_code?: string; [k: string]: any }[] | null | undefined,
|
|
64
|
+
field: string,
|
|
65
|
+
lang: string,
|
|
66
|
+
) {
|
|
67
|
+
const list = rows ?? [];
|
|
68
|
+
const wanted = lang === "fr" ? "fr-FR" : "en-US";
|
|
69
|
+
const hit =
|
|
70
|
+
list.find((t) => t.languages_code === wanted) ??
|
|
71
|
+
list.find((t) => t.languages_code === "en-US") ??
|
|
72
|
+
list[0];
|
|
73
|
+
const value = hit?.[field];
|
|
74
|
+
return typeof value === "string" ? value.trim() : "";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* « Producteur (jeu) », la forme qu'avaient les libelles ecrits en dur. Un nom
|
|
79
|
+
* de jeu qui porte deja une parenthese passe apres une virgule : imbriquer
|
|
80
|
+
* deux parentheses se lit mal.
|
|
81
|
+
*/
|
|
82
|
+
function buildName(source: DirectusSource, lang: string) {
|
|
83
|
+
const dataset =
|
|
84
|
+
pickTranslation(source.translations, "source_name", lang) ||
|
|
85
|
+
(source.Nickname ?? "").trim();
|
|
86
|
+
const provider = pickTranslation(
|
|
87
|
+
source.provider?.translations,
|
|
88
|
+
"provider_name",
|
|
89
|
+
lang,
|
|
90
|
+
);
|
|
91
|
+
if (!provider) return dataset;
|
|
92
|
+
if (!dataset) return provider;
|
|
93
|
+
if (dataset.includes("(")) return `${provider}, ${dataset}`;
|
|
94
|
+
return `${provider} (${dataset})`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function formatVerified(raw: string | null | undefined, lang: string) {
|
|
98
|
+
if (!raw) return PENDING;
|
|
99
|
+
const parsed = new Date(raw);
|
|
100
|
+
if (Number.isNaN(parsed.getTime())) return String(raw);
|
|
101
|
+
return parsed.toLocaleDateString(lang === "fr" ? "fr-FR" : "en-US", {
|
|
102
|
+
day: "numeric",
|
|
103
|
+
month: "long",
|
|
104
|
+
year: "numeric",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default function useDirectusSources() {
|
|
109
|
+
const cache = useState<Record<string, DirectusSource>>(
|
|
110
|
+
"oip-drugs:directus:sources",
|
|
111
|
+
() => ({}),
|
|
112
|
+
);
|
|
113
|
+
const inflight = useState<Promise<void> | null>(
|
|
114
|
+
"oip-drugs:directus:sources:inflight",
|
|
115
|
+
() => null,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
/** Charge les uuids absents du cache. Idempotent, et groupe en une requete. */
|
|
119
|
+
async function load(uuids: string[]) {
|
|
120
|
+
// Une requete en vol ne porte pas forcement nos uuids : on l'attend, puis
|
|
121
|
+
// on recalcule ce qui manque encore, sinon les pieds de carte restaient
|
|
122
|
+
// sur « … ».
|
|
123
|
+
while (inflight.value) await inflight.value;
|
|
124
|
+
const missing = [...new Set(uuids)].filter((u) => u && !cache.value[u]);
|
|
125
|
+
if (missing.length === 0) return;
|
|
126
|
+
|
|
127
|
+
inflight.value = $fetch<{ sources: DirectusSource[] }>("/api/oip/sources", {
|
|
128
|
+
method: "POST",
|
|
129
|
+
body: { uuids: missing },
|
|
130
|
+
})
|
|
131
|
+
.then((res) => {
|
|
132
|
+
for (const source of res?.sources ?? []) {
|
|
133
|
+
cache.value[source.source_uuid] = source;
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
.catch((e: unknown) => {
|
|
137
|
+
console.warn("[useDirectusSources] load failed", e);
|
|
138
|
+
})
|
|
139
|
+
.finally(() => {
|
|
140
|
+
inflight.value = null;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return inflight.value;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Charge tous les jeux du registre : l'app en cite une dizaine, pas mille. */
|
|
147
|
+
function loadAll() {
|
|
148
|
+
return load(Object.values(SOURCE_UUIDS));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Source adressee par son uuid : Hub-R cite ses sources comme ca (un uuid
|
|
153
|
+
* par onglet de fiche), sans passer par les cles du registre.
|
|
154
|
+
*/
|
|
155
|
+
function metaByUuid(uuid: string, lang: string): SourceMeta {
|
|
156
|
+
const source = uuid ? cache.value[uuid] : undefined;
|
|
157
|
+
if (!source) return { name: PENDING, verified: PENDING };
|
|
158
|
+
return {
|
|
159
|
+
name: buildName(source, lang),
|
|
160
|
+
verified: formatVerified(source.latest_download_date, lang),
|
|
161
|
+
nickname: (source.Nickname ?? "").trim() || undefined,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function meta(key: SourceKey, lang: string): SourceMeta {
|
|
166
|
+
return metaByUuid(SOURCE_UUIDS[key], lang);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Date brute (ISO) de la source, pour les composants qui la formatent
|
|
171
|
+
* eux-memes (RmDate en pied de tiroir).
|
|
172
|
+
*/
|
|
173
|
+
function verifiedRaw(uuid: string): string | null {
|
|
174
|
+
return (uuid ? cache.value[uuid]?.latest_download_date : null) ?? null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Plusieurs jeux cites par une meme carte (le portail lit les trois canaux a
|
|
179
|
+
* la fois). La date retenue est la PLUS ANCIENNE : une donnee agregee n'est
|
|
180
|
+
* a jour que jusqu'a son entree la plus vieille.
|
|
181
|
+
*/
|
|
182
|
+
/**
|
|
183
|
+
* Les sources d'une liste d'uuids telles que les generateurs d'export les
|
|
184
|
+
* attendent : le libelle « Producteur (jeu) » et la date brute de notre
|
|
185
|
+
* dernier chargement, que le classeur, le rapport et l'image formatent
|
|
186
|
+
* eux-memes. Une source que Directus n'a pas encore rendue est omise plutot
|
|
187
|
+
* que citee sans nom.
|
|
188
|
+
*/
|
|
189
|
+
function exportSources(uuids: string[], lang: string): RmExportSource[] {
|
|
190
|
+
return [...new Set(uuids)]
|
|
191
|
+
.map((uuid) => cache.value[uuid])
|
|
192
|
+
.filter((source): source is DirectusSource => !!source)
|
|
193
|
+
.map((source) => ({
|
|
194
|
+
name: buildName(source, lang),
|
|
195
|
+
date: source.latest_download_date ?? null,
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function combinedMeta(keys: SourceKey[], lang: string): SourceMeta {
|
|
200
|
+
const sources = keys
|
|
201
|
+
.map((k) => cache.value[SOURCE_UUIDS[k]])
|
|
202
|
+
.filter(Boolean) as DirectusSource[];
|
|
203
|
+
if (sources.length === 0) return { name: PENDING, verified: PENDING };
|
|
204
|
+
|
|
205
|
+
const providers = [
|
|
206
|
+
...new Set(
|
|
207
|
+
sources
|
|
208
|
+
.map((s) => pickTranslation(s.provider?.translations, "provider_name", lang))
|
|
209
|
+
.filter(Boolean),
|
|
210
|
+
),
|
|
211
|
+
];
|
|
212
|
+
const dates = sources
|
|
213
|
+
.map((s) => s.latest_download_date)
|
|
214
|
+
.filter(Boolean) as string[];
|
|
215
|
+
const oldest = dates.sort()[0];
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
name: providers.join(" & ") || PENDING,
|
|
219
|
+
verified: formatVerified(oldest, lang),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return { cache, load, loadAll, meta, metaByUuid, verifiedRaw, combinedMeta, exportSources };
|
|
224
|
+
}
|