@tekkare/romulus 0.1.251 → 0.1.252

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/dist/module.json +1 -1
  2. package/layer/app/app.config.ts +6 -0
  3. package/layer/app/components/OipPubmedSummary.vue +223 -0
  4. package/layer/app/components/OipResourceDrawer.vue +331 -0
  5. package/layer/app/components/OipResourceDrawerLink.vue +40 -0
  6. package/layer/app/components/OipResourceField.vue +41 -0
  7. package/layer/app/components/OipResourceGenericCard.vue +54 -0
  8. package/layer/app/components/OipResourceSource.vue +50 -0
  9. package/layer/app/components/OipResourceTags.vue +65 -0
  10. package/layer/app/components/OipResourceView.vue +107 -0
  11. package/layer/app/components/oip-drugs/fiches/AssociationFiche.vue +775 -0
  12. package/layer/app/components/oip-drugs/fiches/BrandFiche.vue +1111 -0
  13. package/layer/app/components/oip-drugs/fiches/IndicationFiche.vue +182 -0
  14. package/layer/app/components/oip-drugs/fiches/JoFiche.vue +101 -0
  15. package/layer/app/components/oip-drugs/fiches/PndsFiche.vue +279 -0
  16. package/layer/app/components/oip-drugs/heart/JoCard.vue +314 -0
  17. package/layer/app/components/oip-drugs/heart/TextHighlight.vue +326 -0
  18. package/layer/app/components/oip-drugs/heart/ansm/BrandCard.vue +187 -0
  19. package/layer/app/components/oip-drugs/heart/ansm/CisDetail.vue +363 -0
  20. package/layer/app/components/oip-drugs/heart/ansm/CisTable.vue +167 -0
  21. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyCard.vue +373 -0
  22. package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyFiche.vue +372 -0
  23. package/layer/app/components/oip-drugs/heart/associations/TranscriptionCard.vue +55 -0
  24. package/layer/app/components/oip-drugs/heart/elastic/SourceContainerElastic.vue +103 -0
  25. package/layer/app/components/oip-drugs/heart/france/BrandGroupCard.vue +450 -0
  26. package/layer/app/components/oip-drugs/heart/mitm/Card.vue +199 -0
  27. package/layer/app/components/oip-drugs/heart/mitm/Source.vue +23 -0
  28. package/layer/app/components/oip-drugs/heart/mitm/details/Overview.vue +381 -0
  29. package/layer/app/components/oip-drugs/heart/put/Card.vue +212 -0
  30. package/layer/app/components/oip-drugs/heart/results/AccesCard.vue +696 -0
  31. package/layer/app/components/oip-drugs/heart/results/AlertCard.vue +342 -0
  32. package/layer/app/components/oip-drugs/heart/results/AsmrCursor.vue +229 -0
  33. package/layer/app/components/oip-drugs/heart/results/EvalCard.vue +370 -0
  34. package/layer/app/components/oip-drugs/heart/results/IndicCard.vue +743 -0
  35. package/layer/app/components/oip-drugs/heart/results/MatchExplain.vue +243 -0
  36. package/layer/app/components/oip-drugs/heart/results/PrefRows.vue +105 -0
  37. package/layer/app/components/oip-drugs/heart/results/details/HasHistory.vue +487 -0
  38. package/layer/app/components/oip-drugs/heart/results/details/InternationalCountryCard.vue +461 -0
  39. package/layer/app/components/oip-drugs/heart/results/details/InternationalOverview.vue +200 -0
  40. package/layer/app/components/oip-drugs/heart/results/details/InternationalPriceDrawer.vue +647 -0
  41. package/layer/app/components/oip-drugs/heart/results/details/Monograph.vue +128 -0
  42. package/layer/app/components/oip-drugs/heart/results/details/OpinionDrawer.vue +535 -0
  43. package/layer/app/components/oip-drugs/heart/results/details/PresentationDrawer.vue +738 -0
  44. package/layer/app/components/oip-drugs/heart/results/details/Price.vue +227 -0
  45. package/layer/app/components/oip-drugs/heart/results/details/PriceOverview.vue +1261 -0
  46. package/layer/app/components/oip-drugs/heart/results/details/RecordDrawer.vue +494 -0
  47. package/layer/app/components/oip-drugs/heart/results/details/RecordList.vue +426 -0
  48. package/layer/app/components/oip-drugs/heart/results/les/Card.vue +340 -0
  49. package/layer/app/components/oip-drugs/heart/results/les/Drawer.vue +67 -0
  50. package/layer/app/components/oip-drugs/heart/results/les/IndicationDetail.vue +149 -0
  51. package/layer/app/components/oip-drugs/heart/results/les/Source.vue +20 -0
  52. package/layer/app/components/oip-drugs/heart/results/les/SourceTag.vue +39 -0
  53. package/layer/app/components/oip-drugs/heart/searchPage/DynamicSource.vue +29 -0
  54. package/layer/app/components/oip-drugs/medicam/market/Chart.vue +121 -0
  55. package/layer/app/components/oip-drugs/outpatient/ProductEvolution.vue +236 -0
  56. package/layer/app/components/oip-drugs/resources/Ansm.vue +75 -0
  57. package/layer/app/components/oip-drugs/resources/Association.vue +30 -0
  58. package/layer/app/components/oip-drugs/resources/Brand.vue +65 -0
  59. package/layer/app/components/oip-drugs/resources/Contribution.vue +216 -0
  60. package/layer/app/components/oip-drugs/resources/Hospital.vue +151 -0
  61. package/layer/app/components/oip-drugs/resources/Indication.vue +54 -0
  62. package/layer/app/components/oip-drugs/resources/Jo.vue +57 -0
  63. package/layer/app/components/oip-drugs/resources/Medicam.vue +140 -0
  64. package/layer/app/components/oip-drugs/resources/Mitm.vue +84 -0
  65. package/layer/app/components/oip-drugs/resources/NationalAssembly.vue +93 -0
  66. package/layer/app/components/oip-drugs/resources/Outpatient.vue +97 -0
  67. package/layer/app/components/oip-drugs/resources/Pnds.vue +24 -0
  68. package/layer/app/components/oip-drugs/resources/Price.vue +106 -0
  69. package/layer/app/components/oip-drugs/resources/Put.vue +143 -0
  70. package/layer/app/components/oip-science/appel-a-projet/Cards.vue +162 -0
  71. package/layer/app/components/oip-science/clinical-trials/Cards.vue +382 -0
  72. package/layer/app/components/oip-science/clinical-trials/CardsGlobal.vue +273 -0
  73. package/layer/app/components/oip-science/clinical-trials/LinkedList.vue +366 -0
  74. package/layer/app/components/oip-science/clinical-trials/PairBadge.vue +25 -0
  75. package/layer/app/components/oip-science/clinical-trials/PairBody.vue +182 -0
  76. package/layer/app/components/oip-science/clinical-trials/SitesDrawer.vue +95 -0
  77. package/layer/app/components/oip-science/common/Form.vue +73 -0
  78. package/layer/app/components/oip-science/common/MatchAvgTag.vue +283 -0
  79. package/layer/app/components/oip-science/common/NoData.vue +16 -0
  80. package/layer/app/components/oip-science/common/ScoreBadge.vue +35 -0
  81. package/layer/app/components/oip-science/common/Source.vue +31 -0
  82. package/layer/app/components/oip-science/common/TableTags.vue +279 -0
  83. package/layer/app/components/oip-science/common/Timeline.vue +104 -0
  84. package/layer/app/components/oip-science/common/ToolTips.vue +89 -0
  85. package/layer/app/components/oip-science/cross/publicationPubmed.vue +484 -0
  86. package/layer/app/components/oip-science/crossData/CrossDataLocations.vue +71 -0
  87. package/layer/app/components/oip-science/ctgov/Investigators.vue +148 -0
  88. package/layer/app/components/oip-science/ctgov/Locations.vue +102 -0
  89. package/layer/app/components/oip-science/ctgov/Publications.vue +52 -0
  90. package/layer/app/components/oip-science/ctgov/Sponsors.vue +66 -0
  91. package/layer/app/components/oip-science/ctgov/StudyDescription.vue +1136 -0
  92. package/layer/app/components/oip-science/ctgov-centers/ClinicalTrials.vue +40 -0
  93. package/layer/app/components/oip-science/ctgov-centers/Publications.vue +50 -0
  94. package/layer/app/components/oip-science/ctgov-investigators/ClinicalTrials.vue +43 -0
  95. package/layer/app/components/oip-science/ctgov-investigators/Informations.vue +171 -0
  96. package/layer/app/components/oip-science/ctgov-investigators/Publications.vue +44 -0
  97. package/layer/app/components/oip-science/elastic/DateRangeFilter.vue +209 -0
  98. package/layer/app/components/oip-science/elastic/Filter.vue +719 -0
  99. package/layer/app/components/oip-science/elastic/MatchExplain.vue +152 -0
  100. package/layer/app/components/oip-science/establishments/Cards.vue +183 -0
  101. package/layer/app/components/oip-science/establishments/ClinicalTrials.vue +62 -0
  102. package/layer/app/components/oip-science/establishments/Hcps.vue +227 -0
  103. package/layer/app/components/oip-science/establishments/Informations.vue +263 -0
  104. package/layer/app/components/oip-science/establishments/LinkedList.vue +153 -0
  105. package/layer/app/components/oip-science/establishments/MatchedLabels.vue +194 -0
  106. package/layer/app/components/oip-science/establishments/PublicFinance.vue +62 -0
  107. package/layer/app/components/oip-science/euct/Investigators.vue +125 -0
  108. package/layer/app/components/oip-science/euct/Locations.vue +102 -0
  109. package/layer/app/components/oip-science/euct/Products.vue +101 -0
  110. package/layer/app/components/oip-science/euct/Publications.vue +89 -0
  111. package/layer/app/components/oip-science/euct/Sponsors.vue +86 -0
  112. package/layer/app/components/oip-science/euct/StudyDescription.vue +1075 -0
  113. package/layer/app/components/oip-science/euct-centers/ClinicalTrials.vue +40 -0
  114. package/layer/app/components/oip-science/euct-centers/Publications.vue +55 -0
  115. package/layer/app/components/oip-science/euct-investigators/ClinicalTrials.vue +41 -0
  116. package/layer/app/components/oip-science/euct-investigators/Informations.vue +171 -0
  117. package/layer/app/components/oip-science/euct-investigators/Publications.vue +51 -0
  118. package/layer/app/components/oip-science/hcps/AppelsAProjet.vue +62 -0
  119. package/layer/app/components/oip-science/hcps/Cards.vue +237 -0
  120. package/layer/app/components/oip-science/hcps/ClinicalTrials.vue +78 -0
  121. package/layer/app/components/oip-science/hcps/Informations.vue +296 -0
  122. package/layer/app/components/oip-science/hcps/LinkedList.vue +153 -0
  123. package/layer/app/components/oip-science/hcps/MatchedLabels.vue +207 -0
  124. package/layer/app/components/oip-science/hcps/Pubmed.vue +128 -0
  125. package/layer/app/components/oip-science/local/SliderTabs.vue +99 -0
  126. package/layer/app/components/oip-science/local/SliderTabsFull.vue +135 -0
  127. package/layer/app/components/oip-science/pubmed/Authors.vue +128 -0
  128. package/layer/app/components/oip-science/pubmed/Cards.vue +417 -0
  129. package/layer/app/components/oip-science/pubmed/ClinicalTrials.vue +71 -0
  130. package/layer/app/components/oip-science/resources/Phrc.vue +39 -0
  131. package/layer/app/composables/useOipChapterScroll.ts +43 -0
  132. package/layer/app/composables/useOipResourceHooks.ts +27 -0
  133. package/layer/app/composables/useOipResourceNav.ts +66 -0
  134. package/layer/app/composables/useOipResourceViews.ts +117 -0
  135. package/layer/app/composables/useOipResources.ts +83 -0
  136. package/layer/app/oip-drugs/PORTING.md +120 -0
  137. package/layer/app/oip-drugs/composables/useAssociationDetail.ts +220 -0
  138. package/layer/app/oip-drugs/composables/useAssociationExport.ts +63 -0
  139. package/layer/app/oip-drugs/composables/useAssociationLookup.ts +105 -0
  140. package/layer/app/oip-drugs/composables/useCardPrefOptions.ts +201 -0
  141. package/layer/app/oip-drugs/composables/useCurrency.ts +121 -0
  142. package/layer/app/oip-drugs/composables/useDirectusSources.ts +224 -0
  143. package/layer/app/oip-drugs/composables/useHeartBrandMatches.ts +112 -0
  144. package/layer/app/oip-drugs/composables/useHeartDetailSearches.ts +479 -0
  145. package/layer/app/oip-drugs/composables/useHeartIntlApprovals.ts +661 -0
  146. package/layer/app/oip-drugs/composables/useHeartLesIndication.ts +168 -0
  147. package/layer/app/oip-drugs/composables/useHeartMatchExplain.ts +231 -0
  148. package/layer/app/oip-drugs/composables/useHeartReports.ts +239 -0
  149. package/layer/app/oip-drugs/composables/useHeartSearchFields.ts +233 -0
  150. package/layer/app/oip-drugs/composables/useMergeByKey.ts +23 -0
  151. package/layer/app/oip-drugs/composables/useOipDrugsElastic.ts +621 -0
  152. package/layer/app/oip-drugs/composables/usePageEmpty.ts +58 -0
  153. package/layer/app/oip-drugs/composables/v-useSalesSource.ts +64 -0
  154. package/layer/app/oip-drugs/drugs.css +500 -0
  155. package/layer/app/oip-drugs/esIndices.ts +322 -0
  156. package/layer/app/oip-drugs/kinds.ts +264 -0
  157. package/layer/app/oip-drugs/locales/en.json +1571 -0
  158. package/layer/app/oip-drugs/locales/fr.json +1571 -0
  159. package/layer/app/oip-drugs/monographs/blincyto-has.vue +289 -0
  160. package/layer/app/oip-drugs/monographs/darzalex-has.vue +276 -0
  161. package/layer/app/oip-drugs/monographs/dupixent-has.vue +216 -0
  162. package/layer/app/oip-drugs/monographs/enhertu-has.vue +280 -0
  163. package/layer/app/oip-drugs/monographs/has-sein-2023.vue +1649 -0
  164. package/layer/app/oip-drugs/monographs/ibrance-has.vue +277 -0
  165. package/layer/app/oip-drugs/monographs/imfinzi-has.vue +293 -0
  166. package/layer/app/oip-drugs/monographs/keytruda-has.vue +334 -0
  167. package/layer/app/oip-drugs/monographs/kisqali-has.vue +201 -0
  168. package/layer/app/oip-drugs/monographs/opdivo-has.vue +231 -0
  169. package/layer/app/oip-drugs/monographs/ozempic-has.vue +186 -0
  170. package/layer/app/oip-drugs/monographs/tecentriq-has.vue +312 -0
  171. package/layer/app/oip-drugs/monographs/yescarta-has.vue +272 -0
  172. package/layer/app/oip-drugs/types/elastic.ts +113 -0
  173. package/layer/app/oip-drugs/types/es/agencies.ts +152 -0
  174. package/layer/app/oip-drugs/types/es/directory.ts +54 -0
  175. package/layer/app/oip-drugs/types/es/docByIndex.ts +151 -0
  176. package/layer/app/oip-drugs/types/es/openmedic.ts +67 -0
  177. package/layer/app/oip-drugs/types/es/outpatient.ts +71 -0
  178. package/layer/app/oip-drugs/types/es/portal.ts +130 -0
  179. package/layer/app/oip-drugs/types/es/retail.ts +107 -0
  180. package/layer/app/oip-drugs/types/es/statistics.ts +29 -0
  181. package/layer/app/oip-drugs/types/heart/CurrencyRate.ts +16 -0
  182. package/layer/app/oip-drugs/types/heart/ansm.ts +106 -0
  183. package/layer/app/oip-drugs/types/heart/tag.ts +29 -0
  184. package/layer/app/oip-drugs/utils/associationFields.ts +118 -0
  185. package/layer/app/oip-drugs/utils/contributions.ts +36 -0
  186. package/layer/app/oip-drugs/utils/countryMapping.ts +387 -0
  187. package/layer/app/oip-drugs/utils/esHighlight.ts +21 -0
  188. package/layer/app/oip-drugs/utils/exportSheets.ts +57 -0
  189. package/layer/app/oip-drugs/utils/heartIntlPrice.ts +228 -0
  190. package/layer/app/oip-drugs/utils/heartStyleAndFormat.ts +262 -0
  191. package/layer/app/oip-drugs/utils/heartStyleAnsm.ts +71 -0
  192. package/layer/app/oip-drugs/utils/latestDate.ts +31 -0
  193. package/layer/app/oip-drugs/utils/p-heartCardTags.ts +53 -0
  194. package/layer/app/oip-drugs/utils/priceExport.ts +269 -0
  195. package/layer/app/oip-drugs/utils/q-hasRatings.ts +43 -0
  196. package/layer/app/oip-drugs/utils/s-openCard.ts +20 -0
  197. package/layer/app/oip-drugs/utils/sourceUuids.ts +155 -0
  198. package/layer/app/oip-drugs/utils/styleAndFormat.ts +40 -0
  199. package/layer/app/oip-drugs/utils/v-salesSeries.ts +156 -0
  200. package/layer/app/oip-drugs/utils/v-scopeTags.ts +23 -0
  201. package/layer/app/oip-science/PORTING.md +93 -0
  202. package/layer/app/oip-science/assets/smartSelection.json +428 -0
  203. package/layer/app/oip-science/composables/useAuthorMatching.ts +342 -0
  204. package/layer/app/oip-science/composables/useCTCenter.ts +107 -0
  205. package/layer/app/oip-science/composables/useCTInvestigators.ts +107 -0
  206. package/layer/app/oip-science/composables/useEstablishmentCrossData.ts +139 -0
  207. package/layer/app/oip-science/composables/useEstablishmentMatching.ts +176 -0
  208. package/layer/app/oip-science/composables/useEuct.ts +418 -0
  209. package/layer/app/oip-science/composables/useEuctCenter.ts +107 -0
  210. package/layer/app/oip-science/composables/useEuctInvestigators.ts +107 -0
  211. package/layer/app/oip-science/composables/useGlobalClinicalTrials.ts +624 -0
  212. package/layer/app/oip-science/composables/useLinkedTrials.ts +83 -0
  213. package/layer/app/oip-science/composables/useOipElastic.ts +658 -0
  214. package/layer/app/oip-science/composables/useOipSearchExplain.ts +166 -0
  215. package/layer/app/oip-science/composables/usePubmed.ts +487 -0
  216. package/layer/app/oip-science/composables/useSearchFields.ts +153 -0
  217. package/layer/app/oip-science/indexConfig.json +794 -0
  218. package/layer/app/oip-science/locales/en.json +483 -0
  219. package/layer/app/oip-science/locales/fr.json +483 -0
  220. package/layer/app/oip-science/science.css +1182 -0
  221. package/layer/app/oip-science/utils/a-openCard.ts +23 -0
  222. package/layer/app/oip-science/utils/b-openCard.ts +35 -0
  223. package/layer/app/oip-science/utils/c-filterContext.ts +81 -0
  224. package/layer/app/oip-science/utils/f-types.ts +241 -0
  225. package/layer/app/oip-science/utils/frDiseaseTerms.ts +269 -0
  226. package/layer/app/oip-science/utils/matchMethod.ts +53 -0
  227. package/layer/app/oip-science/utils/sliderTabs.ts +13 -0
  228. package/layer/app/oip-science/utils/sources.ts +15 -0
  229. package/layer/app/oip-science/utils/styleAndFormat.ts +608 -0
  230. package/layer/app/oip-science/utils/toPresets.ts +7 -0
  231. package/layer/app/oip-science/utils/valueTable.ts +126 -0
  232. package/layer/app/plugins/oip-science-i18n.ts +17 -0
  233. package/layer/app/utils/oipFicheRoutes.ts +118 -0
  234. package/layer/app/utils/oipResources.ts +440 -0
  235. package/layer/nuxt.config.ts +22 -2
  236. package/layer/public/reports/blincyto-has.html +292 -0
  237. package/layer/public/reports/blincyto-has.pdf +0 -0
  238. package/layer/public/reports/darzalex-has.html +279 -0
  239. package/layer/public/reports/darzalex-has.pdf +0 -0
  240. package/layer/public/reports/dupixent-has.html +217 -0
  241. package/layer/public/reports/dupixent-has.pdf +0 -0
  242. package/layer/public/reports/enhertu-has.html +283 -0
  243. package/layer/public/reports/enhertu-has.pdf +0 -0
  244. package/layer/public/reports/has-sein-2023.html +1639 -0
  245. package/layer/public/reports/ibrance-has.html +280 -0
  246. package/layer/public/reports/ibrance-has.pdf +0 -0
  247. package/layer/public/reports/imfinzi-has.html +297 -0
  248. package/layer/public/reports/imfinzi-has.pdf +0 -0
  249. package/layer/public/reports/keytruda-has.html +337 -0
  250. package/layer/public/reports/keytruda-has.pdf +0 -0
  251. package/layer/public/reports/kisqali-has.html +202 -0
  252. package/layer/public/reports/kisqali-has.pdf +0 -0
  253. package/layer/public/reports/opdivo-has.html +232 -0
  254. package/layer/public/reports/opdivo-has.pdf +0 -0
  255. package/layer/public/reports/ozempic-has.html +188 -0
  256. package/layer/public/reports/ozempic-has.pdf +0 -0
  257. package/layer/public/reports/tecentriq-has.html +315 -0
  258. package/layer/public/reports/tecentriq-has.pdf +0 -0
  259. package/layer/public/reports/yescarta-has.html +275 -0
  260. package/layer/public/reports/yescarta-has.pdf +0 -0
  261. package/layer/server/api/oip/es.post.ts +99 -0
  262. package/layer/server/api/oip/pdf.get.ts +70 -0
  263. package/layer/server/api/oip/reports.get.ts +98 -0
  264. package/layer/server/api/oip/resource-types.get.ts +22 -0
  265. package/package.json +1 -1
