@cyanheads/college-scorecard-mcp-server 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/AGENTS.md +14 -6
  2. package/CLAUDE.md +14 -6
  3. package/README.md +29 -24
  4. package/changelog/0.6.x/0.6.0.md +34 -0
  5. package/changelog/template.md +10 -3
  6. package/dist/data/field-catalog.d.ts.map +1 -1
  7. package/dist/data/field-catalog.js +60 -23
  8. package/dist/data/field-catalog.js.map +1 -1
  9. package/dist/data/repayment-context.d.ts +74 -0
  10. package/dist/data/repayment-context.d.ts.map +1 -0
  11. package/dist/data/repayment-context.js +69 -0
  12. package/dist/data/repayment-context.js.map +1 -0
  13. package/dist/mcp-server/institution-context.d.ts +14 -1
  14. package/dist/mcp-server/institution-context.d.ts.map +1 -1
  15. package/dist/mcp-server/institution-context.js +24 -1
  16. package/dist/mcp-server/institution-context.js.map +1 -1
  17. package/dist/mcp-server/program-format.d.ts +25 -0
  18. package/dist/mcp-server/program-format.d.ts.map +1 -0
  19. package/dist/mcp-server/program-format.js +71 -0
  20. package/dist/mcp-server/program-format.js.map +1 -0
  21. package/dist/mcp-server/prompts/definitions/compare.prompt.js +4 -4
  22. package/dist/mcp-server/prompts/definitions/compare.prompt.js.map +1 -1
  23. package/dist/mcp-server/repayment.d.ts +107 -0
  24. package/dist/mcp-server/repayment.d.ts.map +1 -0
  25. package/dist/mcp-server/repayment.js +146 -0
  26. package/dist/mcp-server/repayment.js.map +1 -0
  27. package/dist/mcp-server/resources/definitions/programs-page.resource.d.ts +3 -3
  28. package/dist/mcp-server/resources/definitions/programs-page.resource.js +3 -3
  29. package/dist/mcp-server/resources/definitions/programs-page.resource.js.map +1 -1
  30. package/dist/mcp-server/resources/definitions/programs.resource.d.ts +3 -3
  31. package/dist/mcp-server/resources/definitions/programs.resource.js +3 -3
  32. package/dist/mcp-server/resources/definitions/programs.resource.js.map +1 -1
  33. package/dist/mcp-server/resources/definitions/school.resource.d.ts +3 -3
  34. package/dist/mcp-server/resources/definitions/school.resource.d.ts.map +1 -1
  35. package/dist/mcp-server/resources/definitions/school.resource.js +6 -5
  36. package/dist/mcp-server/resources/definitions/school.resource.js.map +1 -1
  37. package/dist/mcp-server/tools/definitions/compare-schools.tool.d.ts +4 -3
  38. package/dist/mcp-server/tools/definitions/compare-schools.tool.d.ts.map +1 -1
  39. package/dist/mcp-server/tools/definitions/compare-schools.tool.js +70 -40
  40. package/dist/mcp-server/tools/definitions/compare-schools.tool.js.map +1 -1
  41. package/dist/mcp-server/tools/definitions/get-earnings.tool.d.ts +3 -3
  42. package/dist/mcp-server/tools/definitions/get-earnings.tool.d.ts.map +1 -1
  43. package/dist/mcp-server/tools/definitions/get-earnings.tool.js +9 -8
  44. package/dist/mcp-server/tools/definitions/get-earnings.tool.js.map +1 -1
  45. package/dist/mcp-server/tools/definitions/get-programs.tool.d.ts +3 -3
  46. package/dist/mcp-server/tools/definitions/get-programs.tool.d.ts.map +1 -1
  47. package/dist/mcp-server/tools/definitions/get-programs.tool.js +15 -21
  48. package/dist/mcp-server/tools/definitions/get-programs.tool.js.map +1 -1
  49. package/dist/mcp-server/tools/definitions/get-school.tool.d.ts +47 -3
  50. package/dist/mcp-server/tools/definitions/get-school.tool.d.ts.map +1 -1
  51. package/dist/mcp-server/tools/definitions/get-school.tool.js +37 -7
  52. package/dist/mcp-server/tools/definitions/get-school.tool.js.map +1 -1
  53. package/dist/mcp-server/tools/definitions/list-fields.tool.js +1 -1
  54. package/dist/mcp-server/tools/definitions/list-fields.tool.js.map +1 -1
  55. package/dist/mcp-server/tools/definitions/lookup-cip.tool.d.ts.map +1 -1
  56. package/dist/mcp-server/tools/definitions/lookup-cip.tool.js +25 -11
  57. package/dist/mcp-server/tools/definitions/lookup-cip.tool.js.map +1 -1
  58. package/dist/mcp-server/tools/definitions/search-programs.tool.d.ts +3 -3
  59. package/dist/mcp-server/tools/definitions/search-programs.tool.d.ts.map +1 -1
  60. package/dist/mcp-server/tools/definitions/search-programs.tool.js +26 -21
  61. package/dist/mcp-server/tools/definitions/search-programs.tool.js.map +1 -1
  62. package/dist/mcp-server/tools/definitions/search-schools.tool.d.ts +14 -3
  63. package/dist/mcp-server/tools/definitions/search-schools.tool.d.ts.map +1 -1
  64. package/dist/mcp-server/tools/definitions/search-schools.tool.js +51 -9
  65. package/dist/mcp-server/tools/definitions/search-schools.tool.js.map +1 -1
  66. package/dist/mcp-server/tools/definitions/value-analysis.tool.d.ts +50 -4
  67. package/dist/mcp-server/tools/definitions/value-analysis.tool.d.ts.map +1 -1
  68. package/dist/mcp-server/tools/definitions/value-analysis.tool.js +53 -18
  69. package/dist/mcp-server/tools/definitions/value-analysis.tool.js.map +1 -1
  70. package/dist/services/scorecard/input-normalization.d.ts.map +1 -1
  71. package/dist/services/scorecard/input-normalization.js +2 -1
  72. package/dist/services/scorecard/input-normalization.js.map +1 -1
  73. package/dist/services/scorecard/scorecard-service.d.ts +1 -1
  74. package/dist/services/scorecard/scorecard-service.d.ts.map +1 -1
  75. package/dist/services/scorecard/scorecard-service.js +19 -2
  76. package/dist/services/scorecard/scorecard-service.js.map +1 -1
  77. package/dist/services/scorecard/types.d.ts +16 -0
  78. package/dist/services/scorecard/types.d.ts.map +1 -1
  79. package/dist/services/scorecard/types.js +7 -1
  80. package/dist/services/scorecard/types.js.map +1 -1
  81. package/package.json +5 -5
  82. package/server.json +3 -3
  83. package/skills/college-scorecard-guide/SKILL.md +4 -4
