@tekkare/romulus 0.1.250 → 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.
Files changed (270) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/BarChart.vue +1 -0
  3. package/dist/runtime/components/ChoroplethMap.vue +4 -1
  4. package/dist/runtime/internal/geo/fr-regions.json +1 -1
  5. package/dist/runtime/utils/chart.d.ts +6 -3
  6. package/dist/runtime/utils/chart.js +15 -2
  7. package/layer/app/app.config.ts +6 -0
  8. package/layer/app/components/OipPubmedSummary.vue +223 -0
  9. package/layer/app/components/OipResourceDrawer.vue +331 -0
  10. package/layer/app/components/OipResourceDrawerLink.vue +40 -0
  11. package/layer/app/components/OipResourceField.vue +41 -0
  12. package/layer/app/components/OipResourceGenericCard.vue +54 -0
  13. package/layer/app/components/OipResourceSource.vue +50 -0
  14. package/layer/app/components/OipResourceTags.vue +65 -0
  15. package/layer/app/components/OipResourceView.vue +107 -0
  16. package/layer/app/components/oip-drugs/fiches/AssociationFiche.vue +775 -0
  17. package/layer/app/components/oip-drugs/fiches/BrandFiche.vue +1111 -0
  18. package/layer/app/components/oip-drugs/fiches/IndicationFiche.vue +182 -0
  19. package/layer/app/components/oip-drugs/fiches/JoFiche.vue +101 -0
  20. package/layer/app/components/oip-drugs/fiches/PndsFiche.vue +279 -0
  21. package/layer/app/components/oip-drugs/heart/JoCard.vue +314 -0
  22. package/layer/app/components/oip-drugs/heart/TextHighlight.vue +326 -0
  23. package/layer/app/components/oip-drugs/heart/ansm/BrandCard.vue +187 -0
  24. package/layer/app/components/oip-drugs/heart/ansm/CisDetail.vue +363 -0
  25. package/layer/app/components/oip-drugs/heart/ansm/CisTable.vue +167 -0
  26. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyCard.vue +373 -0
  27. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyFiche.vue +372 -0
  28. package/layer/app/components/oip-drugs/heart/associations/TranscriptionCard.vue +55 -0
  29. package/layer/app/components/oip-drugs/heart/elastic/SourceContainerElastic.vue +103 -0
  30. package/layer/app/components/oip-drugs/heart/france/BrandGroupCard.vue +450 -0
  31. package/layer/app/components/oip-drugs/heart/mitm/Card.vue +199 -0
  32. package/layer/app/components/oip-drugs/heart/mitm/Source.vue +23 -0
  33. package/layer/app/components/oip-drugs/heart/mitm/details/Overview.vue +381 -0
  34. package/layer/app/components/oip-drugs/heart/put/Card.vue +212 -0
  35. package/layer/app/components/oip-drugs/heart/results/AccesCard.vue +696 -0
  36. package/layer/app/components/oip-drugs/heart/results/AlertCard.vue +342 -0
  37. package/layer/app/components/oip-drugs/heart/results/AsmrCursor.vue +229 -0
  38. package/layer/app/components/oip-drugs/heart/results/EvalCard.vue +370 -0
  39. package/layer/app/components/oip-drugs/heart/results/IndicCard.vue +743 -0
  40. package/layer/app/components/oip-drugs/heart/results/MatchExplain.vue +243 -0
  41. package/layer/app/components/oip-drugs/heart/results/PrefRows.vue +105 -0
  42. package/layer/app/components/oip-drugs/heart/results/details/HasHistory.vue +487 -0
  43. package/layer/app/components/oip-drugs/heart/results/details/InternationalCountryCard.vue +461 -0
  44. package/layer/app/components/oip-drugs/heart/results/details/InternationalOverview.vue +200 -0
  45. package/layer/app/components/oip-drugs/heart/results/details/InternationalPriceDrawer.vue +647 -0
  46. package/layer/app/components/oip-drugs/heart/results/details/Monograph.vue +128 -0
  47. package/layer/app/components/oip-drugs/heart/results/details/OpinionDrawer.vue +535 -0
  48. package/layer/app/components/oip-drugs/heart/results/details/PresentationDrawer.vue +738 -0
  49. package/layer/app/components/oip-drugs/heart/results/details/Price.vue +227 -0
  50. package/layer/app/components/oip-drugs/heart/results/details/PriceOverview.vue +1261 -0
  51. package/layer/app/components/oip-drugs/heart/results/details/RecordDrawer.vue +494 -0
  52. package/layer/app/components/oip-drugs/heart/results/details/RecordList.vue +426 -0
  53. package/layer/app/components/oip-drugs/heart/results/les/Card.vue +340 -0
  54. package/layer/app/components/oip-drugs/heart/results/les/Drawer.vue +67 -0
  55. package/layer/app/components/oip-drugs/heart/results/les/IndicationDetail.vue +149 -0
  56. package/layer/app/components/oip-drugs/heart/results/les/Source.vue +20 -0
  57. package/layer/app/components/oip-drugs/heart/results/les/SourceTag.vue +39 -0
  58. package/layer/app/components/oip-drugs/heart/searchPage/DynamicSource.vue +29 -0
  59. package/layer/app/components/oip-drugs/medicam/market/Chart.vue +121 -0
  60. package/layer/app/components/oip-drugs/outpatient/ProductEvolution.vue +236 -0
  61. package/layer/app/components/oip-drugs/resources/Ansm.vue +75 -0
  62. package/layer/app/components/oip-drugs/resources/Association.vue +30 -0
  63. package/layer/app/components/oip-drugs/resources/Brand.vue +65 -0
  64. package/layer/app/components/oip-drugs/resources/Contribution.vue +216 -0
  65. package/layer/app/components/oip-drugs/resources/Hospital.vue +151 -0
  66. package/layer/app/components/oip-drugs/resources/Indication.vue +54 -0
  67. package/layer/app/components/oip-drugs/resources/Jo.vue +57 -0
  68. package/layer/app/components/oip-drugs/resources/Medicam.vue +140 -0
  69. package/layer/app/components/oip-drugs/resources/Mitm.vue +84 -0
  70. package/layer/app/components/oip-drugs/resources/NationalAssembly.vue +93 -0
  71. package/layer/app/components/oip-drugs/resources/Outpatient.vue +97 -0
  72. package/layer/app/components/oip-drugs/resources/Pnds.vue +24 -0
  73. package/layer/app/components/oip-drugs/resources/Price.vue +106 -0
  74. package/layer/app/components/oip-drugs/resources/Put.vue +143 -0
  75. package/layer/app/components/oip-science/appel-a-projet/Cards.vue +162 -0
  76. package/layer/app/components/oip-science/clinical-trials/Cards.vue +382 -0
  77. package/layer/app/components/oip-science/clinical-trials/CardsGlobal.vue +273 -0
  78. package/layer/app/components/oip-science/clinical-trials/LinkedList.vue +366 -0
  79. package/layer/app/components/oip-science/clinical-trials/PairBadge.vue +25 -0
  80. package/layer/app/components/oip-science/clinical-trials/PairBody.vue +182 -0
  81. package/layer/app/components/oip-science/clinical-trials/SitesDrawer.vue +95 -0
  82. package/layer/app/components/oip-science/common/Form.vue +73 -0
  83. package/layer/app/components/oip-science/common/MatchAvgTag.vue +283 -0
  84. package/layer/app/components/oip-science/common/NoData.vue +16 -0
  85. package/layer/app/components/oip-science/common/ScoreBadge.vue +35 -0
  86. package/layer/app/components/oip-science/common/Source.vue +31 -0
  87. package/layer/app/components/oip-science/common/TableTags.vue +279 -0
  88. package/layer/app/components/oip-science/common/Timeline.vue +104 -0
  89. package/layer/app/components/oip-science/common/ToolTips.vue +89 -0
  90. package/layer/app/components/oip-science/cross/publicationPubmed.vue +484 -0
  91. package/layer/app/components/oip-science/crossData/CrossDataLocations.vue +71 -0
  92. package/layer/app/components/oip-science/ctgov/Investigators.vue +148 -0
  93. package/layer/app/components/oip-science/ctgov/Locations.vue +102 -0
  94. package/layer/app/components/oip-science/ctgov/Publications.vue +52 -0
  95. package/layer/app/components/oip-science/ctgov/Sponsors.vue +66 -0
  96. package/layer/app/components/oip-science/ctgov/StudyDescription.vue +1136 -0
  97. package/layer/app/components/oip-science/ctgov-centers/ClinicalTrials.vue +40 -0
  98. package/layer/app/components/oip-science/ctgov-centers/Publications.vue +50 -0
  99. package/layer/app/components/oip-science/ctgov-investigators/ClinicalTrials.vue +43 -0
  100. package/layer/app/components/oip-science/ctgov-investigators/Informations.vue +171 -0
  101. package/layer/app/components/oip-science/ctgov-investigators/Publications.vue +44 -0
  102. package/layer/app/components/oip-science/elastic/DateRangeFilter.vue +209 -0
  103. package/layer/app/components/oip-science/elastic/Filter.vue +719 -0
  104. package/layer/app/components/oip-science/elastic/MatchExplain.vue +152 -0
  105. package/layer/app/components/oip-science/establishments/Cards.vue +183 -0
  106. package/layer/app/components/oip-science/establishments/ClinicalTrials.vue +62 -0
  107. package/layer/app/components/oip-science/establishments/Hcps.vue +227 -0
  108. package/layer/app/components/oip-science/establishments/Informations.vue +263 -0
  109. package/layer/app/components/oip-science/establishments/LinkedList.vue +153 -0
  110. package/layer/app/components/oip-science/establishments/MatchedLabels.vue +194 -0
  111. package/layer/app/components/oip-science/establishments/PublicFinance.vue +62 -0
  112. package/layer/app/components/oip-science/euct/Investigators.vue +125 -0
  113. package/layer/app/components/oip-science/euct/Locations.vue +102 -0
  114. package/layer/app/components/oip-science/euct/Products.vue +101 -0
  115. package/layer/app/components/oip-science/euct/Publications.vue +89 -0
  116. package/layer/app/components/oip-science/euct/Sponsors.vue +86 -0
  117. package/layer/app/components/oip-science/euct/StudyDescription.vue +1075 -0
  118. package/layer/app/components/oip-science/euct-centers/ClinicalTrials.vue +40 -0
  119. package/layer/app/components/oip-science/euct-centers/Publications.vue +55 -0
  120. package/layer/app/components/oip-science/euct-investigators/ClinicalTrials.vue +41 -0
  121. package/layer/app/components/oip-science/euct-investigators/Informations.vue +171 -0
  122. package/layer/app/components/oip-science/euct-investigators/Publications.vue +51 -0
  123. package/layer/app/components/oip-science/hcps/AppelsAProjet.vue +62 -0
  124. package/layer/app/components/oip-science/hcps/Cards.vue +237 -0
  125. package/layer/app/components/oip-science/hcps/ClinicalTrials.vue +78 -0
  126. package/layer/app/components/oip-science/hcps/Informations.vue +296 -0
  127. package/layer/app/components/oip-science/hcps/LinkedList.vue +153 -0
  128. package/layer/app/components/oip-science/hcps/MatchedLabels.vue +207 -0
  129. package/layer/app/components/oip-science/hcps/Pubmed.vue +128 -0
  130. package/layer/app/components/oip-science/local/SliderTabs.vue +99 -0
  131. package/layer/app/components/oip-science/local/SliderTabsFull.vue +135 -0
  132. package/layer/app/components/oip-science/pubmed/Authors.vue +128 -0
  133. package/layer/app/components/oip-science/pubmed/Cards.vue +417 -0
  134. package/layer/app/components/oip-science/pubmed/ClinicalTrials.vue +71 -0
  135. package/layer/app/components/oip-science/resources/Phrc.vue +39 -0
  136. package/layer/app/composables/useOipChapterScroll.ts +43 -0
  137. package/layer/app/composables/useOipResourceHooks.ts +27 -0
  138. package/layer/app/composables/useOipResourceNav.ts +66 -0
  139. package/layer/app/composables/useOipResourceViews.ts +117 -0
  140. package/layer/app/composables/useOipResources.ts +83 -0
  141. package/layer/app/oip-drugs/PORTING.md +120 -0
  142. package/layer/app/oip-drugs/composables/useAssociationDetail.ts +220 -0
  143. package/layer/app/oip-drugs/composables/useAssociationExport.ts +63 -0
  144. package/layer/app/oip-drugs/composables/useAssociationLookup.ts +105 -0
  145. package/layer/app/oip-drugs/composables/useCardPrefOptions.ts +201 -0
  146. package/layer/app/oip-drugs/composables/useCurrency.ts +121 -0
  147. package/layer/app/oip-drugs/composables/useDirectusSources.ts +224 -0
  148. package/layer/app/oip-drugs/composables/useHeartBrandMatches.ts +112 -0
  149. package/layer/app/oip-drugs/composables/useHeartDetailSearches.ts +479 -0
  150. package/layer/app/oip-drugs/composables/useHeartIntlApprovals.ts +661 -0
  151. package/layer/app/oip-drugs/composables/useHeartLesIndication.ts +168 -0
  152. package/layer/app/oip-drugs/composables/useHeartMatchExplain.ts +231 -0
  153. package/layer/app/oip-drugs/composables/useHeartReports.ts +239 -0
  154. package/layer/app/oip-drugs/composables/useHeartSearchFields.ts +233 -0
  155. package/layer/app/oip-drugs/composables/useMergeByKey.ts +23 -0
  156. package/layer/app/oip-drugs/composables/useOipDrugsElastic.ts +621 -0
  157. package/layer/app/oip-drugs/composables/usePageEmpty.ts +58 -0
  158. package/layer/app/oip-drugs/composables/v-useSalesSource.ts +64 -0
  159. package/layer/app/oip-drugs/drugs.css +500 -0
  160. package/layer/app/oip-drugs/esIndices.ts +322 -0
  161. package/layer/app/oip-drugs/kinds.ts +264 -0
  162. package/layer/app/oip-drugs/locales/en.json +1571 -0
  163. package/layer/app/oip-drugs/locales/fr.json +1571 -0
  164. package/layer/app/oip-drugs/monographs/blincyto-has.vue +289 -0
  165. package/layer/app/oip-drugs/monographs/darzalex-has.vue +276 -0
  166. package/layer/app/oip-drugs/monographs/dupixent-has.vue +216 -0
  167. package/layer/app/oip-drugs/monographs/enhertu-has.vue +280 -0
  168. package/layer/app/oip-drugs/monographs/has-sein-2023.vue +1649 -0
  169. package/layer/app/oip-drugs/monographs/ibrance-has.vue +277 -0
  170. package/layer/app/oip-drugs/monographs/imfinzi-has.vue +293 -0
  171. package/layer/app/oip-drugs/monographs/keytruda-has.vue +334 -0
  172. package/layer/app/oip-drugs/monographs/kisqali-has.vue +201 -0
  173. package/layer/app/oip-drugs/monographs/opdivo-has.vue +231 -0
  174. package/layer/app/oip-drugs/monographs/ozempic-has.vue +186 -0
  175. package/layer/app/oip-drugs/monographs/tecentriq-has.vue +312 -0
  176. package/layer/app/oip-drugs/monographs/yescarta-has.vue +272 -0
  177. package/layer/app/oip-drugs/types/elastic.ts +113 -0
  178. package/layer/app/oip-drugs/types/es/agencies.ts +152 -0
  179. package/layer/app/oip-drugs/types/es/directory.ts +54 -0
  180. package/layer/app/oip-drugs/types/es/docByIndex.ts +151 -0
  181. package/layer/app/oip-drugs/types/es/openmedic.ts +67 -0
  182. package/layer/app/oip-drugs/types/es/outpatient.ts +71 -0
  183. package/layer/app/oip-drugs/types/es/portal.ts +130 -0
  184. package/layer/app/oip-drugs/types/es/retail.ts +107 -0
  185. package/layer/app/oip-drugs/types/es/statistics.ts +29 -0
  186. package/layer/app/oip-drugs/types/heart/CurrencyRate.ts +16 -0
  187. package/layer/app/oip-drugs/types/heart/ansm.ts +106 -0
  188. package/layer/app/oip-drugs/types/heart/tag.ts +29 -0
  189. package/layer/app/oip-drugs/utils/associationFields.ts +118 -0
  190. package/layer/app/oip-drugs/utils/contributions.ts +36 -0
  191. package/layer/app/oip-drugs/utils/countryMapping.ts +387 -0
  192. package/layer/app/oip-drugs/utils/esHighlight.ts +21 -0
  193. package/layer/app/oip-drugs/utils/exportSheets.ts +57 -0
  194. package/layer/app/oip-drugs/utils/heartIntlPrice.ts +228 -0
  195. package/layer/app/oip-drugs/utils/heartStyleAndFormat.ts +262 -0
  196. package/layer/app/oip-drugs/utils/heartStyleAnsm.ts +71 -0
  197. package/layer/app/oip-drugs/utils/latestDate.ts +31 -0
  198. package/layer/app/oip-drugs/utils/p-heartCardTags.ts +53 -0
  199. package/layer/app/oip-drugs/utils/priceExport.ts +269 -0
  200. package/layer/app/oip-drugs/utils/q-hasRatings.ts +43 -0
  201. package/layer/app/oip-drugs/utils/s-openCard.ts +20 -0
  202. package/layer/app/oip-drugs/utils/sourceUuids.ts +155 -0
  203. package/layer/app/oip-drugs/utils/styleAndFormat.ts +40 -0
  204. package/layer/app/oip-drugs/utils/v-salesSeries.ts +156 -0
  205. package/layer/app/oip-drugs/utils/v-scopeTags.ts +23 -0
  206. package/layer/app/oip-science/PORTING.md +93 -0
  207. package/layer/app/oip-science/assets/smartSelection.json +428 -0
  208. package/layer/app/oip-science/composables/useAuthorMatching.ts +342 -0
  209. package/layer/app/oip-science/composables/useCTCenter.ts +107 -0
  210. package/layer/app/oip-science/composables/useCTInvestigators.ts +107 -0
  211. package/layer/app/oip-science/composables/useEstablishmentCrossData.ts +139 -0
  212. package/layer/app/oip-science/composables/useEstablishmentMatching.ts +176 -0
  213. package/layer/app/oip-science/composables/useEuct.ts +418 -0
  214. package/layer/app/oip-science/composables/useEuctCenter.ts +107 -0
  215. package/layer/app/oip-science/composables/useEuctInvestigators.ts +107 -0
  216. package/layer/app/oip-science/composables/useGlobalClinicalTrials.ts +624 -0
  217. package/layer/app/oip-science/composables/useLinkedTrials.ts +83 -0
  218. package/layer/app/oip-science/composables/useOipElastic.ts +658 -0
  219. package/layer/app/oip-science/composables/useOipSearchExplain.ts +166 -0
  220. package/layer/app/oip-science/composables/usePubmed.ts +487 -0
  221. package/layer/app/oip-science/composables/useSearchFields.ts +153 -0
  222. package/layer/app/oip-science/indexConfig.json +794 -0
  223. package/layer/app/oip-science/locales/en.json +483 -0
  224. package/layer/app/oip-science/locales/fr.json +483 -0
  225. package/layer/app/oip-science/science.css +1182 -0
  226. package/layer/app/oip-science/utils/a-openCard.ts +23 -0
  227. package/layer/app/oip-science/utils/b-openCard.ts +35 -0
  228. package/layer/app/oip-science/utils/c-filterContext.ts +81 -0
  229. package/layer/app/oip-science/utils/f-types.ts +241 -0
  230. package/layer/app/oip-science/utils/frDiseaseTerms.ts +269 -0
  231. package/layer/app/oip-science/utils/matchMethod.ts +53 -0
  232. package/layer/app/oip-science/utils/sliderTabs.ts +13 -0
  233. package/layer/app/oip-science/utils/sources.ts +15 -0
  234. package/layer/app/oip-science/utils/styleAndFormat.ts +608 -0
  235. package/layer/app/oip-science/utils/toPresets.ts +7 -0
  236. package/layer/app/oip-science/utils/valueTable.ts +126 -0
  237. package/layer/app/plugins/oip-science-i18n.ts +17 -0
  238. package/layer/app/utils/oipFicheRoutes.ts +118 -0
  239. package/layer/app/utils/oipResources.ts +440 -0
  240. package/layer/nuxt.config.ts +22 -2
  241. package/layer/public/reports/blincyto-has.html +292 -0
  242. package/layer/public/reports/blincyto-has.pdf +0 -0
  243. package/layer/public/reports/darzalex-has.html +279 -0
  244. package/layer/public/reports/darzalex-has.pdf +0 -0
  245. package/layer/public/reports/dupixent-has.html +217 -0
  246. package/layer/public/reports/dupixent-has.pdf +0 -0
  247. package/layer/public/reports/enhertu-has.html +283 -0
  248. package/layer/public/reports/enhertu-has.pdf +0 -0
  249. package/layer/public/reports/has-sein-2023.html +1639 -0
  250. package/layer/public/reports/ibrance-has.html +280 -0
  251. package/layer/public/reports/ibrance-has.pdf +0 -0
  252. package/layer/public/reports/imfinzi-has.html +297 -0
  253. package/layer/public/reports/imfinzi-has.pdf +0 -0
  254. package/layer/public/reports/keytruda-has.html +337 -0
  255. package/layer/public/reports/keytruda-has.pdf +0 -0
  256. package/layer/public/reports/kisqali-has.html +202 -0
  257. package/layer/public/reports/kisqali-has.pdf +0 -0
  258. package/layer/public/reports/opdivo-has.html +232 -0
  259. package/layer/public/reports/opdivo-has.pdf +0 -0
  260. package/layer/public/reports/ozempic-has.html +188 -0
  261. package/layer/public/reports/ozempic-has.pdf +0 -0
  262. package/layer/public/reports/tecentriq-has.html +315 -0
  263. package/layer/public/reports/tecentriq-has.pdf +0 -0
  264. package/layer/public/reports/yescarta-has.html +275 -0
  265. package/layer/public/reports/yescarta-has.pdf +0 -0
  266. package/layer/server/api/oip/es.post.ts +99 -0
  267. package/layer/server/api/oip/pdf.get.ts +70 -0
  268. package/layer/server/api/oip/reports.get.ts +98 -0
  269. package/layer/server/api/oip/resource-types.get.ts +22 -0
  270. package/package.json +1 -1