@@ -0,0 +1,78 @@
1
+ <!--
2
+ Onglet Essais cliniques de la fiche professionnel (porte de la page
3
+ hcps/[id]/clinical-trials de France Augmented Science).
4
+ -->
5
+ <template>
6
+ <div class="oip-science">
7
+ <OipScienceClinicalTrialsLinkedList
8
+ :nct-ids="nctIds"
9
+ :euct-ids="euctIds"
10
+ :extras="extras"
11
+ :title="$t('oipScience.common.clinicalTrials')"
12
+ :sources="['CT.gov', 'EUCT', 'RPPS']"
13
+ index-last-update="HCPs"
14
+ :loading="isLoading"
15
+ />
16
+ </div>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import indexConfig from '../../../oip-science/indexConfig.json';
21
+ import useOipElastic from '../../../oip-science/composables/useOipElastic';
22
+
23
+ // Meme forme que LinkedTrialExtra de clinical-trials/LinkedList.vue.
24
+ type LinkedTrialExtra = { score?: number | null; role?: string | null };
25
+ type HcpTrialName = { first_name?: string; last_name?: string; name?: string; match_method: string; score: number };
26
+
27
+ const props = withDefaults(defineProps<{ id: string; highlight?: string[]; lang?: 'fr' | 'en' }>(), {
28
+ highlight: () => [],
29
+ lang: undefined,
30
+ });
31
+
32
+ const { queryOneElastic } = useOipElastic();
33
+
34
+ const isLoading = ref(true);
35
+ const hcp = ref<any>(null);
36
+
37
+ async function load(urlId: string) {
38
+ isLoading.value = true;
39
+ hcp.value = null;
40
+ try {
41
+ hcp.value = await queryOneElastic({
42
+ index: indexConfig.hcps.index,
43
+ cluster: indexConfig.hcps.cluster,
44
+ path: 'hcp_code',
45
+ id: urlId,
46
+ });
47
+ } catch (e) {
48
+ console.error(e);
49
+ } finally {
50
+ isLoading.value = false;
51
+ }
52
+ }
53
+
54
+ watch(() => props.id, (id) => { if (id) load(id); }, { immediate: true });
55
+
56
+ const nctIds = computed<string[]>(() =>
57
+ (hcp.value?.nct_trial || []).map((t: any) => t?.clinical_trial_id).filter(Boolean),
58
+ );
59
+ const euctIds = computed<string[]>(() =>
60
+ (hcp.value?.euct_trial || []).map((t: any) => t?.clinical_trial_id).filter(Boolean),
61
+ );
62
+
63
+ // Le meilleur score de rapprochement du professionnel, par registre.
64
+ const topScore = (entries: HcpTrialName[] | null | undefined) => {
65
+ if (!Array.isArray(entries) || !entries.length) return null;
66
+ const top = [...entries].sort((a, b) => (b?.score ?? 0) - (a?.score ?? 0))[0];
67
+ return typeof top?.score === 'number' ? top.score : null;
68
+ };
69
+
70
+ const extras = computed<Record<string, LinkedTrialExtra>>(() => {
71
+ const nct = { score: topScore(hcp.value?.nct_name) };
72
+ const euct = { score: topScore(hcp.value?.euct_name) };
73
+ return Object.fromEntries([
74
+ ...nctIds.value.map((id) => [id, nct]),
75
+ ...euctIds.value.map((id) => [id, euct]),
76
+ ]);
77
+ });
78
+ </script>
@@ -0,0 +1,296 @@
1
+ <!--
2
+ Onglet Informations de la fiche professionnel (porte de la page
3
+ hcps/[id]/informations de France Augmented Science).
4
+ -->
5
+ <template>
6
+ <div class="oip-science oip_grid gap-24">
7
+ <RmLoadingState v-if="isLoading" :lines="3" :chart-height="240" />
8
+ <RmEmptyState
9
+ v-if="!isLoading && !rppsData && !hcp"
10
+ :title="$t('oipScience.common.noDataTitle')"
11
+ :text="$t('oipScience.common.noDataText')"
12
+ />
13
+
14
+ <RmSectionCard v-if="!isLoading && (rppsData || hcp)" position="none" class="hcp-info-card">
15
+ <div class="oip_grid gap-24">
16
+ <div class="hcp-info-header" style="position: relative;">
17
+ <div class="hcp-dates-block">
18
+ <div class="oip_row v-center gap-8" v-if="principalEntryDate !== '-'">
19
+ <p class="kk_text cool_grey">{{ $t('oipScience.common.entry') }}:</p>
20
+ <p class="kk_text">{{ principalEntryDate }}</p>
21
+ </div>
22
+ <div class="oip_row v-center gap-8" v-if="updateDate">
23
+ <p class="kk_text cool_grey">{{ $t('oipScience.common.updateDate') }}:</p>
24
+ <p class="kk_text">{{ updateDate }}</p>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="oip_grid gap-8">
29
+ <div class="oip_row v-center gap-8 wrap">
30
+ <RmTag
31
+ v-if="principalSpecialty"
32
+ variant="blue"
33
+ >
34
+ {{ principalSpecialty }}
35
+ </RmTag>
36
+ <RmTag
37
+ v-for="(spec, i) in secondarySpecialties"
38
+ :key="i"
39
+ variant="gray"
40
+ >
41
+ {{ spec }}
42
+ </RmTag>
43
+ </div>
44
+
45
+ <h1 class="kk_title_medium">{{ fullName }}</h1>
46
+
47
+ <div class="oip_row v-center gap-8 wrap">
48
+ <RmIcon name="IdentificationCard" color="var(--cool-grey)" :size="14" />
49
+ <p class="kk_text cool_grey">RPPS:</p>
50
+ <p class="kk_text">{{ ifNoRes(rppsCode) }}</p>
51
+ <p class="kk_text cool_grey" v-if="exerciceMode !== '-'">-</p>
52
+ <RmIcon v-if="exerciceMode !== '-'" name="Briefcase" color="var(--cool-grey)" :size="14" />
53
+ <p class="kk_text cool_grey" v-if="exerciceMode !== '-'">{{ $t('oipScience.common.exerciceMode') }}:</p>
54
+ <p class="kk_text" v-if="exerciceMode !== '-'">{{ exerciceMode }}</p>
55
+ </div>
56
+ </div>
57
+ </div>
58
+
59
+ <RmTable
60
+ v-if="tableContent.length"
61
+ flat
62
+ :columns="addressTable.columns"
63
+ :rows="addressTable.rows"
64
+ />
65
+ </div>
66
+ </RmSectionCard>
67
+
68
+ <div v-if="!isLoading && (rppsData || hcp)" class="hcp-kpi-row oip_row gap-16 wrap">
69
+ <div class="hcp-kpi-card" v-if="nctCount > 0">
70
+ <p class="hcp-kpi-label">🇺🇸 NCT</p>
71
+ <p class="hcp-kpi-value">{{ nctCount }}</p>
72
+ <p class="hcp-kpi-sub">{{ $t('oipScience.kpi.trials') }}</p>
73
+ </div>
74
+ <div class="hcp-kpi-card" v-if="euctCount > 0">
75
+ <p class="hcp-kpi-label">🇪🇺 EUCT (CTIS)</p>
76
+ <p class="hcp-kpi-value">{{ euctCount }}</p>
77
+ <p class="hcp-kpi-sub">{{ $t('oipScience.kpi.trials') }}</p>
78
+ </div>
79
+ <div class="hcp-kpi-card" v-if="pubmedCount > 0">
80
+ <p class="hcp-kpi-label">📚 PubMed</p>
81
+ <p class="hcp-kpi-value">{{ pubmedCount }}</p>
82
+ <p class="hcp-kpi-sub">{{ $t('oipScience.kpi.citations') }}</p>
83
+ </div>
84
+ <div class="hcp-kpi-card" v-if="aapCount > 0">
85
+ <p class="hcp-kpi-label">📋 {{ $t('oipScience.common.aap') }}</p>
86
+ <p class="hcp-kpi-value">{{ aapCount }}</p>
87
+ <p class="hcp-kpi-sub">{{ $t('oipScience.kpi.studies') }}</p>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </template>
92
+
93
+ <script setup lang="ts">
94
+ import indexConfig from '../../../oip-science/indexConfig.json';
95
+ import useOipElastic from '../../../oip-science/composables/useOipElastic';
96
+ import { dateFormat, ifNoRes } from '../../../oip-science/utils/styleAndFormat';
97
+ import { valueTable } from '../../../oip-science/utils/valueTable';
98
+
99
+ const props = withDefaults(defineProps<{ id: string; highlight?: string[]; lang?: 'fr' | 'en' }>(), {
100
+ highlight: () => [],
101
+ lang: undefined,
102
+ });
103
+
104
+ const { t } = useI18n();
105
+ const { queryOneElastic, queryCustomElastic } = useOipElastic();
106
+
107
+ const tableHeads = computed(() => [t('oipScience.common.establishment'), t('oipScience.common.address'), t('oipScience.common.entryDate')]);
108
+
109
+ const isLoading = ref(true);
110
+ const hcp = ref<any>(null);
111
+ const rppsData = ref<any>(null);
112
+ const aapCount = ref(0);
113
+ const pubmedCount = ref(0);
114
+ const nctCount = ref(0);
115
+ const euctCount = ref(0);
116
+
117
+ async function load(urlId: string) {
118
+ isLoading.value = true;
119
+ hcp.value = null;
120
+ rppsData.value = null;
121
+ aapCount.value = 0;
122
+ try {
123
+ const [hcpRes, rppsRes, aapRes] = await Promise.all([
124
+ queryOneElastic({
125
+ index: indexConfig.hcps.index,
126
+ cluster: indexConfig.hcps.cluster,
127
+ path: 'hcp_code',
128
+ id: urlId,
129
+ }),
130
+ queryCustomElastic({
131
+ index: indexConfig.rpps.index,
132
+ cluster: indexConfig.rpps.cluster,
133
+ customQuery: {
134
+ size: 1,
135
+ query: { term: { 'hcp_code.keyword': urlId } },
136
+ },
137
+ }),
138
+ queryCustomElastic({
139
+ index: indexConfig.phrc.index,
140
+ cluster: indexConfig.phrc.cluster,
141
+ customQuery: {
142
+ size: 0,
143
+ query: { term: { 'hcp_code.keyword': urlId } },
144
+ track_total_hits: true,
145
+ },
146
+ allAnswer: true,
147
+ }),
148
+ ]);
149
+
150
+ hcp.value = hcpRes;
151
+ rppsData.value = rppsRes?.results?.[0] || null;
152
+ aapCount.value = aapRes?.hits?.total?.value || 0;
153
+ nctCount.value = hcpRes?.nct_trial_count || 0;
154
+ euctCount.value = hcpRes?.euct_trial_count || 0;
155
+ pubmedCount.value = hcpRes?.pubmed_trial_count || 0;
156
+ } catch (e) {
157
+ console.error(e);
158
+ } finally {
159
+ isLoading.value = false;
160
+ }
161
+ }
162
+
163
+ watch(() => props.id, (id) => { if (id) load(id); }, { immediate: true });
164
+
165
+ // Use rppsData (new index) with fallback to hcp (old index)
166
+ const src = computed(() => rppsData.value || null);
167
+
168
+ const fullName = computed(() => {
169
+ const fallback = src.value || hcp.value;
170
+ if (!fallback) return '';
171
+ return `${fallback.hcp_family_name || ''} ${fallback.hcp_first_name || ''}`.trim().toUpperCase();
172
+ });
173
+
174
+ const allSpecialties = computed(() => {
175
+ const specs = src.value?.specialty || hcp.value?.specialty;
176
+ return Array.isArray(specs) ? specs : [];
177
+ });
178
+
179
+ const principalSpecialty = computed(() => {
180
+ const principal = allSpecialties.value.find((s: any) => s.hcp_specialty_type === 'PRINCIPAL');
181
+ return principal?.hcp_specialty_label_regroup || null;
182
+ });
183
+
184
+ const secondarySpecialties = computed(() => {
185
+ return allSpecialties.value
186
+ .filter((s: any) => s.hcp_specialty_type !== 'PRINCIPAL')
187
+ .map((s: any) => s.hcp_specialty_label_regroup)
188
+ .filter(Boolean);
189
+ });
190
+
191
+ const principalEntryDate = computed(() => {
192
+ const principal = allSpecialties.value.find((s: any) => s.hcp_specialty_type === 'PRINCIPAL');
193
+ return principal?.hcp_specialty_entry_date ? dateFormat(principal.hcp_specialty_entry_date, props.lang) : '-';
194
+ });
195
+
196
+ const updateDate = computed(() => {
197
+ const raw = src.value?.update_date || hcp.value?.update_date;
198
+ return raw ? dateFormat(raw, props.lang) : null;
199
+ });
200
+
201
+ const exerciceMode = computed(() => {
202
+ return src.value?.hcp_exercice_mode || hcp.value?.hcp_exercice_mode || '-';
203
+ });
204
+
205
+ const rppsCode = computed(() => {
206
+ return src.value?.hcp_code || hcp.value?.hcp_code || props.id;
207
+ });
208
+
209
+ const addresses = computed(() => {
210
+ const addrs = src.value?.address || hcp.value?.address;
211
+ if (!Array.isArray(addrs)) return [];
212
+ return [...addrs].sort((a, b) => {
213
+ const da = a?.hcp_address_entry_date || '';
214
+ const db = b?.hcp_address_entry_date || '';
215
+ return db.localeCompare(da);
216
+ });
217
+ });
218
+
219
+ const tableContent = computed(() => {
220
+ return addresses.value
221
+ .filter((a: any) => a?.hco_et_label || a?.hcp_city)
222
+ .map((a: any) => {
223
+ const streetParts = [
224
+ a?.hcp_street_number,
225
+ a?.hcp_street_type_code,
226
+ a?.hcp_street_label,
227
+ ].filter(Boolean);
228
+ const street = streetParts.join(' ').trim();
229
+ const cityPart = [a?.hcp_zip_code, a?.hcp_city].filter(Boolean).join(' ');
230
+ const address = [street, cityPart].filter(Boolean).join(' - ') || cityPart || '-';
231
+ return {
232
+ values: [
233
+ a?.hco_et_label || '-',
234
+ address,
235
+ a?.hcp_address_entry_date ? dateFormat(a.hcp_address_entry_date, props.lang) : '-',
236
+ ],
237
+ };
238
+ });
239
+ });
240
+
241
+ const addressTable = computed(() =>
242
+ valueTable(tableHeads.value, tableContent.value, undefined, {
243
+ align: ['left', 'left', 'left'],
244
+ }),
245
+ );
246
+ </script>
247
+
248
+ <style scoped>
249
+ .hcp-info-card :deep(.card_content) {
250
+ padding: 0;
251
+ }
252
+ .hcp-info-header {
253
+ padding: 24px 24px 0 24px;
254
+ }
255
+ .hcp-dates-block {
256
+ position: absolute;
257
+ top: 24px;
258
+ right: 24px;
259
+ display: flex;
260
+ flex-direction: column;
261
+ align-items: flex-end;
262
+ gap: 4px;
263
+ }
264
+ .hcp-kpi-card {
265
+ flex: 1 1 0;
266
+ min-width: 140px;
267
+ display: flex;
268
+ flex-direction: column;
269
+ align-items: center;
270
+ justify-content: center;
271
+ text-align: center;
272
+ gap: 4px;
273
+ padding: 16px 20px;
274
+ background: var(--white);
275
+ border: 1px solid var(--Main-Light, var(--border-default));
276
+ border-radius: 12px;
277
+ box-shadow: var(--small-shadow);
278
+ }
279
+ .hcp-kpi-label {
280
+ font-size: 12px;
281
+ font-weight: 600;
282
+ color: var(--text-muted);
283
+ white-space: nowrap;
284
+ }
285
+ .hcp-kpi-value {
286
+ font-size: 22px;
287
+ font-weight: 700;
288
+ color: var(--text-primary);
289
+ }
290
+ .hcp-kpi-sub {
291
+ font-size: 11px;
292
+ color: var(--text-muted);
293
+ text-transform: uppercase;
294
+ letter-spacing: 0.5px;
295
+ }
296
+ </style>
@@ -0,0 +1,153 @@
1
+ <!--
2
+ Liste de personnes liees a une fiche (investigateurs d'un essai, auteurs
3
+ d'une publication, professionnels rapproches). Une personne reconnue dans
4
+ l'annuaire RPPS a la carte de la page /hcps ; les autres gardent le meme
5
+ gabarit, sans lien. Filtre local et affichage par paquets : un essai peut
6
+ lister des centaines de contacts. (Portage de components/hcps/LinkedList.vue
7
+ de France Augmented Science.)
8
+ -->
9
+ <template>
10
+ <div class="oip-science">
11
+ <div v-if="!loading && !isLoadingDocs" class="oip_grid gap-16">
12
+ <RmSearchBar
13
+ v-model="searchText"
14
+ :placeholder="$t('oipScience.common.searchTrialInvestigators')"
15
+ :have-advanced="false"
16
+ />
17
+
18
+ <RmResultsLine icon="UserCircle" :items="[{ count: filtered.length, label: $t('oipScience.resultKpis.hcps', filtered.length) }]" />
19
+ <div class="oip_grid gap-8">
20
+ <template v-for="(person, index) in visible" :key="index">
21
+ <OipScienceHcpsCards
22
+ v-if="person.rpps && hcpDocs.get(person.rpps)"
23
+ :data="hcpDocs.get(person.rpps)"
24
+ >
25
+ <template #extra>
26
+ <RmTag v-if="person.role" variant="gray">{{ person.role }}</RmTag>
27
+ <OipScienceCommonScoreBadge :score="person.score" />
28
+ </template>
29
+ </OipScienceHcpsCards>
30
+
31
+ <RmResultCard v-else :actions="false">
32
+ <template #title>{{ ifNoRes(person.name) }}</template>
33
+ <template #subtitle>
34
+ {{ $t('oipScience.common.city') }}: {{ ifNoRes(person.city) }}
35
+ <template v-if="person.facility"> · {{ person.facility }}</template>
36
+ </template>
37
+ <template #meta>
38
+ <RmTag v-if="person.role" variant="gray">{{ person.role }}</RmTag>
39
+ <RmTag v-if="person.country" variant="gray" class="no-wrap">
40
+ {{ getCountryEmoji(person.country) }}
41
+ </RmTag>
42
+ </template>
43
+ <template #footer>
44
+ <OipScienceCommonSource :sources="sources" />
45
+ </template>
46
+ </RmResultCard>
47
+ </template>
48
+ </div>
49
+
50
+ <RmEmptyState v-if="!filtered.length" :title="$t('oipScience.common.noMatchingResult')" />
51
+ <RmButton
52
+ v-if="visible.length < filtered.length"
53
+ variant="secondary"
54
+ class="load_more"
55
+ @click="displayCount += PAGE_SIZE"
56
+ >{{ $t('oipScience.common.loadMore') }}</RmButton>
57
+ </div>
58
+ <RmLoadingState v-else :lines="3" :chart-height="240" />
59
+ </div>
60
+ </template>
61
+
62
+ <script setup lang="ts">
63
+ import indexConfig from '../../../oip-science/indexConfig.json';
64
+ import useOipElastic from '../../../oip-science/composables/useOipElastic';
65
+ import useAuthorMatching from '../../../oip-science/composables/useAuthorMatching';
66
+ import { getCountryEmoji, ifNoRes } from '../../../oip-science/utils/styleAndFormat';
67
+
68
+ export type LinkedPerson = {
69
+ name: string | null;
70
+ role?: string | null;
71
+ facility?: string | null;
72
+ city?: string | null;
73
+ country?: string | null;
74
+ rpps?: string | null;
75
+ score?: number | null;
76
+ };
77
+
78
+ const props = withDefaults(
79
+ defineProps<{
80
+ people: LinkedPerson[];
81
+ title: string;
82
+ sources: string[];
83
+ indexLastUpdate: string;
84
+ loading?: boolean;
85
+ }>(),
86
+ { loading: false },
87
+ );
88
+
89
+ const { normalize } = useAuthorMatching();
90
+ const { queryCustomElastic } = useOipElastic();
91
+
92
+ // Fiches annuaire des personnes reconnues, en une seule requete.
93
+ const hcpDocs = ref<Map<string, any>>(new Map());
94
+ const isLoadingDocs = ref(false);
95
+
96
+ async function loadHcpDocs(codes: (string | null | undefined)[]) {
97
+ const ids = [...new Set(codes.filter((x): x is string => !!x))].filter(
98
+ (id) => !hcpDocs.value.has(id),
99
+ );
100
+ if (!ids.length) return;
101
+ isLoadingDocs.value = true;
102
+ try {
103
+ const res = await queryCustomElastic({
104
+ index: indexConfig.hcps.index,
105
+ cluster: indexConfig.hcps.cluster,
106
+ customQuery: {
107
+ size: ids.length,
108
+ query: { terms: { hcp_code: ids } },
109
+ },
110
+ });
111
+ const map = new Map(hcpDocs.value);
112
+ for (const doc of res?.results || []) {
113
+ if (doc?.hcp_code) map.set(String(doc.hcp_code), doc);
114
+ }
115
+ hcpDocs.value = map;
116
+ } finally {
117
+ isLoadingDocs.value = false;
118
+ }
119
+ }
120
+
121
+ watch(
122
+ () => props.people.map((p) => p.rpps),
123
+ (codes) => loadHcpDocs(codes),
124
+ { immediate: true },
125
+ );
126
+
127
+ const searchText = ref('');
128
+ const PAGE_SIZE = 20;
129
+ const displayCount = ref(PAGE_SIZE);
130
+
131
+ const filtered = computed(() => {
132
+ const q = normalize(searchText.value);
133
+ if (!q) return props.people;
134
+ return props.people.filter((p) =>
135
+ normalize(
136
+ [p.name, p.role, p.facility, p.city, p.country, p.rpps].filter(Boolean).join(' '),
137
+ ).includes(q),
138
+ );
139
+ });
140
+
141
+ const visible = computed(() => filtered.value.slice(0, displayCount.value));
142
+
143
+ watch(searchText, () => {
144
+ displayCount.value = PAGE_SIZE;
145
+ });
146
+ </script>
147
+
148
+ <style scoped>
149
+ .load_more {
150
+ align-self: center;
151
+ justify-self: center;
152
+ }
153
+ </style>