Skip to content

Commit

Permalink
Release v3.1.44-20240313050848
Browse files Browse the repository at this point in the history
  • Loading branch information
esitarski committed Mar 13, 2024
2 parents d199009 + 3b9229f commit 794adac
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 77 deletions.
4 changes: 2 additions & 2 deletions Announcer.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,9 @@ def refresh( self ):
race = Model.race
if race and race.isRunning():
tRace = race.curRaceTime()
self.expected, self.recorded, _ = getExpectedRecorded()
self.expected, self.recorded, resultsIndex = getExpectedRecorded()
else:
self.expected = self.recorded = []
self.expected, self.recorded, resultsIndex = [], [], {}
self.resetTimer()

tRace = race.lastRaceTime()
Expand Down
3 changes: 2 additions & 1 deletion RaceDB.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ def fixUrl( url ):
if platform.system() == 'Windows':
url = url.replace('www.localhost', 'localhost')
elif re.search( '([0-9]{1,3}\.){3}[0-9]{1,3}', url ): # If a pure ip address, change to http and remove subdomain.
url = url.replace( 'https://www.', 'http://' )
url = url.replace( 'https://www.', 'http://' ).replace( 'http://www.', 'http://' )

globalRaceDBUrl = url
return url

Expand Down
11 changes: 8 additions & 3 deletions SeriesMgr/CategorySequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,15 @@ def gridAutoSize( self ):
self.Layout()
self.Refresh()

def refresh( self ):
def refresh( self, backgroundUpdate=False ):
model = SeriesModel.model
model.extractAllRaceResults() # Also harmonizes the categorySequence
categoryList = model.getCategoriesSorted()

if backgroundUpdate:
categoryList = []
else:
with wx.BusyCursor() as wait:
model.extractAllRaceResults() # Also harmonizes the categorySequence
categoryList = model.getCategoriesSorted()

Utils.AdjustGridSize( self.grid, len(categoryList) )
for row, c in enumerate(categoryList):
Expand Down
91 changes: 71 additions & 20 deletions SeriesMgr/GetModelInfo.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import re
import math
import pickle
import datetime
Expand Down Expand Up @@ -74,16 +75,30 @@ def safe_upper( f ):
except:
return f

def fix_uci_id( uci_id ):
uci_id = str(uci_id or '').strip()
if uci_id.endswith('.0'): # Correct if the uci_id is a floating point number.
uci_id = uci_id[-2:]
uci_id = re.sub( '[^0-9]', '', uci_id )
return uci_id
'''
# Check for valid UCI id.
if len(uci_id) == 11 and sum( int(d) for d in uci_id[:9] ) % 97 == int(uci_id[9:]):
return uci_id
return None
'''

class RaceResult:
def __init__( self, firstName, lastName, license, team, categoryName, raceName, raceDate, raceFileName, bib, rank, raceOrganizer,
raceURL=None, raceInSeries=None, tFinish=None, tProjected=None, primePoints=0, timeBonus=0, laps=1, pointsInput=None ):
raceURL=None, raceInSeries=None, tFinish=None, tProjected=None, primePoints=0, timeBonus=0, laps=1, pointsInput=None, uci_id=None ):
self.firstName = str(firstName or '')
self.lastName = str(lastName or '')

self.license = (license or '')
if isinstance(self.license, float) and int(self.license) == self.license:
self.license = int(self.license)
self.license = str(self.license)
self.uci_id = fix_uci_id( uci_id )

self.team = str(team or '')

