Skip to content

Commit

Permalink
Release v3.1.48-20240420090842
Browse files Browse the repository at this point in the history
  • Loading branch information
esitarski committed Apr 20, 2024
2 parents 2ba0301 + fa76dd6 commit ce880ef
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 49 deletions.
27 changes: 14 additions & 13 deletions CrossMgrHtml/TTCountdown.html

Large diffs are not rendered by default.

37 changes: 24 additions & 13 deletions ForecastHistory.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def __init__( self, parent, id = wx.ID_ANY, style = 0 ):
self.entryCur = None
self.orangeColour = wx.Colour(255, 165, 0)
self.redColour = wx.Colour(255, 51, 51)
self.yellowColour = wx.Colour(255, 255, 102)
self.groupColour = wx.Colour(220, 220, 220)

self.callFutureRefresh = None
Expand Down Expand Up @@ -625,20 +626,25 @@ def refresh( self ):
#------------------------------------------------------------------
# Highlight the leaders in the expected list.
iBeforeLeader = None
# Highlight the leader by category.
# Highlight the leader by category. Also highlight missing riders, riders outside of 80%, rider eligible for early pull.
catNextTime = {}
outsideTimeBound = set()
outsideTimeBound, outsideEarlyPull = set(), set()
for r, e in enumerate(expected):
if e.num in nextCatLeaders:
backgroundColour[(r, iExpectedNoteCol)] = wx.GREEN
catNextTime[nextCatLeaders[e.num]] = e.t
if e.num == leaderNext:
backgroundColour[(r, iExpectedNumCol)] = wx.GREEN
iBeforeLeader = r
elif tRace < tRaceLength and race.isOutsideTimeBound(e.num):
backgroundColour[(r, iExpectedNoteCol)] = self.redColour
textColour[(r, iExpectedNoteCol)] = wx.WHITE
outsideTimeBound.add( e.num )
elif tRace < tRaceLength:
isOutsideTimeBound, isOutsideEarlyPull = race.isOutsideTimeBound(e.num)
if isOutsideTimeBound:
backgroundColour[(r, iExpectedNoteCol)] = self.redColour
textColour[(r, iExpectedNoteCol)] = wx.WHITE
outsideTimeBound.add( e.num )
elif isOutsideEarlyPull:
backgroundColour[(r, iExpectedNoteCol)] = self.yellowColour
outsideEarlyPull.add( e.num )

data = [None] * iExpectedColMax
data[iExpectedNumCol] = ['{}'.format(e.num) for e in expected]
Expand All @@ -661,7 +667,7 @@ def getNoteExpected( e ):
elif e.t < tMissing:
return _('miss')
elif position >= 0:
return resultsIndex[e.num]._getExpectedLapChar(tRace) + Utils.ordinal(position)
return ('⌦ ' if e.num in outsideEarlyPull else '') + resultsIndex[e.num]._getExpectedLapChar(tRace) + Utils.ordinal(position)
else:
return ' '

Expand Down Expand Up @@ -699,7 +705,7 @@ def getWave( e ):

backgroundColour = {}
textColour = {}
outsideTimeBound = set()
outsideTimeBound, outsideEarlyPull = set(), set()
# Highlight the leader in the recorded list.
for r, e in enumerate(recorded):
if e.isGap():
Expand All @@ -709,10 +715,15 @@ def getWave( e ):
backgroundColour[(r, iRecordedNoteCol)] = wx.GREEN
if e.num == leaderPrev:
backgroundColour[(r, iRecordedNumCol)] = wx.GREEN
elif tRace < tRaceLength and race.isOutsideTimeBound(e.num):
backgroundColour[(r, iRecordedNoteCol)] = self.redColour
textColour[(r, iRecordedNoteCol)] = wx.WHITE
outsideTimeBound.add( e.num )
elif tRace < tRaceLength:
isOutsidetimeBound, isOutsideEarlyPull = race.isOutsideTimeBound(e.num)
if isOutsidetimeBound:
backgroundColour[(r, iRecordedNoteCol)] = self.redColour
textColour[(r, iRecordedNoteCol)] = wx.WHITE
outsideTimeBound.add( e.num )
elif isOutsideEarlyPull:
backgroundColour[(r, iRecordedNoteCol)] = self.yellowColour
outsideEarlyPull.add( e.num )

