@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.
- package/dist/module.json +1 -1
- package/dist/runtime/utils/projects.d.ts +8 -0
- package/dist/runtime/utils/projects.js +8 -5
- package/layer/app/app.config.ts +6 -0
- package/layer/app/components/OipPubmedSummary.vue +223 -0
- package/layer/app/components/OipResourceDrawer.vue +331 -0
- package/layer/app/components/OipResourceDrawerLink.vue +40 -0
- package/layer/app/components/OipResourceField.vue +41 -0
- package/layer/app/components/OipResourceGenericCard.vue +54 -0
- package/layer/app/components/OipResourceSource.vue +50 -0
- package/layer/app/components/OipResourceTags.vue +65 -0
- package/layer/app/components/OipResourceView.vue +107 -0
- package/layer/app/components/oip-drugs/fiches/AssociationFiche.vue +775 -0
- package/layer/app/components/oip-drugs/fiches/BrandFiche.vue +1111 -0
- package/layer/app/components/oip-drugs/fiches/IndicationFiche.vue +182 -0
- package/layer/app/components/oip-drugs/fiches/JoFiche.vue +101 -0
- package/layer/app/components/oip-drugs/fiches/PndsFiche.vue +279 -0
- package/layer/app/components/oip-drugs/heart/JoCard.vue +314 -0
- package/layer/app/components/oip-drugs/heart/TextHighlight.vue +326 -0
- package/layer/app/components/oip-drugs/heart/ansm/BrandCard.vue +187 -0
- package/layer/app/components/oip-drugs/heart/ansm/CisDetail.vue +363 -0
- package/layer/app/components/oip-drugs/heart/ansm/CisTable.vue +167 -0
- package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyCard.vue +373 -0
- package/layer/app/components/oip-drugs/heart/associations/NationalAssemblyFiche.vue +372 -0
- package/layer/app/components/oip-drugs/heart/associations/TranscriptionCard.vue +55 -0
- package/layer/app/components/oip-drugs/heart/elastic/SourceContainerElastic.vue +103 -0
- package/layer/app/components/oip-drugs/heart/france/BrandGroupCard.vue +450 -0
- package/layer/app/components/oip-drugs/heart/mitm/Card.vue +199 -0
- package/layer/app/components/oip-drugs/heart/mitm/Source.vue +23 -0
- package/layer/app/components/oip-drugs/heart/mitm/details/Overview.vue +381 -0
- package/layer/app/components/oip-drugs/heart/put/Card.vue +212 -0
- package/layer/app/components/oip-drugs/heart/results/AccesCard.vue +696 -0
- package/layer/app/components/oip-drugs/heart/results/AlertCard.vue +342 -0
- package/layer/app/components/oip-drugs/heart/results/AsmrCursor.vue +229 -0
- package/layer/app/components/oip-drugs/heart/results/EvalCard.vue +370 -0
- package/layer/app/components/oip-drugs/heart/results/IndicCard.vue +743 -0
- package/layer/app/components/oip-drugs/heart/results/MatchExplain.vue +243 -0
- package/layer/app/components/oip-drugs/heart/results/PrefRows.vue +105 -0
- package/layer/app/components/oip-drugs/heart/results/details/HasHistory.vue +487 -0
- package/layer/app/components/oip-drugs/heart/results/details/InternationalCountryCard.vue +461 -0
- package/layer/app/components/oip-drugs/heart/results/details/InternationalOverview.vue +200 -0
- package/layer/app/components/oip-drugs/heart/results/details/InternationalPriceDrawer.vue +647 -0
- package/layer/app/components/oip-drugs/heart/results/details/Monograph.vue +128 -0
- package/layer/app/components/oip-drugs/heart/results/details/OpinionDrawer.vue +535 -0
- package/layer/app/components/oip-drugs/heart/results/details/PresentationDrawer.vue +738 -0
- package/layer/app/components/oip-drugs/heart/results/details/Price.vue +227 -0
- package/layer/app/components/oip-drugs/heart/results/details/PriceOverview.vue +1261 -0
- package/layer/app/components/oip-drugs/heart/results/details/RecordDrawer.vue +494 -0
- package/layer/app/components/oip-drugs/heart/results/details/RecordList.vue +426 -0
- package/layer/app/components/oip-drugs/heart/results/les/Card.vue +340 -0
- package/layer/app/components/oip-drugs/heart/results/les/Drawer.vue +67 -0
- package/layer/app/components/oip-drugs/heart/results/les/IndicationDetail.vue +149 -0
- package/layer/app/components/oip-drugs/heart/results/les/Source.vue +20 -0
- package/layer/app/components/oip-drugs/heart/results/les/SourceTag.vue +39 -0
- package/layer/app/components/oip-drugs/heart/searchPage/DynamicSource.vue +29 -0
- package/layer/app/components/oip-drugs/medicam/market/Chart.vue +121 -0
- package/layer/app/components/oip-drugs/outpatient/ProductEvolution.vue +236 -0
- package/layer/app/components/oip-drugs/resources/Ansm.vue +75 -0
- package/layer/app/components/oip-drugs/resources/Association.vue +30 -0
- package/layer/app/components/oip-drugs/resources/Brand.vue +65 -0
- package/layer/app/components/oip-drugs/resources/Contribution.vue +216 -0
- package/layer/app/components/oip-drugs/resources/Hospital.vue +151 -0
- package/layer/app/components/oip-drugs/resources/Indication.vue +54 -0
- package/layer/app/components/oip-drugs/resources/Jo.vue +57 -0
- package/layer/app/components/oip-drugs/resources/Medicam.vue +140 -0
- package/layer/app/components/oip-drugs/resources/Mitm.vue +84 -0
- package/layer/app/components/oip-drugs/resources/NationalAssembly.vue +93 -0
- package/layer/app/components/oip-drugs/resources/Outpatient.vue +97 -0
- package/layer/app/components/oip-drugs/resources/Pnds.vue +24 -0
- package/layer/app/components/oip-drugs/resources/Price.vue +106 -0
- package/layer/app/components/oip-drugs/resources/Put.vue +143 -0
- package/layer/app/components/oip-science/appel-a-projet/Cards.vue +162 -0
- package/layer/app/components/oip-science/clinical-trials/Cards.vue +382 -0
- package/layer/app/components/oip-science/clinical-trials/CardsGlobal.vue +273 -0
- package/layer/app/components/oip-science/clinical-trials/LinkedList.vue +366 -0
- package/layer/app/components/oip-science/clinical-trials/PairBadge.vue +25 -0
- package/layer/app/components/oip-science/clinical-trials/PairBody.vue +182 -0
- package/layer/app/components/oip-science/clinical-trials/SitesDrawer.vue +95 -0
- package/layer/app/components/oip-science/common/Form.vue +73 -0
- package/layer/app/components/oip-science/common/MatchAvgTag.vue +283 -0
- package/layer/app/components/oip-science/common/NoData.vue +16 -0
- package/layer/app/components/oip-science/common/ScoreBadge.vue +35 -0
- package/layer/app/components/oip-science/common/Source.vue +31 -0
- package/layer/app/components/oip-science/common/TableTags.vue +279 -0
- package/layer/app/components/oip-science/common/Timeline.vue +104 -0
- package/layer/app/components/oip-science/common/ToolTips.vue +89 -0
- package/layer/app/components/oip-science/cross/publicationPubmed.vue +484 -0
- package/layer/app/components/oip-science/crossData/CrossDataLocations.vue +71 -0
- package/layer/app/components/oip-science/ctgov/Investigators.vue +148 -0
- package/layer/app/components/oip-science/ctgov/Locations.vue +102 -0
- package/layer/app/components/oip-science/ctgov/Publications.vue +52 -0
- package/layer/app/components/oip-science/ctgov/Sponsors.vue +66 -0
- package/layer/app/components/oip-science/ctgov/StudyDescription.vue +1136 -0
- package/layer/app/components/oip-science/ctgov-centers/ClinicalTrials.vue +40 -0
- package/layer/app/components/oip-science/ctgov-centers/Publications.vue +50 -0
- package/layer/app/components/oip-science/ctgov-investigators/ClinicalTrials.vue +43 -0
- package/layer/app/components/oip-science/ctgov-investigators/Informations.vue +171 -0
- package/layer/app/components/oip-science/ctgov-investigators/Publications.vue +44 -0
- package/layer/app/components/oip-science/elastic/DateRangeFilter.vue +209 -0
- package/layer/app/components/oip-science/elastic/Filter.vue +719 -0
- package/layer/app/components/oip-science/elastic/MatchExplain.vue +152 -0
- package/layer/app/components/oip-science/establishments/Cards.vue +183 -0
- package/layer/app/components/oip-science/establishments/ClinicalTrials.vue +62 -0
- package/layer/app/components/oip-science/establishments/Hcps.vue +227 -0
- package/layer/app/components/oip-science/establishments/Informations.vue +263 -0
- package/layer/app/components/oip-science/establishments/LinkedList.vue +153 -0
- package/layer/app/components/oip-science/establishments/MatchedLabels.vue +194 -0
- package/layer/app/components/oip-science/establishments/PublicFinance.vue +62 -0
- package/layer/app/components/oip-science/euct/Investigators.vue +125 -0
- package/layer/app/components/oip-science/euct/Locations.vue +102 -0
- package/layer/app/components/oip-science/euct/Products.vue +101 -0
- package/layer/app/components/oip-science/euct/Publications.vue +89 -0
- package/layer/app/components/oip-science/euct/Sponsors.vue +86 -0
- package/layer/app/components/oip-science/euct/StudyDescription.vue +1075 -0
- package/layer/app/components/oip-science/euct-centers/ClinicalTrials.vue +40 -0
- package/layer/app/components/oip-science/euct-centers/Publications.vue +55 -0
- package/layer/app/components/oip-science/euct-investigators/ClinicalTrials.vue +41 -0
- package/layer/app/components/oip-science/euct-investigators/Informations.vue +171 -0
- package/layer/app/components/oip-science/euct-investigators/Publications.vue +51 -0
- package/layer/app/components/oip-science/hcps/AppelsAProjet.vue +62 -0
- package/layer/app/components/oip-science/hcps/Cards.vue +237 -0
- package/layer/app/components/oip-science/hcps/ClinicalTrials.vue +78 -0
- package/layer/app/components/oip-science/hcps/Informations.vue +296 -0
- package/layer/app/components/oip-science/hcps/LinkedList.vue +153 -0
- package/layer/app/components/oip-science/hcps/MatchedLabels.vue +207 -0
- package/layer/app/components/oip-science/hcps/Pubmed.vue +128 -0
- package/layer/app/components/oip-science/local/SliderTabs.vue +99 -0
- package/layer/app/components/oip-science/local/SliderTabsFull.vue +135 -0
- package/layer/app/components/oip-science/pubmed/Authors.vue +128 -0
- package/layer/app/components/oip-science/pubmed/Cards.vue +417 -0
- package/layer/app/components/oip-science/pubmed/ClinicalTrials.vue +71 -0
- package/layer/app/components/oip-science/resources/Phrc.vue +39 -0
- package/layer/app/composables/useOipChapterScroll.ts +43 -0
- package/layer/app/composables/useOipResourceHooks.ts +27 -0
- package/layer/app/composables/useOipResourceNav.ts +66 -0
- package/layer/app/composables/useOipResourceViews.ts +117 -0
- package/layer/app/composables/useOipResources.ts +83 -0
- package/layer/app/oip-drugs/PORTING.md +120 -0
- package/layer/app/oip-drugs/composables/useAssociationDetail.ts +220 -0
- package/layer/app/oip-drugs/composables/useAssociationExport.ts +63 -0
- package/layer/app/oip-drugs/composables/useAssociationLookup.ts +105 -0
- package/layer/app/oip-drugs/composables/useCardPrefOptions.ts +201 -0
- package/layer/app/oip-drugs/composables/useCurrency.ts +121 -0
- package/layer/app/oip-drugs/composables/useDirectusSources.ts +224 -0
- package/layer/app/oip-drugs/composables/useHeartBrandMatches.ts +112 -0
- package/layer/app/oip-drugs/composables/useHeartDetailSearches.ts +479 -0
- package/layer/app/oip-drugs/composables/useHeartIntlApprovals.ts +661 -0
- package/layer/app/oip-drugs/composables/useHeartLesIndication.ts +168 -0
- package/layer/app/oip-drugs/composables/useHeartMatchExplain.ts +231 -0
- package/layer/app/oip-drugs/composables/useHeartReports.ts +239 -0
- package/layer/app/oip-drugs/composables/useHeartSearchFields.ts +233 -0
- package/layer/app/oip-drugs/composables/useMergeByKey.ts +23 -0
- package/layer/app/oip-drugs/composables/useOipDrugsElastic.ts +621 -0
- package/layer/app/oip-drugs/composables/usePageEmpty.ts +58 -0
- package/layer/app/oip-drugs/composables/v-useSalesSource.ts +64 -0
- package/layer/app/oip-drugs/drugs.css +500 -0
- package/layer/app/oip-drugs/esIndices.ts +322 -0
- package/layer/app/oip-drugs/kinds.ts +264 -0
- package/layer/app/oip-drugs/locales/en.json +1571 -0
- package/layer/app/oip-drugs/locales/fr.json +1571 -0
- package/layer/app/oip-drugs/monographs/blincyto-has.vue +289 -0
- package/layer/app/oip-drugs/monographs/darzalex-has.vue +276 -0
- package/layer/app/oip-drugs/monographs/dupixent-has.vue +216 -0
- package/layer/app/oip-drugs/monographs/enhertu-has.vue +280 -0
- package/layer/app/oip-drugs/monographs/has-sein-2023.vue +1649 -0
- package/layer/app/oip-drugs/monographs/ibrance-has.vue +277 -0
- package/layer/app/oip-drugs/monographs/imfinzi-has.vue +293 -0
- package/layer/app/oip-drugs/monographs/keytruda-has.vue +334 -0
- package/layer/app/oip-drugs/monographs/kisqali-has.vue +201 -0
- package/layer/app/oip-drugs/monographs/opdivo-has.vue +231 -0
- package/layer/app/oip-drugs/monographs/ozempic-has.vue +186 -0
- package/layer/app/oip-drugs/monographs/tecentriq-has.vue +312 -0
- package/layer/app/oip-drugs/monographs/yescarta-has.vue +272 -0
- package/layer/app/oip-drugs/types/elastic.ts +113 -0
- package/layer/app/oip-drugs/types/es/agencies.ts +152 -0
- package/layer/app/oip-drugs/types/es/directory.ts +54 -0
- package/layer/app/oip-drugs/types/es/docByIndex.ts +151 -0
- package/layer/app/oip-drugs/types/es/openmedic.ts +67 -0
- package/layer/app/oip-drugs/types/es/outpatient.ts +71 -0
- package/layer/app/oip-drugs/types/es/portal.ts +130 -0
- package/layer/app/oip-drugs/types/es/retail.ts +107 -0
- package/layer/app/oip-drugs/types/es/statistics.ts +29 -0
- package/layer/app/oip-drugs/types/heart/CurrencyRate.ts +16 -0
- package/layer/app/oip-drugs/types/heart/ansm.ts +106 -0
- package/layer/app/oip-drugs/types/heart/tag.ts +29 -0
- package/layer/app/oip-drugs/utils/associationFields.ts +118 -0
- package/layer/app/oip-drugs/utils/contributions.ts +36 -0
- package/layer/app/oip-drugs/utils/countryMapping.ts +387 -0
- package/layer/app/oip-drugs/utils/esHighlight.ts +21 -0
- package/layer/app/oip-drugs/utils/exportSheets.ts +57 -0
- package/layer/app/oip-drugs/utils/heartIntlPrice.ts +228 -0
- package/layer/app/oip-drugs/utils/heartStyleAndFormat.ts +262 -0
- package/layer/app/oip-drugs/utils/heartStyleAnsm.ts +71 -0
- package/layer/app/oip-drugs/utils/latestDate.ts +31 -0
- package/layer/app/oip-drugs/utils/p-heartCardTags.ts +53 -0
- package/layer/app/oip-drugs/utils/priceExport.ts +269 -0
- package/layer/app/oip-drugs/utils/q-hasRatings.ts +43 -0
- package/layer/app/oip-drugs/utils/s-openCard.ts +20 -0
- package/layer/app/oip-drugs/utils/sourceUuids.ts +155 -0
- package/layer/app/oip-drugs/utils/styleAndFormat.ts +40 -0
- package/layer/app/oip-drugs/utils/v-salesSeries.ts +156 -0
- package/layer/app/oip-drugs/utils/v-scopeTags.ts +23 -0
- package/layer/app/oip-science/PORTING.md +93 -0
- package/layer/app/oip-science/assets/smartSelection.json +428 -0
- package/layer/app/oip-science/composables/useAuthorMatching.ts +342 -0
- package/layer/app/oip-science/composables/useCTCenter.ts +107 -0
- package/layer/app/oip-science/composables/useCTInvestigators.ts +107 -0
- package/layer/app/oip-science/composables/useEstablishmentCrossData.ts +139 -0
- package/layer/app/oip-science/composables/useEstablishmentMatching.ts +176 -0
- package/layer/app/oip-science/composables/useEuct.ts +418 -0
- package/layer/app/oip-science/composables/useEuctCenter.ts +107 -0
- package/layer/app/oip-science/composables/useEuctInvestigators.ts +107 -0
- package/layer/app/oip-science/composables/useGlobalClinicalTrials.ts +624 -0
- package/layer/app/oip-science/composables/useLinkedTrials.ts +83 -0
- package/layer/app/oip-science/composables/useOipElastic.ts +658 -0
- package/layer/app/oip-science/composables/useOipSearchExplain.ts +166 -0
- package/layer/app/oip-science/composables/usePubmed.ts +487 -0
- package/layer/app/oip-science/composables/useSearchFields.ts +153 -0
- package/layer/app/oip-science/indexConfig.json +794 -0
- package/layer/app/oip-science/locales/en.json +483 -0
- package/layer/app/oip-science/locales/fr.json +483 -0
- package/layer/app/oip-science/science.css +1182 -0
- package/layer/app/oip-science/utils/a-openCard.ts +23 -0
- package/layer/app/oip-science/utils/b-openCard.ts +35 -0
- package/layer/app/oip-science/utils/c-filterContext.ts +81 -0
- package/layer/app/oip-science/utils/f-types.ts +241 -0
- package/layer/app/oip-science/utils/frDiseaseTerms.ts +269 -0
- package/layer/app/oip-science/utils/matchMethod.ts +53 -0
- package/layer/app/oip-science/utils/sliderTabs.ts +13 -0
- package/layer/app/oip-science/utils/sources.ts +15 -0
- package/layer/app/oip-science/utils/styleAndFormat.ts +608 -0
- package/layer/app/oip-science/utils/toPresets.ts +7 -0
- package/layer/app/oip-science/utils/valueTable.ts +126 -0
- package/layer/app/plugins/oip-science-i18n.ts +17 -0
- package/layer/app/utils/oipFicheRoutes.ts +118 -0
- package/layer/app/utils/oipResources.ts +440 -0
- package/layer/nuxt.config.ts +22 -2
- package/layer/public/reports/blincyto-has.html +292 -0
- package/layer/public/reports/blincyto-has.pdf +0 -0
- package/layer/public/reports/darzalex-has.html +279 -0
- package/layer/public/reports/darzalex-has.pdf +0 -0
- package/layer/public/reports/dupixent-has.html +217 -0
- package/layer/public/reports/dupixent-has.pdf +0 -0
- package/layer/public/reports/enhertu-has.html +283 -0
- package/layer/public/reports/enhertu-has.pdf +0 -0
- package/layer/public/reports/has-sein-2023.html +1639 -0
- package/layer/public/reports/ibrance-has.html +280 -0
- package/layer/public/reports/ibrance-has.pdf +0 -0
- package/layer/public/reports/imfinzi-has.html +297 -0
- package/layer/public/reports/imfinzi-has.pdf +0 -0
- package/layer/public/reports/keytruda-has.html +337 -0
- package/layer/public/reports/keytruda-has.pdf +0 -0
- package/layer/public/reports/kisqali-has.html +202 -0
- package/layer/public/reports/kisqali-has.pdf +0 -0
- package/layer/public/reports/opdivo-has.html +232 -0
- package/layer/public/reports/opdivo-has.pdf +0 -0
- package/layer/public/reports/ozempic-has.html +188 -0
- package/layer/public/reports/ozempic-has.pdf +0 -0
- package/layer/public/reports/tecentriq-has.html +315 -0
- package/layer/public/reports/tecentriq-has.pdf +0 -0
- package/layer/public/reports/yescarta-has.html +275 -0
- package/layer/public/reports/yescarta-has.pdf +0 -0
- package/layer/server/api/oip/es.post.ts +99 -0
- package/layer/server/api/oip/pdf.get.ts +70 -0
- package/layer/server/api/oip/reports.get.ts +98 -0
- package/layer/server/api/oip/resource-types.get.ts +22 -0
- package/package.json +1 -1
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import indexConfig from '../indexConfig.json';
|
|
2
|
+
import useOipElastic from './useOipElastic';
|
|
3
|
+
|
|
4
|
+
const normalize = (s: string | null | undefined) =>
|
|
5
|
+
(s || '')
|
|
6
|
+
.toString()
|
|
7
|
+
.normalize('NFD')
|
|
8
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
9
|
+
.replace(/[^a-zA-Z0-9]/g, '')
|
|
10
|
+
.toLowerCase();
|
|
11
|
+
|
|
12
|
+
export default function useAuthorMatching() {
|
|
13
|
+
const { queryCustomElastic } = useOipElastic();
|
|
14
|
+
|
|
15
|
+
type PubmedAuthorMatch = {
|
|
16
|
+
hcp_code: string;
|
|
17
|
+
first_name: string;
|
|
18
|
+
family_name: string;
|
|
19
|
+
match_method: string | null;
|
|
20
|
+
score: number | null;
|
|
21
|
+
address: Array<{
|
|
22
|
+
hco_et_code: string | null;
|
|
23
|
+
hco_et_label: string | null;
|
|
24
|
+
city: string | null;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
async function fetchPubmedAuthorMatches(pubmedId: string): Promise<PubmedAuthorMatch[]> {
|
|
29
|
+
const res = await queryCustomElastic({
|
|
30
|
+
index: indexConfig.hcpPubmed.index,
|
|
31
|
+
cluster: indexConfig.hcpPubmed.cluster,
|
|
32
|
+
customQuery: {
|
|
33
|
+
size: 1,
|
|
34
|
+
query: { term: { PubMed_ID: pubmedId } },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const doc = res?.results?.[0];
|
|
39
|
+
const matches: PubmedAuthorMatch[] = [];
|
|
40
|
+
for (const h of doc?.hcp || []) {
|
|
41
|
+
if (!h?.hcp_code || h.hcp_code.length !== 11) continue;
|
|
42
|
+
matches.push({
|
|
43
|
+
hcp_code: h.hcp_code,
|
|
44
|
+
first_name: h.hcp_first_name || '',
|
|
45
|
+
family_name: h.hcp_family_name || '',
|
|
46
|
+
match_method: h.match_method || null,
|
|
47
|
+
score: typeof h.score === 'number' ? h.score : null,
|
|
48
|
+
address: (h.address || []).map((a: any) => ({
|
|
49
|
+
hco_et_code: a.hco_et_code || null,
|
|
50
|
+
hco_et_label: a.hco_et_label || null,
|
|
51
|
+
city: a.hcp_city || null,
|
|
52
|
+
})),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return matches;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type TrialHcpMatch = {
|
|
59
|
+
hcp_code: string;
|
|
60
|
+
first_name: string;
|
|
61
|
+
family_name: string;
|
|
62
|
+
full_name: string;
|
|
63
|
+
match_method: string | null;
|
|
64
|
+
score: number | null;
|
|
65
|
+
specialty: Array<{ label: string; type: string }>;
|
|
66
|
+
address: Array<{
|
|
67
|
+
hco_et_label: string | null;
|
|
68
|
+
city: string | null;
|
|
69
|
+
dpt_label: string | null;
|
|
70
|
+
region_label: string | null;
|
|
71
|
+
}>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
async function fetchTrialHcpMatches(
|
|
75
|
+
trialId: string,
|
|
76
|
+
source: 'nct' | 'euct',
|
|
77
|
+
): Promise<TrialHcpMatch[]> {
|
|
78
|
+
const cfg = source === 'nct' ? indexConfig.hcpNct : indexConfig.hcpEuct;
|
|
79
|
+
const res = await queryCustomElastic({
|
|
80
|
+
index: cfg.index,
|
|
81
|
+
cluster: cfg.cluster,
|
|
82
|
+
customQuery: {
|
|
83
|
+
size: 1,
|
|
84
|
+
query: { term: { clinical_trial_id: trialId } },
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const doc = res?.results?.[0];
|
|
89
|
+
const matches: TrialHcpMatch[] = [];
|
|
90
|
+
for (const h of doc?.hcp || []) {
|
|
91
|
+
if (!h?.hcp_code || h.hcp_code.length !== 11) continue;
|
|
92
|
+
// NCT exposes `hcp_full_name`; EUCT exposes accented `first_name`/`last_name`
|
|
93
|
+
const firstAccented = h.first_name || h.hcp_first_name || '';
|
|
94
|
+
const familyAccented = h.last_name || h.hcp_family_name || '';
|
|
95
|
+
matches.push({
|
|
96
|
+
hcp_code: h.hcp_code,
|
|
97
|
+
first_name: h.hcp_first_name || '',
|
|
98
|
+
family_name: h.hcp_family_name || '',
|
|
99
|
+
full_name: h.hcp_full_name || `${firstAccented} ${familyAccented}`.trim(),
|
|
100
|
+
match_method: h.match_method || null,
|
|
101
|
+
score: typeof h.score === 'number' ? h.score : null,
|
|
102
|
+
specialty: (h.specialty || []).map((s: any) => ({
|
|
103
|
+
label: s.hcp_specialty_label_regroup || '',
|
|
104
|
+
type: s.hcp_specialty_type || '',
|
|
105
|
+
})),
|
|
106
|
+
address: (h.address || []).map((a: any) => ({
|
|
107
|
+
hco_et_code: a.hco_et_code || null,
|
|
108
|
+
hco_et_label: a.hco_et_label || null,
|
|
109
|
+
city: a.hcp_city || null,
|
|
110
|
+
dpt_label: a.hcp_dpt_label || null,
|
|
111
|
+
region_label: a.hcp_region_label || null,
|
|
112
|
+
})),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return matches;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const fetchNctHcpMatches = (nctId: string) => fetchTrialHcpMatches(nctId, 'nct');
|
|
119
|
+
const fetchEuctHcpMatches = (euctId: string) => fetchTrialHcpMatches(euctId, 'euct');
|
|
120
|
+
|
|
121
|
+
// Bulk variant: one ES request fetches all matches for many trial IDs at
|
|
122
|
+
// once. Used by the Generate-a-Report bulk export to enrich Investigators
|
|
123
|
+
// / Authors tabs with RPPS without firing N round-trips.
|
|
124
|
+
async function fetchTrialHcpMatchesBulk(
|
|
125
|
+
trialIds: string[],
|
|
126
|
+
source: 'nct' | 'euct' | 'pubmed',
|
|
127
|
+
): Promise<Map<string, TrialHcpMatch[]>> {
|
|
128
|
+
const out = new Map<string, TrialHcpMatch[]>();
|
|
129
|
+
if (!trialIds.length) return out;
|
|
130
|
+
const cfg =
|
|
131
|
+
source === 'nct'
|
|
132
|
+
? indexConfig.hcpNct
|
|
133
|
+
: source === 'euct'
|
|
134
|
+
? indexConfig.hcpEuct
|
|
135
|
+
: indexConfig.hcpPubmed;
|
|
136
|
+
const idField = source === 'pubmed' ? 'PubMed_ID' : 'clinical_trial_id';
|
|
137
|
+
const res = await queryCustomElastic({
|
|
138
|
+
index: cfg.index,
|
|
139
|
+
cluster: cfg.cluster,
|
|
140
|
+
customQuery: {
|
|
141
|
+
size: trialIds.length,
|
|
142
|
+
query: { terms: { [idField]: trialIds } },
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
for (const doc of res?.results || []) {
|
|
146
|
+
const id = doc?.[idField];
|
|
147
|
+
if (!id) continue;
|
|
148
|
+
const matches: TrialHcpMatch[] = [];
|
|
149
|
+
for (const h of doc?.hcp || []) {
|
|
150
|
+
if (!h?.hcp_code || h.hcp_code.length !== 11) continue;
|
|
151
|
+
const firstAccented = h.first_name || h.hcp_first_name || '';
|
|
152
|
+
const familyAccented = h.last_name || h.hcp_family_name || '';
|
|
153
|
+
matches.push({
|
|
154
|
+
hcp_code: h.hcp_code,
|
|
155
|
+
first_name: h.hcp_first_name || '',
|
|
156
|
+
family_name: h.hcp_family_name || '',
|
|
157
|
+
full_name:
|
|
158
|
+
h.hcp_full_name || `${firstAccented} ${familyAccented}`.trim(),
|
|
159
|
+
match_method: h.match_method || null,
|
|
160
|
+
score: typeof h.score === 'number' ? h.score : null,
|
|
161
|
+
specialty: (h.specialty || []).map((s: any) => ({
|
|
162
|
+
label: s.hcp_specialty_label_regroup || '',
|
|
163
|
+
type: s.hcp_specialty_type || '',
|
|
164
|
+
})),
|
|
165
|
+
address: (h.address || []).map((a: any) => ({
|
|
166
|
+
hco_et_code: a.hco_et_code || null,
|
|
167
|
+
hco_et_label: a.hco_et_label || null,
|
|
168
|
+
city: a.hcp_city || null,
|
|
169
|
+
dpt_label: a.hcp_dpt_label || null,
|
|
170
|
+
region_label: a.hcp_region_label || null,
|
|
171
|
+
})),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
out.set(String(id), matches);
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
type HcpTrialRow = {
|
|
180
|
+
clinical_trial_id: string;
|
|
181
|
+
result_id: string;
|
|
182
|
+
score: number | null;
|
|
183
|
+
match_method: string | null;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
async function fetchTrialsForHcps(
|
|
187
|
+
rppsIds: string[],
|
|
188
|
+
source: 'nct' | 'euct',
|
|
189
|
+
): Promise<HcpTrialRow[]> {
|
|
190
|
+
if (!rppsIds.length) return [];
|
|
191
|
+
const cfg = source === 'nct' ? indexConfig.hcpNct : indexConfig.hcpEuct;
|
|
192
|
+
const rppsSet = new Set(rppsIds);
|
|
193
|
+
const res = await queryCustomElastic({
|
|
194
|
+
index: cfg.index,
|
|
195
|
+
cluster: cfg.cluster,
|
|
196
|
+
customQuery: {
|
|
197
|
+
size: 10000,
|
|
198
|
+
_source: ['clinical_trial_id', 'hcp'],
|
|
199
|
+
query: {
|
|
200
|
+
nested: {
|
|
201
|
+
path: 'hcp',
|
|
202
|
+
query: { terms: { 'hcp.hcp_code.keyword': rppsIds } },
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
const rows: HcpTrialRow[] = [];
|
|
208
|
+
for (const doc of res?.results || []) {
|
|
209
|
+
if (!doc?.clinical_trial_id) continue;
|
|
210
|
+
for (const h of doc.hcp || []) {
|
|
211
|
+
if (!h?.hcp_code || !rppsSet.has(h.hcp_code)) continue;
|
|
212
|
+
rows.push({
|
|
213
|
+
clinical_trial_id: doc.clinical_trial_id,
|
|
214
|
+
result_id: h.hcp_code,
|
|
215
|
+
score: typeof h.score === 'number' ? h.score : null,
|
|
216
|
+
match_method: h.match_method || null,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return rows;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function filterExistingHcps(rppsIds: (string | null)[]): Promise<Set<string>> {
|
|
224
|
+
const ids = [...new Set(rppsIds.filter((x): x is string => !!x))];
|
|
225
|
+
if (ids.length === 0) return new Set();
|
|
226
|
+
const res = await queryCustomElastic({
|
|
227
|
+
index: indexConfig.hcps.index,
|
|
228
|
+
cluster: indexConfig.hcps.cluster,
|
|
229
|
+
customQuery: {
|
|
230
|
+
size: ids.length,
|
|
231
|
+
_source: ['hcp_code'],
|
|
232
|
+
query: { terms: { hcp_code: ids } },
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
const set = new Set<string>();
|
|
236
|
+
for (const r of res?.results || []) {
|
|
237
|
+
if (r?.hcp_code) set.add(r.hcp_code);
|
|
238
|
+
}
|
|
239
|
+
return set;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Build enriched author rows for one or many PubMed documents — pairs
|
|
243
|
+
// each author with its RPPS / FINESS / Matching Score / Match Method
|
|
244
|
+
// by querying the hcp-pubmed match index in a single bulk request.
|
|
245
|
+
// The returned rows feed the "Authors" tab on every PubMed Excel export.
|
|
246
|
+
type PubmedAuthorExportRow = {
|
|
247
|
+
pmid: string;
|
|
248
|
+
name: string | null;
|
|
249
|
+
affiliation: string | null;
|
|
250
|
+
rpps: string | null;
|
|
251
|
+
finess_et: string | null;
|
|
252
|
+
finess_label: string | null;
|
|
253
|
+
city: string | null;
|
|
254
|
+
score: number | null;
|
|
255
|
+
match_method: string | null;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
async function buildPubmedAuthorExportRows(
|
|
259
|
+
pubmedDocs: Array<{
|
|
260
|
+
PubMed_ID?: string | number;
|
|
261
|
+
authors?: Array<any>;
|
|
262
|
+
}>,
|
|
263
|
+
): Promise<PubmedAuthorExportRow[]> {
|
|
264
|
+
if (!pubmedDocs?.length) return [];
|
|
265
|
+
const pmids = pubmedDocs
|
|
266
|
+
.map((d) => (d.PubMed_ID != null ? String(d.PubMed_ID) : ''))
|
|
267
|
+
.filter((id) => id);
|
|
268
|
+
if (!pmids.length) return [];
|
|
269
|
+
|
|
270
|
+
const matchesByPmid = await fetchTrialHcpMatchesBulk(pmids, 'pubmed').catch(
|
|
271
|
+
() => new Map<string, TrialHcpMatch[]>(),
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const rows: PubmedAuthorExportRow[] = [];
|
|
275
|
+
for (const doc of pubmedDocs) {
|
|
276
|
+
const pmid = doc.PubMed_ID != null ? String(doc.PubMed_ID) : '';
|
|
277
|
+
const docMatches = matchesByPmid.get(pmid) || [];
|
|
278
|
+
// Index matches by normalized first+last and last+first for tolerant lookup.
|
|
279
|
+
const byName = new Map<string, TrialHcpMatch>();
|
|
280
|
+
for (const m of docMatches) {
|
|
281
|
+
const a = normalize(`${m.first_name || ''}${m.family_name || ''}`);
|
|
282
|
+
const b = normalize(`${m.family_name || ''}${m.first_name || ''}`);
|
|
283
|
+
if (a) byName.set(a, m);
|
|
284
|
+
if (b) byName.set(b, m);
|
|
285
|
+
}
|
|
286
|
+
const seen = new Set<string>();
|
|
287
|
+
for (const author of doc.authors || []) {
|
|
288
|
+
const forename = author?.forename || '';
|
|
289
|
+
const lastname = author?.lastname || '';
|
|
290
|
+
const fullName = `${forename} ${lastname}`.trim();
|
|
291
|
+
if (!fullName) continue;
|
|
292
|
+
const a = normalize(`${forename}${lastname}`);
|
|
293
|
+
const b = normalize(`${lastname}${forename}`);
|
|
294
|
+
const m = byName.get(a) || byName.get(b) || null;
|
|
295
|
+
if (m?.hcp_code) seen.add(m.hcp_code);
|
|
296
|
+
const primary = (m?.address || []).find((x: any) => x?.hco_et_code);
|
|
297
|
+
rows.push({
|
|
298
|
+
pmid,
|
|
299
|
+
name: fullName,
|
|
300
|
+
affiliation: author?.affiliation || null,
|
|
301
|
+
rpps: m?.hcp_code || null,
|
|
302
|
+
finess_et: primary?.hco_et_code || null,
|
|
303
|
+
finess_label: primary?.hco_et_label || null,
|
|
304
|
+
city: primary?.city || null,
|
|
305
|
+
score: m?.score ?? null,
|
|
306
|
+
match_method: m?.match_method ?? null,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
// Any matched HCP that wasn't paired to a doc author (e.g. matched on
|
|
310
|
+
// a normalized variant we didn't try) still deserves a row so the
|
|
311
|
+
// Excel reflects the full set of HCPs identified for this PMID.
|
|
312
|
+
for (const m of docMatches) {
|
|
313
|
+
if (!m.hcp_code || seen.has(m.hcp_code)) continue;
|
|
314
|
+
const primary = (m.address || []).find((x: any) => x?.hco_et_code);
|
|
315
|
+
rows.push({
|
|
316
|
+
pmid,
|
|
317
|
+
name: `${m.first_name || ''} ${m.family_name || ''}`.trim() || null,
|
|
318
|
+
affiliation: null,
|
|
319
|
+
rpps: m.hcp_code,
|
|
320
|
+
finess_et: primary?.hco_et_code || null,
|
|
321
|
+
finess_label: primary?.hco_et_label || null,
|
|
322
|
+
city: primary?.city || null,
|
|
323
|
+
score: typeof m.score === 'number' ? m.score : null,
|
|
324
|
+
match_method: m.match_method ?? null,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return rows;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
normalize,
|
|
333
|
+
fetchPubmedAuthorMatches,
|
|
334
|
+
buildPubmedAuthorExportRows,
|
|
335
|
+
fetchTrialHcpMatches,
|
|
336
|
+
fetchTrialHcpMatchesBulk,
|
|
337
|
+
fetchNctHcpMatches,
|
|
338
|
+
fetchEuctHcpMatches,
|
|
339
|
+
fetchTrialsForHcps,
|
|
340
|
+
filterExistingHcps,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import indexConfig from '../indexConfig.json';
|
|
2
|
+
import useOipElastic from './useOipElastic';
|
|
3
|
+
import type { CTCenters } from '../utils/f-types';
|
|
4
|
+
|
|
5
|
+
export default function useCTCenter() {
|
|
6
|
+
// Une instance par composant (dans FAS, useElastic etait appele au niveau du module)
|
|
7
|
+
const { queryCustomElastic, queryOneElastic } = useOipElastic();
|
|
8
|
+
const indexQuery = ref(indexConfig.globalCTCenters.index);
|
|
9
|
+
const cluster = ref(indexConfig.globalCTCenters.cluster);
|
|
10
|
+
|
|
11
|
+
const resultsCTCenters = ref(null as CTCenters | null);
|
|
12
|
+
const CTCentersResultsList = ref(null as CTCenters[] | null);
|
|
13
|
+
const isLoadingQueryCTCenters = ref(false);
|
|
14
|
+
|
|
15
|
+
const findCTCentersById = ({
|
|
16
|
+
id,
|
|
17
|
+
source,
|
|
18
|
+
}: {
|
|
19
|
+
id: string;
|
|
20
|
+
source?: string[];
|
|
21
|
+
}): Promise<CTCenters> => {
|
|
22
|
+
isLoadingQueryCTCenters.value = true;
|
|
23
|
+
return new Promise<CTCenters>(async (resolve, reject) => {
|
|
24
|
+
try {
|
|
25
|
+
const res = await queryOneElastic({
|
|
26
|
+
index: indexQuery.value,
|
|
27
|
+
path: '_id',
|
|
28
|
+
source: {
|
|
29
|
+
include: source || [],
|
|
30
|
+
exclude: [],
|
|
31
|
+
},
|
|
32
|
+
cluster: cluster.value,
|
|
33
|
+
id,
|
|
34
|
+
});
|
|
35
|
+
if (res) {
|
|
36
|
+
resultsCTCenters.value = res as CTCenters;
|
|
37
|
+
resolve(res as CTCenters);
|
|
38
|
+
} else {
|
|
39
|
+
// Document absent de l'index : la fiche montre son etat vide.
|
|
40
|
+
resolve(null as any);
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
reject(error);
|
|
44
|
+
} finally {
|
|
45
|
+
isLoadingQueryCTCenters.value = false;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const findManyCTCentersById = async ({
|
|
51
|
+
ids,
|
|
52
|
+
source,
|
|
53
|
+
}: {
|
|
54
|
+
ids: string[];
|
|
55
|
+
source?: string[];
|
|
56
|
+
}): Promise<CTCenters[]> => {
|
|
57
|
+
isLoadingQueryCTCenters.value = true;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const query = {
|
|
61
|
+
from: 0,
|
|
62
|
+
|
|
63
|
+
query: {
|
|
64
|
+
bool: {
|
|
65
|
+
must: [
|
|
66
|
+
{
|
|
67
|
+
terms: {
|
|
68
|
+
_id: ids,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
size: 1000,
|
|
75
|
+
track_total_hits: true,
|
|
76
|
+
_source: {
|
|
77
|
+
include: source || [],
|
|
78
|
+
exclude: [],
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const res = await queryCustomElastic({
|
|
83
|
+
index: indexQuery.value,
|
|
84
|
+
customQuery: query,
|
|
85
|
+
cluster: cluster.value,
|
|
86
|
+
});
|
|
87
|
+
if (res && res.results) {
|
|
88
|
+
CTCentersResultsList.value = res.results as CTCenters[];
|
|
89
|
+
return res.results as CTCenters[];
|
|
90
|
+
} else {
|
|
91
|
+
throw new Error('Aucun résultat trouvé');
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
throw error;
|
|
95
|
+
} finally {
|
|
96
|
+
isLoadingQueryCTCenters.value = false;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
resultsCTCenters,
|
|
102
|
+
CTCentersResultsList,
|
|
103
|
+
isLoadingQueryCTCenters,
|
|
104
|
+
findCTCentersById,
|
|
105
|
+
findManyCTCentersById,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import indexConfig from '../indexConfig.json';
|
|
2
|
+
import useOipElastic from './useOipElastic';
|
|
3
|
+
import type { CTInvestigators } from '../utils/f-types';
|
|
4
|
+
export default function useCTInvestigators() {
|
|
5
|
+
// Une instance par composant (dans FAS, useElastic etait appele au niveau du module)
|
|
6
|
+
const { queryCustomElastic, queryOneElastic } = useOipElastic();
|
|
7
|
+
const indexQuery = ref(indexConfig.globalCTInvestigators.index);
|
|
8
|
+
const cluster = ref(indexConfig.globalCTInvestigators.cluster);
|
|
9
|
+
|
|
10
|
+
const resultsCTInvestigators = ref(null as CTInvestigators | null);
|
|
11
|
+
const CTInvestigatorsResultsList = ref(null as CTInvestigators[] | null);
|
|
12
|
+
const isLoadingQueryCTInvestigators = ref(false);
|
|
13
|
+
|
|
14
|
+
const findCTInvestigatorsById = ({
|
|
15
|
+
id,
|
|
16
|
+
source,
|
|
17
|
+
}: {
|
|
18
|
+
id: string;
|
|
19
|
+
source?: string[];
|
|
20
|
+
}): Promise<CTInvestigators> => {
|
|
21
|
+
isLoadingQueryCTInvestigators.value = true;
|
|
22
|
+
return new Promise<CTInvestigators>(async (resolve, reject) => {
|
|
23
|
+
try {
|
|
24
|
+
const res = await queryOneElastic({
|
|
25
|
+
index: indexQuery.value,
|
|
26
|
+
path: '_id',
|
|
27
|
+
source: {
|
|
28
|
+
include: source || [],
|
|
29
|
+
exclude: ['pubmed_data.all_authors'],
|
|
30
|
+
},
|
|
31
|
+
cluster: cluster.value,
|
|
32
|
+
id,
|
|
33
|
+
});
|
|
34
|
+
if (res) {
|
|
35
|
+
resultsCTInvestigators.value = res as CTInvestigators;
|
|
36
|
+
resolve(res as CTInvestigators);
|
|
37
|
+
} else {
|
|
38
|
+
// Document absent de l'index : la fiche montre son etat vide.
|
|
39
|
+
resolve(null as any);
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
reject(error);
|
|
43
|
+
} finally {
|
|
44
|
+
isLoadingQueryCTInvestigators.value = false;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const findManyCTInvestigatorsById = async ({
|
|
50
|
+
ids,
|
|
51
|
+
source,
|
|
52
|
+
}: {
|
|
53
|
+
ids: string[];
|
|
54
|
+
source?: string[];
|
|
55
|
+
}): Promise<CTInvestigators[]> => {
|
|
56
|
+
isLoadingQueryCTInvestigators.value = true;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const query = {
|
|
60
|
+
from: 0,
|
|
61
|
+
|
|
62
|
+
query: {
|
|
63
|
+
bool: {
|
|
64
|
+
must: [
|
|
65
|
+
{
|
|
66
|
+
terms: {
|
|
67
|
+
_id: ids,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
size: 1000,
|
|
74
|
+
track_total_hits: true,
|
|
75
|
+
_source: {
|
|
76
|
+
include: source || [],
|
|
77
|
+
exclude: ['pubmed_data.all_authors'],
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const res = await queryCustomElastic({
|
|
82
|
+
index: indexQuery.value,
|
|
83
|
+
customQuery: query,
|
|
84
|
+
cluster: cluster.value,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (res && res.results) {
|
|
88
|
+
CTInvestigatorsResultsList.value = res.results as CTInvestigators[];
|
|
89
|
+
return res.results as CTInvestigators[];
|
|
90
|
+
} else {
|
|
91
|
+
throw new Error('Aucun résultat trouvé');
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
throw error;
|
|
95
|
+
} finally {
|
|
96
|
+
isLoadingQueryCTInvestigators.value = false;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
resultsCTInvestigators,
|
|
102
|
+
CTInvestigatorsResultsList,
|
|
103
|
+
isLoadingQueryCTInvestigators,
|
|
104
|
+
findCTInvestigatorsById,
|
|
105
|
+
findManyCTInvestigatorsById,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import indexConfig from '../indexConfig.json';
|
|
2
|
+
import useOipElastic from './useOipElastic';
|
|
3
|
+
|
|
4
|
+
interface CrossDataResult {
|
|
5
|
+
clinical_trial_id: string;
|
|
6
|
+
establishment_id: string;
|
|
7
|
+
verified_id?: string;
|
|
8
|
+
ai_matched_id?: string;
|
|
9
|
+
nct_id_linked?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
status?: string;
|
|
12
|
+
score?: number;
|
|
13
|
+
match_method?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface EstablishmentCrossData {
|
|
17
|
+
crossResults: CrossDataResult[];
|
|
18
|
+
nctIds: string[];
|
|
19
|
+
euctIds: string[];
|
|
20
|
+
nctToEuct: Record<string, string>;
|
|
21
|
+
euctLinkedToNct: Set<string>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function useEstablishmentCrossData() {
|
|
25
|
+
const crossData = useState<EstablishmentCrossData | null>('oip-science-establishment-cross-data', () => null);
|
|
26
|
+
const isLoading = useState<boolean>('oip-science-establishment-cross-data-loading', () => false);
|
|
27
|
+
const fetchedForId = useState<string | null>('oip-science-establishment-cross-data-id', () => null);
|
|
28
|
+
|
|
29
|
+
async function fetchCrossData(urlId: string) {
|
|
30
|
+
// Skip if already fetched for this establishment
|
|
31
|
+
if (fetchedForId.value === urlId && crossData.value) return crossData.value;
|
|
32
|
+
|
|
33
|
+
isLoading.value = true;
|
|
34
|
+
fetchedForId.value = urlId;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const { queryCustomElastic, queryOneElastic } = useOipElastic();
|
|
38
|
+
|
|
39
|
+
// 1. Fetch establishment doc from the new HCO directory
|
|
40
|
+
const establishment: any = await queryOneElastic({
|
|
41
|
+
index: indexConfig.establishments.index,
|
|
42
|
+
cluster: indexConfig.establishments.cluster,
|
|
43
|
+
path: 'hco_et_code',
|
|
44
|
+
id: urlId,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!establishment) {
|
|
48
|
+
crossData.value = {
|
|
49
|
+
crossResults: [],
|
|
50
|
+
nctIds: [],
|
|
51
|
+
euctIds: [],
|
|
52
|
+
nctToEuct: {},
|
|
53
|
+
euctLinkedToNct: new Set(),
|
|
54
|
+
};
|
|
55
|
+
return crossData.value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. Extract NCT and EUCT IDs from embedded arrays
|
|
59
|
+
const nctIds: string[] = (establishment.nct_trial || [])
|
|
60
|
+
.map((t: any) => t?.clinical_trial_id)
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
const euctIds: string[] = (establishment.euct_trial || [])
|
|
63
|
+
.map((t: any) => t?.clinical_trial_id)
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
|
|
66
|
+
// 3. Build top-scored name lookups so consumers can read item.name / item.score
|
|
67
|
+
const bestByMethodName = (entries: any[] | null | undefined): { name?: string; score?: number; match_method?: string } => {
|
|
68
|
+
if (!Array.isArray(entries) || entries.length === 0) return {};
|
|
69
|
+
// Take highest-scored entry
|
|
70
|
+
const top = [...entries].sort((a: any, b: any) => (b?.score ?? 0) - (a?.score ?? 0))[0];
|
|
71
|
+
return { name: top?.name, score: top?.score, match_method: top?.match_method };
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const nctMeta = bestByMethodName(establishment.nct_name);
|
|
75
|
+
const euctMeta = bestByMethodName(establishment.euct_name);
|
|
76
|
+
|
|
77
|
+
const crossResults: CrossDataResult[] = [
|
|
78
|
+
...nctIds.map((id: string) => ({
|
|
79
|
+
clinical_trial_id: id,
|
|
80
|
+
establishment_id: `FR-${urlId}`,
|
|
81
|
+
name: nctMeta.name,
|
|
82
|
+
score: nctMeta.score,
|
|
83
|
+
match_method: nctMeta.match_method,
|
|
84
|
+
})),
|
|
85
|
+
...euctIds.map((id: string) => ({
|
|
86
|
+
clinical_trial_id: id,
|
|
87
|
+
establishment_id: `FR-${urlId}`,
|
|
88
|
+
name: euctMeta.name,
|
|
89
|
+
score: euctMeta.score,
|
|
90
|
+
match_method: euctMeta.match_method,
|
|
91
|
+
})),
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// 4. Compute NCT <-> EUCT linkage by fetching EUCT details (only euct_number + nct_number)
|
|
95
|
+
const nctToEuct: Record<string, string> = {};
|
|
96
|
+
const euctLinkedToNct = new Set<string>();
|
|
97
|
+
const nctIdSet = new Set(nctIds);
|
|
98
|
+
|
|
99
|
+
if (euctIds.length > 0) {
|
|
100
|
+
const euctRes = await queryCustomElastic({
|
|
101
|
+
index: indexConfig.euct.index,
|
|
102
|
+
cluster: indexConfig.euct.cluster,
|
|
103
|
+
customQuery: {
|
|
104
|
+
size: euctIds.length,
|
|
105
|
+
_source: ['euct_number', 'nct_number'],
|
|
106
|
+
query: { terms: { 'euct_number.keyword': euctIds } },
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
for (const euct of euctRes?.results || []) {
|
|
111
|
+
if (euct?.nct_number && nctIdSet.has(euct.nct_number)) {
|
|
112
|
+
nctToEuct[euct.nct_number] = euct.euct_number;
|
|
113
|
+
euctLinkedToNct.add(euct.euct_number);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
crossData.value = { crossResults, nctIds, euctIds, nctToEuct, euctLinkedToNct };
|
|
119
|
+
return crossData.value;
|
|
120
|
+
} catch (e) {
|
|
121
|
+
console.error('Error fetching cross-data:', e);
|
|
122
|
+
return null;
|
|
123
|
+
} finally {
|
|
124
|
+
isLoading.value = false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function clear() {
|
|
129
|
+
crossData.value = null;
|
|
130
|
+
fetchedForId.value = null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
crossData,
|
|
135
|
+
isLoading,
|
|
136
|
+
fetchCrossData,
|
|
137
|
+
clear,
|
|
138
|
+
};
|
|
139
|
+
}
|