@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,658 @@
1
+ // Portage de useElastic de France Augmented Science, pour les fiches du layer
2
+ // (cf. components/oip-science) : memes signatures, mais les requetes passent
3
+ // par le proxy du layer (/api/oip/es, cluster `science`), joignable depuis
4
+ // toutes les apps. Le suivi des listes par `keyName` (resultStore des pages
5
+ // de recherche) n'a pas de sens ici : les resultats reviennent a l'appelant.
6
+
7
+ import { instrumentSearchClause, searchMatchOf } from "./useOipSearchExplain";
8
+
9
+ export default function useOipElastic() {
10
+ const config = useRuntimeConfig();
11
+ //
12
+ // const resultsQuery = ref([] as Array<object>);
13
+ const queryTotalResults = ref(0 as number);
14
+ // const isLoadingQuery = ref(false as Boolean);
15
+
16
+ // MAKE QUEUE
17
+ const queryQueue = ref<Function[]>([]);
18
+ let isProcessingQueue = false;
19
+ let isDisposed = false;
20
+
21
+ // Resolvers of the promises handed out by `addToQueue`. Kept so a scope
22
+ // dispose can settle them instead of leaving awaiting callers hanging on a
23
+ // queue that will never run.
24
+ const pendingResolvers = new Set<() => void>();
25
+
26
+ // In-flight ES requests, aborted on dispose so navigating away doesn't leave
27
+ // requests running against a slow connection.
28
+ const inFlight = new Set<AbortController>();
29
+
30
+ // Clean up queue when the calling component/scope is destroyed
31
+ if (getCurrentScope()) {
32
+ onScopeDispose(() => {
33
+ isDisposed = true;
34
+ queryQueue.value = [];
35
+ isProcessingQueue = false;
36
+ for (const resolve of pendingResolvers) resolve();
37
+ pendingResolvers.clear();
38
+ for (const controller of inFlight) controller.abort();
39
+ inFlight.clear();
40
+ });
41
+ }
42
+
43
+ async function processQueue() {
44
+ if (isProcessingQueue) return;
45
+ isProcessingQueue = true;
46
+
47
+ while (queryQueue.value.length > 0 && !isDisposed) {
48
+ const currentQuery = queryQueue.value.shift();
49
+ if (currentQuery && !isDisposed) {
50
+ try {
51
+ await currentQuery();
52
+ } catch (e) {
53
+ if (!isDisposed) console.error('Error executing query:', e);
54
+ }
55
+ }
56
+ }
57
+
58
+ isProcessingQueue = false;
59
+ }
60
+
61
+ /**
62
+ * Queue a query and return a promise that settles when it has actually run.
63
+ *
64
+ * Callers used to fire-and-forget this, which made their `isLoadingQuery`
65
+ * flag flip back to `false` in the same tick — the loader never showed and
66
+ * the "no results" state rendered while the request was still in flight.
67
+ * The returned promise is what makes `await launchQuery()` meaningful.
68
+ */
69
+ function addToQueue(queryFunction: Function): Promise<void> {
70
+ if (isDisposed) return Promise.resolve();
71
+
72
+ return new Promise<void>((resolve) => {
73
+ const settle = () => {
74
+ if (pendingResolvers.delete(settle)) resolve();
75
+ };
76
+ pendingResolvers.add(settle);
77
+
78
+ queryQueue.value.push(async () => {
79
+ try {
80
+ await queryFunction();
81
+ } finally {
82
+ settle();
83
+ }
84
+ });
85
+
86
+ if (queryQueue.value.length === 1) {
87
+ processQueue();
88
+ }
89
+ });
90
+ }
91
+
92
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
93
+ const filterResults = ref({} as Object);
94
+
95
+ // Last ES failure, so pages can tell "the search failed" from "the search
96
+ // returned nothing". Without it both render as "0 results" and a network
97
+ // outage is indistinguishable from an empty result set.
98
+ const queryError = ref<string | null>(null);
99
+
100
+ // Users are regularly on slow hospital networks. A request with no bound
101
+ // stays in flight for minutes and the page just looks frozen, so every ES
102
+ // call gets an explicit ceiling.
103
+ const ES_TIMEOUT = 20000;
104
+
105
+ // ES calls go through the app's own server proxy (server/api/es) so the
106
+ // cluster domain + ApiKey never reach the browser. Auth is injected server-side.
107
+ const setFetchUrl = (_cluster: string | undefined, index: any) => {
108
+ return `/api/oip/es?cluster=science&index=${encodeURIComponent(index)}`;
109
+ };
110
+
111
+ const setFetchHeaders = (_cluster?: string) => {
112
+ return {};
113
+ };
114
+
115
+ /**
116
+ * Single entry point for every ES request: bounds it in time, aborts it on
117
+ * dispose, and records failures in `queryError`.
118
+ *
119
+ * Returns `null` on failure (never throws) so the ~60 existing call sites
120
+ * keep their current shape. `retry` is pinned to 0 — ofetch would otherwise
121
+ * replay these queries, and they are heavy enough that a retry storm is
122
+ * worse than a clean failure the user can act on.
123
+ *
124
+ * `signal` lets a caller cancel its own request — typeahead uses it to drop
125
+ * the query a previous keystroke started.
126
+ *
127
+ * `timeoutMs` raises the ceiling for deliberately heavy, user-initiated work
128
+ * (an export pulling hundreds of documents). The default suits the
129
+ * interactive queries a user is sitting in front of.
130
+ *
131
+ * Note: ofetch only honours its own `timeout` when no `signal` is passed,
132
+ * so the deadline is enforced here rather than delegated.
133
+ */
134
+ async function esFetch(
135
+ cluster: string | undefined,
136
+ index: string,
137
+ body: any,
138
+ signal?: AbortSignal,
139
+ timeoutMs?: number
140
+ ): Promise<any | null> {
141
+ if (signal?.aborted) return null;
142
+
143
+ const controller = new AbortController();
144
+ inFlight.add(controller);
145
+
146
+ let timedOut = false;
147
+ const timer = setTimeout(() => {
148
+ timedOut = true;
149
+ controller.abort();
150
+ }, timeoutMs ?? ES_TIMEOUT);
151
+
152
+ const onCallerAbort = () => controller.abort();
153
+ signal?.addEventListener('abort', onCallerAbort, { once: true });
154
+
155
+ try {
156
+ const res = await $fetch(setFetchUrl(cluster, index), {
157
+ method: 'POST',
158
+ headers: setFetchHeaders(cluster),
159
+ body,
160
+ signal: controller.signal,
161
+ retry: 0,
162
+ });
163
+ queryError.value = null;
164
+ return res;
165
+ } catch (err: any) {
166
+ // An abort triggered by dispose is expected — the user navigated away.
167
+ if (isDisposed || (err?.name === 'AbortError' && !timedOut)) return null;
168
+
169
+ queryError.value = timedOut
170
+ ? 'timeout'
171
+ : err?.statusCode
172
+ ? `http_${err.statusCode}`
173
+ : 'network';
174
+ console.error('[useOipElastic] query failed', {
175
+ index,
176
+ reason: queryError.value,
177
+ message: err?.message,
178
+ });
179
+ return null;
180
+ } finally {
181
+ clearTimeout(timer);
182
+ signal?.removeEventListener('abort', onCallerAbort);
183
+ inFlight.delete(controller);
184
+ }
185
+ }
186
+
187
+ function removeDuplicateMatchAll(queryArray: any) {
188
+ const hasOtherQueries = queryArray.some(
189
+ (item) => JSON.stringify(item) !== JSON.stringify({ match_all: {} })
190
+ );
191
+
192
+ const uniqueQueryArray = [];
193
+ let matchAllAdded = false;
194
+
195
+ for (const item of queryArray) {
196
+ if (JSON.stringify(item) === JSON.stringify({ match_all: {} })) {
197
+ if (hasOtherQueries) continue;
198
+
199
+ if (!matchAllAdded) {
200
+ uniqueQueryArray.push(item);
201
+ matchAllAdded = true;
202
+ }
203
+ } else {
204
+ uniqueQueryArray.push(item);
205
+ }
206
+ }
207
+
208
+ return uniqueQueryArray;
209
+ }
210
+
211
+ // Helper function to get sort values
212
+ function getSortValues(sortQuery: any): any[] {
213
+ if (!sortQuery) return [];
214
+ return sortQuery.values || [];
215
+ }
216
+
217
+ async function queryElastic({
218
+ index,
219
+ take,
220
+ skip,
221
+ customQuery,
222
+ sortQuery,
223
+ keyName,
224
+ source,
225
+ highlight,
226
+ with_id,
227
+ cluster,
228
+ mustNot,
229
+ timeout,
230
+ explainFilter,
231
+ }: {
232
+ index: string;
233
+ take: any;
234
+ skip?: number;
235
+ customQuery: any;
236
+ sortQuery?: any;
237
+ keyName?: any;
238
+ source?: any;
239
+ highlight?: any;
240
+ with_id?: boolean;
241
+ cluster?: string;
242
+ mustNot?: any;
243
+ // Raise the deadline for heavy, user-initiated pulls (exports).
244
+ timeout?: number;
245
+ // Filtre de la barre de recherche a instrumenter pour le « Voir pourquoi »
246
+ // (cf. useSearchExplain). Chaque resultat garde alors son `_match`.
247
+ explainFilter?: string;
248
+ }) {
249
+ let esResults: any = {
250
+ results: [],
251
+ total: 0,
252
+ };
253
+
254
+ // isLoadingQuery.value = true;
255
+
256
+ // Ensure customQuery is always an array
257
+ const customQueryArray = Array.isArray(customQuery) ? customQuery : [customQuery];
258
+
259
+ let explainHighlight: any;
260
+ const queryRaw = customQueryArray
261
+ .map((obj: any) => {
262
+ if (!explainFilter || obj?.filter !== explainFilter) return obj.values;
263
+ const { query, highlight } = instrumentSearchClause(obj.values);
264
+ explainHighlight = highlight;
265
+ return query;
266
+ })
267
+ .filter((item: any) => item && item !== undefined);
268
+
269
+ // Sans explication, le resultat reste le `_source` tel quel.
270
+ const sourceOf = (x: any) => {
271
+ if (!explainFilter) return x._source;
272
+ const match = searchMatchOf(x);
273
+ return match ? { ...x._source, _match: match } : x._source;
274
+ };
275
+
276
+ const queryMustValues = removeDuplicateMatchAll(queryRaw);
277
+
278
+ // QUERY
279
+ let query: any = {
280
+ from: skip ? skip : 0,
281
+ _source: source ? source : true,
282
+ query: {
283
+ bool: {
284
+ must: queryMustValues,
285
+ must_not: mustNot ? mustNot : [],
286
+ },
287
+ },
288
+
289
+ highlight: highlight ? highlight : explainHighlight ?? {},
290
+ size: take ? (take.values ? take.values : take) : 10,
291
+ sort: getSortValues(sortQuery),
292
+ track_total_hits: true,
293
+ };
294
+
295
+ // FETCH
296
+ const res: any = await esFetch(cluster, index, query, undefined, timeout);
297
+
298
+ if (!isDisposed && res) {
299
+ {
300
+ // OTHERWISE USE resultsQuery as result
301
+
302
+ if (!with_id) {
303
+ esResults.results = res.hits.hits.map(sourceOf);
304
+ esResults.total = res.hits.total.value;
305
+ }
306
+
307
+ if (with_id) {
308
+ esResults.results = res.hits.hits.map((x: any) => x);
309
+ esResults.total = res.hits.total.value;
310
+ }
311
+ }
312
+ }
313
+
314
+ return esResults;
315
+ }
316
+
317
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
318
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
319
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
320
+
321
+ async function queryOneElastic({
322
+ index,
323
+ path,
324
+ id,
325
+ source,
326
+ cluster,
327
+ }: {
328
+ index: string;
329
+ path: any;
330
+ id: any;
331
+ source?: any;
332
+ cluster?: string;
333
+ }) {
334
+ let esResults: object = {};
335
+
336
+ // QUERY
337
+ let query: any = {
338
+ from: 0,
339
+ _source: source ? source : [],
340
+ query: {
341
+ bool: {
342
+ must: [{ match: { [`${path}`]: id } }],
343
+ },
344
+ },
345
+
346
+ size: 1,
347
+
348
+ track_total_hits: true,
349
+ };
350
+
351
+ // FETCH
352
+ const res: any = await esFetch(cluster, index, query);
353
+
354
+ if (res) {
355
+ esResults = res.hits.hits.map((x: any) => x._source)[0];
356
+ }
357
+
358
+ return esResults;
359
+ }
360
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
361
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
362
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
363
+
364
+ async function queryCustomElastic({
365
+ index,
366
+ customQuery,
367
+ onlyOne,
368
+ cluster,
369
+
370
+ allAnswer,
371
+ with_id,
372
+ signal,
373
+ timeout,
374
+ }: {
375
+ index: string;
376
+ customQuery: any;
377
+ onlyOne?: boolean;
378
+ cluster?: string;
379
+ allAnswer?: boolean;
380
+ with_id?: boolean;
381
+ // Lets the caller cancel a superseded request (typeahead).
382
+ signal?: AbortSignal;
383
+ // Raise the deadline for heavy, user-initiated pulls (exports).
384
+ timeout?: number;
385
+ }) {
386
+ let esResults: any = {
387
+ results: [],
388
+ total: 0,
389
+ };
390
+
391
+ // QUERY
392
+ let query = customQuery;
393
+
394
+ // FETCH
395
+ const res: any = await esFetch(cluster, index, query, signal, timeout);
396
+
397
+ if (!isDisposed && res) {
398
+ if (onlyOne) {
399
+ esResults = res.hits.hits.map((x: any) => (with_id ? x : x._source))[0];
400
+ } else if (allAnswer) {
401
+ esResults = res;
402
+ } else {
403
+ esResults.results = res.hits.hits.map((x: any) =>
404
+ with_id ? x : x._source
405
+ );
406
+ // ES drops `hits.total` (or changes its shape) when the caller
407
+ // sets `track_total_hits: false`. Treat any non-object as the
408
+ // raw count and fall back to 0 when the field is absent.
409
+ const t = res.hits.total;
410
+ esResults.total = typeof t === 'number' ? t : (t?.value ?? 0);
411
+ }
412
+ }
413
+
414
+ return esResults;
415
+ }
416
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
417
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
418
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
419
+
420
+ async function queryFilter({
421
+ index,
422
+ filters,
423
+ take,
424
+ customQuery,
425
+ cluster,
426
+ mustNot,
427
+ }: {
428
+ index: string;
429
+ filters: any;
430
+ take: number;
431
+ customQuery: any;
432
+ cluster?: string;
433
+ mustNot?: any;
434
+ }) {
435
+ let esResults: object = {};
436
+
437
+ // Ensure customQuery is always an array
438
+ const customQueryArray = Array.isArray(customQuery) ? customQuery : [customQuery];
439
+
440
+ const queryMustValues = customQueryArray
441
+ .filter((x: any) => x.filter != filters.name)
442
+ .map((obj: any) => obj.values)
443
+ .filter((item: any) => item && item !== undefined);
444
+
445
+ // QUERY
446
+ let query = {
447
+ from: 0,
448
+ query: {
449
+ bool: {
450
+ must: queryMustValues,
451
+ must_not: mustNot ? mustNot : [],
452
+ },
453
+ },
454
+
455
+ aggs: filters.agg,
456
+
457
+ size: 0,
458
+ track_total_hits: true,
459
+ };
460
+
461
+ // FETCH. FAS chose the aggregation shape from the current route (/pubmed,
462
+ // /euct); a layer view has no route of its own, the index says the same.
463
+ const currentPath = index.includes('pubmed') ? '/pubmed' : index.includes('euct') ? '/euct' : '';
464
+ const res: any = await esFetch(cluster, index, query);
465
+
466
+ if (!isDisposed && res) {
467
+ try {
468
+ if(filters.path === 'diseases.icd10.combined_label' && currentPath.includes('pubmed')){
469
+ esResults = res.aggregations['diseases.icd10'][
470
+ 'diseases.icd10.combined_label'
471
+ ].buckets.map((x: { key: any }) => x.key);
472
+
473
+ }
474
+ else if(filters.path === 'diseases.icd10.combined_label' && currentPath.includes('euct')){
475
+ esResults = res.aggregations['diseases.icd10.combined_label'].buckets.map((x: { key: any }) => x.key);
476
+
477
+ }
478
+ else if(filters.path === 'diseases_nested.icd10_combined_label' && currentPath.includes('euct')){
479
+ esResults = res.aggregations['diseases_nested']['icd10_combined_label'].buckets.map((x: { key: any }) => x.key);
480
+
481
+ }
482
+ else if(filters.path === 'diseases.icd10.combined_label'){
483
+ esResults = res.aggregations['diseases'][
484
+ 'diseases.icd10.combined_label'
485
+ ].buckets.map((x: { key: any }) => x.key);
486
+
487
+ } else if(filters.path === 'diseases_nested.icd10_nested.icd10_combined_label'){
488
+ esResults = res.aggregations['diseases_nested'][
489
+ 'icd10_nested'
490
+ ].icd10_combined_label.buckets.map((x: { key: any }) => x.key);
491
+ } else if(filters.path === 'diseases.record_diseases.section_label'){
492
+ esResults = res.aggregations.diseases.record_diseases.section_label.buckets.map((x: { key: any }) => x.key);
493
+ }
494
+ else if (filters.isNested) {
495
+ esResults = res.aggregations[filters.path.split('.')[0]][
496
+ filters.path
497
+ ].buckets.map((x: { key: any }) => x.key);
498
+ } else {
499
+ esResults = res.aggregations[filters.path].buckets.map(
500
+ (x: { key: any }) => x.key
501
+ );
502
+ }
503
+ } catch (err: any) {
504
+ // The aggregation came back in a shape this branch didn't expect —
505
+ // a mapping change, not a network problem. Report it as such rather
506
+ // than letting it read as an empty filter.
507
+ queryError.value = 'unexpected_aggregation';
508
+ console.error('[useOipElastic] queryFilter: unexpected aggregation shape', {
509
+ index,
510
+ path: filters.path,
511
+ message: err?.message,
512
+ });
513
+ }
514
+ }
515
+
516
+ return esResults;
517
+ }
518
+
519
+ async function queryAggregations({
520
+ index,
521
+ queryFilter,
522
+ aggs,
523
+ cluster,
524
+ isNested,
525
+ allAnswer,
526
+ }: {
527
+ index: string;
528
+ queryFilter: any;
529
+ aggs: any;
530
+ cluster?: string;
531
+ isNested?: boolean;
532
+ allAnswer?: boolean;
533
+ }) {
534
+ let esResults: object = {};
535
+
536
+ // QUERY
537
+ let query: any = {
538
+ from: 0,
539
+ query: queryFilter,
540
+ aggs: aggs,
541
+ size: 0,
542
+ track_total_hits: true,
543
+ };
544
+
545
+ const aggsKey = Object.keys(aggs)[0];
546
+
547
+ // FETCH
548
+ const res: any = await esFetch(cluster, index, query);
549
+
550
+ if (!isDisposed && res) {
551
+ if (allAnswer) {
552
+ esResults = res.aggregations;
553
+ } else {
554
+ if (isNested) {
555
+ esResults = res.aggregations[aggsKey][aggsKey].buckets.map(
556
+ (x: { key: any }) => x.key
557
+ );
558
+ } else {
559
+ esResults = res.aggregations[aggsKey].buckets.map(
560
+ (x: { key: any }) => x.key
561
+ );
562
+ }
563
+ }
564
+ }
565
+
566
+ return esResults;
567
+ }
568
+
569
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
570
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
571
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
572
+
573
+ async function querySearchFilter({
574
+ index,
575
+ filters,
576
+ take,
577
+ customQuery,
578
+ cluster,
579
+ mustNot,
580
+ }: {
581
+ index: string;
582
+ filters: any;
583
+ take: number;
584
+ customQuery: any;
585
+ cluster?: string;
586
+ mustNot?: any;
587
+ }) {
588
+ let esResults: object = {};
589
+
590
+ // Ensure customQuery is always an array
591
+ const customQueryArray = Array.isArray(customQuery) ? customQuery : [customQuery];
592
+
593
+ const queryMustValues = customQueryArray
594
+ .filter((x: any) => x.filter != filters.name)
595
+ .map((obj: any) => obj.values)
596
+ .filter((item: any) => item !== undefined);
597
+
598
+ let query: any = {
599
+ from: 0,
600
+ query: {
601
+ bool: {
602
+ must: [
603
+ {
604
+ bool: {
605
+ must: queryMustValues,
606
+ },
607
+ },
608
+ filters.searchQuery,
609
+ ],
610
+ must_not: mustNot ? mustNot : [],
611
+ },
612
+ },
613
+
614
+ aggs: filters.agg,
615
+
616
+ size: 0,
617
+
618
+ track_total_hits: true,
619
+ };
620
+
621
+ // FETCH
622
+ const res: any = await esFetch(cluster, index, query);
623
+
624
+ if (!isDisposed && res) {
625
+ if (filters.isNested) {
626
+ esResults = res.aggregations[filters.path][filters.path].buckets.map(
627
+ (x: { key: any }) => x.key
628
+ );
629
+ } else {
630
+ esResults = res.aggregations[filters.path].buckets.map(
631
+ (x: { key: any }) => x.key
632
+ );
633
+ }
634
+ }
635
+
636
+ return esResults;
637
+ }
638
+
639
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
640
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
641
+ return {
642
+ queryElastic,
643
+
644
+ // isLoadingQuery,
645
+ queryTotalResults,
646
+ // resultsQuery,
647
+ // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
648
+ queryFilter,
649
+ filterResults,
650
+ queryCustomElastic,
651
+ querySearchFilter,
652
+
653
+ queryOneElastic,
654
+ queryAggregations,
655
+ addToQueue,
656
+ queryError,
657
+ };
658
+ }