Expand Down Expand Up @@ -112,19 +127,32 @@ def __init__( self, firstName, lastName, license, team, categoryName, raceName,
@property
def teamIsValid( self ):
return self.team and self.team.lower() not in {'no team', 'no-team', 'independent'}


'''
def keySort( self ):
fields = ['categoryName', 'lastName', 'firstName', 'license', 'raceDate', 'raceName']
if SeriesModel.model.uciIdKey:
fields = 'categoryName', 'lastName', 'firstName', 'uci_id', 'raceDate', 'raceName'
else:
fields = 'categoryName', 'lastName', 'firstName', 'license', 'raceDate', 'raceName'
return tuple( safe_upper(getattr(self, a)) for a in fields )
def keyMatch( self ):
fields = ['categoryName', 'lastName', 'firstName', 'license']
if SeriesModel.model.uciIdKey:
fields = 'categoryName', 'lastName', 'firstName', 'uci_id'
else:
fields = 'categoryName', 'lastName', 'firstName', 'license'
return tuple( safe_upper(getattr(self, a)) for a in fields )
'''

def key( self ):
k = self.full_name.upper()
s = Utils.removeDiacritic(k) # If the full name has characters that have no ascii representation, return it as-is.
return (s if len(s) == len(k) else k, Utils.removeDiacritic(self.license))
if SeriesModel.model.riderKey == SeriesModel.SeriesModel.KeyByUciId:
return self.uci_id
elif SeriesModel.model.riderKey == SeriesModel.SeriesModel.KeyByLicense:
return self.license
else:
k = self.full_name.upper()
s = Utils.removeDiacritic(k) # If the full name has characters that have no ascii representation, return it as-is.
return (s if len(s) == len(k) else k, Utils.removeDiacritic(self.license))

def keyTeam( self ):
k = self.team.upper()
Expand Down Expand Up @@ -155,6 +183,11 @@ def toInt( n ):
def ExtractRaceResultsExcel( raceInSeries, seriesModel ):
ret = { 'success':True, 'explanation':'success', 'raceResults':[], 'licenseLinkTemplate':None }

if not os.path.exists( raceInSeries.getFileName() ):
ret['success'] = False
ret['explanation'] = 'File not found'
return ret

getReferenceName = seriesModel.getReferenceName
getReferenceLicense = seriesModel.getReferenceLicense
getReferenceTeam = seriesModel.getReferenceTeam
Expand All @@ -168,10 +201,25 @@ def ExtractRaceResultsExcel( raceInSeries, seriesModel ):
if sfa[0] == 'pos':
posHeader = set( a.lower() for a in sfa[1] )
break

# Check if this is a UCI Dataride spreadsheet.
uciDatarideSheets = {'General', 'Reference', 'Country Reference'}
isUCIDataride = any( (s.strip() in uciDatarideSheets) for s in excel.sheet_names() )
if isUCIDataride:
# Get the category name as the directory name.
uciCategoryName = os.path.basename( os.path.dirname(raceInSeries.getFileName()) )
else:
uciCategoryName = None

for sheetName in excel.sheet_names():
hasPointsInput, defaultPointsInput = False, None
fm = None
categoryNameSheet = sheetName.strip()
if isUCIDataride:
if categoryNameSheet in uciDatarideSheets:
continue
categoryNameSheet = uciCategoryName

for row in excel.iter_list(sheetName):
if fm:
f = fm.finder( row )
Expand All @@ -187,8 +235,9 @@ def ExtractRaceResultsExcel( raceInSeries, seriesModel ):
'firstName': str(f('first_name','')).strip(),
'lastName' : str(f('last_name','')).strip(),
'license': str(f('license_code','')).strip(),
'uci_id': f('uci_id',''),
'team': str(f('team','')).strip(),
'categoryName': f('category_code',None),
'categoryName': categoryNameSheet if isUCIDataride else f('category_code',None),
'laps': f('laps',1),
'pointsInput': f('points',defaultPointsInput),
}
Expand Down Expand Up @@ -340,7 +389,7 @@ def ExtractRaceResultsCrossMgr( raceInSeries, seriesModel ):
'raceURL': raceURL,
'raceInSeries': raceInSeries,
}
for fTo, fFrom in [('firstName', 'FirstName'), ('lastName', 'LastName'), ('license', 'License'), ('team', 'Team')]:
for fTo, fFrom in [('firstName', 'FirstName'), ('lastName', 'LastName'), ('license', 'License'), ('uci_id', 'UCIID'), ('team', 'Team')]:
info[fTo] = getattr(rr, fFrom, '')

if not info['firstName'] and not info['lastName']:
Expand Down Expand Up @@ -446,26 +495,28 @@ def GetPotentialDuplicateFullNames( riderNameLicense ):
return {full_name for full_name, licenses in nameLicense.items() if len(licenses) > 1}

