jumbotron 0.1.1
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 +7 -0
- data/MIT-LICENSE +21 -0
- data/README.md +5 -0
- data/app/adapters/jumbotron/adapters/base.rb +136 -0
- data/app/adapters/jumbotron/adapters/cadence.rb +42 -0
- data/app/adapters/jumbotron/adapters/discovery_definition.rb +7 -0
- data/app/adapters/jumbotron/adapters/espn/nfl.rb +169 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/acquisition_scope.rb +29 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/competition_odds.rb +185 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/lifecycle.rb +30 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/line_acquisition_scope.rb +26 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/progress.rb +54 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/scoreboard.rb +146 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/season_phase.rb +33 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/teams.rb +35 -0
- data/app/adapters/jumbotron/adapters/line_acquisition.rb +19 -0
- data/app/adapters/jumbotron/adapters/operation.rb +23 -0
- data/app/adapters/jumbotron/adapters/policy_definition.rb +14 -0
- data/app/adapters/jumbotron/adapters/registry.rb +78 -0
- data/app/adapters/jumbotron/adapters.rb +11 -0
- data/app/boot/jumbotron/boot/enqueue_discoveries.rb +41 -0
- data/app/clients/jumbotron/clients/espn/competition_odds/get.rb +26 -0
- data/app/clients/jumbotron/clients/espn/competition_odds/get_input.rb +16 -0
- data/app/clients/jumbotron/clients/espn/scoreboard/get.rb +18 -0
- data/app/clients/jumbotron/clients/espn/scoreboard/get_input.rb +17 -0
- data/app/clients/jumbotron/clients/espn/teams/get.rb +18 -0
- data/app/clients/jumbotron/clients/espn/teams/get_input.rb +15 -0
- data/app/clients/jumbotron/clients/espn.rb +44 -0
- data/app/clients/jumbotron/clients.rb +8 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition.rb +79 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/draw_odds.rb +31 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/get.rb +66 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/item.rb +60 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/listed_provider.rb +31 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/market_window.rb +31 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/point_spread.rb +27 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/price.rb +35 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/side_quote.rb +35 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/team_odds.rb +40 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competitor.rb +49 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/event.rb +81 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/scoreboard/get.rb +88 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/season.rb +27 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/status.rb +69 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/support.rb +61 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/team.rb +42 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/teams/get.rb +76 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/venue.rb +34 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/week.rb +23 -0
- data/app/deserializers/jumbotron/deserializers/public/game.rb +26 -0
- data/app/deserializers/jumbotron/deserializers/public/game_ids.rb +28 -0
- data/app/deserializers/jumbotron/deserializers/public/includes.rb +32 -0
- data/app/deserializers/jumbotron/deserializers/public/schedule.rb +151 -0
- data/app/deserializers/jumbotron/deserializers/public/schedule_groups.rb +91 -0
- data/app/errors/jumbotron/error.rb +6 -0
- data/app/errors/jumbotron/errors/adapters.rb +143 -0
- data/app/errors/jumbotron/errors/canonical.rb +67 -0
- data/app/errors/jumbotron/errors/public.rb +27 -0
- data/app/errors/jumbotron/invalid_request_error.rb +6 -0
- data/app/errors/jumbotron/not_found_error.rb +6 -0
- data/app/errors/jumbotron/unsupported_capability_error.rb +6 -0
- data/app/jobs/jumbotron/application_job.rb +6 -0
- data/app/jobs/jumbotron/discovery_job.rb +14 -0
- data/app/jobs/jumbotron/game_update_job.rb +12 -0
- data/app/jobs/jumbotron/line_update_job.rb +12 -0
- data/app/models/jumbotron/application_record.rb +8 -0
- data/app/models/jumbotron/bookmaker.rb +11 -0
- data/app/models/jumbotron/canonical.rb +128 -0
- data/app/models/jumbotron/game.rb +92 -0
- data/app/models/jumbotron/game_participant.rb +10 -0
- data/app/models/jumbotron/historical_change.rb +9 -0
- data/app/models/jumbotron/league.rb +12 -0
- data/app/models/jumbotron/line_observation.rb +18 -0
- data/app/models/jumbotron/observation_batch.rb +14 -0
- data/app/models/jumbotron/provider_identity.rb +32 -0
- data/app/models/jumbotron/public/translate_outcome.rb +57 -0
- data/app/models/jumbotron/public.rb +88 -0
- data/app/models/jumbotron/schedule_group.rb +22 -0
- data/app/models/jumbotron/season.rb +13 -0
- data/app/models/jumbotron/season_phase.rb +12 -0
- data/app/models/jumbotron/sport.rb +10 -0
- data/app/models/jumbotron/team.rb +11 -0
- data/app/models/jumbotron/venue.rb +10 -0
- data/app/providers/jumbotron/providers/espn/cooldown.rb +88 -0
- data/app/providers/jumbotron/providers/espn/cooldown_active_error.rb +13 -0
- data/app/providers/jumbotron/providers/espn/provider_failure.rb +40 -0
- data/app/providers/jumbotron/providers/espn/resources/competition_odds.rb +29 -0
- data/app/providers/jumbotron/providers/espn/resources/scoreboard.rb +90 -0
- data/app/providers/jumbotron/providers/espn/resources/teams.rb +19 -0
- data/app/scheduling/jumbotron/scheduling/backends/sidekiq_cron.rb +70 -0
- data/app/scheduling/jumbotron/scheduling/backends/solid_queue.rb +92 -0
- data/app/scheduling/jumbotron/scheduling/clock.rb +61 -0
- data/app/scheduling/jumbotron/scheduling/desired_schedules.rb +51 -0
- data/app/scheduling/jumbotron/scheduling/detect_integration.rb +45 -0
- data/app/scheduling/jumbotron/scheduling/materializer.rb +13 -0
- data/app/scheduling/jumbotron/scheduling/owned.rb +19 -0
- data/app/scheduling/jumbotron/scheduling/schedule_definition.rb +7 -0
- data/app/serializers/jumbotron/serializers/clients/espn/competition_odds/get.rb +17 -0
- data/app/serializers/jumbotron/serializers/clients/espn/scoreboard/get.rb +22 -0
- data/app/serializers/jumbotron/serializers/clients/espn/teams/get.rb +20 -0
- data/app/services/jumbotron/services/adapters/build_acquisition_scopes.rb +37 -0
- data/app/services/jumbotron/services/adapters/build_line_acquisition_scopes.rb +39 -0
- data/app/services/jumbotron/services/adapters/ensure_league.rb +29 -0
- data/app/services/jumbotron/services/adapters/execute_endpoint.rb +49 -0
- data/app/services/jumbotron/services/adapters/execute_line_acquisition_scope.rb +75 -0
- data/app/services/jumbotron/services/adapters/execute_synchronization_scope.rb +64 -0
- data/app/services/jumbotron/services/adapters/resolve_discovery.rb +53 -0
- data/app/services/jumbotron/services/adapters/resolve_game_update_policy.rb +74 -0
- data/app/services/jumbotron/services/adapters/resolve_line_update_policy.rb +74 -0
- data/app/services/jumbotron/services/adapters/select_eligible_games.rb +41 -0
- data/app/services/jumbotron/services/adapters/validate_sync_request.rb +51 -0
- data/app/services/jumbotron/services/canonical/attach_provider_identities.rb +59 -0
- data/app/services/jumbotron/services/canonical/derive_consensus_for_games.rb +18 -0
- data/app/services/jumbotron/services/canonical/derive_consensus_line.rb +70 -0
- data/app/services/jumbotron/services/canonical/ensure_bookmaker.rb +70 -0
- data/app/services/jumbotron/services/canonical/ensure_schedule_group.rb +40 -0
- data/app/services/jumbotron/services/canonical/ensure_season.rb +22 -0
- data/app/services/jumbotron/services/canonical/ensure_season_phase.rb +22 -0
- data/app/services/jumbotron/services/canonical/persist_line_observations.rb +145 -0
- data/app/services/jumbotron/services/canonical/record_observation_evidence.rb +51 -0
- data/app/services/jumbotron/services/canonical/resolve_current_lines.rb +21 -0
- data/app/services/jumbotron/services/canonical/resolve_current_lines_for_games.rb +69 -0
- data/app/services/jumbotron/services/canonical/upsert_game.rb +186 -0
- data/app/services/jumbotron/services/canonical/upsert_game_participant.rb +88 -0
- data/app/services/jumbotron/services/canonical/upsert_team.rb +90 -0
- data/app/services/jumbotron/services/canonical/upsert_venue.rb +92 -0
- data/app/services/jumbotron/services/public/assemble_public_game.rb +131 -0
- data/app/services/jumbotron/services/public/assemble_public_schedule.rb +63 -0
- data/app/services/jumbotron/services/public/derive_consensus_for_games.rb +21 -0
- data/app/services/jumbotron/services/public/load_games_by_ids.rb +18 -0
- data/app/services/jumbotron/services/public/load_included_lines.rb +47 -0
- data/app/services/jumbotron/services/public/map_public_consensus_lines.rb +44 -0
- data/app/services/jumbotron/services/public/map_public_current_lines.rb +27 -0
- data/app/services/jumbotron/services/public/read_game.rb +32 -0
- data/app/services/jumbotron/services/public/read_schedule.rb +116 -0
- data/app/services/jumbotron/services/public/read_schedule_groups.rb +90 -0
- data/app/services/jumbotron/services/public/resolve_current_lines_for_games.rb +21 -0
- data/app/synchronization/jumbotron/synchronization/lease.rb +127 -0
- data/app/workflows/jumbotron/workflows/execute_discovery_workflow.rb +131 -0
- data/app/workflows/jumbotron/workflows/execute_game_update_policy_workflow.rb +159 -0
- data/app/workflows/jumbotron/workflows/execute_line_update_policy_workflow.rb +156 -0
- data/app/workflows/jumbotron/workflows/public_read.rb +29 -0
- data/app/workflows/jumbotron/workflows/read_consensus_workflow.rb +37 -0
- data/app/workflows/jumbotron/workflows/read_current_lines_workflow.rb +27 -0
- data/app/workflows/jumbotron/workflows/read_game_workflow.rb +34 -0
- data/app/workflows/jumbotron/workflows/read_schedule_groups_workflow.rb +21 -0
- data/app/workflows/jumbotron/workflows/read_schedule_workflow.rb +48 -0
- data/app/workflows/jumbotron/workflows/synchronize_adapter_workflow.rb +183 -0
- data/db/migrate/20260812000001_create_jumbotron_foundational_catalog.rb +68 -0
- data/db/migrate/20260812000002_create_jumbotron_game_graph.rb +70 -0
- data/db/migrate/20260812000003_add_freshness_to_jumbotron_provider_mutated_entities.rb +31 -0
- data/db/migrate/20260812000004_create_jumbotron_observation_evidence.rb +34 -0
- data/db/migrate/20260813000001_create_jumbotron_schedule_groups.rb +24 -0
- data/db/migrate/20260814000001_create_jumbotron_line_observations.rb +38 -0
- data/db/migrate/20260820000001_add_progress_to_jumbotron_games.rb +14 -0
- data/lib/jumbotron/client.rb +40 -0
- data/lib/jumbotron/engine.rb +32 -0
- data/lib/jumbotron/version.rb +5 -0
- data/lib/jumbotron.rb +13 -0
- data/lib/tasks/jumbotron_schedules.rake +12 -0
- metadata +239 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Errors
|
|
5
|
+
module Adapters
|
|
6
|
+
class InvalidAdapterError < CommandTower::Errors::ApplicationError
|
|
7
|
+
def code
|
|
8
|
+
"invalid_adapter"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def message
|
|
12
|
+
"adapter is invalid"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class NotRegisteredError < CommandTower::Errors::ApplicationError
|
|
17
|
+
def code
|
|
18
|
+
"adapter_not_registered"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def message
|
|
22
|
+
adapter = details.is_a?(Hash) ? details[:adapter] : nil
|
|
23
|
+
adapter ? "#{adapter} is not registered" : "adapter is not registered"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class UnknownEndpointError < CommandTower::Errors::ApplicationError
|
|
28
|
+
def code
|
|
29
|
+
"unknown_endpoint"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def message
|
|
33
|
+
endpoint = details.is_a?(Hash) ? details[:endpoint] : nil
|
|
34
|
+
endpoint ? "adapter has no endpoint :#{endpoint}" : "unknown endpoint"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class LeagueNotFoundError < CommandTower::Errors::ApplicationError
|
|
39
|
+
def code
|
|
40
|
+
"league_not_found"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def message
|
|
44
|
+
league_id = details.is_a?(Hash) ? details[:league_id] : nil
|
|
45
|
+
league_id ? "league #{league_id} not found" : "league not found"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class AcquisitionFailedError < CommandTower::Errors::ApplicationError
|
|
50
|
+
def code
|
|
51
|
+
"acquisition_failed"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def message
|
|
55
|
+
details.is_a?(Hash) && details[:message].present? ? details[:message].to_s : "provider acquisition failed"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class NormalizationFailedError < CommandTower::Errors::ApplicationError
|
|
60
|
+
def code
|
|
61
|
+
"normalization_failed"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def message
|
|
65
|
+
details.is_a?(Hash) && details[:message].present? ? details[:message].to_s : "normalization failed"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class UnknownAdapterError < CommandTower::Errors::ApplicationError
|
|
70
|
+
def code
|
|
71
|
+
"unknown_adapter"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def message
|
|
75
|
+
adapter_id = details.is_a?(Hash) ? details[:adapter_id] : nil
|
|
76
|
+
adapter_id ? "unknown adapter #{adapter_id}" : "unknown adapter"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class UnknownPolicyError < CommandTower::Errors::ApplicationError
|
|
81
|
+
def code
|
|
82
|
+
"unknown_policy"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def message
|
|
86
|
+
policy_id = details.is_a?(Hash) ? details[:policy_id] : nil
|
|
87
|
+
policy_id ? "unknown policy #{policy_id}" : "unknown policy"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class DiscoveryNotExecutableError < CommandTower::Errors::ApplicationError
|
|
92
|
+
def code
|
|
93
|
+
"discovery_not_executable"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def message
|
|
97
|
+
"discovery is not executable as a game-update policy"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class UnknownDiscoveryError < CommandTower::Errors::ApplicationError
|
|
102
|
+
def code
|
|
103
|
+
"unknown_discovery"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def message
|
|
107
|
+
discovery_id = details.is_a?(Hash) ? details[:discovery_id] : nil
|
|
108
|
+
discovery_id ? "unknown discovery #{discovery_id}" : "unknown discovery"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class InvalidPolicyTypeError < CommandTower::Errors::ApplicationError
|
|
113
|
+
def code
|
|
114
|
+
"invalid_policy_type"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def message
|
|
118
|
+
"policy is not an executable game-update policy"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class InvalidLinePolicyTypeError < CommandTower::Errors::ApplicationError
|
|
123
|
+
def code
|
|
124
|
+
"invalid_policy_type"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def message
|
|
128
|
+
"policy is not an executable line-update policy"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class ProviderCooldownActiveError < CommandTower::Errors::ApplicationError
|
|
133
|
+
def code
|
|
134
|
+
"provider_cooldown_active"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def message
|
|
138
|
+
"provider cooldown is active"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Errors
|
|
5
|
+
module Canonical
|
|
6
|
+
class InvalidSyncInputError < CommandTower::Errors::ApplicationError
|
|
7
|
+
def code
|
|
8
|
+
"invalid_sync_input"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def message
|
|
12
|
+
"canonical input must be Canonical::SyncInput"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class ProviderIdentityConflictError < CommandTower::Errors::ApplicationError
|
|
17
|
+
def code
|
|
18
|
+
"provider_identity_conflict"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def message
|
|
22
|
+
"provider identity is already attached to a different target"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class MutationFailedError < CommandTower::Errors::ApplicationError
|
|
27
|
+
def code
|
|
28
|
+
"mutation_failed"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def message
|
|
32
|
+
details.is_a?(Hash) && details[:message].present? ? details[:message].to_s : "canonical mutation failed"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class InvalidLineIngestError < CommandTower::Errors::ApplicationError
|
|
37
|
+
def code
|
|
38
|
+
"invalid_line_ingest"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def message
|
|
42
|
+
details.is_a?(Hash) && details[:message].present? ? details[:message].to_s : "invalid line ingest"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class GameNotFoundError < CommandTower::Errors::ApplicationError
|
|
47
|
+
def code
|
|
48
|
+
"game_not_found"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def message
|
|
52
|
+
"canonical game was not found for the provided identities"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class ObservationEvidenceFailedError < CommandTower::Errors::ApplicationError
|
|
57
|
+
def code
|
|
58
|
+
"observation_evidence_failed"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def message
|
|
62
|
+
details.is_a?(Hash) && details[:message].present? ? details[:message].to_s : "observation evidence failed"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Errors
|
|
5
|
+
module Public
|
|
6
|
+
class NotFoundError < CommandTower::Errors::ApplicationError
|
|
7
|
+
def code
|
|
8
|
+
details.is_a?(Hash) && details[:code].present? ? details[:code].to_s : "not_found"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def message
|
|
12
|
+
details.is_a?(Hash) && details[:message].present? ? details[:message].to_s : "resource was not found"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class InvalidRequestError < CommandTower::Errors::ApplicationError
|
|
17
|
+
def code
|
|
18
|
+
"invalid_request"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def message
|
|
22
|
+
details.is_a?(Hash) && details[:message].present? ? details[:message].to_s : "invalid public request"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class DiscoveryJob < ApplicationJob
|
|
5
|
+
def perform(adapter_id:, discovery_id:, continuation_attempt: 1)
|
|
6
|
+
execute_workflow(
|
|
7
|
+
Workflows::ExecuteDiscoveryWorkflow,
|
|
8
|
+
adapter_id: adapter_id,
|
|
9
|
+
discovery_id: discovery_id,
|
|
10
|
+
continuation_attempt: continuation_attempt
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class GameUpdateJob < ApplicationJob
|
|
5
|
+
def perform(adapter_id:, policy_id:)
|
|
6
|
+
Workflows::ExecuteGameUpdatePolicyWorkflow.call_from_job(
|
|
7
|
+
adapter_id: adapter_id,
|
|
8
|
+
policy_id: policy_id
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class LineUpdateJob < ApplicationJob
|
|
5
|
+
def perform(adapter_id:, policy_id:)
|
|
6
|
+
Workflows::ExecuteLineUpdatePolicyWorkflow.call_from_job(
|
|
7
|
+
adapter_id: adapter_id,
|
|
8
|
+
policy_id: policy_id
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class Bookmaker < ApplicationRecord
|
|
5
|
+
has_many :provider_identities, as: :target, inverse_of: :target, dependent: :restrict_with_exception
|
|
6
|
+
has_many :line_observations, inverse_of: :bookmaker, dependent: :restrict_with_exception
|
|
7
|
+
|
|
8
|
+
validates :name, presence: true
|
|
9
|
+
validates :observed_at, :changed_at, presence: true
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Canonical
|
|
5
|
+
ProviderIdentityRef = Data.define(:provider, :namespace, :id)
|
|
6
|
+
|
|
7
|
+
TeamInput = Data.define(:provider_identities, :name)
|
|
8
|
+
|
|
9
|
+
VenueInput = Data.define(:provider_identities, :name)
|
|
10
|
+
|
|
11
|
+
ParticipantInput = Data.define(
|
|
12
|
+
:provider_identities,
|
|
13
|
+
:team_name,
|
|
14
|
+
:role,
|
|
15
|
+
:score,
|
|
16
|
+
:result
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
ScheduleGroupInput = Data.define(:kind, :number, :name)
|
|
20
|
+
|
|
21
|
+
GameSegment = Data.define(:kind, :number)
|
|
22
|
+
# Clock label text. Name is canonical/public contract, not Kernel#display.
|
|
23
|
+
GameClock = Data.define(:mode, :seconds, :display) # rubocop:disable Lint/DataDefineOverride
|
|
24
|
+
GameProgress = Data.define(:state, :segment, :clock)
|
|
25
|
+
|
|
26
|
+
GameInput = Data.define(
|
|
27
|
+
:provider_identities,
|
|
28
|
+
:scheduled_at,
|
|
29
|
+
:lifecycle,
|
|
30
|
+
:neutral_site,
|
|
31
|
+
:season_year,
|
|
32
|
+
:season_phase_key,
|
|
33
|
+
:schedule_group,
|
|
34
|
+
:venue,
|
|
35
|
+
:participants,
|
|
36
|
+
:progress
|
|
37
|
+
) do
|
|
38
|
+
def initialize(progress: nil, **members)
|
|
39
|
+
super
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
SyncInput = Data.define(:observed_at, :games, :teams)
|
|
44
|
+
|
|
45
|
+
LineObservationInput = Data.define(
|
|
46
|
+
:bookmaker_identity,
|
|
47
|
+
:bookmaker_name,
|
|
48
|
+
:market,
|
|
49
|
+
:outcome,
|
|
50
|
+
:source,
|
|
51
|
+
:line_value,
|
|
52
|
+
:price_american
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
LineIngestInput = Data.define(
|
|
56
|
+
:observed_at,
|
|
57
|
+
:provider,
|
|
58
|
+
:adapter_scope,
|
|
59
|
+
:game_identities,
|
|
60
|
+
:observations
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
CurrentLine = Data.define(
|
|
64
|
+
:line_observation_id,
|
|
65
|
+
:game_id,
|
|
66
|
+
:bookmaker_id,
|
|
67
|
+
:bookmaker_name,
|
|
68
|
+
:market,
|
|
69
|
+
:outcome,
|
|
70
|
+
:line_value,
|
|
71
|
+
:price_american,
|
|
72
|
+
:observed_at,
|
|
73
|
+
:changed_at
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
ConsensusConstituent = Data.define(:line_observation_id, :bookmaker_id)
|
|
77
|
+
|
|
78
|
+
ConsensusLine = Data.define(
|
|
79
|
+
:game_id,
|
|
80
|
+
:market,
|
|
81
|
+
:outcome,
|
|
82
|
+
:line_value,
|
|
83
|
+
:constituent_count,
|
|
84
|
+
:constituents
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Transaction-local accumulator for material canonical attribute changes.
|
|
88
|
+
class ChangeSet
|
|
89
|
+
include Enumerable
|
|
90
|
+
|
|
91
|
+
Entry = Data.define(:subject, :attribute, :previous, :new_value)
|
|
92
|
+
|
|
93
|
+
def initialize
|
|
94
|
+
@entries = []
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def record(subject:, attribute:, previous:, new_value:)
|
|
98
|
+
return if serialize(previous) == serialize(new_value)
|
|
99
|
+
|
|
100
|
+
@entries << Entry.new(
|
|
101
|
+
subject: subject,
|
|
102
|
+
attribute: attribute.to_s,
|
|
103
|
+
previous: previous,
|
|
104
|
+
new_value: new_value
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
delegate :any?, :size, to: :entries
|
|
109
|
+
|
|
110
|
+
def each(&)
|
|
111
|
+
entries.each(&)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
attr_reader :entries
|
|
117
|
+
|
|
118
|
+
def serialize(value)
|
|
119
|
+
case value
|
|
120
|
+
when Time, ActiveSupport::TimeWithZone
|
|
121
|
+
value.iso8601
|
|
122
|
+
else
|
|
123
|
+
value
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class Game < ApplicationRecord
|
|
5
|
+
LIFECYCLES = %w[
|
|
6
|
+
scheduled
|
|
7
|
+
in_progress
|
|
8
|
+
completed
|
|
9
|
+
postponed
|
|
10
|
+
cancelled
|
|
11
|
+
suspended
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
PROGRESS_STATES = %w[active intermission].freeze
|
|
15
|
+
PROGRESS_CLOCK_MODES = %w[remaining elapsed].freeze
|
|
16
|
+
PROGRESS_CORE_ATTRIBUTES = %i[
|
|
17
|
+
progress_state
|
|
18
|
+
progress_segment_kind
|
|
19
|
+
progress_segment_number
|
|
20
|
+
].freeze
|
|
21
|
+
PROGRESS_CLOCK_ATTRIBUTES = %i[
|
|
22
|
+
progress_clock_mode
|
|
23
|
+
progress_clock_seconds
|
|
24
|
+
progress_clock_display
|
|
25
|
+
].freeze
|
|
26
|
+
PROGRESS_ATTRIBUTES = (PROGRESS_CORE_ATTRIBUTES + PROGRESS_CLOCK_ATTRIBUTES).freeze
|
|
27
|
+
|
|
28
|
+
belongs_to :league, inverse_of: :games
|
|
29
|
+
belongs_to :season, inverse_of: :games
|
|
30
|
+
belongs_to :season_phase, optional: true, inverse_of: :games
|
|
31
|
+
belongs_to :schedule_group, optional: true, inverse_of: :games
|
|
32
|
+
belongs_to :venue, optional: true, inverse_of: :games
|
|
33
|
+
has_many :game_participants, inverse_of: :game, dependent: :restrict_with_exception
|
|
34
|
+
has_many :teams, through: :game_participants
|
|
35
|
+
has_many :provider_identities, as: :target, inverse_of: :target, dependent: :restrict_with_exception
|
|
36
|
+
has_many :line_observations, inverse_of: :game, dependent: :restrict_with_exception
|
|
37
|
+
|
|
38
|
+
attribute :lifecycle, :string, default: "scheduled"
|
|
39
|
+
attribute :neutral_site, :boolean, default: false
|
|
40
|
+
|
|
41
|
+
validates :lifecycle, inclusion: { in: LIFECYCLES }
|
|
42
|
+
validates :progress_state, inclusion: { in: PROGRESS_STATES, allow_nil: true }
|
|
43
|
+
validates :progress_clock_mode, inclusion: { in: PROGRESS_CLOCK_MODES, allow_nil: true }
|
|
44
|
+
validate :season_belongs_to_league
|
|
45
|
+
validate :season_phase_belongs_to_season
|
|
46
|
+
validate :schedule_group_belongs_to_season
|
|
47
|
+
validate :progress_is_all_nil_or_core_complete
|
|
48
|
+
validate :intermission_has_no_clock
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def season_belongs_to_league
|
|
53
|
+
return if season.blank? || league.blank?
|
|
54
|
+
return if season.league_id == league_id
|
|
55
|
+
|
|
56
|
+
errors.add(:season, "must belong to the same league")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def season_phase_belongs_to_season
|
|
60
|
+
return if season_phase.blank? || season.blank?
|
|
61
|
+
return if season_phase.season_id == season_id
|
|
62
|
+
|
|
63
|
+
errors.add(:season_phase, "must belong to the same season")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def schedule_group_belongs_to_season
|
|
67
|
+
return if schedule_group.blank? || season.blank?
|
|
68
|
+
return if schedule_group.season_id == season_id
|
|
69
|
+
|
|
70
|
+
errors.add(:schedule_group, "must belong to the same season")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def progress_is_all_nil_or_core_complete
|
|
74
|
+
values = PROGRESS_ATTRIBUTES.map { |attribute| public_send(attribute) }
|
|
75
|
+
return if values.all?(&:nil?)
|
|
76
|
+
return if progress_core_complete?
|
|
77
|
+
|
|
78
|
+
errors.add(:progress_state, "must include state, segment kind, and segment number together")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def progress_core_complete?
|
|
82
|
+
progress_state.present? && progress_segment_kind.present? && !progress_segment_number.nil?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def intermission_has_no_clock
|
|
86
|
+
return unless progress_state == "intermission"
|
|
87
|
+
return if PROGRESS_CLOCK_ATTRIBUTES.all? { |attribute| public_send(attribute).nil? }
|
|
88
|
+
|
|
89
|
+
errors.add(:progress_clock_mode, "must be blank during intermission")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class GameParticipant < ApplicationRecord
|
|
5
|
+
belongs_to :game, inverse_of: :game_participants
|
|
6
|
+
belongs_to :team, inverse_of: :game_participants
|
|
7
|
+
|
|
8
|
+
validates :team_id, uniqueness: { scope: :game_id }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class HistoricalChange < ApplicationRecord
|
|
5
|
+
belongs_to :observation_batch, inverse_of: :historical_changes
|
|
6
|
+
|
|
7
|
+
validates :subject_type, :subject_id, :attribute_name, :observed_at, :provider, presence: true
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class League < ApplicationRecord
|
|
5
|
+
belongs_to :sport, inverse_of: :leagues
|
|
6
|
+
has_many :seasons, inverse_of: :league, dependent: :restrict_with_exception
|
|
7
|
+
has_many :games, inverse_of: :league, dependent: :restrict_with_exception
|
|
8
|
+
has_many :provider_identities, as: :target, inverse_of: :target, dependent: :restrict_with_exception
|
|
9
|
+
|
|
10
|
+
validates :name, presence: true
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class LineObservation < ApplicationRecord
|
|
5
|
+
MARKETS = %w[spread total moneyline].freeze
|
|
6
|
+
OUTCOMES = %w[home away over under draw].freeze
|
|
7
|
+
SOURCES = %w[observed provider_open provider_close].freeze
|
|
8
|
+
|
|
9
|
+
belongs_to :game, inverse_of: :line_observations
|
|
10
|
+
belongs_to :bookmaker, inverse_of: :line_observations
|
|
11
|
+
belongs_to :observation_batch, inverse_of: :line_observations
|
|
12
|
+
|
|
13
|
+
validates :market, inclusion: { in: MARKETS }
|
|
14
|
+
validates :outcome, inclusion: { in: OUTCOMES }
|
|
15
|
+
validates :source, inclusion: { in: SOURCES }
|
|
16
|
+
validates :observed_at, :changed_at, presence: true
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class ObservationBatch < ApplicationRecord
|
|
5
|
+
has_many :historical_changes,
|
|
6
|
+
inverse_of: :observation_batch,
|
|
7
|
+
dependent: :restrict_with_exception
|
|
8
|
+
has_many :line_observations,
|
|
9
|
+
inverse_of: :observation_batch,
|
|
10
|
+
dependent: :restrict_with_exception
|
|
11
|
+
|
|
12
|
+
validates :provider, :adapter_scope, :observed_at, presence: true
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class ProviderIdentity < ApplicationRecord
|
|
5
|
+
TARGET_TYPES = [
|
|
6
|
+
"Jumbotron::Sport",
|
|
7
|
+
"Jumbotron::League",
|
|
8
|
+
"Jumbotron::Season",
|
|
9
|
+
"Jumbotron::SeasonPhase",
|
|
10
|
+
"Jumbotron::Team",
|
|
11
|
+
"Jumbotron::Venue",
|
|
12
|
+
"Jumbotron::Game",
|
|
13
|
+
"Jumbotron::Bookmaker"
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
belongs_to :target, polymorphic: true, inverse_of: :provider_identities
|
|
17
|
+
|
|
18
|
+
before_validation :normalize_provider
|
|
19
|
+
|
|
20
|
+
validates :provider, presence: true
|
|
21
|
+
validates :object_namespace, presence: true
|
|
22
|
+
validates :provider_id, presence: true
|
|
23
|
+
validates :target_type, inclusion: { in: TARGET_TYPES }
|
|
24
|
+
validates :provider_id, uniqueness: { scope: %i[provider object_namespace] }
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def normalize_provider
|
|
29
|
+
self.provider = provider&.strip&.downcase
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|