2025-08-03 17:48:34 +02:00
|
|
|
// frontend/src/api/services/forecasting.service.ts
|
|
|
|
|
/**
|
|
|
|
|
* Forecasting Service
|
|
|
|
|
* Handles forecast operations and predictions
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { apiClient } from '../client';
|
|
|
|
|
import { RequestTimeouts } from '../client/config';
|
|
|
|
|
import type {
|
|
|
|
|
SingleForecastRequest,
|
|
|
|
|
BatchForecastRequest,
|
|
|
|
|
ForecastResponse,
|
|
|
|
|
BatchForecastResponse,
|
|
|
|
|
ForecastAlert,
|
|
|
|
|
QuickForecast,
|
|
|
|
|
PaginatedResponse,
|
|
|
|
|
BaseQueryParams,
|
|
|
|
|
} from '../types';
|
|
|
|
|
|
|
|
|
|
export class ForecastingService {
|
|
|
|
|
/**
|
|
|
|
|
* Create Single Product Forecast
|
|
|
|
|
*/
|
|
|
|
|
async createSingleForecast(
|
2025-08-04 22:46:05 +02:00
|
|
|
tenantId: string,
|
|
|
|
|
request: SingleForecastRequest
|
|
|
|
|
): Promise<ForecastResponse[]> {
|
|
|
|
|
console.log('🔮 Creating single forecast:', { tenantId, request });
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Backend returns single ForecastResponse object
|
|
|
|
|
const response = await apiClient.post(
|
|
|
|
|
`/tenants/${tenantId}/forecasts/single`,
|
|
|
|
|
request,
|
|
|
|
|
{
|
|
|
|
|
timeout: RequestTimeouts.MEDIUM,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
console.log('🔮 Forecast API Response:', response);
|
|
|
|
|
console.log('- Type:', typeof response);
|
|
|
|
|
console.log('- Is Array:', Array.isArray(response));
|
|
|
|
|
|
|
|
|
|
// ✅ FIX: Convert single response to array
|
|
|
|
|
if (response && typeof response === 'object' && !Array.isArray(response)) {
|
|
|
|
|
// Single forecast response - wrap in array
|
|
|
|
|
const forecastArray = [response as ForecastResponse];
|
|
|
|
|
console.log('✅ Converted single forecast to array:', forecastArray);
|
|
|
|
|
return forecastArray;
|
|
|
|
|
} else if (Array.isArray(response)) {
|
|
|
|
|
// Already an array (unexpected but handle gracefully)
|
|
|
|
|
console.log('✅ Response is already an array:', response);
|
|
|
|
|
return response;
|
|
|
|
|
} else {
|
|
|
|
|
console.error('❌ Unexpected response format:', response);
|
|
|
|
|
throw new Error('Invalid forecast response format');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('❌ Forecast API Error:', error);
|
|
|
|
|
throw error;
|
2025-08-03 17:48:34 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create Batch Forecast
|
|
|
|
|
*/
|
|
|
|
|
async createBatchForecast(
|
|
|
|
|
tenantId: string,
|
|
|
|
|
request: BatchForecastRequest
|
|
|
|
|
): Promise<BatchForecastResponse> {
|
|
|
|
|
return apiClient.post(
|
|
|
|
|
`/tenants/${tenantId}/forecasts/batch`,
|
|
|
|
|
request,
|
|
|
|
|
{
|
|
|
|
|
timeout: RequestTimeouts.LONG,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get Forecast by ID
|
|
|
|
|
*/
|
|
|
|
|
async getForecast(tenantId: string, forecastId: string): Promise<ForecastResponse> {
|
|
|
|
|
return apiClient.get(`/tenants/${tenantId}/forecasts/${forecastId}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get Forecasts
|
|
|
|
|
*/
|
|
|
|
|
async getForecasts(
|
|
|
|
|
tenantId: string,
|
|
|
|
|
params?: BaseQueryParams & {
|
|
|
|
|
product_name?: string;
|
|
|
|
|
start_date?: string;
|
|
|
|
|
end_date?: string;
|
|
|
|
|
model_id?: string;
|
|
|
|
|
}
|
|
|
|
|
): Promise<PaginatedResponse<ForecastResponse>> {
|
|
|
|
|
return apiClient.get(`/tenants/${tenantId}/forecasts`, { params });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get Batch Forecast Status
|
|
|
|
|
*/
|
|
|
|
|
async getBatchForecastStatus(
|
|
|
|
|
tenantId: string,
|
|
|
|
|
batchId: string
|
|
|
|
|
): Promise<BatchForecastResponse> {
|
|
|
|
|
return apiClient.get(`/tenants/${tenantId}/forecasts/batch/${batchId}/status`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get Batch Forecasts
|
|
|
|
|
*/
|
|
|
|
|
async getBatchForecasts(
|
|
|
|
|
tenantId: string,
|
|
|
|
|
params?: BaseQueryParams & {
|
|
|
|
|
status?: string;
|
|
|
|
|
start_date?: string;
|
|
|
|
|
end_date?: string;
|
|
|
|
|
}
|
|
|
|
|
): Promise<PaginatedResponse<BatchForecastResponse>> {
|
|
|
|
|
return apiClient.get(`/tenants/${tenantId}/forecasts/batch`, { params });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Cancel Batch Forecast
|
|
|
|
|
*/
|
|
|
|
|
async cancelBatchForecast(tenantId: string, batchId: string): Promise<{ message: string }> {
|
|
|
|
|
return apiClient.post(`/tenants/${tenantId}/forecasts/batch/${batchId}/cancel`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get Quick Forecasts for Dashboard
|
|
|
|
|
*/
|
|
|
|
|
async getQuickForecasts(tenantId: string, limit?: number): Promise<QuickForecast[]> {
|
|
|
|
|
return apiClient.get(`/tenants/${tenantId}/forecasts/quick`, {
|
|
|
|
|
params: { limit },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get Forecast Alerts
|
|
|
|
|
*/
|
|
|
|
|
async getForecastAlerts(
|
|
|
|
|
tenantId: string,
|
|
|
|
|
params?: BaseQueryParams & {
|
|
|
|
|
is_active?: boolean;
|
|
|
|
|
severity?: string;
|
|
|
|
|
alert_type?: string;
|
|
|
|
|
}
|
|
|
|
|
): Promise<PaginatedResponse<ForecastAlert>> {
|
|
|
|
|
return apiClient.get(`/tenants/${tenantId}/forecasts/alerts`, { params });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Acknowledge Forecast Alert
|
|
|
|
|
*/
|
|
|
|
|
async acknowledgeForecastAlert(
|
|
|
|
|
tenantId: string,
|
|
|
|
|
alertId: string
|
|
|
|
|
): Promise<ForecastAlert> {
|
|
|
|
|
return apiClient.post(`/tenants/${tenantId}/forecasts/alerts/${alertId}/acknowledge`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Delete Forecast
|
|
|
|
|
*/
|
|
|
|
|
async deleteForecast(tenantId: string, forecastId: string): Promise<{ message: string }> {
|
|
|
|
|
return apiClient.delete(`/tenants/${tenantId}/forecasts/${forecastId}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Export Forecasts
|
|
|
|
|
*/
|
|
|
|
|
async exportForecasts(
|
|
|
|
|
tenantId: string,
|
|
|
|
|
format: 'csv' | 'excel' | 'json',
|
|
|
|
|
params?: {
|
|
|
|
|
product_name?: string;
|
|
|
|
|
start_date?: string;
|
|
|
|
|
end_date?: string;
|
|
|
|
|
}
|
|
|
|
|
): Promise<Blob> {
|
|
|
|
|
const response = await apiClient.request(`/tenants/${tenantId}/forecasts/export`, {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
params: { ...params, format },
|
|
|
|
|
headers: {
|
|
|
|
|
'Accept': format === 'csv' ? 'text/csv' :
|
|
|
|
|
format === 'excel' ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' :
|
|
|
|
|
'application/json',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return new Blob([response], {
|
|
|
|
|
type: format === 'csv' ? 'text/csv' :
|
|
|
|
|
format === 'excel' ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' :
|
|
|
|
|
'application/json',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get Forecast Accuracy Metrics
|
|
|
|
|
*/
|
|
|
|
|
async getForecastAccuracy(
|
|
|
|
|
tenantId: string,
|
|
|
|
|
params?: {
|
|
|
|
|
product_name?: string;
|
|
|
|
|
model_id?: string;
|
|
|
|
|
start_date?: string;
|
|
|
|
|
end_date?: string;
|
|
|
|
|
}
|
|
|
|
|
): Promise<{
|
|
|
|
|
overall_accuracy: number;
|
|
|
|
|
product_accuracy: Array<{
|
|
|
|
|
product_name: string;
|
|
|
|
|
accuracy: number;
|
|
|
|
|
sample_size: number;
|
|
|
|
|
}>;
|
|
|
|
|
}> {
|
|
|
|
|
return apiClient.get(`/tenants/${tenantId}/forecasts/accuracy`, { params });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const forecastingService = new ForecastingService();
|