397 lines
15 KiB
Python
397 lines
15 KiB
Python
![]() |
#!/usr/bin/python
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
|
|||
|
try:
|
|||
|
import xml.etree.cElementTree as ET
|
|||
|
except ImportError:
|
|||
|
import xml.etree.ElementTree as ET
|
|||
|
|
|||
|
import time
|
|||
|
|
|||
|
|
|||
|
class Race:
|
|||
|
"""A single race event."""
|
|||
|
|
|||
|
def __init__(self, name=None, date=None, fiso_code=0):
|
|||
|
""""""
|
|||
|
self.name = name
|
|||
|
self.date = date
|
|||
|
self.classes = {} # Key = classes_id, Values = the Class itself.
|
|||
|
self.runners = {} # Key = runner_id, Values = the Runner itself.
|
|||
|
self.legs = {} # Key = legname, Values = the leg itself.
|
|||
|
self.clubs = {} # Key = club_id, Values = the Club itself.
|
|||
|
self.ns = '' # Current iof file namespace.
|
|||
|
self.statuses = [] # List of all NON 'OK' statuses found.
|
|||
|
self.unk_cnt = 0 # A counter to name/list the ones without a fiso id.
|
|||
|
self.org_cnt = 0 # A counter to name/list the ones without an organisation
|
|||
|
self.avoid_unk = False # Used to skip the ones without a valid Fiso_id.
|
|||
|
self.fiso_code = fiso_code
|
|||
|
|
|||
|
def load_from_iofxml(self, filename_=None, class_remap=None, clubs_ko=None, avoid_unk=False):
|
|||
|
if class_remap is None:
|
|||
|
class_remap = {}
|
|||
|
if clubs_ko is None:
|
|||
|
clubs_ko = []
|
|||
|
if filename_ is None:
|
|||
|
return
|
|||
|
self.avoid_unk = avoid_unk
|
|||
|
# print('Loading:', filename_)
|
|||
|
xmlf = ET.ElementTree(file=filename_)
|
|||
|
root = xmlf.getroot()
|
|||
|
self.ns = root.tag.split('}')[0] + '}'
|
|||
|
|
|||
|
self.name = (root.find('{0}Event/{0}Name'.format(self.ns))).text
|
|||
|
dt = (root.find('{0}Event/{0}StartTime/{0}Date'.format(self.ns))).text
|
|||
|
tm = (root.find('{0}Event/{0}StartTime/{0}Time'.format(self.ns))).text
|
|||
|
self.date = '{} {}'.format(dt, tm)
|
|||
|
|
|||
|
for child_of_root in root:
|
|||
|
clean_tag = child_of_root.tag.split("}")[1]
|
|||
|
if 'ClassResult' == clean_tag:
|
|||
|
for res in child_of_root:
|
|||
|
clean_res = res.tag.split("}")[1]
|
|||
|
if 'Class' == clean_res:
|
|||
|
id_ = res.find('{0}Id'.format(self.ns)).text
|
|||
|
# print('Class id:', id_)
|
|||
|
if id_ in class_remap:
|
|||
|
id_ = class_remap[id_]
|
|||
|
# print('Remaped to:', id_)
|
|||
|
name = res.find('{0}Name'.format(self.ns))
|
|||
|
if id_ in self.classes:
|
|||
|
now_category = self.classes[id_]
|
|||
|
else:
|
|||
|
now_category = Class(name=name.text, id_=id_)
|
|||
|
self.classes[id_] = now_category
|
|||
|
if 'Course' == clean_res:
|
|||
|
course_len = res.find('{0}Length'.format(self.ns))
|
|||
|
course_clib = res.find('{0}Climb'.format(self.ns))
|
|||
|
now_category.distance = float(course_len.text)
|
|||
|
now_category.climb = float(course_clib.text)
|
|||
|
if 'PersonResult' == clean_res:
|
|||
|
self._load_a_runner(res, now_category, clubs_ko=clubs_ko)
|
|||
|
|
|||
|
def _load_a_runner(self, pers_res, class_, clubs_ko=[]):
|
|||
|
"""
|
|||
|
It loads the runner details from an ElemnetTree.
|
|||
|
:param pers_res: the base element found (the PersonResult element)
|
|||
|
:type pers_res: ElementTree
|
|||
|
:return: the runner object
|
|||
|
:rtype: Runner
|
|||
|
"""
|
|||
|
fisoid = pers_res.find('{0}Person/{0}Id'.format(self.ns))
|
|||
|
ng = pers_res.find('{0}Person/{0}Name/{0}Given'.format(self.ns))
|
|||
|
nf = pers_res.find('{0}Person/{0}Name/{0}Family'.format(self.ns))
|
|||
|
clubid = pers_res.find('{0}Organisation/{0}Id'.format(self.ns))
|
|||
|
club_name = pers_res.find('{0}Organisation/{0}Name'.format(self.ns))
|
|||
|
bib = pers_res.find('{0}Result/{0}BibNumber'.format(self.ns))
|
|||
|
time = pers_res.find('{0}Result/{0}Time'.format(self.ns))
|
|||
|
position = pers_res.find('{0}Result/{0}Position'.format(self.ns))
|
|||
|
score = pers_res.find('{0}Result/{0}Score'.format(self.ns))
|
|||
|
status = pers_res.find('{0}Result/{0}Status'.format(self.ns))
|
|||
|
sicard = pers_res.find('{0}Result/{0}ControlCard'.format(self.ns))
|
|||
|
current_runner = None
|
|||
|
if fisoid.text is None:
|
|||
|
if self.avoid_unk:
|
|||
|
return
|
|||
|
if '{Libero}' == nf.text:
|
|||
|
return
|
|||
|
runner_id = 'UNK{:03d}'.format(self.unk_cnt)
|
|||
|
print('#### UNK ####' * 10)
|
|||
|
print(ng.text, nf.text)
|
|||
|
self.unk_cnt += 1
|
|||
|
else:
|
|||
|
runner_id = fisoid.text
|
|||
|
|
|||
|
if club_name is None or club_name.text is None or clubid is None or clubid.text is None:
|
|||
|
club_str = 'ORG{:03d}'.format(self.org_cnt)
|
|||
|
clubid_str = club_str
|
|||
|
self.org_cnt += 1
|
|||
|
else:
|
|||
|
club_str = club_name.text
|
|||
|
clubid_str = clubid.text
|
|||
|
|
|||
|
if 'OK' == status.text.upper() and clubid_str not in clubs_ko:
|
|||
|
# print fisoid.text, ng.text, nf.text,\
|
|||
|
# clubid.text, club_name.text, bib.text,\
|
|||
|
# time.text, position.text, score.text, status.text
|
|||
|
|
|||
|
if clubid_str not in self.clubs:
|
|||
|
new_club = Club(name=club_str, id_=clubid_str)
|
|||
|
self.clubs[clubid_str] = new_club
|
|||
|
|
|||
|
current_runner = Runner(name=ng.text,
|
|||
|
class_=class_.id_,
|
|||
|
club=club_str,
|
|||
|
total=float(time.text),
|
|||
|
bib=int(bib.text),
|
|||
|
fisoid=runner_id,
|
|||
|
place=int(position.text),
|
|||
|
score=float(score.text),
|
|||
|
family=nf.text,
|
|||
|
sicard=int(sicard.text),
|
|||
|
clubid=clubid_str
|
|||
|
)
|
|||
|
last_time = 0.0
|
|||
|
previous_cn = 0
|
|||
|
self.runners[current_runner.fisoid] = current_runner
|
|||
|
# print current_runner
|
|||
|
results = pers_res.find('{0}Result'.format(self.ns))
|
|||
|
# visit_deep(results, 1000)
|
|||
|
for spl in results.findall('{0}SplitTime'.format(self.ns)):
|
|||
|
cne = spl.find('{0}ControlCode'.format(self.ns))
|
|||
|
splte = spl.find('{0}Time'.format(self.ns))
|
|||
|
if cne is not None and cne.text is not None and splte is not None and splte.text is not None:
|
|||
|
cn = cne.text
|
|||
|
# @MC to float directly PLEASE!
|
|||
|
splt = splte.text
|
|||
|
tsplit = float(splt) - last_time
|
|||
|
last_time = float(splt)
|
|||
|
self.__leg_to_insert(previous_cn, cn, tsplit, current_runner)
|
|||
|
previous_cn = int(cn)
|
|||
|
else:
|
|||
|
break
|
|||
|
# do add the last (implicit) split time
|
|||
|
tsplit = current_runner.time - last_time
|
|||
|
self.__leg_to_insert(previous_cn, 999, tsplit, current_runner) # Last control point receives the name '999'
|
|||
|
else:
|
|||
|
if status.text not in self.statuses:
|
|||
|
self.statuses.append(status.text)
|
|||
|
if current_runner is not None:
|
|||
|
# print 'Adding {} {}'.format(current_runner,current_runner.fisoid)
|
|||
|
class_.runnersid.append(current_runner.fisoid) # do add the current runner to the class list
|
|||
|
if class_.min is None or class_.min > current_runner.time:
|
|||
|
class_.min = current_runner.time
|
|||
|
if class_.max is None or class_.max < current_runner.time:
|
|||
|
class_.max = current_runner.time
|
|||
|
class_.tot_t += current_runner.time
|
|||
|
class_.avg = class_.tot_t / float(len(class_.runnersid))
|
|||
|
|
|||
|
return current_runner
|
|||
|
|
|||
|
def __leg_to_insert(self, from_, to_, time_, runner):
|
|||
|
"""It computes the leg name, then adds/updates the leg in the legs list"""
|
|||
|
nfrom = int(from_)
|
|||
|
runner.add_cn(nfrom, time_)
|
|||
|
legname = "%03d-%03d" % (nfrom, int(to_))
|
|||
|
if legname not in self.legs: # Do instantiate a new leg if it is a new one
|
|||
|
newleg = Leg(legname)
|
|||
|
self.legs[legname] = newleg
|
|||
|
self.legs[legname].add_a_leg(time_, runner.class_, runner)
|
|||
|
|
|||
|
def adj_scores(self, k_factor=1.0):
|
|||
|
"""It copmutes the score foreach runner"""
|
|||
|
for clid in self.classes:
|
|||
|
cl = self.classes[clid]
|
|||
|
if len(cl.runnersid) > 0:
|
|||
|
# print '{}: winner time= {:6.1f}'.format(cl.id_, cl.min),
|
|||
|
for runid in cl.runnersid:
|
|||
|
new_score = k_factor * 100.0 * cl.min / self.runners[runid].time
|
|||
|
# print ' {}({:7.3f})'.format(runid, new_score),
|
|||
|
# if self.runners[runid].score != 0.0 and self.runners[runid].score != new_score:
|
|||
|
# print '!!!Score Mismatch: new={:7.3f} {}'.format(new_score, self.runners[runid])
|
|||
|
self.runners[runid].score = new_score
|
|||
|
# print
|
|||
|
|
|||
|
|
|||
|
class Class:
|
|||
|
"""A class defining a Category"""
|
|||
|
|
|||
|
def __init__(self, name=None, id_=None, distance=None, climb=None, kmsf=None):
|
|||
|
self.name = name
|
|||
|
self.id_ = id_
|
|||
|
self.distance = distance
|
|||
|
self.climb = climb
|
|||
|
self.kmsf = kmsf
|
|||
|
self.runnersid = [] # A list of runners.fifoid 's belonging to the class
|
|||
|
self.min = None
|
|||
|
self.max = None
|
|||
|
self.tot_t = 0.0
|
|||
|
self.avg = 0.0
|
|||
|
|
|||
|
def __str__(self):
|
|||
|
cl_string = 'Cat: '
|
|||
|
if self.name is not None:
|
|||
|
# cl_string += '{:6.6s} ({:3s})'.format(self.name.encode('utf-8'), self.id_)
|
|||
|
cl_string += '{:6.6s} ({:3s})'.format(self.name, self.id_)
|
|||
|
else:
|
|||
|
cl_string += "Sconos"
|
|||
|
if self.distance is not None:
|
|||
|
cl_string += ' Lunghezza: %.0f' % float(self.distance)
|
|||
|
if self.climb is not None:
|
|||
|
cl_string += ' Dislivello: %.0f ' % float(self.climb)
|
|||
|
if self.kmsf is not None:
|
|||
|
cl_string += ' kmsf: %.0f ' % float(self.kmsf)
|
|||
|
nr = len(self.runnersid)
|
|||
|
if nr > 0:
|
|||
|
cl_string += 'winner: {:6.1f} average: {:6.1f} last: {:4.0f} runners({:2d})'.format(
|
|||
|
self.min,
|
|||
|
self.avg,
|
|||
|
self.max,
|
|||
|
nr,
|
|||
|
)
|
|||
|
for runner in self.runnersid:
|
|||
|
cl_string += ' {}'.format(runner)
|
|||
|
return cl_string
|
|||
|
|
|||
|
|
|||
|
class Club:
|
|||
|
"""A class defining a Club"""
|
|||
|
|
|||
|
def __init__(self, name=None, id_=None, country=None, region=None):
|
|||
|
self.name = name
|
|||
|
self.id_ = id_
|
|||
|
self.country = country
|
|||
|
self.region = region
|
|||
|
|
|||
|
|
|||
|
class Runner:
|
|||
|
"""An object describing a single runner"""
|
|||
|
|
|||
|
def __init__(self, name=None, class_=None, club=None, bib=0, total=0.0,
|
|||
|
place=0, score=0.0, family=None, fisoid=None, sicard=None, clubid=None):
|
|||
|
self.name = name
|
|||
|
self.family = family
|
|||
|
self.fisoid = fisoid
|
|||
|
self.class_ = class_
|
|||
|
self.club = club
|
|||
|
self.bib = bib
|
|||
|
self.time = total
|
|||
|
self.place = place
|
|||
|
self.score = score
|
|||
|
self.sicard = int(sicard)
|
|||
|
self.clubid = clubid
|
|||
|
self.cn_seq = [(0, 0.0), ] # each list items is a tuple with the control number and the time (cn=0 for start
|
|||
|
# for k, v in self.__dict__.items():
|
|||
|
# print('{}= {} type={}'.format(k, v, type(v)))
|
|||
|
|
|||
|
def add_cn(self, c_n=None, tmm=0.0):
|
|||
|
"""Method to add a splittime/controlpoint in a sigle race"""
|
|||
|
if c_n is not None:
|
|||
|
self.cn_seq.append((c_n, tmm))
|
|||
|
|
|||
|
def full_name(self, given_first=False):
|
|||
|
if given_first:
|
|||
|
return '{} {}'.format(self.name, self.family.upper())
|
|||
|
else:
|
|||
|
return '{} {}'.format(self.family.upper(), self.name)
|
|||
|
|
|||
|
# if given_first:
|
|||
|
# return '{} {}'.format(self.name.encode('utf-8'), self.family.encode('utf-8').upper())
|
|||
|
# else:
|
|||
|
# return '{} {}'.format(self.family.encode('utf-8').upper(), self.name.encode('utf-8'))
|
|||
|
|
|||
|
def __str__(self):
|
|||
|
# runner_str = ''
|
|||
|
# try:
|
|||
|
# runner_str = '{:8.8} "{:10.10} {:10.10}" Categoria:{:5}'.format(
|
|||
|
# self.fisoid,
|
|||
|
# self.name,
|
|||
|
# self.family.upper(),
|
|||
|
# self.class_,
|
|||
|
# )
|
|||
|
# except:
|
|||
|
# print('%' * 100)
|
|||
|
# for k, v in self.__dict__.items():
|
|||
|
# print('{}= {} type={}'.format(k, v, type(v)))
|
|||
|
# print('/' * 100)
|
|||
|
|
|||
|
runner_str = ('{:8.8} "{:10.10} {:10.10}" Categoria:{:5.5} Posizione:{:3d}'
|
|||
|
# ' Tempo:{:6.1f} Pettorale:{:4} score:{:7.3f} sicard:{} Società :{} '
|
|||
|
' Tempo: {:5} Pettorale:{:4} score:{:7.3f} sicard:{} Società :{} '
|
|||
|
).format(
|
|||
|
self.fisoid,
|
|||
|
self.name,
|
|||
|
self.family.upper(),
|
|||
|
self.class_,
|
|||
|
self.place,
|
|||
|
time.strftime('%H:%M:%S', time.gmtime(self.time)),
|
|||
|
self.bib,
|
|||
|
self.score,
|
|||
|
self.sicard,
|
|||
|
self.club,
|
|||
|
)
|
|||
|
# print(type(self.sicard), type(self.bib))
|
|||
|
for j in range(1, len(self.cn_seq)):
|
|||
|
runner_str += '[%d/%.0f]' % self.cn_seq[j]
|
|||
|
return runner_str
|
|||
|
|
|||
|
|
|||
|
class Leg:
|
|||
|
"""A Leg in a race.
|
|||
|
It will hold a list of classes and a list of runners involved in that leg.
|
|||
|
"""
|
|||
|
|
|||
|
def __init__(self, name=None):
|
|||
|
self.name = name
|
|||
|
self.runners = []
|
|||
|
self.classes = []
|
|||
|
self.min = None
|
|||
|
self.max = None
|
|||
|
self.tot_t = 0.0
|
|||
|
self.avg = 0.0
|
|||
|
|
|||
|
def add_a_leg(self, tempo_, class_, runner):
|
|||
|
self.runners.append((tempo_, class_, runner))
|
|||
|
if self.max is None or self.max < tempo_:
|
|||
|
self.max = tempo_
|
|||
|
if self.min is None or self.min > tempo_:
|
|||
|
self.min = tempo_
|
|||
|
self.tot_t += tempo_
|
|||
|
self.avg = self.tot_t / float(len(self.runners))
|
|||
|
if class_ not in self.classes:
|
|||
|
self.classes.append(class_)
|
|||
|
|
|||
|
def __str__(self):
|
|||
|
leg_str = ('Tratta: {:.7s} corridori: {:3d} migliore: {:3.0f}'
|
|||
|
' media: {:5.1f} peggiore: {:3.0f} Categorie({:2d}):'
|
|||
|
).format(
|
|||
|
self.name.encode('utf-8'),
|
|||
|
len(self.runners),
|
|||
|
self.min,
|
|||
|
self.avg,
|
|||
|
self.max,
|
|||
|
len(self.classes),
|
|||
|
)
|
|||
|
for cl in self.classes:
|
|||
|
leg_str += ' ' + cl.encode('utf-8')
|
|||
|
return leg_str
|
|||
|
|
|||
|
|
|||
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|||
|
# test
|
|||
|
if __name__ == '__main__':
|
|||
|
# filename = 'c:/MC/O/Bologna_2017/201713.xml'
|
|||
|
filename = 'C:/Mc/Python/PyPrjs/TrofeoGPeroni2018/01_Carrega_2018199.xml'
|
|||
|
|
|||
|
gara = Race()
|
|||
|
gara.load_from_iofxml(filename)
|
|||
|
|
|||
|
print('Race name: ', gara.name)
|
|||
|
print(' Classes: ', len(gara.classes))
|
|||
|
print(' Runners: ', len(gara.runners))
|
|||
|
print(' Legs: ', len(gara.legs))
|
|||
|
print(' Unrecognized statuses: ', gara.statuses)
|
|||
|
|
|||
|
|
|||
|
def get_runners(rrr):
|
|||
|
return len(gara.legs[rrr].runners)
|
|||
|
|
|||
|
|
|||
|
"""
|
|||
|
for ll in sorted(gara.legs, key=get_runners):
|
|||
|
theleg = gara.legs[ll]
|
|||
|
print theleg
|
|||
|
print '-' * 70
|
|||
|
for rr in gara.runners:
|
|||
|
print gara.runners[rr]
|
|||
|
print '+' * 70
|
|||
|
for cl in gara.classes:
|
|||
|
print gara.classes[cl]
|
|||
|
print '/\\' * 35
|
|||
|
"""
|
|||
|
gara.adj_scores()
|
|||
|
|
|||
|
print('[-]' * 35)
|
|||
|
for rr in gara.runners:
|
|||
|
print(gara.runners[rr])
|