fifadat 2026.9.23 → 2026.9.30

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d19213e5956ee6aaf3b22b96239fb452d2e3f1209ffe084bb87fda84583e6b4
4
- data.tar.gz: d7a67da62d2988d9d5339ac46f2d87363cbead3837a55efab62f40afbf21e4fb
3
+ metadata.gz: 7785bb2b01f91bf0eca6a9d47ec73e0a7a6b98c6d11c23dc150843a3e2d4502d
4
+ data.tar.gz: c7d838da7303b761ec6b5fcd0d46459e17df1d9513ebb4c418724f22d2846622
5
5
  SHA512:
6
- metadata.gz: 0d499453ba6e7484508714ba66e1b22dd7fafb18f8d91a0bb78c55de469b8e568adb6335d7fd40dbf6ee054835e9cd401b9a627da5b2a1b40f69eeccdbdd3de5
7
- data.tar.gz: ee280e1f231c66d1d494f6f07ee13be39f9b0d66a64e3bb46bcdaa0b6d583e506389461c4ab9ea4204da0b6a80f83c9c79845459543127bb79b4273280d4def2
6
+ metadata.gz: 2076c18eb49a0b5a22fddf58ac082eb81cf83cd1c47cba048278b22f77bae6fb4c6e3c268f5069fc943b800f13ed3be0c5301f03f9cb23a53ef1d2a62e9a55ae
7
+ data.tar.gz: 12554947e6f9105fb1c965bfa56c7d9685287bd60119661928ff2e9a356c5d9d2ef9dfdbf361d7bbe61f40948c50845845190b13e96446399f6a26ee3531b6fb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- ### 2026.9.23
2
+ ### 2026.9.30
3
3
  ### 0.0.1 / 2026-07-22
4
4
 
5
5
  * Everything is new. First release.
data/Manifest.txt CHANGED
@@ -63,6 +63,11 @@ config/seasons/za.csv
63
63
  lib/fifadat.rb
64
64
  lib/fifadat/api.rb
65
65
  lib/fifadat/config.rb
66
+ lib/fifadat/helper.rb
66
67
  lib/fifadat/json.rb
68
+ lib/fifadat/pp/ppdebug.rb
69
+ lib/fifadat/pp/ppmatch.rb
70
+ lib/fifadat/pp/ppstats.rb
67
71
  lib/fifadat/prepare.rb
72
+ lib/fifadat/types.rb
68
73
  lib/fifadat/version.rb
data/lib/fifadat/api.rb CHANGED
@@ -74,11 +74,19 @@ class Metal
74
74
 
75
75
 
76
76
 
77
+
77
78
  def self.squads_url( idSeason:, idCompetition: )
78
79
  ## API_ROOT/teams/squads/all/108/278491
79
80
  "#{BASE_URL}/teams/squads/all/#{idCompetition}/#{idSeason}"
80
81
  end
81
82
 
83
+ def self.teams_url( idSeason: )
84
+ ## /competitions/teams/{season}
85
+ # Teams participating in a competition/season
86
+ "#{BASE_URL}/competitions/teams/#{idSeason}" +
87
+ "?language=en"
88
+ end
89
+
82
90
  def self.matches_url( idSeason: )
83
91
  ## note - may add &idCompetition=17
84
92
 
@@ -101,10 +109,61 @@ class Metal
101
109
  "#{BASE_URL}/live/football/#{idCompetition}/#{idSeason}/#{idStage}/#{idMatch}?language=en"
102
110
  end
103
111
 
112
+ =begin
104
113
  def self.timeline_url( idCompetition:, idSeason:,
105
114
  idStage:, idMatch: )
106
115
  #API_ROOT/timelines/108/278491/278493/300424860?language=en-GB
116
+
107
117
  "#{BASE_URL}/timelines/#{idCompetition}/#{idSeason}/#{idStage}/#{idMatch}?language=en"
108
118
  end