data = [None] * iRecordedColMax
data[iRecordedNumCol] = ['{}{}'.format(e.num,"\u2190" if IsRiderFinished(e.num, e.t) else '') if e.num > 0 else ' ' for e in recorded]
Expand All @@ -733,7 +744,7 @@ def getNoteHistory( e ):
if position == 1:
return resultsIndex[e.num]._getRecordedLapChar(tRace) + _('Lead')
elif position >= 0:
return resultsIndex[e.num]._getRecordedLapChar(tRace) + Utils.ordinal(position)
return ('⌦ ' if e.num in outsideEarlyPull else '') + resultsIndex[e.num]._getRecordedLapChar(tRace) + Utils.ordinal(position)
else:
return ' '

Expand Down
23 changes: 18 additions & 5 deletions Model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1849,35 +1849,48 @@ def getCategoryRaceLaps( self ):
return crl

def isOutsideTimeBound( self, num ):
if self.isTimeTrial:
return False, False

# Returns boolean tuple (Outside80TimeBound, OutsideEarlyPullTimebound)
category = self.getCategory( num )

rule80Time = self.getRule80CountdownTime(category)
if not rule80Time:
return False
return False, False

try:
leaderTimes = self.getCategoryTimesNums()[category][0]
except KeyError:
leaderTimes = self.getLeaderTimesNums()[0]

if not leaderTimes:
return False
return False, False

# Get the time the leader started this lap.
t = self.curRaceTime()
leaderLap = bisect.bisect_right(leaderTimes, t) - 1
leaderTime = leaderTimes[leaderLap]

# Get the rider time for the same lap.
# No special case here. if the rider has been lapped, the lap time difference will exceed rule80Time.
entries = self.interpolate()
i = bisect.bisect_left( entries, Entry(num = 0, lap = leaderLap, t = leaderTime, interp = False) )
try:
riderTime = next((e.t for e in itertools.islice(entries, i, len(entries)) if e.num == num and e.lap == leaderLap))
except StopIteration:
return False
return False, False

# Get the specified number of laps for this category. If none, don't compute early pull time.
raceLaps = self.getNumLapsFromCategory( category )
earlyPullTime = min( rule80Time, rule80Time * leaderLap / raceLaps ) if raceLaps and (leaderLap >= 2 and raceLaps - 3 <= leaderLap < raceLaps) else math.inf

# Check if the difference exceeds the rule80 time.
return riderTime - leaderTime > rule80Time
# print( "Category={}, leaderLap={}, laps={}, rule80Time={:.2f}, earlyPullTime={:.2f}".format( category.name, leaderLap, raceLaps, rule80Time, earlyPullTime) )

# Check if the difference exceeds the rule80 time and the early pull time (pro-rated rule80 time).
outsideRule80 = (riderTime - leaderTime > rule80Time)
#return outsideRule80, outsideRule80 or (riderTime - leaderTime > earlyPullTime)
return outsideRule80, False

def getCatPrevNextLeaders( self, t ):
''' Return a dict accessed by number referring to category. '''
Expand Down
17 changes: 12 additions & 5 deletions SeriesMgr/CmdLine.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,21 @@
import Results
import pickle

def CmdLine( args ):
def CmdLine( args ):
import pdb; pdb.set_trace()

seriesFileName = None
if args.series:
seriesFileName = args.series
ext = os.path.splitext( seriesFileName )
if ext != '.smn':
print( 'series file "{}" lacks .smn extension.'.format(seriesFileName), file=sys.stderr )
return 1
try:
with open(seriesFileName, 'rb') as fp:
SeriesModel.model = pickle.load( fp, encoding='latin1', errors='replace' )
except IOError:
print( 'cannot open series file "{}".'.format(seriesFileName) )
print( 'cannot open series file "{}".'.format(seriesFileName), file=sys.stderr )
return 1
SeriesModel.model.postReadFix()

