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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ dist
/metrics
/reports

# AI Landscape tool for interaction
/ai-landscape-tools


# WSL / windows file, not needed
*Zone.Identifier*

server.log
Expand Down
87 changes: 87 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,93 @@ 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.4.1] — 2026-06-04

This release is additive — no breaking changes from 0.4.0. It introduces a
unified JSON degree format and the tooling around it, parallel/process-isolated
batch analysis, and two new MCP tools, plus two engine fixes that make
machine-converted catalogs analyzable.

### Added

- **Unified JSON degree format.** Degree programs can now be authored and
consumed as JSON (the `DegreeProgram` model serialized directly) alongside
YAML. Every `degree` subcommand auto-detects the format on load (content
starting with `{`/`[` → JSON, otherwise YAML), and raw ai-landscape JSON
shapes are converted on the fly. Prerequisites serialize as a symmetric
tagged structure (`{"and"|"or": [...]}`, with a bare string as a leaf), and
`tags` are available on degrees, requirements, and courses.

- **`degree convert`** — convert ai-landscape program JSON into the unified
format. Category lists and picklists map to requirements, AND-of-OR
prerequisites flip into the internal `PrereqExpr` tree, and missing credits
default to 3 (with warnings). ai-landscape *cluster* pipeline files
(`course_verifier`/`course_scraper.<program>.results`) expand into one unified
file per program with collision-safe `<school>__<program>.unified.json` names.
`-o <PATH>` accepts a file (single input) or directory; `--pretty` pretty-prints.

- **`degree schema`** — emit the unified-degree JSON Schema
(`src/assets/degree.schema.json`), the same schema the MCP server serves, to
stdout or `-o <PATH>`. The schema now documents the `from` clause
(`fromClause`: courses / pattern / include / exclude / groups), confirming the
unified format supports the same wildcard gen-ed/elective pools as YAML
(e.g. `"CS:2500+"`, `"*:*"`).

- **Parallel `degree analyze` (`-j`/`--jobs`, default 8).** A multi-file analyze
now runs as a rolling pool of worker processes, one file per OS process. A
pathological degree (e.g. a full-catalog scrape) is contained to its own
process: if it OOMs or crashes, the kernel kills only that child, the parent
records it in `<metrics-dir>/failures.log` with its exit status (so a
`SIGKILL` is distinguishable from a non-zero exit), and the rest continue.
Single-file, `--school`, and `-j 1` runs stay in-process with full per-degree
output.

- **`--school <NAME>` on `degree analyze`** — treat all inputs as programs of
one school and emit a combined `<school>_school_report.json` rolling up
degree-level metrics across the programs.

- **`scripts/analyze-batch.sh`** — process-isolated batch analyze with a
per-process virtual-memory cap (`ulimit -v`) and a timeout, for running large
directories of degrees without the OS OOM-killer taking down the whole batch.

- **JSON input for `degree trim`.** Trim now accepts unified (and raw
ai-landscape) JSON and writes the trimmed program back in the input's format —
a `.json` input yields a trimmed `.json`; YAML stays YAML.

- **Metrics-rich report JSON.** Output JSON now opens with the degree block and
is laid out degree → analysis → requirements → selected plans → courses. Each
selected plan carries its courses, credits, course count, critical path, and a
term-by-term schedule (mirroring the MCP `analyze_degree` shape). `total_credits`
surfaces at the top.

- **New MCP tools.** `convert_degree` (ai-landscape JSON → unified JSON +
warnings, caching the result for chaining by `degree_id`; a cluster file
returns a bounded program inventory) and `get_degree_json_schema` (returns the
machine JSON Schema). The existing degree tools
(`validate_degree` / `analyze_degree` / `audit_degree` / `trim_degree` /
`get_course_detail`) now accept unified and ai-landscape JSON content — and the
`cache:<hash>` handle from `convert_degree` — in addition to YAML, via a
content-level format sniff; `validate_degree` surfaces any
`conversion_warnings`.

### Fixed

- **Out-of-memory on large select pools.** `RequirementResolver` materialized
every `C(n, k)` combination of a select pool — a "choose 15 of 42" pool
(~10¹¹) could allocate tens of GB and get OOM-killed even for an otherwise
tiny program. Combination generation is now bounded: when `C(n, k)` exceeds
2000, it deterministically down-samples to that cap. Peak memory on the worst
catalog programs drops from >6 GB to ~25–120 MB.