119
+ =end
120
+
121
+ def self.timeline_url( idMatch: )
122
+ #API_ROOT/timelines/108/278491/278493/300424860?language=en-GB
123
+
124
+ ##
125
+ ## todo/check
126
+ ## only use idMatch ??
127
+ ## e.g. GET https://api.fifa.com/api/v3/timelines/{matchId}?language=en
128
+
129
+ "#{BASE_URL}/timelines/#{idMatch}?language=en"
130
+ end
131
+
132
+ #############
133
+ ## more support urls
134
+
135
+ # get_team
136
+ # Get a national team or club by its idTeam: name, abbreviation, country,
137
+ # home city, and stadium.
138
+ # Surfaces idStadium so you can chain to get_stadium.
139
+ def self.team_url( idTeam: ) ## note - singular (NOT plural)
140
+ "#{BASE_URL}/teams/#{idTeam}?language=en"
141
+ end
142
+
143
+
144
+ ## note - singular (NOT plural)
145
+ # Squad/player information
146
+ def self.squad_url( idTeam:, idCompetition:, idSeason: )
147
+ "#{BASE_URL}/teams/#{idTeam}/squad?idCompetition=#{idCompetition}&idSeason=#{idSeason}&language=en"
148
+ end
149
+
150
+
151
+ # list_countries
152
+ # List FIFA countries and member associations (reference data),
153
+ # each with its idCountry
154
+
155
+ def self.countries_url
156
+ "#{BASE_URL}/countries?count=500&language=en"
157
+ end
158
+
159
+ # list_confederations
160
+ # List the six FIFA confederations
161
+ # (UEFA, CONMEBOL, CONCACAF, CAF, AFC, OFC), each with its idConfederation
162
+
163
+ def self.confederations_url
164
+ "#{BASE_URL}/confederations?language=en"
165
+ end
166
+
167
+
109
168
  end ## class Metal
110
169
  end # class Fifa
@@ -0,0 +1,52 @@
1
+
2
+
3
+ def assert( test, msg )
4
+ if test
5
+ else
6
+ puts "!! ASSERT FAILED - #{msg}"
7
+ exit 1
8
+ end
9
+ end
10
+
11
+ ### fix - change/rename to datetime!!!
12
+ def parse_date( date_str )
13
+ date = DateTime.strptime( date_str, '%Y-%m-%dT%H:%M:%S%z' )
14
+
15
+ assert( date_str == date.strftime('%Y-%m-%dT%H:%M:%SZ'),
16
+ "date parse expected #{date_str} - got #{date.inspect}" )
17
+ date
18
+ end
19
+
20
+
21
+ def parse_date_utc( str )
22
+ date = DateTime.strptime( str, '%Y-%m-%dT%H:%M:%S%z' )
23
+
24
+ ## note: check for seconds is 00
25
+ ## AND timezone is Z (zulu) (shortkey for utc!)
26
+ ## e.g. hard code ":00Z" in strftime!!!
27
+
28
+ str_exp = date.strftime('%Y-%m-%dT%H:%M:00Z')
29
+ assert( str == str_exp,
30
+ "date parse expected #{str_exp} - got #{str} #{date.inspect}" )
31
+ date
32
+ end
33
+
34
+
35
+
36
+ =begin
37
+ - [ ] add desc json samples here
38
+
39
+ =end
40
+
41
+
42
+
43
+ def desc( data ) ## get description
44
+ return nil if data.nil? || (data.is_a?(Array) && data.empty?)
45
+
46
+ row = data[0] ## assume first entry is en-GB
47
+ locale = row['Locale']
48
+ assert( locale == 'en-GB' || locale == 'en-gb',
49
+ "locale en-GB expected in data[0] - got #{data}")
50
+
51
+ row['Description']
52
+ end
@@ -0,0 +1,69 @@
1
+
2
+
3
+
4
+ def pp_debug( data )
5
+
6
+ errors = []
7
+
8
+
9
+ matches = data['Results'] ## only use results (match) array
10
+
11
+ ## get SeasonName from first match
12
+ m = matches[0]
13
+ competitionName = desc( m['CompetitionName'])
14
+ seasonName = desc( m['SeasonName'])
15
+
16
+
17
+ ## pp data
18
+ puts " #{matches.size} match(es) in #{competitionName} / #{seasonName}"
19
+
20
+
21
+ buf = String.new
22
+
23
+ matches.each_with_index do |m, i|
24
+
25
+
26
+ ## todo -- log match details
27
+ ## if matchstatus not 0,1
28
+ ## or result type not 0,1,2,3,4,5 !!!
29
+
30
+
31
+ buf << "==> [#{i+1}/#{matches.size}] "
32
+ buf << pp_match(m)
33
+
34
+
35
+ idMatch = m['IdMatch']
36
+
37
+ matchStatus = m['MatchStatus']
38
+ resultType = m['ResultType']
39
+
40
+ ## add aggregate score too
41
+ score = [m['HomeTeamScore'], m['AwayTeamScore']].compact
42
+ penScore = [m['HomeTeamPenaltyScore'], m['AwayTeamPenaltyScore']].compact
43
+ aggScore = [m['AggregateHomeTeamScore'],m['AggregateAwayTeamScore']].compact
44
+
45
+
46
+ if resultType == 0 && !score.empty?
47
+ ## pachuca vs salzburg in cwc 2025??
48
+ ## double check if score present
49
+ ## raise ArgumentError,
50
+ ## " resultType == 0 but score present idMatch #{m['IdMatch']} #{m['HomeTeamScore']}-#{m['AwayTeamScore']}"
51
+
52
+ msg = " resultType == 0 but score present idMatch #{idMatch} #{score.inspect}"
53
+ errors << msg
54
+ end
55
+
56
+ if ![0,1,9].include?(matchStatus)
57
+ msg = "matchStatus 0-END,1-SCHED,9-AWD expected; got #{matchStatus} in: #{m.pretty_inspect}"
58
+ errors << msg
59
+ end
60
+
61
+ if ![0,1,2,3,4,5,8,12].include?(resultType)
62
+ msg = "resultType 0,1,2,3,4,5,8,12 expected; got #{resultType} in: #{m.pretty_inspect}"
63
+ errors << msg
64
+ end
65
+ end
66
+
67
+
68
+ [buf, errors]
69
+ end
@@ -0,0 +1,78 @@
1
+
2
+
3
+ ## todo/check:
4
+ ## use: fomat: full/fuller, short/long or such - why? why not?
5
+ def pp_match( m, season: false )
6
+
7
+ competitionName = desc( m['CompetitionName'])
8
+ seasonName = desc( m['SeasonName'])
9
+
10
+
11
+ idCompetition = m['IdCompetition']
12
+ idSeason = m['IdSeason']
13
+ idStage = m['IdStage']
14
+ idMatch = m['IdMatch']
15
+
16
+ stageName = desc( m['StageName'] )
17
+ groupName = desc( m['GroupName'] ) # optional
18
+ matchDay = m['MatchDay'] # optional
19
+
20
+ matchNumber = m['MatchNumber'] # optional
21
+
22
+ team1 = m['Home'] ? { name: desc( m['Home']['TeamName'] ),
23
+ abbrev: m['Home']['Abbreviation'],
24
+ country: m['Home']['IdCountry'],
25
+ }
26
+ : { name: '?', abbrev: '?', country: '?' }
27
+
28
+ team2 = m['Away'] ? { name: desc( m['Away']['TeamName'] ),
29
+ abbrev: m['Away']['Abbreviation'],
30
+ country: m['Away']['IdCountry'],
31
+ }
32
+ : { name: '?', abbrev: '?', country: '?' }
33
+
34
+
35
+ matchStatus = m['MatchStatus']
36
+ resultType = m['ResultType']
37
+
38
+ ## add aggregate score too
39
+ score = [m['HomeTeamScore'], m['AwayTeamScore']].compact
40
+ penScore = [m['HomeTeamPenaltyScore'], m['AwayTeamPenaltyScore']].compact
41
+ aggScore = [m['AggregateHomeTeamScore'],m['AggregateAwayTeamScore']].compact
42
+
43
+
44
+
45
+ buf = String.new
46
+
47
+
48
+ if season
49
+ ## beautify
50
+ ## possible rm competitionName from season
51
+
52
+ seasonNameAlt = seasonName.sub( competitionName, '').gsub( /[ ]{2,}/,' ').strip
53
+ buf << "== #{competitionName}"
54
+ buf << " | #{seasonNameAlt}"
55
+ buf << "\n"
56
+ end
57
+
58
+
59
+ buf << "#{matchStatus}-#{MATCH_STATUS[matchStatus]||'??'} "
60
+ buf << " ▪ #{stageName}"
61
+ buf << ", #{groupName}" if groupName
62
+ buf << " - #{matchDay}" if matchDay
63
+ buf << " (#{matchNumber})" if matchNumber
64
+
65
+ buf << " #{team1[:name]} | #{team1[:abbrev]} (#{team1[:country]})"
66
+ buf << " v "
67
+ buf << "#{team2[:name]} | #{team2[:abbrev]} (#{team2[:country]}) "
68
+ buf << "\n"
69
+
70
+ buf << " "
71
+ buf << " (#{resultType}-#{RESULT_TYPE[resultType]||'??'})"
72
+ buf << " score: #{score.inspect}" unless score.empty?
73
+ buf << " pen: #{penScore.inspect}" unless penScore.empty?
74
+ buf << " agg: #{aggScore.inspect}" unless aggScore.empty?
75
+ buf << "\n"
76
+
77
+ buf
78
+ end
@@ -0,0 +1,97 @@
1
+
2
+
3
+
4
+ class Counter
5
+ def initialize( types )
6
+ @types = types
7
+ @counter = Hash.new(0)
8
+ end
9
+
10
+ def [](key) @counter[key]; end
11
+ def []=(key,value) @counter[key]=value; end
12
+
13
+ def pretty_print(q)
14
+ q.group(1, "{", "}") do
15
+ ## sort by keys
16
+ pairs = @counter.sort_by { |key,_| key }
17
+ pairs.each_with_index do |(key,count),i|
18
+ q.breakable(', ') if i != 0
19
+ q.text( "#{key}-#{(@types[key]||'??')}=>#{count}")
20
+ end
21
+ end
22
+ end
23
+
24
+ end ## class Counter
25
+
26
+
27
+
28
+ def mk_stats
29
+ stats = {
30
+ ## match stats
31
+ 'MatchStatus' => Counter.new( MATCH_STATUS ),
32
+ 'ResultType' => Counter.new( RESULT_TYPE ),
33
+ 'TimeDefined' => Hash.new(0),
34
+
35
+ 'MatchDay' => Hash.new(0),
36
+ 'MatchNumber' => Hash.new(0),
37
+ 'Leg' => Hash.new(0),
38
+ 'IsHomeMatch' => Hash.new(0),
39
+
40
+ 'Attendance' => Hash.new(0),
41
+ 'Weather' => Hash.new(0),
42
+
43
+ ## add stage & group
44
+ ## add agg score and pen score!!
45
+
46
+
47
+ ## team stats
48
+ ## 'TeamType' => Hash.new(0),
49
+ ## 'AgeType' => Hash.new(0),
50
+ ## 'FootballType' => Hash.new(0),
51
+ }
52
+
53
+ stats
54
+ end
55
+
56
+
57
+
58
+ def collect_stats( data, stats: )
59
+
60
+ ## use results (match) array
61
+ data['Results'].each_with_index do |m, i|
62
+
63
+
64
+ ##
65
+ ## check usage
66
+ stats[ 'MatchStatus' ][m['MatchStatus']] +=1
67
+ stats[ 'ResultType'][m['ResultType']] +=1
68
+
69
+ stats[ 'TimeDefined'][m['TimeDefined']] +=1
70
+
71
+
72
+ stats[ 'MatchDay'][!m['MatchDay'].nil?] +=1
73
+ stats[ 'MatchNumber'][!m['MatchNumber'].nil?] +=1
74
+
75
+ stats[ 'Leg'][m['Leg']] +=1
76
+ stats[ 'IsHomeMatch'][m['IsHomeMatch']] +=1
77
+
78
+ stats[ 'Attendance'][!m['MatchDay'].nil?] +=1
79
+ stats[ 'Weather'][!m['Weather'].nil?] +=1
80
+
81
+ =begin
82
+ if m['Home']
83
+ stats[ 'TeamType'][m['Home']['TeamType']] +=1
84
+ stats[ 'AgeType'][m['Home']['AgeType']] +=1
85
+ stats[ 'FootballType'][m['Home']['FootballType']] +=1
86
+ end
87
+ if m['Away']
88
+ stats[ 'TeamType'][m['Away']['TeamType']] +=1
89
+ stats[ 'AgeType'][m['Away']['AgeType']] +=1
90
+ stats[ 'FootballType'][m['Away']['FootballType']] +=1
91
+ end
92
+ =end
93
+
94
+ end
95
+
96
+ stats
97
+ end
@@ -22,6 +22,9 @@ def prepare( name:,
22
22
  fetch_json_if( Fifa::Metal.matches_url( idSeason: idSeason ),
23
23
  "#{outdir}/#{name}/#{season.to_path}_matches.json", force: force )
24
24
 
25
+ fetch_json_if( Fifa::Metal.teams_url( idSeason: idSeason ),
26
+ "#{outdir}/#{name}/misc/#{season.to_path}_teams.json", force: force )
27
+
25
28
  fetch_json_if( Fifa::Metal.stages_url( idSeason: idSeason ),
26
29
  "#{outdir}/#{name}/misc/#{season.to_path}_stages.json", force: force )
27
30
 
@@ -45,7 +48,8 @@ def prepare_reports( name:,
45
48
  matches = data['Results']
46
49
 
47
50
 
48
- puts " #{matches.size} match(es) in season #{season}"
51
+ puts "==> prepare reports - #{matches.size} match(es) in #{name} #{season}"
52
+
49
53
 
50
54
 
51
55
  matches.each_with_index do |m, i|
@@ -54,8 +58,6 @@ def prepare_reports( name:,
54
58
  idStage = m['IdStage']
55
59
  idMatch = m['IdMatch']
56
60
 
57
- stageName = desc( m['StageName'] )
58
-
59
61
 
60
62
  ## note - skip if teams not yet know
61
63
  ## e.g. Home & Away is null
@@ -66,14 +68,34 @@ def prepare_reports( name:,
66
68
  ## e.g. MatchStatus = 1 -- future
67
69
  ## ResultType = 0 -- not played yet
68
70
 
71
+ ## if not yet played!!!
72
+ # 1-scheduled and
73
+ # 2-live
74
+ # 7-postponed !!!! (too)
75
+ # return empty score!!!
76
+ if m['MatchStatus'] == 1 ||
77
+ m['MatchStatus'] == 2 ||
78
+ m['MatchStatus'] == 7
79
+ ## skip scheduled/live matches (not yet played)
80
+ next
81
+ end
82
+
83
+
84
+
85
+
69
86
  if m['Home'].nil? || m['Away'].nil?
70
87
  puts "[#{i+1}/#{matches.size}] ?? ??, #{stageName} (SKIPPED - TO BE DONE)"
71
88
  next
72
89
  end
73
90
 
74
91
 
75
- dateTime = parse_date( m['Date'] ) ## utc
76
- localDateTime = parse_date( m['LocalDate'] )
92
+ ### note - date is always utc without timezone even if local!!
93
+ ## local is utc (plus/minus offset)!!!
94
+ ## e.g.
95
+ ## "Date": "2026-08-02T15:00:00Z",
96
+ ## "LocalDate": "2026-08-02T17:00:00Z",
97
+ dateTime = parse_date_utc( m['Date'] ) ## utc
98
+ localDateTime = parse_date_utc( m['LocalDate'] )
77
99
 
78
100
 
79
101
  ## pp m['Home']
@@ -84,8 +106,21 @@ def prepare_reports( name:,
84
106
  teamName2 = desc( m['Away']['TeamName'] )
85
107
  teamCode2 = m['Away']['Abbreviation']
86
108
 
109
+ stageName = desc( m['StageName'] )
110
+ ## "MatchDay": null,
111
+ matchday = m['MatchDay'] || '∅'
112
+ groupName = desc( m['GroupName'] )
113
+
114
+ print "[#{i+1}/#{matches.size}] "
115
+ print MATCH_STATUS[m['MatchStatus']]||"???-#{m['MatchStatus']}"
116
+ print "/"
117
+ print RESULT_TYPE[m['ResultType']]||"???-#{m['ResultType']}"
118
+ print " #{teamName1} (#{teamCode1}) v #{teamName2} (#{teamCode2})"
119
+ print " | #{stageName} - #{matchday}"
120
+ print ", #{groupName}" if groupName
121
+ print " | #{localDateTime}"
122
+ print "\n"
87
123
 
88
- puts "[#{i+1}/#{matches.size}] #{teamName1} #{teamName2}, #{stageName}, #{localDateTime}"
89
124
 
90
125
 
91
126
  outpath = "#{outdir}/#{name}/matches/#{season.to_path}/#{localDateTime.strftime('%Y-%m-%d')}_#{teamCode1}-#{teamCode2}__#{idMatch}.json"
@@ -93,7 +128,7 @@ def prepare_reports( name:,
93
128
  url = Fifa::Metal.live_url( idCompetition: idCompetition,
94
129
  idSeason: idSeason,
95
130
  idStage: idStage,
96
- idMatch: idMatch )
131
+ idMatch: idMatch )
97
132
 
98
133
  fetch_json_if( url, outpath, force: force )
99
134
 
@@ -101,15 +136,16 @@ def prepare_reports( name:,
101
136
  ###
102
137
  ## add timeline (only) if score incl. penalty shoot-out
103
138
  resultType = m['ResultType']
104
- if resultType == 2 ## (aet??) win on pens
139
+ if resultType == 2 ## WIN_ON_PENS
105
140
 
106
141
  ## download timeline
107
142
  outpath = "#{outdir}/#{name}/timelines/#{season.to_path}/#{localDateTime.strftime('%Y-%m-%d')}_#{teamCode1}-#{teamCode2}__#{idMatch}.json"
108
143
 
109
- url = Fifa::Metal.timeline_url( idCompetition: idCompetition,
110
- idSeason: idSeason,
111
- idStage: idStage,
112
- idMatch: idMatch )
144
+ # url = Fifa::Metal.timeline_url( idCompetition: idCompetition,
145
+ # idSeason: idSeason,
146
+ # idStage: idStage,
147
+ # idMatch: idMatch )
148
+ url = Fifa::Metal.timeline_url( idMatch:idMatch )
113
149
 
114
150
  fetch_json_if( url, outpath, force: force )
115
151
  end
@@ -0,0 +1,202 @@
1
+
2
+ ##
3
+ ## help - yes, you can.
4
+ ## if you have any more info about the fifa types,
5
+ ## please tell and help fill-in the details.
6
+
7
+
8
+
9
+ MATCH_STATUS = {
10
+ 0 => 'END', # finished
11
+ 1 => 'SCHEDULED', # scheduled (with or without time - see timed flag)
12
+ 2 => 'LIVE',
13
+
14
+ ## ???
15
+
16
+ 7 => 'POSTPONED', # postponed - use PPD ??
17
+
18
+ 9 => 'AWD', # awarded !!!
19
+ # check if 9/0 is possibly cancelled/annulled/walkover (if no score)
20
+ # 9/12 (AWD 3-0 or such)
21
+
22
+ }
23
+
24
+
25
+ ##
26
+ ## "MatchStatus"=>9, cancelled!!!!
27
+ ##
28
+ ## "ResultType"=>12,
29
+ ## 21:30 CD Independiente Medellín (COL) v CR Flamengo (BRA) [cancelled]
30
+ ##
31
+
32
+ ##
33
+ ## Result: Al Duhail awarded a 3–0 walkover victory.
34
+ ## Reason: Auckland City FC withdrew from the tournament
35
+ ## due to strict COVID-19 pandemic quarantine and isolation measures
36
+ ## enforced by New Zealand authorities.
37
+
38
+ ##
39
+ ## check 2 is only win on pens!!!
40
+ ## might be WITHOUT extra time e.g. copa liber.
41
+ ## [28/142] 0-FIN 3rd Round Sporting Cristal | SCL (PER) v Carabobo FC | CRB (VEN)
42
+ ## (2-AET_WIN_ON_PENS) score: [1, 2] pen: [3, 2] agg: [2, 2]
43
+ ## and others!!!
44
+ ##
45
+ ## how to check if extra time was played?? (use period??)
46
+
47
+ # resultType
48
+ # 0 => no result / not played yet
49
+ # 1 => regular (90 mins)
50
+ # 2 => aet (120 mins), win on pens
51
+ # 3 => aet (120 mins)
52
+ # 8 => same as 3? -aet with golden goal/silver goal in 1998 FRA-PAR
53
+
54
+
55
+
56
+ RESULT_TYPE = {
57
+ 0 => 'NO',
58
+ 1 => 'REGULAR',
59
+ ## note - cannot tell if
60
+ ## win on penalties
61
+ ## is after extra-time!!!
62
+ ## used many times in copa libertadores
63
+ ## check gold cup too
64
+ ## change to WIN_ON_PENS only - why? why not?
65
+ 2 => 'WIN_ON_PENS', ## AET_WIN_ON_PENS or REG_WIN_ON_PENS or AGG_WIN_ON_PENS !!!!!!
66
+ 3 => 'AET',
67
+ ##
68
+ ## note - agg - used for 1st & 2nd leg
69
+ 4 => 'REGULAR/AGG', ## aggregate (1st/2nd leg) - regular
70
+ 5 => 'AET/AGG', ## aggregate - after extra-time
71
+ ## e.g.
72
+ ## 2nd Leg Feb 25, 2026 Juventus 3–2 (AET) Galatasaray
73
+ ##
74
+ 8 => 'AET/GG', ## after extra-time/golden goal !!!
75
+
76
+ 12 => 'AWD', ## AWARDED or ???
77
+ }
78
+
79
+
80
+ GOAL_TYPE = {
81
+ 1 => 'PENALTY',
82
+ 2 => 'REGULAR',
83
+ 3 => 'OWN_GOAL',
84
+ }
85
+
86
+
87
+ ## use PERIOD only - why? why not?
88
+ PERIOD_TYPE = {
89
+ 3 => '1ST_HALF', ## "MatchMinute": "0'", "1'", "45'+2'",
90
+ 4 => 'HALF', ## "MatchMinute": "46'", "90'+2'",
91
+ 5 => '2ND_HALF', ## "MatchMinute": "91'", "105'+2'",
92
+ 6 => 'EXTRA_TIME', ## or end of regular time??
93
+ 7 => 'EXTRA_TIME_1ST_HALF', ## "MatchMinute": "106'", "120'+2'",
94
+ 8 => 'EXTRA_TIME_HALF',
95
+ 9 => 'EXTRA_TIME_2ND_HALF',
96
+ 10 => 'END', ## or use MATCH_END ???
97
+ ## (generic incl. optional extra time and/or penalty shootout)
98
+ 11 => 'PENALTY_SHOOTOUT', ## "MatchMinute": "120'", "121'", "122'", "123'",
99
+ ## "MatchMinute": "127'", "130'",
100
+ }
101
+
102
+ =begin
103
+ "MatchMinute": "1'",
104
+ "Period": 0, !!!!
105
+ "MatchMinute": "91'",
106
+ "Period": 0, !!!
107
+ =end
108
+
109
+
110
+ EVENT_TYPE = {
111
+ 0 => 'Goal!', ## e.g. DI MARÍA (Argentina) scores!!
112
+ 1 => 'Assist', ## e.g. Assisted by MAC ALLISTER.
113
+ 2 => 'Yellow card', ## e.g. FERNÁNDEZ (Argentina) is booked by the referee.
114
+ 3 => 'Red card', ## e.g. Zinedine ZIDANE (France) is sent off
115
+
116
+ 5 => 'Substitution', ## e.g. ACUÑA (in) comes off the bench to replace DI MARÍA (out) (Argentina)
117
+ 6 => 'Penalty Awarded',
118
+ 7 => 'Start Time', ## e.g. The referee signals the start of the first period.
119
+ 8 => 'End Time', ## e.g. 'The referee brings the first period to an end.
120
+
121
+ 12 => 'Attempt at Goal',
122
+
123
+ 15 => 'Offside',
124
+ 16 => 'Corner',
125
+ 18 => 'Foul',
126
+ 19 => 'Coin Toss',
127
+
128
+ 23 => 'Dropped Ball',
129
+ 24 => 'Throw In',
130
+ 25 => 'Clearance',
131
+ 26 => 'Match end',
132
+ 27 => 'Aerial Duel',
133
+
134
+ 41 => 'Penalty Goal',
135
+ 46 => 'Penalty missed', ## David TREZEGUET (France) rattles the crossbar from the spot!
136
+ 51 => 'Penalty missed', ## JULIO CESAR (Brazil) hits the post from the spot!
137
+ 60 => 'Penalty missed', ## COMAN (France) sees his penalty saved by the goalkeeper.
138
+ 65 => 'Penalty missed', ## TCHOUAMENI (France) misses from the penalty spot!
139
+ ## Michel PLATINI (France) misses from the penalty spot!
140
+
141
+ }
142
+
143
+
144
+
145
+ __END__
146
+
147
+
148
+
149
+ TeamType
150
+ 0 => club
151
+ 1 => nati(onal) team
152
+ AgeType
153
+ 0 => ??
154
+ 1 => U17 U-17 World Cup / U17 National Team Friendlies
155
+ MLS Generation Cup U17
156
+ 2 => U18 U18 National Team Friendlies
157
+ U18 Premier League Cup
158
+ 3 => U19 U19 Championship Qualification
159
+ 4 => U20 U-20 World Cup / CONMEBOL U20
160
+
161
+ 5 => ?? Olympic Football Tournament / Olympics Intercontinental Play-offs /
162
+ AFC U23 Asian Cup
163
+
164
+ 10 => U15 U15 National Team Friendlies
165
+ MLS Generation Cup U15
166
+ 11 => U16 U16 National Team Friendlies
167
+ 12 => U21 U21 Championship / Tournoi Maurice Revello /
168
+ U21 National Team Friendlies
169
+ Premier League 2
170
+
171
+ 14 => ?? U23 U23 National Team Friendlies / Asian Games
172
+
173
+ FootballType
174
+ 0 => "classic"
175
+ 1 => Futsal
176
+ 2 => Beach Soccer
177
+ Gender
178
+ 1 => m
179
+ 2 => f
180
+
181
+
182
+ note - matchStatus for 12-AWD should also be 9-AWD!!!
183
+
184
+ resultType 12
185
+ The Clausura 2022 match between Querétaro and Atlas on March 5, 2022,
186
+ was officially suspended in the 62nd minute
187
+ and later recorded as a 3–0 victory for Atlas
188
+ : Atlas was leading 1–0 at the time of suspension.
189
+ Forfeit Victory: Atlas was officially awarded a 3–0 walkover win.
190
+
191
+
192
+ 0 => no result / not played yet
193
+ 1 => regular (90 mins)
194
+ 2 => aet (120 mins), win on pens
195
+ 3 => aet (120 mins)
196
+
197
+ 8 => same as 3? -aet with golden goal/silver goal in 1998 FRA-PAR
198
+
199
+ 4 => aggregate leg 1/2 ?? e.g.
200
+ "AggregateHomeTeamScore": 2,
201
+ "AggregateAwayTeamScore": 4,
202
+ =end
@@ -2,7 +2,7 @@
2
2
  module Fifadat ## use FifaApi or such - why? why not
3
3
  MAJOR = 2026 ## todo: namespace inside version or something - why? why not??
4
4
  MINOR = 9
5
- PATCH = 23
5
+ PATCH = 30
6
6
  VERSION = [MAJOR,MINOR,PATCH].join('.')
7
7
 
8
8
  def self.version
data/lib/fifadat.rb CHANGED
@@ -18,8 +18,11 @@ require_relative 'fifadat/json' ## read_json_v2, fetch_json, fetch_json_if, e
18
18
  ## our own code
19
19
  require_relative 'fifadat/version'
20
20
 
21
- require_relative 'fifadat/config' ## base
22
- require_relative 'fifadat/api' ## base
21
+ require_relative 'fifadat/helper'
22
+ require_relative 'fifadat/config'
23
+ require_relative 'fifadat/api'
24
+ require_relative 'fifadat/types'
25
+
23
26
  require_relative 'fifadat/prepare' ## "all-in-one" prepare (download cache) helpers etc.
24
27
 
25
28
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fifadat
3
3
  version: !ruby/object:Gem::Version
4
- version: 2026.9.23
4
+ version: 2026.9.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-23 00:00:00.000000000 Z
11
+ date: 2026-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cocos
@@ -160,8 +160,13 @@ files:
160
160
  - lib/fifadat.rb
161
161
  - lib/fifadat/api.rb
162
162
  - lib/fifadat/config.rb
163
+ - lib/fifadat/helper.rb
163
164
  - lib/fifadat/json.rb
165
+ - lib/fifadat/pp/ppdebug.rb
166
+ - lib/fifadat/pp/ppmatch.rb
167
+ - lib/fifadat/pp/ppstats.rb
164
168
  - lib/fifadat/prepare.rb
169
+ - lib/fifadat/types.rb
165
170
  - lib/fifadat/version.rb
166
171
  homepage: https://github.com/sportdb/sport.db
167
172
  licenses: