jumbotron 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/app/adapters/jumbotron/adapters/sandbox/nfl.rb +135 -0
  3. data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/acquisition_scope.rb +74 -0
  4. data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/competition_odds.rb +82 -0
  5. data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/current_observation_state.rb +53 -0
  6. data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/line_acquisition_scope.rb +30 -0
  7. data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/monotonic_observation.rb +113 -0
  8. data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/observation_values.rb +53 -0
  9. data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/outcome_candidates.rb +38 -0
  10. data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/projection.rb +87 -0
  11. data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/scoreboard.rb +115 -0
  12. data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/synthetic_observation.rb +32 -0
  13. data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/timeline_observation.rb +119 -0
  14. data/app/deserializers/jumbotron/deserializers/public/register_sandbox_projection.rb +42 -0
  15. data/app/deserializers/jumbotron/deserializers/public/reset_sandbox_projection.rb +51 -0
  16. data/app/errors/jumbotron/error.rb +7 -0
  17. data/app/errors/jumbotron/errors/sandbox.rb +67 -0
  18. data/app/errors/jumbotron/sandbox_reset_rejected_error.rb +6 -0
  19. data/app/models/jumbotron/game.rb +11 -0
  20. data/app/models/jumbotron/public/translate_outcome.rb +13 -7
  21. data/app/models/jumbotron/public.rb +18 -1
  22. data/app/models/jumbotron/sandbox_game_mapping.rb +24 -0
  23. data/app/models/jumbotron/sandbox_game_plan.rb +23 -0
  24. data/app/models/jumbotron/sandbox_projection.rb +30 -0
  25. data/app/models/jumbotron/sandbox_projection_data.rb +32 -0
  26. data/app/models/jumbotron/season.rb +10 -0
  27. data/app/providers/jumbotron/providers/sandbox/resources/competition_odds.rb +24 -0
  28. data/app/providers/jumbotron/providers/sandbox/resources/projection.rb +23 -0
  29. data/app/providers/jumbotron/providers/sandbox/resources/scoreboard.rb +24 -0
  30. data/app/services/jumbotron/services/adapters/execute_line_acquisition_scope.rb +2 -1
  31. data/app/services/jumbotron/services/canonical/upsert_game_graph.rb +126 -0
  32. data/app/services/jumbotron/services/canonical/upsert_game_participant.rb +23 -3
  33. data/app/services/jumbotron/services/public/assemble_sandbox_projection.rb +30 -0
  34. data/app/services/jumbotron/services/public/assemble_sandbox_projection_reset.rb +49 -0
  35. data/app/services/jumbotron/services/sandbox/build_source_snapshot.rb +224 -0
  36. data/app/services/jumbotron/services/sandbox/project_game.rb +73 -0
  37. data/app/services/jumbotron/services/sandbox/register_projection.rb +76 -0
  38. data/app/services/jumbotron/services/sandbox/remove_empty_projection_geometry.rb +46 -0
  39. data/app/services/jumbotron/services/sandbox/remove_mapped_game.rb +29 -0
  40. data/app/services/jumbotron/services/sandbox/resolve_game_observation.rb +106 -0
  41. data/app/services/jumbotron/services/sandbox/seed_projected_game.rb +95 -0
  42. data/app/services/jumbotron/services/sandbox/select_line_basis.rb +69 -0
  43. data/app/services/jumbotron/services/sandbox/validate_projected_season.rb +77 -0
  44. data/app/workflows/jumbotron/workflows/public_read.rb +2 -1
  45. data/app/workflows/jumbotron/workflows/sandbox/register_projection_workflow.rb +33 -0
  46. data/app/workflows/jumbotron/workflows/sandbox/reset_projection_workflow.rb +158 -0
  47. data/app/workflows/jumbotron/workflows/synchronize_adapter_workflow.rb +3 -51
  48. data/db/migrate/20260908000001_create_jumbotron_sandbox_game_plans.rb +16 -0
  49. data/db/migrate/20260908000002_create_jumbotron_sandbox_projections.rb +39 -0
  50. data/docs/upgrades/0.2.0.md +74 -0
  51. data/lib/jumbotron/client.rb +14 -0
  52. data/lib/jumbotron/version.rb +1 -1
  53. data/spec/factories/jumbotron/sandbox_game_mapping.rb +24 -0
  54. data/spec/factories/jumbotron/sandbox_game_plan.rb +10 -0
  55. data/spec/factories/jumbotron/sandbox_projection.rb +18 -0
  56. metadata +59 -2
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jumbotron
4
+ module Workflows
5
+ module Sandbox
6
+ class ResetProjectionWorkflow < CommandTower::Workflows::ApplicationWorkflow
7
+ include Jumbotron::Workflows::PublicRead
8
+
9
+ retry_strategy :none
10
+
11
+ def call(**kwargs)
12
+ deserialized = Jumbotron::Deserializers::Public::ResetSandboxProjection.call(kwargs)
13
+ return map_deserializer_failure(deserialized) unless deserialized.success?
14
+
15
+ request = deserialized.input
16
+ projection = Jumbotron::SandboxProjection.find_by(name: request.sandbox)
17
+ return missing_projection if projection.nil?
18
+ return unsafe_projection if unsafe_projection?(projection)
19
+
20
+ built = Jumbotron::Services::Sandbox::BuildSourceSnapshot.call(
21
+ projection: projection,
22
+ start_week: request.start_week,
23
+ anchor_day: request.anchor_day,
24
+ now: request.now
25
+ )
26
+ return map_service_failure(built) unless built.success?
27
+
28
+ reset_in_transaction(projection, built.data[:snapshot], request.now)
29
+ end
30
+
31
+ private
32
+
33
+ def reset_in_transaction(projection, snapshot, reset_at)
34
+ transaction do
35
+ projection = Jumbotron::SandboxProjection.lock.find(projection.id)
36
+ counts = exact_retry?(projection, snapshot) ? retry_counts : reconcile!(projection, snapshot, reset_at)
37
+ validation = Jumbotron::Services::Sandbox::ValidateProjectedSeason.call(
38
+ projection: projection,
39
+ snapshot: snapshot,
40
+ now: reset_at
41
+ )
42
+ fail_transaction!(map_service_failure(validation)) unless validation.success?
43
+
44
+ counts[:start_week] = validation.data[:start_week_game_count]
45
+ counts[:naturally_locked] = validation.data[:naturally_locked_game_count]
46
+ persist_success!(projection, snapshot, reset_at)
47
+ render_result!(projection, snapshot, counts, reset_at)
48
+ end
49
+ end
50
+
51
+ def reconcile!(projection, snapshot, reset_at)
52
+ change_set = Jumbotron::Canonical::ChangeSet.new
53
+ added = 0
54
+
55
+ snapshot.games.each do |projected_game|
56
+ projected = Jumbotron::Services::Sandbox::ProjectGame.call(
57
+ projection: projection,
58
+ projected_game: projected_game,
59
+ observed_at: reset_at,
60
+ change_set: change_set
61
+ )
62
+ fail_transaction!(map_service_failure(projected)) unless projected.success?
63
+ added += 1 if projected.data[:created]
64
+ end
65
+
66
+ removed = remove_stale!(projection, snapshot)
67
+ geometry = Jumbotron::Services::Sandbox::RemoveEmptyProjectionGeometry.call(projection: projection)
68
+ fail_transaction!(map_service_failure(geometry)) unless geometry.success?
69
+ seed_all!(projection, snapshot, reset_at, change_set)
70
+ {
71
+ added: added,
72
+ updated: snapshot.games.count - added,
73
+ removed: removed,
74
+ start_week: 0,
75
+ naturally_locked: 0
76
+ }
77
+ end
78
+
79
+ def remove_stale!(projection, snapshot)
80
+ current_source_ids = snapshot.games.map(&:source_game_id)
81
+ stale = projection.sandbox_game_mappings.where.not(source_game_id: current_source_ids).to_a
82
+ stale.each do |mapping|
83
+ result = Jumbotron::Services::Sandbox::RemoveMappedGame.call(mapping: mapping)
84
+ fail_transaction!(map_service_failure(result)) unless result.success?
85
+ end
86
+ stale.count
87
+ end
88
+
89
+ def seed_all!(projection, snapshot, reset_at, change_set)
90
+ mappings = projection.sandbox_game_mappings.index_by(&:source_game_id)
91
+ snapshot.games.each do |projected_game|
92
+ result = Jumbotron::Services::Sandbox::SeedProjectedGame.call(
93
+ game: mappings.fetch(projected_game.source_game_id).sandbox_game,
94
+ observed_at: reset_at,
95
+ change_set: change_set
96
+ )
97
+ fail_transaction!(map_service_failure(result)) unless result.success?
98
+ end
99
+ end
100
+
101
+ def render_result!(projection, snapshot, counts, reset_at)
102
+ assembled = Jumbotron::Services::Public::AssembleSandboxProjectionReset.call(
103
+ projection: projection,
104
+ snapshot: snapshot,
105
+ counts: counts,
106
+ reset_at: reset_at
107
+ )
108
+ fail_transaction!(map_service_failure(assembled)) unless assembled.success?
109
+
110
+ success(payload: { sandbox_reset: assembled.data[:public_reset] }, http_status: :ok)
111
+ end
112
+
113
+ def persist_success!(projection, snapshot, reset_at)
114
+ projection.update!(
115
+ last_reset_fingerprint: snapshot.fingerprint,
116
+ last_reset_at: reset_at,
117
+ last_date_shift_days: snapshot.date_shift_days
118
+ )
119
+ end
120
+
121
+ def exact_retry?(projection, snapshot)
122
+ projection.last_reset_fingerprint == snapshot.fingerprint
123
+ end
124
+
125
+ def retry_counts
126
+ { added: 0, updated: 0, removed: 0, start_week: 0, naturally_locked: 0 }
127
+ end
128
+
129
+ def unsafe_projection?(projection)
130
+ projection.adapter_id != "sandbox_nfl" ||
131
+ projection.sandbox_season.league.name != "nfl-sandbox" ||
132
+ projection.source_season_id == projection.sandbox_season_id
133
+ end
134
+
135
+ def missing_projection
136
+ failure(
137
+ errors: [Jumbotron::Errors::Sandbox::ProjectionNotFoundError.new],
138
+ http_status: :not_found
139
+ )
140
+ end
141
+
142
+ def unsafe_projection
143
+ failure(
144
+ errors: [
145
+ Jumbotron::Errors::Sandbox::ProjectionRejectedError.new(
146
+ details: {
147
+ code: "sandbox_target_forbidden",
148
+ message: "reset target is not a registered sandbox season"
149
+ }
150
+ )
151
+ ],
152
+ http_status: :unprocessable_entity
153
+ )
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -94,63 +94,15 @@ module Jumbotron
94
94
  end
