Skip to content

Commit 028a511

Browse files
committed
Added 5 additional BBQS projects missing from projects page. Cherry picked some changes from 75- branch
1 parent 2cce08f commit 028a511

File tree

4 files changed

+317
-69
lines changed

4 files changed

+317
-69
lines changed
Lines changed: 116 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,121 @@
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-
//
1118
import axios from 'axios';
1219
import * 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(/"pi"/g, 'ContributorRole.principalInvestigator')
180+
.replace(/"contact_pi"/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

Comments
 (0)