## ADDED Requirements ### Requirement: Mode-dependent enumeration ordering The decision-tree search SHALL select its DFS child-ordering heuristic based on the optimization mode. In `priority-order` mode, children at each level SHALL be ordered with `priorityTarget`-qualifying courses first (existing behavior). In `maximize-count` mode, children SHALL be ordered by the descending count of their qualifications for *reachable* specializations (specializations whose upper-bound credit potential meets the credit threshold). #### Scenario: maximize-count orders generalist courses first - **WHEN** maximize-count mode is selected and Fall Set 3 contains both `fall3-climate-finance` (qualifies for BNK/CRF/FIN/FIM/GLB/SBI — 6 reachable specs) and `fall3-emerging-tech` (qualifies for BRM/EMT/ENT/MTO/STR — 5) - **THEN** the DFS visits combinations including `fall3-climate-finance` before combinations including `fall3-emerging-tech` #### Scenario: priority-order ordering is unchanged - **WHEN** priority-order mode is selected with HCR ranked first - **THEN** courses qualifying for HCR are tried before courses that do not (existing target-first behavior) ### Requirement: Cells distinguish unevaluated from evaluated-zero Each `ChoiceOutcome` SHALL carry an `evaluated: boolean` field. The field SHALL initialize to `false`. The field SHALL be set to `true` upon the first leaf evaluation that includes the corresponding `(setId, courseId)` pair. The UI SHALL render unevaluated cells with a visible "still searching" indicator distinct from cells that are evaluated and achieve zero specializations. #### Scenario: New cell starts unevaluated - **WHEN** the search begins - **THEN** every choice in every set has `evaluated: false` #### Scenario: First leaf marks the cell evaluated - **WHEN** any leaf containing `(spr3, spr3-analytics-ml)` has been evaluated - **THEN** the cell for `spr3-analytics-ml` has `evaluated: true` #### Scenario: UI distinguishes unevaluated from evaluated-zero - **WHEN** a cell has `evaluated: false` - **THEN** the UI renders a "searching" indicator (not "0 specs") - **AND WHEN** a cell has `evaluated: true` and `ceilingSpecs.length === 0` - **THEN** the UI renders "0 specs" in muted styling ### Requirement: Per-set and global progress indication The decision-tree worker SHALL emit a `progress` event carrying `{ iterations, iterationsTotal }` at most once every 100 milliseconds during an active search. The UI SHALL display global search progress in the Top Plans panel header and SHALL display a per-set indicator next to each elective set's name while that set has at least one unevaluated choice and the search is still running. #### Scenario: Progress events emitted at throttled rate - **WHEN** the search is running - **THEN** the worker emits at most one `progress` event per 100ms #### Scenario: Global progress visible in header - **WHEN** the search is running and 15234 of 49152 leaves have been evaluated - **THEN** the Top Plans header shows progress text such as `Searching… 15234 / 49152 explored` #### Scenario: Per-set indicator shown while choices are unevaluated - **WHEN** the search is running and Spring Set 1 has at least one choice with `evaluated: false` - **THEN** a spinner or activity indicator appears next to the Spring Set 1 heading - **AND WHEN** every choice in Spring Set 1 has `evaluated: true` (or the search completes) - **THEN** the indicator clears ### Requirement: Recommended choice per set For each open elective set, the UI SHALL identify and visually mark the choice with the best `(ceilingCount desc, priorityScore desc)` ordering as the "Recommended" choice. The marker SHALL be visible only when at least one choice in the set has `evaluated: true`. The recommendation SHALL update progressively as the search streams better outcomes for that set's choices. #### Scenario: Recommended marker uses same comparator as top-K - **WHEN** Spring Set 3 has choices with ceilings `[HCR, BNK]` (count=2, score=29) and `[FIN, MTO]` (count=2, score=22) - **THEN** the choice with `[HCR, BNK]` is marked Recommended #### Scenario: Higher count beats higher priority for recommendation - **WHEN** one choice has ceiling count 3 and another has ceiling count 2 with higher priority score - **THEN** the count-3 choice is Recommended ## MODIFIED Requirements ### Requirement: Bounded search with saturation termination The decision-tree search SHALL terminate when the iteration count exceeds `MAX_TREE_ITERATIONS` (default 100,000). When this cap terminates the search before the cartesian product has been fully enumerated, the result SHALL include `partial: true`. The search SHALL otherwise enumerate every leaf in the cartesian product of open-set courses (the saturation-limit termination is removed). #### Scenario: Search exhausts the cartesian product when within the cap - **WHEN** the open-set cartesian product is smaller than `MAX_TREE_ITERATIONS` - **THEN** the search evaluates every leaf, every cell ends with `evaluated: true`, and `partial` is `false` #### Scenario: Search returns partial when cap is hit - **WHEN** the cartesian product exceeds `MAX_TREE_ITERATIONS` - **THEN** the search stops at the cap, sets `partial: true`, and returns the best top-K and ceilings found so far ### Requirement: Decision-tree worker protocol The decision-tree worker SHALL accept a `WorkerRequest` that includes optional `topK` (default 10). It SHALL emit a tagged-union `WorkerResponse` stream with four event types: `topKUpdate` (when the ranked top-K list changes), `choiceUpdate` (when a per-set ceiling cell changes), `progress` (throttled to 100ms intervals during search, carrying iteration counters), and `allComplete` (when the search terminates, carrying both final top-K and final per-set analyses, plus a `partial` flag). #### Scenario: Worker emits progress events - **WHEN** the search runs for more than 100ms - **THEN** the worker emits at least one `progress` event with `iterations` and `iterationsTotal` #### Scenario: Worker emits final allComplete event with partial flag - **WHEN** the search terminates - **THEN** the worker emits `{ type: 'allComplete', topK, setAnalyses, partial }` - **AND** `partial` is `true` only if the iteration cap fired #### Scenario: Worker emits per-cell choice updates - **WHEN** a single combination causes a ceiling change for one course in one set - **THEN** the worker emits one `choiceUpdate` event identifying that set ## REMOVED Requirements ### Requirement: Saturation-limit early termination **Reason**: The saturation criterion (`top-K stable for SATURATION_LIMIT iterations`) terminates the search before many `(set, course)` pairs have been evaluated and before the search reaches deeper combinations that yield higher-count outcomes. The user-visible result is "0 specs" labels on un-evaluated cells and missing high-count plans in the top-K. Replaced by exhaustive enumeration up to the iteration cap. **Migration**: No consumer migration needed. The `searchDecisionTree` function signature is unchanged; behavior changes from "may stop early" to "always exhausts (within cap)". The `partial: true` flag remains as the only signal that the result may be incomplete.