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
77 changes: 77 additions & 0 deletions openspec/specs/generator-output/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Spec — Generator Output (`generator-output`)

**Change**: `winning-numbers-pipeline` · **Store**: `openspec` · **Date**: 2026-08-23
**Artifact**: base spec — promoted from change `winning-numbers-pipeline` (archive).
**Sources**: official rules verified in Engram #1870 (`domain/baloto-revancha-official-rules`): 5 distinct numbers 1–43 + Superbalota 1–16; the 0+SB refund tier makes SB mandatory. Seams: `gen_service.py:167` persists `"super_number": None, "score": None`; `sampling.py:77` passes `None` SB into `validate_combination`; `LotteryConfig` protocol already declares `super_number_min`/`super_number_max`.
**Verify**: pytest (`backend/tests/gen/`).

## Requirements

### REQ-01: Legality Enforced Pre-Persist With Explicit Codes

| Field | Value |
|-------|-------|
| **ID** | R1 |
| **RFC** | MUST |

Every persisted combination SHALL satisfy the official bet shape resolved from `LotteryConfig`: exactly `numbers_to_select` distinct integers within `[min_number, max_number]` (5 distinct in 1–43) plus one Superbalota within `[super_number_min, super_number_max]` (1–16). `validate_combination(numbers, super_number, cfg)` SHALL gate persistence — no combination may be written without passing it WITH a non-null Superbalota. Violations SHALL raise typed errors defined in `services/errors.py`: `GEN_INVALID_NUMBERS` (count, duplicate, range, or ordering violation) and `GEN_INVALID_SUPER_NUMBER` (SB missing or out-of-range); both map to HTTP 422 via the global handler. Existing `GEN_SPACE_EXHAUSTED` semantics (resampling exhaustion, zero combos persisted) are unchanged.

#### Scenario: all generated combos legal

- GIVEN a seeded generate request against config 5 / 1–43 / SB 1–16
- WHEN generation completes
- THEN every persisted row passes `validate_combination(numbers, sb, cfg)` with `sb is not None`

#### Scenario: illegal SB rejected before persist

- GIVEN a candidate Superbalota outside 1–16
- WHEN validation gates the write
- THEN error code `GEN_INVALID_SUPER_NUMBER` raises and zero rows persist

#### Scenario: duplicate number rejected before persist

- GIVEN a candidate `[7, 7, 12, 30, 41]`
- WHEN validation gates the write
- THEN error code `GEN_INVALID_NUMBERS` raises and zero rows persist

### REQ-02: Reproducible SB From Historical Marginals

| Field | Value |
|-------|-------|
| **ID** | R2 |
| **RFC** | MUST |

Sampling SHALL emit `(combination, super_balota)` pairs: the Superbalota is drawn per combination from the historical SB-marginal distribution on the SAME isolated RNG stream as the numbers, so one seed reproduces the entire ticket including SB. Because stream consumption changes, `GENERATOR_VERSION` (`generators/version.py`) SHALL be bumped in the same slice so `generation_seed`/`snapshot_fingerprint` outputs differ from every pre-change value — new snapshots MUST NOT alias legacy fingerprints. Legacy rows (`super_number IS NULL`) stay readable; post-change generations contain zero `NULL` Superbalotas. Marginal fallbacks: sparse/incomplete SB marginals SHALL fall back to uniform over 1–16; zero imported draws SHALL fail with `GEN_NO_HISTORY` and persist nothing.

#### Scenario: SB present, in range, byte-reproducible

- GIVEN seed S generates a snapshot twice from identical history
- WHEN the outputs are compared
- THEN every combination carries an integer SB in 1–16 and both runs are identical including SB

#### Scenario: version bump prevents fingerprint aliasing

- GIVEN `GENERATOR_VERSION` incremented
- WHEN fingerprints are computed for a new generation
- THEN none equals a pre-change snapshot fingerprint, and legacy rows still deserialize

#### Scenario: no history fails explicitly

- GIVEN zero imported draws
- WHEN generation runs
- THEN `GEN_NO_HISTORY` raises and no snapshot persists

### REQ-03: Non-Null Selection-Weighted Score

| Field | Value |
|-------|-------|
| **ID** | R3 |
| **RFC** | MUST |

Every persisted combination SHALL carry a non-null, finite selection-weighted score computed from its entry-selection weight and the probability distribution, replacing the `"score": None` placeholder at `gen_service.py:167`. Generator responses (`/gen/generate`, `/gen/combinations`) SHALL expose `super_number` and `score` for every combination.