@@ -0,0 +1,661 @@
1
+ /**
2
+ * Les autorisations et avis etrangers d'une marque, un registre par pays.
3
+ *
4
+ * Six agences, six schemas : l'AIFA publie des extraits de Gazzetta Ufficiale,
5
+ * l'AEMPS des rapports de positionnement, le G-BA des evaluations de benefice
6
+ * par population, la MHRA un document par molecule qui porte ses produits,
7
+ * l'EMA un EPAR par medicament, la FDA une pile de soumissions par dossier.
8
+ *
9
+ * Tout est ramene ici a un seul objet (le meme que les onglets Alertes, PUT
10
+ * et Contributions donnent deja a `heart-results-details-record-drawer`) pour
11
+ * que la carte pays n'ait pas a connaitre six formats.
12
+ */
13
+ import { ES_INDEX } from "../esIndices";
14
+ import useOipDrugsElastic from "./useOipDrugsElastic";
15
+
16
+ export interface HeartIntlRecord {
17
+ key: string;
18
+ /** Nature du document, telle que l'agence la nomme. */
19
+ typeLabel: string;
20
+ icon: string;
21
+ date: string | null;
22
+ title: string;
23
+ excerpt?: string | null;
24
+ badge?: string | null;
25
+ badgeTone?: "pre" | "post" | "good" | "warn" | "neutral";
26
+ status?: string | null;
27
+ statusTone?: "good" | "bad" | "neutral";
28
+ url?: string | null;
29
+ sourceLabel: string;
30
+ sections: { key: string; label: string; body: string }[];
31
+ }
32
+
33
+ /**
34
+ * Les pays du regard international, dans l'ordre de lecture de la fiche.
35
+ *
36
+ * `priceSource` et `approvalSource` sont les fiches Directus citees en pied de
37
+ * carte, comme partout ailleurs dans l'app. L'Italie n'en a qu'une : l'AIFA
38
+ * publie sous la meme reference ses listes de prix et ses decisions.
39
+ */
40
+ export const HEART_INTL_COUNTRIES = [
41
+ {
42
+ iso: "EU",
43
+ key: "EU",
44
+ agency: "EMA",
45
+ fr: "Union européenne",
46
+ en: "European Union",
47
+ priceSource: null,
48
+ approvalSource: "d5a2cb42-69af-4c6d-8ea7-b318b2538bfd",
49
+ },
50
+ {
51
+ iso: "DE",
52
+ key: "DE",
53
+ agency: "G-BA",
54
+ fr: "Allemagne",
55
+ en: "Germany",
56
+ priceSource: "70416855-3761-4085-9664-b01ec4db30dc",
57
+ approvalSource: "4fa70cd9-7918-47e9-920e-a2059b964519",
58
+ },
59
+ {
60
+ iso: "ES",
61
+ key: "ES",
62
+ agency: "AEMPS",
63
+ fr: "Espagne",
64
+ en: "Spain",
65
+ priceSource: "765a7f89-09c3-483a-b6b0-4046738a3bb4",
66
+ approvalSource: "7cfd0f9c-cdda-44fe-872f-989edd258c16",
67
+ },
68
+ {
69
+ iso: "IT",
70
+ key: "IT",
71
+ agency: "AIFA",
72
+ fr: "Italie",
73
+ en: "Italy",
74
+ priceSource: "a91e97d1-b6b2-4f65-98b4-0914d14c4ac0",
75
+ approvalSource: null,
76
+ },
77
+ {
78
+ iso: "GB",
79
+ key: "GB",
80
+ agency: "MHRA",
81
+ fr: "Royaume-Uni",
82
+ en: "United Kingdom",
83
+ priceSource: "6ae4d4c6-a073-4cce-975f-3b255d3dd908",
84
+ approvalSource: "6ed39046-aa49-4266-bb02-7c3fe339d2b1",
85
+ },
86
+ {
87
+ iso: "US",
88
+ key: "US",
89
+ agency: "FDA",
90
+ fr: "États-Unis",
91
+ en: "United States",
92
+ priceSource: "244d86d9-5a81-4560-ba75-d5a8664a2d18",
93
+ approvalSource: "4c344cfe-94b5-4e25-9241-0f74d04a939b",
94
+ },
95
+ ] as const;
96
+
97
+ /** Le texte des agences arrive parfois en HTML : on ne veut que la phrase. */
98
+ function clean(value: any): string {
99
+ if (typeof value !== "string") return "";
100
+ return value
101
+ .replace(/ /g, " ")
102
+ .replace(/<[^>]+>/g, " ")
103
+ .replace(/\s+/g, " ")
104
+ .trim();
105
+ }
106
+
107
+ /** Premiere valeur remplie : la langue du lecteur, puis celle de l'agence. */
108
+ function pick(...values: any[]): string {
109
+ for (const value of values) {
110
+ const text = clean(value);
111
+ if (text) return text;
112
+ }
113
+ return "";
114
+ }
115
+
116
+ /** Un texte long ne tient pas dans une ligne de tableau. */
117
+ function excerptOf(text: string, length = 180) {
118
+ const value = clean(text);
119
+ return value.length > length ? `${value.slice(0, length).trimEnd()}...` : value;
120
+ }
121
+
122
+ /** Une date d'agence, parfois en millisecondes (EMA), parfois en ISO. */
123
+ function asDate(value: any): string | null {
124
+ if (value === null || value === undefined || value === "") return null;
125
+ if (typeof value === "number") return new Date(value).toISOString().slice(0, 10);
126
+ return String(value).slice(0, 10);
127
+ }
128
+
129
+ /** Le benefice supplementaire du G-BA : une bonne et une mauvaise nouvelle. */
130
+ function benefitTone(label: string): "good" | "bad" | "neutral" {
131
+ const value = label.toLowerCase();
132
+ if (!value) return "neutral";
133
+ if (value.includes("not proven") || value.includes("no added") || value.includes("lesser"))
134
+ return "bad";
135
+ if (value.includes("non-quantifiable") || value.includes("non quantifiable")) return "neutral";
136
+ return "good";
137
+ }
138
+
139
+ export default function useHeartIntlApprovals() {
140
+ // Les index APPROVAL_* passent par la cle ES dediee : le proxy du layer la
141
+ // choisit, a condition de les interroger un par un (jamais en multi-index).
142
+ const { queryCustomElastic } = useOipDrugsElastic();
143
+ const { locale } = useI18n();
144
+
145
+ const records = ref<Record<string, HeartIntlRecord[]>>({});
146
+ const isLoading = ref(false);
147
+
148
+ const en = () => locale.value === "en";
149
+
150
+ /** L'EPAR europeen : un document par medicament autorise. */
151
+ async function loadEma(brand: string): Promise<HeartIntlRecord[]> {
152
+ const res = await queryCustomElastic({
153
+ index: ES_INDEX.APPROVAL_EU_EMA,
154
+ customQuery: {
155
+ size: 10,
156
+ _source: [
157
+ "product_label",
158
+ "ema_product_number",
159
+ "medicine_status",
160
+ "marketing_authorisation_date",
161
+ "european_commission_decision_date",
162
+ "therapeutic_indication",
163
+ "inn_common_name",
164
+ "company",
165
+ "atc_code",
166
+ "url",
167
+ "is_orphan_medicine",
168
+ "is_conditional_approval",
169
+ "is_biosimilar",
170
+ "is_generic",
171
+ "is_advanced_therapy",
172
+ "is_additional_monitoring",
173
+ ],
174
+ query: { match_phrase: { product_label: brand } },
175
+ track_total_hits: true,
176
+ },
177
+ });
178
+
179
+ return (res?.results ?? []).map((doc: any, index: number) => {
180
+ const flags = [
181
+ doc.is_orphan_medicine && (en() ? "Orphan" : "Médicament orphelin"),
182
+ doc.is_conditional_approval && (en() ? "Conditional" : "AMM conditionnelle"),
183
+ doc.is_biosimilar && (en() ? "Biosimilar" : "Biosimilaire"),
184
+ doc.is_generic && (en() ? "Generic" : "Générique"),
185
+ doc.is_advanced_therapy && (en() ? "ATMP" : "Thérapie innovante"),
186
+ doc.is_additional_monitoring && (en() ? "Additional monitoring" : "Surveillance renforcée"),
187
+ ].filter(Boolean) as string[];
188
+
189
+ return {
190
+ key: doc.ema_product_number || `ema-${index}`,
191
+ typeLabel: "EPAR",
192
+ icon: "Certificate",
193
+ date: asDate(doc.marketing_authorisation_date ?? doc.european_commission_decision_date),
194
+ title: pick(doc.product_label) || brand,
195
+ excerpt: excerptOf(doc.therapeutic_indication),
196
+ badge: doc.ema_product_number || null,
197
+ status: pick(doc.medicine_status),
198
+ statusTone: /authorised/i.test(doc.medicine_status || "") ? "good" : "bad",
199
+ url: doc.url || null,
200
+ sourceLabel: "EMA",
201
+ sections: [
202
+ {
203
+ key: "indication",
204
+ label: en() ? "Therapeutic indication" : "Indication thérapeutique",
205
+ body: clean(doc.therapeutic_indication),
206
+ },
207
+ {
208
+ key: "identity",
209
+ label: en() ? "Marketing authorisation" : "Autorisation de mise sur le marché",
210
+ body: [
211
+ doc.inn_common_name && `${en() ? "INN" : "DCI"} : ${doc.inn_common_name}`,
212
+ doc.company && `${en() ? "Company" : "Laboratoire"} : ${doc.company}`,
213
+ doc.atc_code && `ATC : ${doc.atc_code}`,
214
+ flags.length && `${en() ? "Status" : "Statuts"} : ${flags.join(", ")}`,
215
+ ]
216
+ .filter(Boolean)
217
+ .join("\n"),
218
+ },
219
+ ],
220
+ } satisfies HeartIntlRecord;
221
+ });
222
+ }
223
+
224
+ /** G-BA : une evaluation par population de patients. */
225
+ async function loadGba(brand: string): Promise<HeartIntlRecord[]> {
226
+ const res = await queryCustomElastic({
227
+ index: ES_INDEX.APPROVAL_DE_GBA,
228
+ customQuery: {
229
+ size: 100,
230
+ _source: [
231
+ "benefit_assessment_id",
232
+ "case_reference",
233
+ "valid_from",
234
+ "patient_group_en",
235
+ "patient_group_de",
236
+ "indication_en",
237
+ "indication_de",
238
+ "added_benefit_en",
239
+ "added_benefit_de",
240
+ "added_benefit_reason_en",
241
+ "evidence_certainty_en",
242
+ "endpoint_mortality_en",
243
+ "endpoint_morbidity_en",
244
+ "endpoint_quality_of_life_en",
245
+ "endpoint_adverse_events_en",
246
+ "comparator_en",
247
+ "comparator_de",
248
+ "procedure_type_en",
249
+ "is_orphan",
250
+ "icd10_gm",
251
+ "url",
252
+ ],
253
+ query: { match_phrase: { brand_name: brand } },
254
+ sort: [{ valid_from: { order: "desc", unmapped_type: "date" } }],
255
+ track_total_hits: true,
256
+ },
257
+ });
258
+
259
+ return (res?.results ?? []).map((doc: any, index: number) => {
260
+ const benefit = pick(doc.added_benefit_en, doc.added_benefit_de);
261
+ const endpoints = [
262
+ ["Mortality", "Mortalité", doc.endpoint_mortality_en],
263
+ ["Morbidity", "Morbidité", doc.endpoint_morbidity_en],
264
+ ["Quality of life", "Qualité de vie", doc.endpoint_quality_of_life_en],
265
+ ["Adverse events", "Effets indésirables", doc.endpoint_adverse_events_en],
266
+ ]
267
+ .filter(([, , value]) => clean(value))
268
+ .map(([labelEn, labelFr, value]) => `${en() ? labelEn : labelFr} : ${clean(value)}`)
269
+ .join("\n");
270
+
271
+ return {
272
+ key: `${doc.benefit_assessment_id ?? index}`,
273
+ typeLabel: en() ? "Benefit assessment" : "Évaluation du bénéfice",
274
+ icon: "Scales",
275
+ date: asDate(doc.valid_from),
276
+ title:
277
+ pick(doc.patient_group_en, doc.patient_group_de) || pick(doc.indication_en),
278
+ excerpt: excerptOf(doc.indication_en || doc.indication_de),
279
+ badge: doc.is_orphan
280
+ ? en()
281
+ ? "Orphan drug"
282
+ : "Médicament orphelin"
283
+ : pick(doc.procedure_type_en) || null,
284
+ badgeTone: doc.is_orphan ? "warn" : "neutral",
285
+ status: benefit || null,
286
+ statusTone: benefitTone(benefit),
287
+ url: doc.url || null,
288
+ sourceLabel: "G-BA",
289
+ sections: [
290
+ {
291
+ key: "population",
292
+ label: en() ? "Patient group" : "Population de patients",
293
+ body: pick(doc.patient_group_en, doc.patient_group_de),
294
+ },
295
+ {
296
+ key: "indication",
297
+ label: "Indication",
298
+ body: pick(doc.indication_en, doc.indication_de),
299
+ },
300
+ {
301
+ key: "benefit",
302
+ label: en() ? "Added benefit" : "Bénéfice supplémentaire",
303
+ body: [
304
+ benefit && `${en() ? "Extent" : "Ampleur"} : ${benefit}`,
305
+ clean(doc.added_benefit_reason_en) &&
306
+ `${en() ? "Reason" : "Motif"} : ${clean(doc.added_benefit_reason_en)}`,
307
+ clean(doc.evidence_certainty_en) &&
308
+ `${en() ? "Certainty of evidence" : "Certitude des preuves"} : ${clean(doc.evidence_certainty_en)}`,
309
+ ]
310
+ .filter(Boolean)
311
+ .join("\n"),
312
+ },
313
+ {
314
+ key: "endpoints",
315
+ label: en() ? "Endpoints" : "Critères de jugement",
316
+ body: endpoints,
317
+ },
318
+ {
319
+ key: "context",
320
+ label: en() ? "Procedure" : "Procédure",
321
+ body: [
322
+ doc.case_reference && `${en() ? "Case" : "Dossier"} : ${doc.case_reference}`,
323
+ clean(doc.comparator_en || doc.comparator_de) &&
324
+ `${en() ? "Comparator" : "Comparateur"} : ${clean(doc.comparator_en || doc.comparator_de)}`,
325
+ Array.isArray(doc.icd10_gm) && doc.icd10_gm.length && `CIM-10 : ${doc.icd10_gm.join(", ")}`,
326
+ doc.valid_from && `${en() ? "Valid from" : "En vigueur depuis"} : ${asDate(doc.valid_from)}`,
327
+ ]
328
+ .filter(Boolean)
329
+ .join("\n"),
330
+ },
331
+ ],
332
+ } satisfies HeartIntlRecord;
333
+ });
334
+ }
335
+
336
+ /** AEMPS : les rapports de positionnement therapeutique (IPT). */
337
+ async function loadAemps(brand: string): Promise<HeartIntlRecord[]> {
338
+ const res = await queryCustomElastic({
339
+ index: ES_INDEX.APPROVAL_ES_AEMPS,
340
+ customQuery: {
341
+ size: 100,
342
+ _source: [
343
+ "detail_id",
344
+ "report_date",
345
+ "report_english_title",
346
+ "report_spanish_title",
347
+ "illness_english_label",
348
+ "illness_spanish_label",
349
+ "atc2_code",
350
+ "atc2_english_label",
351
+ "atc2_spanish_label",
352
+ "version",
353
+ "pdf_url",
354
+ "tekkare_pdf_url",
355
+ ],
356
+ query: { match_phrase: { brand } },
357
+ sort: [{ report_date: { order: "desc", unmapped_type: "date" } }],
358
+ track_total_hits: true,
359
+ },
360
+ });
361
+
362
+ return (res?.results ?? []).map((doc: any, index: number) => ({
363
+ key: doc.detail_id || `aemps-${index}`,
364
+ typeLabel: en() ? "Positioning report (IPT)" : "Rapport de positionnement (IPT)",
365
+ icon: "FileText",
366
+ date: asDate(doc.report_date),
367
+ title:
368
+ pick(doc.report_english_title, doc.report_spanish_title) || brand,
369
+ excerpt: pick(doc.illness_english_label, doc.illness_spanish_label),
370
+ badge: doc.version ? `v${doc.version}` : null,
371
+ status: null,
372
+ url: doc.tekkare_pdf_url || doc.pdf_url || null,
373
+ sourceLabel: "AEMPS",
374
+ sections: [
375
+ {
376
+ key: "title",
377
+ label: en() ? "Report" : "Rapport",
378
+ body: pick(doc.report_english_title, doc.report_spanish_title),
379
+ },
380
+ {
381
+ key: "titleEn",
382
+ label: en() ? "Spanish title" : "Titre espagnol",
383
+ body: pick(doc.report_spanish_title),
384
+ },
385
+ {
386
+ key: "illness",
387
+ label: en() ? "Condition" : "Pathologie",
388
+ body: [
389
+ pick(doc.illness_english_label, doc.illness_spanish_label),
390
+ doc.atc2_code &&
391
+ `ATC : ${doc.atc2_code} (${pick(doc.atc2_english_label, doc.atc2_spanish_label)})`,
392
+ ]
393
+ .filter(Boolean)
394
+ .join("\n"),
395
+ },
396
+ ],
397
+ }));
398
+ }
399
+
400
+ /** AIFA : un extrait de Gazzetta Ufficiale par decision. */
401
+ async function loadAifa(brand: string): Promise<HeartIntlRecord[]> {
402
+ const res = await queryCustomElastic({
403
+ index: ES_INDEX.APPROVAL_IT_AIFA,
404
+ customQuery: {
405
+ size: 100,
406
+ _source: [
407
+ "opinion_id",
408
+ "decision_date",
409
+ "has_code",
410
+ "brand",
411
+ "molecule",
412
+ "company",
413
+ "atc5_code",
414
+ "nature_of_request",
415
+ "nature_of_request_en",
416
+ "indication",
417
+ "indication_en",
418
+ "essential",
419
+ "essential_en",
420
+ "aifa_url",
421
+ "tekkare_pdf_url",
422
+ "tekkare_pdf_url_en",
423
+ ],
424
+ query: { match_phrase: { brand } },
425
+ sort: [{ decision_date: { order: "desc", unmapped_type: "date" } }],
426
+ track_total_hits: true,
427
+ },
428
+ });
429
+
430
+ return (res?.results ?? []).map((doc: any, index: number) => ({
431
+ key: doc.opinion_id || `aifa-${index}`,
432
+ typeLabel:
433
+ pick(doc.nature_of_request_en, doc.nature_of_request) || "AIFA",
434
+ icon: "Stamp",
435
+ date: asDate(doc.decision_date),
436
+ // La nature tient deja la colonne « Nature » : l'objet, c'est l'indication.
437
+ title:
438
+ excerptOf(doc.indication_en || doc.indication, 120) ||
439
+ pick(doc.nature_of_request_en, doc.nature_of_request) ||
440
+ brand,
441
+ excerpt: [doc.molecule, doc.company].filter(Boolean).join(" · ") || null,
442
+ badge: doc.has_code ? `GU ${doc.has_code}` : null,
443
+ url: doc.tekkare_pdf_url_en || doc.tekkare_pdf_url || doc.aifa_url || null,
444
+ sourceLabel: "AIFA",
445
+ sections: [
446
+ {
447
+ key: "indication",
448
+ label: "Indication",
449
+ body: pick(doc.indication_en, doc.indication),
450
+ },
451
+ {
452
+ key: "essential",
453
+ label: en() ? "Decision" : "Décision",
454
+ body: pick(doc.essential_en, doc.essential),
455
+ },
456
+ {
457
+ key: "context",
458
+ label: en() ? "Reference" : "Référence",
459
+ body: [
460
+ doc.molecule && `${en() ? "Molecule" : "Molécule"} : ${doc.molecule}`,
461
+ doc.company && `${en() ? "Company" : "Laboratoire"} : ${doc.company}`,
462
+ doc.atc5_code && `ATC : ${doc.atc5_code}`,
463
+ ]
464
+ .filter(Boolean)
465
+ .join("\n"),
466
+ },
467
+ ],
468
+ }));
469
+ }
470
+
471
+ /**
472
+ * MHRA : l'index tient un document par molecule, les marques sont dans ses
473
+ * produits. La marque est donc cherchee dans le tableau imbrique, et chaque
474
+ * produit retenu devient une ligne.
475
+ */
476
+ async function loadMhra(brand: string): Promise<HeartIntlRecord[]> {
477
+ const res = await queryCustomElastic({
478
+ index: ES_INDEX.APPROVAL_UK_MHRA,
479
+ customQuery: {
480
+ size: 10,
481
+ _source: ["molecule", "last_indication"],
482
+ query: {
483
+ nested: {
484
+ path: "products",
485
+ query: { match_phrase: { "products.brand": brand } },
486
+ inner_hits: { size: 50 },
487
+ },
488
+ },
489
+ track_total_hits: true,
490
+ },
491
+ allAnswer: true,
492
+ });
493
+
494
+ const hits = res?.hits?.hits ?? [];
495
+ const rows: HeartIntlRecord[] = [];
496
+
497
+ for (const hit of hits) {
498
+ const molecule = hit._source?.molecule ?? "";
499
+ const products = hit.inner_hits?.products?.hits?.hits ?? [];
500
+ for (const product of products) {
501
+ const source = product._source ?? {};
502
+ // L'indication vit dans les PDF du produit ; a defaut, celle que le
503
+ // document porte pour la molecule entiere.
504
+ const pdf = (source.pdfs ?? []).find((entry: any) => clean(entry?.indication));
505
+ const indication = pick(pdf?.indication, hit._source?.last_indication);
506
+
507
+ rows.push({
508
+ key: `${source.product_code ?? source.brand ?? ""}-${product._nested?.offset ?? rows.length}`,
509
+ typeLabel: en() ? "Marketing authorisation" : "Autorisation de mise sur le marché",
510
+ icon: "Certificate",
511
+ date: asDate(source.last_approval_date),
512
+ title: pick(source.product_label, source.brand) || brand,
513
+ excerpt: excerptOf(indication),
514
+ badge: source.atc_code || null,
515
+ status: pick(source.status) || null,
516
+ url: pdf?.pdf_source_url || null,
517
+ sourceLabel: "MHRA",
518
+ sections: [
519
+ { key: "indication", label: "Indication", body: indication },
520
+ {
521
+ key: "context",
522
+ label: en() ? "Product" : "Produit",
523
+ body: [
524
+ molecule && `${en() ? "Molecule" : "Molécule"} : ${molecule}`,
525
+ source.manufacturer && `${en() ? "Manufacturer" : "Fabricant"} : ${source.manufacturer}`,
526
+ source.product_code && `${en() ? "Licence" : "Licence"} : ${source.product_code}`,
527
+ source.last_update_date &&
528
+ `${en() ? "Last update" : "Dernière mise à jour"} : ${asDate(source.last_update_date)}`,
529
+ ]
530
+ .filter(Boolean)
531
+ .join("\n"),
532
+ },
533
+ ],
534
+ });
535
+ }
536
+ }
537
+
538
+ return rows.sort(
539
+ (a, b) => new Date(b.date || 0).getTime() - new Date(a.date || 0).getTime(),
540
+ );
541
+ }
542
+
543
+ /**
544
+ * FDA : un dossier par marque, et sa pile de soumissions. L'index repete la
545
+ * meme soumission autant de fois qu'elle porte de PDF : une ligne par couple
546
+ * date + soumission suffit.
547
+ */
548
+ async function loadFda(brand: string): Promise<HeartIntlRecord[]> {
549
+ const res = await queryCustomElastic({
550
+ index: ES_INDEX.APPROVAL_US_FDA,
551
+ customQuery: {
552
+ size: 10,
553
+ _source: [
554
+ "applno",
555
+ "appltype",
556
+ "drug_name",
557
+ "molecules",
558
+ "drug_company",
559
+ "drug_description",
560
+ "dailymed_indication",
561
+ "approvals.action_date",
562
+ "approvals.submission",
563
+ "approvals.supplement_categories_approval_type",
564
+ "approvals.url",
565
+ ],
566
+ query: { match_phrase: { drug_name: brand } },
567
+ track_total_hits: true,
568
+ },
569
+ });
570
+
571
+ const rows: HeartIntlRecord[] = [];
572
+
573
+ for (const doc of res?.results ?? []) {
574
+ const seen = new Set<string>();
575
+ for (const approval of doc.approvals ?? []) {
576
+ const key = `${doc.applno}-${approval.action_date}-${approval.submission}`;
577
+ if (seen.has(key)) continue;
578
+ seen.add(key);
579
+
580
+ rows.push({
581
+ key,
582
+ typeLabel: pick(approval.supplement_categories_approval_type) || doc.appltype || "FDA",
583
+ icon: "Stamp",
584
+ date: asDate(approval.action_date),
585
+ title: `${pick(doc.drug_name)} ${approval.submission ?? ""}`.trim(),
586
+ excerpt: excerptOf(doc.dailymed_indication),
587
+ badge: doc.appltype ? `${doc.appltype} ${doc.applno}` : null,
588
+ url: approval.url || null,
589
+ sourceLabel: "FDA",
590
+ sections: [
591
+ {
592
+ key: "submission",
593
+ label: en() ? "Submission" : "Soumission",
594
+ body: [
595
+ approval.submission && `${en() ? "Submission" : "Soumission"} : ${approval.submission}`,
596
+ approval.supplement_categories_approval_type &&
597
+ `${en() ? "Type" : "Nature"} : ${approval.supplement_categories_approval_type}`,
598
+ approval.action_date &&
599
+ `${en() ? "Action date" : "Date de décision"} : ${asDate(approval.action_date)}`,
600
+ ]
601
+ .filter(Boolean)
602
+ .join("\n"),
603
+ },
604
+ {
605
+ key: "product",
606
+ label: en() ? "Product" : "Produit",
607
+ body: [
608
+ doc.molecules && `${en() ? "Molecules" : "Molécules"} : ${doc.molecules}`,
609
+ doc.drug_company && `${en() ? "Company" : "Laboratoire"} : ${doc.drug_company}`,
610
+ doc.drug_description && `${en() ? "Form" : "Forme"} : ${doc.drug_description}`,
611
+ ]
612
+ .filter(Boolean)
613
+ .join("\n"),
614
+ },
615
+ {
616
+ key: "indication",
617
+ label: "Indication",
618
+ body: clean(doc.dailymed_indication),
619
+ },
620
+ ],
621
+ });
622
+ }
623
+ }
624
+
625
+ return rows.sort(
626
+ (a, b) => new Date(b.date || 0).getTime() - new Date(a.date || 0).getTime(),
627
+ );
628
+ }
629
+
630
+ /**
631
+ * Les six registres pour une marque. Un registre en echec (droits, index
632
+ * indisponible) laisse sa carte vide ; les cinq autres restent lisibles.
633
+ */
634
+ async function load(brand: string) {
635
+ if (!brand) return;
636
+ isLoading.value = true;
637
+
638
+ const safe = async (key: string, run: () => Promise<HeartIntlRecord[]>) => {
639
+ try {
640
+ return [key, await run()] as const;
641
+ } catch (error) {
642
+ console.error(`[useHeartIntlApprovals] ${key}`, error);
643
+ return [key, [] as HeartIntlRecord[]] as const;
644
+ }
645
+ };
646
+
647
+ const answers = await Promise.all([
648
+ safe("EU", () => loadEma(brand)),
649
+ safe("DE", () => loadGba(brand)),
650
+ safe("ES", () => loadAemps(brand)),
651
+ safe("IT", () => loadAifa(brand)),
652
+ safe("GB", () => loadMhra(brand)),
653
+ safe("US", () => loadFda(brand)),
654
+ ]);
655
+
656
+ records.value = Object.fromEntries(answers);
657
+ isLoading.value = false;
658
+ }
659
+
660
+ return { records, isLoading, load };
661
+ }