- **Converted programs collapsed to a single plan.** Elective-category selects
were excluded from the plan space (`ENUMERABLE_CATEGORIES` was `["major"]`
only), so converted programs produced one plan with `std_dev = 0`. Electives
are now enumerated (plan-count estimation uses saturating multiplication so a
capped pool can't overflow), restoring real metric spread.

- **JSON parse errors** now report as a distinct `JsonError` rather than
"YAML Parse Error".

## [0.4.0] — 2026-05-20

### Breaking changes
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nu-analytics"
version = "0.4.0"
version = "0.4.1"
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
8 changes: 5 additions & 3 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# NuAnalytics

**Version 0.4.0**
**Version 0.4.1**

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 All @@ -16,7 +16,9 @@ It is based off the work of Greg Heileman, and CurricularAnalytics.org. Current
- **Curriculum Analysis**: Parse CSV-formatted curriculum files and analyze course dependencies
- **Degree Program Validation**: Validate YAML-based degree programs with comprehensive checks
- **Degree Plan Generation**: Generate all possible degree plans and compute aggregate statistics
- **Degree Trim** *(new in 0.4.0)*: Collapse a degree YAML to one walkable shortest-path-per-course variant — useful for visualisation and downstream tools that don't reason about alternatives. Protected major subjects keep all options; pattern pools survive orphan pruning.
- **Unified JSON Degree Format** *(new in 0.4.1)*: Author and analyze degrees as JSON as well as YAML — every `degree` subcommand auto-detects the format on load. `degree convert` turns ai-landscape program JSON into the unified format, and `degree schema` emits its JSON Schema for downstream validation.
- **Parallel Batch Analysis** *(new in 0.4.1)*: `degree analyze -j N` runs a process-isolated worker pool (default 8) so a pathological degree (e.g. a full-catalog scrape) can't take down the whole batch; failures are logged and the run continues.
- **Degree Trim** *(new in 0.4.0)*: Collapse a degree to one walkable shortest-path-per-course variant — useful for visualisation and downstream tools that don't reason about alternatives. Protected major subjects keep all options; pattern pools survive orphan pruning. Accepts YAML or JSON and round-trips the input format.
- **Gen-Ed Tracking**: Track how major courses satisfy general education requirements
- **Graph Analysis**: Build and analyze course prerequisite graphs
- **Curriculum Auditing**: Identify issues like missing prerequisites and complex prerequisite chains
Expand All @@ -31,7 +33,7 @@ It is based off the work of Greg Heileman, and CurricularAnalytics.org. Current
- **Markdown**: Text-based reports for documentation
- **Term Scheduling**: Automatic course scheduling respecting prerequisites and credit limits
- **Configuration Management**: Flexible configuration system with CLI overrides
- **MCP Server** (optional): AI model integration for interactive degree building via Model Context Protocol, including a `trim_degree` tool that pipes a fresh `cache:<hash>` handle back for chained `validate_degree` / `audit_degree` calls.
- **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.

## Quick Start
Expand Down
103 changes: 103 additions & 0 deletions docs/degree.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,20 @@ nuanalytics degree print-graph samples/degrees/csu-cs-bscs-general.yaml

# Trim alternatives down to a single shared shortest path
nuanalytics degree trim samples/degrees/csu-cs-bscs-general.yaml

# Convert ai-landscape program JSON to the unified degree JSON
nuanalytics degree convert program.json -o converted/

# Emit the unified-degree JSON Schema
nuanalytics degree schema -o degree.schema.json
```

> **Input formats:** every subcommand accepts both YAML and the unified
> JSON degree format, and auto-detects which on load (content starting
> with `{` or `[` is parsed as JSON, otherwise YAML). Raw ai-landscape
> program JSON is detected and converted automatically. See
> [Input File Format](#input-file-format).

> **Migration note:** prior versions exposed these as flags
> (`degree --validate`, `degree --analyze`, etc.). Every flag has moved
> to a subcommand of the same name. There is no implicit default action;
Expand Down Expand Up @@ -66,6 +78,8 @@ This generates:
| `--report-dir <DIR>` | Override reports output directory | from config |
| `--metrics-dir <DIR>` | Override metrics output directory | from config |
| `--include <COURSES>` | Courses to always include in all plans (comma-separated) | none |
| `-j, --jobs <N>` | Files to analyze concurrently when multiple are given, each in its own process (see [Parallel analysis](#parallel-analysis)) | 8 |
| `--school <NAME>` | Treat all inputs as programs of one school and also emit a combined `<school>_school_report.json` rollup | none |

**Examples:**

Expand Down Expand Up @@ -112,6 +126,32 @@ nuanalytics degree analyze --include "CS3500,STAT301" samples/degrees/csu-cs-bsc

`degree trim` reuses the same flag — see the **Trim** section below for trim-specific semantics.

### Parallel Analysis

When you pass **multiple** files to `analyze`, the command runs a rolling
pool of worker processes — one file per OS process, `-j/--jobs` at a time
(default 8). Process isolation matters: a pathological degree (for example a
full-catalog scrape with thousands of courses) is contained to its own
process, so if it exhausts memory or crashes, the kernel kills only that
child. The parent records the failure in `<metrics-dir>/failures.log` — one
`path<TAB>status` line, so an OOM kill shows as `signal: 9 (SIGKILL)` and is
distinguishable from a non-zero exit — and the remaining files carry on. The
parent prints a progress line and a summary; per-worker stdout/stderr is
suppressed.

```bash
# Analyze a directory of degrees, 12 at a time
nuanalytics degree analyze samples/degrees/*.yaml -j 12 --metrics-dir out/

# Roll the per-degree metrics up into one school report
nuanalytics degree analyze cs-programs/*.json --school "Example University"
```

Single-file runs, `--school`, and `-j 1` run in-process with full per-degree
console output. For an externally throttled variant (a per-process
`ulimit -v` memory cap and a timeout), see `scripts/analyze-batch.sh`; the
in-process pool deliberately imposes no ulimit or timeout.

### Validation (`validate`)

```bash
Expand Down Expand Up @@ -231,6 +271,60 @@ prints the protected-subject set and the list of removed orphans.
- `type: one_of` concentrations are preserved as a whole; trim recurses
into each concentration's nested requirements.

### Convert (`convert`)

Convert program file(s) into the unified degree JSON format.

```bash
nuanalytics degree convert path/to/program.json
```

The input may be raw ai-landscape program JSON (auto-detected and
converted), an existing unified JSON file, or YAML; the output is always
unified JSON with structured prerequisites. The converter maps ai-landscape
category lists and picklists to requirements, flips their AND-of-OR
prerequisites into the internal expression tree, and defaults missing course
credits to 3. Data-quality issues (such as assumed credits) are reported and
embedded as a `conversion_warnings` array in the output.

ai-landscape *cluster* pipeline files (`course_verifier` /
`course_scraper.<program>.results`) are expanded into **one unified file per
program**, using collision-safe `<school>__<program>.unified.json` names.

| Option | Description | Default |
|--------|-------------|---------|
| `-o, --out <PATH>` | Output file (single input) or directory (one `<stem>.unified.json` per input) | next to each input as `<stem>.unified.json` |
| `--pretty` | Pretty-print the JSON (default is compact, one line) | false |

```bash
# Convert a directory of ai-landscape programs into ./converted/
nuanalytics degree convert ai-landscape-tools/validation_jsons/*.json -o converted/

# Convert one program, pretty-printed
nuanalytics degree convert program.json --pretty -o program.unified.json
```

> This converter is transitional — once upstream emits unified JSON
> directly it is no longer needed.

### Schema (`schema`)

Print the JSON Schema for the unified degree format — useful for validating
unified JSON files in other tools or pipelines. This is the same
`degree.schema.json` the MCP server serves via `get_degree_json_schema`.

```bash
# Print to stdout
nuanalytics degree schema

# Write to a file
nuanalytics degree schema -o degree.schema.json
```

| Option | Description | Default |
|--------|-------------|---------|
| `-o, --out <PATH>` | Write the schema to this file instead of stdout | stdout |

## Analysis Output

### Console Output
Expand Down Expand Up @@ -332,6 +426,15 @@ nuanalytics config set metrics_dir "./metrics"

Degree programs are defined in YAML files with three main sections:

> **Unified JSON.** The same three-section model can also be expressed as
> *unified JSON* — the degree model serialized directly to JSON, with
> prerequisites written as a symmetric tagged structure
> (`{"and"|"or": [...]}`, a bare string being a single prerequisite) and an
> optional `tags` array on degrees, requirements, and courses. Every
> subcommand auto-detects YAML vs. JSON on load, and `degree schema` emits
> the JSON Schema for the format. The sections below describe the YAML form;
> the JSON form mirrors it field-for-field.

### Degree Metadata

```yaml
Expand Down
Loading
Loading