Skip to content

Release v0.5.0 — database-backed degree import & stored programs#11

Merged
lionelle merged 10 commits into
mainfrom
mcp
Jun 9, 2026
Merged

Release v0.5.0 — database-backed degree import & stored programs#11
lionelle merged 10 commits into
mainfrom
mcp

Conversation

@lionelle

@lionelle lionelle commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Release v0.5.0

Additive — no breaking changes from 0.4.1. Adds a database-backed degree-storage subsystem: a normalized, queryable projection of imported degree programs alongside the lossless source document, plus the CLI, MCP, and analyze paths that write to and read from it.

Highlights

  • Normalized program storage schema (docs/database/programs-schema.sql + program-lookup-seed.sql): 8 tables — degree_types, programs (lossless document JSONB + queryable scalar projection), courses (shared per-institution catalog), program_courses (M:N junction), program_requirements (requirement tree flattened by req_path/parent_path), analysis_runs / analysis_course_metrics / analysis_plans. Hybrid storage, FK-free natural keys, RLS auth-required, idempotent generation-stamped re-sync.
  • db import <FILES>... — import degree-first reports (or unified/YAML degrees) into the tables; institution resolution (degree.unitid → name+CIP → slug; ambiguous → candidates), catalog_year-aware program_key, --variant/--dry-run/--force/--replace/-j, batch isolation.
  • import_degree MCP tool — the same import core over MCP (DB-gated; dry_run preview; institution_candidates on ambiguity).
  • degree analyze --from-db <NAME> — analyze a stored program pulled from the DB (program_key → degree_id → name ILIKE; ambiguous → candidate list).
  • degree.unitid field on the degree model for institution linking.

Verification

  • cargo fmt --check, cargo clippy --all-targets --all-features -D warnings, full test suite — all green.
  • Exercised end-to-end: db import --dry-run against real full_degree/metrics reports (name+CIP resolution, idempotent skip), a live-DB field review across all 8 tables (no orphans; requirement-tree adjacency intact), the import_degree tool, and analyze --from-db (unique + ambiguous paths).

See CHANGELOG.md [0.5.0] for the full entry.

🤖 Generated with Claude Code

lionelle and others added 10 commits June 7, 2026 19:47
`expand_courses_with_prerequisites` ran a Phase-2 redundancy prune that could
delete a course from the original plan when it was also an OR-prerequisite
alternative for another course — e.g. a `type: all` core course CS320 that is
also an option of an elective's `CS320 | CS370` prereq was silently dropped
from generated plans whenever the sibling CS370 was present. Now only courses
ADDED during expansion may be pruned; original plan courses (and user
--include `protected_courses`) are preserved.

Adds regression test test_expand_preserves_required_or_alternative.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…layout only)

Database layout for the upcoming `db import` feature — store imported degree
programs as queryable rows instead of an opaque YAML blob. Not yet applied to
the live database.

- docs/database/programs-schema.sql: `programs` (+ lossless `document` JSONB as
  source of truth), shared `courses` catalog + `program_courses` M:N junction,
  `program_requirements` (recursive requirement tree flattened via
  req_path/parent_path), `degree_types` lookup, and metrics tables
  (`analysis_runs` / `analysis_course_metrics` / `analysis_plans`) keyed by
  run_key so trimmed-vs-full and differing iteration counts coexist. Indexes
  (pg_trgm on courses.name, GIN on programs.tags) + RLS cloned from the
  `degrees` pattern. Idempotent (IF NOT EXISTS / DROP POLICY) — safe to re-run
  on a live DB.
- docs/database/program-lookup-seed.sql: seeds `degree_types`.
- src/core/database/mod.rs: tables:: consts for the new tables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Import a degree-first analysis report (or a plain unified degree) into the
normalized program tables. One report populates the program, its shared courses
+ junction, the flattened requirement tree, and — when the report carries an
`analysis` block — one analysis run with its per-course metrics and sample plans.

- core `src/core/database/import.rs`: pure `build_import_plan` (report → rows,
  used by --dry-run) + async `execute_import` (institution resolution →
  existence/verified decision → ordered writes, children first / program last).
  `program_key` includes catalog_year so different years coexist; `full` is the
  canonical upload, non-`full` variants attach a run only. sha256 content hashes.
- models: Stored{Program,Course,ProgramCourse,ProgramRequirement,AnalysisRun,
  AnalysisCourseMetric,AnalysisPlan}.
- `degree.unitid` (IPEDS id) added to the Degree model — fast-path institution
  linking; falls back to name+cip resolution, else an institution-name slug.
- CLI `nuanalytics db import <FILES>... [--variant] [--unitid] [--cip] [--catalog]
  [--degree-id] [--force|--replace|--skip-existing] [--dry-run] [-j]`: single-file
  detailed outcome (institution, variations, sample plans, row counts) + batch
  isolation with import_failures.log.
- sha2 dependency, gated to the `database` feature.

Verified via --dry-run against full_degree/metrics reports (name+cip institution
resolution, idempotent skip on unchanged hash) and a live-DB field review
(3 programs / 305 courses / 80 requirements / 3 runs / 216 course-metrics /
21 plans; no orphans; requirement-tree adjacency intact). Clippy
(--all-targets --all-features -D warnings) clean; import unit tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New MCP tool wrapping the shared import core: imports a degree-first report
(or unified degree) JSON into the normalized program tables. DB-gated like the
other database tools (only exposed when a session exists).

- params: json_content | json_path (+ variant/unitid/cip/catalog/degree_id/
  force/replace/skip_existing/dry_run); lenient scalar/bool deserializers.
- response: result tag (created|updated|skipped|needs_confirmation|
  institution_ambiguous|rejected) + counts + institution/variations_run/
  sample_type; institution_candidates on ambiguity, reason/errors otherwise.
- registered in server.rs via call_db.

9 offline unit tests (request deserialization, exactly-one-source validation,
outcome→JSON mapping for every ImportResult variant).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`degree analyze` can now pull a program's canonical degree from the database
instead of a local file. Resolve ladder: exact program_key → exact degree_id →
name ILIKE. One match analyzes it (reusing the normal single-degree pipeline via
the extracted `analyze_program` seam); zero → error; many → prints the candidate
programs (program_key · name · institution · catalog_year) so the caller can
re-run more specifically.

- `--from-db <NAME>` on Analyze; positional FILES now optional, with a
  one-of-{files, --from-db} check in the dispatcher.
- the stored `document` is rehydrated via parse_degree_auto (recovers the
  structured prerequisites).
- dedupe: degree.rs reuses db.rs's `make_runtime` (now pub(super)).

Offline tests: candidate formatting, row parsing (malformed / non-array),
document round-trip, invalid-document panic-safety.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Analysis report JSONs for the three sample degrees (csu/neu/uhm), produced by
`degree analyze`, kept as reference samples of the degree-first report format
and as convenient `db import` fixtures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rom-db

- CHANGELOG: new [Unreleased] section (normalized program schema, db import,
  import_degree, analyze --from-db, degree.unitid).
- docs/degree.md: `--from-db <NAME>` option + "Analyze a stored program" section.
- docs/mcp.md: `import_degree` tool (params/response/example) + search_institutions
  cross-reference for unitid lookup.
- docs/database/setup.md: programs-schema.sql + program-lookup-seed.sql in the
  run order, and a "Stored programs (normalized)" section (8 tables + db import).
- Readme: degree database import / stored-programs feature bullet.
- docs/degree-ingestion-plan.md: status note that the import path is implemented.
- programs-schema.sql: fix stale program_key comment (prog: form, not id:/nat:).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Normalized program storage schema (programs/courses/program_courses/
program_requirements + analysis_runs/analysis_course_metrics/analysis_plans +
degree_types), the `db import` CLI, the `import_degree` MCP tool,
`degree analyze --from-db`, and the `degree.unitid` field. Additive — no
breaking changes from 0.4.1.

Closes out the CHANGELOG [Unreleased] section as [0.5.0].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`cargo doc --no-deps --all-features` (CI's Documentation job, RUSTDOCFLAGS=-D
warnings) failed on two `[`DegreeProgram`]` intra-doc links in degree.rs: in the
bin crate the type is referenced by full path (`nu_analytics::core::DegreeProgram`)
and isn't in scope as a bare name, so rustdoc couldn't resolve the link. Demote
both to plain code spans.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lionelle lionelle merged commit 589e0e6 into main Jun 9, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant