fifadat 0.0.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/CHANGELOG.md +4 -0
- data/Manifest.txt +91 -0
- data/README.md +24 -0
- data/Rakefile +32 -0
- data/bin/fifadat +14 -0
- data/lib/fifadat/api-config.rb +120 -0
- data/lib/fifadat/api.rb +110 -0
- data/lib/fifadat/config/codes_club-europe.csv +59 -0
- data/lib/fifadat/config/codes_club.csv +41 -0
- data/lib/fifadat/config/codes_nati.csv +6 -0
- data/lib/fifadat/config/seasons/ar.csv +7 -0
- data/lib/fifadat/config/seasons/at.csv +7 -0
- data/lib/fifadat/config/seasons/at.cup.csv +5 -0
- data/lib/fifadat/config/seasons/au.csv +7 -0
- data/lib/fifadat/config/seasons/be.csv +7 -0
- data/lib/fifadat/config/seasons/br.csv +6 -0
- data/lib/fifadat/config/seasons/ch.csv +7 -0
- data/lib/fifadat/config/seasons/clubworldcup.csv +2 -0
- data/lib/fifadat/config/seasons/cn.csv +7 -0
- data/lib/fifadat/config/seasons/co.csv +7 -0
- data/lib/fifadat/config/seasons/copa.l.csv +7 -0
- data/lib/fifadat/config/seasons/copa.s.csv +7 -0
- data/lib/fifadat/config/seasons/cz.csv +7 -0
- data/lib/fifadat/config/seasons/de.2.csv +6 -0
- data/lib/fifadat/config/seasons/de.csv +7 -0
- data/lib/fifadat/config/seasons/de.cup.csv +6 -0
- data/lib/fifadat/config/seasons/eg.csv +6 -0
- data/lib/fifadat/config/seasons/eng.2.csv +7 -0
- data/lib/fifadat/config/seasons/eng.csv +7 -0
- data/lib/fifadat/config/seasons/eng.facup.csv +6 -0
- data/lib/fifadat/config/seasons/es.csv +6 -0
- data/lib/fifadat/config/seasons/fr.csv +6 -0
- data/lib/fifadat/config/seasons/gr.csv +7 -0
- data/lib/fifadat/config/seasons/hu.csv +6 -0
- data/lib/fifadat/config/seasons/ie.csv +6 -0
- data/lib/fifadat/config/seasons/il.csv +6 -0
- data/lib/fifadat/config/seasons/interconticup.csv +24 -0
- data/lib/fifadat/config/seasons/is.csv +6 -0
- data/lib/fifadat/config/seasons/it.csv +7 -0
- data/lib/fifadat/config/seasons/it.cup.csv +5 -0
- data/lib/fifadat/config/seasons/jp.csv +7 -0
- data/lib/fifadat/config/seasons/kr.csv +7 -0
- data/lib/fifadat/config/seasons/lu.csv +3 -0
- data/lib/fifadat/config/seasons/ma.csv +3 -0
- data/lib/fifadat/config/seasons/mls.csv +6 -0
- data/lib/fifadat/config/seasons/mx.2.csv +5 -0
- data/lib/fifadat/config/seasons/mx.csv +6 -0
- data/lib/fifadat/config/seasons/mx.cup.csv +3 -0
- data/lib/fifadat/config/seasons/nir.csv +6 -0
- data/lib/fifadat/config/seasons/nl.csv +7 -0
- data/lib/fifadat/config/seasons/no.csv +6 -0
- data/lib/fifadat/config/seasons/pl.csv +6 -0
- data/lib/fifadat/config/seasons/pt.csv +7 -0
- data/lib/fifadat/config/seasons/sa.csv +7 -0
- data/lib/fifadat/config/seasons/sco.csv +7 -0
- data/lib/fifadat/config/seasons/se.csv +7 -0
- data/lib/fifadat/config/seasons/tn.csv +6 -0
- data/lib/fifadat/config/seasons/tr.csv +7 -0
- data/lib/fifadat/config/seasons/uefa.cl.csv +7 -0
- data/lib/fifadat/config/seasons/uefa.con.csv +6 -0
- data/lib/fifadat/config/seasons/uefa.europa.csv +6 -0
- data/lib/fifadat/config/seasons/uy.csv +6 -0
- data/lib/fifadat/config/seasons/wal.csv +6 -0
- data/lib/fifadat/config/seasons/worldcup.csv +24 -0
- data/lib/fifadat/config/seasons/za.csv +6 -0
- data/lib/fifadat/errata.rb +59 -0
- data/lib/fifadat/helper.rb +46 -0
- data/lib/fifadat/json.rb +113 -0
- data/lib/fifadat/pp/build_match.rb +105 -0
- data/lib/fifadat/pp/build_report.rb +135 -0
- data/lib/fifadat/pp/convert-reports.rb +114 -0
- data/lib/fifadat/pp/convert.rb +163 -0
- data/lib/fifadat/pp/goals-calc_score.rb +58 -0
- data/lib/fifadat/pp/goals.rb +89 -0
- data/lib/fifadat/pp/helper-date.rb +13 -0
- data/lib/fifadat/pp/helper-minute.rb +81 -0
- data/lib/fifadat/pp/helper-score.rb +139 -0
- data/lib/fifadat/pp/norm.rb +34 -0
- data/lib/fifadat/pp/officials.rb +86 -0
- data/lib/fifadat/pp/penalties.rb +132 -0
- data/lib/fifadat/pp/players.rb +323 -0
- data/lib/fifadat/pp/stadiums.rb +99 -0
- data/lib/fifadat/pp/stages.rb +119 -0
- data/lib/fifadat/pp/substitutions.rb +444 -0
- data/lib/fifadat/pp/teams.rb +91 -0
- data/lib/fifadat/ppdebug.rb +128 -0
- data/lib/fifadat/ppmatch.rb +78 -0
- data/lib/fifadat/prepare.rb +117 -0
- data/lib/fifadat/tool.rb +184 -0
- data/lib/fifadat/types.rb +166 -0
- data/lib/fifadat.rb +54 -0
- metadata +215 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _build_match( m ) ## use _fill/build_match_basics() or such ???
|
|
5
|
+
rec = {}
|
|
6
|
+
|
|
7
|
+
## 0 => FINISHED/complete (OK)
|
|
8
|
+
## 1 => SCHEDULED/not yet played
|
|
9
|
+
status = m['MatchStatus']
|
|
10
|
+
timed = m['TimeDefined']
|
|
11
|
+
|
|
12
|
+
if status == 1 && timed ## note: use TIMED (instead of SCHEDULED)
|
|
13
|
+
rec[:status] = 'TIMED'
|
|
14
|
+
else
|
|
15
|
+
rec[:status] = MATCH_STATUS[ status ] || "#{status}-???"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
rec[:stage] = desc( m['StageName'] )
|
|
21
|
+
|
|
22
|
+
## check optional group, matchday, (match) num
|
|
23
|
+
## note - matchday is a string!! (check if always a number)
|
|
24
|
+
## ## convert to int if str - why? why not??
|
|
25
|
+
group = desc( m['GroupName'] )
|
|
26
|
+
rec[:group] = group if group
|
|
27
|
+
|
|
28
|
+
matchday = m['MatchDay']
|
|
29
|
+
rec[:matchday] = matchday.to_i(10) if matchday
|
|
30
|
+
|
|
31
|
+
num = m['MatchNumber']
|
|
32
|
+
rec[:num] = num if num
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
dateTime = parse_date_utc( m['Date'] )
|
|
36
|
+
localDateTime = parse_date_utc( m['LocalDate'] )
|
|
37
|
+
|
|
38
|
+
if timed
|
|
39
|
+
## pass along as is for now - why? why not
|
|
40
|
+
## note - Date is date_utc and
|
|
41
|
+
## LocalDate is date_local (also in utc BUT different HH:MM or possible day)
|
|
42
|
+
## note - also in utc, that is, timezone is Z (not +03:00 or such!!!)
|
|
43
|
+
|
|
44
|
+
## note - use datetime - why? why not?
|
|
45
|
+
##
|
|
46
|
+
## note - cut-off/remove seconds
|
|
47
|
+
rec[:datetime_utc] = dateTime.strftime( '%Y-%m-%dT%H:%MZ' )
|
|
48
|
+
|
|
49
|
+
## note - use 20:30 UTC+1 or 20:30 UTC-3 for timezone format for now
|
|
50
|
+
rec[:datetime_local] = _fmt_date_local( dateTime, localDateTime )
|
|
51
|
+
else
|
|
52
|
+
rec[:date] = dateTime.strftime( '%Y-%m-%d' )
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if m['HomeTeam'] && m['AwayTeam'] ## assume match reports
|
|
58
|
+
team1 = build_team( m['HomeTeam'] )
|
|
59
|
+
team2 = build_team( m['AwayTeam'] )
|
|
60
|
+
## use full team details here (via lookup)
|
|
61
|
+
## when report (mode) - why? why not?
|
|
62
|
+
rec[:team1] = team1[:name]
|
|
63
|
+
rec[:team2] = team2[:name]
|
|
64
|
+
else
|
|
65
|
+
team1 = build_team( m['Home'] )
|
|
66
|
+
team2 = build_team( m['Away'] )
|
|
67
|
+
rec[:team1] = team1[:name]
|
|
68
|
+
rec[:team2] = team2[:name]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
#####
|
|
74
|
+
# handle score
|
|
75
|
+
## m = (full) match hash incl. IdMatch, etc.
|
|
76
|
+
## returns string e.g. 4-4 or 4-3 a.e.t etc
|
|
77
|
+
|
|
78
|
+
score = _parse_score( m )
|
|
79
|
+
rec[:score] = score unless score.empty?
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
## check - never in use?
|
|
83
|
+
rec[:home] = true if m['IsHome']
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
## add country to stadium too - why? why not?
|
|
87
|
+
stadium = build_stadium( m['Stadium'] )
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if m['HomeTeam'] && m['AwayTeam'] ## assume match reports
|
|
91
|
+
rec[:stadium] = { name: stadium[:name],
|
|
92
|
+
city: stadium[:city],
|
|
93
|
+
street: stadium[:street],
|
|
94
|
+
country: stadium[:country] }
|
|
95
|
+
else
|
|
96
|
+
rec[:stadium] = { name: stadium[:name],
|
|
97
|
+
city: stadium[:city] }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
attendance = m['Attendance']
|
|
101
|
+
rec[:attendance] = attendance.to_i(10) if attendance
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
rec
|
|
105
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
def _build_report_score( live, timeline=nil )
|
|
4
|
+
|
|
5
|
+
_scores = _build_score_from_goals( live )
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
score = nil
|
|
9
|
+
|
|
10
|
+
case live['ResultType']
|
|
11
|
+
when 1,4
|
|
12
|
+
## 1 => 'REGULAR',
|
|
13
|
+
## 4 => 'REGULAR/AGG', ## aggregate (1st/2nd leg) - regular
|
|
14
|
+
## add ht
|
|
15
|
+
## assert ft match
|
|
16
|
+
score = { ht: _scores[:ht][:score],
|
|
17
|
+
ft: _scores[:ft][:score]
|
|
18
|
+
}
|
|
19
|
+
when 3,5,8
|
|
20
|
+
score = { ht: _scores[:ht][:score],
|
|
21
|
+
ft: _scores[:ft][:score],
|
|
22
|
+
et: _scores[:et][:score],
|
|
23
|
+
}
|
|
24
|
+
when 2
|
|
25
|
+
## todo/fix - pull in timeline check too to check
|
|
26
|
+
## if any events recorded for extra time!!
|
|
27
|
+
score = { ht: _scores[:ht][:score],
|
|
28
|
+
ft: _scores[:ft][:score],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
## note - only incl. extra-time (et) if a goal scored in extra time
|
|
32
|
+
score[:et] = _scores[:et][:score] unless _scores[:et][:minutes].empty?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
score
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _build_report( live, timeline=nil )
|
|
41
|
+
rec = {}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
####
|
|
45
|
+
## add goals
|
|
46
|
+
players = Players.new
|
|
47
|
+
players.add( live['HomeTeam']['Players'] )
|
|
48
|
+
players.add( live['AwayTeam']['Players'] )
|
|
49
|
+
|
|
50
|
+
rec[:goals1] = build_goals( live['HomeTeam']['Goals'], players: players )
|
|
51
|
+
rec[:goals2] = build_goals( live['AwayTeam']['Goals'], players: players )
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
##########
|
|
55
|
+
## add penalty kicks / penalties
|
|
56
|
+
if live['ResultType'] == 2 ## aet, win on pens
|
|
57
|
+
if timeline.nil?
|
|
58
|
+
##
|
|
59
|
+
team1_code = live['HomeTeam']['Abbreviation']
|
|
60
|
+
team2_code = live['AwayTeam']['Abbreviation']
|
|
61
|
+
puts "!! ERROR - no timeline for match #{team1_code} v #{team2_code} with win on penalties!!"
|
|
62
|
+
exit 1
|
|
63
|
+
else
|
|
64
|
+
### get timeline with penalty shoot-out details
|
|
65
|
+
## note - requires team1/2_ids!!!
|
|
66
|
+
team1_id = live['HomeTeam']['IdTeam']
|
|
67
|
+
team2_id = live['AwayTeam']['IdTeam']
|
|
68
|
+
|
|
69
|
+
pens = build_penalties( timeline['Event'],
|
|
70
|
+
players: players,
|
|
71
|
+
team1_id: team1_id,
|
|
72
|
+
team2_id: team2_id )
|
|
73
|
+
pp pens
|
|
74
|
+
|
|
75
|
+
rec[:penalties] = pens
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
## add players by team1/team2
|
|
82
|
+
players1 = Players.new
|
|
83
|
+
players1.add( live['HomeTeam']['Players'] )
|
|
84
|
+
|
|
85
|
+
players2 = Players.new
|
|
86
|
+
players2.add( live['AwayTeam']['Players'] )
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
rec[:formation1] = live['HomeTeam']['Tactics'] ## e.g. "Tactics": "3-1-4-2"
|
|
90
|
+
rec[:lineup1] = players1.lineup ## starter XI
|
|
91
|
+
rec[:bench1] = players1.bench ## substitutes
|
|
92
|
+
|
|
93
|
+
rec[:formation2] = live['AwayTeam']['Tactics']
|
|
94
|
+
rec[:lineup2] = players2.lineup
|
|
95
|
+
rec[:bench2] = players2.bench
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
#####
|
|
99
|
+
## add bookings (yellow/red/red-yellow cards)
|
|
100
|
+
players1.add_bookings( live['HomeTeam']['Bookings'])
|
|
101
|
+
players2.add_bookings( live['AwayTeam']['Bookings'])
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
yellow1 = players1.yellowcards
|
|
105
|
+
red1 = players1.redcards
|
|
106
|
+
yellowred1 = players1.yellowredcards
|
|
107
|
+
rec[:yellow1] = yellow1 unless yellow1.empty?
|
|
108
|
+
rec[:red1] = red1 unless red1.empty?
|
|
109
|
+
rec[:yellowred1] = yellowred1 unless yellowred1.empty?
|
|
110
|
+
|
|
111
|
+
yellow2 = players2.yellowcards
|
|
112
|
+
red2 = players2.redcards
|
|
113
|
+
yellowred2 = players2.yellowredcards
|
|
114
|
+
rec[:yellow2] = yellow2 unless yellow2.empty?
|
|
115
|
+
rec[:red2] = red2 unless red2.empty?
|
|
116
|
+
rec[:yellowred2] = yellowred2 unless yellowred2.empty?
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
## add substitutions (off/on - in/out)
|
|
121
|
+
rec[:subs1] = build_subs( live['HomeTeam']['Substitutions'], players: players1 )
|
|
122
|
+
rec[:subs2] = build_subs( live['AwayTeam']['Substitutions'], players: players2 )
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
## add referees
|
|
126
|
+
officials = build_officials( live['Officials'], id: false )
|
|
127
|
+
|
|
128
|
+
if officials.empty?
|
|
129
|
+
## puts "!! WARN no refs / officials found"
|
|
130
|
+
else
|
|
131
|
+
rec[:referees] = officials
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
rec
|
|
135
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
## read & build match report
|
|
5
|
+
|
|
6
|
+
## use mk/build_report_basename - why? why not?
|
|
7
|
+
## fix-fix-fix - change to _match_basename !!!
|
|
8
|
+
## use by (match)report and (match)timeline !!!
|
|
9
|
+
def _report_basename( m )
|
|
10
|
+
### get match (live) details
|
|
11
|
+
team1_code = m['Home']['Abbreviation']
|
|
12
|
+
team2_code = m['Away']['Abbreviation']
|
|
13
|
+
localDateTime = parse_date_utc( m['LocalDate'] )
|
|
14
|
+
|
|
15
|
+
## e.g. 2020-06-24_RAP-AUS__54545454
|
|
16
|
+
basename = String.new
|
|
17
|
+
basename += localDateTime.strftime('%Y-%m-%d') ## i) date
|
|
18
|
+
basename += "_#{team1_code}-#{team2_code}" ## ii) team1 v team2
|
|
19
|
+
|
|
20
|
+
basename
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _read_timeline( m, timeline_dir: )
|
|
26
|
+
return nil if m['Home'].nil? || m['Away'].nil?
|
|
27
|
+
|
|
28
|
+
basename = _report_basename( m )
|
|
29
|
+
basename += "__#{m['IdMatch']}"
|
|
30
|
+
|
|
31
|
+
path = "#{timeline_dir}/#{basename}.json"
|
|
32
|
+
|
|
33
|
+
## note - skip if no match report available!!!
|
|
34
|
+
if File.file?( path )
|
|
35
|
+
timeline = read_json_v2( path )
|
|
36
|
+
timeline
|
|
37
|
+
else
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def _read_report( m, report_dir: )
|
|
43
|
+
return nil if m['Home'].nil? || m['Away'].nil?
|
|
44
|
+
|
|
45
|
+
## e.g. 2020-06-24_RAP-AUS__54545454
|
|
46
|
+
# note - add match id for "raw" match reports!!!
|
|
47
|
+
basename = _report_basename( m )
|
|
48
|
+
basename += "__#{m['IdMatch']}"
|
|
49
|
+
|
|
50
|
+
## #{indir}/#{slug}/matches/#{season.to_path}
|
|
51
|
+
path = "#{report_dir}/#{basename}.json"
|
|
52
|
+
|
|
53
|
+
## note - skip if no match report available!!!
|
|
54
|
+
if File.file?( path )
|
|
55
|
+
live = read_json_v2( path )
|
|
56
|
+
live
|
|
57
|
+
else
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def convert_reports( slug:, season:,
|
|
68
|
+
indir: '.',
|
|
69
|
+
outdir: './tmp' )
|
|
70
|
+
|
|
71
|
+
season = Season(season)
|
|
72
|
+
|
|
73
|
+
data = read_json_v2( "#{indir}/#{slug}/#{season.to_path}_matches.json" )
|
|
74
|
+
matches = data['Results'] ## only use results (match) array
|
|
75
|
+
puts " #{matches.size} match(es) in season #{season}"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
report_dir = "#{indir}/#{slug}/matches/#{season.to_path}"
|
|
79
|
+
timeline_dir = "#{indir}/#{slug}/timelines/#{season.to_path}"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
matches.each_with_index do |m, i|
|
|
84
|
+
live = _read_report( m, report_dir: report_dir )
|
|
85
|
+
|
|
86
|
+
## no report found; continue
|
|
87
|
+
next if live.nil?
|
|
88
|
+
|
|
89
|
+
## check for optional timeline
|
|
90
|
+
timeline = _read_timeline( m, timeline_dir: timeline_dir )
|
|
91
|
+
|
|
92
|
+
rec = {
|
|
93
|
+
meta: {
|
|
94
|
+
name: desc(live['SeasonName']),
|
|
95
|
+
slug: slug,
|
|
96
|
+
season: season.to_s,
|
|
97
|
+
generated: Time.now.to_s,
|
|
98
|
+
}
|
|
99
|
+
}.merge( _build_match( live ),
|
|
100
|
+
_build_report( live, timeline ))
|
|
101
|
+
|
|
102
|
+
## try update of score via goals from report
|
|
103
|
+
score_more = _build_report_score( live, timeline )
|
|
104
|
+
if score_more
|
|
105
|
+
rec[:score] = rec[:score].merge( score_more )
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
## build basename e.g 2026-07-15_ARG-ENG
|
|
110
|
+
basename = _report_basename( m )
|
|
111
|
+
outpath = "#{outdir}/#{season.to_path}/#{slug}/#{basename}.json"
|
|
112
|
+
write_json( outpath, rec )
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def convert( slug:, season:,
|
|
5
|
+
indir: '.',
|
|
6
|
+
outdir: './tmp' )
|
|
7
|
+
|
|
8
|
+
season = Season(season)
|
|
9
|
+
|
|
10
|
+
## fix - change data to top or ??? - why? why not?
|
|
11
|
+
data = {}
|
|
12
|
+
|
|
13
|
+
## add slug & seasons (add name to be done!!)
|
|
14
|
+
data[:meta] = { slug: slug,
|
|
15
|
+
season: season.to_s,
|
|
16
|
+
generated: Time.now.to_s,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
matches = read_json_v2( "#{indir}/#{slug}/#{season.to_path}_matches.json" )
|
|
21
|
+
matches = matches['Results'] ## only use results (match) array
|
|
22
|
+
|
|
23
|
+
## pp matches
|
|
24
|
+
puts " #{matches.size} match(es) in season #{season}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## read in stages
|
|
28
|
+
## incl. SequenceOrder, StageLevel (optional)
|
|
29
|
+
stages = Stages.read( "#{indir}/#{slug}/misc/#{season.to_path}_stages.json" )
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
stages.add_matches( matches )
|
|
33
|
+
data[:stages] = stages.as_json
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
teams = Teams.new
|
|
37
|
+
teams.add_matches( matches )
|
|
38
|
+
data[:teams] = teams.as_json
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
stadiums = Stadiums.new
|
|
42
|
+
stadiums.add_matches( matches )
|
|
43
|
+
data[:stadiums] = stadiums.as_json
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## for match-by-match live reports
|
|
47
|
+
report_dir = "#{indir}/#{slug}/matches/#{season.to_path}"
|
|
48
|
+
timeline_dir = "#{indir}/#{slug}/timelines/#{season.to_path}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
recs = []
|
|
52
|
+
matches.each_with_index do |m, i|
|
|
53
|
+
|
|
54
|
+
## add/track id too - why? why not?
|
|
55
|
+
## rec[:id] = m['IdMatch']
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## add/fill-up match basic
|
|
59
|
+
rec = _build_match( m )
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
### get match (live) details
|
|
63
|
+
###
|
|
64
|
+
## check if match report exits
|
|
65
|
+
## optional for now!!
|
|
66
|
+
|
|
67
|
+
live = _read_report( m, report_dir: report_dir )
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## if live.nil?
|
|
71
|
+
## puts "warn no match report for #{_report_basename(m)}"
|
|
72
|
+
## end
|
|
73
|
+
|
|
74
|
+
if live
|
|
75
|
+
## check for timeline
|
|
76
|
+
timeline = _read_timeline( m, timeline_dir: timeline_dir )
|
|
77
|
+
## reuse generated output from report
|
|
78
|
+
report = _build_report( live, timeline )
|
|
79
|
+
|
|
80
|
+
## try update of score via goals from (match) report
|
|
81
|
+
score_more = _build_report_score( live, timeline )
|
|
82
|
+
if score_more
|
|
83
|
+
rec[:score] = rec[:score].merge( score_more )
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
goals1 = report[:goals1]
|
|
89
|
+
goals2 = report[:goals2]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if goals1.empty? && goals2.empty?
|
|
93
|
+
## skip if no goals
|
|
94
|
+
else
|
|
95
|
+
rec[:goals1] = goals1
|
|
96
|
+
rec[:goals2] = goals2
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
#########
|
|
104
|
+
## add penalties
|
|
105
|
+
penalties = report[:penalties]
|
|
106
|
+
rec[:penalties] = penalties if penalties && !penalties.empty?
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
sentoff1 = (report[:red1]||[]) + (report[:yellowred1]||[])
|
|
110
|
+
sentoff2 = (report[:red2]||[]) + (report[:yellowred2]||[])
|
|
111
|
+
|
|
112
|
+
## sort by minute
|
|
113
|
+
sentoff1 = sentoff1.sort { |l,r| l[:minute] <=> r[:minute] }
|
|
114
|
+
sentoff2 = sentoff2.sort { |l,r| l[:minute] <=> r[:minute] }
|
|
115
|
+
|
|
116
|
+
rec[:sentoff1] = sentoff1 unless sentoff1.empty?
|
|
117
|
+
rec[:sentoff2] = sentoff2 unless sentoff2.empty?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
=begin
|
|
123
|
+
"Officials":
|
|
124
|
+
[{"IdCountry": "BRA",
|
|
125
|
+
"OfficialId": "361561",
|
|
126
|
+
"NameShort": [{"Locale": "en-GB", "Description": "Wilton SAMPAIO"}],
|
|
127
|
+
"Name": [{"Locale": "en-GB", "Description": "Wilton SAMPAIO"}],
|
|
128
|
+
"OfficialType": 1,
|
|
129
|
+
"TypeLocalized": [{"Locale": "en-GB", "Description": "Referee"}]},
|
|
130
|
+
{"IdCountry": "PAR",
|
|
131
|
+
"OfficialId": "416159",
|
|
132
|
+
"NameShort":
|
|
133
|
+
[{"Locale": "en-GB", "Description": "Juan Gabriel BENITEZ"}],
|
|
134
|
+
"Name": [{"Locale": "en-GB", "Description": "Juan Gabriel Benítez"}],
|
|
135
|
+
"OfficialType": 4,
|
|
136
|
+
"TypeLocalized":
|
|
137
|
+
[{"Locale": "en-GB", "Description": "Fourth official"}]}],
|
|
138
|
+
=end
|
|
139
|
+
|
|
140
|
+
###
|
|
141
|
+
## add referees
|
|
142
|
+
officials = build_officials( m['Officials'], id: false )
|
|
143
|
+
|
|
144
|
+
if officials.empty?
|
|
145
|
+
## puts "!! WARN no refs / officials found"
|
|
146
|
+
else
|
|
147
|
+
rec[:referees] = officials
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
recs << rec
|
|
154
|
+
end
|
|
155
|
+
data[:matches] = recs
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
outpath = "#{outdir}/#{season.to_path}/#{slug}.json"
|
|
159
|
+
write_json( outpath, data)
|
|
160
|
+
puts " written to >#{outpath}<"
|
|
161
|
+
|
|
162
|
+
true
|
|
163
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
def _build_score_from_goals( m )
|
|
4
|
+
##
|
|
5
|
+
## 5 periods - 3 (1ST_HALF), 5 (2ND_HALF),
|
|
6
|
+
## 7 (EXTRA_TIME_1ST_HALF), 9 (EXTRA_TIME_2ND_HALF),
|
|
7
|
+
## 11 (PENALTY_SHOOTOUT) possibly
|
|
8
|
+
##
|
|
9
|
+
goals = {
|
|
10
|
+
count: 0,
|
|
11
|
+
score: [0,0],
|
|
12
|
+
ht: { score: [0,0], minutes: [] },
|
|
13
|
+
ft: { score: [0,0], minutes: [] },
|
|
14
|
+
et: { score: [0,0], minutes: [] },
|
|
15
|
+
p: { score: [0,0], minutes: [] },
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
## note - quick fix
|
|
19
|
+
## add period 2 for USA v Belgium (PKO) - worldcup
|
|
20
|
+
|
|
21
|
+
## note - i is 0|1 - array index for team
|
|
22
|
+
[m['HomeTeam']['Goals'],
|
|
23
|
+
m['AwayTeam']['Goals']].each_with_index do |recs,i|
|
|
24
|
+
recs.each do |h|
|
|
25
|
+
|
|
26
|
+
h = errata_autofix_goal( h )
|
|
27
|
+
|
|
28
|
+
period = h['Period']
|
|
29
|
+
## include penalty shootout (11) - why? why not?
|
|
30
|
+
assert( [3,5,7,9,11].include?(period),
|
|
31
|
+
"goal in period 3/5/7/9/11 expected; got #{h.pretty_inspect} in match #{m.pretty_inspect}" )
|
|
32
|
+
|
|
33
|
+
minute = h['Minute']
|
|
34
|
+
key = case period
|
|
35
|
+
when 3 then :ht
|
|
36
|
+
when 5 then :ft
|
|
37
|
+
when 7, 9 then :et
|
|
38
|
+
when 11 then :p
|
|
39
|
+
else
|
|
40
|
+
raise ArgumentError,
|
|
41
|
+
"goal in period 3/5/7/9/11 expected; got #{h.pretty_inspect}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
goals[:count] += 1
|
|
45
|
+
goals[:score][i] += 1 unless key == :p
|
|
46
|
+
goals[key][:score][i] += 1
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
## use/calc cummulative score (BUT not for penalties)
|
|
51
|
+
goals[:ft][:score][0] += goals[:ht][:score][0]
|
|
52
|
+
goals[:ft][:score][1] += goals[:ht][:score][1]
|
|
53
|
+
|
|
54
|
+
goals[:et][:score][0] += goals[:ft][:score][0]
|
|
55
|
+
goals[:et][:score][1] += goals[:ft][:score][1]
|
|
56
|
+
|
|
57
|
+
goals
|
|
58
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def build_goal( h, players: )
|
|
6
|
+
|
|
7
|
+
## split into minute
|
|
8
|
+
## and offset (stoppage/injury time)
|
|
9
|
+
## e.g. 90'+11'
|
|
10
|
+
|
|
11
|
+
h = errata_autofix_goal( h )
|
|
12
|
+
|
|
13
|
+
minute_str = h['Minute']
|
|
14
|
+
|
|
15
|
+
period = h['Period']
|
|
16
|
+
|
|
17
|
+
## note - penalty shootout gets excluded upstream!!
|
|
18
|
+
## include penalty shootout (11) - why? why not?
|
|
19
|
+
## assert( [3,5,7,9].include?(period),
|
|
20
|
+
## "goal in period 3/5/7/9 expected; got #{h.pretty_inspect}" )
|
|
21
|
+
|
|
22
|
+
## quick fix:
|
|
23
|
+
## check for weird minute 0 e.g.
|
|
24
|
+
## Germany-Austria 1934
|
|
25
|
+
if minute_str == "0'"
|
|
26
|
+
h['Minute'] = "1'"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if minute_str.nil? || minute_str.empty?
|
|
30
|
+
puts "!! minute in goal is nil or empty:"
|
|
31
|
+
pp h
|
|
32
|
+
exit 1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
minute = _build_minute( h )
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
rec = {}
|
|
39
|
+
|
|
40
|
+
idPlayer = h['IdPlayer']
|
|
41
|
+
|
|
42
|
+
if idPlayer.nil?
|
|
43
|
+
puts "!! no idPlayer for goal!"
|
|
44
|
+
pp h
|
|
45
|
+
##exit 1
|
|
46
|
+
## use 'N.N.'
|
|
47
|
+
|
|
48
|
+
rec[ :name ] = 'N.N.'
|
|
49
|
+
|
|
50
|
+
else
|
|
51
|
+
player = players.find!( idPlayer )
|
|
52
|
+
rec[ :name ] = player[ :name ]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
rec[ :minute] = minute
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## check for goal type (og) or (p)
|
|
59
|
+
## 1 - "penalty"
|
|
60
|
+
## 2 - "regular"
|
|
61
|
+
## 3 - "own goal"
|
|
62
|
+
|
|
63
|
+
type = h['Type']
|
|
64
|
+
rec[ :pen ] = true if type == 1
|
|
65
|
+
rec[ :og ] = true if type == 3
|
|
66
|
+
|
|
67
|
+
rec
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def build_goals( recs, players:, penalties: false )
|
|
74
|
+
|
|
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
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
recs = recs.map { |h| build_goal( h, players: players ) }
|
|
85
|
+
|
|
86
|
+
## note - sort by minutes; goals may not be sorted
|
|
87
|
+
recs = recs.sort { |l,r| l[:minute] <=> r[:minute] }
|
|
88
|
+
recs
|
|
89
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
def _fmt_date_local( date_utc, date_local )
|
|
4
|
+
|
|
5
|
+
## note: returns Rational (e.g. 3/1 or 1/4 etc.) use to_f/to_i to convert
|
|
6
|
+
diff_in_hours = ((date_local - date_utc) * 24).to_f
|
|
7
|
+
diff_in_days = date_local.jd - date_utc.jd
|
|
8
|
+
## pp [diff_in_hours, diff_in_days]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## use 20:30 UTC+1 or 20:30 UTC-3
|
|
12
|
+
"#{date_local.strftime( '%Y-%m-%d %H:%M' )} UTC%+d" % diff_in_hours
|
|
13
|
+
end
|