# KOPR R163 — Comet Analyzer automatic range invalidation report

**Base build:** `KOPR_CLEAN_BUILD_20260801_R162_COMET_ANALYZER_CHANNEL_VISIBILITY_ENCODING_HOTFIX.zip`  
**Date:** 2026-08-01  
**Status:** PASS (PyQt-dependent historical gates remain PASS_HEADLESS)

## 1. Problem

The Analyzer already derived axis minima and maxima from rendered observations when no explicit limits were supplied, but ownership of manual limits was not centralised. A previously committed range could survive a change of target, loaded observations, channel visibility, observers, magnitude projection, phase correction, `d_90`, or formula content. Conversely, some settings that did not change plotted coordinates could reset the range unnecessarily.

## 2. Production changes

### 2.1 Qt-free AUTO/USER range contract

Added `analyzer_range_state.py` with:

- `AnalyzerRangeMode.AUTO`;
- `AnalyzerRangeMode.USER`;
- immutable `AnalyzerRangeIdentity`;
- immutable `AnalyzerRangeState`;
- `resolve_analyzer_range()`;
- `invalidate_analyzer_range()`.

A USER range is retained only while its complete rendered-data identity remains unchanged. A changed identity resolves to AUTO with no explicit limits.

### 2.2 Range identity

The identity includes:

- loaded observation fingerprint;
- selected target set;
- enabled target/return identities;
- visible channel IDs;
- enabled observers;
- uncertain and negative observation filtering;
- X-axis projection;
- magnitude mode;
- phase-correction state and display mode;
- dust `d_90`;
- complete per-target regular/additional formula document.

The identity deliberately excludes:

- comet and channel colours;
- comet and channel markers;
- display-encoding mode;
- legend layout;
- FitGroup labels, membership and enabled state;
- Editing-target navigation;
- Bortle reference-line visibility.

Target, channel, observer and return ordering is normalised so a pure reorder does not invalidate the range. Formula fingerprinting uses the complete sorted per-target document and does not depend on the current Editing target.

### 2.3 Range-field intent tracking

The four range fields now record `textEdited` without triggering a redraw. This distinguishes deliberate user input from values populated automatically by the renderer.

- Deliberately edited valid fields + Refresh commit USER limits.
- Unedited stale fields after a data/formula identity change are ignored; Refresh requests a new AUTO range.
- Programmatic range-field updates clear the dirty flag.

### 2.4 Central render resolution

`DoPlotUpdate()`, `_analysis_start_plot()` and `_render_current_view()` resolve limits through the same range contract. Historical four-value lists passed by internal callbacks are accepted only when they still belong to the unchanged USER identity.

New target selection explicitly invalidates the prior range. Reloaded data are detected through the observation fingerprint when the new dataset is committed.

### 2.5 Settings classification

Range reset is now limited to settings that can change displayed rows or coordinates:

- reject uncertain;
- reject negative;
- phase correction;
- phase display mode;
- dust `d_90`;
- return selection;
- observer selection;
- channel `Show` state.

Fit-only, Bortle-reference and display-style changes redraw without resetting limits.

### 2.6 Automatic min/max source

AUTO limits are derived after rendering:

1. selected targets and returns;
2. visible observation channels;
3. observer and quality filters;
4. current X and magnitude transforms;
5. regular model series;
6. additional model series.

The Bortle survival reference line remains excluded so it cannot flatten the scientific data range. An explicit validation error is produced if no visible observations or model curves match the current selection.

## 3. Scientific non-impact

R163 does not change:

- stored observations;
- magnitudes or colour indices;
- phase-correction equations;
- fit mathematics;
- ephemerides or geometry;
- channel selection semantics;
- display-style assignments.

The change is restricted to deciding when explicit plot limits remain valid and how AUTO minima/maxima are derived.

## 4. Files changed

Production:

- `analyzer_range_state.py` — new;
- `wlightcurves.py`;
- `docs/CHANGELOG.md`.

QA additions/updates:

- `tests/test_r163_analyzer_automatic_range_invalidation.py`;
- `r163_comet_analyzer_range_invalidation_release_gate.py`;
- `validate_performance_release.py`.

## 5. Validation

- R163 targeted tests: **22 passed**.
- R163 release gate: **11/11 PASS**.
- R130–R163 regression set: **260 passed, 2 skipped**.
- R146–R153, R155–R160, R162 and R163 release gates: **PASS**.
- R154 and R161 gates: **PASS_HEADLESS** because PyQt5 is unavailable on the build host.
- Aggregate performance/release validator: **PASS**.
- Python syntax compilation: CLEAN **99 files / 0 errors**; QA **382 files / 0 errors**.
- CLEAN distribution contains no tests, benchmarks or release-gate scripts.

## 6. Acceptance examples

1. A manual range is active; only CCD V and CCD R are left visible. The old manual limits are discarded and the fields are repopulated from V/R observations and active models.
2. Switching between comet-colour and channel-colour encoding preserves the current range.
3. Changing a comet or channel colour preserves the current range.
4. Reloading the same target with newly downloaded observations recomputes AUTO limits from the new observation fingerprint.
5. Changing `d_90`, phase correction, X axis, magnitude mode or formula content recomputes AUTO limits unless the user deliberately edits and commits new range values.
6. Changing FitGroup membership or Editing target does not alter the range.
