@tekkare/romulus 0.1.251 → 0.1.253

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 (267) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/utils/projects.d.ts +8 -0
  3. package/dist/runtime/utils/projects.js +8 -5
  4. package/layer/app/app.config.ts +6 -0
  5. package/layer/app/components/OipPubmedSummary.vue +223 -0
  6. package/layer/app/components/OipResourceDrawer.vue +331 -0
  7. package/layer/app/components/OipResourceDrawerLink.vue +40 -0
  8. package/layer/app/components/OipResourceField.vue +41 -0
  9. package/layer/app/components/OipResourceGenericCard.vue +54 -0
  10. package/layer/app/components/OipResourceSource.vue +50 -0
  11. package/layer/app/components/OipResourceTags.vue +65 -0
  12. package/layer/app/components/OipResourceView.vue +107 -0
  13. package/layer/app/components/oip-drugs/fiches/AssociationFiche.vue +775 -0
  14. package/layer/app/components/oip-drugs/fiches/BrandFiche.vue +1111 -0
  15. package/layer/app/components/oip-drugs/fiches/IndicationFiche.vue +182 -0
  16. package/layer/app/components/oip-drugs/fiches/JoFiche.vue +101 -0
  17. package/layer/app/components/oip-drugs/fiches/PndsFiche.vue +279 -0
  18. package/layer/app/components/oip-drugs/heart/JoCard.vue +314 -0
  19. package/layer/app/components/oip-drugs/heart/TextHighlight.vue +326 -0
  20. package/layer/app/components/oip-drugs/heart/ansm/BrandCard.vue +187 -0
  21. package/layer/app/components/oip-drugs/heart/ansm/CisDetail.vue +363 -0
  22. package/layer/app/components/oip-drugs/heart/ansm/CisTable.vue +167 -0
  23. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyCard.vue +373 -0
  24. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyFiche.vue +372 -0
  25. package/layer/app/components/oip-drugs/heart/associations/TranscriptionCard.vue +55 -0
  26. package/layer/app/components/oip-drugs/heart/elastic/SourceContainerElastic.vue +103 -0
  27. package/layer/app/components/oip-drugs/heart/france/BrandGroupCard.vue +450 -0
  28. package/layer/app/components/oip-drugs/heart/mitm/Card.vue +199 -0
  29. package/layer/app/components/oip-drugs/heart/mitm/Source.vue +23 -0
  30. package/layer/app/components/oip-drugs/heart/mitm/details/Overview.vue +381 -0
  31. package/layer/app/components/oip-drugs/heart/put/Card.vue +212 -0
  32. package/layer/app/components/oip-drugs/heart/results/AccesCard.vue +696 -0
  33. package/layer/app/components/oip-drugs/heart/results/AlertCard.vue +342 -0
  34. package/layer/app/components/oip-drugs/heart/results/AsmrCursor.vue +229 -0
  35. package/layer/app/components/oip-drugs/heart/results/EvalCard.vue +370 -0
  36. package/layer/app/components/oip-drugs/heart/results/IndicCard.vue +743 -0
  37. package/layer/app/components/oip-drugs/heart/results/MatchExplain.vue +243 -0
  38. package/layer/app/components/oip-drugs/heart/results/PrefRows.vue +105 -0
  39. package/layer/app/components/oip-drugs/heart/results/details/HasHistory.vue +487 -0
  40. package/layer/app/components/oip-drugs/heart/results/details/InternationalCountryCard.vue +461 -0
  41. package/layer/app/components/oip-drugs/heart/results/details/InternationalOverview.vue +200 -0
  42. package/layer/app/components/oip-drugs/heart/results/details/InternationalPriceDrawer.vue +647 -0
  43. package/layer/app/components/oip-drugs/heart/results/details/Monograph.vue +128 -0
  44. package/layer/app/components/oip-drugs/heart/results/details/OpinionDrawer.vue +535 -0
  45. package/layer/app/components/oip-drugs/heart/results/details/PresentationDrawer.vue +738 -0
  46. package/layer/app/components/oip-drugs/heart/results/details/Price.vue +227 -0
  47. package/layer/app/components/oip-drugs/heart/results/details/PriceOverview.vue +1261 -0
  48. package/layer/app/components/oip-drugs/heart/results/details/RecordDrawer.vue +494 -0
  49. package/layer/app/components/oip-drugs/heart/results/details/RecordList.vue +426 -0
  50. package/layer/app/components/oip-drugs/heart/results/les/Card.vue +340 -0
  51. package/layer/app/components/oip-drugs/heart/results/les/Drawer.vue +67 -0
  52. package/layer/app/components/oip-drugs/heart/results/les/IndicationDetail.vue +149 -0
  53. package/layer/app/components/oip-drugs/heart/results/les/Source.vue +20 -0
  54. package/layer/app/components/oip-drugs/heart/results/les/SourceTag.vue +39 -0
  55. package/layer/app/components/oip-drugs/heart/searchPage/DynamicSource.vue +29 -0
  56. package/layer/app/components/oip-drugs/medicam/market/Chart.vue +121 -0
  57. package/layer/app/components/oip-drugs/outpatient/ProductEvolution.vue +236 -0
  58. package/layer/app/components/oip-drugs/resources/Ansm.vue +75 -0
  59. package/layer/app/components/oip-drugs/resources/Association.vue +30 -0
  60. package/layer/app/components/oip-drugs/resources/Brand.vue +65 -0
  61. package/layer/app/components/oip-drugs/resources/Contribution.vue +216 -0
  62. package/layer/app/components/oip-drugs/resources/Hospital.vue +151 -0
  63. package/layer/app/components/oip-drugs/resources/Indication.vue +54 -0
  64. package/layer/app/components/oip-drugs/resources/Jo.vue +57 -0
  65. package/layer/app/components/oip-drugs/resources/Medicam.vue +140 -0
  66. package/layer/app/components/oip-drugs/resources/Mitm.vue +84 -0
  67. package/layer/app/components/oip-drugs/resources/NationalAssembly.vue +93 -0
  68. package/layer/app/components/oip-drugs/resources/Outpatient.vue +97 -0
  69. package/layer/app/components/oip-drugs/resources/Pnds.vue +24 -0
  70. package/layer/app/components/oip-drugs/resources/Price.vue +106 -0
  71. package/layer/app/components/oip-drugs/resources/Put.vue +143 -0
  72. package/layer/app/components/oip-science/appel-a-projet/Cards.vue +162 -0
  73. package/layer/app/components/oip-science/clinical-trials/Cards.vue +382 -0
  74. package/layer/app/components/oip-science/clinical-trials/CardsGlobal.vue +273 -0
  75. package/layer/app/components/oip-science/clinical-trials/LinkedList.vue +366 -0
  76. package/layer/app/components/oip-science/clinical-trials/PairBadge.vue +25 -0
  77. package/layer/app/components/oip-science/clinical-trials/PairBody.vue +182 -0
  78. package/layer/app/components/oip-science/clinical-trials/SitesDrawer.vue +95 -0
  79. package/layer/app/components/oip-science/common/Form.vue +73 -0
  80. package/layer/app/components/oip-science/common/MatchAvgTag.vue +283 -0
  81. package/layer/app/components/oip-science/common/NoData.vue +16 -0
  82. package/layer/app/components/oip-science/common/ScoreBadge.vue +35 -0
  83. package/layer/app/components/oip-science/common/Source.vue +31 -0
  84. package/layer/app/components/oip-science/common/TableTags.vue +279 -0
  85. package/layer/app/components/oip-science/common/Timeline.vue +104 -0
  86. package/layer/app/components/oip-science/common/ToolTips.vue +89 -0
  87. package/layer/app/components/oip-science/cross/publicationPubmed.vue +484 -0
  88. package/layer/app/components/oip-science/crossData/CrossDataLocations.vue +71 -0
  89. package/layer/app/components/oip-science/ctgov/Investigators.vue +148 -0
  90. package/layer/app/components/oip-science/ctgov/Locations.vue +102 -0
  91. package/layer/app/components/oip-science/ctgov/Publications.vue +52 -0
  92. package/layer/app/components/oip-science/ctgov/Sponsors.vue +66 -0
  93. package/layer/app/components/oip-science/ctgov/StudyDescription.vue +1136 -0
  94. package/layer/app/components/oip-science/ctgov-centers/ClinicalTrials.vue +40 -0
  95. package/layer/app/components/oip-science/ctgov-centers/Publications.vue +50 -0
  96. package/layer/app/components/oip-science/ctgov-investigators/ClinicalTrials.vue +43 -0
  97. package/layer/app/components/oip-science/ctgov-investigators/Informations.vue +171 -0
  98. package/layer/app/components/oip-science/ctgov-investigators/Publications.vue +44 -0
  99. package/layer/app/components/oip-science/elastic/DateRangeFilter.vue +209 -0
  100. package/layer/app/components/oip-science/elastic/Filter.vue +719 -0
  101. package/layer/app/components/oip-science/elastic/MatchExplain.vue +152 -0
  102. package/layer/app/components/oip-science/establishments/Cards.vue +183 -0
  103. package/layer/app/components/oip-science/establishments/ClinicalTrials.vue +62 -0
  104. package/layer/app/components/oip-science/establishments/Hcps.vue +227 -0
  105. package/layer/app/components/oip-science/establishments/Informations.vue +263 -0
  106. package/layer/app/components/oip-science/establishments/LinkedList.vue +153 -0
  107. package/layer/app/components/oip-science/establishments/MatchedLabels.vue +194 -0
  108. package/layer/app/components/oip-science/establishments/PublicFinance.vue +62 -0
  109. package/layer/app/components/oip-science/euct/Investigators.vue +125 -0
  110. package/layer/app/components/oip-science/euct/Locations.vue +102 -0
  111. package/layer/app/components/oip-science/euct/Products.vue +101 -0
  112. package/layer/app/components/oip-science/euct/Publications.vue +89 -0
  113. package/layer/app/components/oip-science/euct/Sponsors.vue +86 -0
  114. package/layer/app/components/oip-science/euct/StudyDescription.vue +1075 -0
  115. package/layer/app/components/oip-science/euct-centers/ClinicalTrials.vue +40 -0
  116. package/layer/app/components/oip-science/euct-centers/Publications.vue +55 -0
  117. package/layer/app/components/oip-science/euct-investigators/ClinicalTrials.vue +41 -0
  118. package/layer/app/components/oip-science/euct-investigators/Informations.vue +171 -0
  119. package/layer/app/components/oip-science/euct-investigators/Publications.vue +51 -0
  120. package/layer/app/components/oip-science/hcps/AppelsAProjet.vue +62 -0
  121. package/layer/app/components/oip-science/hcps/Cards.vue +237 -0
  122. package/layer/app/components/oip-science/hcps/ClinicalTrials.vue +78 -0
  123. package/layer/app/components/oip-science/hcps/Informations.vue +296 -0
  124. package/layer/app/components/oip-science/hcps/LinkedList.vue +153 -0
  125. package/layer/app/components/oip-science/hcps/MatchedLabels.vue +207 -0
  126. package/layer/app/components/oip-science/hcps/Pubmed.vue +128 -0
  127. package/layer/app/components/oip-science/local/SliderTabs.vue +99 -0
  128. package/layer/app/components/oip-science/local/SliderTabsFull.vue +135 -0
  129. package/layer/app/components/oip-science/pubmed/Authors.vue +128 -0
  130. package/layer/app/components/oip-science/pubmed/Cards.vue +417 -0
  131. package/layer/app/components/oip-science/pubmed/ClinicalTrials.vue +71 -0
  132. package/layer/app/components/oip-science/resources/Phrc.vue +39 -0
  133. package/layer/app/composables/useOipChapterScroll.ts +43 -0
  134. package/layer/app/composables/useOipResourceHooks.ts +27 -0
  135. package/layer/app/composables/useOipResourceNav.ts +66 -0
  136. package/layer/app/composables/useOipResourceViews.ts +117 -0
  137. package/layer/app/composables/useOipResources.ts +83 -0
  138. package/layer/app/oip-drugs/PORTING.md +120 -0
  139. package/layer/app/oip-drugs/composables/useAssociationDetail.ts +220 -0
  140. package/layer/app/oip-drugs/composables/useAssociationExport.ts +63 -0
  141. package/layer/app/oip-drugs/composables/useAssociationLookup.ts +105 -0
  142. package/layer/app/oip-drugs/composables/useCardPrefOptions.ts +201 -0
  143. package/layer/app/oip-drugs/composables/useCurrency.ts +121 -0
  144. package/layer/app/oip-drugs/composables/useDirectusSources.ts +224 -0
  145. package/layer/app/oip-drugs/composables/useHeartBrandMatches.ts +112 -0
  146. package/layer/app/oip-drugs/composables/useHeartDetailSearches.ts +479 -0
  147. package/layer/app/oip-drugs/composables/useHeartIntlApprovals.ts +661 -0
  148. package/layer/app/oip-drugs/composables/useHeartLesIndication.ts +168 -0
  149. package/layer/app/oip-drugs/composables/useHeartMatchExplain.ts +231 -0
  150. package/layer/app/oip-drugs/composables/useHeartReports.ts +239 -0
  151. package/layer/app/oip-drugs/composables/useHeartSearchFields.ts +233 -0
  152. package/layer/app/oip-drugs/composables/useMergeByKey.ts +23 -0
  153. package/layer/app/oip-drugs/composables/useOipDrugsElastic.ts +621 -0
  154. package/layer/app/oip-drugs/composables/usePageEmpty.ts +58 -0
  155. package/layer/app/oip-drugs/composables/v-useSalesSource.ts +64 -0
  156. package/layer/app/oip-drugs/drugs.css +500 -0
  157. package/layer/app/oip-drugs/esIndices.ts +322 -0
  158. package/layer/app/oip-drugs/kinds.ts +264 -0
  159. package/layer/app/oip-drugs/locales/en.json +1571 -0
  160. package/layer/app/oip-drugs/locales/fr.json +1571 -0
  161. package/layer/app/oip-drugs/monographs/blincyto-has.vue +289 -0
  162. package/layer/app/oip-drugs/monographs/darzalex-has.vue +276 -0
  163. package/layer/app/oip-drugs/monographs/dupixent-has.vue +216 -0
  164. package/layer/app/oip-drugs/monographs/enhertu-has.vue +280 -0
  165. package/layer/app/oip-drugs/monographs/has-sein-2023.vue +1649 -0
  166. package/layer/app/oip-drugs/monographs/ibrance-has.vue +277 -0
  167. package/layer/app/oip-drugs/monographs/imfinzi-has.vue +293 -0
  168. package/layer/app/oip-drugs/monographs/keytruda-has.vue +334 -0
  169. package/layer/app/oip-drugs/monographs/kisqali-has.vue +201 -0
  170. package/layer/app/oip-drugs/monographs/opdivo-has.vue +231 -0
  171. package/layer/app/oip-drugs/monographs/ozempic-has.vue +186 -0
  172. package/layer/app/oip-drugs/monographs/tecentriq-has.vue +312 -0
  173. package/layer/app/oip-drugs/monographs/yescarta-has.vue +272 -0
  174. package/layer/app/oip-drugs/types/elastic.ts +113 -0
  175. package/layer/app/oip-drugs/types/es/agencies.ts +152 -0
  176. package/layer/app/oip-drugs/types/es/directory.ts +54 -0
  177. package/layer/app/oip-drugs/types/es/docByIndex.ts +151 -0
  178. package/layer/app/oip-drugs/types/es/openmedic.ts +67 -0
  179. package/layer/app/oip-drugs/types/es/outpatient.ts +71 -0
  180. package/layer/app/oip-drugs/types/es/portal.ts +130 -0
  181. package/layer/app/oip-drugs/types/es/retail.ts +107 -0
  182. package/layer/app/oip-drugs/types/es/statistics.ts +29 -0
  183. package/layer/app/oip-drugs/types/heart/CurrencyRate.ts +16 -0
  184. package/layer/app/oip-drugs/types/heart/ansm.ts +106 -0
  185. package/layer/app/oip-drugs/types/heart/tag.ts +29 -0
  186. package/layer/app/oip-drugs/utils/associationFields.ts +118 -0
  187. package/layer/app/oip-drugs/utils/contributions.ts +36 -0
  188. package/layer/app/oip-drugs/utils/countryMapping.ts +387 -0
  189. package/layer/app/oip-drugs/utils/esHighlight.ts +21 -0
  190. package/layer/app/oip-drugs/utils/exportSheets.ts +57 -0
  191. package/layer/app/oip-drugs/utils/heartIntlPrice.ts +228 -0
  192. package/layer/app/oip-drugs/utils/heartStyleAndFormat.ts +262 -0
  193. package/layer/app/oip-drugs/utils/heartStyleAnsm.ts +71 -0
  194. package/layer/app/oip-drugs/utils/latestDate.ts +31 -0
  195. package/layer/app/oip-drugs/utils/p-heartCardTags.ts +53 -0
  196. package/layer/app/oip-drugs/utils/priceExport.ts +269 -0
  197. package/layer/app/oip-drugs/utils/q-hasRatings.ts +43 -0
  198. package/layer/app/oip-drugs/utils/s-openCard.ts +20 -0
  199. package/layer/app/oip-drugs/utils/sourceUuids.ts +155 -0
  200. package/layer/app/oip-drugs/utils/styleAndFormat.ts +40 -0
  201. package/layer/app/oip-drugs/utils/v-salesSeries.ts +156 -0
  202. package/layer/app/oip-drugs/utils/v-scopeTags.ts +23 -0
  203. package/layer/app/oip-science/PORTING.md +93 -0
  204. package/layer/app/oip-science/assets/smartSelection.json +428 -0
  205. package/layer/app/oip-science/composables/useAuthorMatching.ts +342 -0
  206. package/layer/app/oip-science/composables/useCTCenter.ts +107 -0
  207. package/layer/app/oip-science/composables/useCTInvestigators.ts +107 -0
  208. package/layer/app/oip-science/composables/useEstablishmentCrossData.ts +139 -0
  209. package/layer/app/oip-science/composables/useEstablishmentMatching.ts +176 -0
  210. package/layer/app/oip-science/composables/useEuct.ts +418 -0
  211. package/layer/app/oip-science/composables/useEuctCenter.ts +107 -0
  212. package/layer/app/oip-science/composables/useEuctInvestigators.ts +107 -0
  213. package/layer/app/oip-science/composables/useGlobalClinicalTrials.ts +624 -0
  214. package/layer/app/oip-science/composables/useLinkedTrials.ts +83 -0
  215. package/layer/app/oip-science/composables/useOipElastic.ts +658 -0
  216. package/layer/app/oip-science/composables/useOipSearchExplain.ts +166 -0
  217. package/layer/app/oip-science/composables/usePubmed.ts +487 -0
  218. package/layer/app/oip-science/composables/useSearchFields.ts +153 -0
  219. package/layer/app/oip-science/indexConfig.json +794 -0
  220. package/layer/app/oip-science/locales/en.json +483 -0
  221. package/layer/app/oip-science/locales/fr.json +483 -0
  222. package/layer/app/oip-science/science.css +1182 -0
  223. package/layer/app/oip-science/utils/a-openCard.ts +23 -0
  224. package/layer/app/oip-science/utils/b-openCard.ts +35 -0
  225. package/layer/app/oip-science/utils/c-filterContext.ts +81 -0
  226. package/layer/app/oip-science/utils/f-types.ts +241 -0
  227. package/layer/app/oip-science/utils/frDiseaseTerms.ts +269 -0
  228. package/layer/app/oip-science/utils/matchMethod.ts +53 -0
  229. package/layer/app/oip-science/utils/sliderTabs.ts +13 -0
  230. package/layer/app/oip-science/utils/sources.ts +15 -0
  231. package/layer/app/oip-science/utils/styleAndFormat.ts +608 -0
  232. package/layer/app/oip-science/utils/toPresets.ts +7 -0
  233. package/layer/app/oip-science/utils/valueTable.ts +126 -0
  234. package/layer/app/plugins/oip-science-i18n.ts +17 -0
  235. package/layer/app/utils/oipFicheRoutes.ts +118 -0
  236. package/layer/app/utils/oipResources.ts +440 -0
  237. package/layer/nuxt.config.ts +22 -2
  238. package/layer/public/reports/blincyto-has.html +292 -0
  239. package/layer/public/reports/blincyto-has.pdf +0 -0
  240. package/layer/public/reports/darzalex-has.html +279 -0
  241. package/layer/public/reports/darzalex-has.pdf +0 -0
  242. package/layer/public/reports/dupixent-has.html +217 -0
  243. package/layer/public/reports/dupixent-has.pdf +0 -0
  244. package/layer/public/reports/enhertu-has.html +283 -0
  245. package/layer/public/reports/enhertu-has.pdf +0 -0
  246. package/layer/public/reports/has-sein-2023.html +1639 -0
  247. package/layer/public/reports/ibrance-has.html +280 -0
  248. package/layer/public/reports/ibrance-has.pdf +0 -0
  249. package/layer/public/reports/imfinzi-has.html +297 -0
  250. package/layer/public/reports/imfinzi-has.pdf +0 -0
  251. package/layer/public/reports/keytruda-has.html +337 -0
  252. package/layer/public/reports/keytruda-has.pdf +0 -0
  253. package/layer/public/reports/kisqali-has.html +202 -0
  254. package/layer/public/reports/kisqali-has.pdf +0 -0
  255. package/layer/public/reports/opdivo-has.html +232 -0
  256. package/layer/public/reports/opdivo-has.pdf +0 -0
  257. package/layer/public/reports/ozempic-has.html +188 -0
  258. package/layer/public/reports/ozempic-has.pdf +0 -0
  259. package/layer/public/reports/tecentriq-has.html +315 -0
  260. package/layer/public/reports/tecentriq-has.pdf +0 -0
  261. package/layer/public/reports/yescarta-has.html +275 -0
  262. package/layer/public/reports/yescarta-has.pdf +0 -0
  263. package/layer/server/api/oip/es.post.ts +99 -0
  264. package/layer/server/api/oip/pdf.get.ts +70 -0
  265. package/layer/server/api/oip/reports.get.ts +98 -0
  266. package/layer/server/api/oip/resource-types.get.ts +22 -0
  267. package/package.json +1 -1