Expand Down Expand Up @@ -42,7 +48,7 @@ def CmdLine( args ):
else:
fileName = components[0]
if not any( fileName.endswith(suffix) for suffix in ('.xlsx', 'xlsm', '.xls') ):
print( 'unrecognized file suffix "{}".'.format(fileName) )
print( 'unrecognized file suffix "{}".'.format(fileName), file=sys.stderr )
return 2

pointStructures = None
Expand All @@ -52,7 +58,7 @@ def CmdLine( args ):
break

if pointStructures is None:
print( 'cannot find points structure "{}".'.format(pointStructuresName) )
print( 'cannot find points structure "{}".'.format(pointStructuresName), file=sys.stderr )
return 3

races.append( SeriesModel.Race(fileName, pointStructures) )
Expand All @@ -67,7 +73,8 @@ def CmdLine( args ):
score_by_points, score_by_time, score_by_percent = False, False, True

output_file = args.output or ((os.path.splitext(args.series)[0] + '.html') if args.series else 'SeriesMgr.html')
with open( output_file, 'w' ) as f:
results = SeriesModel.model.extractAllRaceResults()
with open( output_file, 'w', encoding='utf8' ) as f:
f.write( Results.getHtml(seriesFileName) )

return 0
7 changes: 5 additions & 2 deletions SeriesMgr/MainWin.py
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,7 @@ def openSeries( self, fileName ):
with open(fileName, 'rb') as fp:
try:
SeriesModel.model = pickle.load( fp, encoding='latin1', errors='replace' )
except:
except Exception as e:
fp.seek( 0 )
SeriesModel.model = ModuleUnpickler( fp, module='SeriesMgr', encoding='latin1', errors='replace' ).load()
except IOError:
Expand Down Expand Up @@ -1062,7 +1062,10 @@ def MainLoop():
fileName = args.filename

# Try to load a series.
if fileName and fileName.lower().endswith('.smn'):
if fileName:
if os.path.splitext( fileName )[1] != '.smn':
print( 'Cannot open non SeriesMgr file "{}". Aborting.'.format( filename ), file=sys.stderr )
sys.exit( 1 )
try:
mainWin.openSeries( fileName )
except (IndexError, AttributeError, ValueError):
Expand Down
2 changes: 1 addition & 1 deletion SeriesMgr/SeriesModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ def clearCache( self ):

@memoize
def _extractAllRaceResultsCore( self ):
with modelUpdateLock:
with modelUpdateLock:
# Extract all race results in parallel. Arguments are the race info and this series (to get the alias lookups).
with Pool() as p:
p_results = p.starmap( GetModelInfo.ExtractRaceResults, ((r,self) for r in self.races) )
Expand Down
2 changes: 1 addition & 1 deletion Version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
AppVerName="CrossMgr 3.1.47-private"
AppVerName="CrossMgr 3.1.48-private"
29 changes: 26 additions & 3 deletions helptxt/MainScreen.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,35 @@ Shows the numbers of riders expected to arrive. The columns in the table are as
Column|Description
:-----|:----------
Bib|The rider's number
Note|Shows the riders position by category. Leaders are highlighted in green. The Race Leader is highlighted with a full green bar. Riders outside of the 80% time rule are shown in red.
Note|Shows the riders position by category. Leaders are highlighted in green. The Race Leader is highlighted with a full green bar. Riders outside of the 80% time rule are shown in red. Riders eligible for "Early Pull" are shown in yellow with a pull symbol (⌦). Riders starting their laps lap have a bell (🔔). Riders finishing has a finish flag (🏁).
Lap|Shows the lap the rider is currently on.
ETA|Shows the Estimated Time of Arrival of the rider
Name|Shows the rider's name. Requires configuring the [External Excel][] sheet.

