jumbotron 0.1.6 → 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/espn/nfl.rb +15 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/scoreboard.rb +14 -2
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/teams.rb +2 -1
- 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/clients/espn/competitor.rb +14 -1
- data/app/deserializers/jumbotron/deserializers/clients/espn/competitor_record.rb +27 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/venue.rb +10 -1
- data/app/deserializers/jumbotron/deserializers/clients/espn/venue_address.rb +27 -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/canonical.rb +20 -5
- data/app/models/jumbotron/game.rb +11 -0
- data/app/models/jumbotron/public/translate_outcome.rb +13 -7
- data/app/models/jumbotron/public.rb +31 -6
- 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/adapters/resolve_game_update_policy.rb +1 -0
- data/app/services/jumbotron/services/adapters/select_eligible_games.rb +1 -1
- data/app/services/jumbotron/services/canonical/upsert_game_graph.rb +126 -0
- data/app/services/jumbotron/services/canonical/upsert_game_participant.rb +114 -6
- data/app/services/jumbotron/services/canonical/upsert_team.rb +24 -0
- data/app/services/jumbotron/services/canonical/upsert_venue.rb +33 -1
- data/app/services/jumbotron/services/public/assemble_public_game.rb +20 -2
- 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/20260828220001_add_game_presentation_contract_fields.rb +19 -0
- 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 +62 -2
|
@@ -4,13 +4,17 @@ module Jumbotron
|
|
|
4
4
|
module Canonical
|
|
5
5
|
ProviderIdentityRef = Data.define(:provider, :namespace, :id)
|
|
6
6
|
|
|
7
|
-
TeamInput = Data.define(:provider_identities, :name, :nickname) do
|
|
8
|
-
def initialize(provider_identities:, name:, nickname: nil)
|
|
7
|
+
TeamInput = Data.define(:provider_identities, :name, :nickname, :abbreviation) do
|
|
8
|
+
def initialize(provider_identities:, name:, nickname: nil, abbreviation: nil)
|
|
9
9
|
super
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
VenueInput = Data.define(:provider_identities, :name)
|
|
13
|
+
VenueInput = Data.define(:provider_identities, :name, :city, :region) do
|
|
14
|
+
def initialize(provider_identities:, name:, city: nil, region: nil)
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
end
|
|
14
18
|
|
|
15
19
|
ParticipantInput = Data.define(
|
|
16
20
|
:provider_identities,
|
|
@@ -18,9 +22,20 @@ module Jumbotron
|
|
|
18
22
|
:role,
|
|
19
23
|
:score,
|
|
20
24
|
:result,
|
|
21
|
-
:team_nickname
|
|
25
|
+
:team_nickname,
|
|
26
|
+
:team_abbreviation,
|
|
27
|
+
:record_summary
|
|
22
28
|
) do
|
|
23
|
-
def initialize(
|
|
29
|
+
def initialize(
|
|
30
|
+
provider_identities:,
|
|
31
|
+
team_name:,
|
|
32
|
+
role:,
|
|
33
|
+
score:,
|
|
34
|
+
result:,
|
|
35
|
+
team_nickname: nil,
|
|
36
|
+
team_abbreviation: nil,
|
|
37
|
+
record_summary: nil
|
|
38
|
+
)
|
|
24
39
|
super
|
|
25
40
|
end
|
|
26
41
|
end
|
|
@@ -34,6 +34,17 @@ module Jumbotron
|
|
|
34
34
|
has_many :teams, through: :game_participants
|
|
35
35
|
has_many :provider_identities, as: :target, inverse_of: :target, dependent: :restrict_with_exception
|
|
36
36
|
has_many :line_observations, inverse_of: :game, dependent: :restrict_with_exception
|
|
37
|
+
has_one :sandbox_game_plan, inverse_of: :game, dependent: :restrict_with_exception
|
|
38
|
+
has_many :source_sandbox_game_mappings,
|
|
39
|
+
class_name: "Jumbotron::SandboxGameMapping",
|
|
40
|
+
foreign_key: :source_game_id,
|
|
41
|
+
inverse_of: :source_game,
|
|
42
|
+
dependent: :restrict_with_exception
|
|
43
|
+
has_one :sandbox_game_mapping,
|
|
44
|
+
class_name: "Jumbotron::SandboxGameMapping",
|
|
45
|
+
foreign_key: :sandbox_game_id,
|
|
46
|
+
inverse_of: :sandbox_game,
|
|
47
|
+
dependent: :restrict_with_exception
|
|
37
48
|
|
|
38
49
|
attribute :lifecycle, :string, default: "scheduled"
|
|
39
50
|
attribute :neutral_site, :boolean, default: false
|
|
@@ -10,6 +10,7 @@ module Jumbotron
|
|
|
10
10
|
season_not_found
|
|
11
11
|
season_phase_not_found
|
|
12
12
|
schedule_group_not_found
|
|
13
|
+
sandbox_not_found
|
|
13
14
|
].freeze
|
|
14
15
|
|
|
15
16
|
module_function
|
|
@@ -25,29 +26,34 @@ module Jumbotron
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def public_error_for(result)
|
|
28
|
-
code, message =
|
|
29
|
+
code, message, details = error_code_message_and_details(result)
|
|
29
30
|
klass = exception_class_for(code)
|
|
30
|
-
klass.new(message)
|
|
31
|
+
klass.new(message, code: code, details: details)
|
|
31
32
|
end
|
|
32
33
|
module_function :public_error_for
|
|
33
34
|
|
|
34
|
-
def
|
|
35
|
+
def error_code_message_and_details(result) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
35
36
|
error = Array(result.errors).first
|
|
36
37
|
case error
|
|
37
38
|
when CommandTower::Errors::ApplicationError
|
|
38
|
-
[error.code.to_s, error.message]
|
|
39
|
+
[error.code.to_s, error.message, error.details || {}]
|
|
39
40
|
when Hash
|
|
40
|
-
[
|
|
41
|
+
[
|
|
42
|
+
(error[:code] || error["code"]).to_s,
|
|
43
|
+
(error[:message] || error["message"]).to_s,
|
|
44
|
+
error[:details] || error["details"] || {}
|
|
45
|
+
]
|
|
41
46
|
else
|
|
42
|
-
["internal_error", error.to_s]
|
|
47
|
+
["internal_error", error.to_s, {}]
|
|
43
48
|
end
|
|
44
49
|
end
|
|
45
|
-
module_function :
|
|
50
|
+
module_function :error_code_message_and_details
|
|
46
51
|
|
|
47
52
|
def exception_class_for(code)
|
|
48
53
|
return Jumbotron::NotFoundError if NOT_FOUND_CODES.include?(code)
|
|
49
54
|
return Jumbotron::InvalidRequestError if code == "invalid_request"
|
|
50
55
|
return Jumbotron::UnsupportedCapabilityError if code == "unsupported"
|
|
56
|
+
return Jumbotron::SandboxResetRejectedError if code.start_with?("sandbox_", "source_", "anchor_")
|
|
51
57
|
|
|
52
58
|
Jumbotron::Error
|
|
53
59
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Jumbotron
|
|
4
|
-
module Public
|
|
4
|
+
module Public # rubocop:disable Metrics/ModuleLength
|
|
5
5
|
ALLOWED_INCLUDES = %i[consensus current_lines consensus_constituents].freeze
|
|
6
6
|
|
|
7
7
|
Sport = Data.define(:id, :name)
|
|
@@ -10,6 +10,23 @@ module Jumbotron
|
|
|
10
10
|
SeasonPhase = Data.define(:id, :name)
|
|
11
11
|
ScheduleGroup = Data.define(:id, :kind, :number, :name)
|
|
12
12
|
ScheduleGroupEnumeration = Data.define(:groups, :completeness)
|
|
13
|
+
SandboxScheduleSelector = Data.define(:sport, :league, :season, :season_phase)
|
|
14
|
+
RegisterSandboxProjectionRequest = Data.define(:name, :source)
|
|
15
|
+
ResetSandboxProjectionRequest = Data.define(:sandbox, :start_week, :anchor_day, :now)
|
|
16
|
+
SandboxProjection = Data.define(:name, :source, :sandbox)
|
|
17
|
+
SandboxProjectionReset = Data.define(
|
|
18
|
+
:projection,
|
|
19
|
+
:source_anchor_date,
|
|
20
|
+
:target_anchor_date,
|
|
21
|
+
:date_shift_days,
|
|
22
|
+
:projected_game_count,
|
|
23
|
+
:added_game_count,
|
|
24
|
+
:updated_game_count,
|
|
25
|
+
:removed_game_count,
|
|
26
|
+
:start_week_game_count,
|
|
27
|
+
:naturally_locked_game_count,
|
|
28
|
+
:reset_at
|
|
29
|
+
)
|
|
13
30
|
ScheduleGroupsRequest = Data.define(
|
|
14
31
|
:league_id,
|
|
15
32
|
:sport,
|
|
@@ -19,13 +36,21 @@ module Jumbotron
|
|
|
19
36
|
:season_phase,
|
|
20
37
|
:kind
|
|
21
38
|
)
|
|
22
|
-
Team = Data.define(:id, :name, :nickname, :key) do
|
|
23
|
-
def initialize(id:, name:, key:, nickname: nil)
|
|
24
|
-
super(id:, name:, nickname:, key:)
|
|
39
|
+
Team = Data.define(:id, :name, :nickname, :key, :abbreviation) do
|
|
40
|
+
def initialize(id:, name:, key:, nickname: nil, abbreviation: nil)
|
|
41
|
+
super(id:, name:, nickname:, key:, abbreviation:)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
Venue = Data.define(:id, :name, :city, :region) do
|
|
45
|
+
def initialize(id:, name:, city: nil, region: nil)
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
Participant = Data.define(:role, :score, :result, :team, :record) do
|
|
50
|
+
def initialize(role:, score:, result:, team:, record: nil)
|
|
51
|
+
super
|
|
25
52
|
end
|
|
26
53
|
end
|
|
27
|
-
Venue = Data.define(:id, :name)
|
|
28
|
-
Participant = Data.define(:role, :score, :result, :team)
|
|
29
54
|
GameSegment = Data.define(:kind, :number)
|
|
30
55
|
# Clock label text. Name is canonical/public contract, not Kernel#display.
|
|
31
56
|
GameClock = Data.define(:mode, :seconds, :display) # rubocop:disable Lint/DataDefineOverride
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class SandboxGameMapping < ApplicationRecord
|
|
5
|
+
belongs_to :sandbox_projection, inverse_of: :sandbox_game_mappings
|
|
6
|
+
belongs_to :source_game, class_name: "Jumbotron::Game", inverse_of: :source_sandbox_game_mappings
|
|
7
|
+
belongs_to :sandbox_game, class_name: "Jumbotron::Game", inverse_of: :sandbox_game_mapping
|
|
8
|
+
|
|
9
|
+
validates :source_game_id, uniqueness: { scope: :sandbox_projection_id }
|
|
10
|
+
validates :sandbox_game_id, uniqueness: true
|
|
11
|
+
validates :home_spread, :total, numericality: true
|
|
12
|
+
validates :line_fingerprint, presence: true
|
|
13
|
+
validate :games_are_in_expected_worlds
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def games_are_in_expected_worlds
|
|
18
|
+
return if source_game.nil? || sandbox_game.nil?
|
|
19
|
+
return if source_game.league.name != "nfl-sandbox" && sandbox_game.league.name == "nfl-sandbox"
|
|
20
|
+
|
|
21
|
+
errors.add(:base, "mapping requires live source and sandbox target games")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class SandboxGamePlan < ApplicationRecord
|
|
5
|
+
IMMUTABLE_ATTRIBUTES = %w[game_id home_score away_score selected_at].freeze
|
|
6
|
+
|
|
7
|
+
belongs_to :game, inverse_of: :sandbox_game_plan
|
|
8
|
+
|
|
9
|
+
validates :game_id, uniqueness: true
|
|
10
|
+
validates :home_score, :away_score,
|
|
11
|
+
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
|
12
|
+
validates :selected_at, presence: true
|
|
13
|
+
validate :selected_outcome_is_immutable, on: :update
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def selected_outcome_is_immutable
|
|
18
|
+
return unless IMMUTABLE_ATTRIBUTES.any? { |attribute| will_save_change_to_attribute?(attribute) }
|
|
19
|
+
|
|
20
|
+
errors.add(:base, "selected sandbox outcome is immutable")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
class SandboxProjection < ApplicationRecord
|
|
5
|
+
belongs_to :source_season, class_name: "Jumbotron::Season", inverse_of: :source_sandbox_projections
|
|
6
|
+
belongs_to :source_anchor_phase, class_name: "Jumbotron::SeasonPhase"
|
|
7
|
+
belongs_to :sandbox_season, class_name: "Jumbotron::Season", inverse_of: :sandbox_projection
|
|
8
|
+
has_many :sandbox_game_mappings, inverse_of: :sandbox_projection, dependent: :restrict_with_exception
|
|
9
|
+
|
|
10
|
+
validates :name, presence: true, uniqueness: true, format: { with: /\A[a-z0-9][a-z0-9-]*\z/ }
|
|
11
|
+
validates :adapter_id, inclusion: { in: %w[sandbox_nfl] }
|
|
12
|
+
validates :calendar_time_zone, inclusion: { in: ActiveSupport::TimeZone.all.map(&:tzinfo).map(&:name) }
|
|
13
|
+
validate :anchor_phase_belongs_to_source
|
|
14
|
+
validate :source_and_sandbox_are_distinct
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def anchor_phase_belongs_to_source
|
|
19
|
+
return if source_anchor_phase.nil? || source_anchor_phase.season_id == source_season_id
|
|
20
|
+
|
|
21
|
+
errors.add(:source_anchor_phase, "must belong to source season")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def source_and_sandbox_are_distinct
|
|
25
|
+
return unless source_season_id == sandbox_season_id
|
|
26
|
+
|
|
27
|
+
errors.add(:sandbox_season, "must differ from source season")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module SandboxProjectionData
|
|
5
|
+
Team = Data.define(:provider_id, :name, :nickname, :abbreviation)
|
|
6
|
+
Game = Data.define(
|
|
7
|
+
:source_game_id,
|
|
8
|
+
:event_id,
|
|
9
|
+
:competition_id,
|
|
10
|
+
:scheduled_at,
|
|
11
|
+
:season,
|
|
12
|
+
:season_phase,
|
|
13
|
+
:week_number,
|
|
14
|
+
:week_name,
|
|
15
|
+
:neutral_site,
|
|
16
|
+
:home,
|
|
17
|
+
:away,
|
|
18
|
+
:home_spread,
|
|
19
|
+
:total,
|
|
20
|
+
:line_fingerprint,
|
|
21
|
+
:line_source
|
|
22
|
+
)
|
|
23
|
+
Snapshot = Data.define(
|
|
24
|
+
:games,
|
|
25
|
+
:source_anchor_date,
|
|
26
|
+
:target_anchor_date,
|
|
27
|
+
:date_shift_days,
|
|
28
|
+
:fingerprint,
|
|
29
|
+
:start_week
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -7,6 +7,16 @@ module Jumbotron
|
|
|
7
7
|
has_many :schedule_groups, inverse_of: :season, dependent: :restrict_with_exception
|
|
8
8
|
has_many :games, inverse_of: :season, dependent: :restrict_with_exception
|
|
9
9
|
has_many :provider_identities, as: :target, inverse_of: :target, dependent: :restrict_with_exception
|
|
10
|
+
has_many :source_sandbox_projections,
|
|
11
|
+
class_name: "Jumbotron::SandboxProjection",
|
|
12
|
+
foreign_key: :source_season_id,
|
|
13
|
+
inverse_of: :source_season,
|
|
14
|
+
dependent: :restrict_with_exception
|
|
15
|
+
has_one :sandbox_projection,
|
|
16
|
+
class_name: "Jumbotron::SandboxProjection",
|
|
17
|
+
foreign_key: :sandbox_season_id,
|
|
18
|
+
inverse_of: :sandbox_season,
|
|
19
|
+
dependent: :restrict_with_exception
|
|
10
20
|
|
|
11
21
|
validates :name, presence: true
|
|
12
22
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Providers
|
|
5
|
+
module Sandbox
|
|
6
|
+
module Resources
|
|
7
|
+
class CompetitionOdds
|
|
8
|
+
def self.acquire(adapter:, **query)
|
|
9
|
+
return invalid_adapter unless adapter.provider == "sandbox"
|
|
10
|
+
|
|
11
|
+
CommandTower::Clients::ClientResult.success(output: query.symbolize_keys)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.invalid_adapter
|
|
15
|
+
CommandTower::Clients::ClientResult.failure(
|
|
16
|
+
error: ArgumentError.new("sandbox provider requires a sandbox adapter")
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
private_class_method :invalid_adapter
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Providers
|
|
5
|
+
module Sandbox
|
|
6
|
+
module Resources
|
|
7
|
+
module Projection
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def acquire(adapter:, **projection)
|
|
11
|
+
unless adapter.provider.to_s == "sandbox"
|
|
12
|
+
return CommandTower::Clients::ClientResult.failure(
|
|
13
|
+
error: ArgumentError.new("sandbox projection requires sandbox adapter")
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
CommandTower::Clients::ClientResult.success(output: projection)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Providers
|
|
5
|
+
module Sandbox
|
|
6
|
+
module Resources
|
|
7
|
+
class Scoreboard
|
|
8
|
+
def self.acquire(adapter:, **query)
|
|
9
|
+
return invalid_adapter unless adapter.provider == "sandbox"
|
|
10
|
+
|
|
11
|
+
CommandTower::Clients::ClientResult.success(output: query.symbolize_keys)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.invalid_adapter
|
|
15
|
+
CommandTower::Clients::ClientResult.failure(
|
|
16
|
+
error: ArgumentError.new("sandbox provider requires a sandbox adapter")
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
private_class_method :invalid_adapter
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -6,6 +6,7 @@ module Jumbotron
|
|
|
6
6
|
class ExecuteLineAcquisitionScope < CommandTower::Services::ApplicationService
|
|
7
7
|
validate :adapter, required: true
|
|
8
8
|
validate :acquisition, is_a: Hash, required: true
|
|
9
|
+
validate :observed_at, required: false
|
|
9
10
|
|
|
10
11
|
def call
|
|
11
12
|
if adapter.provider_cooling_down?
|
|
@@ -43,7 +44,7 @@ module Jumbotron
|
|
|
43
44
|
|
|
44
45
|
ingest = adapter.translate_line_odds(
|
|
45
46
|
provider_result.output,
|
|
46
|
-
observed_at: Time.current,
|
|
47
|
+
observed_at: observed_at || Time.current,
|
|
47
48
|
acquisition: acquisition
|
|
48
49
|
)
|
|
49
50
|
persisted = Canonical::PersistLineObservations.call(ingest: ingest)
|
|
@@ -20,7 +20,7 @@ module Jumbotron
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
games = Game.where(league_id: league.id)
|
|
23
|
-
.includes(:season, :season_phase, :schedule_group)
|
|
23
|
+
.includes(:season, :season_phase, :schedule_group, :game_participants)
|
|
24
24
|
.select { |game| policy.eligible?(game, now: now) }
|
|
25
25
|
|
|
26
26
|
context.league = league
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Services
|
|
5
|
+
module Canonical
|
|
6
|
+
class UpsertGameGraph < CommandTower::Services::ApplicationService
|
|
7
|
+
validate :game_input, required: true
|
|
8
|
+
validate :league, required: true
|
|
9
|
+
validate :observed_at, required: true
|
|
10
|
+
validate :change_set, required: true
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
season = resolve(EnsureSeason.call(league: league, year: game_input.season_year), :season)
|
|
14
|
+
return if season.nil?
|
|
15
|
+
|
|
16
|
+
phase = resolve(
|
|
17
|
+
EnsureSeasonPhase.call(season: season, phase_key: game_input.season_phase_key),
|
|
18
|
+
:season_phase
|
|
19
|
+
)
|
|
20
|
+
return if phase.nil?
|
|
21
|
+
|
|
22
|
+
group = resolve_group(season, phase)
|
|
23
|
+
return if context.failure?
|
|
24
|
+
|
|
25
|
+
venue = resolve(
|
|
26
|
+
UpsertVenue.call(
|
|
27
|
+
venue_input: game_input.venue,
|
|
28
|
+
observed_at: observed_at,
|
|
29
|
+
change_set: change_set
|
|
30
|
+
),
|
|
31
|
+
:venue,
|
|
32
|
+
allow_nil: true
|
|
33
|
+
)
|
|
34
|
+
return if context.failure?
|
|
35
|
+
|
|
36
|
+
game = upsert_game(season, phase, group, venue)
|
|
37
|
+
return if game.nil?
|
|
38
|
+
return unless attach_identities?(game)
|
|
39
|
+
return unless upsert_participants?(game)
|
|
40
|
+
|
|
41
|
+
context.game = game
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def resolve_group(season, phase)
|
|
47
|
+
resolve(
|
|
48
|
+
EnsureScheduleGroup.call(
|
|
49
|
+
season: season,
|
|
50
|
+
season_phase: phase,
|
|
51
|
+
schedule_group_input: game_input.schedule_group
|
|
52
|
+
),
|
|
53
|
+
:schedule_group,
|
|
54
|
+
allow_nil: true
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def upsert_game(season, phase, group, venue)
|
|
59
|
+
resolve(
|
|
60
|
+
UpsertGame.call(
|
|
61
|
+
game_input: game_input,
|
|
62
|
+
league: league,
|
|
63
|
+
season: season,
|
|
64
|
+
season_phase: phase,
|
|
65
|
+
schedule_group: group,
|
|
66
|
+
venue: venue,
|
|
67
|
+
observed_at: observed_at,
|
|
68
|
+
change_set: change_set
|
|
69
|
+
),
|
|
70
|
+
:game
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def attach_identities?(game)
|
|
75
|
+
result = AttachProviderIdentities.call(target: game, refs: game_input.provider_identities)
|
|
76
|
+
return true if result.success?
|
|
77
|
+
|
|
78
|
+
fail_from(result)
|
|
79
|
+
false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def upsert_participants?(game)
|
|
83
|
+
game_input.participants.each do |participant_input|
|
|
84
|
+
result = UpsertGameParticipant.call(
|
|
85
|
+
game: game,
|
|
86
|
+
participant_input: participant_input,
|
|
87
|
+
observed_at: observed_at,
|
|
88
|
+
change_set: change_set,
|
|
89
|
+
preserve_existing_team: sandbox_game_input?
|
|
90
|
+
)
|
|
91
|
+
unless result.success?
|
|
92
|
+
fail_from(result)
|
|
93
|
+
return false
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
true
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def sandbox_game_input?
|
|
100
|
+
game_input.provider_identities.any? { |identity| identity.provider == "sandbox" }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def resolve(result, key, allow_nil: false)
|
|
104
|
+
unless result.success?
|
|
105
|
+
fail_from(result)
|
|
106
|
+
return
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
value = result.data[key]
|
|
110
|
+
return value if allow_nil || !value.nil?
|
|
111
|
+
|
|
112
|
+
context.fail!(
|
|
113
|
+
application_error: Jumbotron::Errors::Canonical::MutationFailedError.new(
|
|
114
|
+
details: { message: "#{key} was not resolved" }
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def fail_from(result)
|
|
121
|
+
context.fail!(application_error: Array(result.errors).first)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|