95
95
 
96
96
  def sync_one_game!(game_input, league, observed_at, change_set)
97
- season_result = Services::Canonical::EnsureSeason.call(
98
- league: league,
99
- year: game_input.season_year
100
- )
101
- fail_on_service_failure!(season_result)
102
-
103
- phase_result = Services::Canonical::EnsureSeasonPhase.call(
104
- season: season_result.data[:season],
105
- phase_key: game_input.season_phase_key
106
- )
107
- fail_on_service_failure!(phase_result)
108
-
109
- group_result = Services::Canonical::EnsureScheduleGroup.call(
110
- season: season_result.data[:season],
111
- season_phase: phase_result.data[:season_phase],
112
- schedule_group_input: game_input.schedule_group
113
- )
114
- fail_on_service_failure!(group_result)
115
-
116
- venue_result = Services::Canonical::UpsertVenue.call(
117
- venue_input: game_input.venue,
118
- observed_at: observed_at,
119
- change_set: change_set
120
- )
121
- fail_on_service_failure!(venue_result)
122
-
123
- game_result = Services::Canonical::UpsertGame.call(
97
+ result = Services::Canonical::UpsertGameGraph.call(
124
98
  game_input: game_input,
125
99
  league: league,
126
- season: season_result.data[:season],
127
- season_phase: phase_result.data[:season_phase],
128
- schedule_group: group_result.data[:schedule_group],
129
- venue: venue_result.data[:venue],
130
100
  observed_at: observed_at,
131
101
  change_set: change_set
132
102
  )
133
- fail_on_service_failure!(game_result)
134
-
135
- game = game_result.data[:game]
136
-
137
- attach = Services::Canonical::AttachProviderIdentities.call(
138
- target: game,
139
- refs: game_input.provider_identities
140
- )
141
- fail_on_service_failure!(attach)
142
-
143
- game_input.participants.each do |participant_input|
144
- gp_result = Services::Canonical::UpsertGameParticipant.call(
145
- game: game,
146
- participant_input: participant_input,
147
- observed_at: observed_at,
148
- change_set: change_set
149
- )
150
- fail_on_service_failure!(gp_result)
151
- end
103
+ fail_on_service_failure!(result)
152
104
 
153
- game
105
+ result.data[:game]
154
106
  end
155
107
 
156
108
  def fail_on_service_failure!(service_result)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateJumbotronSandboxGamePlans < ActiveRecord::Migration[8.1]
4
+ def change
5
+ create_table :jumbotron_sandbox_game_plans do |t|
6
+ t.references :game,
7
+ null: false,
8
+ foreign_key: { to_table: :jumbotron_games },
9
+ index: { unique: true }
10
+ t.integer :home_score, null: false
11
+ t.integer :away_score, null: false
12
+ t.datetime :selected_at, null: false
13
+ t.timestamps
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateJumbotronSandboxProjections < ActiveRecord::Migration[8.1]
4
+ def change
5
+ create_table :jumbotron_sandbox_projections do |t|
6
+ t.string :name, null: false
7
+ t.references :source_season, null: false, foreign_key: { to_table: :jumbotron_seasons }
8
+ t.references :source_anchor_phase, null: false, foreign_key: { to_table: :jumbotron_season_phases }
9
+ t.references :sandbox_season,
10
+ null: false,
11
+ foreign_key: { to_table: :jumbotron_seasons },
12
+ index: { unique: true }
13
+ t.string :adapter_id, null: false
14
+ t.string :calendar_time_zone, null: false
15
+ t.string :last_reset_fingerprint
16
+ t.datetime :last_reset_at
17
+ t.integer :last_date_shift_days
18
+ t.timestamps
19
+ end
20
+ add_index :jumbotron_sandbox_projections, :name, unique: true
21
+
22
+ create_table :jumbotron_sandbox_game_mappings do |t|
23
+ t.references :sandbox_projection, null: false, foreign_key: { to_table: :jumbotron_sandbox_projections }
24
+ t.references :source_game, null: false, foreign_key: { to_table: :jumbotron_games }
25
+ t.references :sandbox_game,
26
+ null: false,
27
+ foreign_key: { to_table: :jumbotron_games },
28
+ index: { unique: true }
29
+ t.decimal :home_spread, precision: 6, scale: 2, null: false
30
+ t.decimal :total, precision: 6, scale: 2, null: false
31
+ t.string :line_fingerprint, null: false
32
+ t.timestamps
33
+ end
34
+ add_index :jumbotron_sandbox_game_mappings,
35
+ %i[sandbox_projection_id source_game_id],
36
+ unique: true,
37
+ name: "idx_jumbotron_sandbox_mapping_source"
38
+ end
39
+ end
@@ -0,0 +1,74 @@
1
+ # Upgrade: Jumbotron 0.2.0
2
+
3
+ **From:** `0.1.7`
4
+ **To:** `0.2.0`
5
+
6
+ Minor release: named NFL sandbox projection and Reset/reprojection behind the public client. ESPN `espn_nfl` read and update contracts are unchanged.
7
+
8
+ ## Host-facing change
9
+
10
+ | Change | Host impact |
11
+ |--------|-------------|
12
+ | `sandbox_nfl` adapter | In-process NFL sandbox adapter (`league: nfl-sandbox`) with the same Game/Line policy set as ESPN. Not ESPN I/O. |
13
+ | `Jumbotron.client.register_sandbox_projection` | Idempotent named bind of a sandbox to immutable source selectors. Rejects conflicting redefinition. |
14
+ | `Jumbotron.client.reset_sandbox_projection` | Whole-season Reset: Eastern calendar-day shift, stable source→sandbox game mappings, complete projection or no commit. |
15
+ | Sandbox public errors | Structured `code` + safe `details` on public exceptions (`SandboxResetRejectedError` and mapped sandbox failures). |
16
+ | Recurring schedules | Desired schedules now include `jumbotron:sandbox_nfl:*` policy keys (still no `full_season` discovery cron). |
17
+ | Temporary `json < 3` gemspec constraint | Bundler will refuse json 3.x for any host that depends on this gem. Not a Jumbotron API change. See below. |
18
+
19
+ ## Upgrade checklist
20
+
21
+ 1. Bump gem to `0.2.0` (or `>= 0.2.0`).
22
+ 2. Copy engine migrations and migrate:
23
+
24
+ ```bash
25
+ bin/rails jumbotron:install:migrations
26
+ bin/rails db:migrate
27
+ ```
28
+
29
+ New engine files:
30
+
31
+ - `20260908000001_create_jumbotron_sandbox_game_plans.rb`
32
+ - `20260908000002_create_jumbotron_sandbox_projections.rb` (also creates `jumbotron_sandbox_game_mappings`)
33
+
34
+ 3. Rematerialize Solid Queue recurring YAML so `jumbotron:sandbox_nfl:*` keys appear if the host should run sandbox polls:
35
+
36
+ ```bash
37
+ bin/rails jumbotron:schedules:materialize
38
+ ```
39
+
40
+ Host-owned keys (`pickem:*`, `clear_solid_queue_finished_jobs`) are preserved. ESPN `jumbotron:espn_nfl:*` keys are unchanged.
41
+
42
+ 4. Optional: register then Reset a named projection from a host (no HTTP in Jumbotron):
43
+
44
+ ```ruby
45
+ Jumbotron.client.register_sandbox_projection(name: "nfl-sandbox", source: "nfl")
46
+ Jumbotron.client.reset_sandbox_projection(
47
+ sandbox: "nfl-sandbox",
48
+ start_week: 1,
49
+ anchor_day: 1,
50
+ now: Time.current.iso8601
51
+ )
52
+ ```
53
+
54
+ ## Temporary `json < 3` constraint
55
+
56
+ **Why.** json 3.0 made `JSON.parse` keyword-only. Released Rails 8.1.3.1 still does `JSON.parse(json, options)` with a positional Hash (default `{}` even when callers pass no options). That raises `ArgumentError: wrong number of arguments (given 2, expected 1)` on every `ActiveSupport::JSON.decode`, including ActiveRecord `:json` attributes (`ObservationBatch`, `LineObservation`, `HistoricalChange`) and ActionDispatch JSON request bodies.
57
+
58
+ A Gemfile pin in this engine repo only protects dummy-app CI. Hosts (Pick'em, others) resolve `json` from their own lockfiles. Putting `json < 3` in the **gemspec** makes Bundler enforce the ceiling wherever Jumbotron is a dependency.
59
+
60
+ This is a workaround for a Rails/json incompatibility, not a Jumbotron product dependency. Rails main / `8-1-stable` already splat (`JSON.parse(json, **options)`); no Rails gem that includes that fix was published at 0.2.0 (`8.1.3.1` is still latest).
61
+
62
+ **Undo when all of the following are true:**
63
+
64
+ 1. A Rails gem is released whose `ActiveSupport::JSON.decode` calls `JSON.parse(json, **options)` (or equivalent: no positional options Hash). Confirm in that gem's `activesupport/lib/active_support/json/decoding.rb`.
65
+ 2. Every Jumbotron host is on that Rails version (or newer). Pick'em today is `~> 8.1.3`, `>= 8.1.3.1`.
66
+ 3. Then remove `spec.add_dependency "json", "< 3"` from `jumbotron.gemspec`, `bundle install` in Docker, drop this section, and drop the packaging example that asserts the ceiling.
67
+
68
+ Do not undo just because a host Gemfile pins json, or because Jumbotron CI happens to lock 2.x.
69
+
70
+ ## What is not in this release
71
+
72
+ - No ESPN public Game/Schedule/Consensus/CurrentLines contract change.
73
+ - No Pick'em competitive domain. Hosts own whether they materialize sandbox seasons and whether production runs `sandbox_nfl` polls.
74
+ - No new `full_season` discovery job for sandbox (sandbox catalog is projected, not ESPN-discovered).
@@ -2,6 +2,20 @@
2
2
 
3
3
  module Jumbotron
4
4
  class Client
5
+ def register_sandbox_projection(**)
6
+ Public::TranslateOutcome.call(
7
+ Workflows::Sandbox::RegisterProjectionWorkflow.call(**),
8
+ payload_key: :sandbox_projection
9
+ )
10
+ end
11
+
12
+ def reset_sandbox_projection(**)
13
+ Public::TranslateOutcome.call(
14
+ Workflows::Sandbox::ResetProjectionWorkflow.call(**),
15
+ payload_key: :sandbox_reset
16
+ )
17
+ end
18
+
5
19
  def schedule(**)
6
20
  Public::TranslateOutcome.call(
7
21
  Workflows::ReadScheduleWorkflow.call(**),
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Jumbotron
4
- VERSION = "0.1.7"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :jumbotron_sandbox_game_mapping, class: "Jumbotron::SandboxGameMapping" do
5
+ association :sandbox_projection, factory: :jumbotron_sandbox_projection
6
+ source_game do
7
+ association(
8
+ :jumbotron_game,
9
+ season: sandbox_projection.source_season,
10
+ league: sandbox_projection.source_season.league
11
+ )
12
+ end
13
+ sandbox_game do
14
+ association(
15
+ :jumbotron_game,
16
+ season: sandbox_projection.sandbox_season,
17
+ league: sandbox_projection.sandbox_season.league
18
+ )
19
+ end
20
+ home_spread { BigDecimal("-3.5") }
21
+ total { BigDecimal("44.5") }
22
+ line_fingerprint { "line-v1" }
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :jumbotron_sandbox_game_plan, class: "Jumbotron::SandboxGamePlan" do
5
+ association :game, factory: :jumbotron_game
6
+ home_score { 24 }
7
+ away_score { 20 }
8
+ selected_at { Time.current }
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :jumbotron_sandbox_projection, class: "Jumbotron::SandboxProjection" do
5
+ sequence(:name) { |number| "sandbox-#{number}" }
6
+ association :source_season, factory: :jumbotron_season
7
+ source_anchor_phase do
8
+ association :jumbotron_season_phase, season: source_season, name: "regular_season"
9
+ end
10
+ sandbox_season do
11
+ sport = association(:jumbotron_sport, name: "football")
12
+ league = association(:jumbotron_league, sport: sport, name: "nfl-sandbox")
13
+ association(:jumbotron_season, league: league, name: name)
14
+ end
15
+ adapter_id { "sandbox_nfl" }
16
+ calendar_time_zone { "America/New_York" }
17
+ end
18
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jumbotron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - matt-taylor
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-29 00:00:00.000000000 Z
11
+ date: 2026-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: command_tower
@@ -44,6 +44,20 @@ dependencies:
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '9.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: json
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "<"
52
+ - !ruby/object:Gem::Version
53
+ version: '3'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "<"
59
+ - !ruby/object:Gem::Version
60
+ version: '3'
47
61
  description: Jumbotron owns canonical sports truth independently of ESPN and Pick'em.
48
62
  email:
49
63
  - ''
@@ -71,6 +85,18 @@ files:
71
85
  - app/adapters/jumbotron/adapters/operation.rb
72
86
  - app/adapters/jumbotron/adapters/policy_definition.rb
73
87
  - app/adapters/jumbotron/adapters/registry.rb
88
+ - app/adapters/jumbotron/adapters/sandbox/nfl.rb
89
+ - app/adapters/jumbotron/adapters/sandbox/nfl_helpers/acquisition_scope.rb
90
+ - app/adapters/jumbotron/adapters/sandbox/nfl_helpers/competition_odds.rb
91
+ - app/adapters/jumbotron/adapters/sandbox/nfl_helpers/current_observation_state.rb
92
+ - app/adapters/jumbotron/adapters/sandbox/nfl_helpers/line_acquisition_scope.rb
93
+ - app/adapters/jumbotron/adapters/sandbox/nfl_helpers/monotonic_observation.rb
94
+ - app/adapters/jumbotron/adapters/sandbox/nfl_helpers/observation_values.rb
95
+ - app/adapters/jumbotron/adapters/sandbox/nfl_helpers/outcome_candidates.rb
96
+ - app/adapters/jumbotron/adapters/sandbox/nfl_helpers/projection.rb
97
+ - app/adapters/jumbotron/adapters/sandbox/nfl_helpers/scoreboard.rb
98
+ - app/adapters/jumbotron/adapters/sandbox/nfl_helpers/synthetic_observation.rb
99
+ - app/adapters/jumbotron/adapters/sandbox/nfl_helpers/timeline_observation.rb
74
100
  - app/boot/jumbotron/boot/enqueue_discoveries.rb
75
101
  - app/clients/jumbotron/clients.rb
76
102
  - app/clients/jumbotron/clients/espn.rb
@@ -105,14 +131,18 @@ files:
105
131
  - app/deserializers/jumbotron/deserializers/public/game.rb
106
132
  - app/deserializers/jumbotron/deserializers/public/game_ids.rb
107
133
  - app/deserializers/jumbotron/deserializers/public/includes.rb
134
+ - app/deserializers/jumbotron/deserializers/public/register_sandbox_projection.rb
135
+ - app/deserializers/jumbotron/deserializers/public/reset_sandbox_projection.rb
108
136
  - app/deserializers/jumbotron/deserializers/public/schedule.rb
109
137
  - app/deserializers/jumbotron/deserializers/public/schedule_groups.rb
110
138
  - app/errors/jumbotron/error.rb
111
139
  - app/errors/jumbotron/errors/adapters.rb
112
140
  - app/errors/jumbotron/errors/canonical.rb
113
141
  - app/errors/jumbotron/errors/public.rb
142
+ - app/errors/jumbotron/errors/sandbox.rb
114
143
  - app/errors/jumbotron/invalid_request_error.rb
115
144
  - app/errors/jumbotron/not_found_error.rb
145
+ - app/errors/jumbotron/sandbox_reset_rejected_error.rb
116
146
  - app/errors/jumbotron/unsupported_capability_error.rb
117
147
  - app/jobs/jumbotron/application_job.rb
118
148
  - app/jobs/jumbotron/discovery_job.rb
@@ -130,6 +160,10 @@ files:
130
160
  - app/models/jumbotron/provider_identity.rb
131
161
  - app/models/jumbotron/public.rb
132
162
  - app/models/jumbotron/public/translate_outcome.rb
163
+ - app/models/jumbotron/sandbox_game_mapping.rb
164
+ - app/models/jumbotron/sandbox_game_plan.rb
165
+ - app/models/jumbotron/sandbox_projection.rb
166
+ - app/models/jumbotron/sandbox_projection_data.rb
133
167
  - app/models/jumbotron/schedule_group.rb
134
168
  - app/models/jumbotron/season.rb
135
169
  - app/models/jumbotron/season_phase.rb
@@ -142,6 +176,9 @@ files:
142
176
  - app/providers/jumbotron/providers/espn/resources/competition_odds.rb
143
177
  - app/providers/jumbotron/providers/espn/resources/scoreboard.rb
144
178
  - app/providers/jumbotron/providers/espn/resources/teams.rb
179
+ - app/providers/jumbotron/providers/sandbox/resources/competition_odds.rb
180
+ - app/providers/jumbotron/providers/sandbox/resources/projection.rb
181
+ - app/providers/jumbotron/providers/sandbox/resources/scoreboard.rb
145
182
  - app/scheduling/jumbotron/scheduling/backends/sidekiq_cron.rb
146
183
  - app/scheduling/jumbotron/scheduling/backends/solid_queue.rb
147
184
  - app/scheduling/jumbotron/scheduling/clock.rb
@@ -176,11 +213,14 @@ files:
176
213
  - app/services/jumbotron/services/canonical/resolve_current_lines.rb
177
214
  - app/services/jumbotron/services/canonical/resolve_current_lines_for_games.rb
178
215
  - app/services/jumbotron/services/canonical/upsert_game.rb
216
+ - app/services/jumbotron/services/canonical/upsert_game_graph.rb
179
217
  - app/services/jumbotron/services/canonical/upsert_game_participant.rb
180
218
  - app/services/jumbotron/services/canonical/upsert_team.rb
181
219
  - app/services/jumbotron/services/canonical/upsert_venue.rb
182
220
  - app/services/jumbotron/services/public/assemble_public_game.rb
183
221
  - app/services/jumbotron/services/public/assemble_public_schedule.rb
222
+ - app/services/jumbotron/services/public/assemble_sandbox_projection.rb
223
+ - app/services/jumbotron/services/public/assemble_sandbox_projection_reset.rb
184
224
  - app/services/jumbotron/services/public/derive_consensus_for_games.rb
185
225
  - app/services/jumbotron/services/public/load_games_by_ids.rb
186
226
  - app/services/jumbotron/services/public/load_included_lines.rb
@@ -190,6 +230,15 @@ files:
190
230
  - app/services/jumbotron/services/public/read_schedule.rb
191
231
  - app/services/jumbotron/services/public/read_schedule_groups.rb
192
232
  - app/services/jumbotron/services/public/resolve_current_lines_for_games.rb
233
+ - app/services/jumbotron/services/sandbox/build_source_snapshot.rb
234
+ - app/services/jumbotron/services/sandbox/project_game.rb
235
+ - app/services/jumbotron/services/sandbox/register_projection.rb
236
+ - app/services/jumbotron/services/sandbox/remove_empty_projection_geometry.rb
237
+ - app/services/jumbotron/services/sandbox/remove_mapped_game.rb
238
+ - app/services/jumbotron/services/sandbox/resolve_game_observation.rb
239
+ - app/services/jumbotron/services/sandbox/seed_projected_game.rb
240
+ - app/services/jumbotron/services/sandbox/select_line_basis.rb
241
+ - app/services/jumbotron/services/sandbox/validate_projected_season.rb
193
242
  - app/synchronization/jumbotron/synchronization/lease.rb
194
243
  - app/workflows/jumbotron/workflows/execute_discovery_workflow.rb
195
244
  - app/workflows/jumbotron/workflows/execute_game_update_policy_workflow.rb
@@ -200,6 +249,8 @@ files:
200
249
  - app/workflows/jumbotron/workflows/read_game_workflow.rb
201
250
  - app/workflows/jumbotron/workflows/read_schedule_groups_workflow.rb
202
251
  - app/workflows/jumbotron/workflows/read_schedule_workflow.rb
252
+ - app/workflows/jumbotron/workflows/sandbox/register_projection_workflow.rb
253
+ - app/workflows/jumbotron/workflows/sandbox/reset_projection_workflow.rb
203
254
  - app/workflows/jumbotron/workflows/synchronize_adapter_workflow.rb
204
255
  - db/migrate/20260812000001_create_jumbotron_foundational_catalog.rb
205
256
  - db/migrate/20260812000002_create_jumbotron_game_graph.rb
@@ -211,10 +262,13 @@ files:
211
262
  - db/migrate/20260827150001_add_key_to_jumbotron_teams.rb
212
263
  - db/migrate/20260828180001_add_nickname_to_jumbotron_teams.rb
213
264
  - db/migrate/20260828220001_add_game_presentation_contract_fields.rb
265
+ - db/migrate/20260908000001_create_jumbotron_sandbox_game_plans.rb
266
+ - db/migrate/20260908000002_create_jumbotron_sandbox_projections.rb
214
267
  - docs/testing.md
215
268
  - docs/upgrades/0.1.2.md
216
269
  - docs/upgrades/0.1.3.md
217
270
  - docs/upgrades/0.1.4.md
271
+ - docs/upgrades/0.2.0.md
218
272
  - lib/jumbotron.rb
219
273
  - lib/jumbotron/client.rb
220
274
  - lib/jumbotron/engine.rb
@@ -229,6 +283,9 @@ files:
229
283
  - spec/factories/jumbotron/line_observation.rb
230
284
  - spec/factories/jumbotron/observation_batch.rb
231
285
  - spec/factories/jumbotron/provider_identity.rb
286
+ - spec/factories/jumbotron/sandbox_game_mapping.rb
287
+ - spec/factories/jumbotron/sandbox_game_plan.rb
288
+ - spec/factories/jumbotron/sandbox_projection.rb
232
289
  - spec/factories/jumbotron/schedule_group.rb
233
290
  - spec/factories/jumbotron/season.rb
234
291
  - spec/factories/jumbotron/season_phase.rb