@cyanheads/college-scorecard-mcp-server 0.1.1
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/CLAUDE.md +405 -0
- package/Dockerfile +98 -0
- package/README.md +356 -0
- package/changelog/0.1.x/0.1.0.md +24 -0
- package/changelog/0.1.x/0.1.1.md +14 -0
- package/changelog/template.md +119 -0
- package/dist/config/server-config.d.ts +12 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +17 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/data/cip-codes.d.ts +23 -0
- package/dist/data/cip-codes.d.ts.map +1 -0
- package/dist/data/cip-codes.js +274 -0
- package/dist/data/cip-codes.js.map +1 -0
- package/dist/data/field-catalog.d.ts +25 -0
- package/dist/data/field-catalog.d.ts.map +1 -0
- package/dist/data/field-catalog.js +122 -0
- package/dist/data/field-catalog.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/format-helpers.d.ts +9 -0
- package/dist/mcp-server/format-helpers.d.ts.map +1 -0
- package/dist/mcp-server/format-helpers.js +24 -0
- package/dist/mcp-server/format-helpers.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/compare.prompt.d.ts +15 -0
- package/dist/mcp-server/prompts/definitions/compare.prompt.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/compare.prompt.js +55 -0
- package/dist/mcp-server/prompts/definitions/compare.prompt.js.map +1 -0
- package/dist/mcp-server/resources/definitions/programs.resource.d.ts +10 -0
- package/dist/mcp-server/resources/definitions/programs.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/programs.resource.js +42 -0
- package/dist/mcp-server/resources/definitions/programs.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/school.resource.d.ts +10 -0
- package/dist/mcp-server/resources/definitions/school.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/school.resource.js +43 -0
- package/dist/mcp-server/resources/definitions/school.resource.js.map +1 -0
- package/dist/mcp-server/tools/definitions/compare-schools.tool.d.ts +46 -0
- package/dist/mcp-server/tools/definitions/compare-schools.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/compare-schools.tool.js +367 -0
- package/dist/mcp-server/tools/definitions/compare-schools.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-earnings.tool.d.ts +43 -0
- package/dist/mcp-server/tools/definitions/get-earnings.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/get-earnings.tool.js +183 -0
- package/dist/mcp-server/tools/definitions/get-earnings.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-programs.tool.d.ts +46 -0
- package/dist/mcp-server/tools/definitions/get-programs.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/get-programs.tool.js +194 -0
- package/dist/mcp-server/tools/definitions/get-programs.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-school.tool.d.ts +67 -0
- package/dist/mcp-server/tools/definitions/get-school.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/get-school.tool.js +324 -0
- package/dist/mcp-server/tools/definitions/get-school.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/list-fields.tool.d.ts +28 -0
- package/dist/mcp-server/tools/definitions/list-fields.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/list-fields.tool.js +92 -0
- package/dist/mcp-server/tools/definitions/list-fields.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/lookup-cip.tool.d.ts +26 -0
- package/dist/mcp-server/tools/definitions/lookup-cip.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/lookup-cip.tool.js +71 -0
- package/dist/mcp-server/tools/definitions/lookup-cip.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/search-programs.tool.d.ts +49 -0
- package/dist/mcp-server/tools/definitions/search-programs.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/search-programs.tool.js +188 -0
- package/dist/mcp-server/tools/definitions/search-programs.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/search-schools.tool.d.ts +57 -0
- package/dist/mcp-server/tools/definitions/search-schools.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/search-schools.tool.js +223 -0
- package/dist/mcp-server/tools/definitions/search-schools.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/value-analysis.tool.d.ts +37 -0
- package/dist/mcp-server/tools/definitions/value-analysis.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/value-analysis.tool.js +227 -0
- package/dist/mcp-server/tools/definitions/value-analysis.tool.js.map +1 -0
- package/dist/services/scorecard/scorecard-service.d.ts +44 -0
- package/dist/services/scorecard/scorecard-service.d.ts.map +1 -0
- package/dist/services/scorecard/scorecard-service.js +376 -0
- package/dist/services/scorecard/scorecard-service.js.map +1 -0
- package/dist/services/scorecard/types.d.ts +131 -0
- package/dist/services/scorecard/types.d.ts.map +1 -0
- package/dist/services/scorecard/types.js +6 -0
- package/dist/services/scorecard/types.js.map +1 -0
- package/package.json +80 -0
- package/server.json +99 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Program outcomes tool. Returns all field-of-study programs at one school
|
|
3
|
+
* with earnings, debt, and enrollment figures.
|
|
4
|
+
* @module mcp-server/tools/definitions/get-programs.tool
|
|
5
|
+
*/
|
|
6
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
8
|
+
export declare const getProgramsTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
9
|
+
id: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
10
|
+
cip_code: z.ZodOptional<z.ZodString>;
|
|
11
|
+
min_earnings: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
credential_level: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
14
|
+
school_id: z.ZodNumber;
|
|
15
|
+
school_name: z.ZodString;
|
|
16
|
+
programs: z.ZodArray<z.ZodObject<{
|
|
17
|
+
code: z.ZodString;
|
|
18
|
+
title: z.ZodOptional<z.ZodString>;
|
|
19
|
+
credential_level: z.ZodString;
|
|
20
|
+
earnings_1yr_median: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
earnings_count: z.ZodOptional<z.ZodNumber>;
|
|
22
|
+
median_debt: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
enrollment: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
suppressed: z.ZodBoolean;
|
|
25
|
+
suppression_note: z.ZodOptional<z.ZodString>;
|
|
26
|
+
}, z.core.$strip>>;
|
|
27
|
+
total: z.ZodNumber;
|
|
28
|
+
suppressed_count: z.ZodNumber;
|
|
29
|
+
notice: z.ZodOptional<z.ZodString>;
|
|
30
|
+
}, z.core.$strip>, readonly [{
|
|
31
|
+
readonly reason: "school_not_found";
|
|
32
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
33
|
+
readonly when: "No school record found for the requested ID.";
|
|
34
|
+
readonly recovery: "Verify the school ID using scorecard_search_schools and retry.";
|
|
35
|
+
}, {
|
|
36
|
+
readonly reason: "no_programs";
|
|
37
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
38
|
+
readonly when: "No programs matched the filters at this school.";
|
|
39
|
+
readonly recovery: "Remove the cip_code or min_earnings filter and retry to see all programs.";
|
|
40
|
+
}, {
|
|
41
|
+
readonly reason: "api_error";
|
|
42
|
+
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
43
|
+
readonly when: "The College Scorecard API returned an error.";
|
|
44
|
+
readonly recovery: "Check SCORECARD_API_KEY validity and retry after a brief delay.";
|
|
45
|
+
}]>;
|
|
46
|
+
//# sourceMappingURL=get-programs.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-programs.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-programs.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAkCjE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyL1B,CAAC"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Program outcomes tool. Returns all field-of-study programs at one school
|
|
3
|
+
* with earnings, debt, and enrollment figures.
|
|
4
|
+
* @module mcp-server/tools/definitions/get-programs.tool
|
|
5
|
+
*/
|
|
6
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
8
|
+
import { getScorecardService } from '../../../services/scorecard/scorecard-service.js';
|
|
9
|
+
const credentialLabel = (v) => {
|
|
10
|
+
if (v == null)
|
|
11
|
+
return 'Unknown';
|
|
12
|
+
const map = {
|
|
13
|
+
1: 'Certificate',
|
|
14
|
+
2: "Associate's",
|
|
15
|
+
3: "Bachelor's",
|
|
16
|
+
6: 'Post-baccalaureate certificate',
|
|
17
|
+
7: "Master's",
|
|
18
|
+
8: 'Doctoral',
|
|
19
|
+
17: 'Professional',
|
|
20
|
+
};
|
|
21
|
+
return map[v] ?? String(v);
|
|
22
|
+
};
|
|
23
|
+
const ProgramSchema = z.object({
|
|
24
|
+
code: z.string().describe('4-digit CIP program code.'),
|
|
25
|
+
title: z.string().optional().describe('Program title.'),
|
|
26
|
+
credential_level: z.string().describe('Degree/credential level.'),
|
|
27
|
+
earnings_1yr_median: z.number().optional().describe('Median earnings 1 year after graduation.'),
|
|
28
|
+
earnings_count: z.number().optional().describe('Number of Title IV students in earnings cohort.'),
|
|
29
|
+
median_debt: z.number().optional().describe('Median debt at graduation.'),
|
|
30
|
+
enrollment: z.number().optional().describe('IPEDS enrollment count.'),
|
|
31
|
+
suppressed: z
|
|
32
|
+
.boolean()
|
|
33
|
+
.describe('True when earnings data is suppressed due to small cohort (FERPA).'),
|
|
34
|
+
suppression_note: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Explains data suppression when suppressed=true.'),
|
|
38
|
+
});
|
|
39
|
+
export const getProgramsTool = tool('scorecard_get_programs', {
|
|
40
|
+
title: 'Get School Programs',
|
|
41
|
+
description: 'All field-of-study programs at one school with 1-year post-graduation earnings (P25/median/P75), debt at graduation, and enrollment figures. This is the primary source for program-level earnings — institution-level 6/8/10-year earnings are available via scorecard_get_earnings. Earnings may be suppressed (null) for programs with small cohorts due to FERPA privacy protection; suppressed=true flags this explicitly. Use scorecard_lookup_cip to find CIP codes by name.',
|
|
42
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
43
|
+
input: z.object({
|
|
44
|
+
id: z
|
|
45
|
+
.union([
|
|
46
|
+
z.string().describe('School unit ID as string.'),
|
|
47
|
+
z.number().describe('School unit ID as number.'),
|
|
48
|
+
])
|
|
49
|
+
.describe('School unit ID from scorecard_search_schools.'),
|
|
50
|
+
cip_code: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('Filter to a specific 4-digit CIP code (e.g. "11.07" for Computer Science). Returns only this program.'),
|
|
54
|
+
min_earnings: z
|
|
55
|
+
.number()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe('Minimum 1-year post-graduation median earnings filter.'),
|
|
58
|
+
credential_level: z
|
|
59
|
+
.number()
|
|
60
|
+
.int()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe('Filter by credential level: 1=certificate, 2=associate, 3=bachelor, 6=post-bac cert, 7=master, 8=doctoral, 17=professional.'),
|
|
63
|
+
}),
|
|
64
|
+
output: z.object({
|
|
65
|
+
school_id: z.number().describe('School unit ID.'),
|
|
66
|
+
school_name: z.string().describe('Institution name.'),
|
|
67
|
+
programs: z
|
|
68
|
+
.array(ProgramSchema.describe('Program outcome record.'))
|
|
69
|
+
.describe('All programs at this school matching the filters. Sorted by median earnings descending (suppressed programs at end).'),
|
|
70
|
+
total: z.number().describe('Total programs returned after filtering.'),
|
|
71
|
+
suppressed_count: z
|
|
72
|
+
.number()
|
|
73
|
+
.describe('Number of programs with earnings suppressed due to small cohort.'),
|
|
74
|
+
notice: z
|
|
75
|
+
.string()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe('Guidance when no programs match the filters or all data is suppressed.'),
|
|
78
|
+
}),
|
|
79
|
+
errors: [
|
|
80
|
+
{
|
|
81
|
+
reason: 'school_not_found',
|
|
82
|
+
code: JsonRpcErrorCode.NotFound,
|
|
83
|
+
when: 'No school record found for the requested ID.',
|
|
84
|
+
recovery: 'Verify the school ID using scorecard_search_schools and retry.',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
reason: 'no_programs',
|
|
88
|
+
code: JsonRpcErrorCode.NotFound,
|
|
89
|
+
when: 'No programs matched the filters at this school.',
|
|
90
|
+
recovery: 'Remove the cip_code or min_earnings filter and retry to see all programs.',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
reason: 'api_error',
|
|
94
|
+
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
95
|
+
when: 'The College Scorecard API returned an error.',
|
|
96
|
+
recovery: 'Check SCORECARD_API_KEY validity and retry after a brief delay.',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
async handler(input, ctx) {
|
|
100
|
+
ctx.log.info('Fetching school programs', { id: input.id, cip_code: input.cip_code });
|
|
101
|
+
const service = getScorecardService();
|
|
102
|
+
const response = await service.getSchoolPrograms(input.id, input.cip_code, input.min_earnings, input.credential_level, ctx);
|
|
103
|
+
const record = response.results[0];
|
|
104
|
+
if (!record) {
|
|
105
|
+
throw ctx.fail('school_not_found', `No school record found for ID ${input.id}.`, {
|
|
106
|
+
id: input.id,
|
|
107
|
+
recovery: { hint: 'Search for valid school IDs with scorecard_search_schools.' },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const rawPrograms = record['latest.programs.cip_4_digit'] ?? [];
|
|
111
|
+
// Filter programs by client-supplied criteria
|
|
112
|
+
let filtered = rawPrograms;
|
|
113
|
+
if (input.cip_code) {
|
|
114
|
+
filtered = filtered.filter((p) => p.code === input.cip_code);
|
|
115
|
+
}
|
|
116
|
+
if (input.credential_level != null) {
|
|
117
|
+
filtered = filtered.filter((p) => p.credential?.level === input.credential_level);
|
|
118
|
+
}
|
|
119
|
+
const programs = filtered.map((p) => {
|
|
120
|
+
const rawEarnings = p.earnings?.highest?.['1_yr']?.overall_median_earnings;
|
|
121
|
+
const suppressed = rawEarnings == null;
|
|
122
|
+
return {
|
|
123
|
+
code: p.code ?? 'Unknown',
|
|
124
|
+
...(p.title && { title: p.title }),
|
|
125
|
+
credential_level: credentialLabel(p.credential?.level),
|
|
126
|
+
...(rawEarnings != null && { earnings_1yr_median: rawEarnings }),
|
|
127
|
+
...(p.earnings?.highest?.['1_yr']?.overall_count_titleiv != null && {
|
|
128
|
+
earnings_count: p.earnings.highest['1_yr'].overall_count_titleiv,
|
|
129
|
+
}),
|
|
130
|
+
...(p.debt?.median_debt != null && { median_debt: p.debt.median_debt }),
|
|
131
|
+
...(p.counts?.ipeds_enrollment != null && { enrollment: p.counts.ipeds_enrollment }),
|
|
132
|
+
suppressed,
|
|
133
|
+
...(suppressed && {
|
|
134
|
+
suppression_note: 'Earnings data suppressed — cohort too small to report under FERPA privacy rules.',
|
|
135
|
+
}),
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
// Apply min_earnings filter after normalization (post-fetch)
|
|
139
|
+
const minEarnings = input.min_earnings;
|
|
140
|
+
const earningsFiltered = minEarnings != null
|
|
141
|
+
? programs.filter((p) => !p.suppressed && (p.earnings_1yr_median ?? 0) >= minEarnings)
|
|
142
|
+
: programs;
|
|
143
|
+
if (earningsFiltered.length === 0) {
|
|
144
|
+
const hint = filtered.length > 0
|
|
145
|
+
? 'Lower the min_earnings threshold or remove it to see all programs.'
|
|
146
|
+
: 'Remove the cip_code or credential_level filter to see all available programs.';
|
|
147
|
+
throw ctx.fail('no_programs', `No programs found at school ${input.id} matching the filters.`, {
|
|
148
|
+
recovery: { hint },
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// Sort: programs with earnings first, descending by earnings
|
|
152
|
+
const sorted = earningsFiltered.sort((a, b) => {
|
|
153
|
+
if (a.suppressed && !b.suppressed)
|
|
154
|
+
return 1;
|
|
155
|
+
if (!a.suppressed && b.suppressed)
|
|
156
|
+
return -1;
|
|
157
|
+
return (b.earnings_1yr_median ?? 0) - (a.earnings_1yr_median ?? 0);
|
|
158
|
+
});
|
|
159
|
+
const suppressed_count = sorted.filter((p) => p.suppressed).length;
|
|
160
|
+
const notice = suppressed_count > 0
|
|
161
|
+
? `${suppressed_count} of ${sorted.length} programs have suppressed earnings due to small cohorts (FERPA). These are listed last.`
|
|
162
|
+
: undefined;
|
|
163
|
+
return {
|
|
164
|
+
school_id: record.id ?? Number(input.id),
|
|
165
|
+
school_name: record['school.name'] ?? 'Unknown',
|
|
166
|
+
programs: sorted,
|
|
167
|
+
total: sorted.length,
|
|
168
|
+
suppressed_count,
|
|
169
|
+
...(notice && { notice }),
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
format: (result) => {
|
|
173
|
+
const lines = [
|
|
174
|
+
`## Programs at ${result.school_name} (ID: ${result.school_id})`,
|
|
175
|
+
`**Total:** ${result.total} | **Suppressed:** ${result.suppressed_count}`,
|
|
176
|
+
];
|
|
177
|
+
if (result.notice)
|
|
178
|
+
lines.push(`\n> ${result.notice}`);
|
|
179
|
+
for (const p of result.programs) {
|
|
180
|
+
lines.push(`\n**${p.code}** — ${p.title ?? 'Unknown title'} (${p.credential_level})`);
|
|
181
|
+
lines.push(`1-Year Earnings (median): ${p.earnings_1yr_median != null ? `$${p.earnings_1yr_median.toLocaleString()}` : p.suppressed ? 'Suppressed (small cohort, FERPA)' : 'Not available'}`);
|
|
182
|
+
if (p.suppression_note != null)
|
|
183
|
+
lines.push(`Suppression note: ${p.suppression_note}`);
|
|
184
|
+
if (p.median_debt != null)
|
|
185
|
+
lines.push(`Median Debt: $${p.median_debt.toLocaleString()}`);
|
|
186
|
+
if (p.enrollment != null)
|
|
187
|
+
lines.push(`Enrollment: ${p.enrollment.toLocaleString()}`);
|
|
188
|
+
if (p.earnings_count != null)
|
|
189
|
+
lines.push(`Earnings Cohort: ${p.earnings_count} students`);
|
|
190
|
+
}
|
|
191
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
//# sourceMappingURL=get-programs.tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-programs.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-programs.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview School profile tool. Fetches full institutional profiles for one or more
|
|
3
|
+
* school IDs — costs, admissions, outcomes, aid, and completion rates.
|
|
4
|
+
* @module mcp-server/tools/definitions/get-school.tool
|
|
5
|
+
*/
|
|
6
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
8
|
+
export declare const getSchoolTool: import("@cyanheads/mcp-ts-core").ToolDefinition<z.ZodObject<{
|
|
9
|
+
id: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>]>;
|
|
10
|
+
fields: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
12
|
+
schools: z.ZodArray<z.ZodObject<{
|
|
13
|
+
id: z.ZodNumber;
|
|
14
|
+
name: z.ZodString;
|
|
15
|
+
city: z.ZodOptional<z.ZodString>;
|
|
16
|
+
state: z.ZodOptional<z.ZodString>;
|
|
17
|
+
zip: z.ZodOptional<z.ZodString>;
|
|
18
|
+
url: z.ZodOptional<z.ZodString>;
|
|
19
|
+
ownership: z.ZodString;
|
|
20
|
+
degree_level: z.ZodString;
|
|
21
|
+
hbcu: z.ZodOptional<z.ZodBoolean>;
|
|
22
|
+
enrollment: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
admission_rate: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
sat_average: z.ZodOptional<z.ZodNumber>;
|
|
25
|
+
sat_reading_25: z.ZodOptional<z.ZodNumber>;
|
|
26
|
+
sat_reading_75: z.ZodOptional<z.ZodNumber>;
|
|
27
|
+
sat_math_25: z.ZodOptional<z.ZodNumber>;
|
|
28
|
+
sat_math_75: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
act_cumulative_25: z.ZodOptional<z.ZodNumber>;
|
|
30
|
+
act_cumulative_75: z.ZodOptional<z.ZodNumber>;
|
|
31
|
+
tuition_in_state: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
tuition_out_of_state: z.ZodOptional<z.ZodNumber>;
|
|
33
|
+
net_price_overall: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
net_price_0_30k: z.ZodOptional<z.ZodNumber>;
|
|
35
|
+
net_price_30k_48k: z.ZodOptional<z.ZodNumber>;
|
|
36
|
+
net_price_48k_75k: z.ZodOptional<z.ZodNumber>;
|
|
37
|
+
net_price_75k_110k: z.ZodOptional<z.ZodNumber>;
|
|
38
|
+
net_price_110k_plus: z.ZodOptional<z.ZodNumber>;
|
|
39
|
+
cost_of_attendance: z.ZodOptional<z.ZodNumber>;
|
|
40
|
+
median_debt: z.ZodOptional<z.ZodNumber>;
|
|
41
|
+
pell_grant_rate: z.ZodOptional<z.ZodNumber>;
|
|
42
|
+
federal_loan_rate: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
repayment_rate_3yr: z.ZodOptional<z.ZodNumber>;
|
|
44
|
+
completion_rate: z.ZodOptional<z.ZodNumber>;
|
|
45
|
+
earnings_6yr_median: z.ZodOptional<z.ZodNumber>;
|
|
46
|
+
earnings_6yr_p25: z.ZodOptional<z.ZodNumber>;
|
|
47
|
+
earnings_6yr_p75: z.ZodOptional<z.ZodNumber>;
|
|
48
|
+
earnings_8yr_median: z.ZodOptional<z.ZodNumber>;
|
|
49
|
+
earnings_10yr_median: z.ZodOptional<z.ZodNumber>;
|
|
50
|
+
earnings_10yr_p25: z.ZodOptional<z.ZodNumber>;
|
|
51
|
+
earnings_10yr_p75: z.ZodOptional<z.ZodNumber>;
|
|
52
|
+
}, z.core.$strip>>;
|
|
53
|
+
total_requested: z.ZodNumber;
|
|
54
|
+
total_found: z.ZodNumber;
|
|
55
|
+
notice: z.ZodOptional<z.ZodString>;
|
|
56
|
+
}, z.core.$strip>, readonly [{
|
|
57
|
+
readonly reason: "not_found";
|
|
58
|
+
readonly code: JsonRpcErrorCode.NotFound;
|
|
59
|
+
readonly when: "None of the requested school IDs returned a record.";
|
|
60
|
+
readonly recovery: "Verify the unit IDs using scorecard_search_schools and retry with valid IDs.";
|
|
61
|
+
}, {
|
|
62
|
+
readonly reason: "api_error";
|
|
63
|
+
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
64
|
+
readonly when: "The College Scorecard API returned an error.";
|
|
65
|
+
readonly recovery: "Check SCORECARD_API_KEY validity and retry after a brief delay.";
|
|
66
|
+
}]>;
|
|
67
|
+
//# sourceMappingURL=get-school.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-school.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-school.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAwFjE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0PxB,CAAC"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview School profile tool. Fetches full institutional profiles for one or more
|
|
3
|
+
* school IDs — costs, admissions, outcomes, aid, and completion rates.
|
|
4
|
+
* @module mcp-server/tools/definitions/get-school.tool
|
|
5
|
+
*/
|
|
6
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
8
|
+
import { degreeLevelLabel, ownershipLabel } from '../../../mcp-server/format-helpers.js';
|
|
9
|
+
import { getScorecardService } from '../../../services/scorecard/scorecard-service.js';
|
|
10
|
+
function fmt(n, prefix = '') {
|
|
11
|
+
if (n == null)
|
|
12
|
+
return 'Not available';
|
|
13
|
+
return `${prefix}${n.toLocaleString()}`;
|
|
14
|
+
}
|
|
15
|
+
function fmtPct(n) {
|
|
16
|
+
if (n == null)
|
|
17
|
+
return 'Not available';
|
|
18
|
+
return `${(n * 100).toFixed(1)}%`;
|
|
19
|
+
}
|
|
20
|
+
const SchoolProfileSchema = z.object({
|
|
21
|
+
id: z.number().describe('Unit ID.'),
|
|
22
|
+
name: z.string().describe('Institution name.'),
|
|
23
|
+
city: z.string().optional().describe('City.'),
|
|
24
|
+
state: z.string().optional().describe('State code.'),
|
|
25
|
+
zip: z.string().optional().describe('ZIP code.'),
|
|
26
|
+
url: z.string().optional().describe('Institution website.'),
|
|
27
|
+
ownership: z.string().describe('Control type: Public, Private nonprofit, or For-profit.'),
|
|
28
|
+
degree_level: z.string().describe('Predominant degree awarded.'),
|
|
29
|
+
hbcu: z.boolean().optional().describe('Historically Black College or University.'),
|
|
30
|
+
enrollment: z.number().optional().describe('Undergraduate enrollment.'),
|
|
31
|
+
// Admissions
|
|
32
|
+
admission_rate: z.number().optional().describe('Overall admission rate (0–1).'),
|
|
33
|
+
sat_average: z.number().optional().describe('Average SAT score.'),
|
|
34
|
+
sat_reading_25: z.number().optional().describe('SAT Critical Reading 25th percentile.'),
|
|
35
|
+
sat_reading_75: z.number().optional().describe('SAT Critical Reading 75th percentile.'),
|
|
36
|
+
sat_math_25: z.number().optional().describe('SAT Math 25th percentile.'),
|
|
37
|
+
sat_math_75: z.number().optional().describe('SAT Math 75th percentile.'),
|
|
38
|
+
act_cumulative_25: z.number().optional().describe('ACT composite 25th percentile.'),
|
|
39
|
+
act_cumulative_75: z.number().optional().describe('ACT composite 75th percentile.'),
|
|
40
|
+
// Cost
|
|
41
|
+
tuition_in_state: z.number().optional().describe('In-state tuition and fees.'),
|
|
42
|
+
tuition_out_of_state: z.number().optional().describe('Out-of-state tuition and fees.'),
|
|
43
|
+
net_price_overall: z.number().optional().describe('Average net price (all income levels).'),
|
|
44
|
+
net_price_0_30k: z
|
|
45
|
+
.number()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('Average net price for family income $0–$30,000.'),
|
|
48
|
+
net_price_30k_48k: z
|
|
49
|
+
.number()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe('Average net price for family income $30,001–$48,000.'),
|
|
52
|
+
net_price_48k_75k: z
|
|
53
|
+
.number()
|
|
54
|
+
.optional()
|
|
55
|
+
.describe('Average net price for family income $48,001–$75,000.'),
|
|
56
|
+
net_price_75k_110k: z
|
|
57
|
+
.number()
|
|
58
|
+
.optional()
|
|
59
|
+
.describe('Average net price for family income $75,001–$110,000.'),
|
|
60
|
+
net_price_110k_plus: z
|
|
61
|
+
.number()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe('Average net price for family income $110,001+.'),
|
|
64
|
+
cost_of_attendance: z.number().optional().describe('Total cost of attendance per year.'),
|
|
65
|
+
// Aid
|
|
66
|
+
median_debt: z.number().optional().describe('Median debt at graduation (completers).'),
|
|
67
|
+
pell_grant_rate: z.number().optional().describe('Share of students receiving Pell grants (0–1).'),
|
|
68
|
+
federal_loan_rate: z
|
|
69
|
+
.number()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe('Share of students receiving federal loans (0–1).'),
|
|
72
|
+
repayment_rate_3yr: z
|
|
73
|
+
.number()
|
|
74
|
+
.optional()
|
|
75
|
+
.describe('3-year loan repayment rate (share not in default, 0–1).'),
|
|
76
|
+
// Completion
|
|
77
|
+
completion_rate: z.number().optional().describe('Completion rate at 150% normal time.'),
|
|
78
|
+
// Earnings
|
|
79
|
+
earnings_6yr_median: z.number().optional().describe('Median earnings 6 years after entry.'),
|
|
80
|
+
earnings_6yr_p25: z.number().optional().describe('25th percentile earnings 6 years after entry.'),
|
|
81
|
+
earnings_6yr_p75: z.number().optional().describe('75th percentile earnings 6 years after entry.'),
|
|
82
|
+
earnings_8yr_median: z.number().optional().describe('Median earnings 8 years after entry.'),
|
|
83
|
+
earnings_10yr_median: z.number().optional().describe('Median earnings 10 years after entry.'),
|
|
84
|
+
earnings_10yr_p25: z
|
|
85
|
+
.number()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe('25th percentile earnings 10 years after entry.'),
|
|
88
|
+
earnings_10yr_p75: z
|
|
89
|
+
.number()
|
|
90
|
+
.optional()
|
|
91
|
+
.describe('75th percentile earnings 10 years after entry.'),
|
|
92
|
+
});
|
|
93
|
+
export const getSchoolTool = tool('scorecard_get_school', {
|
|
94
|
+
title: 'Get School Profile',
|
|
95
|
+
description: 'Full institutional profile for one or more school IDs — costs, admissions, outcomes, aid, demographics, and completion rates. Pass a single ID or an array of up to 100 IDs. For side-by-side comparison on a specific dimension (costs, admissions, outcomes, or aid) use scorecard_compare_schools. The fields parameter accepts a comma-separated list of field paths from scorecard_list_fields to override the default field set.',
|
|
96
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
97
|
+
input: z.object({
|
|
98
|
+
id: z
|
|
99
|
+
.union([
|
|
100
|
+
z.string().describe('Single school unit ID as string.'),
|
|
101
|
+
z.number().describe('Single school unit ID as number.'),
|
|
102
|
+
z
|
|
103
|
+
.array(z
|
|
104
|
+
.union([
|
|
105
|
+
z.string().describe('School unit ID as string.'),
|
|
106
|
+
z.number().describe('School unit ID as number.'),
|
|
107
|
+
])
|
|
108
|
+
.describe('School unit ID (string or number).'))
|
|
109
|
+
.describe('Array of school unit IDs (up to 100).'),
|
|
110
|
+
])
|
|
111
|
+
.describe('School unit ID or array of unit IDs (up to 100). IDs are integers from scorecard_search_schools.'),
|
|
112
|
+
fields: z
|
|
113
|
+
.string()
|
|
114
|
+
.optional()
|
|
115
|
+
.describe('Optional comma-separated list of custom field paths from scorecard_list_fields. Overrides the default field set.'),
|
|
116
|
+
}),
|
|
117
|
+
output: z.object({
|
|
118
|
+
schools: z
|
|
119
|
+
.array(SchoolProfileSchema.describe('Full institutional profile.'))
|
|
120
|
+
.describe('Full profiles for the requested school(s).'),
|
|
121
|
+
total_requested: z.number().describe('Number of IDs requested.'),
|
|
122
|
+
total_found: z.number().describe('Number of records returned.'),
|
|
123
|
+
notice: z.string().optional().describe('Warning when some requested IDs returned no record.'),
|
|
124
|
+
}),
|
|
125
|
+
errors: [
|
|
126
|
+
{
|
|
127
|
+
reason: 'not_found',
|
|
128
|
+
code: JsonRpcErrorCode.NotFound,
|
|
129
|
+
when: 'None of the requested school IDs returned a record.',
|
|
130
|
+
recovery: 'Verify the unit IDs using scorecard_search_schools and retry with valid IDs.',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
reason: 'api_error',
|
|
134
|
+
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
135
|
+
when: 'The College Scorecard API returned an error.',
|
|
136
|
+
recovery: 'Check SCORECARD_API_KEY validity and retry after a brief delay.',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
async handler(input, ctx) {
|
|
140
|
+
const ids = Array.isArray(input.id) ? input.id : [input.id];
|
|
141
|
+
ctx.log.info('Fetching school profiles', { ids: ids.slice(0, 5), count: ids.length });
|
|
142
|
+
const service = getScorecardService();
|
|
143
|
+
const response = await service.getSchoolProfiles(ids, input.fields, ctx);
|
|
144
|
+
if (response.results.length === 0) {
|
|
145
|
+
throw ctx.fail('not_found', `No records found for ${ids.length} requested IDs.`, {
|
|
146
|
+
ids,
|
|
147
|
+
recovery: { hint: 'Search for valid school IDs using scorecard_search_schools first.' },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const schools = response.results.map((r) => ({
|
|
151
|
+
id: r.id ?? 0,
|
|
152
|
+
name: r['school.name'] ?? 'Unknown',
|
|
153
|
+
ownership: ownershipLabel(r['school.ownership']),
|
|
154
|
+
degree_level: degreeLevelLabel(r['school.degrees_awarded.predominant']),
|
|
155
|
+
...(r['school.city'] && { city: r['school.city'] }),
|
|
156
|
+
...(r['school.state'] && { state: r['school.state'] }),
|
|
157
|
+
...(r['school.zip'] && { zip: r['school.zip'] }),
|
|
158
|
+
...(r['school.school_url'] && { url: r['school.school_url'] }),
|
|
159
|
+
...(r['school.hbcu'] === 1 && { hbcu: true }),
|
|
160
|
+
...(r['latest.student.size'] != null && { enrollment: r['latest.student.size'] }),
|
|
161
|
+
...(r['latest.admissions.admission_rate.overall'] != null && {
|
|
162
|
+
admission_rate: r['latest.admissions.admission_rate.overall'],
|
|
163
|
+
}),
|
|
164
|
+
...(r['latest.admissions.sat_scores.average.overall'] != null && {
|
|
165
|
+
sat_average: r['latest.admissions.sat_scores.average.overall'],
|
|
166
|
+
}),
|
|
167
|
+
...(r['latest.admissions.sat_scores.25th_percentile.critical_reading'] != null && {
|
|
168
|
+
sat_reading_25: r['latest.admissions.sat_scores.25th_percentile.critical_reading'],
|
|
169
|
+
}),
|
|
170
|
+
...(r['latest.admissions.sat_scores.75th_percentile.critical_reading'] != null && {
|
|
171
|
+
sat_reading_75: r['latest.admissions.sat_scores.75th_percentile.critical_reading'],
|
|
172
|
+
}),
|
|
173
|
+
...(r['latest.admissions.sat_scores.25th_percentile.math'] != null && {
|
|
174
|
+
sat_math_25: r['latest.admissions.sat_scores.25th_percentile.math'],
|
|
175
|
+
}),
|
|
176
|
+
...(r['latest.admissions.sat_scores.75th_percentile.math'] != null && {
|
|
177
|
+
sat_math_75: r['latest.admissions.sat_scores.75th_percentile.math'],
|
|
178
|
+
}),
|
|
179
|
+
...(r['latest.admissions.act_scores.25th_percentile.cumulative'] != null && {
|
|
180
|
+
act_cumulative_25: r['latest.admissions.act_scores.25th_percentile.cumulative'],
|
|
181
|
+
}),
|
|
182
|
+
...(r['latest.admissions.act_scores.75th_percentile.cumulative'] != null && {
|
|
183
|
+
act_cumulative_75: r['latest.admissions.act_scores.75th_percentile.cumulative'],
|
|
184
|
+
}),
|
|
185
|
+
...(r['latest.cost.tuition.in_state'] != null && {
|
|
186
|
+
tuition_in_state: r['latest.cost.tuition.in_state'],
|
|
187
|
+
}),
|
|
188
|
+
...(r['latest.cost.tuition.out_of_state'] != null && {
|
|
189
|
+
tuition_out_of_state: r['latest.cost.tuition.out_of_state'],
|
|
190
|
+
}),
|
|
191
|
+
...(r['latest.cost.avg_net_price.overall'] != null && {
|
|
192
|
+
net_price_overall: r['latest.cost.avg_net_price.overall'],
|
|
193
|
+
}),
|
|
194
|
+
...(r['latest.cost.avg_net_price.by_income.0-30000'] != null && {
|
|
195
|
+
net_price_0_30k: r['latest.cost.avg_net_price.by_income.0-30000'],
|
|
196
|
+
}),
|
|
197
|
+
...(r['latest.cost.avg_net_price.by_income.30001-48000'] != null && {
|
|
198
|
+
net_price_30k_48k: r['latest.cost.avg_net_price.by_income.30001-48000'],
|
|
199
|
+
}),
|
|
200
|
+
...(r['latest.cost.avg_net_price.by_income.48001-75000'] != null && {
|
|
201
|
+
net_price_48k_75k: r['latest.cost.avg_net_price.by_income.48001-75000'],
|
|
202
|
+
}),
|
|
203
|
+
...(r['latest.cost.avg_net_price.by_income.75001-110000'] != null && {
|
|
204
|
+
net_price_75k_110k: r['latest.cost.avg_net_price.by_income.75001-110000'],
|
|
205
|
+
}),
|
|
206
|
+
...(r['latest.cost.avg_net_price.by_income.110001-plus'] != null && {
|
|
207
|
+
net_price_110k_plus: r['latest.cost.avg_net_price.by_income.110001-plus'],
|
|
208
|
+
}),
|
|
209
|
+
...(r['latest.cost.attendance.academic_year'] != null && {
|
|
210
|
+
cost_of_attendance: r['latest.cost.attendance.academic_year'],
|
|
211
|
+
}),
|
|
212
|
+
...(r['latest.aid.median_debt.completers.overall'] != null && {
|
|
213
|
+
median_debt: r['latest.aid.median_debt.completers.overall'],
|
|
214
|
+
}),
|
|
215
|
+
...(r['latest.aid.pell_grant_rate'] != null && {
|
|
216
|
+
pell_grant_rate: r['latest.aid.pell_grant_rate'],
|
|
217
|
+
}),
|
|
218
|
+
...(r['latest.aid.federal_loan_rate'] != null && {
|
|
219
|
+
federal_loan_rate: r['latest.aid.federal_loan_rate'],
|
|
220
|
+
}),
|
|
221
|
+
...(r['latest.repayment.3_yr_repayment.overall'] != null && {
|
|
222
|
+
repayment_rate_3yr: r['latest.repayment.3_yr_repayment.overall'] / 1000,
|
|
223
|
+
}),
|
|
224
|
+
...(r['latest.completion.rate_suppressed.overall'] != null && {
|
|
225
|
+
completion_rate: r['latest.completion.rate_suppressed.overall'],
|
|
226
|
+
}),
|
|
227
|
+
...(r['latest.earnings.6_yrs_after_entry.median'] != null && {
|
|
228
|
+
earnings_6yr_median: r['latest.earnings.6_yrs_after_entry.median'],
|
|
229
|
+
}),
|
|
230
|
+
...(r['latest.earnings.6_yrs_after_entry.percentile25'] != null && {
|
|
231
|
+
earnings_6yr_p25: r['latest.earnings.6_yrs_after_entry.percentile25'],
|
|
232
|
+
}),
|
|
233
|
+
...(r['latest.earnings.6_yrs_after_entry.percentile75'] != null && {
|
|
234
|
+
earnings_6yr_p75: r['latest.earnings.6_yrs_after_entry.percentile75'],
|
|
235
|
+
}),
|
|
236
|
+
...(r['latest.earnings.8_yrs_after_entry.median_earnings'] != null && {
|
|
237
|
+
earnings_8yr_median: r['latest.earnings.8_yrs_after_entry.median_earnings'],
|
|
238
|
+
}),
|
|
239
|
+
...(r['latest.earnings.10_yrs_after_entry.median'] != null && {
|
|
240
|
+
earnings_10yr_median: r['latest.earnings.10_yrs_after_entry.median'],
|
|
241
|
+
}),
|
|
242
|
+
...(r['latest.earnings.10_yrs_after_entry.percentile25'] != null && {
|
|
243
|
+
earnings_10yr_p25: r['latest.earnings.10_yrs_after_entry.percentile25'],
|
|
244
|
+
}),
|
|
245
|
+
...(r['latest.earnings.10_yrs_after_entry.percentile75'] != null && {
|
|
246
|
+
earnings_10yr_p75: r['latest.earnings.10_yrs_after_entry.percentile75'],
|
|
247
|
+
}),
|
|
248
|
+
}));
|
|
249
|
+
const notice = schools.length < ids.length
|
|
250
|
+
? `${ids.length - schools.length} of ${ids.length} requested IDs returned no record — they may be invalid or no longer in the database.`
|
|
251
|
+
: undefined;
|
|
252
|
+
return {
|
|
253
|
+
schools,
|
|
254
|
+
total_requested: ids.length,
|
|
255
|
+
total_found: schools.length,
|
|
256
|
+
...(notice && { notice }),
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
format: (result) => {
|
|
260
|
+
const lines = [
|
|
261
|
+
`## School Profile${result.schools.length > 1 ? 's' : ''}`,
|
|
262
|
+
`**Requested:** ${result.total_requested} | **Found:** ${result.total_found}`,
|
|
263
|
+
];
|
|
264
|
+
if (result.notice)
|
|
265
|
+
lines.push(`\n> **Note:** ${result.notice}`);
|
|
266
|
+
for (const s of result.schools) {
|
|
267
|
+
lines.push(`\n---\n### ${s.name} (ID: ${s.id})`);
|
|
268
|
+
const loc = [s.city, s.state].filter(Boolean).join(', ');
|
|
269
|
+
if (loc)
|
|
270
|
+
lines.push(`**Location:** ${loc}${s.zip ? ` ${s.zip}` : ''}`);
|
|
271
|
+
if (s.url)
|
|
272
|
+
lines.push(`**Website:** ${s.url}`);
|
|
273
|
+
lines.push(`**Type:** ${s.ownership} | **Degree:** ${s.degree_level}`);
|
|
274
|
+
if (s.hbcu)
|
|
275
|
+
lines.push(`**HBCU:** Yes`);
|
|
276
|
+
if (s.enrollment != null)
|
|
277
|
+
lines.push(`**Enrollment:** ${s.enrollment.toLocaleString()}`);
|
|
278
|
+
lines.push('\n**Admissions**');
|
|
279
|
+
lines.push(`Acceptance Rate: ${fmtPct(s.admission_rate)}`);
|
|
280
|
+
if (s.sat_average != null)
|
|
281
|
+
lines.push(`SAT Average: ${s.sat_average}`);
|
|
282
|
+
if (s.sat_reading_25 != null)
|
|
283
|
+
lines.push(`SAT Reading: ${s.sat_reading_25}–${s.sat_reading_75 ?? 'N/A'}`);
|
|
284
|
+
if (s.sat_math_25 != null)
|
|
285
|
+
lines.push(`SAT Math: ${s.sat_math_25}–${s.sat_math_75}`);
|
|
286
|
+
if (s.act_cumulative_25 != null)
|
|
287
|
+
lines.push(`ACT: ${s.act_cumulative_25}–${s.act_cumulative_75}`);
|
|
288
|
+
lines.push('\n**Cost**');
|
|
289
|
+
lines.push(`In-State Tuition: ${fmt(s.tuition_in_state, '$')}`);
|
|
290
|
+
lines.push(`Out-of-State Tuition: ${fmt(s.tuition_out_of_state, '$')}`);
|
|
291
|
+
lines.push(`Avg Net Price (overall): ${fmt(s.net_price_overall, '$')}`);
|
|
292
|
+
if (s.net_price_0_30k != null)
|
|
293
|
+
lines.push(`Net Price ($0–30k income): $${s.net_price_0_30k.toLocaleString()}`);
|
|
294
|
+
if (s.net_price_30k_48k != null)
|
|
295
|
+
lines.push(`Net Price ($30k–48k income): $${s.net_price_30k_48k.toLocaleString()}`);
|
|
296
|
+
if (s.net_price_48k_75k != null)
|
|
297
|
+
lines.push(`Net Price ($48k–75k income): $${s.net_price_48k_75k.toLocaleString()}`);
|
|
298
|
+
if (s.net_price_75k_110k != null)
|
|
299
|
+
lines.push(`Net Price ($75k–110k income): $${s.net_price_75k_110k.toLocaleString()}`);
|
|
300
|
+
if (s.net_price_110k_plus != null)
|
|
301
|
+
lines.push(`Net Price ($110k+ income): $${s.net_price_110k_plus.toLocaleString()}`);
|
|
302
|
+
if (s.cost_of_attendance != null)
|
|
303
|
+
lines.push(`Total Cost of Attendance: $${s.cost_of_attendance.toLocaleString()}`);
|
|
304
|
+
lines.push('\n**Financial Aid**');
|
|
305
|
+
lines.push(`Median Debt at Graduation: ${fmt(s.median_debt, '$')}`);
|
|
306
|
+
if (s.pell_grant_rate != null)
|
|
307
|
+
lines.push(`Pell Grant Rate: ${fmtPct(s.pell_grant_rate)}`);
|
|
308
|
+
if (s.federal_loan_rate != null)
|
|
309
|
+
lines.push(`Federal Loan Rate: ${fmtPct(s.federal_loan_rate)}`);
|
|
310
|
+
lines.push(`3-Year Repayment Rate: ${fmtPct(s.repayment_rate_3yr)}`);
|
|
311
|
+
lines.push('\n**Outcomes**');
|
|
312
|
+
lines.push(`Completion Rate (150%): ${fmtPct(s.completion_rate)}`);
|
|
313
|
+
lines.push(`Median Earnings (6yr): ${fmt(s.earnings_6yr_median, '$')}`);
|
|
314
|
+
if (s.earnings_6yr_p25 != null)
|
|
315
|
+
lines.push(`Earnings P25/P75 (6yr): $${s.earnings_6yr_p25.toLocaleString()} / $${s.earnings_6yr_p75?.toLocaleString() ?? 'N/A'}`);
|
|
316
|
+
lines.push(`Median Earnings (8yr): ${fmt(s.earnings_8yr_median, '$')}`);
|
|
317
|
+
lines.push(`Median Earnings (10yr): ${fmt(s.earnings_10yr_median, '$')}`);
|
|
318
|
+
if (s.earnings_10yr_p25 != null)
|
|
319
|
+
lines.push(`Earnings P25/P75 (10yr): $${s.earnings_10yr_p25.toLocaleString()} / $${s.earnings_10yr_p75?.toLocaleString() ?? 'N/A'}`);
|
|
320
|
+
}
|
|
321
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
//# sourceMappingURL=get-school.tool.js.map
|