#### Scenario: scores always populated and exposed

- GIVEN any successful seeded generation
- WHEN persisted rows and the API payload are inspected
- THEN every row has a non-null finite `score` and responses echo `super_number` and `score`
110 changes: 110 additions & 0 deletions openspec/specs/mis-numeros-page/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Spec — Mis Números Page (`mis-numeros-page`)

**Change**: `winning-numbers-pipeline` · **Store**: `openspec` · **Date**: 2026-08-23
**Artifact**: base spec — promoted from change `winning-numbers-pipeline` (archive).
**Binding product decisions**: Revancha is ALWAYS bundled — one ticket valid for BOTH draws (Baloto+Revancha), no toggle. Default combination count: 5.
**Prize-tier source**: Engram #1870 — eight official tiers.
**Verify**: vitest + MSW (Models/DL page patterns).

## Requirements

### REQ-01: Single End-To-End Action

| Field | Value |
|-------|-------|
| **ID** | R1 |
| **RFC** | MUST |

The page SHALL offer exactly ONE primary CTA that invokes the numbers-orchestrator end-to-end — no per-stage manual buttons. Because the sync call may run minutes-scale, the busy state SHALL hold for the whole request: CTA disabled with `aria-busy` and progress wording. A failed request SHALL render `ErrorState` whose retry re-issues the orchestrator call (DL page precedent).

#### Scenario: one CTA drives the whole chain

- GIVEN an MSW handler for the orchestrator POST
- WHEN the CTA is clicked
- THEN exactly one orchestrator request fires and no other stage endpoints are called

#### Scenario: busy held through slow call, retry on failure

- GIVEN the POST handler delays then returns 500
- WHEN the request resolves
- THEN during flight the CTA is disabled with `aria-busy`, and afterwards ErrorState offers Retry that re-posts

### REQ-02: Chain Progress Rendering

| Field | Value |
|-------|-------|
| **ID** | R2 |
| **RFC** | MUST |

The page SHALL render the response's per-stage report: all eight canonical stages in order with status labels; a `failed` stage SHALL show its error visibly. While the request is in flight the page SHALL show an indeterminate busy indicator — sync-with-stages delivers statuses only at completion.

#### Scenario: statuses render after the response

- GIVEN a 200 response carrying eight stage entries
- WHEN it lands
- THEN all eight render in canonical order with their statuses

#### Scenario: failed stage surfaces without crashing

- GIVEN a response whose `rank` stage failed
- WHEN it lands
- THEN `rank` shows its failed status and combinations are absent, and the page stays interactive

### REQ-03: Revancha Always Bundled

| Field | Value |
|-------|-------|
| **ID** | R3 |
| **RFC** | MUST |

Every presented ticket SHALL be labeled as valid for BOTH draws using the owner-approved phrase “un boleto, dos sorteos (Baloto+Revancha)”. The page SHALL NOT offer a Baloto/Revancha toggle or any separate Revancha generation control. This is presentation-only: data-layer unification of Baloto(id 1)/Revancha(id 3) remains out of scope.

#### Scenario: dual-draw presentation, no toggle

- GIVEN generated combinations render
- WHEN the ticket area is inspected
- THEN the both-draws phrase labels the tickets and no toggle control exists in the DOM

### REQ-04: Default Combination Count

| Field | Value |
|-------|-------|
| **ID** | R4 |
| **RFC** | MUST |

The page SHALL request five combinations by default; the user MAY adjust the count before running.

#### Scenario: default payload carries five

- GIVEN the user does not modify the count control
- WHEN the CTA fires
- THEN the orchestrator request body contains count 5

### REQ-05: Eight Official Tiers Table

| Field | Value |
|-------|-------|
| **ID** | R5 |
| **RFC** | MUST |

The page SHALL display a static prize-tier reference listing ALL EIGHT official tiers: 5+SB (jackpot), 5, 4+SB, 4, 3+SB, 3, 2+SB (paramutual), and 0+SB (bet refund) — presented as official-rules reference, never as outcome promises.

#### Scenario: tiers table complete

- WHEN the page renders
- THEN exactly the eight official tiers appear with their match descriptions

### REQ-06: Randomness Disclaimer

| Field | Value |
|-------|-------|
| **ID** | R6 |
| **RFC** | MUST |

A visible disclaimer SHALL state that candidates are statistically informed over historical draws, that draws remain random, and that no prediction improvement is promised. It SHALL remain visible on load and after generation.

