jumbotron 0.1.6 → 0.1.7
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/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/models/jumbotron/canonical.rb +20 -5
- data/app/models/jumbotron/public.rb +13 -5
- 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_participant.rb +91 -3
- 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/db/migrate/20260828220001_add_game_presentation_contract_fields.rb +19 -0
- data/lib/jumbotron/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 59e55efb1ece4d237011869ce7416fd4cda99a2e95feb914c21e93a3f23aecaa
|
|
4
|
+
data.tar.gz: 2199925a6f4a46d2db581c63e2a5eb55b81a26483195a396a4b7c111ae41bee1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae7c4b2c06eed8291bafcb279940f0ca45ad5b33a62cc5970a5c1d353eae0c4d8bde4173e62811d13dade3038107940a828f37b2b3e1c1d6a7f2c118f62d5447
|
|
7
|
+
data.tar.gz: 8ecd32a04d755e5eb0422fccbb98b10beead9f3b88de8f8e885f2b8e4f458af6a672d1bdf9a63040b88e352186cea328565263dff18d001298b1a0cf97389748
|
|
@@ -66,6 +66,21 @@ module Jumbotron
|
|
|
66
66
|
INTERRUPTED_LIFECYCLES.include?(game.lifecycle)
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
# Bounded post-final scoreboard re-observation when post-game record is still
|
|
70
|
+
# missing (Strategy B). Stops after window or once post_game is persisted.
|
|
71
|
+
policy :post_final_record,
|
|
72
|
+
type: :post_final_game_update,
|
|
73
|
+
cadence: Cadence.new(every: 15, unit: :minute),
|
|
74
|
+
eligible: lambda { |game, now:|
|
|
75
|
+
return false unless game.lifecycle == "completed"
|
|
76
|
+
return false if game.changed_at.nil? || game.changed_at < now - 6.hours
|
|
77
|
+
|
|
78
|
+
participants = game.game_participants
|
|
79
|
+
return false if participants.empty?
|
|
80
|
+
|
|
81
|
+
participants.any? { |participant| participant.record_summary_post_game.blank? }
|
|
82
|
+
}
|
|
83
|
+
|
|
69
84
|
policy :far_future_lines,
|
|
70
85
|
type: :future_line_update,
|
|
71
86
|
cadence: Cadence.new(every: 1, unit: :week),
|
|
@@ -80,11 +80,14 @@ module Jumbotron
|
|
|
80
80
|
raise TransformError, "venue id required" if venue.id.blank?
|
|
81
81
|
raise TransformError, "venue name required" if venue.full_name.blank?
|
|
82
82
|
|
|
83
|
+
address = venue.respond_to?(:address) ? venue.address : nil
|
|
83
84
|
Canonical::VenueInput.new(
|
|
84
85
|
provider_identities: [
|
|
85
86
|
Canonical::ProviderIdentityRef.new(provider: "espn", namespace: "venue", id: venue.id.to_s)
|
|
86
87
|
],
|
|
87
|
-
name: venue.full_name.to_s
|
|
88
|
+
name: venue.full_name.to_s,
|
|
89
|
+
city: address&.city.presence,
|
|
90
|
+
region: address&.state.presence
|
|
88
91
|
)
|
|
89
92
|
end
|
|
90
93
|
private_class_method :transform_venue
|
|
@@ -104,13 +107,22 @@ module Jumbotron
|
|
|
104
107
|
],
|
|
105
108
|
team_name: name.to_s,
|
|
106
109
|
team_nickname: TeamNickname.resolve(team),
|
|
110
|
+
team_abbreviation: team.abbreviation.presence,
|
|
107
111
|
role: competitor.home_away.to_s,
|
|
108
112
|
score: coerce_score(competitor.score),
|
|
109
|
-
result: coerce_result(competitor.winner, lifecycle)
|
|
113
|
+
result: coerce_result(competitor.winner, lifecycle),
|
|
114
|
+
record_summary: total_record_summary(competitor)
|
|
110
115
|
)
|
|
111
116
|
end
|
|
112
117
|
private_class_method :transform_participant
|
|
113
118
|
|
|
119
|
+
def self.total_record_summary(competitor)
|
|
120
|
+
records = competitor.respond_to?(:records) ? Array(competitor.records) : []
|
|
121
|
+
total = records.find { |record| record.type.to_s == "total" }
|
|
122
|
+
total&.summary.presence
|
|
123
|
+
end
|
|
124
|
+
private_class_method :total_record_summary
|
|
125
|
+
|
|
114
126
|
def self.coerce_score(raw)
|
|
115
127
|
return nil if raw.nil? || raw == ""
|
|
116
128
|
|
|
@@ -25,7 +25,8 @@ module Jumbotron
|
|
|
25
25
|
Canonical::ProviderIdentityRef.new(provider: "espn", namespace: "team", id: team.id.to_s)
|
|
26
26
|
],
|
|
27
27
|
name: name.to_s,
|
|
28
|
-
nickname: TeamNickname.resolve(team)
|
|
28
|
+
nickname: TeamNickname.resolve(team),
|
|
29
|
+
abbreviation: team.abbreviation.presence
|
|
29
30
|
)
|
|
30
31
|
end
|
|
31
32
|
private_class_method :transform_team
|
|
@@ -13,6 +13,7 @@ module Jumbotron
|
|
|
13
13
|
field :winner, type: Support.types.boolean, nullable: true
|
|
14
14
|
field :order, type: Support.types.integer, nullable: true
|
|
15
15
|
field :team, type: Team::Result, required: true
|
|
16
|
+
field :records, type: Support.types.array(CompetitorRecord::Result), nullable: true
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def self.call(payload)
|
|
@@ -25,10 +26,22 @@ module Jumbotron
|
|
|
25
26
|
score: coerce_score(Support.payload.fetch(payload, "score")),
|
|
26
27
|
winner: Support.payload.fetch(payload, "winner"),
|
|
27
28
|
order: Support.payload.fetch(payload, "order"),
|
|
28
|
-
team: Support.nest("team", Support.payload.fetch(payload, "team")) { |raw| Team.call(raw) }
|
|
29
|
+
team: Support.nest("team", Support.payload.fetch(payload, "team")) { |raw| Team.call(raw) },
|
|
30
|
+
records: map_records(Support.payload.fetch(payload, "records"))
|
|
29
31
|
)
|
|
30
32
|
end
|
|
31
33
|
|
|
34
|
+
def self.map_records(raw)
|
|
35
|
+
return nil if raw.equal?(Support.missing) || raw.nil?
|
|
36
|
+
|
|
37
|
+
Array(raw).each_with_index.map do |item, index|
|
|
38
|
+
CompetitorRecord.call(item)
|
|
39
|
+
rescue CommandTower::Clients::Errors::DeserializationError => e
|
|
40
|
+
raise CommandTower::Deserializers::Clients::Errors.prefix(e, "records[#{index}]")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
private_class_method :map_records
|
|
44
|
+
|
|
32
45
|
def self.coerce_id(raw)
|
|
33
46
|
return raw if raw.equal?(Support.missing) || raw.nil? || raw.is_a?(String)
|
|
34
47
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Clients
|
|
6
|
+
module Espn
|
|
7
|
+
class CompetitorRecord
|
|
8
|
+
class Result < CommandTower::Deserializers::Clients::Result
|
|
9
|
+
field :type, type: Support.types.string, required: true
|
|
10
|
+
field :summary, type: Support.types.string, required: true
|
|
11
|
+
field :name, type: Support.types.string, nullable: true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.call(payload)
|
|
15
|
+
Support.ensure_hash!(payload, label: "competitor.record")
|
|
16
|
+
|
|
17
|
+
Result.build!(
|
|
18
|
+
type: Support.payload.fetch(payload, "type"),
|
|
19
|
+
summary: Support.payload.fetch(payload, "summary"),
|
|
20
|
+
name: Support.payload.fetch(payload, "name")
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -9,6 +9,7 @@ module Jumbotron
|
|
|
9
9
|
field :id, type: Support.types.string, required: true
|
|
10
10
|
field :full_name, type: Support.types.string, required: true
|
|
11
11
|
field :indoor, type: Support.types.boolean, nullable: true
|
|
12
|
+
field :address, type: VenueAddress::Result, nullable: true
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def self.call(payload)
|
|
@@ -17,10 +18,18 @@ module Jumbotron
|
|
|
17
18
|
Result.build!(
|
|
18
19
|
id: coerce_id(Support.payload.fetch(payload, "id")),
|
|
19
20
|
full_name: Support.payload.fetch(payload, "fullName"),
|
|
20
|
-
indoor: Support.payload.fetch(payload, "indoor")
|
|
21
|
+
indoor: Support.payload.fetch(payload, "indoor"),
|
|
22
|
+
address: map_address(Support.payload.fetch(payload, "address"))
|
|
21
23
|
)
|
|
22
24
|
end
|
|
23
25
|
|
|
26
|
+
def self.map_address(raw)
|
|
27
|
+
return nil if raw.equal?(Support.missing) || raw.nil?
|
|
28
|
+
|
|
29
|
+
VenueAddress.call(raw)
|
|
30
|
+
end
|
|
31
|
+
private_class_method :map_address
|
|
32
|
+
|
|
24
33
|
def self.coerce_id(raw)
|
|
25
34
|
return raw if raw.equal?(Support.missing) || raw.nil? || raw.is_a?(String)
|
|
26
35
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Clients
|
|
6
|
+
module Espn
|
|
7
|
+
class VenueAddress
|
|
8
|
+
class Result < CommandTower::Deserializers::Clients::Result
|
|
9
|
+
field :city, type: Support.types.string, nullable: true
|
|
10
|
+
field :state, type: Support.types.string, nullable: true
|
|
11
|
+
field :country, type: Support.types.string, nullable: true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.call(payload)
|
|
15
|
+
Support.ensure_hash!(payload, label: "venue.address")
|
|
16
|
+
|
|
17
|
+
Result.build!(
|
|
18
|
+
city: Support.payload.fetch(payload, "city"),
|
|
19
|
+
state: Support.payload.fetch(payload, "state"),
|
|
20
|
+
country: Support.payload.fetch(payload, "country")
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -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
|
|
@@ -19,13 +19,21 @@ module Jumbotron
|
|
|
19
19
|
:season_phase,
|
|
20
20
|
:kind
|
|
21
21
|
)
|
|
22
|
-
Team = Data.define(:id, :name, :nickname, :key) do
|
|
23
|
-
def initialize(id:, name:, key:, nickname: nil)
|
|
24
|
-
super(id:, name:, nickname:, key:)
|
|
22
|
+
Team = Data.define(:id, :name, :nickname, :key, :abbreviation) do
|
|
23
|
+
def initialize(id:, name:, key:, nickname: nil, abbreviation: nil)
|
|
24
|
+
super(id:, name:, nickname:, key:, abbreviation:)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
Venue = Data.define(:id, :name, :city, :region) do
|
|
28
|
+
def initialize(id:, name:, city: nil, region: nil)
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
Participant = Data.define(:role, :score, :result, :team, :record) do
|
|
33
|
+
def initialize(role:, score:, result:, team:, record: nil)
|
|
34
|
+
super
|
|
25
35
|
end
|
|
26
36
|
end
|
|
27
|
-
Venue = Data.define(:id, :name)
|
|
28
|
-
Participant = Data.define(:role, :score, :result, :team)
|
|
29
37
|
GameSegment = Data.define(:kind, :number)
|
|
30
38
|
# Clock label text. Name is canonical/public contract, not Kernel#display.
|
|
31
39
|
GameClock = Data.define(:mode, :seconds, :display) # rubocop:disable Lint/DataDefineOverride
|
|
@@ -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
|
|
@@ -14,7 +14,8 @@ module Jumbotron
|
|
|
14
14
|
team_input: Jumbotron::Canonical::TeamInput.new(
|
|
15
15
|
provider_identities: participant_input.provider_identities,
|
|
16
16
|
name: participant_input.team_name,
|
|
17
|
-
nickname: participant_input.team_nickname
|
|
17
|
+
nickname: participant_input.team_nickname,
|
|
18
|
+
abbreviation: participant_input.team_abbreviation
|
|
18
19
|
),
|
|
19
20
|
observed_at: observed_at,
|
|
20
21
|
change_set: change_set
|
|
@@ -46,16 +47,18 @@ module Jumbotron
|
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
def create_new!(team)
|
|
49
|
-
|
|
50
|
+
attrs = {
|
|
50
51
|
team: team,
|
|
51
52
|
role: participant_input.role,
|
|
52
53
|
score: participant_input.score,
|
|
53
54
|
result: participant_input.result,
|
|
54
55
|
observed_at: observed_at,
|
|
55
56
|
changed_at: observed_at
|
|
56
|
-
)
|
|
57
|
+
}.merge(initial_record_attrs)
|
|
58
|
+
gp = game.game_participants.create!(attrs)
|
|
57
59
|
change_set.record(subject: gp, attribute: "role", previous: nil, new_value: gp.role)
|
|
58
60
|
change_set.record(subject: gp, attribute: "score", previous: nil, new_value: gp.score)
|
|
61
|
+
record_create_history!(gp)
|
|
59
62
|
gp
|
|
60
63
|
end
|
|
61
64
|
|
|
@@ -78,11 +81,96 @@ module Jumbotron
|
|
|
78
81
|
participant.public_send("#{key}=", value)
|
|
79
82
|
material = true
|
|
80
83
|
end
|
|
84
|
+
material |= apply_record_snapshots!(participant)
|
|
81
85
|
participant.observed_at = observed_at
|
|
82
86
|
participant.changed_at = observed_at if material
|
|
83
87
|
participant.save!
|
|
84
88
|
participant
|
|
85
89
|
end
|
|
90
|
+
|
|
91
|
+
def initial_record_attrs
|
|
92
|
+
summary = participant_input.record_summary
|
|
93
|
+
return {} if summary.blank?
|
|
94
|
+
|
|
95
|
+
attrs = {
|
|
96
|
+
record_summary_current: summary,
|
|
97
|
+
record_observed_at: observed_at
|
|
98
|
+
}
|
|
99
|
+
if game.lifecycle == "completed"
|
|
100
|
+
attrs[:record_summary_post_game] = summary
|
|
101
|
+
elsif game.lifecycle != "cancelled"
|
|
102
|
+
attrs[:record_summary_entering] = summary
|
|
103
|
+
end
|
|
104
|
+
attrs
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def record_create_history!(participant)
|
|
108
|
+
%i[
|
|
109
|
+
record_summary_entering
|
|
110
|
+
record_summary_current
|
|
111
|
+
record_summary_post_game
|
|
112
|
+
].each do |attr|
|
|
113
|
+
value = participant.public_send(attr)
|
|
114
|
+
next if value.blank?
|
|
115
|
+
|
|
116
|
+
change_set.record(subject: participant, attribute: attr.to_s, previous: nil, new_value: value)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def apply_record_snapshots!(participant)
|
|
121
|
+
summary = participant_input.record_summary
|
|
122
|
+
return false if summary.blank?
|
|
123
|
+
|
|
124
|
+
material = false
|
|
125
|
+
if participant.record_summary_current != summary
|
|
126
|
+
change_set.record(
|
|
127
|
+
subject: participant,
|
|
128
|
+
attribute: "record_summary_current",
|
|
129
|
+
previous: participant.record_summary_current,
|
|
130
|
+
new_value: summary
|
|
131
|
+
)
|
|
132
|
+
participant.record_summary_current = summary
|
|
133
|
+
material = true
|
|
134
|
+
end
|
|
135
|
+
participant.record_observed_at = observed_at
|
|
136
|
+
|
|
137
|
+
if game.lifecycle == "completed"
|
|
138
|
+
material |= apply_post_game_record!(participant, summary)
|
|
139
|
+
elsif game.lifecycle != "cancelled"
|
|
140
|
+
material |= assign_entering_record!(participant, summary)
|
|
141
|
+
end
|
|
142
|
+
material
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def assign_entering_record!(participant, summary) # rubocop:disable Naming/PredicateMethod
|
|
146
|
+
return false if participant.record_summary_entering == summary
|
|
147
|
+
|
|
148
|
+
change_set.record(
|
|
149
|
+
subject: participant,
|
|
150
|
+
attribute: "record_summary_entering",
|
|
151
|
+
previous: participant.record_summary_entering,
|
|
152
|
+
new_value: summary
|
|
153
|
+
)
|
|
154
|
+
participant.record_summary_entering = summary
|
|
155
|
+
true
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Authoritative post-game only: never invent W–L; if total still equals
|
|
159
|
+
# entering on a completed observation, leave post_game nil (Strategy B).
|
|
160
|
+
def apply_post_game_record!(participant, summary) # rubocop:disable Naming/PredicateMethod
|
|
161
|
+
entering = participant.record_summary_entering
|
|
162
|
+
return false if entering.present? && summary == entering
|
|
163
|
+
return false if participant.record_summary_post_game == summary
|
|
164
|
+
|
|
165
|
+
change_set.record(
|
|
166
|
+
subject: participant,
|
|
167
|
+
attribute: "record_summary_post_game",
|
|
168
|
+
previous: participant.record_summary_post_game,
|
|
169
|
+
new_value: summary
|
|
170
|
+
)
|
|
171
|
+
participant.record_summary_post_game = summary
|
|
172
|
+
true
|
|
173
|
+
end
|
|
86
174
|
end
|
|
87
175
|
end
|
|
88
176
|
end
|
|
@@ -58,6 +58,7 @@ module Jumbotron
|
|
|
58
58
|
team.changed_at = observed_at
|
|
59
59
|
end
|
|
60
60
|
apply_nickname!(team)
|
|
61
|
+
apply_abbreviation!(team)
|
|
61
62
|
# key is immutable after assignment — never regenerate from name or provider id
|
|
62
63
|
team.observed_at = observed_at
|
|
63
64
|
team.save!
|
|
@@ -68,6 +69,7 @@ module Jumbotron
|
|
|
68
69
|
team = Team.create!(
|
|
69
70
|
name: team_input.name,
|
|
70
71
|
nickname: team_input.nickname,
|
|
72
|
+
abbreviation: team_input.abbreviation,
|
|
71
73
|
key: allocate_key!(team_input.name),
|
|
72
74
|
observed_at: observed_at,
|
|
73
75
|
changed_at: observed_at
|
|
@@ -82,6 +84,14 @@ module Jumbotron
|
|
|
82
84
|
new_value: team.nickname
|
|
83
85
|
)
|
|
84
86
|
end
|
|
87
|
+
if team.abbreviation.present?
|
|
88
|
+
change_set.record(
|
|
89
|
+
subject: team,
|
|
90
|
+
attribute: "abbreviation",
|
|
91
|
+
previous: nil,
|
|
92
|
+
new_value: team.abbreviation
|
|
93
|
+
)
|
|
94
|
+
end
|
|
85
95
|
team
|
|
86
96
|
end
|
|
87
97
|
|
|
@@ -99,6 +109,20 @@ module Jumbotron
|
|
|
99
109
|
team.changed_at = observed_at
|
|
100
110
|
end
|
|
101
111
|
|
|
112
|
+
def apply_abbreviation!(team)
|
|
113
|
+
return if team_input.abbreviation.nil?
|
|
114
|
+
return if team.abbreviation == team_input.abbreviation
|
|
115
|
+
|
|
116
|
+
change_set.record(
|
|
117
|
+
subject: team,
|
|
118
|
+
attribute: "abbreviation",
|
|
119
|
+
previous: team.abbreviation,
|
|
120
|
+
new_value: team_input.abbreviation
|
|
121
|
+
)
|
|
122
|
+
team.abbreviation = team_input.abbreviation
|
|
123
|
+
team.changed_at = observed_at
|
|
124
|
+
end
|
|
125
|
+
|
|
102
126
|
def allocate_key!(name)
|
|
103
127
|
base = Jumbotron::TeamKey.normalize(name)
|
|
104
128
|
return base unless Team.exists?(key: base)
|
|
@@ -52,6 +52,7 @@ module Jumbotron
|
|
|
52
52
|
|
|
53
53
|
def update_existing!(venue_id)
|
|
54
54
|
venue = Venue.lock.find(venue_id)
|
|
55
|
+
material = false
|
|
55
56
|
if venue.name != venue_input.name
|
|
56
57
|
change_set.record(
|
|
57
58
|
subject: venue,
|
|
@@ -60,8 +61,10 @@ module Jumbotron
|
|
|
60
61
|
new_value: venue_input.name
|
|
61
62
|
)
|
|
62
63
|
venue.name = venue_input.name
|
|
63
|
-
|
|
64
|
+
material = true
|
|
64
65
|
end
|
|
66
|
+
material |= apply_location!(venue)
|
|
67
|
+
venue.changed_at = observed_at if material
|
|
65
68
|
venue.observed_at = observed_at
|
|
66
69
|
venue.save!
|
|
67
70
|
venue
|
|
@@ -70,13 +73,42 @@ module Jumbotron
|
|
|
70
73
|
def create_new!
|
|
71
74
|
venue = Venue.create!(
|
|
72
75
|
name: venue_input.name,
|
|
76
|
+
city: venue_input.city,
|
|
77
|
+
region: venue_input.region,
|
|
73
78
|
observed_at: observed_at,
|
|
74
79
|
changed_at: observed_at
|
|
75
80
|
)
|
|
76
81
|
change_set.record(subject: venue, attribute: "name", previous: nil, new_value: venue.name)
|
|
82
|
+
if venue.city.present?
|
|
83
|
+
change_set.record(subject: venue, attribute: "city", previous: nil, new_value: venue.city)
|
|
84
|
+
end
|
|
85
|
+
if venue.region.present?
|
|
86
|
+
change_set.record(subject: venue, attribute: "region", previous: nil, new_value: venue.region)
|
|
87
|
+
end
|
|
77
88
|
venue
|
|
78
89
|
end
|
|
79
90
|
|
|
91
|
+
def apply_location!(venue)
|
|
92
|
+
material = false
|
|
93
|
+
{
|
|
94
|
+
city: venue_input.city,
|
|
95
|
+
region: venue_input.region
|
|
96
|
+
}.each do |key, value|
|
|
97
|
+
next if value.nil?
|
|
98
|
+
next if venue.public_send(key) == value
|
|
99
|
+
|
|
100
|
+
change_set.record(
|
|
101
|
+
subject: venue,
|
|
102
|
+
attribute: key.to_s,
|
|
103
|
+
previous: venue.public_send(key),
|
|
104
|
+
new_value: value
|
|
105
|
+
)
|
|
106
|
+
venue.public_send("#{key}=", value)
|
|
107
|
+
material = true
|
|
108
|
+
end
|
|
109
|
+
material
|
|
110
|
+
end
|
|
111
|
+
|
|
80
112
|
def recover_after_race!
|
|
81
113
|
ref = venue_input.provider_identities.first
|
|
82
114
|
identity = ProviderIdentity.find_by!(
|
|
@@ -61,7 +61,12 @@ module Jumbotron
|
|
|
61
61
|
def build_venue(venue)
|
|
62
62
|
return if venue.nil?
|
|
63
63
|
|
|
64
|
-
Jumbotron::Public::Venue.new(
|
|
64
|
+
Jumbotron::Public::Venue.new(
|
|
65
|
+
id: venue.id,
|
|
66
|
+
name: venue.name,
|
|
67
|
+
city: venue.city,
|
|
68
|
+
region: venue.region
|
|
69
|
+
)
|
|
65
70
|
end
|
|
66
71
|
|
|
67
72
|
def build_participant(participant)
|
|
@@ -69,15 +74,28 @@ module Jumbotron
|
|
|
69
74
|
role: participant.role,
|
|
70
75
|
score: participant.score,
|
|
71
76
|
result: participant.result,
|
|
77
|
+
record: resolve_presentation_record(participant),
|
|
72
78
|
team: Jumbotron::Public::Team.new(
|
|
73
79
|
id: participant.team.id,
|
|
74
80
|
name: participant.team.name,
|
|
75
81
|
nickname: participant.team.nickname,
|
|
76
|
-
key: participant.team.key
|
|
82
|
+
key: participant.team.key,
|
|
83
|
+
abbreviation: participant.team.abbreviation
|
|
77
84
|
)
|
|
78
85
|
)
|
|
79
86
|
end
|
|
80
87
|
|
|
88
|
+
# Lifecycle-resolved game-time record for presentation. Never falls back to
|
|
89
|
+
# entering when completed post-game is missing.
|
|
90
|
+
def resolve_presentation_record(participant)
|
|
91
|
+
case game.lifecycle
|
|
92
|
+
when "scheduled", "in_progress", "postponed", "suspended"
|
|
93
|
+
participant.record_summary_entering
|
|
94
|
+
when "completed"
|
|
95
|
+
participant.record_summary_post_game
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
81
99
|
def map_currents(lines)
|
|
82
100
|
return [] unless Array(includes).include?(:current_lines)
|
|
83
101
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddGamePresentationContractFields < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
change_table :jumbotron_game_participants, bulk: true do |t|
|
|
6
|
+
t.string :record_summary_entering
|
|
7
|
+
t.string :record_summary_current
|
|
8
|
+
t.string :record_summary_post_game
|
|
9
|
+
t.datetime :record_observed_at
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
change_table :jumbotron_venues, bulk: true do |t|
|
|
13
|
+
t.string :city
|
|
14
|
+
t.string :region
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
add_column :jumbotron_teams, :abbreviation, :string
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/jumbotron/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.1.7
|
|
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-
|
|
11
|
+
date: 2026-08-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: command_tower
|
|
@@ -91,6 +91,7 @@ files:
|
|
|
91
91
|
- app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/side_quote.rb
|
|
92
92
|
- app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/team_odds.rb
|
|
93
93
|
- app/deserializers/jumbotron/deserializers/clients/espn/competitor.rb
|
|
94
|
+
- app/deserializers/jumbotron/deserializers/clients/espn/competitor_record.rb
|
|
94
95
|
- app/deserializers/jumbotron/deserializers/clients/espn/event.rb
|
|
95
96
|
- app/deserializers/jumbotron/deserializers/clients/espn/scoreboard/get.rb
|
|
96
97
|
- app/deserializers/jumbotron/deserializers/clients/espn/season.rb
|
|
@@ -99,6 +100,7 @@ files:
|
|
|
99
100
|
- app/deserializers/jumbotron/deserializers/clients/espn/team.rb
|
|
100
101
|
- app/deserializers/jumbotron/deserializers/clients/espn/teams/get.rb
|
|
101
102
|
- app/deserializers/jumbotron/deserializers/clients/espn/venue.rb
|
|
103
|
+
- app/deserializers/jumbotron/deserializers/clients/espn/venue_address.rb
|
|
102
104
|
- app/deserializers/jumbotron/deserializers/clients/espn/week.rb
|
|
103
105
|
- app/deserializers/jumbotron/deserializers/public/game.rb
|
|
104
106
|
- app/deserializers/jumbotron/deserializers/public/game_ids.rb
|
|
@@ -208,6 +210,7 @@ files:
|
|
|
208
210
|
- db/migrate/20260820000001_add_progress_to_jumbotron_games.rb
|
|
209
211
|
- db/migrate/20260827150001_add_key_to_jumbotron_teams.rb
|
|
210
212
|
- db/migrate/20260828180001_add_nickname_to_jumbotron_teams.rb
|
|
213
|
+
- db/migrate/20260828220001_add_game_presentation_contract_fields.rb
|
|
211
214
|
- docs/testing.md
|
|
212
215
|
- docs/upgrades/0.1.2.md
|
|
213
216
|
- docs/upgrades/0.1.3.md
|