fifadat-convert 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 +27 -0
- data/README.md +24 -0
- data/Rakefile +30 -0
- data/bin/fifadat +16 -0
- data/lib/fifadat/convert/build_match.rb +105 -0
- data/lib/fifadat/convert/build_report.rb +135 -0
- data/lib/fifadat/convert/convert-reports.rb +114 -0
- data/lib/fifadat/convert/convert.rb +172 -0
- data/lib/fifadat/convert/errata.rb +60 -0
- data/lib/fifadat/convert/goals-calc_score.rb +58 -0
- data/lib/fifadat/convert/goals.rb +89 -0
- data/lib/fifadat/convert/helper-date.rb +13 -0
- data/lib/fifadat/convert/helper-minute.rb +81 -0
- data/lib/fifadat/convert/helper-score.rb +139 -0
- data/lib/fifadat/convert/norm.rb +34 -0
- data/lib/fifadat/convert/officials.rb +86 -0
- data/lib/fifadat/convert/penalties.rb +132 -0
- data/lib/fifadat/convert/players.rb +329 -0
- data/lib/fifadat/convert/stadiums.rb +99 -0
- data/lib/fifadat/convert/stages.rb +119 -0
- data/lib/fifadat/convert/substitutions.rb +465 -0
- data/lib/fifadat/convert/teams.rb +91 -0
- data/lib/fifadat/convert/tool.rb +184 -0
- data/lib/fifadat/convert/version.rb +22 -0
- data/lib/fifadat/convert.rb +37 -0
- data/lib/fifadata-convert.rb +5 -0
- metadata +123 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 98a8e2d8a9d493086e5b0758df77f1c372437961fadb6fdbf30a715b50316af7
|
|
4
|
+
data.tar.gz: 1efd005037d164e2ffdf71f677616d0427a558ee076654128d8e6d2047f27498
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1d01a876f48d6a96ddecc640d90daeb1b507ed88e6f513510bfdbba5a0154c05c3502fe5354b6129908e6f3547853897ab09661683c1d7c99d3440c42dec6d26
|
|
7
|
+
data.tar.gz: f98f022b43b2b0483fe66446a95fa64f9306a9d2be623759e8a2b93a0664c23f584e3900b340e0b4bb54bbeca7b42d0edb03762ce91903c25763a2906b9b7ced
|
data/CHANGELOG.md
ADDED
data/Manifest.txt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
CHANGELOG.md
|
|
2
|
+
Manifest.txt
|
|
3
|
+
README.md
|
|
4
|
+
Rakefile
|
|
5
|
+
bin/fifadat
|
|
6
|
+
lib/fifadat/convert.rb
|
|
7
|
+
lib/fifadat/convert/build_match.rb
|
|
8
|
+
lib/fifadat/convert/build_report.rb
|
|
9
|
+
lib/fifadat/convert/convert-reports.rb
|
|
10
|
+
lib/fifadat/convert/convert.rb
|
|
11
|
+
lib/fifadat/convert/errata.rb
|
|
12
|
+
lib/fifadat/convert/goals-calc_score.rb
|
|
13
|
+
lib/fifadat/convert/goals.rb
|
|
14
|
+
lib/fifadat/convert/helper-date.rb
|
|
15
|
+
lib/fifadat/convert/helper-minute.rb
|
|
16
|
+
lib/fifadat/convert/helper-score.rb
|
|
17
|
+
lib/fifadat/convert/norm.rb
|
|
18
|
+
lib/fifadat/convert/officials.rb
|
|
19
|
+
lib/fifadat/convert/penalties.rb
|
|
20
|
+
lib/fifadat/convert/players.rb
|
|
21
|
+
lib/fifadat/convert/stadiums.rb
|
|
22
|
+
lib/fifadat/convert/stages.rb
|
|
23
|
+
lib/fifadat/convert/substitutions.rb
|
|
24
|
+
lib/fifadat/convert/teams.rb
|
|
25
|
+
lib/fifadat/convert/tool.rb
|
|
26
|
+
lib/fifadat/convert/version.rb
|
|
27
|
+
lib/fifadata-convert.rb
|
data/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# fifadat-convert - convert fifa api json
|
|
2
|
+
|
|
3
|
+
* home :: [github.com/sportdb/sport.db](https://github.com/sportdb/sport.db)
|
|
4
|
+
* bugs :: [github.com/sportdb/sport.db/issues](https://github.com/sportdb/sport.db/issues)
|
|
5
|
+
* gem :: [rubygems.org/gems/fifadat-convert](https://rubygems.org/gems/fifadat-convert)
|
|
6
|
+
* rdoc :: [rubydoc.info/gems/fifadat-convert](http://rubydoc.info/gems/fifadat-convert)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
To be done
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## License
|
|
16
|
+
|
|
17
|
+
The `fifadat-convert` scripts are dedicated to the public domain.
|
|
18
|
+
Use as you please with no restrictions whatsoever.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Questions? Comments?
|
|
22
|
+
|
|
23
|
+
Yes, you can. More than welcome.
|
|
24
|
+
See [Help & Support »](https://github.com/openfootball/help)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'hoe'
|
|
2
|
+
require './lib/fifadat/convert/version.rb'
|
|
3
|
+
|
|
4
|
+
Hoe.spec 'fifadat-convert' do
|
|
5
|
+
|
|
6
|
+
self.version = FifadatConvert::VERSION
|
|
7
|
+
|
|
8
|
+
self.summary = "fifadat-convert - convert fifa api json"
|
|
9
|
+
self.description = summary
|
|
10
|
+
|
|
11
|
+
self.urls = { home: 'https://github.com/sportdb/sport.db' }
|
|
12
|
+
|
|
13
|
+
self.author = 'Gerald Bauer'
|
|
14
|
+
self.email = 'gerald.bauer@gmail.com'
|
|
15
|
+
|
|
16
|
+
# switch extension to .markdown for gihub formatting
|
|
17
|
+
self.readme_file = 'README.md'
|
|
18
|
+
self.history_file = 'CHANGELOG.md'
|
|
19
|
+
|
|
20
|
+
self.extra_deps = [
|
|
21
|
+
['fifadat'],
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
self.licenses = ['Public Domain']
|
|
25
|
+
|
|
26
|
+
self.spec_extras = {
|
|
27
|
+
required_ruby_version: '>= 3.1.0'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
end
|
data/bin/fifadat
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
## tip: to test run:
|
|
4
|
+
## $ ruby bin/fifadat
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
## note - always use latest (local) if present
|
|
9
|
+
$LOAD_PATH.unshift( '/sports/yorobot/fifadat/fifadat/lib' )
|
|
10
|
+
$LOAD_PATH.unshift( '/sports/yorobot/fifadat/fifadat-convert/lib' )
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
require 'fifadat/convert'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Fifadat.main( ARGV )
|
|
@@ -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,172 @@
|
|
|
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
|
+
|
|
75
|
+
###
|
|
76
|
+
## note - skip check for match events / goals etc.
|
|
77
|
+
## if not yet played!!!
|
|
78
|
+
|
|
79
|
+
if live && !(rec[:status] == 'TIMED' ||
|
|
80
|
+
rec[:status] == 'SCHED' || rec[:status] == 'SCHEDULED' ||
|
|
81
|
+
rec[:status] == 'LIVE')
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
## check for timeline
|
|
85
|
+
timeline = _read_timeline( m, timeline_dir: timeline_dir )
|
|
86
|
+
## reuse generated output from report
|
|
87
|
+
report = _build_report( live, timeline )
|
|
88
|
+
|
|
89
|
+
## try update of score via goals from (match) report
|
|
90
|
+
score_more = _build_report_score( live, timeline )
|
|
91
|
+
if score_more
|
|
92
|
+
rec[:score] = rec[:score].merge( score_more )
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
goals1 = report[:goals1]
|
|
98
|
+
goals2 = report[:goals2]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if goals1.empty? && goals2.empty?
|
|
102
|
+
## skip if no goals
|
|
103
|
+
else
|
|
104
|
+
rec[:goals1] = goals1
|
|
105
|
+
rec[:goals2] = goals2
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
#########
|
|
113
|
+
## add penalties
|
|
114
|
+
penalties = report[:penalties]
|
|
115
|
+
rec[:penalties] = penalties if penalties && !penalties.empty?
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
sentoff1 = (report[:red1]||[]) + (report[:yellowred1]||[])
|
|
119
|
+
sentoff2 = (report[:red2]||[]) + (report[:yellowred2]||[])
|
|
120
|
+
|
|
121
|
+
## sort by minute
|
|
122
|
+
sentoff1 = sentoff1.sort { |l,r| l[:minute] <=> r[:minute] }
|
|
123
|
+
sentoff2 = sentoff2.sort { |l,r| l[:minute] <=> r[:minute] }
|
|
124
|
+
|
|
125
|
+
rec[:sentoff1] = sentoff1 unless sentoff1.empty?
|
|
126
|
+
rec[:sentoff2] = sentoff2 unless sentoff2.empty?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
=begin
|
|
132
|
+
"Officials":
|
|
133
|
+
[{"IdCountry": "BRA",
|
|
134
|
+
"OfficialId": "361561",
|
|
135
|
+
"NameShort": [{"Locale": "en-GB", "Description": "Wilton SAMPAIO"}],
|
|
136
|
+
"Name": [{"Locale": "en-GB", "Description": "Wilton SAMPAIO"}],
|
|
137
|
+
"OfficialType": 1,
|
|
138
|
+
"TypeLocalized": [{"Locale": "en-GB", "Description": "Referee"}]},
|
|
139
|
+
{"IdCountry": "PAR",
|
|
140
|
+
"OfficialId": "416159",
|
|
141
|
+
"NameShort":
|
|
142
|
+
[{"Locale": "en-GB", "Description": "Juan Gabriel BENITEZ"}],
|
|
143
|
+
"Name": [{"Locale": "en-GB", "Description": "Juan Gabriel Benítez"}],
|
|
144
|
+
"OfficialType": 4,
|
|
145
|
+
"TypeLocalized":
|
|
146
|
+
[{"Locale": "en-GB", "Description": "Fourth official"}]}],
|
|
147
|
+
=end
|
|
148
|
+
|
|
149
|
+
###
|
|
150
|
+
## add referees
|
|
151
|
+
officials = build_officials( m['Officials'], id: false )
|
|
152
|
+
|
|
153
|
+
if officials.empty?
|
|
154
|
+
## puts "!! WARN no refs / officials found"
|
|
155
|
+
else
|
|
156
|
+
rec[:referees] = officials
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
recs << rec
|
|
163
|
+
end
|
|
164
|
+
data[:matches] = recs
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
outpath = "#{outdir}/#{season.to_path}/#{slug}.json"
|
|
168
|
+
write_json( outpath, data)
|
|
169
|
+
puts " written to >#{outpath}<"
|
|
170
|
+
|
|
171
|
+
true
|
|
172
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
###
|
|
2
|
+
# known data gotchas/errors
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# worldcup 2026 - USA v Belgium
|
|
7
|
+
# -- goal (period, minute)
|
|
8
|
+
|
|
9
|
+
=begin
|
|
10
|
+
in USA v Belgium
|
|
11
|
+
"Type"=>2,
|
|
12
|
+
"IdPlayer"=>"448362",
|
|
13
|
+
"Minute"=>"1'",
|
|
14
|
+
"IdAssistPlayer"=>nil,
|
|
15
|
+
"Period"=>2,
|
|
16
|
+
"IdGoal"=>nil,
|
|
17
|
+
"IdTeam"=>"43935"}
|
|
18
|
+
|
|
19
|
+
{ "Type": 2,
|
|
20
|
+
"IdPlayer": "448362",
|
|
21
|
+
"Minute": "0'",
|
|
22
|
+
"IdAssistPlayer": null,
|
|
23
|
+
"Period": 2, ## change to 3!!!!
|
|
24
|
+
"IdGoal": null,
|
|
25
|
+
"IdTeam": "43935"},
|
|
26
|
+
|
|
27
|
+
=end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def errata_autofix_goal( h )
|
|
31
|
+
if h['Period'] == 2 &&
|
|
32
|
+
h['IdPlayer'] == '448362' && ##
|
|
33
|
+
h['IdTeam'] == '43935' ## Belgium
|
|
34
|
+
puts "-- ERRATA - autofix goal USA v Belgium / Worldcup"
|
|
35
|
+
pp h
|
|
36
|
+
|
|
37
|
+
h['Period'] = 3 ## 1ST_HALF
|
|
38
|
+
h['Minute'] = "9'"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
h
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# clubworldcup
|
|
47
|
+
# -- score (ResultType)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def errata_autofix_score( m )
|
|
51
|
+
## pachuca vs salzburg in cwc 2025??
|
|
52
|
+
if m['IdMatch'] == '400019191' && ## fix for pachuca vs salzburg
|
|
53
|
+
m['ResultType'] == 0
|
|
54
|
+
m['ResultType'] == 1 ## regular (or use 4-REG/AGG - why? why not?)
|
|
55
|
+
|
|
56
|
+
puts "-- ERRATA - autofix score pachuca vs salzburg / Club Worldcup"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
m
|
|
60
|
+
end
|