1- /* eslint-disable @typescript-eslint/no-explicit-any */
21/**
3- * Run with
4- * yarn ts-node scripts/fetchProjectMetadata.ts
2+ * fetchProjectMetadata.ts
53 *
4+ * Purpose:
5+ * Fetch project metadata from NIH RePORTER site and write to both a JSON and TS file.
6+ * - JSON file: May be useful for viewing, sharing, or exporting metadata
7+ * - TS file: Can be used to copy and paste metadata into src/constants/projects.ts
68 *
7- * Generated with assistance from ChatGPT.
9+ * Setup:
10+ * Modify grantNumbers list as needed
11+ *
12+ * Usage:
13+ * yarn tsx scripts/fetchProjectMetadata.ts
14+ *
15+ * This file was generated with assistance from ChatGPT.
816 */
917
10- //
1118import axios from 'axios' ;
1219import * as fs from 'fs' ;
13- import { Contributor , Person , ProjectMetadata } from 'src/models/projects' ;
20+ import {
21+ ContributorRole ,
22+ type Contributor ,
23+ type NIHProjectMetadata ,
24+ type Person ,
25+ } from 'src/models/projects' ;
26+
27+ const grantNumbers : string [ ] = [
28+ // NIH BBQS Project List
29+ 'U01DA063534' ,
30+ 'R61MH138705' ,
31+ 'R61MH135106' ,
32+ 'R34DA059510' ,
33+ 'R34DA059509' ,
34+ 'R34DA059513' ,
35+ 'R34DA059507' ,
36+ 'R34DA059718' ,
37+ 'R34DA059506' ,
38+ 'R34DA059512' ,
39+ 'R34DA059716' ,
40+ 'R34DA059723' ,
41+ 'R34DA059514' ,
42+ 'R34DA059500' ,
43+ 'R61MH135109' ,
44+ 'R61MH135114' ,
45+ 'R61MH135405' ,
46+ 'R61MH135407' ,
47+ 'R34DA061924' ,
48+ 'R34DA061984' ,
49+ 'R61MH138966' ,
50+ 'R34DA061925' ,
51+ 'R61MH138713' ,
52+ // EMBER Project - Kumar 2025
53+ // 'R21DA048634',
54+ // 'U01DA051235'
55+ ] ;
56+
57+ /**
58+ * Convert string to Title Case.
59+ * @param str string to convert
60+ * @returns string formatted with Title Case
61+ */
62+ function toTitleCase ( str : string ) : string {
63+ if ( ! str ) return str ;
64+ return str
65+ . toLowerCase ( )
66+ . replace ( / \b \w / g, ( char ) => char . toUpperCase ( ) )
67+ . replace ( / \s + / g, ' ' )
68+ . trim ( ) ;
69+ }
1470
1571// Function to query the NIH Reporter API
16- async function fetchProjectData ( grantNumber : string ) : Promise < ProjectMetadata | null > {
72+ async function fetchProjectData ( grantNumber : string ) : Promise < NIHProjectMetadata | null > {
1773 const url = 'https://api.reporter.nih.gov/v2/projects/search' ;
1874
1975 // API payload
20- const payload = {
21- criteria : {
22- project_nums : [ grantNumber ] ,
23- } ,
24- } ;
76+ const payload = { criteria : { project_nums : [ grantNumber ] } } ;
2577
2678 try {
2779 // Make the POST request to the API
2880 const response = await axios . post ( url , payload ) ;
81+ const projects = response . data ?. results ;
2982
3083 // Check if data is available
31- const projects = response . data ?. results ;
32- if ( projects && projects . length > 0 ) {
84+ if ( projects && projects . length !== 0 ) {
3385 const project = projects [ 0 ] ; // Assuming we want the first result
3486
3587 // Dates
3688 const startDate = new Date ( project . project_start_date ) ;
3789 const endDate = new Date ( project . project_end_date ) ;
3890 const yearsBetween = endDate . getFullYear ( ) - startDate . getFullYear ( ) ;
3991
40- const contributors : Contributor [ ] = project . principal_investigators . map ( ( pi : any ) => {
41- if ( pi . is_contact_pi ) {
42- return { name : pi . full_name , email : pi . email || undefined , roles : [ 'pi' , 'contact_pi' ] } ;
43- } else {
44- return { name : pi . full_name , email : pi . email || undefined , roles : [ 'pi' ] } ;
45- }
46- } ) ;
92+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93+ const contributors : Contributor [ ] = project . principal_investigators . map ( ( pi : any ) => ( {
94+ name : toTitleCase ( pi . full_name ) ,
95+ email : pi . email || undefined ,
96+ roles : pi . is_contact_pi
97+ ? [ ContributorRole . principalInvestigator , ContributorRole . contactPrincipalInvestigator ]
98+ : [ ContributorRole . principalInvestigator ] ,
99+ } ) ) ;
47100
48101 // Map the API response to the TypeScript object
49- const projectData : ProjectMetadata = {
102+ const projectData : NIHProjectMetadata = {
50103 funding : {
51104 awardTitle : project . project_title ,
52105 awardIdentifier : project . core_project_num ,
53106 activityCode : project . activity_code ,
54- awardeeOrganization : project . organization . org_name ,
107+ awardeeOrganization : toTitleCase ( project . organization . org_name ) ,
55108 startDate,
56109 periodOfPerformance : yearsBetween ,
57110 awardLink : project . project_detail_url ,
58- programOfficers : project . program_officers . map ( ( po : any ) => {
59- return { name : po . full_name } ;
60- } ) ,
61- principalInvestigators : contributors . map ( ( c : Contributor ) => c as Person ) ,
111+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
112+ programOfficers : project . program_officers . map ( ( po : any ) => ( {
113+ name : toTitleCase ( po . full_name ) ,
114+ } ) ) ,
115+ principalInvestigators : contributors . map ( ( c : Contributor ) => ( {
116+ name : c . name ,
117+ email : c . email ,
118+ } ) ) as Person [ ] ,
62119 } ,
63120 contributors : contributors ,
64121 species : [ ] ,
@@ -70,7 +127,7 @@ async function fetchProjectData(grantNumber: string): Promise<ProjectMetadata |
70127
71128 return projectData ;
72129 } else {
73- console . error ( ' No project data found for the provided grant number.' ) ;
130+ console . error ( ` No project data found for the provided grant number: ${ grantNumber } .` ) ;
74131 return null ;
75132 }
76133 } catch ( error ) {
@@ -79,29 +136,9 @@ async function fetchProjectData(grantNumber: string): Promise<ProjectMetadata |
79136 }
80137}
81138
82- // Example usage
83- ( async ( ) => {
84- const projects : ProjectMetadata [ ] = [ ] ;
85- const grantNumbers : string [ ] = [
86- 'U01DA063534' ,
87- 'R61MH138705' ,
88- 'R61MH135106' ,
89- 'R34DA059510' ,
90- 'R34DA059509' ,
91- 'R34DA059513' ,
92- 'R34DA059507' ,
93- 'R34DA059718' ,
94- 'R34DA059506' ,
95- 'R34DA059512' ,
96- 'R34DA059716' ,
97- 'R34DA059723' ,
98- 'R34DA059514' ,
99- 'R34DA059500' ,
100- 'R61MH135109' ,
101- 'R61MH135114' ,
102- 'R61MH135405' ,
103- 'R61MH135407' ,
104- ] ;
139+ // Usage
140+ await ( async ( ) => {
141+ const projects : NIHProjectMetadata [ ] = [ ] ;
105142
106143 for ( const grant of grantNumbers ) {
107144 const projectData = await fetchProjectData ( grant ) ;
@@ -115,14 +152,38 @@ async function fetchProjectData(grantNumber: string): Promise<ProjectMetadata |
115152 }
116153
117154 const content = `${ JSON . stringify ( projects , null , 2 ) } ` ;
118-
119- // Write to a file
120155 const today = new Date ( ) . toISOString ( ) ;
121- fs . writeFile ( `scripts/out/projectMetadata_${ today } .json` , content , ( err ) => {
156+
157+ // Write to a JSON file
158+ const jsonFilePath = `scripts/out/projectMetadata_${ today } .json` ;
159+ fs . writeFile ( jsonFilePath , content , ( err ) => {
122160 if ( err ) {
123161 console . error ( 'Error writing to file:' , err ) ;
124162 return ;
125163 }
126- console . log ( 'File has been written successfully.' ) ;
164+ console . log ( `JSON file written to ${ jsonFilePath } .` ) ;
127165 } ) ;
166+
167+ // Write to TS file (for easy copy and paste in src/constants/projects.ts)
168+ const tsFileContent = `// Project Metadata generated from fetchProjectMetadata.ts script.
169+ import type { NIHProjectMetadata } from 'src/models/projects';
170+ import { ContributorRole } from 'src/models/projects';
171+
172+ export const nihProjectMetadataList: NIHProjectMetadata[] = ${ JSON . stringify ( projects , null , 2 )
173+ // Dates → new Date()
174+ . replace (
175+ / " ( \d { 4 } - \d { 2 } - \d { 2 } T \d { 2 } : \d { 2 } : \d { 2 } \. \d { 3 } Z ) " / g,
176+ 'new Date("$1")'
177+ )
178+ // Enum replacements
179+ . replace ( / " p i " / g, 'ContributorRole.principalInvestigator' )
180+ . replace ( / " c o n t a c t _ p i " / g, 'ContributorRole.contactPrincipalInvestigator' )
181+ // Unquote keys
182+ . replace ( / " ( [ ^ " ] + ) " : / g, '$1:' )
183+ . replace ( / \\ n / g, '\n' ) } ;
184+ ` ;
185+
186+ const tsFilePath = `scripts/out/projectMetadata_${ today } .ts`
187+ fs . writeFileSync ( tsFilePath , tsFileContent , 'utf8' ) ;
188+ console . log ( `TypeScript file written to: ${ tsFilePath } ` ) ;
128189} ) ( ) ;
0 commit comments