Top Level Namespace

Defined Under Namespace

Modules: Fifadat, FifadatConvert Classes: Array, Goal, Hash, Match, MatchReport, Minute, Object, Official, Penalty, Player, Players, Stadium, Stadiums, Stage, Stages, Sub, Team, Teams

Constant Summary collapse

ALPHA_RE =
%r{ \A
     \p{L}
     [\p{L} '.-]*
 \z
}ix
MINUTE_RE =
%r{  \A
      (?<minute>\d{1,3}) '
       (  \+
         (?<offset>\d{1,2}) '
       )?
  \z
}x

Instance Method Summary collapse

Instance Method Details

#_build_minute(h) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/fifadat/convert/helper-minute.rb', line 29

def _build_minute( h )

    ## split into minute
    ##  and offset (stoppage/injury/added time)
    ##  e.g. 90'+11'

     minute_str = h['Minute']
          if minute_str.nil? || minute_str.empty?
              puts "!! minute is nil or empty:"
              pp h
              exit 1
          end

     minute, offset  = _parse_minute( minute_str )

     Minute.new( m:      minute,
                 offset: offset,
                 period: h['Period'] )
end

#_build_report_score(live, timeline = nil) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/fifadat/convert/helper-score.rb', line 90

def _build_report_score( live, timeline=nil )

    _scores = _build_score_from_goals( live )


    score = nil

     case live['ResultType']
     when 1,4
         ## 1 => 'REGULAR',
         ## 4 => 'REGULAR/AGG',  ##  aggregate (1st/2nd leg) - regular
        ## add ht
        ##  assert ft match
        score      = {  ht: _scores[:ht][:score],
                         ft: _scores[:ft][:score]
                      }
     when 3,5,8
       score       = {   ht: _scores[:ht][:score],
                         ft: _scores[:ft][:score],
                         et: _scores[:et][:score],
                      }
     when 2
        ##  todo/fix - pull in timeline check too to check
        ##     if any events recorded for extra time!!
        score      = {   ht: _scores[:ht][:score],
                         ft: _scores[:ft][:score],
                      }

        ## note - only incl. extra-time (et) if a goal scored in extra time
        score[:et] = _scores[:et][:score]   unless _scores[:et][:minutes].empty?
     end

     score
end

#_build_score_from_goals(m) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/fifadat/convert/helper-score.rb', line 127

def _build_score_from_goals( m )
   ##
   ## 5 periods - 3 (1ST_HALF), 5 (2ND_HALF),
   ##             7 (EXTRA_TIME_1ST_HALF), 9 (EXTRA_TIME_2ND_HALF),
   ##             11 (PENALTY_SHOOTOUT) possibly
   ##
   goals = {
      count: 0,
      score: [0,0],
      ht:   { score: [0,0], minutes: [] },
      ft:   { score: [0,0], minutes: [] },
      et:   { score: [0,0], minutes: [] },
      p:    { score: [0,0], minutes: [] },
   }

   ## note - quick fix
   ##            add  period 2  for USA v Belgium (PKO) - worldcup

   ## note - i is 0|1  -  array index for team
   [m['HomeTeam']['Goals'],
    m['AwayTeam']['Goals']].each_with_index do |recs,i|
      recs.each do |h|

        h = errata_autofix_goal( h )

        period = h['Period']
        ## include penalty shootout (11) - why? why not?
        assert(  [3,5,7,9,11].include?(period),
                  "goal in period 3/5/7/9/11 expected; got #{h.pretty_inspect} in match #{m.pretty_inspect}" )

        minute = h['Minute']
        key =  case period
               when 3    then  :ht
               when 5    then  :ft
               when 7, 9 then  :et
               when 11   then  :p
               else
                raise ArgumentError,
                  "goal in period 3/5/7/9/11 expected; got #{h.pretty_inspect}"
               end

        goals[:count] += 1
        goals[:score][i] += 1   unless key == :p
        goals[key][:score][i] += 1
      end
   end

   ## use/calc cummulative score  (BUT not for penalties)
   goals[:ft][:score][0] += goals[:ht][:score][0]
   goals[:ft][:score][1] += goals[:ht][:score][1]

   goals[:et][:score][0] += goals[:ft][:score][0]
   goals[:et][:score][1] += goals[:ft][:score][1]

   goals
end

#_fmt_date_local(date_utc, date_local) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
# File 'lib/fifadat/convert/helper-date.rb', line 3

def _fmt_date_local(  date_utc, date_local )

  ## note:  returns Rational (e.g. 3/1 or 1/4 etc.) use to_f/to_i to convert
  diff_in_hours = ((date_local - date_utc) * 24).to_f
  diff_in_days  =  date_local.jd - date_utc.jd
  ## pp [diff_in_hours, diff_in_days]


  ## use   20:30 UTC+1  or 20:30 UTC-3
  "#{date_local.strftime( '%Y-%m-%d %H:%M' )} UTC%+d" % diff_in_hours
end

#_fmt_minute(minute, offset) ⇒ Object



73
74
75
76
77
78
79
80
81
# File 'lib/fifadat/convert/helper-minute.rb', line 73

def _fmt_minute( minute, offset )
     ## pp [minute,offset]

     buf = String.new
     buf << "#{minute}"
     buf << "+#{offset}"   if offset
     buf << "'"
     buf
end

#_fmt_time_local(date_utc, date_local) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
# File 'lib/fifadat/convert/helper-date.rb', line 15

def _fmt_time_local(  date_utc, date_local )

  ## note:  returns Rational (e.g. 3/1 or 1/4 etc.) use to_f/to_i to convert
  diff_in_hours = ((date_local - date_utc) * 24).to_f
  diff_in_days  =  date_local.jd - date_utc.jd
  ## pp [diff_in_hours, diff_in_days]


  ## use   20:30 UTC+1  or 20:30 UTC-3
  "#{date_local.strftime( '%H:%M' )} UTC%+d" % diff_in_hours
end

#_parse_minute(str) ⇒ Object

Raises:

  • (ArgumentError)


59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/fifadat/convert/helper-minute.rb', line 59

def _parse_minute( str )

    ## support weirdo  120'+-30'  -- remove minuts
    str = str.gsub( '-', '' )

    m = MINUTE_RE.match( str )
    raise ArgumentError, "unknown goal minute format in #{str.inspect}"  if m.nil?

    minute = m[:minute].to_i(10)
    offset = m[:offset] ? m[:offset].to_i(10) : nil

    [minute,offset]
end

#_parse_score(m) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/fifadat/convert/helper-score.rb', line 3

def _parse_score( m )
   h = { }

   ###
   #  note: for match status
   #       1-scheduled and
   #       2-live
   #       7-postponed
   #    return empty score!!!

   return h    if m['MatchStatus'] == 1 ||
                  m['MatchStatus'] == 2 ||
                  m['MatchStatus'] == 7


#
# add quick fix for club world cup?!
#      elsif  resultType == 1  ||  ## assume 1 - regular (90 mins+stoppage/injury time)
#              resultType == 4  ||
#        m['IdMatch'] == '400019191'  ##  fix for pachuca vs salzburg !!!
#   has resultType == 0!!!

   m = errata_autofix_score( m )


  resultType  = m['ResultType']
   ## add score[:type] - why? why not?
   h[:type] =  RESULT_TYPE[resultType] || "???-#{resultType}"


  score =  if m['HomeTeam'] && m['AwayTeam']  ## assume inside (match) report
             [m['HomeTeam']['Score'], m['AwayTeam']['Score']].compact
          else   ## assume (seasons) matches list
            ## note - report has no "inline/flat" HomeTeamScore/AwayTeamScore
             [m['HomeTeamScore'], m['AwayTeamScore']].compact
          end


   ## is score after extra-time or not?
   ##    note - CANNOT tell for resultType 2!!!

   if score.empty?
       ## do nothing
   else
     if [1,4].include?( resultType )
        ##  1 - regular - 90min
        ##  4 - regular - 90min (on aggregate)
        h[:ft] = score
     elsif [3,5,8].include?( resultType )
         ## 3 - aet (for sure)
         ## 5 - aet [on aggregate] (for sure)
         ## 8 -  golden goal in extra time  (aet/gg)
         ##   fix/fix/fix  - add gold_goal flag for 8!!!
        h[:et] = score   ## assume after extra-time !!!
     elsif resultType == 2
         ### note - 2 win on pens
         ## 2 - win on pens  (with or WITHOUT aet!!)

        ## check for competition for now
        ##    incl. south american
        if false   ## add some competitions here
           h[:ft] = score
        else
           h[:et] = score
        end
     else
        h[:score] = score
     end
    end


    penScore = [m['HomeTeamPenaltyScore'],  m['AwayTeamPenaltyScore']].compact
    aggScore = [m['AggregateHomeTeamScore'],m['AggregateAwayTeamScore']].compact


    ## note - worldcup has penScore [0,0] for all matches, for example!!!
     if penScore.empty? || penScore == [0,0]
     else
        h[:p] = penScore
     end

     h[:agg] = aggScore   if !aggScore.empty?
     h
end

#_read_report(m, report_dir:) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/fifadat/convert/reader.rb', line 37

def _read_report( m, report_dir: )
      return nil     if m['Home'].nil? || m['Away'].nil?

      ##  e.g.  2020-06-24_RAP-AUS__54545454
      #   note - add match id for "raw" match reports!!!
      basename = _report_basename( m )
      basename += "__#{m['IdMatch']}"

      ## #{indir}/#{slug}/matches/#{season.to_path}
      path = "#{report_dir}/#{basename}.json"

      ## note - skip if no match report available!!!
      if File.file?( path )
         live = read_json_v2( path )
         live
      else
         nil
      end
end

#_read_timeline(m, timeline_dir:) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/fifadat/convert/reader.rb', line 20

def _read_timeline( m, timeline_dir: )
      return nil     if m['Home'].nil? || m['Away'].nil?

      basename = _report_basename( m )
      basename += "__#{m['IdMatch']}"

      path = "#{timeline_dir}/#{basename}.json"

      ## note - skip if no match report available!!!
      if File.file?( path )
         timeline = read_json_v2( path )
         timeline
      else
         nil
      end
end

#_report_basename(m) ⇒ Object

use mk/build_report_basename - why? why not? fix-fix-fix - change to _match_basename !!! use by (match)report and (match)timeline !!!



4
5
6
7
8
9
10
11
12
13
14
15
16
# File 'lib/fifadat/convert/reader.rb', line 4

def _report_basename( m )
      ### get match (live) details
      team1_code = m['Home']['Abbreviation']
      team2_code = m['Away']['Abbreviation']
      localDateTime  = parse_date_utc( m['LocalDate'] )

      ##  e.g.  2020-06-24_RAP-AUS__54545454
      basename = String.new
      basename += localDateTime.strftime('%Y-%m-%d')    ##   i) date
      basename += "_#{team1_code}-#{team2_code}"        ##  ii) team1 v team2

      basename
end

say hello



77
# File 'lib/fifadat/convert.rb', line 77

puts FifadatConvert.banner

#convert(slug:, season:, indir: '.', outdir: './tmp') ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/fifadat/convert/convert.rb', line 4

def convert( slug:, season:,
                indir: '.',
                outdir: './tmp' )

   season = Season(season)


   ### fix-fix-fix  change to read_json_results or ??? - why? why not?
   matches =  read_json_v2( "#{indir}/#{slug}/#{season.to_path}_matches.json" )
   matches = matches['Results']  ## only use results (match) array

   ## pp matches
   puts "==> convert - #{matches.size} match(es) in #{slug} #{season}"


   ## read in stages
   ##   incl.  SequenceOrder, StageLevel (optional)
   stages = Stages.read( "#{indir}/#{slug}/misc/#{season.to_path}_stages.json" )
   stages.add_matches( matches )

   teams = Teams.new
   teams.add_matches( matches )

   stadiums = Stadiums.new
   stadiums.add_matches( matches )



    data = {}

    ## add slug & seasons (add name to be done!!)
    data[:meta] = { slug:      slug,
                    season:    season.to_s,
                    generated: Time.now.to_s,
                    version:   FifadatConvert::VERSION,
                    teams:    teams.size,
                    matches:  matches.size,
                    stages:   stages.size,
                    stadiums:  stadiums.size,
                  }

   data[:stages]   = stages.as_json
   data[:teams]    = teams.as_json
   data[:stadiums] = stadiums.as_json



   ## for match-by-match live reports
   report_dir   = "#{indir}/#{slug}/matches/#{season.to_path}"
   timeline_dir = "#{indir}/#{slug}/timelines/#{season.to_path}"


   recs = []
   matches.each_with_index do |m, i|

       ## add/track id too - why? why not?
       ##  rec[:id]  = m['IdMatch']


        ## add/fill-up match basic
       rec = Match.build( m,  teams:    teams,
                              stadiums: stadiums,
                              stages:   stages )

   ###
   ##   note - skip check for match events / goals etc.
   ##      if not yet played!!!
   #         1-scheduled and
   #         2-live
   #         7-postponed (too)
   #    return empty score!!!
      if !(m['MatchStatus'] == 1 ||
           m['MatchStatus'] == 2 ||
           m['MatchStatus'] == 7)



      ### get match (live) details
      ###
       ##   check if match report & timeline exits
      ##    optional for now!!

      live     = _read_report( m, report_dir: report_dir )
      timeline = _read_timeline( m, timeline_dir: timeline_dir )

      ## if live.nil?
      ##   puts "warn no match report for #{_report_basename(m)}"
      ## end


        ## try  update of score via goals from (match) report
        score_more =  _build_report_score( live, timeline )
        if score_more
           rec.score = {}.merge( rec.score||{}, score_more )
        end


        ## reuse generated output from report
        report =  MatchReport.build( live, timeline )

       ############
       ## add goals

       if report.goals1.empty? && report.goals2.empty?
          ## skip if no goals
       else
         rec.goals1 = report.goals1
         rec.goals2 = report.goals2
       end


       #########
       ## add penalties
       rec.penalties = report.penalties   if report.penalties &&
                                            !report.penalties.empty?


      sentoff1 = report.sentoff1
      sentoff2 = report.sentoff2
      rec.sentoff1 = sentoff1    unless sentoff1.empty?
      rec.sentoff2 = sentoff2    unless sentoff2.empty?

      ###
      ##  add referees
      rec.officials = report.officials   if report.officials && !report.officials.empty?
      end


      recs << rec
   end
   data[:matches] = recs.as_json



    outpath =  "#{outdir}/#{season.to_path}/#{slug}.json"
    write_json( outpath, data)
    puts "  written to >#{outpath}<"

    true
end

#convert_reports(slug:, season:, indir: '.', outdir: './tmp') ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/fifadat/convert/convert-reports.rb', line 7

def convert_reports( slug:, season:,
                         indir: '.',
                         outdir: './tmp' )

   season = Season(season)

   data =  read_json_v2( "#{indir}/#{slug}/#{season.to_path}_matches.json" )
   matches = data['Results']  ## only use results (match) array
   puts "==> convert reports - #{matches.size} match(es) in #{slug} #{season}"


   ## read in stages
   ##   incl.  SequenceOrder, StageLevel (optional)
   stages = Stages.read( "#{indir}/#{slug}/misc/#{season.to_path}_stages.json" )
   stages.add_matches( matches )

   teams = Teams.new
   teams.add_matches( matches )

   stadiums = Stadiums.new
   stadiums.add_matches( matches )




   report_dir = "#{indir}/#{slug}/matches/#{season.to_path}"
   timeline_dir = "#{indir}/#{slug}/timelines/#{season.to_path}"



   matches.each_with_index do |m, i|

       print "."
       print i+1    if (i+1) %10 == 0


       ## note skip if SCHEDULED == 1
       ##   0 =>   FINISHED/complete (OK)
       ##   1 =>   SCHEDULED/not yet played
       ##   2 =>   LIVE
       ##   7 =>   POSTPONED

       next if m['MatchStatus'] == 1 ||
               m['MatchStatus'] == 2 ||
               m['MatchStatus'] == 7


      live     = _read_report( m, report_dir: report_dir )

      ## no report found; continue
      next  if live.nil?

      ## check for optional timeline
      timeline = _read_timeline( m, timeline_dir: timeline_dir )


      rec    = Match.build( live,   teams:    teams,
                                    stadiums: stadiums,
                                    stages:   stages )


       ## try  update of score via goals from (match) report
        score_more =  _build_report_score( live, timeline )
        if score_more
           rec.score = {}.merge( rec.score||{}, score_more )
        end


      report = MatchReport.build( live, timeline )

      data = {
               meta: {
                  name: desc(live['SeasonName']),
                  slug: slug,
                  season: season.to_s,
                  generated: Time.now.to_s,
               }
            }

      data = data.merge( rec.as_json, report.as_json )


      ## build basename e.g  2026-07-15_ARG-ENG
      basename = _report_basename( m )
      outpath = "#{outdir}/#{season.to_path}/#{slug}/#{basename}.json"
      write_json( outpath, data )
   end

   print "DONE\n"
end

#errata_autofix_goal(h) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/fifadat/convert/errata.rb', line 30

def errata_autofix_goal( h )
   if h['Period']     ==  2 &&
      h['IdPlayer'] == '448362' &&         ##
      h['IdTeam']   == '43935'             ## Belgium
         puts "-- ERRATA - autofix goal  USA v Belgium / Worldcup"
         pp h

         h['Period']  = 3    ## 1ST_HALF
         h['Minute']  = "9'"
   end

   h
end

#errata_autofix_score(m) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
# File 'lib/fifadat/convert/errata.rb', line 50

def errata_autofix_score( m )
   ##  pachuca vs salzburg in cwc 2025??
   if m['IdMatch'] == '400019191'  &&        ##  fix for pachuca vs salzburg
      m['ResultType'] == 0
            m['ResultType'] == 1   ## regular   (or use 4-REG/AGG - why? why not?)

            puts "-- ERRATA - autofix score    pachuca vs salzburg / Club Worldcup"
    end

    m
end

#is_alpha?(name) ⇒ Boolean

  1. HARTING - note: include . Jose BUSTAMANTE-NAVA - note: include - Yannick N'Djeng - note: include ' Ismail JAKOBS Ismail JAKOBS

Returns:

  • (Boolean)


15
16
17
# File 'lib/fifadat/convert/norm.rb', line 15

def is_alpha?( name )  ## use is_alpha_name? or is_unialpha? or such??
    ALPHA_RE.match( name ) ? true : false
end

#pp_alpha(name) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/fifadat/convert/norm.rb', line 20

def pp_alpha( name )
  ##
##  note:  e is decomposed   !!!!  - will break lexer!!!
##   e (101)
##   ́  (769)
##   vs
##     é (233)

  buf = String.new
  buf << ">#{name}< => "
  name.each_char do |char|
      buf << "#{char} (#{char.ord}) | "
  end
  buf
end