better stuffs

This commit is contained in:
g
2026-01-10 02:07:03 +00:00
parent c69367e6da
commit 6c90ba90c3
2 changed files with 180 additions and 23 deletions

View File

@@ -90,6 +90,8 @@ export default function PredictionsChart({
const getConfidenceColor = (confidence: string) => { const getConfidenceColor = (confidence: string) => {
switch (confidence) { switch (confidence) {
case "very_high":
return "bg-emerald-600/20 text-emerald-700 dark:text-emerald-400 border-emerald-600/30";
case "high": case "high":
return "bg-green-500/10 text-green-700 dark:text-green-400"; return "bg-green-500/10 text-green-700 dark:text-green-400";
case "medium": case "medium":
@@ -101,6 +103,21 @@ export default function PredictionsChart({
} }
}; };
const getConfidenceLabel = (confidence: string) => {
switch (confidence) {
case "very_high":
return "Very High";
case "high":
return "High";
case "medium":
return "Medium";
case "low":
return "Low";
default:
return confidence;
}
};
if (loading) { if (loading) {
return ( return (
<Card> <Card>
@@ -212,14 +229,43 @@ export default function PredictionsChart({
<div className="text-2xl font-bold"> <div className="text-2xl font-bold">
{formatGBP(predictions.sales.predicted)} {formatGBP(predictions.sales.predicted)}
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 flex-wrap">
<Badge <Badge
className={getConfidenceColor( className={getConfidenceColor(
predictions.sales.confidence, predictions.sales.confidence,
)} )}
> >
{predictions.sales.confidence} confidence {getConfidenceLabel(predictions.sales.confidence)} Confidence
{predictions.sales.confidenceScore !== undefined && (
<span className="ml-1 opacity-75">
({Math.round(predictions.sales.confidenceScore * 100)}%)
</span>
)}
</Badge> </Badge>
{predictions.sales.trend && (
<Badge
variant="outline"
className={
predictions.sales.trend.direction === "up"
? "text-green-600 border-green-600"
: predictions.sales.trend.direction === "down"
? "text-red-600 border-red-600"
: ""
}
>
{predictions.sales.trend.direction === "up" && (
<TrendingUp className="h-3 w-3 mr-1" />
)}
{predictions.sales.trend.direction === "down" && (
<TrendingDown className="h-3 w-3 mr-1" />
)}
{predictions.sales.trend.direction === "up"
? "Trending Up"
: predictions.sales.trend.direction === "down"
? "Trending Down"
: "Stable"}
</Badge>
)}
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
Next {daysAhead} days Next {daysAhead} days
</span> </span>
@@ -230,7 +276,38 @@ export default function PredictionsChart({
orders orders
</div> </div>
)} )}
{predictions.sales.minPrediction && {predictions.sales.confidenceIntervals && (
<div className="text-xs text-muted-foreground space-y-1">
<div>
Range: {formatGBP(predictions.sales.confidenceIntervals.lower)} -{" "}
{formatGBP(predictions.sales.confidenceIntervals.upper)}
</div>
<div className="text-xs opacity-75">
95% confidence interval
</div>
{predictions.sales.confidenceIntervals.confidenceScore !== undefined && (
<div className="flex gap-3 text-xs opacity-75 mt-2 pt-2 border-t">
{predictions.sales.confidenceIntervals.avgModelAccuracy !== undefined && (
<div>
Model Accuracy: {Math.round(predictions.sales.confidenceIntervals.avgModelAccuracy * 100)}%
</div>
)}
{predictions.sales.confidenceIntervals.modelAgreement !== undefined && (
<div>
Agreement: {Math.round(predictions.sales.confidenceIntervals.modelAgreement * 100)}%
</div>
)}
{predictions.sales.confidenceIntervals.dataConsistency !== undefined && (
<div>
Data Quality: {Math.round(predictions.sales.confidenceIntervals.dataConsistency * 100)}%
</div>
)}
</div>
)}
</div>
)}
{!predictions.sales.confidenceIntervals &&
predictions.sales.minPrediction &&
predictions.sales.maxPrediction && ( predictions.sales.maxPrediction && (
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
Range: {formatGBP(predictions.sales.minPrediction)} -{" "} Range: {formatGBP(predictions.sales.minPrediction)} -{" "}
@@ -266,7 +343,7 @@ export default function PredictionsChart({
predictions.demand.confidence, predictions.demand.confidence,
)} )}
> >
{predictions.demand.confidence} confidence {getConfidenceLabel(predictions.demand.confidence)} Confidence
</Badge> </Badge>
</div> </div>
{predictions.demand.predictedWeekly && ( {predictions.demand.predictedWeekly && (
@@ -279,6 +356,12 @@ export default function PredictionsChart({
~{predictions.demand.predictedMonthly.toFixed(0)} units/month ~{predictions.demand.predictedMonthly.toFixed(0)} units/month
</div> </div>
)} )}
{predictions.demand.confidenceIntervals && (
<div className="text-xs text-muted-foreground">
Range: {predictions.demand.confidenceIntervals.lower.toFixed(1)} -{" "}
{predictions.demand.confidenceIntervals.upper.toFixed(1)} units/day
</div>
)}
</div> </div>
) : ( ) : (
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
@@ -369,19 +452,56 @@ export default function PredictionsChart({
{prediction.currentStock} {prediction.unitType} {prediction.currentStock} {prediction.unitType}
</TableCell> </TableCell>
<TableCell> <TableCell>
{prediction.prediction.daysUntilOutOfStock !== null {prediction.prediction.daysUntilOutOfStock !== null ? (
? `${prediction.prediction.daysUntilOutOfStock} days` <div className="space-y-1">
: "N/A"} <div className="font-medium">
{prediction.prediction.daysUntilOutOfStock} days
</div>
{prediction.prediction.optimisticDays !== null &&
prediction.prediction.pessimisticDays && (
<div className="text-xs text-muted-foreground">
{prediction.prediction.optimisticDays} -{" "}
{prediction.prediction.pessimisticDays} days
</div>
)}
</div>
) : (
"N/A"
)}
</TableCell> </TableCell>
<TableCell> <TableCell>
{prediction.prediction.estimatedDate {prediction.prediction.estimatedDate ? (
? format( <div className="space-y-1">
<div>
{format(
new Date( new Date(
prediction.prediction.estimatedDate, prediction.prediction.estimatedDate,
), ),
"MMM d, yyyy", "MMM d, yyyy",
) )}
: "N/A"} </div>
{prediction.prediction.optimisticDate &&
prediction.prediction.pessimisticDate && (
<div className="text-xs text-muted-foreground">
{format(
new Date(
prediction.prediction.optimisticDate,
),
"MMM d",
)}{" "}
-{" "}
{format(
new Date(
prediction.prediction.pessimisticDate,
),
"MMM d",
)}
</div>
)}
</div>
) : (
"N/A"
)}
</TableCell> </TableCell>
<TableCell> <TableCell>
<Badge <Badge
@@ -389,7 +509,7 @@ export default function PredictionsChart({
prediction.prediction.confidence, prediction.prediction.confidence,
)} )}
> >
{prediction.prediction.confidence} {getConfidenceLabel(prediction.prediction.confidence)}
</Badge> </Badge>
</TableCell> </TableCell>
<TableCell> <TableCell>

View File

@@ -308,17 +308,42 @@ export interface SalesPrediction {
predicted: number; predicted: number;
date: string; date: string;
}>; }>;
confidence: "high" | "medium" | "low"; confidence: "very_high" | "high" | "medium" | "low";
method: string; method: string;
methods?: { methods?: {
movingAverage?: number | null; weightedMovingAverage?: number | null;
exponentialAverage?: number | null; exponentialSmoothing?: number | null;
linearRegression?: number | null; holtWinters?: number | null;
trendAverage?: number | null; weightedLinearRegression?: number | null;
trendAdjusted?: number | null;
};
trend?: {
direction: "up" | "down" | "neutral";
strength: number;
acceleration: number;
slope?: number;
}; };
variance?: number; variance?: number;
minPrediction?: number; minPrediction?: number;
maxPrediction?: number; maxPrediction?: number;
confidenceScore?: number;
confidenceIntervals?: {
lower: number;
upper: number;
confidenceScore?: number;
modelAgreement?: number;
avgModelAccuracy?: number;
dataConsistency?: number;
};
modelPerformance?: {
[key: string]: {
mae?: number;
mape?: number;
rmse?: number;
accuracy?: number;
confidence?: string;
};
};
seasonality?: { seasonality?: {
dayOfWeek: Record<string, number>; dayOfWeek: Record<string, number>;
month: Record<string, number>; month: Record<string, number>;
@@ -333,10 +358,18 @@ export interface DemandPrediction {
predictedDaily: number | null; predictedDaily: number | null;
predictedWeekly: number | null; predictedWeekly: number | null;
predictedMonthly: number | null; predictedMonthly: number | null;
confidence: "high" | "medium" | "low"; confidence: "very_high" | "high" | "medium" | "low";
averageDaily?: number; averageDaily?: number;
trendFactor?: number; trendFactor?: number;
stdDev?: number; stdDev?: number;
confidenceIntervals?: {
lower: number;
upper: number;
confidenceScore?: number;
modelAgreement?: number;
avgModelAccuracy?: number;
dataConsistency?: number;
};
historicalPeriod: number; historicalPeriod: number;
predictionPeriod: number; predictionPeriod: number;
productId?: string | null; productId?: string | null;
@@ -352,9 +385,13 @@ export interface StockPrediction {
prediction: { prediction: {
daysUntilOutOfStock: number | null; daysUntilOutOfStock: number | null;
estimatedDate: string | null; estimatedDate: string | null;
confidence: "high" | "medium" | "low"; confidence: "very_high" | "high" | "medium" | "low";
averageDailySales?: number; averageDailySales?: number;
stdDev?: number; stdDev?: number;
optimisticDays?: number | null;
pessimisticDays?: number | null;
optimisticDate?: string | null;
pessimisticDate?: string | null;
message?: string; message?: string;
}; };
needsRestock: boolean; needsRestock: boolean;