package/AGENTS.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** college-scorecard-mcp-server
4
- **Version:** 0.5.0
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`
6
- **Engines:** Bun ≥1.4.0, Node ≥24.0.0
4
+ **Version:** 0.6.0
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.22`
6
+ **Engines:** Bun ≥1.4.4, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.4.0
8
8
  **Zod:** ^4.6.7
9
9
 
@@ -185,6 +185,12 @@ Institution earnings cover federally aided undergraduate entrants working and no
185
185
 
186
186
  `readMetric` distinguishes numbers (including zero), unknown-cause null/absence, and explicit `PS` / `PrivacySuppressed` markers. Suppression flags/counts require the latter; unexpected values are provider contract failures. CIP lookup covers all current NCES CIP 2020 four-digit groups, with exact group/family titles and verified descendant aliases. Program search applies CIP × credential together upstream before school pagination; totals remain school-level.
187
187
 
188
+ `src/data/repayment-context.ts` holds dictionary provenance for discontinued declining-balance rates and the separate two- and four-year undergraduate federal Direct Loan borrower statuses. The three-year declining-balance rate uses the 2016–17 file. Profiles and value analysis retain each borrower-status horizon's OPEID6 scope, denominator when available, and reported decimal or bin. Only point estimates carry numeric values. Unsupported status rates yield an unavailable value and a notice. Comparison ranks use the historical three-year rate.
189
+
190
+ `school.operating` is current operating certification. Actual closure requires separate evidence. School search applies certification, HBCU, locale, price, and completion filters upstream before pagination. Use observed provider support for catalog sort flags, including unsortable pooled full-time retention projections.
191
+
192
+ Program tool text leads with completion earnings and renders legacy highest-credential values separately. School comparison leaves descriptive metrics, singleton results, shared OPEID6 outcomes, and mixed price periods unranked. Negative dollar selectors normalize to zero with a notice. Earnings vintages deduplicate in caller order. Six-digit CIP lookup resolves to the four-digit group used by program selectors.
193
+
188
194
  ---
189
195
 
190
196
  ## Context
@@ -356,7 +362,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
356
362
  | `bun run audit:fix` | `bun audit fix` — upgrade vulnerable packages to the lowest safe version within existing ranges (`--dry-run` previews, `--latest` rewrites ranges). First response when `devcheck` flags a transitive advisory; then `bun update <name>`, then `bun dedupe` |
357
363
  | `bun run audit:refresh` | Delete `bun.lock` and reinstall. Last resort after `audit:fix`, `bun update <name>`, and `bun dedupe` — re-resolves every ranged dep (the framework pin included) and rewrites the lockfile as `lockfileVersion: 2` |
358
364
  | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
359
- | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |
365
+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, `.mcpbignore` coverage, plugin manifests, the published `skills/` tree (run by devcheck) |
360
366
  | `bun run list-skills` | Print the skill registry |
361
367
  | `bun run tree` | Generate directory structure doc |
362
368
  | `bun run format` | Auto-fix formatting (safe fixes only) |
@@ -376,7 +382,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
376
382
 
377
383
  ## Bundling
378
384
 
379
- `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. `mcpb pack` reads `.mcpbignore`, not `.gitignore`, so every root path the bundle must not ship — source, tests, docs, caches, logs, agent docs, plugin manifests, gitignored local files — needs a root-anchored line there (`/docs/`, never `docs/`, which also strips `node_modules/*/docs/`); `lint:packaging` names each missing or unanchored line. Root `skills/` ships on purpose. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips four classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/`, `SKILL.md`); platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on; node-gyp build output beside a `binding.gyp` (`config.gypi`, Makefiles, `obj.target/`, `.deps/`, object files and debug symbols), which records the packing machine's paths — compiled `*.node` addons stay; and build-only package files — the framework's `changelog/`, `scripts/`, `templates/`, `CLAUDE.md`, `AGENTS.md`, and config bases (`biome.json`, `vitest.config.base.mjs`, `config/tsconfig.base.json`), and `better-sqlite3`'s `deps/` and `src/` C sources. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
385
+ `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. `mcpb pack` reads `.mcpbignore`, not `.gitignore`, so every root path the bundle must not ship — source, tests, docs, caches, logs, agent docs, plugin manifests, gitignored local files — needs a root-anchored line there (`/docs/`, never `docs/`, which also strips `node_modules/*/docs/`); `lint:packaging` names each missing or unanchored line. Root `skills/` ships on purpose. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips four classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs — `framework-skills/`, `skills/`, `.claude/`, `.agents/`, `AGENTS.md`, and `CLAUDE.md` at a package's root, and `SKILL.md` anywhere (a `skills/` deeper in a package is runtime code and stays); platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on; node-gyp build output beside a `binding.gyp` (`config.gypi`, Makefiles, `obj.target/`, `.deps/`, object files and debug symbols), which records the packing machine's paths — compiled `*.node` addons stay; and build-only package files — the framework's `changelog/`, `scripts/`, `templates/`, and config bases (`biome.json`, `vitest.config.base.mjs`, `config/tsconfig.base.json`), and `better-sqlite3`'s `deps/` and `src/` C sources. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
380
386
 
381
387
  **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match, that every `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.X}"` (the host substitutes nothing else — `"${X}"` reaches the server as that literal string), and that an optional string option carries `"default": ""`.
382
388
 
@@ -447,4 +453,6 @@ import { getMyService } from '@/services/my-domain/my-service.js';
447
453
  - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name (never the npm scope — `lint:packaging` enforces this); `interface.shortDescription` from `package.json` description
448
454
  - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable (API key, contact email, instance URL) is listed in `env_vars` so Codex forwards it from the user's environment. Never write `"KEY": ""` into `env` — an empty value replaces the user's exported key and is read as unset
449
455
  - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `author`, `repository`, `license`, `keywords` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name. Every user-supplied variable is declared under `userConfig` (`type`, `title`, `description`; `sensitive: true` for keys and tokens; `required: true` or `default: ""`) and referenced from `env` as `"KEY": "${user_config.<option>}"` — mirror the `user_config` block in `manifest.json`. Never write `"KEY": ""` into `env`
450
- - [ ] `npm run devcheck` passes
456
+ - [ ] Any new root dev path has a root-anchored line in `.mcpbignore` and passes `bun run bundle` and `lint:packaging`
457
+ - [ ] Field-test verified every gate and identifier claim in the published `skills/college-scorecard-guide` against live tool behavior
458
+ - [ ] `bun run devcheck` passes
package/CLAUDE.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** college-scorecard-mcp-server
4
- **Version:** 0.5.0
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`
6
- **Engines:** Bun ≥1.4.0, Node ≥24.0.0
4
+ **Version:** 0.6.0
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.22`
6
+ **Engines:** Bun ≥1.4.4, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.4.0
8
8
  **Zod:** ^4.6.7
9
9
 
@@ -185,6 +185,12 @@ Institution earnings cover federally aided undergraduate entrants working and no
185
185
 
186
186
  `readMetric` distinguishes numbers (including zero), unknown-cause null/absence, and explicit `PS` / `PrivacySuppressed` markers. Suppression flags/counts require the latter; unexpected values are provider contract failures. CIP lookup covers all current NCES CIP 2020 four-digit groups, with exact group/family titles and verified descendant aliases. Program search applies CIP × credential together upstream before school pagination; totals remain school-level.
187
187
 
188
+ `src/data/repayment-context.ts` holds dictionary provenance for discontinued declining-balance rates and the separate two- and four-year undergraduate federal Direct Loan borrower statuses. The three-year declining-balance rate uses the 2016–17 file. Profiles and value analysis retain each borrower-status horizon's OPEID6 scope, denominator when available, and reported decimal or bin. Only point estimates carry numeric values. Unsupported status rates yield an unavailable value and a notice. Comparison ranks use the historical three-year rate.
189
+
190
+ `school.operating` is current operating certification. Actual closure requires separate evidence. School search applies certification, HBCU, locale, price, and completion filters upstream before pagination. Use observed provider support for catalog sort flags, including unsortable pooled full-time retention projections.
191
+
192
+ Program tool text leads with completion earnings and renders legacy highest-credential values separately. School comparison leaves descriptive metrics, singleton results, shared OPEID6 outcomes, and mixed price periods unranked. Negative dollar selectors normalize to zero with a notice. Earnings vintages deduplicate in caller order. Six-digit CIP lookup resolves to the four-digit group used by program selectors.
193
+
188
194
  ---
189
195
 
190
196
  ## Context
@@ -356,7 +362,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
356
362
  | `bun run audit:fix` | `bun audit fix` — upgrade vulnerable packages to the lowest safe version within existing ranges (`--dry-run` previews, `--latest` rewrites ranges). First response when `devcheck` flags a transitive advisory; then `bun update <name>`, then `bun dedupe` |
357
363
  | `bun run audit:refresh` | Delete `bun.lock` and reinstall. Last resort after `audit:fix`, `bun update <name>`, and `bun dedupe` — re-resolves every ranged dep (the framework pin included) and rewrites the lockfile as `lockfileVersion: 2` |
358
364
  | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
359
- | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |
365
+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, `.mcpbignore` coverage, plugin manifests, the published `skills/` tree (run by devcheck) |
360
366
  | `bun run list-skills` | Print the skill registry |
361
367
  | `bun run tree` | Generate directory structure doc |
362
368
  | `bun run format` | Auto-fix formatting (safe fixes only) |
@@ -376,7 +382,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
376
382
 
377
383
  ## Bundling
378
384
 
379
- `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. `mcpb pack` reads `.mcpbignore`, not `.gitignore`, so every root path the bundle must not ship — source, tests, docs, caches, logs, agent docs, plugin manifests, gitignored local files — needs a root-anchored line there (`/docs/`, never `docs/`, which also strips `node_modules/*/docs/`); `lint:packaging` names each missing or unanchored line. Root `skills/` ships on purpose. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips four classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/`, `SKILL.md`); platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on; node-gyp build output beside a `binding.gyp` (`config.gypi`, Makefiles, `obj.target/`, `.deps/`, object files and debug symbols), which records the packing machine's paths — compiled `*.node` addons stay; and build-only package files — the framework's `changelog/`, `scripts/`, `templates/`, `CLAUDE.md`, `AGENTS.md`, and config bases (`biome.json`, `vitest.config.base.mjs`, `config/tsconfig.base.json`), and `better-sqlite3`'s `deps/` and `src/` C sources. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
385
+ `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. `mcpb pack` reads `.mcpbignore`, not `.gitignore`, so every root path the bundle must not ship — source, tests, docs, caches, logs, agent docs, plugin manifests, gitignored local files — needs a root-anchored line there (`/docs/`, never `docs/`, which also strips `node_modules/*/docs/`); `lint:packaging` names each missing or unanchored line. Root `skills/` ships on purpose. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips four classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs — `framework-skills/`, `skills/`, `.claude/`, `.agents/`, `AGENTS.md`, and `CLAUDE.md` at a package's root, and `SKILL.md` anywhere (a `skills/` deeper in a package is runtime code and stays); platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on; node-gyp build output beside a `binding.gyp` (`config.gypi`, Makefiles, `obj.target/`, `.deps/`, object files and debug symbols), which records the packing machine's paths — compiled `*.node` addons stay; and build-only package files — the framework's `changelog/`, `scripts/`, `templates/`, and config bases (`biome.json`, `vitest.config.base.mjs`, `config/tsconfig.base.json`), and `better-sqlite3`'s `deps/` and `src/` C sources. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
380
386
 
381
387
  **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match, that every `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.X}"` (the host substitutes nothing else — `"${X}"` reaches the server as that literal string), and that an optional string option carries `"default": ""`.
382
388
 
@@ -447,4 +453,6 @@ import { getMyService } from '@/services/my-domain/my-service.js';
447
453
  - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name (never the npm scope — `lint:packaging` enforces this); `interface.shortDescription` from `package.json` description
448
454
  - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable (API key, contact email, instance URL) is listed in `env_vars` so Codex forwards it from the user's environment. Never write `"KEY": ""` into `env` — an empty value replaces the user's exported key and is read as unset
449
455
  - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `author`, `repository`, `license`, `keywords` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name. Every user-supplied variable is declared under `userConfig` (`type`, `title`, `description`; `sensitive: true` for keys and tokens; `required: true` or `default: ""`) and referenced from `env` as `"KEY": "${user_config.<option>}"` — mirror the `user_config` block in `manifest.json`. Never write `"KEY": ""` into `env`
450
- - [ ] `npm run devcheck` passes
456
+ - [ ] Any new root dev path has a root-anchored line in `.mcpbignore` and passes `bun run bundle` and `lint:packaging`
457
+ - [ ] Field-test verified every gate and identifier claim in the published `skills/college-scorecard-guide` against live tool behavior
458
+ - [ ] `bun run devcheck` passes
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.5.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/college-scorecard-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.4.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/college-scorecard-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/college-scorecard-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.4-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-0.6.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/college-scorecard-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.4.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/college-scorecard-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/college-scorecard-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.4-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -35,13 +35,13 @@ US college data from the Department of Education College Scorecard: costs, earni
35
35
 
