favicon and some TODOs
This commit is contained in:
+59
-3
@@ -1,6 +1,16 @@
|
||||
import type { Config, EstimationResult, MissingPeriod, Plan, Selections } from "./types";
|
||||
import type {
|
||||
BalanceComparison,
|
||||
Config,
|
||||
EstimationResult,
|
||||
MissingPeriod,
|
||||
Plan,
|
||||
Selections,
|
||||
} from "./types";
|
||||
import { clampDate, daysBetween, inclusiveDayCount, overlapDays } from "./dates";
|
||||
|
||||
/** A balance is treated as "on track" when within this fraction of expected. */
|
||||
const ON_TRACK_TOLERANCE = 0.02;
|
||||
|
||||
/** The active missing periods for a set of ids, in config order. */
|
||||
function activePeriods(config: Config, ids: Set<string>): MissingPeriod[] {
|
||||
return config.missingPeriods.filter((p) => ids.has(p.id));
|
||||
@@ -76,18 +86,64 @@ export function estimate(config: Config, selections: Selections): EstimationResu
|
||||
const consumedMealBlocks = clamp(blockRate * consumedDays, 0, plan.totalMealBlocks);
|
||||
const consumedFlexDollars = clamp(flexRate * consumedDays, 0, plan.totalFlexDollars);
|
||||
|
||||
const remainingMealBlocks = plan.totalMealBlocks - consumedMealBlocks;
|
||||
const remainingFlexDollars = plan.totalFlexDollars - consumedFlexDollars;
|
||||
|
||||
// Active days still ahead of the target date (for catch-up guidance).
|
||||
const activeDaysRemaining = Math.max(0, total - consumedDays);
|
||||
|
||||
return {
|
||||
perWeekMealBlocks: blockRate * 7,
|
||||
perWeekFlexDollars: flexRate * 7,
|
||||
consumedMealBlocks,
|
||||
consumedFlexDollars,
|
||||
remainingMealBlocks: plan.totalMealBlocks - consumedMealBlocks,
|
||||
remainingFlexDollars: plan.totalFlexDollars - consumedFlexDollars,
|
||||
remainingMealBlocks,
|
||||
remainingFlexDollars,
|
||||
totalActiveDays: total,
|
||||
activeDaysConsumed: consumedDays,
|
||||
activeDaysRemaining,
|
||||
blockComparison: compareBalance(
|
||||
selections.actualMealBlocks,
|
||||
remainingMealBlocks,
|
||||
activeDaysRemaining
|
||||
),
|
||||
flexComparison: compareBalance(
|
||||
selections.actualFlexDollars,
|
||||
remainingFlexDollars,
|
||||
activeDaysRemaining
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare an actual balance the user entered against the on-pace expected
|
||||
* remaining amount. Returns null when no actual balance was entered.
|
||||
*
|
||||
* A higher actual balance than expected means the student is spending slower
|
||||
* than the even pace (ahead / not running low). The suggested per-week rate is
|
||||
* the pace that would use the actual balance up exactly over the days left.
|
||||
*/
|
||||
export function compareBalance(
|
||||
actual: number | null,
|
||||
expectedRemaining: number,
|
||||
activeDaysRemaining: number
|
||||
): BalanceComparison | null {
|
||||
if (actual === null) return null;
|
||||
|
||||
const difference = actual - expectedRemaining;
|
||||
|
||||
let status: BalanceComparison["status"];
|
||||
const tolerance = Math.max(ON_TRACK_TOLERANCE * expectedRemaining, 0.5);
|
||||
if (Math.abs(difference) <= tolerance) status = "onTrack";
|
||||
else if (difference > 0) status = "ahead";
|
||||
else status = "behind";
|
||||
|
||||
const suggestedPerWeek =
|
||||
activeDaysRemaining > 0 ? (actual / activeDaysRemaining) * 7 : null;
|
||||
|
||||
return { actual, expected: expectedRemaining, difference, status, suggestedPerWeek };
|
||||
}
|
||||
|
||||
function clamp(v: number, lo: number, hi: number): number {
|
||||
return Math.min(hi, Math.max(lo, v));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user