@tekkare/romulus 0.1.250 → 0.1.252

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/BarChart.vue +1 -0
  3. package/dist/runtime/components/ChoroplethMap.vue +4 -1
  4. package/dist/runtime/internal/geo/fr-regions.json +1 -1
  5. package/dist/runtime/utils/chart.d.ts +6 -3
  6. package/dist/runtime/utils/chart.js +15 -2
  7. package/layer/app/app.config.ts +6 -0
  8. package/layer/app/components/OipPubmedSummary.vue +223 -0
  9. package/layer/app/components/OipResourceDrawer.vue +331 -0
  10. package/layer/app/components/OipResourceDrawerLink.vue +40 -0
  11. package/layer/app/components/OipResourceField.vue +41 -0
  12. package/layer/app/components/OipResourceGenericCard.vue +54 -0
  13. package/layer/app/components/OipResourceSource.vue +50 -0
  14. package/layer/app/components/OipResourceTags.vue +65 -0
  15. package/layer/app/components/OipResourceView.vue +107 -0
  16. package/layer/app/components/oip-drugs/fiches/AssociationFiche.vue +775 -0
  17. package/layer/app/components/oip-drugs/fiches/BrandFiche.vue +1111 -0
  18. package/layer/app/components/oip-drugs/fiches/IndicationFiche.vue +182 -0
  19. package/layer/app/components/oip-drugs/fiches/JoFiche.vue +101 -0
  20. package/layer/app/components/oip-drugs/fiches/PndsFiche.vue +279 -0
  21. package/layer/app/components/oip-drugs/heart/JoCard.vue +314 -0
  22. package/layer/app/components/oip-drugs/heart/TextHighlight.vue +326 -0
  23. package/layer/app/components/oip-drugs/heart/ansm/BrandCard.vue +187 -0
  24. package/layer/app/components/oip-drugs/heart/ansm/CisDetail.vue +363 -0
  25. package/layer/app/components/oip-drugs/heart/ansm/CisTable.vue +167 -0
  26. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyCard.vue +373 -0
  27. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyFiche.vue +372 -0
  28. package/layer/app/components/oip-drugs/heart/associations/TranscriptionCard.vue +55 -0
  29. package/layer/app/components/oip-drugs/heart/elastic/SourceContainerElastic.vue +103 -0
  30. package/layer/app/components/oip-drugs/heart/france/BrandGroupCard.vue +450 -0
  31. package/layer/app/components/oip-drugs/heart/mitm/Card.vue +199 -0
  32. package/layer/app/components/oip-drugs/heart/mitm/Source.vue +23 -0
  33. package/layer/app/components/oip-drugs/heart/mitm/details/Overview.vue +381 -0
  34. package/layer/app/components/oip-drugs/heart/put/Card.vue +212 -0
  35. package/layer/app/components/oip-drugs/heart/results/AccesCard.vue +696 -0
  36. package/layer/app/components/oip-drugs/heart/results/AlertCard.vue +342 -0
  37. package/layer/app/components/oip-drugs/heart/results/AsmrCursor.vue +229 -0
  38. package/layer/app/components/oip-drugs/heart/results/EvalCard.vue +370 -0
  39. package/layer/app/components/oip-drugs/heart/results/IndicCard.vue +743 -0
  40. package/layer/app/components/oip-drugs/heart/results/MatchExplain.vue +243 -0
  41. package/layer/app/components/oip-drugs/heart/results/PrefRows.vue +105 -0
  42. package/layer/app/components/oip-drugs/heart/results/details/HasHistory.vue +487 -0
  43. package/layer/app/components/oip-drugs/heart/results/details/InternationalCountryCard.vue +461 -0
  44. package/layer/app/components/oip-drugs/heart/results/details/InternationalOverview.vue +200 -0
  45. package/layer/app/components/oip-drugs/heart/results/details/InternationalPriceDrawer.vue +647 -0
  46. package/layer/app/components/oip-drugs/heart/results/details/Monograph.vue +128 -0
  47. package/layer/app/components/oip-drugs/heart/results/details/OpinionDrawer.vue +535 -0
  48. package/layer/app/components/oip-drugs/heart/results/details/PresentationDrawer.vue +738 -0
  49. package/layer/app/components/oip-drugs/heart/results/details/Price.vue +227 -0
  50. package/layer/app/components/oip-drugs/heart/results/details/PriceOverview.vue +1261 -0
  51. package/layer/app/components/oip-drugs/heart/results/details/RecordDrawer.vue +494 -0
  52. package/layer/app/components/oip-drugs/heart/results/details/RecordList.vue +426 -0
  53. package/layer/app/components/oip-drugs/heart/results/les/Card.vue +340 -0
  54. package/layer/app/components/oip-drugs/heart/results/les/Drawer.vue +67 -0
  55. package/layer/app/components/oip-drugs/heart/results/les/IndicationDetail.vue +149 -0
  56. package/layer/app/components/oip-drugs/heart/results/les/Source.vue +20 -0
  57. package/layer/app/components/oip-drugs/heart/results/les/SourceTag.vue +39 -0
  58. package/layer/app/components/oip-drugs/heart/searchPage/DynamicSource.vue +29 -0
  59. package/layer/app/components/oip-drugs/medicam/market/Chart.vue +121 -0
  60. package/layer/app/components/oip-drugs/outpatient/ProductEvolution.vue +236 -0
  61. package/layer/app/components/oip-drugs/resources/Ansm.vue +75 -0
  62. package/layer/app/components/oip-drugs/resources/Association.vue +30 -0
  63. package/layer/app/components/oip-drugs/resources/Brand.vue +65 -0
  64. package/layer/app/components/oip-drugs/resources/Contribution.vue +216 -0
  65. package/layer/app/components/oip-drugs/resources/Hospital.vue +151 -0
  66. package/layer/app/components/oip-drugs/resources/Indication.vue +54 -0
  67. package/layer/app/components/oip-drugs/resources/Jo.vue +57 -0
  68. package/layer/app/components/oip-drugs/resources/Medicam.vue +140 -0
  69. package/layer/app/components/oip-drugs/resources/Mitm.vue +84 -0
  70. package/layer/app/components/oip-drugs/resources/NationalAssembly.vue +93 -0
  71. package/layer/app/components/oip-drugs/resources/Outpatient.vue +97 -0
  72. package/layer/app/components/oip-drugs/resources/Pnds.vue +24 -0
  73. package/layer/app/components/oip-drugs/resources/Price.vue +106 -0
  74. package/layer/app/components/oip-drugs/resources/Put.vue +143 -0
  75. package/layer/app/components/oip-science/appel-a-projet/Cards.vue +162 -0
  76. package/layer/app/components/oip-science/clinical-trials/Cards.vue +382 -0
  77. package/layer/app/components/oip-science/clinical-trials/CardsGlobal.vue +273 -0
  78. package/layer/app/components/oip-science/clinical-trials/LinkedList.vue +366 -0
  79. package/layer/app/components/oip-science/clinical-trials/PairBadge.vue +25 -0
  80. package/layer/app/components/oip-science/clinical-trials/PairBody.vue +182 -0
  81. package/layer/app/components/oip-science/clinical-trials/SitesDrawer.vue +95 -0
  82. package/layer/app/components/oip-science/common/Form.vue +73 -0
  83. package/layer/app/components/oip-science/common/MatchAvgTag.vue +283 -0
  84. package/layer/app/components/oip-science/common/NoData.vue +16 -0
  85. package/layer/app/components/oip-science/common/ScoreBadge.vue +35 -0
  86. package/layer/app/components/oip-science/common/Source.vue +31 -0
  87. package/layer/app/components/oip-science/common/TableTags.vue +279 -0
  88. package/layer/app/components/oip-science/common/Timeline.vue +104 -0
  89. package/layer/app/components/oip-science/common/ToolTips.vue +89 -0
  90. package/layer/app/components/oip-science/cross/publicationPubmed.vue +484 -0
  91. package/layer/app/components/oip-science/crossData/CrossDataLocations.vue +71 -0
  92. package/layer/app/components/oip-science/ctgov/Investigators.vue +148 -0
  93. package/layer/app/components/oip-science/ctgov/Locations.vue +102 -0
  94. package/layer/app/components/oip-science/ctgov/Publications.vue +52 -0
  95. package/layer/app/components/oip-science/ctgov/Sponsors.vue +66 -0
  96. package/layer/app/components/oip-science/ctgov/StudyDescription.vue +1136 -0
  97. package/layer/app/components/oip-science/ctgov-centers/ClinicalTrials.vue +40 -0
  98. package/layer/app/components/oip-science/ctgov-centers/Publications.vue +50 -0
  99. package/layer/app/components/oip-science/ctgov-investigators/ClinicalTrials.vue +43 -0
  100. package/layer/app/components/oip-science/ctgov-investigators/Informations.vue +171 -0
  101. package/layer/app/components/oip-science/ctgov-investigators/Publications.vue +44 -0
  102. package/layer/app/components/oip-science/elastic/DateRangeFilter.vue +209 -0
  103. package/layer/app/components/oip-science/elastic/Filter.vue +719 -0
  104. package/layer/app/components/oip-science/elastic/MatchExplain.vue +152 -0
  105. package/layer/app/components/oip-science/establishments/Cards.vue +183 -0
  106. package/layer/app/components/oip-science/establishments/ClinicalTrials.vue +62 -0
  107. package/layer/app/components/oip-science/establishments/Hcps.vue +227 -0
  108. package/layer/app/components/oip-science/establishments/Informations.vue +263 -0
  109. package/layer/app/components/oip-science/establishments/LinkedList.vue +153 -0
  110. package/layer/app/components/oip-science/establishments/MatchedLabels.vue +194 -0
  111. package/layer/app/components/oip-science/establishments/PublicFinance.vue +62 -0
  112. package/layer/app/components/oip-science/euct/Investigators.vue +125 -0
  113. package/layer/app/components/oip-science/euct/Locations.vue +102 -0
  114. package/layer/app/components/oip-science/euct/Products.vue +101 -0
  115. package/layer/app/components/oip-science/euct/Publications.vue +89 -0
  116. package/layer/app/components/oip-science/euct/Sponsors.vue +86 -0
  117. package/layer/app/components/oip-science/euct/StudyDescription.vue +1075 -0
  118. package/layer/app/components/oip-science/euct-centers/ClinicalTrials.vue +40 -0
  119. package/layer/app/components/oip-science/euct-centers/Publications.vue +55 -0
  120. package/layer/app/components/oip-science/euct-investigators/ClinicalTrials.vue +41 -0
  121. package/layer/app/components/oip-science/euct-investigators/Informations.vue +171 -0
  122. package/layer/app/components/oip-science/euct-investigators/Publications.vue +51 -0
  123. package/layer/app/components/oip-science/hcps/AppelsAProjet.vue +62 -0
  124. package/layer/app/components/oip-science/hcps/Cards.vue +237 -0
  125. package/layer/app/components/oip-science/hcps/ClinicalTrials.vue +78 -0
  126. package/layer/app/components/oip-science/hcps/Informations.vue +296 -0
  127. package/layer/app/components/oip-science/hcps/LinkedList.vue +153 -0
  128. package/layer/app/components/oip-science/hcps/MatchedLabels.vue +207 -0
  129. package/layer/app/components/oip-science/hcps/Pubmed.vue +128 -0
  130. package/layer/app/components/oip-science/local/SliderTabs.vue +99 -0
  131. package/layer/app/components/oip-science/local/SliderTabsFull.vue +135 -0
  132. package/layer/app/components/oip-science/pubmed/Authors.vue +128 -0
  133. package/layer/app/components/oip-science/pubmed/Cards.vue +417 -0
  134. package/layer/app/components/oip-science/pubmed/ClinicalTrials.vue +71 -0
  135. package/layer/app/components/oip-science/resources/Phrc.vue +39 -0
  136. package/layer/app/composables/useOipChapterScroll.ts +43 -0
  137. package/layer/app/composables/useOipResourceHooks.ts +27 -0
  138. package/layer/app/composables/useOipResourceNav.ts +66 -0
  139. package/layer/app/composables/useOipResourceViews.ts +117 -0
  140. package/layer/app/composables/useOipResources.ts +83 -0
  141. package/layer/app/oip-drugs/PORTING.md +120 -0
  142. package/layer/app/oip-drugs/composables/useAssociationDetail.ts +220 -0
  143. package/layer/app/oip-drugs/composables/useAssociationExport.ts +63 -0
  144. package/layer/app/oip-drugs/composables/useAssociationLookup.ts +105 -0
  145. package/layer/app/oip-drugs/composables/useCardPrefOptions.ts +201 -0
  146. package/layer/app/oip-drugs/composables/useCurrency.ts +121 -0
  147. package/layer/app/oip-drugs/composables/useDirectusSources.ts +224 -0
  148. package/layer/app/oip-drugs/composables/useHeartBrandMatches.ts +112 -0
  149. package/layer/app/oip-drugs/composables/useHeartDetailSearches.ts +479 -0
  150. package/layer/app/oip-drugs/composables/useHeartIntlApprovals.ts +661 -0
  151. package/layer/app/oip-drugs/composables/useHeartLesIndication.ts +168 -0
  152. package/layer/app/oip-drugs/composables/useHeartMatchExplain.ts +231 -0
  153. package/layer/app/oip-drugs/composables/useHeartReports.ts +239 -0
  154. package/layer/app/oip-drugs/composables/useHeartSearchFields.ts +233 -0
  155. package/layer/app/oip-drugs/composables/useMergeByKey.ts +23 -0
  156. package/layer/app/oip-drugs/composables/useOipDrugsElastic.ts +621 -0
  157. package/layer/app/oip-drugs/composables/usePageEmpty.ts +58 -0
  158. package/layer/app/oip-drugs/composables/v-useSalesSource.ts +64 -0
  159. package/layer/app/oip-drugs/drugs.css +500 -0
  160. package/layer/app/oip-drugs/esIndices.ts +322 -0
  161. package/layer/app/oip-drugs/kinds.ts +264 -0
  162. package/layer/app/oip-drugs/locales/en.json +1571 -0
  163. package/layer/app/oip-drugs/locales/fr.json +1571 -0
  164. package/layer/app/oip-drugs/monographs/blincyto-has.vue +289 -0
  165. package/layer/app/oip-drugs/monographs/darzalex-has.vue +276 -0
  166. package/layer/app/oip-drugs/monographs/dupixent-has.vue +216 -0
  167. package/layer/app/oip-drugs/monographs/enhertu-has.vue +280 -0
  168. package/layer/app/oip-drugs/monographs/has-sein-2023.vue +1649 -0
  169. package/layer/app/oip-drugs/monographs/ibrance-has.vue +277 -0
  170. package/layer/app/oip-drugs/monographs/imfinzi-has.vue +293 -0
  171. package/layer/app/oip-drugs/monographs/keytruda-has.vue +334 -0
  172. package/layer/app/oip-drugs/monographs/kisqali-has.vue +201 -0
  173. package/layer/app/oip-drugs/monographs/opdivo-has.vue +231 -0
  174. package/layer/app/oip-drugs/monographs/ozempic-has.vue +186 -0
  175. package/layer/app/oip-drugs/monographs/tecentriq-has.vue +312 -0
  176. package/layer/app/oip-drugs/monographs/yescarta-has.vue +272 -0
  177. package/layer/app/oip-drugs/types/elastic.ts +113 -0
  178. package/layer/app/oip-drugs/types/es/agencies.ts +152 -0
  179. package/layer/app/oip-drugs/types/es/directory.ts +54 -0
  180. package/layer/app/oip-drugs/types/es/docByIndex.ts +151 -0
  181. package/layer/app/oip-drugs/types/es/openmedic.ts +67 -0
  182. package/layer/app/oip-drugs/types/es/outpatient.ts +71 -0
  183. package/layer/app/oip-drugs/types/es/portal.ts +130 -0
  184. package/layer/app/oip-drugs/types/es/retail.ts +107 -0
  185. package/layer/app/oip-drugs/types/es/statistics.ts +29 -0
  186. package/layer/app/oip-drugs/types/heart/CurrencyRate.ts +16 -0
  187. package/layer/app/oip-drugs/types/heart/ansm.ts +106 -0
  188. package/layer/app/oip-drugs/types/heart/tag.ts +29 -0
  189. package/layer/app/oip-drugs/utils/associationFields.ts +118 -0
  190. package/layer/app/oip-drugs/utils/contributions.ts +36 -0
  191. package/layer/app/oip-drugs/utils/countryMapping.ts +387 -0
  192. package/layer/app/oip-drugs/utils/esHighlight.ts +21 -0
  193. package/layer/app/oip-drugs/utils/exportSheets.ts +57 -0
  194. package/layer/app/oip-drugs/utils/heartIntlPrice.ts +228 -0
  195. package/layer/app/oip-drugs/utils/heartStyleAndFormat.ts +262 -0
  196. package/layer/app/oip-drugs/utils/heartStyleAnsm.ts +71 -0
  197. package/layer/app/oip-drugs/utils/latestDate.ts +31 -0
  198. package/layer/app/oip-drugs/utils/p-heartCardTags.ts +53 -0
  199. package/layer/app/oip-drugs/utils/priceExport.ts +269 -0
  200. package/layer/app/oip-drugs/utils/q-hasRatings.ts +43 -0
  201. package/layer/app/oip-drugs/utils/s-openCard.ts +20 -0
  202. package/layer/app/oip-drugs/utils/sourceUuids.ts +155 -0
  203. package/layer/app/oip-drugs/utils/styleAndFormat.ts +40 -0
  204. package/layer/app/oip-drugs/utils/v-salesSeries.ts +156 -0
  205. package/layer/app/oip-drugs/utils/v-scopeTags.ts +23 -0
  206. package/layer/app/oip-science/PORTING.md +93 -0
  207. package/layer/app/oip-science/assets/smartSelection.json +428 -0
  208. package/layer/app/oip-science/composables/useAuthorMatching.ts +342 -0
  209. package/layer/app/oip-science/composables/useCTCenter.ts +107 -0
  210. package/layer/app/oip-science/composables/useCTInvestigators.ts +107 -0
  211. package/layer/app/oip-science/composables/useEstablishmentCrossData.ts +139 -0
  212. package/layer/app/oip-science/composables/useEstablishmentMatching.ts +176 -0
  213. package/layer/app/oip-science/composables/useEuct.ts +418 -0
  214. package/layer/app/oip-science/composables/useEuctCenter.ts +107 -0
  215. package/layer/app/oip-science/composables/useEuctInvestigators.ts +107 -0
  216. package/layer/app/oip-science/composables/useGlobalClinicalTrials.ts +624 -0
  217. package/layer/app/oip-science/composables/useLinkedTrials.ts +83 -0
  218. package/layer/app/oip-science/composables/useOipElastic.ts +658 -0
  219. package/layer/app/oip-science/composables/useOipSearchExplain.ts +166 -0
  220. package/layer/app/oip-science/composables/usePubmed.ts +487 -0
  221. package/layer/app/oip-science/composables/useSearchFields.ts +153 -0
  222. package/layer/app/oip-science/indexConfig.json +794 -0
  223. package/layer/app/oip-science/locales/en.json +483 -0
  224. package/layer/app/oip-science/locales/fr.json +483 -0
  225. package/layer/app/oip-science/science.css +1182 -0
  226. package/layer/app/oip-science/utils/a-openCard.ts +23 -0
  227. package/layer/app/oip-science/utils/b-openCard.ts +35 -0
  228. package/layer/app/oip-science/utils/c-filterContext.ts +81 -0
  229. package/layer/app/oip-science/utils/f-types.ts +241 -0
  230. package/layer/app/oip-science/utils/frDiseaseTerms.ts +269 -0
  231. package/layer/app/oip-science/utils/matchMethod.ts +53 -0
  232. package/layer/app/oip-science/utils/sliderTabs.ts +13 -0
  233. package/layer/app/oip-science/utils/sources.ts +15 -0
  234. package/layer/app/oip-science/utils/styleAndFormat.ts +608 -0
  235. package/layer/app/oip-science/utils/toPresets.ts +7 -0
  236. package/layer/app/oip-science/utils/valueTable.ts +126 -0
  237. package/layer/app/plugins/oip-science-i18n.ts +17 -0
  238. package/layer/app/utils/oipFicheRoutes.ts +118 -0
  239. package/layer/app/utils/oipResources.ts +440 -0
  240. package/layer/nuxt.config.ts +22 -2
  241. package/layer/public/reports/blincyto-has.html +292 -0
  242. package/layer/public/reports/blincyto-has.pdf +0 -0
  243. package/layer/public/reports/darzalex-has.html +279 -0
  244. package/layer/public/reports/darzalex-has.pdf +0 -0
  245. package/layer/public/reports/dupixent-has.html +217 -0
  246. package/layer/public/reports/dupixent-has.pdf +0 -0
  247. package/layer/public/reports/enhertu-has.html +283 -0
  248. package/layer/public/reports/enhertu-has.pdf +0 -0
  249. package/layer/public/reports/has-sein-2023.html +1639 -0
  250. package/layer/public/reports/ibrance-has.html +280 -0
  251. package/layer/public/reports/ibrance-has.pdf +0 -0
  252. package/layer/public/reports/imfinzi-has.html +297 -0
  253. package/layer/public/reports/imfinzi-has.pdf +0 -0
  254. package/layer/public/reports/keytruda-has.html +337 -0
  255. package/layer/public/reports/keytruda-has.pdf +0 -0
  256. package/layer/public/reports/kisqali-has.html +202 -0
  257. package/layer/public/reports/kisqali-has.pdf +0 -0
  258. package/layer/public/reports/opdivo-has.html +232 -0
  259. package/layer/public/reports/opdivo-has.pdf +0 -0
  260. package/layer/public/reports/ozempic-has.html +188 -0
  261. package/layer/public/reports/ozempic-has.pdf +0 -0
  262. package/layer/public/reports/tecentriq-has.html +315 -0
  263. package/layer/public/reports/tecentriq-has.pdf +0 -0
  264. package/layer/public/reports/yescarta-has.html +275 -0
  265. package/layer/public/reports/yescarta-has.pdf +0 -0
  266. package/layer/server/api/oip/es.post.ts +99 -0
  267. package/layer/server/api/oip/pdf.get.ts +70 -0
  268. package/layer/server/api/oip/reports.get.ts +98 -0
  269. package/layer/server/api/oip/resource-types.get.ts +22 -0
  270. package/package.json +1 -1
@@ -0,0 +1,166 @@
1
+ /**
2
+ * « Voir pourquoi » : ou les termes de la recherche ont ete trouves.
3
+ *
4
+ * L'explication vient d'Elasticsearch, pas d'une reconstitution cote client.
5
+ * La clause de la barre de recherche est instrumentee au moment ou la page
6
+ * lance sa liste : chaque clause nested recoit des `inner_hits` nommes
7
+ * `search:<famille>` (qui comptent les objets matches et surlignent l'extrait),
8
+ * et les champs plats un `highlight` a la racine. Le magasin de requetes, les
9
+ * agregations, les alertes et les suggestions gardent la clause nue.
10
+ */
11
+
12
+ import { bareSearchField, searchFieldGroup, searchFieldGroupByKey } from "./useSearchFields";
13
+
14
+ export const SEARCH_EXPLAIN_PREFIX = "search:";
15
+
16
+ export type SearchMatchField = {
17
+ key: string;
18
+ label: string;
19
+ /** Objets nested matches (conditions, auteurs...) ; 0 sur un champ plat. */
20
+ count: number;
21
+ /** Extraits surlignes en `<mark>`, tels que renvoyes par le moteur. */
22
+ fragments: string[];
23
+ };
24
+
25
+ /** Ce qu'un hit porte d'explication, garde a cote de son `_source`. */
26
+ export type SearchMatch = { highlight?: Record<string, string[]>; inner_hits?: Record<string, any> };
27
+
28
+ const highlightBlock = (fields: string[]) => ({
29
+ pre_tags: ["<mark>"],
30
+ post_tags: ["</mark>"],
31
+ fields: Object.fromEntries(
32
+ [...new Set(fields.map(bareSearchField))].map((field) => [field, { number_of_fragments: 2, fragment_size: 180 }]),
33
+ ),
34
+ });
35
+
36
+ /** Les champs interroges par une clause (`query_string` / `multi_match`), a toute profondeur. */
37
+ function fieldsIn(node: any): string[] {
38
+ if (!node || typeof node !== "object") return [];
39
+ if (Array.isArray(node)) return node.flatMap(fieldsIn);
40
+ return Object.entries(node).flatMap(([key, value]) =>
41
+ key === "fields" && Array.isArray(value) ? (value as string[]) : fieldsIn(value),
42
+ );
43
+ }
44
+
45
+ /**
46
+ * Pose les inner hits sur une chaine de `nested`. Sur un nested imbrique
47
+ * (`diseases` > `diseases.icd10`), ES ne remonte les inner hits du niveau
48
+ * interne que si le niveau externe en demande aussi : chaque niveau en porte,
49
+ * le surlignage n'est demande qu'au plus profond.
50
+ */
51
+ function withInnerHits(node: any, name: string, fields: string[]): any {
52
+ const inner = node.nested.query;
53
+ const deepest = !inner?.nested;
54
+ return {
55
+ nested: {
56
+ ...node.nested,
57
+ query: deepest ? inner : withInnerHits(inner, `${name}>`, fields),
58
+ inner_hits: {
59
+ name,
60
+ size: 3,
61
+ _source: false,
62
+ ...(deepest ? { highlight: highlightBlock(fields) } : {}),
63
+ },
64
+ },
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Instrumente la clause de la barre (`{ bool: { should: [...] } }`) : inner
70
+ * hits sur ses clauses nested, liste des champs plats pour le highlight
71
+ * racine. Un `match_all` (barre vide) repart tel quel.
72
+ */
73
+ export function instrumentSearchClause(values: any): { query: any; highlight?: Record<string, any> } {
74
+ const should = values?.bool?.should;
75
+ if (!Array.isArray(should)) return { query: values };
76
+
77
+ const flatFields: string[] = [];
78
+ const used = new Map<string, number>();
79
+ const instrumented = should.map((clause: any) => {
80
+ const fields = fieldsIn(clause);
81
+ if (!clause?.nested) {
82
+ flatFields.push(...fields);
83
+ return clause;
84
+ }
85
+ // Une meme famille peut partir sur deux chemins nested (CIM-10 et
86
+ // sections) : ES refuse deux inner hits du meme nom.
87
+ const key = fields.length ? searchFieldGroup(fields[0]!).key : "other";
88
+ const n = used.get(key) ?? 0;
89
+ used.set(key, n + 1);
90
+ return withInnerHits(clause, `${SEARCH_EXPLAIN_PREFIX}${key}${n ? `#${n}` : ""}`, fields);
91
+ });
92
+
93
+ return {
94
+ query: { ...values, bool: { ...values.bool, should: instrumented } },
95
+ highlight: flatFields.length ? highlightBlock(flatFields) : undefined,
96
+ };
97
+ }
98
+
99
+ /** Garde d'un hit ce qui l'explique, ou `undefined` s'il n'y a rien. */
100
+ export function searchMatchOf(hit: any): SearchMatch | undefined {
101
+ const inner = Object.fromEntries(
102
+ Object.entries<any>(hit?.inner_hits ?? {}).filter(([name]) => name.startsWith(SEARCH_EXPLAIN_PREFIX)),
103
+ );
104
+ const hasHighlight = hit?.highlight && Object.keys(hit.highlight).length > 0;
105
+ const hasInner = Object.keys(inner).length > 0;
106
+ if (!hasHighlight && !hasInner) return undefined;
107
+ return {
108
+ ...(hasHighlight ? { highlight: hit.highlight } : {}),
109
+ ...(hasInner ? { inner_hits: inner } : {}),
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Surlignage d'un champ `keyword` (ou d'un identifiant) : le moteur renvoie la
115
+ * valeur entiere enveloppee. L'extrait ne montre rien de plus que la pastille,
116
+ * il tombe ; la famille garde sa pastille.
117
+ */
118
+ const WHOLE_VALUE = /^<mark>[^<]*<\/mark>$/;
119
+
120
+ /** Extraits d'un inner hit, en descendant dans ses inner hits imbriques. */
121
+ function fragmentsOf(innerHit: any): string[] {
122
+ return (innerHit?.hits?.hits ?? []).flatMap((hit: any) => [
123
+ ...(Object.values(hit?.highlight ?? {}).flat() as string[]),
124
+ ...Object.values<any>(hit?.inner_hits ?? {}).flatMap(fragmentsOf),
125
+ ]);
126
+ }
127
+
128
+ /**
129
+ * Familles ou les termes ont ete trouves, la plus fournie d'abord. `null`
130
+ * quand le resultat ne porte aucune explication (barre vide).
131
+ */
132
+ export function explainSearchMatch(match: SearchMatch | undefined, locale: string): SearchMatchField[] | null {
133
+ if (!match) return null;
134
+ const byKey = new Map<string, SearchMatchField>();
135
+ const entry = (key: string) => {
136
+ if (!byKey.has(key)) {
137
+ const group = searchFieldGroupByKey(key);
138
+ byKey.set(key, { key, label: searchGroupLabel(group, locale), count: 0, fragments: [] });
139
+ }
140
+ return byKey.get(key)!;
141
+ };
142
+ const push = (field: SearchMatchField, html: unknown) => {
143
+ if (typeof html !== "string" || !html || WHOLE_VALUE.test(html)) return;
144
+ if (!field.fragments.includes(html)) field.fragments.push(html);
145
+ };
146
+
147
+ for (const [name, innerHit] of Object.entries<any>(match.inner_hits ?? {})) {
148
+ const count = innerHit?.hits?.total?.value ?? 0;
149
+ if (!count) continue;
150
+ const field = entry(name.slice(SEARCH_EXPLAIN_PREFIX.length).replace(/#\d+$/, ""));
151
+ field.count += count;
152
+ fragmentsOf(innerHit).forEach((html) => push(field, html));
153
+ }
154
+
155
+ for (const [name, fragments] of Object.entries(match.highlight ?? {})) {
156
+ const field = entry(searchFieldGroup(name).key);
157
+ (Array.isArray(fragments) ? fragments : []).forEach((html) => push(field, html));
158
+ }
159
+
160
+ // `search_text` concatene tout le document : a cote d'une famille precise,
161
+ // il ne fait que repeter le meme passage. Il ne parle que seul.
162
+ let fields = [...byKey.values()];
163
+ if (fields.length > 1) fields = fields.filter((field) => field.key !== "full_text");
164
+ if (!fields.length) return null;
165
+ return fields.sort((a, b) => b.count - a.count || b.fragments.length - a.fragments.length);
166
+ }
@@ -0,0 +1,487 @@
1
+ import indexConfig from '../indexConfig.json';
2
+ import useOipElastic from './useOipElastic';
3
+ import { dateFormat } from '../utils/styleAndFormat';
4
+
5
+ // Types de France Augmented Science (types/Pubmed) : pas portes, le document reste libre.
6
+ type Pubmed = any;
7
+
8
+ export default function usePubmed() {
9
+ // Dans FAS, queryElastic / queryCustomElastic etaient pris au niveau du
10
+ // module : ici dans la fonction, useOipElastic lit la config Nuxt.
11
+ const { queryElastic, queryCustomElastic } = useOipElastic();
12
+ const isLoadingPubmedList = ref(false);
13
+
14
+ const indexQuery = ref(indexConfig.pubmed.index);
15
+ const cluster = ref(indexConfig.pubmed.cluster);
16
+ const { queryOneElastic } = useOipElastic();
17
+
18
+ const resultsQueryPubmed = ref(null as Pubmed | null);
19
+ const pubmedListResult = ref(null as Pubmed[] | null);
20
+ const isLoadingQueryPubmed = ref(false);
21
+
22
+ const findPubmedById = ({
23
+ id,
24
+ source,
25
+ }: {
26
+ id: string;
27
+ source?: string[];
28
+ }): Promise<Pubmed> => {
29
+ isLoadingQueryPubmed.value = true;
30
+ return new Promise<Pubmed>(async (resolve, reject) => {
31
+ try {
32
+ const res = await queryOneElastic({
33
+ index: indexQuery.value,
34
+ path: '_id',
35
+ source: source,
36
+ cluster: cluster.value,
37
+ id,
38
+ });
39
+ if (res) {
40
+ resultsQueryPubmed.value = res as Pubmed;
41
+ resolve(res as Pubmed);
42
+ } else {
43
+ // Document absent de l'index : la fiche montre son etat vide.
44
+ resolve(null as any);
45
+ }
46
+ } catch (error) {
47
+ reject(error);
48
+ } finally {
49
+ isLoadingQueryPubmed.value = false;
50
+ }
51
+ });
52
+ };
53
+
54
+ const findManyPubmedById = async ({
55
+ ids,
56
+ source,
57
+ }: {
58
+ ids: string[];
59
+ source?: string[];
60
+ }): Promise<Pubmed[]> => {
61
+ isLoadingQueryPubmed.value = true;
62
+
63
+ try {
64
+ const query = {
65
+ from: 0,
66
+ query: {
67
+ bool: {
68
+ must: [
69
+ {
70
+ terms: {
71
+ _id: ids,
72
+ },
73
+ },
74
+ ],
75
+ },
76
+ },
77
+ size: 1000,
78
+ track_total_hits: true,
79
+ _source: {
80
+ include: source || [],
81
+ exclude: [],
82
+ },
83
+ };
84
+
85
+ const res = await queryCustomElastic({
86
+ index: indexQuery.value,
87
+ customQuery: query,
88
+ cluster: cluster.value,
89
+ });
90
+
91
+ if (res && res.results) {
92
+ pubmedListResult.value = res.results as Pubmed[];
93
+ return res.results as Pubmed[];
94
+ } else {
95
+ throw new Error('Aucun résultat trouvé');
96
+ }
97
+ } catch (error) {
98
+ throw error;
99
+ } finally {
100
+ isLoadingQueryPubmed.value = false;
101
+ }
102
+ };
103
+
104
+ async function searchPubmedFromIds(array: any[]) {
105
+ try {
106
+ const response = await $fetch(
107
+ `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?&db=pubmed&id=${array.join(
108
+ ','
109
+ )}&retmode=json&retstart=0`
110
+ );
111
+ // @ts-ignore
112
+ if (response && response.result) {
113
+ // @ts-ignore
114
+ const uids = response.result.uids;
115
+
116
+ const results = uids.map((uid: any) => {
117
+ // @ts-ignore
118
+ const { uids, ...filtered } = response.result[uid];
119
+ return filtered;
120
+ });
121
+
122
+ return results;
123
+ }
124
+
125
+ return [];
126
+ } catch (error) {
127
+ console.error('Error in searchPubmedFromIds:', error);
128
+ throw error;
129
+ }
130
+ }
131
+
132
+ async function searchPubmeds(array: any[]) {
133
+ try {
134
+ const response = await $fetch(
135
+ `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?retmode=json&retmax=5000&db=pubmed&sort=pub+date&term=%20%22last%20555%20years%22[dp]`
136
+ );
137
+ // @ts-ignore
138
+ if (response && response.result) {
139
+ // @ts-ignore
140
+ const uids = response.result.uids;
141
+
142
+ const results = uids.map((uid: any) => {
143
+ // @ts-ignore
144
+ const { uids, ...filtered } = response.result[uid];
145
+ return filtered;
146
+ });
147
+
148
+ return results;
149
+ }
150
+
151
+ return [];
152
+ } catch (error) {
153
+ console.error('Error in searchPubmedFromIds:', error);
154
+ throw error;
155
+ }
156
+ }
157
+
158
+ const reconstructPubmed = async (pubMedIds: any[]) => {
159
+ const pubmedContent = [];
160
+
161
+ const chunkSize = 400;
162
+ const chunks = [];
163
+ for (let i = 0; i < pubMedIds.length; i += chunkSize) {
164
+ chunks.push(pubMedIds.slice(i, i + chunkSize));
165
+ }
166
+
167
+ // Traitement de chaque tranche
168
+ for (const chunk of chunks) {
169
+ try {
170
+ const res = await searchPubmedFromIds(chunk);
171
+ if (res) {
172
+ pubmedContent.push(res);
173
+ }
174
+ } catch (error) {
175
+ console.error('Error processing PubMed chunk:', error);
176
+ }
177
+ }
178
+
179
+ return pubmedContent;
180
+ };
181
+
182
+ const lastUpdatedData = async () => {
183
+ let lastUpdate = null;
184
+ await queryCustomElastic({
185
+ index: 'oip-discovery-pubmed',
186
+ customQuery: {
187
+ from: 0,
188
+
189
+ query: {
190
+ match_all: {},
191
+ },
192
+ sort: [
193
+ {
194
+ revised_date: {
195
+ order: 'desc',
196
+ },
197
+ },
198
+ ],
199
+ size: 1,
200
+ track_total_hits: true,
201
+ },
202
+ onlyOne: true,
203
+ cluster: 'pubmed',
204
+ }).then((res) => {
205
+ if (res) {
206
+ lastUpdate = dateFormat(res.revised_date);
207
+ }
208
+ });
209
+
210
+ return lastUpdate;
211
+ };
212
+
213
+ async function pubmedContentFromIds({
214
+ ids,
215
+ customQuery,
216
+ }: {
217
+ ids?: any;
218
+ customQuery?: any;
219
+ }): Promise<any> {
220
+ isLoadingPubmedList.value = true;
221
+ return new Promise((resolve, reject) => {
222
+ let query = {
223
+ _source: {
224
+ exclude: ['embedding', 'embeddings_small', 'med_embeddings'],
225
+ },
226
+ from: 0,
227
+ query: {
228
+ bool: {
229
+ must: [
230
+ {
231
+ terms: {
232
+ _id: ids,
233
+ },
234
+ },
235
+ ],
236
+ },
237
+ },
238
+ size: 1000,
239
+ track_total_hits: true,
240
+ sort: [{ article_date: { order: 'desc' } }],
241
+ };
242
+
243
+ queryCustomElastic({
244
+ index: 'oip-discovery-pubmed',
245
+ cluster: 'pubmed',
246
+ customQuery: customQuery ? customQuery : query,
247
+ })
248
+ .then((res) => {
249
+ if (res) {
250
+ pubmedListResult.value = res?.results;
251
+ resolve(res);
252
+ } else {
253
+ reject('Aucun résultat trouvé');
254
+ }
255
+ })
256
+ .catch((error) => {
257
+ reject(error);
258
+ })
259
+ .finally(() => {
260
+ isLoadingPubmedList.value = false;
261
+ });
262
+ });
263
+ }
264
+
265
+ // SPECIAL QUERY
266
+ // ::::::::::::::::::::::::::::::::::::::::::::::::::
267
+
268
+ const pubmedDiseaseQuery = (name: string, synonymes?: string[]) => {
269
+ if (synonymes && synonymes.length) {
270
+ const mergeResult = synonymes.concat(name);
271
+ const query = {
272
+ bool: {
273
+ should: [] as any[],
274
+ },
275
+ };
276
+
277
+ mergeResult.forEach((name) => {
278
+ query.bool.should.push(
279
+ {
280
+ query_string: {
281
+ query: `(\"${name}\")`,
282
+ fields: ['title', 'mesh_terms', 'keywords', 'chemical_list'],
283
+ default_operator: 'AND',
284
+ analyze_wildcard: true,
285
+ allow_leading_wildcard: false,
286
+ phrase_slop: 0,
287
+ auto_generate_synonyms_phrase_query: true,
288
+ fuzziness: 'AUTO',
289
+ },
290
+ },
291
+ {
292
+ nested: {
293
+ path: 'sections',
294
+ query: {
295
+ query_string: {
296
+ query: `(\"${name}\")`,
297
+ fields: ['sections.label'],
298
+ default_operator: 'AND',
299
+ analyze_wildcard: true,
300
+ allow_leading_wildcard: false,
301
+ phrase_slop: 0,
302
+ auto_generate_synonyms_phrase_query: true,
303
+ fuzziness: 'AUTO',
304
+ },
305
+ },
306
+ },
307
+ },
308
+ {
309
+ nested: {
310
+ path: 'sections',
311
+ query: {
312
+ query_string: {
313
+ query: `(\"${name}\")`,
314
+ fields: ['sections.text'],
315
+ default_operator: 'AND',
316
+ analyze_wildcard: true,
317
+ allow_leading_wildcard: false,
318
+ phrase_slop: 0,
319
+ auto_generate_synonyms_phrase_query: true,
320
+ fuzziness: 'AUTO',
321
+ },
322
+ },
323
+ },
324
+ },
325
+ {
326
+ nested: {
327
+ path: 'rare_diseases',
328
+ query: {
329
+ query_string: {
330
+ query: `(\"${name}\")`,
331
+ fields: ['rare_diseases.concat_disease_name_orpha_code'],
332
+ default_operator: 'AND',
333
+ analyze_wildcard: true,
334
+ allow_leading_wildcard: false,
335
+ phrase_slop: 0,
336
+ auto_generate_synonyms_phrase_query: true,
337
+ fuzziness: 'AUTO',
338
+ },
339
+ },
340
+ },
341
+ },
342
+ {
343
+ nested: {
344
+ path: 'rare_diseases',
345
+ query: {
346
+ query_string: {
347
+ query: `(\"${name}\")`,
348
+ fields: ['rare_diseases.disease_synonyms'],
349
+ default_operator: 'AND',
350
+ analyze_wildcard: true,
351
+ allow_leading_wildcard: false,
352
+ phrase_slop: 0,
353
+ auto_generate_synonyms_phrase_query: true,
354
+ fuzziness: 'AUTO',
355
+ },
356
+ },
357
+ },
358
+ }
359
+ );
360
+ });
361
+ return query;
362
+ }
363
+
364
+ return {
365
+ bool: {
366
+ should: [
367
+ {
368
+ nested: {
369
+ path: 'rare_diseases',
370
+ query: {
371
+ query_string: {
372
+ query: `(\"${name}\")`,
373
+ fields: ['rare_diseases.concat_disease_name_orpha_code'],
374
+ default_operator: 'AND',
375
+ analyze_wildcard: true,
376
+ allow_leading_wildcard: false,
377
+ phrase_slop: 0,
378
+ auto_generate_synonyms_phrase_query: true,
379
+ fuzziness: 'AUTO',
380
+ },
381
+ },
382
+ },
383
+ },
384
+ {
385
+ nested: {
386
+ path: 'rare_diseases',
387
+ query: {
388
+ query_string: {
389
+ query: `(\"${name}\")`,
390
+ fields: ['rare_diseases.disease_synonyms'],
391
+ default_operator: 'AND',
392
+ analyze_wildcard: true,
393
+ allow_leading_wildcard: false,
394
+ phrase_slop: 0,
395
+ auto_generate_synonyms_phrase_query: true,
396
+ fuzziness: 'AUTO',
397
+ },
398
+ },
399
+ },
400
+ },
401
+ ],
402
+ },
403
+ };
404
+ };
405
+
406
+ const pubmedFromIdsQuery = (ids: string[]) => ({
407
+ terms: {
408
+ PubMed_ID: ids,
409
+ },
410
+ });
411
+
412
+ const pubmedFromGeneSymbolQuery = (geneSymbol: any) => ({
413
+ bool: {
414
+ must: [
415
+ {
416
+ nested: {
417
+ path: 'genomics',
418
+ query: {
419
+ terms: {
420
+ 'genomics.gene_symbol.keyword': geneSymbol,
421
+ },
422
+ },
423
+ },
424
+ },
425
+ {
426
+ term: {
427
+ delete: false,
428
+ },
429
+ },
430
+ ],
431
+ },
432
+ });
433
+
434
+ const pubmedFromMoleculeQuery = (molecule: any) => {
435
+ const query: any = {
436
+ bool: {
437
+ should: [
438
+ {
439
+ nested: {
440
+ path: 'molecules',
441
+ query: {
442
+ bool: {
443
+ should: [
444
+ {
445
+ match: {
446
+ 'molecules.label.keyword': molecule.label,
447
+ },
448
+ },
449
+ ],
450
+ },
451
+ },
452
+ },
453
+ },
454
+ ],
455
+ },
456
+ };
457
+
458
+ if (molecule.atc_code) {
459
+ query.bool.should[0].nested.query.bool.should.push({
460
+ terms: {
461
+ 'molecules.atc5.keyword': molecule.atc_code,
462
+ },
463
+ });
464
+ }
465
+
466
+ return query;
467
+ };
468
+ return {
469
+ searchPubmeds,
470
+ searchPubmedFromIds,
471
+ reconstructPubmed,
472
+ lastUpdatedData,
473
+ // :::::::::::::::::::
474
+ pubmedContentFromIds,
475
+ pubmedListResult,
476
+ isLoadingPubmedList,
477
+ pubmedFromIdsQuery,
478
+ pubmedDiseaseQuery,
479
+ pubmedFromGeneSymbolQuery,
480
+ pubmedFromMoleculeQuery,
481
+
482
+ findPubmedById,
483
+ resultsQueryPubmed,
484
+ isLoadingQueryPubmed,
485
+ findManyPubmedById,
486
+ };
487
+ }