solo-auswertung/src/solo_turnier/worker.py

323 lines
11 KiB
Python
Raw Normal View History

2022-11-14 19:01:32 +00:00
import logging
from solo_turnier import html_parser
2022-11-27 08:10:17 +00:00
class HtmlPerson:
def __init__(self, name, id, group):
self.name = name
self.id = id
self.group = group
def __repr__(self):
return f'{self.name} ({self.id}, {self.group})'
2022-11-14 19:01:32 +00:00
class ResultPerson:
2022-11-15 09:48:50 +00:00
def __init__(self, firstName, lastName, club, id = None, group = None):
2022-11-14 19:01:32 +00:00
self.firstName = firstName
self.lastName = lastName
self.name = f'{firstName} {lastName}'
self.club = club
self.id = id
2022-11-15 09:48:50 +00:00
self.group = group
2022-11-14 19:01:32 +00:00
@staticmethod
def extractFromResultRow(row: ResultRow):
return ResultPerson(
firstName=row.firstName,
lastName=row.lastName,
club=row.club
)
2022-11-15 09:48:50 +00:00
def __eq__(self, o):
if not isinstance(o, ResultPerson):
return False
return (
self.firstName == o.firstName and
self.lastName == o.lastName and
self.club == o.club and
self.id == o.id
)
def __repr__(self):
if self.id is None:
return f'{self.name} ({self.club})'
else:
return f'{self.name} ({self.club}) [{self.id}]'
def __hash__(self):
text = str(self)
return text.__hash__()
2022-11-14 19:01:32 +00:00
class CompetitionResult:
def __init__(self, dance, group, class_, place, placeTo, id, competitionGroup, competitionClass):
2022-11-14 19:01:32 +00:00
self.dance = dance
2022-11-15 09:48:50 +00:00
self.group = group
2022-11-14 19:01:32 +00:00
self.class_ = class_
self.place = place
self.placeTo = placeTo
self.id = int(id)
self.competitionGroup = competitionGroup
self.competitionClass = competitionClass
2022-11-15 13:38:46 +00:00
self.finalist = None
2022-11-14 19:01:32 +00:00
@staticmethod
def extractFromResultRow(row: ResultRow):
return CompetitionResult(
dance=row.dance,
2022-11-15 09:48:50 +00:00
group=row.group,
2022-11-14 19:01:32 +00:00
class_=row.class_,
place=row.place, placeTo=row.placeTo,
id=row.id,
competitionGroup=row.competitionGroup,
competitionClass=row.competitionClass
2022-11-14 19:01:32 +00:00
)
2022-11-15 09:48:50 +00:00
def __repr__(self):
if self.place == self.placeTo:
result = f'{self.place}.'
else:
result = f'{self.place}.-{self.placeTo}.'
2022-11-15 13:38:46 +00:00
if self.finalist == True:
finalist = '[F]'
else:
finalist = ''
return f'Result[{self.id}]({self.group} {self.class_} {self.dance} as {result}{finalist})'
2022-11-15 09:48:50 +00:00
def __eq__(self, o):
if not isinstance(o, CompetitionResult):
return False
return (
self.dance == o.dance and
self.competitionClass == o.competitionClass and
self.competitionGroup == o.competitionGroup and
2022-11-15 09:48:50 +00:00
self.place == o.place and self.placeTo == o.placeTo and
self.id == o.id
)
2022-11-14 19:01:32 +00:00
2022-11-15 09:48:50 +00:00
2022-11-27 08:10:17 +00:00
class PreviewWorker:
def __init__(self):
self.l = logging.getLogger('solo_turnier.worker.PreviewWorker')
def filterFilesPreview(self, files: list[str]) -> dict[str, html_parser.HtmlParser]:
self.l.debug('Filtering the list of parsers by removing all non preview entries.')
ret = {}
for file in files:
with open(file, 'r') as fp:
text = fp.read()
parser = html_parser.HtmlParser(text)
try:
data = parser.guessDataFromHtmlTitle()
except:
self.l.error(f'Unable to parse html file in {file}. Please check manually.')
continue
if data['class_'] == 'Sichtung':
self.l.debug(f"Found candidate in {file}. Adding to the list.")
ret[file] = parser
else:
self.l.debug(f'Rejecting file {file} as the name {data["class_"]} did not match.')
return ret
def __extractPersonsFromSinglePreview(self, parser: html_parser.HtmlParser):
imported = parser.parsePreparationRound()
parser.cleanPreparationRoundImport(imported)
data = imported['data']
self.l.log(5, data)
if data['titles'][0] != 'Wertungsrichter':
self.l.fatal('Cannot parse the parsed content of the preview file.')
raise Exception('Incompatible export file')
ids = []
names = []
indices = []
for index, e in enumerate(data['table'][0]):
if e['text'] == '':
continue
indices.append(index)
ids.append(e['text'])
names.append(e['meta'])
groups = []
if data['titles'][-1] == 'Startgruppe':
self.l.debug('Combined competition found. Extracting group from table')
groups = [data['table'][-1][idx]['text'] for idx in indices]
else:
self.l.debug('Using group from the title.')
group = parser.guessDataFromHtmlTitle(imported['title'])['group']
groups = [group for i in indices]
ret = []
for i in range(len(ids)):
ret.append(HtmlPerson(names[i], ids[i], groups[i]))
self.l.log(5, ret)
return ret
def extractPersonsFromPreview(self, parsers):
for file in parsers:
self.l.debug('Extracting person data from %s', file)
self.__extractPersonsFromSinglePreview(parsers[file])
2022-11-15 09:48:50 +00:00
class DataWorker:
def __init__(self):
self.l = logging.getLogger('solo_turnier.worker')
def combineRowsByPerson(self, rows: list[ResultRow]) -> dict[ResultPerson, list[CompetitionResult]]:
ret = {}
for row in rows:
result = CompetitionResult.extractFromResultRow(row)
if result.place == '-' or result.placeTo == '-':
continue
person = ResultPerson.extractFromResultRow(row)
if person not in ret:
ret[person] = []
ret[person].append(result)
return ret
def checkUniqueIds(self, data: dict[ResultPerson, list[CompetitionResult]]) -> bool:
unique = True
for person in data:
ids = set([c.id for c in data[person]])
if len(ids) == 1:
person.id = list(ids)[0]
else:
unique = False
return unique
"""
Return a tuple
The first one is True, if all persons could be unambiguously identified a group
The second one is True if there was the need to override a group but it was possible to extract from other data
The second one can be seen as a warning
"""
def consolidateGroups(self, data:dict[ResultPerson, list[CompetitionResult]]) -> tuple[bool, bool]:
ambiguous = False
warnChange = False
unambiguousGroups = set(['Kin.', 'Jun.', 'Jug.'])
combinations = set(['Kin./Jun.', 'Jun./Jug.'])
for person in data:
groupsRaw = set([c.group for c in data[person]])
unknown = groupsRaw.difference(unambiguousGroups).difference(combinations)
if len(unknown) > 0:
raise Exception(f'There were unknown groups found for {person}: {unknown}')
numUnambiguousGroups = len(groupsRaw.intersection(unambiguousGroups))
if numUnambiguousGroups == 0:
if len(groupsRaw) == 2:
warnChange = True
person.group = 'Jun.'
else:
ambiguous = True
if len(groupsRaw) == 1:
person.group = list(groupsRaw)[0]
elif numUnambiguousGroups == 1:
if len(groupsRaw.intersection(combinations)) > 0:
warnChange = True
person.group = list(groupsRaw.intersection(unambiguousGroups))[0]
else:
raise Exception(f'{person} cannot have different groups.')
return (not ambiguous, warnChange)
def _createHtmlLUT(self, htmlImports: list[html_parser.HtmlImport]):
ret = {}
2022-11-26 07:43:15 +00:00
parser = html_parser.HtmlParser('')
for imp in htmlImports:
parsed = parser.guessDataFromHtmlTitle(imp.title)
key = (parsed['group'], parsed['class_'], parsed['dance'])
ret[key] = imp
2022-11-16 09:22:09 +00:00
self.l.debug('LUT[%s] = %s', key, imp)
self.l.debug('LUT completed')
return ret
def mergeHtmlData(self, data:dict[ResultPerson, list[CompetitionResult]], htmlImports: list[html_parser.HtmlImport]):
lut = self._createHtmlLUT(htmlImports)
for person in data:
for competition in data[person]:
key = (competition.competitionGroup, competition.competitionClass, competition.dance)
htmlImport = lut[key]
participant = htmlImport.participants[str(competition.id)]
if participant.name != person.name:
self.l.error(f'Names for {person} and participant in HTML import ({participant}) do not match. Please check carefully.')
competition.finalist = participant.finalist
def getAllDancesInCompetitions(self, data:dict[ResultPerson, list[CompetitionResult]]) -> list[str]:
allDances = [
'Samba', 'Cha Cha', 'Rumba', 'Paso Doble', 'Jive',
'Langs. Walzer', 'Tango', 'Wiener Walzer', 'Slowfox', 'Quickstep'
]
dancesPresent = {d: False for d in allDances}
for person in data:
for competition in data[person]:
dancesPresent[competition.dance] = True
return [d for d in allDances if dancesPresent[d]]
def collectPersonsInGroups(self, data:dict[ResultPerson, list[CompetitionResult]]) -> list[tuple[str, list[ResultPerson]]]:
groups = {
'Kin.': [p for p in data.keys() if p.group == 'Kin.'],
'Jun.': [p for p in data.keys() if p.group == 'Jun.'],
'Jug.': [p for p in data.keys() if p.group == 'Jug.'],
}
found = groups['Kin.'] + groups['Jun.'] + groups['Jug.']
groups['Sonst'] = [p for p in data.keys() if p not in found]
return groups
def sortPersonsInGroup(self, persons: list[ResultPerson]) -> list[ResultPerson]:
ids = [p.id for p in persons]
def decorateByName(p: ResultPerson):
return (f'{p.name} ({p.club})', p)
def decorateById(p: ResultPerson):
return (p.id, p)
if any([id == None for id in ids]):
# We need to sort by name
decorated = [decorateByName(p) for p in persons]
showIds = False
else:
decorated = [decorateById(p) for p in persons]
showIds = True
decorated.sort()
return ([d[1] for d in decorated], showIds)
def mapPersonResultsToDanceList(self, results: list[CompetitionResult], dances: list[str]) -> list[CompetitionResult|None]:
ret = []
for dance in dances:
competitions = [c for c in results if c.dance == dance]
if len(competitions) == 0:
ret.append(None)
elif len(competitions) > 1:
raise Exception(f'Multiple competitions with the same dance "{dance}" found.')
else:
ret.append(competitions[0])
return ret
2022-11-27 08:10:17 +00:00