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.
- checksums.yaml +4 -4
- data/app/adapters/jumbotron/adapters/sandbox/nfl.rb +135 -0
- data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/acquisition_scope.rb +74 -0
- data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/competition_odds.rb +82 -0
- data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/current_observation_state.rb +53 -0
- data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/line_acquisition_scope.rb +30 -0
- data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/monotonic_observation.rb +113 -0
- data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/observation_values.rb +53 -0
- data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/outcome_candidates.rb +38 -0
- data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/projection.rb +87 -0
- data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/scoreboard.rb +115 -0
- data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/synthetic_observation.rb +32 -0
- data/app/adapters/jumbotron/adapters/sandbox/nfl_helpers/timeline_observation.rb +119 -0
- data/app/deserializers/jumbotron/deserializers/public/register_sandbox_projection.rb +42 -0
- data/app/deserializers/jumbotron/deserializers/public/reset_sandbox_projection.rb +51 -0
- data/app/errors/jumbotron/error.rb +7 -0
- data/app/errors/jumbotron/errors/sandbox.rb +67 -0
- data/app/errors/jumbotron/sandbox_reset_rejected_error.rb +6 -0
- data/app/models/jumbotron/game.rb +11 -0
- data/app/models/jumbotron/public/translate_outcome.rb +13 -7
- data/app/models/jumbotron/public.rb +18 -1
- data/app/models/jumbotron/sandbox_game_mapping.rb +24 -0
- data/app/models/jumbotron/sandbox_game_plan.rb +23 -0
- data/app/models/jumbotron/sandbox_projection.rb +30 -0
- data/app/models/jumbotron/sandbox_projection_data.rb +32 -0
- data/app/models/jumbotron/season.rb +10 -0
- data/app/providers/jumbotron/providers/sandbox/resources/competition_odds.rb +24 -0
- data/app/providers/jumbotron/providers/sandbox/resources/projection.rb +23 -0
- data/app/providers/jumbotron/providers/sandbox/resources/scoreboard.rb +24 -0
- data/app/services/jumbotron/services/adapters/execute_line_acquisition_scope.rb +2 -1
- data/app/services/jumbotron/services/canonical/upsert_game_graph.rb +126 -0
- data/app/services/jumbotron/services/canonical/upsert_game_participant.rb +23 -3
- data/app/services/jumbotron/services/public/assemble_sandbox_projection.rb +30 -0
- data/app/services/jumbotron/services/public/assemble_sandbox_projection_reset.rb +49 -0
- data/app/services/jumbotron/services/sandbox/build_source_snapshot.rb +224 -0
- data/app/services/jumbotron/services/sandbox/project_game.rb +73 -0
- data/app/services/jumbotron/services/sandbox/register_projection.rb +76 -0
- data/app/services/jumbotron/services/sandbox/remove_empty_projection_geometry.rb +46 -0
- data/app/services/jumbotron/services/sandbox/remove_mapped_game.rb +29 -0
- data/app/services/jumbotron/services/sandbox/resolve_game_observation.rb +106 -0
- data/app/services/jumbotron/services/sandbox/seed_projected_game.rb +95 -0
- data/app/services/jumbotron/services/sandbox/select_line_basis.rb +69 -0
- data/app/services/jumbotron/services/sandbox/validate_projected_season.rb +77 -0
- data/app/workflows/jumbotron/workflows/public_read.rb +2 -1
- data/app/workflows/jumbotron/workflows/sandbox/register_projection_workflow.rb +33 -0
- data/app/workflows/jumbotron/workflows/sandbox/reset_projection_workflow.rb +158 -0
- data/app/workflows/jumbotron/workflows/synchronize_adapter_workflow.rb +3 -51
- data/db/migrate/20260908000001_create_jumbotron_sandbox_game_plans.rb +16 -0
- data/db/migrate/20260908000002_create_jumbotron_sandbox_projections.rb +39 -0
- data/docs/upgrades/0.2.0.md +74 -0
- data/lib/jumbotron/client.rb +14 -0
- data/lib/jumbotron/version.rb +1 -1
- data/spec/factories/jumbotron/sandbox_game_mapping.rb +24 -0
- data/spec/factories/jumbotron/sandbox_game_plan.rb +10 -0
- data/spec/factories/jumbotron/sandbox_projection.rb +18 -0
- 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
|
-
|
|
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!(
|
|
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).
|
data/lib/jumbotron/client.rb
CHANGED
|
@@ -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(**),
|
data/lib/jumbotron/version.rb
CHANGED
|
@@ -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.
|
|
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-
|
|
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
|