TrofeoGP_2025/pyiofload/__init__.py

397 lines
15 KiB
Python
Raw Permalink Normal View History

2025-03-26 15:39:35 +01:00
#!/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])