396 lines
15 KiB
Python
396 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])
|