fifadat-convert 0.0.1 → 0.1.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.
@@ -0,0 +1,55 @@
1
+ ## use mk/build_report_basename - why? why not?
2
+ ## fix-fix-fix - change to _match_basename !!!
3
+ ## use by (match)report and (match)timeline !!!
4
+ def _report_basename( m )
5
+ ### get match (live) details
6
+ team1_code = m['Home']['Abbreviation']
7
+ team2_code = m['Away']['Abbreviation']
8
+ localDateTime = parse_date_utc( m['LocalDate'] )
9
+
10
+ ## e.g. 2020-06-24_RAP-AUS__54545454
11
+ basename = String.new
12
+ basename += localDateTime.strftime('%Y-%m-%d') ## i) date
13
+ basename += "_#{team1_code}-#{team2_code}" ## ii) team1 v team2
14
+
15
+ basename
16
+ end
17
+
18
+
19
+
20
+ def _read_timeline( m, timeline_dir: )
21
+ return nil if m['Home'].nil? || m['Away'].nil?
22
+
23
+ basename = _report_basename( m )
24
+ basename += "__#{m['IdMatch']}"
25
+
26
+ path = "#{timeline_dir}/#{basename}.json"
27
+
28
+ ## note - skip if no match report available!!!
29
+ if File.file?( path )
30
+ timeline = read_json_v2( path )
31
+ timeline
32
+ else
33
+ nil
34
+ end
35
+ end
36
+
37
+ def _read_report( m, report_dir: )
38
+ return nil if m['Home'].nil? || m['Away'].nil?
39
+
40
+ ## e.g. 2020-06-24_RAP-AUS__54545454
41
+ # note - add match id for "raw" match reports!!!
42
+ basename = _report_basename( m )
43
+ basename += "__#{m['IdMatch']}"
44
+
45
+ ## #{indir}/#{slug}/matches/#{season.to_path}
46
+ path = "#{report_dir}/#{basename}.json"
47
+
48
+ ## note - skip if no match report available!!!
49
+ if File.file?( path )
50
+ live = read_json_v2( path )
51
+ live
52
+ else
53
+ nil
54
+ end
55
+ end
@@ -77,7 +77,7 @@ else
77
77
  idCompetition = h[:idCompetition]
78
78
  comp = Fifa::COMPETITIONS[idCompetition]
79
79
  seasons = comp.values.map { |c| c[:season] }
80
- puts " #{code} (#{seasons.size}) #{seasons.join(',')}"
80
+ puts " #{code} (#{seasons.size}) #{seasons.join(' ')}"
81
81
  end
82
82
  exit 1
83
83
  end
@@ -4,8 +4,8 @@
4
4
 
5
5
  module FifadatConvert
6
6
  MAJOR = 0 ## todo: namespace inside version or something - why? why not??
7
- MINOR = 0
8
- PATCH = 1
7
+ MINOR = 1
8
+ PATCH = 0
9
9
  VERSION = [MAJOR,MINOR,PATCH].join('.')
10
10
 
11
11
  def self.version
@@ -3,6 +3,37 @@ $LOAD_PATH.unshift( '/sports/yorobot/fifadat/fifadat/lib' )
3
3
  require 'fifadat'
4
4
 
5
5
 
6
+ # module JsonSerializable
7
+ # def to_json(*args)
8
+ # as_json.to_json(*args)
9
+ # end
10
+ # end
11
+
12
+ ###
13
+ ## add as_json to Object/Array/Hash
14
+ class Object
15
+ def as_json(*)
16
+ self
17
+ end
18
+ end
19
+
20
+ class Array
21
+ def as_json(*)
22
+ map { |v| v.as_json }
23
+ end
24
+ end
25
+
26
+ class Hash
27
+ def as_json(*)
28
+ each_with_object({}) do |(k, v), h|
29
+ h[k.to_s] = v.as_json
30
+ end
31
+ end
32
+ end
33
+
34
+
35
+
36
+
6
37
  ## our own code
7
38
  require_relative 'convert/version'
8
39
  require_relative 'convert/tool' ## fifadat command-line tool
@@ -11,20 +42,7 @@ require_relative 'convert/tool' ## fifadat command-line tool
11
42
  require_relative 'convert/norm'
12
43
  require_relative 'convert/errata'
13
44
 
14
- require_relative 'convert/stages'
15
- require_relative 'convert/teams'
16
- require_relative 'convert/stadiums'
17
- require_relative 'convert/players'
18
-
19
- require_relative 'convert/officials' ## aka referees
20
- require_relative 'convert/goals'
21
- require_relative 'convert/goals-calc_score'
22
- require_relative 'convert/penalties'
23
- require_relative 'convert/substitutions'
24
-
25
-
26
- require_relative 'convert/build_match'
27
- require_relative 'convert/build_report'
45
+ require_relative 'convert/reader'
28
46
  require_relative 'convert/convert'
29
47
  require_relative 'convert/convert-reports'
30
48
 
@@ -34,4 +52,26 @@ require_relative 'convert/helper-minute'
34
52
 
35
53
 
36
54
 
55
+ require_relative 'models/stage'
56
+ require_relative 'models/team'
57
+ require_relative 'models/stadium'
58
+ require_relative 'models/match'
59
+ require_relative 'models/match_report'
60
+ require_relative 'models/goal'
61
+ require_relative 'models/penalty'
62
+ require_relative 'models/substitution'
63
+ require_relative 'models/official' ## aka referees
64
+ require_relative 'models/player' # note - is lineup player incl. starter/bench, position, etc.
65
+ # (NOT squad player)
66
+
67
+
68
+
69
+ require_relative 'tables/stages'
70
+ require_relative 'tables/teams'
71
+ require_relative 'tables/stadiums'
72
+ require_relative 'tables/players'
73
+
74
+
75
+
76
+
37
77
  puts FifadatConvert.banner ## say hello
@@ -1,8 +1,29 @@
1
1
 
2
+ class Goal
2
3
 
3
4
 
5
+ ## add alias _build_all or _build_collection or ?? - why? why not?
6
+ def self._build_ary( recs, players:,
7
+ penalties: false )
8
+
9
+ ## note - filter out penalties (from shoot-out)!!
10
+ ## min > 120 (e.g. 121, etc.)
11
+ ## note - use period (more reliable)
12
+ ## period 11 is PENALTY_SHOOTOUT!!
13
+ if penalties == false
14
+ recs = recs.select { |rec| rec['Period'] != 11 }
15
+ end
16
+
17
+
18
+ recs = recs.map { |h| build( h, players: players ) }
19
+
20
+ ## note - sort by minutes; goals may not be sorted
21
+ recs = recs.sort { |l,r| l.minute <=> r.minute }
22
+ recs
23
+ end
4
24
 
5
- def build_goal( h, players: )
25
+
26
+ def self.build( h, players: )
6
27
 
7
28
  ## split into minute
8
29
  ## and offset (stoppage/injury time)
@@ -40,16 +61,15 @@ def build_goal( h, players: )
40
61
  idPlayer = h['IdPlayer']
41
62
 
42
63
  if idPlayer.nil?
43
- puts "!! no idPlayer for goal!"
64
+ puts "!! no idPlayer for goal (live):"
44
65
  pp h
45
66
  ##exit 1
46
67
  ## use 'N.N.'
47
68
 
48
- rec[ :name ] = 'N.N.'
69
+ rec[ :player ] = Player::PLAYER_NIL ## 'N.N.'
49
70
 
50
71
  else
51
- player = players.find!( idPlayer )
52
- rec[ :name ] = player[ :name ]
72
+ rec[ :player ] = players.find!( idPlayer )
53
73
  end
54
74
 
55
75
  rec[ :minute] = minute
@@ -64,26 +84,37 @@ def build_goal( h, players: )
64
84
  rec[ :pen ] = true if type == 1
65
85
  rec[ :og ] = true if type == 3
66
86
 
67
- rec
87
+ new( **rec )
68
88
  end
69
89
 
70
90
 
71
91
 
72
92
 
73
- def build_goals( recs, players:, penalties: false )
74
93
 
75
- ## note - filter out penalties (from shoot-out)!!
76
- ## min > 120 (e.g. 121, etc.)
77
- ## note - use period (more reliable)
78
- ## period 11 is PENALTY_SHOOTOUT!!
79
- if penalties == false
80
- recs = recs.select { |rec| rec['Period'] != 11 }
81
- end
94
+ attr_reader :player, :minute,
95
+ :pen, :og
82
96
 
97
+ def initialize(
98
+ player:,
99
+ minute:,
100
+ pen: false, og: false)
101
+ @player = player
102
+ @minute = minute
103
+ @pen = pen
104
+ @og = og
105
+ end
83
106
 
84
- recs = recs.map { |h| build_goal( h, players: players ) }
85
107
 
86
- ## note - sort by minutes; goals may not be sorted
87
- recs = recs.sort { |l,r| l[:minute] <=> r[:minute] }
88
- recs
108
+ def as_json(*)
109
+ h = {
110
+ 'name' => player.name,
111
+ 'minute' => minute,
112
+ }
113
+
114
+ h['og'] = og if og
115
+ h['pen'] = pen if pen
116
+ h
89
117
  end
118
+
119
+
120
+ end # class Goal
@@ -0,0 +1,218 @@
1
+
2
+
3
+ class Match
4
+
5
+
6
+ def self.build( m, teams:, stadiums:, stages: ) ## use _fill/build_match_basics() or such ???
7
+ rec = {}
8
+
9
+ ## 0 => FINISHED/complete (OK)
10
+ ## 1 => SCHEDULED/not yet played
11
+ status = m['MatchStatus']
12
+ timed = m['TimeDefined']
13
+ rec[:timed] = timed ### note - keep timed flag too - why? why not?
14
+
15
+
16
+ if status == 1 && timed ## note: use TIMED (instead of SCHEDULED)
17
+ rec[:status] = 'TIMED'
18
+ else
19
+ rec[:status] = MATCH_STATUS[ status ] || "#{status}-???"
20
+ end
21
+
22
+
23
+
24
+ rec[:stage] = stages.find!( desc( m['StageName'] ))
25
+
26
+
27
+ ## check optional group, matchday, (match) num
28
+ ## note - matchday is a string!! (check if always a number)
29
+ ## ## convert to int if str - why? why not??
30
+ group = desc( m['GroupName'] )
31
+ rec[:group] = group if group
32
+
33
+ matchday = m['MatchDay']
34
+ rec[:matchday] = matchday.to_i(10) if matchday
35
+
36
+ num = m['MatchNumber']
37
+ rec[:num] = num if num
38
+
39
+
40
+ ## e.g. if timed == false (in at)
41
+ ## "Date": "2027-02-27T00:00:00Z",
42
+ ## "LocalDate": "2027-02-27T01:00:00Z",
43
+
44
+ dateTime = parse_date_utc( m['Date'] )
45
+ localDateTime = parse_date_utc( m['LocalDate'] )
46
+
47
+ if timed
48
+ ## pass along as is for now - why? why not
49
+ ## note - Date is date_utc and
50
+ ## LocalDate is date_local (also in utc BUT different HH:MM or possible day)
51
+ ## note - also in utc, that is, timezone is Z (not +03:00 or such!!!)
52
+
53
+ ## note - use datetime - why? why not?
54
+ ##
55
+ ## note - cut-off/remove seconds
56
+ rec[:datetime_utc] = dateTime.strftime( '%Y-%m-%dT%H:%MZ' )
57
+
58
+ ## note - use 20:30 UTC+1 or 20:30 UTC-3 for timezone format for now
59
+ rec[:date_local] = localDateTime.strftime( '%Y-%m-%d' )
60
+ rec[:time_local] = _fmt_time_local( dateTime, localDateTime )
61
+ else
62
+ ## note - uses
63
+ ## todo/fix - assert that utc hour/minute is 00:00 e.g.
64
+ ## "2027-02-27T00:00:00Z"
65
+ rec[:date_local] = dateTime.strftime( '%Y-%m-%d' )
66
+ end
67
+
68
+
69
+
70
+ if m['HomeTeam'] && m['AwayTeam'] ## assume match reports (json-style)
71
+ team1 = teams.find!( m['HomeTeam'] )
72
+ team2 = teams.find!( m['AwayTeam'] )
73
+ else
74
+ team1 = teams.find!( m['Home'] )
75
+ team2 = teams.find!( m['Away'] )
76
+ end
77
+
78
+ rec[:team1] = team1
79
+ rec[:team2] = team2
80
+
81
+ #####
82
+ # handle score
83
+ ## m = (full) match hash incl. IdMatch, etc.
84
+ ## returns string e.g. 4-4 or 4-3 a.e.t etc
85
+
86
+ ##
87
+ ## fix-fix-fix
88
+ ## use a score class!!!
89
+ ## see openliga etc.
90
+
91
+ score = _parse_score( m )
92
+
93
+
94
+ rec[:score] = score
95
+
96
+ ## check - never in use?
97
+ rec[:home] = true if m['IsHome']
98
+
99
+
100
+ ## add country to stadium too - why? why not?
101
+ rec[:stadium] = stadiums.find!( m['Stadium'] )
102
+
103
+
104
+ attendance = m['Attendance']
105
+ rec[:attendance] = attendance.to_i(10) if attendance
106
+
107
+
108
+ new( **rec )
109
+ end
110
+
111
+
112
+ attr_reader :status,
113
+ :team1, :team2,
114
+ :stage, :group, :matchday, :num,
115
+ :datetime_utc,
116
+ :date_local,
117
+ :time_local, :timed,
118
+ :stadium, :home, :attendance
119
+
120
+ attr_accessor :score,
121
+ :goals1, :goals2,
122
+ :penalties,
123
+ :sentoff1, :sentoff2,
124
+ :officials
125
+
126
+
127
+ def initialize(
128
+ status:,
129
+ team1:,
130
+ team2:,
131
+ score: nil,
132
+
133
+ stage: nil,
134
+ group: nil,
135
+ matchday: nil,
136
+ num: nil,
137
+
138
+ datetime_utc: nil,
139
+ date_local: nil,
140
+ time_local: nil,
141
+ timed: nil,
142
+
143
+ stadium: nil,
144
+ home: nil,
145
+ attendance: nil)
146
+ @status = status
147
+ @team1 = team1
148
+ @team2 = team2
149
+ @score = score
150
+
151
+ @stage = stage
152
+ @group = group
153
+ @matchday = matchday
154
+ @num = num
155
+
156
+ @datetime_utc = datetime_utc
157
+ @date_local = date_local
158
+ @time_local = time_local
159
+ @timed = timed
160
+
161
+ @stadium = stadium
162
+ @home = home
163
+ @attendance = attendance
164
+ end
165
+
166
+
167
+
168
+ def as_json(*)
169
+ h = {
170
+ 'status' => status,
171
+ 'stage' => stage.name,
172
+ }
173
+
174
+ h['matchday'] = matchday if matchday
175
+ h['num'] = num if num
176
+ h['group'] = group if group
177
+
178
+ if timed
179
+ h['datetime_utc'] = datetime_utc
180
+ h['date_local'] = date_local
181
+ h['time_local'] = time_local
182
+ else
183
+ h['date_local'] = date_local
184
+ end
185
+
186
+ h['team1'] = team1.name
187
+ h['team2'] = team2.name
188
+
189
+ h['score'] = score if score && !score.empty?
190
+
191
+
192
+ if stadium
193
+ h['stadium'] = { 'name' => stadium.name,
194
+ 'city' => stadium.city }
195
+ end
196
+
197
+ h['attendance'] if attendance
198
+
199
+
200
+
201
+ if goals1 || goals2
202
+ h['goals1'] = (goals1 || []).as_json
203
+ h['goals2'] = (goals2 || []).as_json
204
+ end
205
+
206
+ h['penalities'] = penalties.as_json if penalties
207
+
208
+ if sentoff1 || sentoff2
209
+ h['sentoff1'] = (sentoff1 || []).as_json
210
+ h['sentoff2'] = (sentoff2 || []).as_json
211
+ end
212
+
213
+
214
+ h['referees'] = officials.as_json if officials
215
+ h
216
+ end
217
+
218
+ end # class Match
@@ -0,0 +1,205 @@
1
+
2
+
3
+ ##
4
+ ## holds details of match (kind of match sheet/report)
5
+ ##
6
+
7
+ ## fix-fix-fix move to tables - yes or rename MatchReportBuilder ?
8
+ ## move to tables - why? why not?
9
+
10
+ ##
11
+ ## fix-fix-fix rename to MatchDetails !!!
12
+ ## keep MatchReport/Sheet reserved for later
13
+
14
+
15
+ class MatchReport
16
+
17
+
18
+ def self.build( live, timeline=nil )
19
+ _players1 = Player._build_ary( live['HomeTeam']['Players'] )
20
+ _players2 = Player._build_ary( live['AwayTeam']['Players'] )
21
+
22
+ players = Players.new ## lookup all players (team1+team2)
23
+ players.add( _players1 )
24
+ players.add( _players2 )
25
+
26
+ players1 = Players.new ## lookup players by team1/team2 only
27
+ players1.add( _players1 )
28
+ players2 = Players.new
29
+ players2.add( _players2 )
30
+
31
+
32
+ o = new
33
+ ###
34
+ ## add goals
35
+ o.goals1 = Goal._build_ary( live['HomeTeam']['Goals'], players: players )
36
+ o.goals2 = Goal._build_ary( live['AwayTeam']['Goals'], players: players )
37
+
38
+
39
+ ##########
40
+ ## add penalty kicks / penalties
41
+ ## note - requires timeline !!!
42
+ if live['ResultType'] == 2 ## win on pens
43
+ if timeline.nil?
44
+ ##
45
+ team1_code = live['HomeTeam']['Abbreviation']
46
+ team2_code = live['AwayTeam']['Abbreviation']
47
+ puts "!! ERROR - no timeline for match #{team1_code} v #{team2_code} with win on penalties!!"
48
+ exit 1
49
+ else
50
+ ### get timeline with penalty shoot-out details
51
+ ## note - requires team1/2_ids!!!
52
+ team1_id = live['HomeTeam']['IdTeam']
53
+ team2_id = live['AwayTeam']['IdTeam']
54
+
55
+ pens = Penalty._build_ary( timeline['Event'],
56
+ players: players,
57
+ team1_id: team1_id,
58
+ team2_id: team2_id )
59
+ ## pp pens
60
+
61
+ o.penalties = pens
62
+ end
63
+ end
64
+
65
+
66
+ #######
67
+ ## add lineup/bench
68
+ o.formation1 = live['HomeTeam']['Tactics'] ## e.g. "Tactics": "3-1-4-2"
69
+ o.lineup1 = players1.lineup ## starter XI (only excludes bench)
70
+ o.bench1 = players1.bench ## substitutes
71
+
72
+ o.formation2 = live['AwayTeam']['Tactics']
73
+ o.lineup2 = players2.lineup
74
+ o.bench2 = players2.bench
75
+
76
+
77
+ #####
78
+ ## add bookings (yellow/red/red-yellow cards)
79
+ players1.add_bookings( live['HomeTeam']['Bookings'])
80
+ players2.add_bookings( live['AwayTeam']['Bookings'])
81
+
82
+
83
+
84
+ ##
85
+ ## maybe collect cards on demand (not ahead of time) - why? why not?
86
+ yellow1 = players1.yellowcards
87
+ red1 = players1.redcards
88
+ yellowred1 = players1.yellowredcards
89
+
90
+ o.yellow1 = yellow1 unless yellow1.empty?
91
+ o.red1 = red1 unless red1.empty?
92
+ o.yellowred1 = yellowred1 unless yellowred1.empty?
93
+
94
+ yellow2 = players2.yellowcards
95
+ red2 = players2.redcards
96
+ yellowred2 = players2.yellowredcards
97
+
98
+ o.yellow2 = yellow2 unless yellow2.empty?
99
+ o.red2 = red2 unless red2.empty?
100
+ o.yellowred2 = yellowred2 unless yellowred2.empty?
101
+
102
+
103
+ ## add substitutions (off/on - in/out)
104
+ o.subs1 = Sub._build_ary( live['HomeTeam']['Substitutions'], players: players1 )
105
+ o.subs2 = Sub._build_ary( live['AwayTeam']['Substitutions'], players: players2 )
106
+
107
+
108
+ ## add referees
109
+ officials = Official._build_ary( live['Officials'] )
110
+
111
+ if officials.empty?
112
+ puts "!! WARN no refs / officials found"
113
+ else
114
+ o.officials = officials
115
+ end
116
+
117
+ o
118
+ end
119
+
120
+
121
+
122
+ attr_accessor :goals1, :goals2,
123
+ :penalties,
124
+ :lineup1, :lineup2,
125
+ :bench1, :bench2,
126
+ :formation1, :formation2,
127
+ :yellow1, :yellow2,
128
+ :yellowred1, :yellowred2,
129
+ :red1, :red2,
130
+ :subs1, :subs2,
131
+ :officials # aka referees
132
+
133
+
134
+ def initialize
135
+ @goals1 = [], @goals2 = []
136
+ @penalties = nil ## or use [] - why? why not?
137
+
138
+ @lineup1 = [], @lineup2 = []
139
+ @bench1 = [], @bench2 = []
140
+
141
+ @formation1 = nil, @formation2 = nil
142
+
143
+ @yellow1 = []
144
+ @yellow2 = []
145
+ @yellowred1 = []
146
+ @yellowred2 = []
147
+ @red1 = []
148
+ @red2 = []
149
+
150
+ @subs1 = [], @subs2 = []
151
+
152
+ @officials = nil
153
+ end
154
+
155
+
156
+ def sentoff1
157
+ ## pp self
158
+ sentoff1 = (red1||[]) + (yellowred1||[])
159
+ ## pp red1
160
+ ## pp yellowred1
161
+ ## pp sentoff1
162
+ sentoff1.sort { |l,r| l.minute <=> r.minute } ## sort by minute
163
+ end
164
+ def sentoff2
165
+ sentoff2 = (red2||[]) + (yellowred2||[])
166
+ sentoff2.sort { |l,r| l.minute <=> r.minute } ## sort by minute
167
+ end
168
+
169
+
170
+
171
+ def as_json(*)
172
+ h = {}
173
+
174
+ if goals1 || goals2
175
+ h['goals1'] = (goals1 || []).as_json
176
+ h['goals2'] = (goals2 || []).as_json
177
+ end
178
+
179
+ h['penalities'] = penalties.as_json if penalties
180
+
181
+ h['formation1'] = formation1 if formation1
182
+ h['lineup1'] = lineup1.as_json
183
+ h['bench1'] = bench1.as_json
184
+
185
+ h['formation2'] = formation2 if formation2
186
+ h['lineup2'] = lineup2.as_json
187
+ h['bench2'] = bench2.as_json
188
+
189
+ h['yellow1'] = yellow1.as_json if yellow1 && !yellow1.empty?
190
+ h['yellow2'] = yellow2.as_json if yellow2 && !yellow2.empty?
191
+ h['yellowred1'] = yellowred1.as_json if yellowred1 && !yellowred1.empty?
192
+ h['yellowred2'] = yellowred2.as_json if yellowred2 && !yellowred2.empty?
193
+ h['red1'] = red1.as_json if red1 && !red1.empty?
194
+ h['red2'] = red2.as_json if red2 && !red2.empty?
195
+
196
+ h['subs1'] = subs1.as_json
197
+ h['subs2'] = subs2.as_json
198
+
199
+ h['referees'] = officials.as_json if officials
200
+
201
+ h
202
+ end
203
+
204
+
205
+ end ## class MatchReport