-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Forecast view #252
base: main
Are you sure you want to change the base?
Forecast view #252
Conversation
@@ -72,6 +76,12 @@ const ROUND_UPDATED_SUBSCRIPTION = gql` | |||
${ROUND_RESULT_FRAGMENT} | |||
`; | |||
|
|||
// Events sorted by best don't need forecast view. | |||
// Fewest moves is currently unsupported | |||
export function forecastViewDisabled(format, eventId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a cleaner way to set up "forecastViewDisabled"? I want it to be disabled (icon and functionality) for "Best of" events and FMC. It looks like I need to pass the const "forecastView" since that's what's initialized with useState() and causes the page to reload. However calling forecastViewDisabled() within every child element generator seems inefficient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cleaned this up a bit. No longer passing to child functions. If you navigate from a page with it enabled (ex. 7x7) to a page disabled (BLD), and forecast view is enabled, then line 125 will disable it and do a page reload. So loads the page twice essentially - not sure if this is a big deal or not
@@ -23,6 +25,16 @@ function RoundToolbar({ round, competitionId }) { | |||
<Grid item style={{ flexGrow: 1 }} /> | |||
{mdScreen && ( | |||
<Grid item> | |||
{!forecastViewDisabled(round.format, round.competitionEvent.event.id) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's an icon for right now. Should it be a checkbox?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made it a checkbox. More clear this way. Right now I have it so the entire thing doesn't appear at all if it's not supported. I could also just have it disabled for non supported events. Is that better?
@@ -15,5 +15,7 @@ export default defineConfig({ | |||
proxy: { | |||
"/auth": "http://127.0.0.1:4000", | |||
}, | |||
watch: {usePolling: true}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this before submitting
if (forecastView && forecastViewDisabled(round)) { | ||
setForecastView(false); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe set*
functions should not be called in the function body directly, but rather in response to some events. Here it may actually work because with the if
makes sure it doesn't get called over and over, but I would do this instead:
if (forecastView && forecastViewDisabled(round)) { | |
setForecastView(false); | |
} | |
useEffect(() => { | |
// Reset to default on round change | |
setForecastView(false); | |
}, [roundId]); |
export function forecastViewDisabled(round) { | ||
return round.format.sortBy === "best" || round.competitionEvent.event.id === "333fm"; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to exclude FMC? We only need to * 100
the values when computing average, as in the existing average
function.
onChange={(event) => setForecastView(event.target.checked)} | ||
/> | ||
} | ||
label="Forecast View" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this takes too much space, but don't worry about it. I will experiment with a toggle icon before shipping.
currentResult.ranking = i + 1; | ||
} | ||
// Using simple advancing logic. | ||
// TODO: Implement questionable/clinched logic |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can actually leave advancingQuestionable
untouched and it should be accurate. If advancingQuestionable
is true, it means the result is advancing even if all remaining results are best possible, so it is still valid in the forecast view. If advancingQuestionable
is false, then it should not become true based on projected average.
It may be worth leaving a comment that this is the case actually.
/** | ||
* return the number of competitors that should be marked as advancing. | ||
* When no competitors advance, return 3 for the podium placements. | ||
* Might not be accurate for percentage based advancement but that is acceptable | ||
*/ | ||
function getAdvancingCount(results, advancementCondition) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct me if I'm wrong, but the point of forecast view is for final rounds, because it's usually those that have results entered partially. So what I would do is enable forecast view only for final rounds, and then for advancing we just mark top 3 ranking.
If a use case appears in the future to support other rounds, I think we should support all advancement conditions correctly.
@@ -15,5 +15,7 @@ export default defineConfig({ | |||
proxy: { | |||
"/auth": "http://127.0.0.1:4000", | |||
}, | |||
watch: {usePolling: true}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
watch: {usePolling: true}, | |
// Uncomment when running using Docker on Windows. | |
// See https://github.com/vitejs/vite/issues/1153#issuecomment-785467271 | |
// watch: { usePolling: true }, |
@@ -72,6 +76,12 @@ const ROUND_UPDATED_SUBSCRIPTION = gql` | |||
${ROUND_RESULT_FRAGMENT} | |||
`; | |||
|
|||
// Events sorted by best don't need forecast view. | |||
// Fewest moves is currently unsupported | |||
export function forecastViewDisabled(round) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: let's move this to result.js
also to keep the logic together, and let's revert as forecastViewEnabled
(so that we don't need double negation as in !forecastViewDisabled(round)
).
forFirst: SKIPPED_VALUE, | ||
forThird: SKIPPED_VALUE, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove for now, unless you are working on adding as part of the PR :)
* forFirst: time needed to overtake first place | ||
* forThird: time needed to overtake third place | ||
*/ | ||
export function getExpandedResults(results, format, forecastView, advancementCondition) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this to result.js
.
Basically: attempt-result.js
for various calculations on attempt result (numeric values), and result.js
for dealing with result objects.
I would also rename this to resultsForView
.
const roundIncomplete = results.some((result) => isSkipped(result.average)); | ||
|
||
// Sort based on projection with tiebreakers on single | ||
expandedResults = expandedResults.sort((a, b) => compareProjectedResults(a, b)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can simplify a bit, first define this for attempt result:
function toMonotonic(attemptResult) {
if (isComplete(attemptResult)) {
return attemptResult;
} else {
return Infinity;
}
}
and then here we can do:
expandedResults =
orderBy(expandedResults, [
(result) => toMonotonic(result.projectedAverage),
(result) => toMonotonic(result.best),
]);
"Forecast View" calculates projected averages for all competitors and then ranks the results based on projected average.
Projected average is shown for incomplete results.