## Click on the Row
__Early Pull__

__Early Pull__ is a method of managing the 80% zone at the finish line when then 80% rule is in effect (MTB and Cyclocross),.
The rider is pulled after crossing the finish line and recording a final time, rather than pulling from an 80% zone.
This reduces the complexity of managing 80% pulled riders as no communication or coordination is required from officials in an 80% zone.

When doing early pull at the finish line, we cannot use the 80% time directly. Rather, we need to adjust this time based on the number of laps remaining in the race.
This is done as follows:

EarlyPullTime = 80PercentTime * (RaceLaps - CurrentLap) / RaceLaps

For example, say a race was 10 laps and the 80PercentTime is 8 minutes. When the leader starts the 2 to go lap, the EarlyPullTime = 8 minutes * (10-2)/10 = 6.4 minutes.
So, when the lap counter reads 2 to go, riders behind the leader by 6.4 minutes (or longer) should be pulled after they cross the finish line.

In this example, CrossMgr will indicate all riders who are 6.4 minutes behind the leader as elibigle for early pull with 2 to go.

The idea here is that __Early Pull__ is a time that projects where a rider will be when the leader finishes.

__Early Pull__ can also be used for an Early Bell strategy where indicated riders are given the bell at 2 to go.

__Early Pull__ is only enabled when the number of laps for a category is set explicitly and not computed from a race time.
This can be done from the [Categories][] page, or more easily from the [Record][] page.

## Click anywhere on the Row
Enters that number. Eliminates retyping the number in Record screen.

## Right-Click on the Row
Expand All @@ -44,7 +67,7 @@ The columns in the table are as follows:
Column|Description
:-----|:----------
Bib|The rider's number
Note|Shows the riders position by category. Leaders are highlighted in green. The Race Leader is highlighted with a full green bar. Riders outside of the 80% time rule are shown in red.
Note|Shows the riders position by category. Leaders are highlighted in green. The Race Leader is highlighted with a full green bar. Riders outside of the 80% time rule are shown in red. Riders eligible for "Early Pull" are shown in yellow with a pull symbol (⌦). Riders starting their laps lap have a bell (🔔). Riders finishing has a finish flag (🏁). See the __Expected__ section above for details about __Early Pull__.
Lap|Shows the lap the rider is currently on.
Time|Shows the recorded time
Name|Shows the rider's name. Requires linking an [External Excel][] sheet.
Expand Down
12 changes: 6 additions & 6 deletions helptxt/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,23 @@ def CompileHelp( dir = '.' ):
output_format='html5'
)

with open('markdown.css', 'r') as f:
with open('markdown.css', 'r', encoding='utf8') as f:
style = f.read()
with open('prolog.html', 'r') as f:
with open('prolog.html', 'r', encoding='utf8') as f:
prolog = f.read()
prolog = prolog.replace( '<<<style>>>', style, 1 )
del style
with open('epilog.html', 'r') as f:
with open('epilog.html', 'r', encoding='utf8') as f:
epilog = f.read().replace('YYYY','{}'.format(datetime.datetime.now().year))

contentDiv = '<div class="content">'

with open('Links.md', 'r') as f:
with open('Links.md', 'r', encoding='utf8') as f:
links = f.read()

for fname in getHelpFiles():
print( fname, '...' )
with open(fname, 'r') as f:
with open(fname, 'r', encoding='utf8') as f:
input = io.StringIO()
input.write( links )
input.write( f.read() )
Expand All @@ -79,7 +79,7 @@ def CompileHelp( dir = '.' ):
html = contentDiv + '\n' + html
html += '\n</div>\n'
html = InlineImages( html )
with open( os.path.splitext(fname)[0] + '.html', 'w' ) as f:
with open( os.path.splitext(fname)[0] + '.html', 'w', encoding='utf8' ) as f:
f.write( prolog )
f.write( html )
f.write( epilog )
Expand Down

0 comments on commit ce880ef

Please sign in to comment.