Improve projected revenue

This commit is contained in:
2024-12-26 17:14:15 -05:00
parent 38a8829752
commit aaa95a5b9e
3 changed files with 982 additions and 387 deletions

View File

@@ -2022,4 +2022,160 @@ export class EventsService {
throw error;
}
}
async calculateSmartProjection(params = {}) {
try {
const { timeRange, startDate, endDate } = params;
// Get current period dates
let periodStart, periodEnd;
if (startDate && endDate) {
periodStart = this.timeManager.getDayStart(this.timeManager.toDateTime(startDate));
periodEnd = this.timeManager.getDayEnd(this.timeManager.toDateTime(endDate));
} else if (timeRange) {
const range = this.timeManager.getDateRange(timeRange);
periodStart = range.start;
periodEnd = range.end;
}
// Get the same day of week from the last 4 weeks for pattern matching
const historicalPeriods = [];
let historicalStart = periodStart.minus({ weeks: 4 });
for (let i = 0; i < 4; i++) {
historicalPeriods.push({
start: historicalStart.plus({ weeks: i }),
end: historicalStart.plus({ weeks: i + 1 }).minus({ milliseconds: 1 })
});
}
// Fetch current period data
const currentEvents = await this.getEvents({
startDate: periodStart.toISO(),
endDate: periodEnd.toISO(),
metricId: METRIC_IDS.PLACED_ORDER
});
// Fetch historical data for pattern matching
const historicalPromises = historicalPeriods.map(period =>
this.getEvents({
startDate: period.start.toISO(),
endDate: period.end.toISO(),
metricId: METRIC_IDS.PLACED_ORDER
})
);
const historicalResults = await Promise.all(historicalPromises);
// Process current period data
const currentData = this._transformEvents(currentEvents.data || []);
const currentRevenue = currentData.reduce((sum, event) => {
const props = event.event_properties || {};
return sum + Number(props.TotalAmount || 0);
}, 0);
// Build hourly patterns from historical data
const hourlyPatterns = Array(24).fill(0).map(() => ({
count: 0,
revenue: 0,
percentage: 0
}));
let totalHistoricalRevenue = 0;
let totalHistoricalOrders = 0;
historicalResults.forEach(result => {
const events = this._transformEvents(result.data || []);
events.forEach(event => {
const datetime = this.timeManager.toDateTime(event.attributes?.datetime);
if (!datetime) return;
const hour = datetime.hour;
const props = event.event_properties || {};
const amount = Number(props.TotalAmount || 0);
hourlyPatterns[hour].count++;
hourlyPatterns[hour].revenue += amount;
totalHistoricalRevenue += amount;
totalHistoricalOrders++;
});
});
// Calculate percentages
hourlyPatterns.forEach(pattern => {
pattern.percentage = totalHistoricalRevenue > 0 ?
(pattern.revenue / totalHistoricalRevenue) * 100 : 0;
});
// Get current hour in the period's timezone
const now = this.timeManager.getNow();
const currentHour = now.hour;
const currentMinute = now.minute;
// Calculate how much of the current hour has passed (0-1)
const hourProgress = currentMinute / 60;
// Calculate how much of the expected daily revenue we've seen so far
let expectedPercentageSeen = 0;
for (let i = 0; i < currentHour; i++) {
expectedPercentageSeen += hourlyPatterns[i].percentage;
}
// Add partial current hour
expectedPercentageSeen += hourlyPatterns[currentHour].percentage * hourProgress;
// Calculate projection based on patterns
let projectedRevenue = 0;
if (expectedPercentageSeen > 0) {
projectedRevenue = (currentRevenue / (expectedPercentageSeen / 100));
}
// Calculate confidence score (0-1) based on:
// 1. How much historical data we have
// 2. How consistent the patterns are
// 3. How far through the period we are
const patternConsistency = this._calculatePatternConsistency(hourlyPatterns);
const periodProgress = Math.min(100, Math.max(0, (now.diff(periodStart).milliseconds / periodEnd.diff(periodStart).milliseconds) * 100));
const historicalDataAmount = Math.min(totalHistoricalOrders / 1000, 1); // Normalize to 0-1, considering 1000+ orders as maximum confidence
const confidence = (
(patternConsistency * 0.4) +
(periodProgress / 100 * 0.4) +
(historicalDataAmount * 0.2)
);
// Return both the simple and pattern-based projections with metadata
return {
currentRevenue,
projectedRevenue,
confidence,
metadata: {
periodProgress,
patternConsistency,
historicalOrders: totalHistoricalOrders,
hourlyPatterns,
expectedPercentageSeen,
currentHour,
currentMinute
}
};
} catch (error) {
console.error('[EventsService] Error calculating smart projection:', error);
throw error;
}
}
_calculatePatternConsistency(hourlyPatterns) {
// Calculate the standard deviation of the percentage distribution
const mean = hourlyPatterns.reduce((sum, pattern) => sum + pattern.percentage, 0) / 24;
const variance = hourlyPatterns.reduce((sum, pattern) => {
const diff = pattern.percentage - mean;
return sum + (diff * diff);
}, 0) / 24;
const stdDev = Math.sqrt(variance);
// Normalize to a 0-1 scale where lower standard deviation means higher consistency
// Using a sigmoid function to normalize
const normalizedConsistency = 1 / (1 + Math.exp(stdDev / 10));
return normalizedConsistency;
}
}