Skip to content
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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

Conversation

HaysCubing
Copy link

"Forecast View" calculates projected averages for all competitors and then ranks the results based on projected average.

Projected average is shown for incomplete results.

@@ -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) {
Copy link
Author

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.

Copy link
Author

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) &&
Copy link
Author

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?

Copy link
Author

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},
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this before submitting

Comment on lines +124 to +126
if (forecastView && forecastViewDisabled(round)) {
setForecastView(false);
}
Copy link
Member

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:

Suggested change
if (forecastView && forecastViewDisabled(round)) {
setForecastView(false);
}
useEffect(() => {
// Reset to default on round change
setForecastView(false);
}, [roundId]);

Comment on lines +81 to +83
export function forecastViewDisabled(round) {
return round.format.sortBy === "best" || round.competitionEvent.event.id === "333fm";
}
Copy link
Member

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"
Copy link
Member

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
Copy link
Member

@jonatanklosko jonatanklosko Jan 19, 2025

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.

Comment on lines +142 to +147
/**
* 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) {
Copy link
Member

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},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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) {
Copy link
Member

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)).

Comment on lines +194 to +195
forFirst: SKIPPED_VALUE,
forThird: SKIPPED_VALUE,
Copy link
Member

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) {
Copy link
Member

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));
Copy link
Member

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),
  ]);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants