Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,68 @@ All notable changes to NuAnalytics are recorded here. Format loosely
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the
project uses semantic versioning.

## [0.5.0] — 2026-06-09

Adds database-backed degree storage: 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. Additive — no breaking
changes.

### Added

- **Normalized program storage schema** (`docs/database/programs-schema.sql`,
seeded by `docs/database/program-lookup-seed.sql`). Eight new tables:
`degree_types` (lookup), `programs` (one row per program, with the lossless
unified-JSON `document` JSONB as the source of truth plus a queryable scalar
projection), `courses` (shared per-institution catalog), `program_courses`
(M:N junction with per-program credit/name overrides), `program_requirements`
(the requirement tree flattened by `req_path`/`parent_path`, with JSONB
`selection_spec`/`req_constraints` and `is_impossible`/`allow_double_count`
flags), `analysis_runs` (one per `degree analyze` run: `variant`, `trimmed`,
`variations_run`, `sample_type`, the `degree_metrics` JSONB plus promoted
`complexity_mean`/`delay_mean`/`credits_mean`), `analysis_course_metrics`
(per run × course), and `analysis_plans` (per run × selected plan). Design is
hybrid (lossless document + normalized projection) with FK-free natural keys
(`program_key`, `(institution_ref, course_code)`) matching the existing
`degrees`/`completions` convention, RLS gated on
`auth.role() = 'authenticated'` for every read and write, and idempotent
re-sync via a per-import `generation` stamp. Apply with
`nuanalytics db exec-sql docs/database/programs-schema.sql` then
`nuanalytics db exec-sql docs/database/program-lookup-seed.sql`.

- **`db import <FILES>...`** — import degree-first analysis reports
(`*_report.json`) or plain unified/ai-landscape/YAML degrees into the
normalized program tables. One report populates the program projection
(`programs`, `courses`, `program_courses`, `program_requirements`) and, when
it carries an `analysis` block, one analysis run with its course metrics and
selected plans. Resolves the institution (`degree.unitid` fast-path → name+CIP
lookup → name slug; an ambiguous name lists candidate institutions and writes
nothing). `program_key` includes `catalog_year`, so different years coexist;
overwriting an existing program needs `--replace` (unverified) or `--force`
(verified). Flags: `--variant`, `--unitid`, `--institution`, `--cip`,
`--catalog`, `--degree-id`, `--force`, `--replace`, `--skip-existing`,
`--dry-run`, `-j/--jobs`. A single file prints a detailed outcome; a
directory/batch isolates per-file failures to `import_failures.log` with a
summary.

- **`import_degree` MCP tool** — the same import core over MCP. Takes
`json_content` or `json_path` (exactly one) plus the import overrides; returns
a structured result tag (`created`/`updated`/`skipped`/`needs_confirmation`/
`institution_ambiguous`/`rejected`) with the row counts, and attaches
`institution_candidates`/`reason`/`errors` on the blocked variants. DB-gated
(registered only with a logged-in session); `dry_run` previews without
writing.

- **`degree analyze --from-db <NAME>`** — analyze a stored program pulled from
the database instead of a file. Resolves by exact `program_key`, then exact
`degree_id`, then a `name` substring; an ambiguous name lists the candidates
and stops. Mutually exclusive with positional `FILES`; single-program only
(no worker pool).

- **`degree.unitid` field** — optional IPEDS unit id on the degree model, set in
a degree file's `degree:` block, used to link an imported program to its
institution.

## [0.4.1] — 2026-06-04

This release is additive — no breaking changes from 0.4.0. It introduces a
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nu-analytics"
version = "0.4.1"
version = "0.5.0"
edition = "2021"
authors = ["Albert Lionelle <a.lionelle@northeastern.edu>"]
description = "A CLI tool for computing Curricular Analytics metrics. Based off metrics from CurricularAnalytics.org. Currently, only basic metrics and additional reporting features."
Expand Down Expand Up @@ -40,6 +40,7 @@ reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-feature
csv = { version = "1.3", optional = true }
zip = { version = "8", optional = true }
form_urlencoded = { version = "1.2", optional = true }
sha2 = { version = "0.10", optional = true }

[dev-dependencies]
tempfile = "3.10"
Expand All @@ -57,7 +58,7 @@ log-info = []
log-debug = []
verbose = []
file-logging = []
database = ["dep:supabase-client-sdk", "dep:reqwest", "dep:csv", "dep:zip", "dep:tokio", "dep:form_urlencoded"]
database = ["dep:supabase-client-sdk", "dep:reqwest", "dep:csv", "dep:zip", "dep:tokio", "dep:form_urlencoded", "dep:sha2"]
mcp = ["dep:rmcp", "dep:tokio", "database"]

[profile.release]
Expand Down
3 changes: 2 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# NuAnalytics

**Version 0.4.1**
**Version 0.5.0**

NuAnalytics is a Rust-based tool for analyzing computer science curricula. It computes detailed metrics about curriculum structure including complexity, blocking relationships, delay paths, and centrality measures to help understand how courses are organized and their impact on students.

Expand Down Expand Up @@ -35,6 +35,7 @@ It is based off the work of Greg Heileman, and CurricularAnalytics.org. Current
- **Configuration Management**: Flexible configuration system with CLI overrides
- **MCP Server** (optional): AI model integration for interactive degree building via Model Context Protocol. Degree tools accept YAML, unified JSON, or raw ai-landscape JSON; `convert_degree` *(new in 0.4.1)* converts ai-landscape JSON to the unified format and `get_degree_json_schema` *(new in 0.4.1)* returns the machine schema. The `trim_degree` tool pipes a fresh `cache:<hash>` handle back for chained `validate_degree` / `audit_degree` calls.
- **Authenticated Database Access** *(behavior change in 0.4.0)*: Supabase reads and writes both require a logged-in user (`nuanalytics db login`); session tokens auto-refresh.
- **Degree Database Import & Stored Programs** *(new in 0.5.0)*: Import degree reports into a normalized, queryable program store (`nuanalytics db import`, or the `import_degree` MCP tool) — one report becomes a program plus its courses, flattened requirement tree, and analysis run, alongside the lossless source document. Stored programs can be analyzed straight from the database with `degree analyze --from-db <NAME>`. See [Database Setup](docs/database/setup.md#stored-programs-normalized).

## Quick Start

Expand Down
21 changes: 21 additions & 0 deletions docs/database/program-lookup-seed.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- NuAnalytics Program Lookup Seed
-- Run after docs/database/programs-schema.sql, via:
-- nuanalytics db exec-sql docs/database/program-lookup-seed.sql
--
-- Seeds the degree_types lookup. Codes are the normalized degree_type values the
-- importer writes to programs.degree_type. Values observed in the corpus
-- (json_corrected + json_corrected_rebuilt): BS, BA, MS, Minor, Certificate,
-- BSE, BAS, AS, Micro-Credential. Idempotent (ON CONFLICT DO UPDATE).

INSERT INTO degree_types (code, label, level, is_degree) VALUES
('BS', 'Bachelor of Science', 'undergraduate', true),
('BA', 'Bachelor of Arts', 'undergraduate', true),
('BSE', 'Bachelor of Science in Engineering', 'undergraduate', true),
('BAS', 'Bachelor of Applied Science', 'undergraduate', true),
('AS', 'Associate of Science', 'undergraduate', true),
('MS', 'Master of Science', 'graduate', true),
('MINOR', 'Minor', 'undergraduate', false),
('CERT', 'Certificate', NULL, false),
('MICRO', 'Micro-Credential', NULL, false)
ON CONFLICT (code) DO UPDATE
SET label = EXCLUDED.label, level = EXCLUDED.level, is_degree = EXCLUDED.is_degree;
Loading
Loading