36
36
  | Tool | Description |
37
37
  |:---|:---|
38
- | `scorecard_search_schools` | Search institutions by name, location, type, size, acceptance rate, and single-sex flags; sort by supported API fields. Returns core identity and cost metrics. |
39
- | `scorecard_get_school` | Full institutional profiles — costs, admissions, outcomes, aid, demographics, single-sex flags, and completion rates. |
40
- | `scorecard_compare_schools` | Normalized side-by-side comparison of 2–5 schools on a named topic. Returns metric values, ordinal ranks, and a comparison summary. |
38
+ | `scorecard_search_schools` | Search institutions by location, price, completion, operating certification, and other school characteristics. Sort by fields with API sort support. |
39
+ | `scorecard_get_school` | Institutional profiles with costs, admissions, aid, demographics, completion, and borrower repayment outcomes. |
40
+ | `scorecard_compare_schools` | Side-by-side comparison of up to five schools. Includes ranks and leaders for eligible metrics. |
41
41
  | `scorecard_get_programs` | Paged field-of-study outcomes: completion-based 1/4-year earnings, legacy highest-credential earnings, debt, and campus awards with separate provenance. |
42
42
  | `scorecard_search_programs` | Find programs by CIP and credential or keyword, retain degree identity, and rank earnings within each fetched school page. |
43
43
  | `scorecard_get_earnings` | Earnings of federally aided undergraduate entrants working and not enrolled, regardless of completion: 6/8/10-year medians, 6/10-year P25/P75, and non-male/male 6-year medians. |
44
- | `scorecard_value_analysis` | Workflow tool: parallel-fetches cost, debt, repayment, and earnings data, then computes ROI metrics — debt-to-earnings ratio and net price to earnings ratio. |
44
+ | `scorecard_value_analysis` | Combines cost, debt, repayment, and earnings data with debt-to-earnings and net-price-to-earnings ratios. |
45
45
  | `scorecard_lookup_cip` | Search all 464 current NCES CIP 2020 four-digit groups by code, keyword, or related program name. Served offline. |
46
46
  | `scorecard_list_fields` | Search the Scorecard field catalog by keyword. Returns matching field paths, descriptions, data types, and sort support. Use before passing custom `fields` parameters. |
47
47
 
@@ -67,9 +67,10 @@ The [college-scorecard-guide](./skills/college-scorecard-guide/SKILL.md) covers
67
67
 
68
68
  ### `scorecard_search_schools` <sub>tool</sub>
69
69
 
70
- - Search by name, state, ownership, degree level, size, acceptance rate, CIP code, or ZIP and distance (miles or `km`). `per_page` is capped at 100; `page` is zero-indexed. State lists accept `WA,CA`.
71
- - Returns core identity and cost metrics for quick scanning.
72
- - `sort` takes an API expression such as `latest.cost.avg_net_price.overall:asc` (`:desc` reverses it). `men_only` and `women_only` accept true or false.
70
+ - Search by name, state, ownership, degree level, size, acceptance rate, CIP code, or ZIP and distance (miles or `km`). State lists accept `WA,CA`.
71
+ - Apply `max_net_price` (inclusive dollars), `min_completion_rate` (inclusive 0–1), and `locale` (`city`, `suburb`, `town`, or `rural`) before paging. These filters require reported values. Negative price ceilings normalize to $0 with a notice.
72
+ - `operating`, `hbcu`, `men_only`, and `women_only` match explicit true or false values. Omit a flag to include unknown values. `operating: false` means the school lacks current operating certification. Verify actual closure separately.
73
+ - Use `scorecard_list_fields` to choose a supported sort such as `latest.cost.avg_net_price.overall:asc`. Omit sort to preserve API order. `page` starts at zero and `per_page` is capped at 100.
73
74
 
74
75
  ---
75
76
 
@@ -78,22 +79,23 @@ The [college-scorecard-guide](./skills/college-scorecard-guide/SKILL.md) covers
78
79
  - Accepts a school ID or an array of up to 1,000. The first 100 distinct IDs are fetched; `missing_ids` lists fetched IDs with no record and `unprocessed_ids` lists the excess.
79
80
  - Default profiles cover costs, admissions, outcomes, financial aid, demographics, and completion rates. Custom `fields` returns `selected_fields` with the requested values, and `absent_fields` lists requested paths missing from the response.
80
81
  - Projected program arrays use `program_limit` (default 20, capped at 100) and `program_offset`; follow each school's `program_pages` continuation. Raw values and provider positions stay aligned.
81
- - HBCU and single-sex flags are true/false when known and unknown when absent.
82
+ - Profiles include operating certification, HBCU, and single-sex flags as booleans when known, omitting unknown flags. The discontinued three-year declining-balance rate carries its 2016–17 file, repayment-entry cohorts, and measurement period.
83
+ - `borrower_repayment` reports making progress, default, and paid in full at two and four years for OPEID6-pooled undergraduate federal Direct Loan borrowers. Each horizon has its own provenance and cohort count when available. Published bins are preserved verbatim, and only decimal point estimates have numeric values.
82
84
 
83
85
  ---
84
86
 
85
87
  ### `scorecard_compare_schools` <sub>tool</sub>
86
88
 
87
89
  - Accepts 2–1,000 raw unit IDs and compares the first five distinct normalized IDs on `costs`, `admissions`, `outcomes`, or `aid`. `missing_ids` were queried; `unprocessed_ids` were not.
88
- - Equal independent values share a rank (1, 1, 3). Missing values, pooled outcome rows containing shared OPEID6 groups, and mixed academic/program-year price rows are unranked.
89
- - Each cell's `availability` distinguishes available, unavailable, and privacy-suppressed values.
90
+ - Equal eligible values share a rank (1, 1, 3). Admissions metrics, aid participation rates, shared OPEID6 outcome rows, and mixed academic/program-year price rows are unranked. A single found school yields values without ranks or leaders.
91
+ - Each cell's `availability` distinguishes available, unavailable, and privacy-suppressed values. Repayment rows rank the historical three-year declining-balance measure and identify its vintage.
90
92
 
91
93
  ---
92
94
 
93
95
  ### `scorecard_get_programs` <sub>tool</sub>
94
96
 
95
97
  - Accepts one school ID, with optional CIP code (dotted or undotted), `credential_level`, and minimum earnings filters. A school with no matching programs returns an empty collection; a nonexistent school is a lookup error.
96
- - Completion-based 1/4-year medians and counts carry separate availability and provenance. Legacy `earnings_1yr_median`/`earnings_count` retain highest-credential semantics; their availability and suppression counts remain separate.
98
+ - Text results lead with completion-based one- and four-year medians, counts, and availability. Legacy `earnings_1yr_median` and `earnings_count` appear separately with their highest-credential provenance.
97
99
  - `min_earnings` and ordering use completion-based one-year earnings, with no legacy fallback. Page with `limit` (default 20, capped at 100) and `offset`; `total` counts matches before paging, and `nextOffset` is null at the end.
98
100
  - `median_debt` is completers' cumulative Stafford/Grad PLUS borrowing. `ipeds_awards_year1` and `ipeds_awards_year2` count awards in each year of the pooled debt cohort, not students.
99
101
 
@@ -103,14 +105,14 @@ The [college-scorecard-guide](./skills/college-scorecard-guide/SKILL.md) covers
103
105
 
104
106
  - Search by CIP code or program name, with optional integer `credential_level` (1–8 and 99), state, ownership, net price, earnings, and debt filters. `page`/`per_page` paginate schools; `offset`/`limit` page matching program rows within that school page.
105
107
  - Returns school IDs and names with program metrics, `credential_level`, and `credential_title`. Available earnings sort first; availability, debt, and award fields match `scorecard_get_programs`.
106
- - `min_earnings` filters locally, so totals and pagination are upstream school counts before filtering. Earnings and debt thresholds are inclusive and exclude unknown values.
108
+ - `min_earnings` filters locally, so totals and pagination are upstream school counts before filtering. Earnings and debt thresholds are inclusive and exclude unknown values. Negative `max_net_price` and `max_debt` normalize to $0 with a notice.
107
109
  - Follow `next` until null: it drains program rows on the current school page before advancing. `shown` counts returned programs; `schools_shown` counts fetched schools. Rankings use current completion-based one-year earnings within the fetched page only.
108
110
 
109
111
  ---
110
112
 
111
113
  ### `scorecard_get_earnings` <sub>tool</sub>
112
114
 
113
- - Accepts one school ID and optional data-file start `years` from 1996–2025 (file vintages, not entry years). Omit `years` or pass `[]` for the snapshot only.
115
+ - Accepts one school ID and optional data-file start `years` from 1996–2025 (file vintages, not entry years). Duplicate years collapse in caller order. Omit `years` or pass `[]` for the snapshot only.
114
116
  - Covers federally aided undergraduate entrants working and not enrolled, regardless of completion: medians at 6/8/10 years after entry, 6/10-year P25/P75, and non-male/male 6-year medians.
115
117
  - `current_availability` and `trend[].availability` classify each metric. `current_context` and `trend[].context` give `data_file_year`, `entry_cohorts`, `measurement_years`, and `dollar_year`; values keep the provider's dollar basis, so check it before comparing across vintages.
116
118
 
@@ -118,24 +120,25 @@ The [college-scorecard-guide](./skills/college-scorecard-guide/SKILL.md) covers
118
120
 
119
121
  ### `scorecard_value_analysis` <sub>tool</sub>
120
122
 
121
- - Accepts one school ID; optional `family_income` selects the applicable net price bracket ($0–30k, $30k–48k, $48k–75k, $75k–110k, or $110k+).
123
+ - Accepts one school ID. Optional `family_income` selects the applicable net price bracket ($0–30k, $30k–48k, $48k–75k, $75k–110k, or $110k+). Negative income normalizes to $0 with a notice.
122
124
  - Returns ratios alongside their source figures. `effective_net_price` is the price used, and `net_price_basis` is `income_bracket`, `overall`, or `unavailable`.
123
125
  - If the bracket is missing, the overall price is used and `data_notes` says so. Ratios require positive annual earnings.
124
126
  - Tuition and attendance independently prefer academic-year prices, otherwise reported program-year prices; each has its own basis. Program tuition never substitutes for net price. OPEID6-pooled outcomes are identified alongside campus prices.
127
+ - Returns the same two- and four-year borrower statuses and historical three-year declining-balance context as the default school profile.
125
128
 
126
129
  ---
127
130
 
128
131
  ### `scorecard_lookup_cip` <sub>tool</sub>
129
132
 
130
133
  - Search by nonblank keyword or partial name (default limit 20, capped at 50). Dotted `11.07` and undotted `1107` are equivalent; continue with `offset` and returned `nextOffset` while more results remain.
131
- - Returns exact group titles and family identities from all 464 current NCES CIP 2020 four-digit groups. Verified six-digit program aliases return their group; no matches return an empty collection with guidance.
134
+ - Returns exact group titles and family identities from all 464 current NCES CIP 2020 four-digit groups. Six-digit queries such as `11.0701` and `110701` resolve to their four-digit group with a notice. Program tools take the four-digit group. No matches return an empty collection with guidance.
132
135
 
133
136
  ---
134
137
 
135
138
  ### `scorecard_list_fields` <sub>tool</sub>
136
139
 
137
- - Search 84 curated offline field entries by nonblank query (default limit 30, capped at 100). Use before passing custom `fields` to `scorecard_get_school`.
138
- - Returns field paths, descriptions, data types, categories, and API sorting support; `tip` flags results containing unsortable fields.
140
+ - Search 94 curated offline field entries by nonblank query (default limit 30, capped at 100). Use before passing custom `fields` to `scorecard_get_school`, including pooled full-time retention for four-year and less-than-four-year institutions.
141
+ - Returns field paths, descriptions, data types, categories, and observed API sorting support. `tip` flags results containing unsortable fields.
139
142
 
140
143
  ---
141
144
 
@@ -159,7 +162,7 @@ The [college-scorecard-guide](./skills/college-scorecard-guide/SKILL.md) covers
159
162
  ### `scorecard_compare_prompt` <sub>prompt</sub>
160
163
 
161
164
  - Arguments: `school_names` (comma-separated list) and `focus` (`costs` | `outcomes` | `programs`), both required
162
- - `costs` and `outcomes` run school discovery, `scorecard_compare_schools`, profiles, and value analysis. `programs` runs school discovery, CIP lookup, and `scorecard_get_programs` per school, then compares earnings, debt, and IPEDS awards for matching credentials.
165
+ - `costs` and `outcomes` run school discovery, `scorecard_compare_schools`, profiles, and value analysis. `programs` runs school discovery, CIP lookup, and `scorecard_get_programs` per school, then compares completion earnings, debt, and IPEDS awards for matching credentials.
163
166
 
164
167
  ## Features
165
168
 
@@ -170,14 +173,14 @@ College Scorecard-specific:
170
173
  - Full College Scorecard API coverage: ~6,500 Title IV institutions, ~2,800 data fields spanning costs, outcomes, demographics, financial aid, and field-of-study earnings
171
174
  - Program completion earnings at one and four years, alongside legacy highest-credential outcomes; each series carries its population and vintage
172
175
  - Field pre-selection per tool — curated field sets appropriate to each tool's purpose; optional `fields` override for custom queries
173
- - Embedded CIP taxonomy (464 groups) and field catalog (84 fields) served offline
176
+ - Embedded CIP taxonomy (464 groups) and field catalog (94 fields) served offline
174
177
  - Geographic filtering via U.S. zip code + distance radius
175
178
 
176
179
  Agent-friendly output:
177
180
 
178
181
  - Availability distinguishes numbers (including zero), missing values, and explicit `PS` / `PrivacySuppressed` provider markers
179
182
  - Derived metrics sit alongside source figures in `scorecard_value_analysis`, and `scorecard_compare_schools` adds ordinal ranks
180
- - School sorting uses indexed API fields; check `scorecard_list_fields` before choosing a sort expression. Six-year earnings does not support API sorting.
183
+ - School sorting uses fields with observed API sort support. Six-year earnings and pooled full-time retention can be projected but cannot be used as sort fields.
181
184
  - Provider failures report distinct validation, authentication, throttling, timeout, and availability reasons; availability uses `api_error` and cancellation remains `RequestCancelled`
182
185
 
183
186
  ## Getting started
@@ -265,7 +268,7 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 SCORECARD_API_KEY=... bun run start:h
265
268
 
266
269
  ### Prerequisites
267
270
 
268
- - [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
271
+ - [Bun v1.4.4](https://bun.sh/) or higher (or Node.js v24+).
269
272
  - A College Scorecard API key — free registration at [api.data.gov/signup](https://api.data.gov/signup/). Rate limit: 1,000 requests/hour per key.
270
273
 
271
274
  ### Installation
@@ -305,12 +308,14 @@ All configuration is validated at startup via Zod schemas in `src/config/server-
305
308
  | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http` | `stdio` |
306
309
  | `MCP_HTTP_PORT` | HTTP server port | `3010` |
307
310
  | `MCP_HTTP_ENDPOINT_PATH` | HTTP endpoint path where the MCP server is mounted | `/mcp` |
308
- | `MCP_PUBLIC_URL` | Public origin override for TLS-terminating reverse-proxy deployments | none |
311
+ | `MCP_PUBLIC_URL` | Public HTTP or HTTPS base URL for TLS-terminating reverse-proxy deployments. Omit credentials, query parameters, and fragments. | none |
309
312
  | `MCP_SESSION_MODE` | HTTP session mode: `stateless`, `stateful`, or `auto` | `stateless` |
310
313
  | `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth` | `none` |
314
+ | `MCP_JWT_EXPECTED_AUDIENCE` | Required `aud` claim in JWT mode. An unset audience logs a startup warning. | none |
315
+ | `MCP_JWT_EXPECTED_ISSUER` | Required `iss` claim in JWT mode. Configure it alongside the audience. | none |
311
316
  | `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.) | `info` |
312
317
  | `MCP_GC_PRESSURE_INTERVAL_MS` | Opt-in forced-GC loop interval in ms (Bun only); try `60000` if heap grows under HTTP load | `0` (disabled) |
313
- | `LOGS_DIR` | Directory for log files (Node.js only) | `<project-root>/logs` |
318
+ | `LOGS_DIR` | Directory for log files. Relative paths resolve from the server package root. | `<server-package-root>/logs` |
314
319
  | `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1` | `in-memory` |
315
320
  | `OTEL_ENABLED` | Enable [OpenTelemetry instrumentation](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry) | `false` |
316
321
 
@@ -0,0 +1,34 @@
1
+ ---
2
+ summary: "Filter schools by price, completion, and certification, and compare outcomes with the reported cohorts and measures."
3
+ breaking: true
4
+ security: false
5
+ ---
6
+
7
+ # 0.6.0 — 2026-11-13
8
+
9
+ ## Added
10
+
11
+ - **`scorecard_search_schools`** filters by maximum net price, minimum completion rate, HBCU status, and locale before pagination. ([#55](https://github.com/cyanheads/college-scorecard-mcp-server/issues/55))
12
+ - **School profiles and value analysis** return two- and four-year undergraduate federal Direct Loan borrower statuses with OPEID6 scope, cohort counts, and the published decimals or bins. ([#58](https://github.com/cyanheads/college-scorecard-mcp-server/issues/58))
13
+
14
+ ## Changed
15
+
16
+ - Remove inherited titles from concrete school and program resource entries; template titles remain.
17
+ - **Runtime** requires Bun 1.4.4 or later when running on Bun.
18
+
19
+ ## Fixed
20
+
21
+ - **Three-year declining-balance repayment** carries its discontinued status and 2016–17 file vintage in school profiles, value analysis, and comparisons. ([#51](https://github.com/cyanheads/college-scorecard-mcp-server/issues/51))
22
+ - **Program tools and comparison prompt** lead with completion-based earnings and distinguish the legacy highest-credential series. Program tools render all results as readable text with availability, provenance, and continuation. ([#52](https://github.com/cyanheads/college-scorecard-mcp-server/issues/52))
23
+ - **`scorecard_list_fields`** reports observed API sort support and includes pooled full-time retention for four-year and less-than-four-year institutions. ([#53](https://github.com/cyanheads/college-scorecard-mcp-server/issues/53))
24
+ - **Operating certification** appears where reported in school tools and resources, with an explicit certification filter in school search. ([#54](https://github.com/cyanheads/college-scorecard-mcp-server/issues/54))
25
+ - **`scorecard_compare_schools`** leaves descriptive metrics and single-school results unranked and preserves unranked rows in the comparison prompt. ([#56](https://github.com/cyanheads/college-scorecard-mcp-server/issues/56))
26
+ - **Input normalization** clamps negative dollar inputs to zero with a notice, deduplicates earnings vintages in caller order, and maps six-digit CIP queries to their four-digit group. Exhausted CIP pages explain how to restart. ([#57](https://github.com/cyanheads/college-scorecard-mcp-server/issues/57))
27
+ - **Readable output** preserves reported numeric precision for comparison values, historical repayment, and family income. Upstream HTTP error bodies stay server-side by default.
28
+ - **MCPB packaging** retains runtime files when stripping dependency development files.
29
+
30
+ ## Dependencies
31
+
32
+ - **`@cyanheads/mcp-ts-core`** ^0.13.18 → ^0.13.22
33
+ - **`@biomejs/biome`** ^2.6.4 → ^2.6.7
34
+ - **`vitest`** ^5.0.5 → ^5.1.0
@@ -116,14 +116,21 @@ security: false
116
116
  resolves to whatever real item already owns 42, and timeline previews pull
117
117
  in that unrelated item's metadata.
118
118
 
119
+ Outside reporters: when the issue or PR came from outside the project
120
+ (`git-wrapup` step 5 says how to tell), thank its author in the same
121
+ parenthetical, profile linked by full URL for the same reason:
122
+
123
+ ([#38](https://github.com/cyanheads/mcp-ts-core/issues/38), thanks [@octocat](https://github.com/octocat)!)
124
+
119
125
  TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
120
126
  via `gh release create --notes-from-tag`. It is a condensed digest of this
121
127
  entry, never a copy, and its format is owned by the `release-and-publish`
122
128
  skill (step 4, "Create the annotated tag"): a short subject line without the
123
129
  version, flat headline bullets — no Keep-a-Changelog section headers, no
124
- gates line — at most one deps line, issue backlinks, and the changelog link
125
- last. In release-PR mode the `git-wrapup` skill authors those bullets as the
126
- PR body's `## Changes` and the tag copies them.
130
+ gates line — at most one deps line, issue backlinks with any outside-reporter
131
+ thanks, and the changelog link last. In release-PR mode the `git-wrapup`
132
+ skill authors those bullets as the PR body's `## Changes` and the tag copies
133
+ them.
127
134
  -->
128
135
 
129
136
  ## Added
@@ -1 +1 @@
1
- {"version":3,"file":"field-catalog.d.ts","sourceRoot":"","sources":["../../src/data/field-catalog.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,UAAU;IACzB,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB;IAChB,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;CAClD;AAED,mFAAmF;AACnF,eAAO,MAAM,aAAa,EAAE,UAAU,EAinBrC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,UAAU,EAAE,CAsB1E"}
1
+ {"version":3,"file":"field-catalog.d.ts","sourceRoot":"","sources":["../../src/data/field-catalog.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,MAAM,WAAW,UAAU;IACzB,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB;IAChB,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;CAClD;AAED,mFAAmF;AACnF,eAAO,MAAM,aAAa,EAAE,UAAU,EAwpBrC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,UAAU,EAAE,CAsB1E"}
@@ -3,6 +3,7 @@
3
3
  * Scorecard data dictionary. Static data — no API call required.
4
4
  * @module data/field-catalog
5
5
  */
6
+ import { BORROWER_REPAYMENT_SERIES, BORROWER_REPAYMENT_STATUSES, borrowerRepaymentField, describeLegacyRepayment, LEGACY_REPAYMENT_CONTEXT, } from './repayment-context.js';
6
7
  /** Core field catalog — most decision-relevant fields from the data dictionary. */
7
8
  export const FIELD_CATALOG = [
8
9
  // Identity
@@ -17,7 +18,7 @@ export const FIELD_CATALOG = [
17
18
  path: 'id',
18
19
  description: 'Unique unit ID (use for ID-based lookups)',
19
20
  type: 'integer',
20
- sortable: false,
21
+ sortable: true,
21
22
  category: 'identity',
22
23
  },
23
24
  {
@@ -101,7 +102,7 @@ export const FIELD_CATALOG = [
101
102
  path: 'school.minority_serving.historically_black',
102
103
  description: 'Historically Black College or University flag',
103
104
  type: 'integer',
104
- sortable: false,
105
+ sortable: true,
105
106
  category: 'identity',
106
107
  },
107
108
  {
@@ -151,70 +152,70 @@ export const FIELD_CATALOG = [
151
152
  path: 'latest.cost.net_price.public.by_income_level.0-30000',
152
153
  description: 'Net price for family income $0–$30,000 (public institutions)',
153
154
  type: 'integer',
154
- sortable: true,
155
+ sortable: false,
155
156
  category: 'cost',
156
157
  },
157
158
  {
158
159
  path: 'latest.cost.net_price.public.by_income_level.30001-48000',
159
160
  description: 'Net price for family income $30,001–$48,000 (public institutions)',
160
161
  type: 'integer',
161
- sortable: true,
162
+ sortable: false,
162
163
  category: 'cost',
163
164
  },
164
165
  {
165
166
  path: 'latest.cost.net_price.public.by_income_level.48001-75000',
166
167
  description: 'Net price for family income $48,001–$75,000 (public institutions)',
167
168
  type: 'integer',
168
- sortable: true,
169
+ sortable: false,
169
170
  category: 'cost',
170
171
  },
171
172
  {
172
173
  path: 'latest.cost.net_price.public.by_income_level.75001-110000',
173
174
  description: 'Net price for family income $75,001–$110,000 (public institutions)',
174
175
  type: 'integer',
175
- sortable: true,
176
+ sortable: false,
176
177
  category: 'cost',
177
178
  },
178
179
  {
179
180
  path: 'latest.cost.net_price.public.by_income_level.110001-plus',
180
181
  description: 'Net price for family income $110,001+ (public institutions)',
181
182
  type: 'integer',
182
- sortable: true,
183
+ sortable: false,
183
184
  category: 'cost',
184
185
  },
185
186
  {
186
187
  path: 'latest.cost.net_price.private.by_income_level.0-30000',
187
188
  description: 'Net price for family income $0–$30,000 (private institutions)',
188
189
  type: 'integer',
189
- sortable: true,
190
+ sortable: false,
190
191
  category: 'cost',
191
192
  },
192
193
  {
193
194
  path: 'latest.cost.net_price.private.by_income_level.30001-48000',
194
195
  description: 'Net price for family income $30,001–$48,000 (private institutions)',
195
196
  type: 'integer',
196
- sortable: true,
197
+ sortable: false,
197
198
  category: 'cost',
198
199
  },
199
200
  {
200
201
  path: 'latest.cost.net_price.private.by_income_level.48001-75000',
201
202
  description: 'Net price for family income $48,001–$75,000 (private institutions)',
202
203
  type: 'integer',
203
- sortable: true,
204
+ sortable: false,
204
205
  category: 'cost',
205
206
  },
206
207
  {
207
208
  path: 'latest.cost.net_price.private.by_income_level.75001-110000',
208
209
  description: 'Net price for family income $75,001–$110,000 (private institutions)',
209
210
  type: 'integer',
210
- sortable: true,
211
+ sortable: false,
211
212
  category: 'cost',
212
213
  },
213
214
  {
214
215
  path: 'latest.cost.net_price.private.by_income_level.110001-plus',
215
216
  description: 'Net price for family income $110,001+ (private institutions)',
216
217
  type: 'integer',
217
- sortable: true,
218
+ sortable: false,
218
219
  category: 'cost',
219
220
  },
220
221
  {
@@ -347,31 +348,52 @@ export const FIELD_CATALOG = [
347
348
  },
348
349
  {
349
350
  path: 'latest.repayment.repayment_cohort.3_year_declining_balance',
350
- description: 'Share of borrowers paying down principal (declining loan balance) 3 years after entering repayment (0–1)',
351
+ description: `Share of borrowers paying down principal (declining loan balance) 3 years after entering repayment (0–1), ${describeLegacyRepayment(LEGACY_REPAYMENT_CONTEXT[3])}`,
351
352
  type: 'float',
352
353
  sortable: false,
353
354
  category: 'aid',
354
355
  },
355
356
  {
356
357
  path: 'latest.repayment.repayment_cohort.1_year_declining_balance',
357
- description: 'Share of borrowers paying down principal (declining loan balance) 1 year after entering repayment (0–1)',
358
+ description: `Share of borrowers paying down principal (declining loan balance) 1 year after entering repayment (0–1), ${describeLegacyRepayment(LEGACY_REPAYMENT_CONTEXT[1])}`,
358
359
  type: 'float',
359
360
  sortable: false,
360
361
  category: 'aid',
361
362
  },
362
363
  {
363
364
  path: 'latest.repayment.repayment_cohort.5_year_declining_balance',
364
- description: 'Share of borrowers paying down principal (declining loan balance) 5 years after entering repayment (0–1)',
365
+ description: `Share of borrowers paying down principal (declining loan balance) 5 years after entering repayment (0–1), ${describeLegacyRepayment(LEGACY_REPAYMENT_CONTEXT[5])}`,
365
366
  type: 'float',
366
367
  sortable: false,
367
368
  category: 'aid',
368
369
  },
370
+ ...BORROWER_REPAYMENT_SERIES.flatMap((series) => {
371
+ const { horizon_years, data_file, repayment_entry_cohorts, measurement_period } = series;
372
+ const countPath = borrowerRepaymentField(horizon_years, 'count');
373
+ const vintage = `OPEID6 pooled; ${data_file} file, repayment-entry cohorts ${repayment_entry_cohorts.join(', ')}, measured ${measurement_period.join(', ')}`;
374
+ return [
375
+ {
376
+ path: countPath,
377
+ description: `Cohort count of undergraduate federal Direct Loan borrowers for the ${horizon_years}-year statuses (${vintage})`,
378
+ type: 'integer',
379
+ sortable: false,
380
+ category: 'aid',
381
+ },
382
+ ...Object.values(BORROWER_REPAYMENT_STATUSES).map(({ label, suffix }) => ({
383
+ path: borrowerRepaymentField(horizon_years, suffix),
384
+ description: `Share of undergraduate federal Direct Loan borrowers assigned ${label} at ${horizon_years} years, published as a decimal or bin. Denominator is ${countPath} (${vintage})`,
385
+ type: 'string',
386
+ sortable: false,
387
+ category: 'aid',
388
+ })),
389
+ ];
390
+ }),
369
391
  // Completion / Outcomes
370
392
  {
371
393
  path: 'latest.completion.rate_suppressed.overall',
372
394
  description: 'Completion rate at 150% normal time (overall)',
373
395
  type: 'float',
374
- sortable: false,
396
+ sortable: true,
375
397
  category: 'completion',
376
398
  },
377
399
  {
@@ -385,9 +407,24 @@ export const FIELD_CATALOG = [
385
407
  path: 'latest.completion.consumer_rate',
386
408
  description: 'Consumer-friendly completion rate',
387
409
  type: 'float',
388
- sortable: false,
410
+ sortable: true,
389
411
  category: 'completion',
390
412
  },
413
+ // Retention
414
+ {
415
+ path: 'latest.student.retention_rate_suppressed.four_year.full_time_pooled',
416
+ description: 'Pooled share of first-time, full-time bachelor’s or equivalent degree-seeking undergraduates who return the next fall at four-year institutions',
417
+ type: 'float',
418
+ sortable: false,
419
+ category: 'retention',
420
+ },
421
+ {
422
+ path: 'latest.student.retention_rate_suppressed.lt_four_year.full_time_pooled',
423
+ description: 'Pooled share of first-time, full-time degree- or certificate-seeking undergraduates who return the next fall at less-than-four-year institutions',
424
+ type: 'float',
425
+ sortable: false,
426
+ category: 'retention',
427
+ },
391
428
  // Earnings — Institution Level
392
429
  {
393
430
  path: 'latest.earnings.6_yrs_after_entry.median',
@@ -530,7 +567,7 @@ export const FIELD_CATALOG = [
530
567
  path: 'latest.programs.cip_4_digit.earnings.4_yr.overall_median_earnings',
531
568
  description: 'Median earnings of graduates working and not enrolled 4 years after completion, pooled by OPEID6, CIP, and credential',
532
569
  type: 'integer',
533
- sortable: true,
570
+ sortable: false,
534
571
  category: 'programs',
535
572
  },
536
573
  {
@@ -551,7 +588,7 @@ export const FIELD_CATALOG = [
551
588
  path: 'latest.programs.cip_4_digit.code',
552
589
  description: 'CIP 4-digit program code (e.g. "11.07")',
553
590
  type: 'string',
554
- sortable: true,
591
+ sortable: false,
555
592
  category: 'programs',
556
593
  },
557
594
  {
@@ -579,28 +616,28 @@ export const FIELD_CATALOG = [
579
616
  path: 'latest.programs.cip_4_digit.debt.staff_grad_plus.all.all_inst.median',
580
617
  description: 'Median cumulative Stafford/Grad PLUS borrowing among completers across institutions at the same academic level',
581
618
  type: 'integer',
582
- sortable: true,
619
+ sortable: false,
583
620
  category: 'programs',
584
621
  },
585
622
  {
586
623
  path: 'latest.programs.cip_4_digit.counts.ipeds_awards1',
587
624
  description: 'IPEDS awards in year 1 of the pooled debt cohort; not enrollment or unique students',
588
625
  type: 'integer',
589
- sortable: true,
626
+ sortable: false,
590
627
  category: 'programs',
591
628
  },
592
629
  {
593
630
  path: 'latest.programs.cip_4_digit.counts.ipeds_awards2',
594
631
  description: 'IPEDS awards in year 2 of the pooled debt cohort; not enrollment or unique students',
595
632
  type: 'integer',
596
- sortable: true,
633
+ sortable: false,
597
634
  category: 'programs',
598
635
  },
599
636
  {
600
637
  path: 'latest.programs.cip_4_digit.credential.level',
601
638
  description: 'Credential level: 1=undergraduate certificate, 2=associate, 3=bachelor, 4=post-baccalaureate certificate, 5=master, 6=doctoral, 7=first professional, 8=graduate/professional certificate, 99=non-credential',
602
639
  type: 'integer',
603
- sortable: true,
640
+ sortable: false,
604
641
  category: 'programs',
605
642
  },
606
643
  ];