---
name: factor-review
description: The R&D review cycle — run the sweep, deflate it against the search ledger, build promotion dossiers and retirement cases for every factor, and produce the entry for the factor decision log. Use monthly, after a regime shift, or before any promotion.
---

# /factor-review — the evidence session

Scope: **$ARGUMENTS** (default: every factor in the registry)

This is the one cycle where the company decides what the product actually
believes. Run it deliberately, not casually.

## 0. Gather the sweep — box task, operator runs it

Nothing here can be answered from a keyless sandbox: a keyless session returns
"unavailable" for every provider-backed read, and that is the harness lying, not
a finding.

Hand the operator these, labels outside the fences, and state your prediction
before the output arrives:

```
cd /opt/market-terminal/deploy && docker compose exec app python -m scripts.validate_factors
```
```
cd /opt/market-terminal/deploy && docker compose exec app python -m scripts.reconcile_factor_ic
```

Both run **inside the `app` container** — the host is Python 3.8 with no project
dependencies (`Dockerfile:8` is `python:3.12-slim`). Read the pattern from
`docs/handoff.md:691` and `deploy/refresh-data.sh:25`, never from memory.

## 1. Fan out — three independent views per factor

Run concurrently, all read-only:

- **`mt-company:factor-validator`** — the gate arithmetic, the cortex cross-check,
  and the multiple-testing deflation the sweep itself omits.
- **`mt-company:decay-watch`** — trajectory and retirement case for every factor
  currently ON.
- **`mt-company:research-lead`** — is the *agenda* right? What is accruing that
  will never clear the bar, and should stop accruing?

## 2. Reconcile before you conclude

Run `reconcile_factor_ic` **first** and read it. It detects two failures that both
masquerade as edge or decay:

- per-row sample mismatch (`_SAMPLE_MISMATCH = 0.5`, `scripts/reconcile_factor_ic.py:66`)
- pooled coverage gap — the rows behind the IC are not the rows behind the hit
  rate (`:107-111`, `:168-182`)

A factor whose row is mixed-sample has produced **no evidence**, in either
direction. Say "unmeasured", not "no edge". They are different findings and this
repo has already confused them once (2026-08-02).

## 3. The deflation step — do not skip it

For each ENABLE the sweep emits, check it against `cortex`'s ledger:
`expected_max_z(trials)` (`cortex/core.py:159-186`). Every hypothesis previously
tested raised this bar.

- Sweep ENABLE **and** cortex `proven` → a real promotion candidate.
- Sweep ENABLE **and** cortex anything else → **not** a promotion. Report it as
  "passes the naive gate, fails the deflated one" and name which.
- Sweep ENABLE with IC unmeasurable → the sweep will pass this on hit-rate alone
  (`tests/test_validate_factors.py:86-91`). Cortex treats it as negative. Cortex
  is right.

## 4. Report

```
## Promotion candidates
<factor>  dossier -> PROMOTE | HOLD | INSUFFICIENT EVIDENCE
## Retirement candidates
<factor>  case -> KEEP | WATCH | OPERATOR SHOULD CONSIDER RETIRING
          (name who inherits the weight if it goes)
## Unmeasured
<factors whose statistics are mixed-sample — evidence-free, not edge-free>
## Accruing but unpromotable
<factors that cannot clear the bar even at full sample — stop accruing or
 change the design>
## Constant drift
<validate_factors.py:65-69 vs factor_status.py:28-30 vs cortex/core.py:56-58>
## Decision-log entry
<a docs/decisions.md entry in the existing shape, handed to docs-keeper>
```

## 5. The last mile stays human

Produce dossiers and cases. **Never flip `decision:factors_enabled`.** No code
path in this repo promotes or retires a factor on evidence, and that is a design
decision, not an omission — `scripts/validate_factors.py:38-39` states it and
`tests/test_validate_factors.py:297-306` pins it.

Your output is what the operator reads before deciding. Make it complete enough
that the decision is obvious, and honest enough that "insufficient evidence" is
the answer whenever it is true.

## 6. Close the loop

Hand the decision-log entry to `docs-keeper`. `docs/decisions.md` currently
contains **zero** mentions of "factor" — every factor finding this project has
produced lives in code comments and test docstrings. That is how the 2026-08-02
provenance bug and the `market_profile` false ENABLE became invisible to later
sessions. Write them down.