#### Scenario: disclaimer persists across states

- GIVEN idle and post-generation states
- WHEN each renders
- THEN the disclaimer text remains visible
87 changes: 87 additions & 0 deletions openspec/specs/numbers-orchestrator/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Spec — Numbers Orchestrator (`numbers-orchestrator`)

**Change**: `winning-numbers-pipeline` · **Store**: `openspec` · **Date**: 2026-08-23
**Artifact**: base spec — promoted from change `winning-numbers-pipeline` (archive).
**Binding owner decisions**: orchestrator MAY auto-train ml/dl when missing (minutes-scale latency acceptable); delivery is sync-with-stages — one call returns the full per-stage report; no job/polling infra in this change.
**Seam**: rank depends on a hardcoded backtesting context hash (`meta_service.py:242`) — brittle coupling that blocks chaining; the orchestrator owns correct context derivation.
**Verify**: pytest against the service layer (`backend/tests/pipeline/`).

## Requirements

### REQ-01: Canonical Ordered Execution In One Call

| Field | Value |
|-------|-------|
| **ID** | R1 |
| **RFC** | MUST |

A single orchestrator entry point SHALL execute the canonical chain `stats → features → ml → dl → bt → rank → select → gen` in that ORDER, enforcing `bt` strictly BEFORE `rank` (rank consumes backtest context). When ml/dl models are missing, the orchestrator SHALL train them automatically. Rank's backtesting context SHALL be derived from the identity of the backtest actually executed in this run (fingerprint/checksum) — never a hardcoded value, retiring the `meta_service.py:242` coupling. Any stage failure SHALL raise `PIPE_STAGE_FAILED` (carrying the failed stage id), defined in `services/errors.py`; remaining stages SHALL NOT run, completed stages' artifacts SHALL persist intact, and NO generator output SHALL be produced for that run.

#### Scenario: cold chain succeeds

- GIVEN imported draws and no chain artifacts
- WHEN the orchestrator runs once
- THEN all eight stages complete in canonical order and final combinations are returned

#### Scenario: bt-before-rank enforced with real context

- GIVEN instrumented stages
- WHEN the chain executes
- THEN `bt` finishes before `rank` starts and `rank` receives that bt run's fingerprint-derived context (no hardcoded hash)

#### Scenario: stage failure aborts cleanly

- GIVEN `rank` will raise
- WHEN the chain reaches `rank`
- THEN `PIPE_STAGE_FAILED` names `rank`, `gen` never runs, and earlier artifacts persist

### REQ-02: Detect Missing Prerequisites And Repair

| Field | Value |
|-------|-------|
| **ID** | R2 |
| **RFC** | MUST |

Before executing each stage, the orchestrator SHALL inspect its prerequisites (active snapshots, model artifacts, input fingerprints): missing or stale items are repaired by running exactly the deficient stages; current-and-valid stages are skipped. New draw coverage that invalidates a stage's fingerprint SHALL invalidate every downstream stage that consumes it.

#### Scenario: partial chain heals forward

- GIVEN stats/features snapshots exist and everything downstream is missing
- WHEN the orchestrator runs
- THEN stats/features report skipped and the five remaining stages run to completion

#### Scenario: fresh draw invalidates downstream only

- GIVEN a completed chain and one newly imported draw
- WHEN the orchestrator runs again
- THEN stages whose fingerprints depend on draw coverage re-run and unaffected upstream stages skip

### REQ-03: Per-Stage Status Report

| Field | Value |
|-------|-------|
| **ID** | R3 |
| **RFC** | MUST |

Every orchestrator response SHALL include an ordered per-stage report: canonical stage id, status ∈ {`skipped`, `completed`, `failed`}, and artifact references (snapshot id / fingerprint) where produced; a `failed` entry carries its error code.

#### Scenario: report matches canonical shape

- GIVEN any finished run
- WHEN the response is inspected
- THEN eight stage entries appear in canonical order with allowed statuses and references

### REQ-04: Fingerprint Reuse, Zero Side Effects

| Field | Value |
|-------|-------|
| **ID** | R4 |
| **RFC** | MUST |

Re-running with unchanged inputs SHALL reuse stored fingerprints end-to-end and return an identical result while performing zero side-effect writes: no store in the chain gains a new snapshot version.

#### Scenario: double run writes nothing

- GIVEN two consecutive runs with identical inputs
- WHEN both complete
- THEN payloads are identical and no stage store gained a new version
Loading