def GetCategoryResults( categoryName, raceResults, pointsForRank, useMostEventsCompleted=False, numPlacesTieBreaker=5 ):
scoreByTime = SeriesModel.model.scoreByTime
scoreByPercent = SeriesModel.model.scoreByPercent
scoreByTrueSkill = SeriesModel.model.scoreByTrueSkill
bestResultsToConsider = SeriesModel.model.bestResultsToConsider
mustHaveCompleted = SeriesModel.model.mustHaveCompleted
showLastToFirst = SeriesModel.model.showLastToFirst
considerPrimePointsOrTimeBonus = SeriesModel.model.considerPrimePointsOrTimeBonus
scoreByPointsInput = SeriesModel.model.scoreByPointsInput
model = SeriesModel.model

scoreByTime = model.scoreByTime
scoreByPercent = model.scoreByPercent
scoreByTrueSkill = model.scoreByTrueSkill
bestResultsToConsider = model.bestResultsToConsider
mustHaveCompleted = model.mustHaveCompleted
showLastToFirst = model.showLastToFirst
considerPrimePointsOrTimeBonus = model.considerPrimePointsOrTimeBonus
scoreByPointsInput = model.scoreByPointsInput

# Get all results for this category.
raceResults = [rr for rr in raceResults if rr.categoryName == categoryName]
if not raceResults:
return [], [], set()

# Create a map for race filenames to grade.
raceGrade = { race.getFileName():race.grade for race in SeriesModel.model.races }
gradesUsed = sorted( set(race.grade for race in SeriesModel.model.races) )
raceGrade = { race.getFileName():race.grade for race in model.races }
gradesUsed = sorted( set(race.grade for race in model.races) )

# Assign a sequence number to the races in the specified order.
for i, r in enumerate(SeriesModel.model.races):
for i, r in enumerate(model.races):
r.iSequence = i

# Get all races for this category.
Expand Down
54 changes: 51 additions & 3 deletions SeriesMgr/MainWin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import traceback
import xlwt
import base64
import threading

FontSize = 20

Expand Down Expand Up @@ -137,7 +138,27 @@ def ShowTipAtStartup():
pass

#----------------------------------------------------------------------------------

class Counter():
count = 0
lock = threading.Lock()

def __enter__(self):
with Counter.lock:
Counter.count += 1
return Counter.count

def __exit__(self, type, value, traceback):
with Counter.lock:
Counter.count -= 1

@staticmethod
def getCount():
with Counter.lock:
return Counter.count

#----------------------------------------------------------------------------------

class MainWin( wx.Frame ):
def __init__( self, parent, id=wx.ID_ANY, title='', size=(200,200) ):
wx.Frame.__init__(self, parent, id, title, size=size)
Expand Down Expand Up @@ -276,6 +297,9 @@ def addPage( page, name ):

self.notebook.SetSelection( 0 )

# Pages that are updated in background.
self.backgroundUpdatePages = {'results', 'teamResults', 'categorySequence'}

#-----------------------------------------------------------------------
self.helpMenu = wx.Menu()

Expand Down Expand Up @@ -894,6 +918,10 @@ def refresh( self ):
self.refreshCurrentPage()

def callPageRefresh( self, i ):
if Counter.getCount() and self.attrClassName[self.notebook.GetSelection()][0] in self.backgroundUpdatePages:
# Don't update the page while a background update is running.
# The page will be updated when the update finishes.
return
try:
self.pages[i].refresh()
except (AttributeError, IndexError) as e:
Expand All @@ -913,11 +941,31 @@ def onPageChanging( self, event ):

def refreshAll( self ):
self.refresh()
iSelect = self.notebook.GetSelection()

model = SeriesModel.model

self.results.refresh( backgroundUpdate=True )
self.teamResults.refresh( backgroundUpdate=True )
self.categorySequence.refresh( backgroundUpdate=True )

# Use a thread to update Results and TeamResults.
# This eliminates user timeouts.
def backgroundRefresh():
with Counter():
raceResults = model.extractAllRaceResults()
wx.CallAfter( self.results.refresh )
wx.CallAfter( self.teamResults.refresh )
wx.CallAfter( self.categorySequence.refresh )
wx.CallAfter( wx.EndBusyCursor ) # End the busy cursor at the end of the thread.

wx.BeginBusyCursor() # Start a busy cursor befpre we start the thread.
t = threading.Thread( target=backgroundRefresh, name='refreshResultsBackground' )
t.start()

pagesToSkip = self.backgroundUpdatePages | { self.attrClassName[self.notebook.GetSelection()][0] }
for i, p in enumerate(self.pages):
if i != iSelect:
if self.attrClassName[i][0] not in pagesToSkip:
self.callPageRefresh( i )
self.setTitle()

def readResetAll( self ):
for p in self.pages:
Expand Down
37 changes: 29 additions & 8 deletions SeriesMgr/Options.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import wx
import wx.grid as gridlib
import wx.lib.agw.floatspin as FS

import os
import re
import sys
Expand All @@ -11,33 +10,55 @@
class Options(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)

#--------------------------------------------------------------------------
box = wx.StaticBox( self, -1, _("Output Options") )
bsizer = wx.StaticBoxSizer( box, wx.VERTICAL )

fgs = wx.FlexGridSizer( rows=0, cols=2, vgap=4, hgap=2 )
fgs.AddGrowableCol( 1, proportion=1 )

self.showLastToFirst = wx.CheckBox( self, label=_("Show Race Results in Last-to-First Order") )
self.showLastToFirst.SetValue( True )
fgs.Add( wx.StaticText(self) )
fgs.Add( self.showLastToFirst )
bsizer.Add( fgs, 1, flag=wx.EXPAND )
bsizer.Add( self.showLastToFirst, 1, flag=wx.EXPAND )

sizer.Add(bsizer, 0, flag=wx.EXPAND|wx.ALL, border = 4 )

#--------------------------------------------------------------------------
box = wx.StaticBox( self, -1, _("Input Options") )
bsizer = wx.StaticBoxSizer( box, wx.VERTICAL )

self.riderKey = wx.Choice( self, choices=[
_("Use First and Last Name to match participants"),
_("Use UCI ID to match participants"),
_("Use License to match participants"),
]
)
self.riderKey.SetSelection( SeriesModel.SeriesModel.KeyByName )
bsizer.Add( self.riderKey )
bsizer.Add( wx.StaticText(self, label='''
The CrossMgr or Spreadsheet results must include the fields you are using to match.
If you have missing UCI ID or License fields, the safest option is to match
using First and Last Name.'''
))

sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(bsizer, 0, flag=wx.EXPAND|wx.ALL, border = 4 )

#--------------------------------------------------------------------------

self.SetSizer(sizer)

def refresh( self ):
model = SeriesModel.model
self.showLastToFirst.SetValue( model.showLastToFirst )
self.riderKey.SetSelection( model.uciIdKey )

def commit( self ):
model = SeriesModel.model
if model.showLastToFirst != self.showLastToFirst.GetValue():
model.showLastToFirst = self.showLastToFirst.GetValue()
model.changed = True
if model.riderKey != self.uciIdKey.GetSelection():
model.riderKey = self.uciIdKey.GetSelection()
model.changed = True

########################################################################

Expand Down
6 changes: 4 additions & 2 deletions SeriesMgr/Races.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def commit( self ):
raceList.append( (fileName, pname, pteamname, grade) )

model = SeriesModel.model
model.setRaces( raceList )
racesChanged = model.setRaces( raceList )

if self.seriesName.GetValue() != model.name:
model.name = self.seriesName.GetValue()
Expand All @@ -228,8 +228,10 @@ def commit( self ):
if self.organizerName.GetValue() != model.organizer:
model.organizer = self.organizerName.GetValue()
model.changed = True

wx.CallAfter( self.refresh )
if racesChanged and Utils.getMainWin():
wx.CallAfter( Utils.getMainWin().refreshAll )

#----------------------------------------------------------------------------

Expand Down
Loading

0 comments on commit 794adac

Please sign in to comment.