@@ -0,0 +1,117 @@
1
+ import { defineAsyncComponent, type Component } from "vue";
2
+
3
+ /**
4
+ * Le composant de chaque onglet de fiche, par type et par cle d'onglet (cf.
5
+ * OIP_RESOURCE_SPECS). Charges a la demande : une fiche ouverte ne tire que
6
+ * ses onglets, ce qui compte sur les reseaux lents des hopitaux.
7
+ *
8
+ * Contrat d'un onglet : props `id` (l'identifiant de route, cf.
9
+ * `spec.routeId`), `highlight` (termes a surligner) et `lang`. Ses liens vers
10
+ * d'autres fiches passent par useOipResourceNav, ses sources par
11
+ * useOipResourceHooks().
12
+ */
13
+ const LOADERS: Record<string, Record<string, () => Promise<any>>> = {
14
+ PubMed: {
15
+ "summary": () => import("../components/OipPubmedSummary.vue"),
16
+ "authors": () => import("../components/oip-science/pubmed/Authors.vue"),
17
+ "clinical-trials": () => import("../components/oip-science/pubmed/ClinicalTrials.vue"),
18
+ },
19
+ ClinicalTrialsNCT: {
20
+ "study-description": () => import("../components/oip-science/ctgov/StudyDescription.vue"),
21
+ "investigators": () => import("../components/oip-science/ctgov/Investigators.vue"),
22
+ "locations": () => import("../components/oip-science/ctgov/Locations.vue"),
23
+ "sponsors": () => import("../components/oip-science/ctgov/Sponsors.vue"),
24
+ "publications": () => import("../components/oip-science/ctgov/Publications.vue"),
25
+ },
26
+ ClinicalTrialsEUCT: {
27
+ "study-description": () => import("../components/oip-science/euct/StudyDescription.vue"),
28
+ "products": () => import("../components/oip-science/euct/Products.vue"),
29
+ "investigators": () => import("../components/oip-science/euct/Investigators.vue"),
30
+ "locations": () => import("../components/oip-science/euct/Locations.vue"),
31
+ "sponsors": () => import("../components/oip-science/euct/Sponsors.vue"),
32
+ "publications": () => import("../components/oip-science/euct/Publications.vue"),
33
+ },
34
+ HCP: {
35
+ "informations": () => import("../components/oip-science/hcps/Informations.vue"),
36
+ "clinical-trials": () => import("../components/oip-science/hcps/ClinicalTrials.vue"),
37
+ "appels-a-projet": () => import("../components/oip-science/hcps/AppelsAProjet.vue"),
38
+ "pubmed": () => import("../components/oip-science/hcps/Pubmed.vue"),
39
+ "matched-labels": () => import("../components/oip-science/hcps/MatchedLabels.vue"),
40
+ },
41
+ Establishment: {
42
+ "informations": () => import("../components/oip-science/establishments/Informations.vue"),
43
+ "clinical-trials": () => import("../components/oip-science/establishments/ClinicalTrials.vue"),
44
+ "public-finance": () => import("../components/oip-science/establishments/PublicFinance.vue"),
45
+ "hcps": () => import("../components/oip-science/establishments/Hcps.vue"),
46
+ "matched-labels": () => import("../components/oip-science/establishments/MatchedLabels.vue"),
47
+ },
48
+ ClinicalTrialsInvestigatorsNCT: {
49
+ "informations": () => import("../components/oip-science/ctgov-investigators/Informations.vue"),
50
+ "clinical-trials": () => import("../components/oip-science/ctgov-investigators/ClinicalTrials.vue"),
51
+ "publications": () => import("../components/oip-science/ctgov-investigators/Publications.vue"),
52
+ },
53
+ ClinicalTrialsInvestigatorsEUCT: {
54
+ "informations": () => import("../components/oip-science/euct-investigators/Informations.vue"),
55
+ "clinical-trials": () => import("../components/oip-science/euct-investigators/ClinicalTrials.vue"),
56
+ "publications": () => import("../components/oip-science/euct-investigators/Publications.vue"),
57
+ },
58
+ ClinicalTrialsCentersNCT: {
59
+ "clinical-trials": () => import("../components/oip-science/ctgov-centers/ClinicalTrials.vue"),
60
+ "publications": () => import("../components/oip-science/ctgov-centers/Publications.vue"),
61
+ },
62
+ ClinicalTrialsCentersEUCT: {
63
+ "clinical-trials": () => import("../components/oip-science/euct-centers/ClinicalTrials.vue"),
64
+ "publications": () => import("../components/oip-science/euct-centers/Publications.vue"),
65
+ },
66
+ };
67
+
68
+ /**
69
+ * Les vues d'un seul tenant (`spec.view`, fiches de fr_drugs_app) et les
70
+ * fiches qu'un lien peut viser (cf. utils/oipFicheRoutes).
71
+ *
72
+ * - `drugs-<famille>` : la ressource rangee, par son `resourceId` (props `id`,
73
+ * parfois `by`). Une PUT, une MITM, un prix ou une AMM ouvrent la fiche
74
+ * marque sur leur onglet, comme fr_drugs_app.
75
+ * - `drugs-<fiche>-fiche` : la fiche elle-meme, telle que la page de
76
+ * fr_drugs_app la monte (props de sa route : `brand` et `tab`, `code`,
77
+ * `id`, `label`).
78
+ */
79
+ const VIEW_LOADERS: Record<string, () => Promise<any>> = {
80
+ "science-phrc": () => import("../components/oip-science/resources/Phrc.vue"),
81
+ "drugs-brand": () => import("../components/oip-drugs/resources/Brand.vue"),
82
+ "drugs-put": () => import("../components/oip-drugs/resources/Put.vue"),
83
+ "drugs-mitm": () => import("../components/oip-drugs/resources/Mitm.vue"),
84
+ "drugs-price": () => import("../components/oip-drugs/resources/Price.vue"),
85
+ "drugs-ansm": () => import("../components/oip-drugs/resources/Ansm.vue"),
86
+ "drugs-indication": () => import("../components/oip-drugs/resources/Indication.vue"),
87
+ "drugs-pnds": () => import("../components/oip-drugs/resources/Pnds.vue"),
88
+ "drugs-jo": () => import("../components/oip-drugs/resources/Jo.vue"),
89
+ "drugs-association": () => import("../components/oip-drugs/resources/Association.vue"),
90
+ "drugs-contribution": () => import("../components/oip-drugs/resources/Contribution.vue"),
91
+ "drugs-national-assembly": () => import("../components/oip-drugs/resources/NationalAssembly.vue"),
92
+ "drugs-medicam": () => import("../components/oip-drugs/resources/Medicam.vue"),
93
+ "drugs-outpatient": () => import("../components/oip-drugs/resources/Outpatient.vue"),
94
+ "drugs-hospital": () => import("../components/oip-drugs/resources/Hospital.vue"),
95
+ "drugs-brand-fiche": () => import("../components/oip-drugs/fiches/BrandFiche.vue"),
96
+ "drugs-indication-fiche": () => import("../components/oip-drugs/fiches/IndicationFiche.vue"),
97
+ "drugs-pnds-fiche": () => import("../components/oip-drugs/fiches/PndsFiche.vue"),
98
+ "drugs-jo-fiche": () => import("../components/oip-drugs/fiches/JoFiche.vue"),
99
+ "drugs-association-fiche": () => import("../components/oip-drugs/fiches/AssociationFiche.vue"),
100
+ };
101
+
102
+ const cache = new Map<string, Component>();
103
+
104
+ export function oipResourceViewComponent(key: string): Component | null {
105
+ const loader = VIEW_LOADERS[key];
106
+ if (!loader) return null;
107
+ if (!cache.has(`view:${key}`)) cache.set(`view:${key}`, defineAsyncComponent(loader));
108
+ return cache.get(`view:${key}`)!;
109
+ }
110
+
111
+ export function oipResourceTabComponent(type: string, key: string): Component | null {
112
+ const loader = LOADERS[type]?.[key];
113
+ if (!loader) return null;
114
+ const cacheKey = `${type}/${key}`;
115
+ if (!cache.has(cacheKey)) cache.set(cacheKey, defineAsyncComponent(loader));
116
+ return cache.get(cacheKey)!;
117
+ }
@@ -0,0 +1,83 @@
1
+ import {
2
+ oipResourceAppUrl,
3
+ oipResourceSpec,
4
+ OIP_RESOURCE_APPS,
5
+ type OipEsCluster,
6
+ type OipResourceTypeEntry,
7
+ } from "../utils/oipResources";
8
+
9
+ /**
10
+ * Lecture des ressources pour les vues du layer : le proxy Elasticsearch
11
+ * commun (/api/oip/es) et le registre des types de l'API.
12
+ */
13
+
14
+ /** Une recherche dans un index declare par une vue (cf. OIP_RESOURCE_SPECS). */
15
+ export function oipEsSearch<T = any>(cluster: OipEsCluster, index: string, body: Record<string, unknown>) {
16
+ return $fetch<T>("/api/oip/es", { method: "POST", query: { cluster, index }, body });
17
+ }
18
+
19
+ /** Un document par son `_id`, ou null. */
20
+ export async function oipEsGet<S = any>(
21
+ cluster: OipEsCluster,
22
+ index: string,
23
+ id: string,
24
+ source?: string[],
25
+ ): Promise<{ _id: string; _source: S } | null> {
26
+ const response = await oipEsSearch<any>(cluster, index, {
27
+ size: 1,
28
+ query: { ids: { values: [id] } },
29
+ ...(source ? { _source: source } : {}),
30
+ });
31
+ return response?.hits?.hits?.[0] ?? null;
32
+ }
33
+
34
+ /**
35
+ * Les documents d'un type decrit par le layer, par `_id`, pour lister ses
36
+ * elements dans la page d'un projet. Vide pour un type que le layer ne
37
+ * connait pas : l'app le relit elle-meme.
38
+ */
39
+ export async function oipHydrate(type: string, ids: string[]): Promise<Record<string, any>> {
40
+ const spec = oipResourceSpec(type);
41
+ if (!spec || !ids.length) return {};
42
+ const response = await oipEsSearch<any>(spec.cluster, spec.kind.index, {
43
+ size: ids.length,
44
+ ...(spec.kind.source ? { _source: spec.kind.source } : {}),
45
+ query: { ids: { values: ids } },
46
+ });
47
+ const docs: Record<string, any> = Object.fromEntries(
48
+ (response?.hits?.hits ?? []).map((hit: any) => [String(hit._id), hit._source]),
49
+ );
50
+ // Une contribution se titre par la marque, lue dans l'index HAS.
51
+ if (type === "FrPatientContribution") {
52
+ const { withContributionBrands } = await import("../oip-drugs/utils/contributions");
53
+ await withContributionBrands(Object.values(docs));
54
+ }
55
+ return docs;
56
+ }
57
+
58
+ // Le registre change quelques fois par an : un appel par session suffit.
59
+ let registry: Promise<OipResourceTypeEntry[]> | null = null;
60
+
61
+ export function useOipResourceTypes(): Promise<OipResourceTypeEntry[]> {
62
+ registry ??= $fetch<OipResourceTypeEntry[]>("/api/oip/resource-types").catch(() => {
63
+ registry = null;
64
+ return [];
65
+ });
66
+ return registry;
67
+ }
68
+
69
+ /**
70
+ * Le lien vers la fiche dans son app d'origine, et le nom de cette app. Pour
71
+ * un type sans vue, l'URL du registre (deja absolue, remplie par l'appelant).
72
+ */
73
+ export function oipResourceOrigin(type: string, id: string, lang: "fr" | "en") {
74
+ const spec = oipResourceSpec(type);
75
+ if (!spec) return null;
76
+ return {
77
+ name: OIP_RESOURCE_APPS[spec.app].name,
78
+ url: oipResourceAppUrl(spec.app, spec.detailPath(id), lang),
79
+ };
80
+ }
81
+
82
+ /** La marque des contributions patients, lue dans l'index HAS (cf. oip-drugs/utils/contributions). */
83
+ export { withContributionBrands } from "../oip-drugs/utils/contributions";
@@ -0,0 +1,120 @@
1
+ # Porter une fiche médicament de fr_drugs_app dans le layer
2
+
3
+ Les fiches médicament (marque HAS et ses onglets, indication, PNDS, publication JO,
4
+ association, contribution, évolutions des ventes) vivent ici pour s'afficher **à
5
+ l'identique** dans toutes les apps du portail : la page de fr_drugs_app et le tiroir des
6
+ projets de n'importe quelle app montent le même composant. Source :
7
+ `~/Projects/fr_drugs_app/app` (lecture seule).
8
+
9
+ Le rendu de fr_drugs_app est la spec : on porte son code presque tel quel, en ne changeant
10
+ que ce qui l'attache à l'app. Pas de refonte. Même démarche que les fiches science
11
+ (`../oip-science/PORTING.md`), à lire pour l'esprit ; les règles ci-dessous priment.
12
+
13
+ ## Où ranger
14
+
15
+ | Quoi | Où | Nom / import |
16
+ |---|---|---|
17
+ | Composant | `app/components/oip-drugs/<dossier>/<Nom>.vue` | auto-importé sous `OipDrugs<Dossier><Nom>` (ex. `oip-drugs/heart/TextHighlight.vue` → `<OipDrugsHeartTextHighlight>`) |
18
+ | Composable, util, store porté | `app/oip-drugs/composables/*.ts`, `app/oip-drugs/utils/*.ts` | **pas** auto-importés : import relatif explicite. Garder les noms de fr_drugs_app. |
19
+ | Index ES | `app/oip-drugs/esIndices.ts` (copie de `types/esIndices.ts`) | `import { ES_INDEX } from "<rel>/oip-drugs/esIndices"` |
20
+ | Types ES | `app/oip-drugs/types/` (copie de `types/elastic.ts`, `types/es/*`, `types/heart/*`) | imports relatifs |
21
+ | Monographies | `app/oip-drugs/monographs/*.vue` (copie de `app/monographs/*.vue`) | `import.meta.glob` relatif |
22
+ | Rapports PDF/HTML | `layer/public/reports/` (déjà copiés) | même URL `/reports/...` |
23
+
24
+ Dans fr_drugs_app, un composant de `components/heart/x/y.vue` devient
25
+ `components/oip-drugs/heart/x/Y.vue` (même arborescence sous `oip-drugs/`, nom de fichier
26
+ avec une majuscule). Tous les lots suivent cette règle : un lot utilise le composant d'un
27
+ autre par ce nom, même s'il n'existe pas encore.
28
+
29
+ ## Remplacements obligatoires
30
+
31
+ - **Racine** : chaque fiche, vue de ressource et composant monté seul (tiroirs téléportés
32
+ compris) porte la classe `oip-drugs` sur sa racine. Les classes globales de fr_drugs_app
33
+ (`main.css`, `heart/css/scoped.css` : `heart_*`, `oip_*`…) marchent dessous (cf.
34
+ `drugs.css`). Ne pas les réécrire. Les règles `.heart_app X` deviennent `.oip-drugs X`.
35
+ - **Elasticsearch** : `useHeartElastic()` → `useOipDrugsElastic()`
36
+ (`app/oip-drugs/composables/useOipDrugsElastic.ts`, mêmes fonctions). Les listes nommées
37
+ (`keyName`), que la page relisait dans `useResultStore`, sont dans `lists[keyName]` du
38
+ même objet. `$fetch('/api/es?index=…')` et `useElasticGeneral`/`useFetch('/api/es…')` →
39
+ `oipEsSearch('drugs', index, body)` (`app/composables/useOipResources.ts`). Index par
40
+ `ES_INDEX.X` (ids Directus). Les surcharges `esIndexFromConfig(runtimeConfig.public.x, …)`
41
+ prennent la valeur du catalogue.
42
+ - **Routes serveur** : `/api/pdf?url=` → `/api/oip/pdf?url=`, `/api/reports` →
43
+ `/api/oip/reports`, `/api/sources` → `/api/oip/sources` (déjà servies par le layer).
44
+ - **i18n** : clés sous `oipDrugs.` : `$t('heart.price')` → `$t('oipDrugs.heart.price')`
45
+ (fichiers `app/oip-drugs/locales/*.json`, copie complète de fr_drugs_app, ne pas les éditer).
46
+ - **Route** : jamais `useRoute()`, `route.params`, `route.query`. Ce que la page lisait dans
47
+ sa route devient une prop (`brand`, `tab`, `code`, `id`, `label`, `cis`, `indication`…).
48
+ Un changement d'onglet que la page écrivait dans `?tab=` devient
49
+ `emit('update:tab', key)` (prop `tab` + `v-model:tab`).
50
+ - **Liens** (`navigateTo`, `router.push`, `NuxtLink`, `localePath`, `heartOpenCard`,
51
+ `heartBrandPath`, `associationPath`) : `const nav = useOipResourceNav()` (auto-importé),
52
+ puis `nav.openApp('fr_drugs_app', path)` au clic et `:href="nav.appHref('fr_drugs_app', path)"`
53
+ pour garder le Ctrl/Cmd-clic (`@click.prevent` sauf touche modificatrice). `path` est le
54
+ chemin de fr_drugs_app, inchangé (`drugsBrandPath`, `drugsAssociationPath` de
55
+ `app/oip-drugs/kinds.ts`). Dans un tiroir, le layer empile la fiche si elle est connue
56
+ (cf. `app/utils/oipFicheRoutes.ts`) ; dans une page, il navigue. Liens externes inchangés.
57
+ - **Bouton Projet** (`hubr-project-add`, `HubrProjectAdd`) :
58
+ `const { projectButton } = useOipResourceHooks()` puis
59
+ `<component :is="projectButton" v-if="projectButton" :resource-id="…" type="…" :label="…" compact />`.
60
+ Ne jamais importer `useHubrProjects`, `useHubrResourceKinds`, `useElysiaApi`.
61
+ - **Sources** (`sourceContainerElastic`, `useHeartSourceUuids`, `HEART_SOURCE_UUIDS`) :
62
+ `<OipResourceSource :uuid="…" />` par uuid (le composant du layer lit `/api/oip/sources`
63
+ et ouvre le panneau Sources par le hook de l'app). Les uuids viennent de
64
+ `app/oip-drugs/utils/sourceUuids.ts` (lot P), plus de `route.path`.
65
+ - **Stores Pinia et `useState`** (`filterStore`, `sharedFiltersStore`, `resultStore`,
66
+ `elasticQueryStore`, `appConfigStore`, `CurrencyStore`, `countryMapping`,
67
+ `priceCurrencyStore`, `countryEdito`, `retail`, `outpatient`, `cardPrefs`,
68
+ `useHeartDetailTab`) : état local du composant, ou petit composable par instance dans
69
+ `app/oip-drugs/composables/`. Les données de référence (taux de change, table des pays)
70
+ deviennent un composable qui les charge.
71
+ `cardPrefs` / `useCardPrefOptions` → prop `prefs` avec les valeurs par défaut de fr_drugs_app.
72
+ - **Cadre de l'app** : retirer `useExportSource`, `useAlertSource`, `useViews`,
73
+ `useHeartLayout().setToolbar*`, `useAppLayout`, `definePageMeta`, `usePosthogTracking`,
74
+ `useHubrEmbedded` (toujours « embarqué » dans le layer) et l'export de la fiche
75
+ (`details/export.vue`, `les/export.vue`, `mitm/export.vue`) : l'en-tête et l'export restent
76
+ dans la page de fr_drugs_app, qui les passe au slot `actions` de la fiche.
77
+ - Surlignage de recherche lu dans un store : prop `highlight: string[]` (défaut `[]`).
78
+
79
+ ## Contrats
80
+
81
+ - **Fiche** (`components/oip-drugs/fiches/*Fiche.vue`, montée par la page de fr_drugs_app
82
+ et par le tiroir) : props de sa route (`BrandFiche` : `brand`, `tab` ;
83
+ `IndicationFiche` : `code` ; `PndsFiche` : `id` ; `JoFiche` : `id` ;
84
+ `AssociationFiche` : `label`), plus `lang`, `highlight`. Elle rend son bandeau
85
+ (`RmPageHeader` : titre, onglets) et son contenu ; slot `actions` dans le bandeau pour
86
+ l'export et le bouton Projet de la page. Emet `update:tab`. Charge ses données elle-même.
87
+ - **Vue de ressource** (`components/oip-drugs/resources/*.vue`, montée par le tiroir d'un
88
+ projet pour un `resourceType`) : props `id` (le `resourceId`), `type`, `lang`,
89
+ `highlight`, parfois `by` (`"cis"`, `"local_drug_id"` : l'id est ce champ et non l'`_id`).
90
+ Elle relit le document et monte la fiche ou la carte qui convient.
91
+
92
+ Affichage tant que les données arrivent : `RmLoadingState` ; document absent : l'état
93
+ vide de fr_drugs_app, sinon `RmEmptyState`.
94
+
95
+ ## Répartition (sept lots en parallèle)
96
+
97
+ Chaque fichier de fr_drugs_app a un seul lot propriétaire. Un lot n'écrit que ses
98
+ fichiers et utilise ceux des autres par leur nom cible (même API que l'original). Fichier
99
+ utile absent du tableau : le créer sous un nom préfixé par la lettre du lot
100
+ (`oip-drugs/utils/q-<nom>.ts`) et le signaler.
101
+
102
+ | Lot | Contenu (chemins fr_drugs_app sous `app/`) |
103
+ |---|---|
104
+ | P, socle | composants `heart/textHighlight`, `heart/elastic/sourceContainerElastic` (→ liste d'`OipResourceSource`), `heart/results/{matchExplain,prefRows,asmrCursor,indicCard,accesCard,evalCard,alertCard}`, `heart/searchPage/dynamicSource` ; composables `useHeartMatchExplain`, `useHeartSearchFields`, `useHeartSourceUuids` (→ `oip-drugs/utils/sourceUuids.ts`, uuids explicites), `useDirectusSources`, `useMergeByKey`, `usePageEmpty`, `useCardPrefOptions` (→ défauts) ; utils `heartStyleAndFormat`, `styleAndFormat`, `latestDate`, `heartStyleAnsm`, `associationFields`, `esHighlight`, `priceExport`, `exportSheets` ; stores `CurrencyStore` (→ `oip-drugs/composables/useCurrency.ts`), `countryMapping` (→ util) |
105
+ | Q, fiche marque (cadre et onglets HAS) | `pages/hubr/heart/details/[id].vue` → `fiches/BrandFiche.vue` (onglets par clé, `tab`) ; `heart/results/details/{monograph,hasHistory,opinionDrawer,recordList,recordDrawer}` ; `heart/les/{drawer,indicationDetail,card,source,sourceTag}` ; `useHeartLesIndication`, `useHeartDetailSearches` ; `app/monographs/*.vue` ; vue de ressource `resources/Brand.vue` (doc BRAND_HAS par `_id` → `brand_group` → `BrandFiche`) |
106
+ | R, fiche marque (prix et international) | `heart/results/details/{priceOverview,presentationDrawer,internationalOverview,internationalCountryCard,internationalPriceDrawer}` ; `useHeartIntlApprovals`, `useHeartReports` (→ `/api/oip/reports`), `useHeartBrandMatches`, utils `heartIntlPrice` |
107
+ | S, fiche marque (ANSM, MITM) et redirections | `heart/ansm/cisDetail` (+ puces CIS), `heart/mitm/details/overview` ; vues de ressource `resources/{Put,Mitm,Price,Ansm}.vue` : relire le document (`_id`, ou `by`), trouver la marque comme la page de fr_drugs_app (`put/[id]`, `mitm/[id]`, `prices/details/[id]`, `ansm/details/[id]`) et monter `BrandFiche` sur l'onglet `put`, `mitm`, `price`, `ansm` ; sans marque, ce que la page montre alors (au minimum sa carte : `heart/put/card`, `heart/mitm/...`, `heart/results/brandGroupCard`, `heart/ansm/brandCard`) |
108
+ | T, indication, PNDS, JO | `pages/hubr/heart/indication/[id].vue` → `fiches/IndicationFiche.vue` (+ `heart/results/details/price`) ; `pages/hubr/heart/pnds/[id].vue` → `fiches/PndsFiche.vue` ; `pages/hubr/heart/prices/jo/[id].vue` → `fiches/JoFiche.vue` (+ `heart/joCard`) ; vues `resources/{Indication,Pnds,Jo}.vue` |
109
+ | U, associations | `pages/hubr/heart/associations/[id].vue` → `fiches/AssociationFiche.vue` ; `heart/associations/{transcriptionCard,nationalAssemblyCard,nationalAssemblyFiche}` ; `useAssociationDetail`, `useAssociationExport` (la partie lecture), `useAssociationLookup` ; vues `resources/{Association,NationalAssembly}.vue` ; `resources/Contribution.vue` : **le détail de la contribution** (décision produit), en reprenant le contenu de `~/Projects/france_augmented_science_app/app/components/drugs/ContributionDetail.vue` mais avec les composants et le style de fr_drugs_app, et un lien vers l'association |
110
+ | V, évolutions des ventes | `hubr/projectMedicamEvolution` + `medicam/market/chart` (store `retail` → état local) → `resources/Medicam.vue` ; `outpatient/productEvolution` (+ `useElasticGeneral`, store `outpatient` → état local) → `resources/Outpatient.vue` ; `resources/Hospital.vue` pour `FrSusBrand` et `FrRetrocessionBrand` (**décision produit** : un graphe d'évolution par marque, au gabarit des deux autres, en reprenant les données et requêtes de `~/Projects/france_augmented_science_app/app/components/drugs/HospitalEvolution.vue`) |
111
+
112
+ ## Compte rendu attendu de chaque lot
113
+
114
+ Fichiers créés, écarts de rendu volontaires, clés i18n manquantes, dépendances vers
115
+ d'autres lots, ce qui n'a pas pu être porté.
116
+
117
+ ## Règles de texte
118
+
119
+ Aucun tiret cadratin (—) dans un texte visible : parenthèses ou virgules. Commentaires en
120
+ français sans accents dans le code, comme le reste du layer.
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Tout ce que les sept index disent d'une association, pour sa fiche (portage
3
+ * de useAssociationDetail de fr_drugs_app).
4
+ *
5
+ * Cinq index portent `association_label_regroup` et se lisent directement.
6
+ * Les deux autres ne l'ont pas : les seances du comite d'interface ANSM se
7
+ * rejoignent par `session_url` en passant par la liste des participants, et le
8
+ * texte des debats HAS par `has_code` + `transcription_url` en passant par la
9
+ * transcription. Les blocs ne partent qu'a la demande : il y en a deux cent
10
+ * cinquante par debat, et une fiche en cite huit.
11
+ */
12
+ import type { Ref } from "vue";
13
+ import { ES_INDEX } from "../esIndices";
14
+ import type { EsIndexName } from "../esIndices";
15
+ import { ASSOCIATION_FIELDS, NATIONAL_ASSEMBLY_FIELDS } from "../utils/associationFields";
16
+ import useOipDrugsElastic from "./useOipDrugsElastic";
17
+ import { useAssociationHasBrands } from "./useAssociationExport";
18
+
19
+ /** Plafond des listes de la fiche. Aucune association n'en approche. */
20
+ const PAGE_SIZE = 200;
21
+ /** Plafond des blocs d'un debat. Le plus long en compte environ 400. */
22
+ const BLOCKS_SIZE = 500;
23
+ /** Plafond des participants d'une fiche : une vingtaine par seance. */
24
+ const ATTENDEES_SIZE = 1000;
25
+
26
+ export function useAssociationDetail(label: Ref<string>) {
27
+ const { queryCustomElastic } = useOipDrugsElastic();
28
+
29
+ const directory = ref<any>(null);
30
+ const contributions = ref<any[]>([]);
31
+ const transcriptions = ref<any[]>([]);
32
+ const sessions = ref<any[]>([]);
33
+ const nationalAssembly = ref<any[]>([]);
34
+ const loading = ref(true);
35
+
36
+ /**
37
+ * Toutes les ecritures du nom que l'annuaire connait pour cette
38
+ * association : son nom regroupe, et les appellations brutes des sources
39
+ * qu'il liste dans `association_label`.
40
+ *
41
+ * Filtrer sur le seul nom regroupe laissait 55 associations sur 159 sans
42
+ * aucune contribution a l'ecran : le regroupement de l'annuaire et celui des
43
+ * index sources ne sont pas le meme. L'appellation brute, elle, se retrouve
44
+ * de part et d'autre. A retirer le jour ou `association_label_regroup` sera
45
+ * aligne sur les six index.
46
+ */
47
+ const knownLabels = ref<string[]>([]);
48
+
49
+ function labelsFrom(doc: any): string[] {
50
+ const raw = doc?.[ASSOCIATION_FIELDS.labelVariants];
51
+ const names = new Set<string>([label.value]);
52
+ if (typeof raw === "string") {
53
+ raw.split(";").forEach((value) => {
54
+ const name = value.trim();
55
+ if (name) names.add(name);
56
+ });
57
+ }
58
+ return [...names];
59
+ }
60
+
61
+ /** Les deux champs de nom des index sources, contre toutes ces ecritures. */
62
+ const byLabel = () => ({
63
+ bool: {
64
+ should: [
65
+ { terms: { [ASSOCIATION_FIELDS.label]: knownLabels.value } },
66
+ { terms: { [ASSOCIATION_FIELDS.labelVariants]: knownLabels.value } },
67
+ ],
68
+ minimum_should_match: 1,
69
+ },
70
+ });
71
+
72
+ async function hits(index: EsIndexName, body: Record<string, any>) {
73
+ const res: any = await queryCustomElastic({ index, customQuery: body as any, allAnswer: true });
74
+ return (res?.hits?.hits ?? []).map((hit: any) => hit._source);
75
+ }
76
+
77
+ /**
78
+ * Les autres associations presentes a chaque seance, par `session_url`. Une
79
+ * seance du comite d'interface reunit une vingtaine d'associations : la
80
+ * fiche les nomme, l'association affichee en moins.
81
+ */
82
+ const sessionAttendees = ref<Record<string, string[]>>({});
83
+
84
+ /** Les seances suivies par l'association, via la liste des participants. */
85
+ async function loadSessions() {
86
+ const attendees = await hits(ES_INDEX.ASSOCIATION_SESSION_ATTENDEE_ANSM, {
87
+ size: PAGE_SIZE,
88
+ _source: ["session_url"],
89
+ query: { bool: { must: [byLabel()] } },
90
+ });
91
+ const urls = [...new Set(attendees.map((a: any) => a.session_url).filter(Boolean))];
92
+ if (!urls.length) return [];
93
+
94
+ const [found, others] = await Promise.all([
95
+ hits(ES_INDEX.ASSOCIATION_SESSION_ANSM, {
96
+ size: PAGE_SIZE,
97
+ query: { terms: { session_url: urls } },
98
+ sort: [{ session_date: { order: "desc" } }],
99
+ }),
100
+ // Toute la liste des participants de ces seances, en une requete : une
101
+ // requete par carte aurait fait autant d'allers-retours qu'il y a de
102
+ // seances.
103
+ hits(ES_INDEX.ASSOCIATION_SESSION_ATTENDEE_ANSM, {
104
+ size: ATTENDEES_SIZE,
105
+ query: { terms: { session_url: urls } },
106
+ }),
107
+ ]);
108
+
109
+ sessionAttendees.value = groupAttendees(others);
110
+ return found;
111
+ }
112
+
113
+ /**
114
+ * Les participants regroupes par seance, l'association affichee retiree et
115
+ * les doublons de nom ecartes : une meme association peut figurer deux fois
116
+ * dans la liste sous deux ecritures qui se regroupent sous le meme nom.
117
+ */
118
+ function groupAttendees(attendees: any[]) {
119
+ const mine = new Set(knownLabels.value.map((name) => name.toLowerCase()));
120
+ const out: Record<string, string[]> = {};
121
+ attendees.forEach((attendee: any) => {
122
+ const url = attendee?.session_url;
123
+ const name =
124
+ attendee?.[ASSOCIATION_FIELDS.label] || attendee?.[ASSOCIATION_FIELDS.labelVariants];
125
+ if (!url || !name || mine.has(String(name).toLowerCase())) return;
126
+ const names = (out[url] ??= []);
127
+ if (!names.some((value) => value.toLowerCase() === String(name).toLowerCase())) {
128
+ names.push(String(name));
129
+ }
130
+ });
131
+ Object.values(out).forEach((names) => names.sort((a, b) => a.localeCompare(b, "fr")));
132
+ return out;
133
+ }
134
+
135
+ async function load() {
136
+ loading.value = true;
137
+
138
+ // L'annuaire d'abord : c'est lui qui dit sous quels noms chercher le
139
+ // reste.
140
+ const directoryHits = await hits(ES_INDEX.ASSOCIATION_DIRECTORY, {
141
+ size: 1,
142
+ query: { term: { _id: label.value } },
143
+ });
144
+ directory.value = directoryHits[0] ?? null;
145
+ knownLabels.value = labelsFrom(directory.value);
146
+
147
+ const [contributionHits, transcriptionHits, sessionHits, nationalAssemblyHits] = await Promise.all([
148
+ hits(ES_INDEX.ASSOCIATION_CONTRIBUTION_HAS, {
149
+ size: PAGE_SIZE,
150
+ query: { bool: { must: [byLabel()] } },
151
+ sort: [{ contribution_date: { order: "desc" } }],
152
+ }),
153
+ hits(ES_INDEX.ASSOCIATION_TRANSCRIPTION_HAS, {
154
+ size: PAGE_SIZE,
155
+ query: { bool: { must: [byLabel()] } },
156
+ sort: [{ session_date: { order: "desc" } }],
157
+ }),
158
+ loadSessions(),
159
+ // Les comptes rendus de commission portent deux listes de noms
160
+ // regroupes : les associations citees et celles de la fiche. Une
161
+ // association compte si elle est dans l'une ou l'autre, sous toutes ses
162
+ // ecritures connues.
163
+ hits(ES_INDEX.ASSOCIATION_COMMISSION_SESSION_NATIONAL_ASSEMBLY, {
164
+ size: PAGE_SIZE,
165
+ _source: { excludes: [NATIONAL_ASSEMBLY_FIELDS.text] },
166
+ query: {
167
+ bool: {
168
+ should: [NATIONAL_ASSEMBLY_FIELDS.associations, NATIONAL_ASSEMBLY_FIELDS.ficheAssociations].map(
169
+ (field) => ({ terms: { [field]: knownLabels.value } }),
170
+ ),
171
+ minimum_should_match: 1,
172
+ },
173
+ },
174
+ sort: [{ [NATIONAL_ASSEMBLY_FIELDS.sessionDate]: { order: "desc" } }],
175
+ }),
176
+ ]);
177
+
178
+ contributions.value = contributionHits;
179
+ transcriptions.value = transcriptionHits;
180
+ sessions.value = sessionHits;
181
+ nationalAssembly.value = nationalAssemblyHits;
182
+ loading.value = false;
183
+
184
+ // Apres coup : les cartes s'affichent sans attendre, le lien vers l'avis
185
+ // apparait quand la marque est resolue.
186
+ await loadBrands(contributionHits.map((contribution: any) => contribution.has_code));
187
+ }
188
+
189
+ /** La marque de l'avis de chaque contribution, par code HAS (cf. useAssociationHasBrands). */
190
+ const { brands, load: loadBrands } = useAssociationHasBrands();
191
+
192
+ /** Le fil d'un debat, tour de parole par tour de parole. */
193
+ async function loadBlocks(hasCode: string, transcriptionUrl: string) {
194
+ return await hits(ES_INDEX.ASSOCIATION_TRANSCRIPTION_BLOCK_HAS, {
195
+ size: BLOCKS_SIZE,
196
+ query: {
197
+ bool: {
198
+ must: [
199
+ { term: { has_code: hasCode } },
200
+ { term: { transcription_url: transcriptionUrl } },
201
+ ],
202
+ },
203
+ },
204
+ sort: [{ block_index: { order: "asc" } }],
205
+ });
206
+ }
207
+
208
+ return {
209
+ directory,
210
+ contributions,
211
+ transcriptions,
212
+ sessions,
213
+ nationalAssembly,
214
+ sessionAttendees,
215
+ loading,
216
+ load,
217
+ loadBlocks,
218
+ brands,
219
+ };
220
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Portage de la partie lecture de useAssociationExport de fr_drugs_app : la
3
+ * marque de l'avis auquel une contribution se rattache. Les schemas de
4
+ * colonnes et la lecture des index lies pour l'export restent dans
5
+ * fr_drugs_app, qui garde l'export de la fiche.
6
+ */
7
+ import { ES_INDEX } from "../esIndices";
8
+ import useOipDrugsElastic from "./useOipDrugsElastic";
9
+
10
+ /**
11
+ * La marque de l'avis auquel se rattache chaque contribution, par code HAS.
12
+ *
13
+ * Les index associations ne portent que le code ; la fiche produit du hub
14
+ * s'ouvre, elle, sur le nom de marque. C'est l'index des avis qui fait le
15
+ * lien, son historique portant le code. Tous les codes partent ensemble, en
16
+ * une agregation : une requete par contribution ferait autant d'allers-retours
17
+ * qu'il y a de contributions. Requete a part : l'index des avis n'est pas un
18
+ * index associations.
19
+ */
20
+ export function useAssociationHasBrands() {
21
+ const { queryCustomElastic } = useOipDrugsElastic();
22
+ const hasIndex = ES_INDEX.BRAND_HAS;
23
+ const brands = ref<Record<string, string>>({});
24
+
25
+ async function load(hasCodes: unknown[]) {
26
+ const codes = [...new Set(hasCodes.filter((code): code is string => typeof code === "string" && code !== ""))].filter(
27
+ (code) => !(code in brands.value),
28
+ );
29
+ if (!codes.length) return;
30
+
31
+ const res: any = await queryCustomElastic({
32
+ index: hasIndex,
33
+ customQuery: {
34
+ size: 0,
35
+ aggs: {
36
+ history: {
37
+ nested: { path: "brand_history" },
38
+ aggs: {
39
+ by_code: {
40
+ // `include` borne l'agregation aux codes demandes : sans lui,
41
+ // `terms` remonterait les codes les plus frequents de l'index.
42
+ terms: { field: "brand_history.has_code.keyword", include: codes, size: codes.length },
43
+ aggs: {
44
+ up: { reverse_nested: {}, aggs: { brand: { terms: { field: "brand_group.keyword", size: 1 } } } },
45
+ },
46
+ },
47
+ },
48
+ },
49
+ },
50
+ } as any,
51
+ allAnswer: true,
52
+ });
53
+
54
+ const found: Record<string, string> = {};
55
+ (res?.aggregations?.history?.by_code?.buckets ?? []).forEach((bucket: any) => {
56
+ const brand = bucket?.up?.brand?.buckets?.[0]?.key;
57
+ if (brand) found[bucket.key] = brand;
58
+ });
59
+ brands.value = { ...brands.value, ...found };
60
+ }
61
+
62
+ return { brands, load };
63
+ }