MouleeswaranM's picture
Upload folder using huggingface_hub
fcf8749 verified
raw
history blame
21.9 kB
/**
* API Integration Module
* Handles communication with the Fair Dispatch backend
* Real-time data fetching with polling support
*/
const API = {
BASE_URL: 'http://localhost:8000',
API_PREFIX: '/api/v1',
// Polling interval in milliseconds - increased to reduce server load
POLL_INTERVAL: 10000, // 10 seconds for routine checks
FAST_POLL_INTERVAL: 3000, // 3 seconds when actively monitoring
// Current polling timer
_pollTimer: null,
// Cache for current allocation run
_currentAllocationRun: null,
_lastAgentTimeline: null,
// Track last known allocation run ID to avoid redundant fetches
_lastKnownRunId: null,
_lastPollTime: 0,
_lastRunCount: 0, // Track number of runs to detect new allocations
// Selected date for dashboard (initialized to today on first access)
_selectedDate: null,
// Get or initialize the selected date
getSelectedDate() {
if (!this._selectedDate) {
this._selectedDate = this.getTodayDate();
}
return this._selectedDate;
},
// Event callbacks
_callbacks: {
onStatusUpdate: null,
onWorkflowUpdate: null,
onError: null
},
/**
* Set the base URL for API calls
*/
setBaseUrl(url) {
this.BASE_URL = url;
},
/**
* Make an API request
*/
async request(endpoint, options = {}) {
const url = `${this.BASE_URL}${this.API_PREFIX}${endpoint}`;
// Add cache busting for GET requests to ensure real-time data
const cacheBuster = options.method === 'GET' || !options.method
? (endpoint.includes('?') ? '&' : '?') + `_t=${new Date().getTime()}`
: '';
try {
const response = await fetch(url + cacheBuster, {
cache: 'no-store',
headers: {
'Content-Type': 'application/json',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache, no-store, must-revalidate',
...options.headers
},
...options
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error(`API Error (${endpoint}):`, error);
if (this._callbacks.onError) {
this._callbacks.onError(error);
}
throw error;
}
},
/**
* Check system health
*/
async checkHealth() {
try {
const response = await fetch(`${this.BASE_URL}/health`);
return await response.json();
} catch (error) {
return { status: 'disconnected', error: error.message };
}
},
/**
* Get admin system health with more details
*/
async getAdminHealth() {
try {
return await this.request('/admin/health');
} catch (error) {
return null;
}
},
/**
* Get allocation runs for a specific date
*/
async getAllocationRuns(date) {
return this.request(`/admin/allocation_runs?date=${date}`);
},
/**
* Get agent timeline for an allocation run
*/
async getAgentTimeline(allocationRunId) {
return this.request(`/admin/agent_timeline?allocation_run_id=${allocationRunId}`);
},
/**
* Get fairness metrics
*/
async getFairnessMetrics(startDate, endDate) {
return this.request(`/admin/metrics/fairness?start_date=${startDate}&end_date=${endDate}`);
},
/**
* Trigger a new allocation (for testing)
*/
async triggerAllocation(data) {
return this.request('/allocate', {
method: 'POST',
body: JSON.stringify(data)
});
},
/**
* Get driver details
*/
async getDriver(driverId) {
return this.request(`/drivers/${driverId}`);
},
/**
* Get route details
*/
async getRoute(routeId) {
return this.request(`/routes/${routeId}`);
},
/**
* Get assignments for a date (needed for map visualization)
*/
async getAssignments(date) {
return this.request(`/admin/assignments?date=${date}`);
},
/**
* Register callback for status updates
*/
onStatusUpdate(callback) {
this._callbacks.onStatusUpdate = callback;
},
/**
* Register callback for workflow updates
*/
onWorkflowUpdate(callback) {
this._callbacks.onWorkflowUpdate = callback;
},
/**
* Register callback for errors
*/
onError(callback) {
this._callbacks.onError = callback;
},
/**
* Start polling for updates
*/
startPolling() {
if (this._pollTimer) {
clearInterval(this._pollTimer);
}
// Use slower polling interval by default
this._pollTimer = setInterval(async () => {
await this._pollStatus();
}, this.POLL_INTERVAL);
// Initial poll
this._pollStatus();
},
/**
* Stop polling
*/
stopPolling() {
if (this._pollTimer) {
clearInterval(this._pollTimer);
this._pollTimer = null;
}
},
/**
* Set the selected date for dashboard queries
*/
setSelectedDate(date) {
this._selectedDate = date;
// Reset cache when date changes
this._lastKnownRunId = null;
this._lastRunCount = 0;
},
/**
* Internal polling function - fetches real data from backend
* Optimized to minimize redundant API calls
*/
async _pollStatus() {
try {
const health = await this.checkHealth();
const connected = health.status === 'healthy';
if (this._callbacks.onStatusUpdate) {
this._callbacks.onStatusUpdate({
connected,
health
});
}
// If connected, check for new data (but minimize API calls)
if (connected && this._callbacks.onWorkflowUpdate) {
const queryDate = this.getSelectedDate();
// Quick check: just get allocation runs count/latest ID
const runsResponse = await this.getAllocationRuns(queryDate);
const runs = runsResponse.runs || [];
const runCount = runs.length;
const latestRunId = runs.length > 0 ? runs[runs.length - 1].id : null;
// Only fetch full data if:
// 1. We have no cached data, OR
// 2. Run count changed (new allocation added), OR
// 3. Latest run ID changed
const needsFullFetch = !this._currentAllocationRun ||
runCount !== this._lastRunCount ||
latestRunId !== this._lastKnownRunId;
if (needsFullFetch && runs.length > 0) {
console.log(`[Poll] New data detected, fetching full workflow state...`);
const workflowState = await this.getRealWorkflowState(queryDate);
this._lastRunCount = runCount;
this._callbacks.onWorkflowUpdate(workflowState);
}
// Otherwise, no API call needed - data hasn't changed
}
} catch (error) {
if (this._callbacks.onStatusUpdate) {
this._callbacks.onStatusUpdate({
connected: false,
error: error.message
});
}
}
},
/**
* Get today's date in local timezone (YYYY-MM-DD format)
*/
getTodayDate() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
/**
* Fetch real workflow state from backend
* @param {string} date - Optional date to fetch, defaults to selected date or today
* @param {string} runId - Optional specific run ID to fetch
*/
async getRealWorkflowState(date = null, runId = null) {
try {
// If a specific run_id is provided, fetch that run directly
if (runId) {
return await this.getWorkflowStateForRun(runId);
}
// Use provided date, or get the selected date
const queryDate = date || this.getSelectedDate();
const runsResponse = await this.getAllocationRuns(queryDate);
if (runsResponse.runs && runsResponse.runs.length > 0) {
// Get the latest allocation run
const latestRun = runsResponse.runs[runsResponse.runs.length - 1];
this._currentAllocationRun = latestRun;
// Track the run ID to avoid redundant fetches
this._lastKnownRunId = latestRun.id;
// Get agent timeline for this run
const timeline = await this.getAgentTimeline(latestRun.id);
this._lastAgentTimeline = timeline;
// Transform timeline into workflow state
return this.transformTimelineToWorkflowState(timeline, latestRun);
}
} catch (error) {
console.log('Using mock data - no allocations found:', error.message);
}
// Fallback to mock state
return this.getMockWorkflowState();
},
/**
* Fetch workflow state for a specific run ID
* @param {string} runId - The allocation run ID to fetch
*/
async getWorkflowStateForRun(runId) {
try {
// Fetch run summary to get run metadata
const summaryResponse = await fetch(`${this.BASE_URL}${this.API_PREFIX}/runs/${runId}/summary`);
if (!summaryResponse.ok) {
throw new Error(`Failed to fetch run summary: ${summaryResponse.status}`);
}
const summary = await summaryResponse.json();
// Build allocation run object from summary
const allocationRun = {
id: summary.allocation_run_id,
date: summary.date,
num_drivers: summary.num_drivers,
num_routes: summary.num_routes,
num_packages: summary.num_packages,
gini_index: summary.global_gini_index,
std_dev: summary.global_std_dev,
status: summary.status,
};
this._currentAllocationRun = allocationRun;
this._lastKnownRunId = runId;
// Get agent timeline for this specific run
const timeline = await this.getAgentTimeline(runId);
this._lastAgentTimeline = timeline;
// Transform timeline into workflow state
return this.transformTimelineToWorkflowState(timeline, allocationRun);
} catch (error) {
console.error('Error fetching workflow state for run:', runId, error);
throw error;
}
},
/**
* Transform agent timeline data to workflow state format
*/
transformTimelineToWorkflowState(timeline, allocationRun) {
// Map backend agent names to frontend node IDs
// Support both UPPERCASE (from LangGraph) and PascalCase formats
const agentNameMap = {
// LangGraph format (UPPERCASE)
'ML_EFFORT': { id: 'route_planner', name: 'Route Planner Agent', type: 'agent' },
'ML_EFFORT_AGENT': { id: 'route_planner', name: 'Route Planner Agent', type: 'agent' },
'ROUTE_PLANNER': { id: 'route_planner', name: 'Route Planner Agent', type: 'agent' },
'FAIRNESS_MANAGER': { id: 'fairness_manager', name: 'Fairness Manager Agent', type: 'agent' },
'DRIVER_LIAISON': { id: 'driver_liaison', name: 'Driver Liaison Agent', type: 'agent' },
'EXPLAINABILITY': { id: 'explainability', name: 'Explainability Agent', type: 'agent' },
'LEARNING': { id: 'learning', name: 'Learning Agent', type: 'agent' },
// PascalCase format (legacy)
'MLEffortAgent': { id: 'route_planner', name: 'Route Planner Agent', type: 'agent' },
'RoutePlannerAgent': { id: 'route_planner', name: 'Route Planner Agent', type: 'agent' },
'FairnessManagerAgent': { id: 'fairness_manager', name: 'Fairness Manager Agent', type: 'agent' },
'DriverLiaisonAgent': { id: 'driver_liaison', name: 'Driver Liaison Agent', type: 'agent' },
'ExplainabilityAgent': { id: 'explainability', name: 'Explainability Agent', type: 'agent' },
'LearningAgent': { id: 'learning', name: 'Learning Agent', type: 'agent' },
};
// Build agent status map from timeline
// Handle both new format (timeline.timeline) and legacy format (timeline.steps)
const agentStatus = {};
const agentLogs = {};
let totalSteps = 0;
// Get the actual steps array from the response
const steps = timeline.timeline || timeline.steps || [];
if (steps && steps.length > 0) {
steps.forEach(step => {
const mapped = agentNameMap[step.agent_name];
if (mapped) {
agentStatus[mapped.id] = 'active';
if (!agentLogs[mapped.id]) {
agentLogs[mapped.id] = [];
}
agentLogs[mapped.id].push(step);
totalSteps++;
}
});
}
// Store logs for terminal display
this._agentLogs = agentLogs;
const agents = [
{
id: 'central_orchestrator',
name: 'Central Orchestrator Agent',
description: 'Central Intelligence Hub',
status: 'active',
type: 'orchestrator',
meta: `${Object.keys(agentStatus).length} Active`
},
{
id: 'route_database',
name: 'Route Database',
description: `Run: ${allocationRun.id.substring(0, 8)}...`,
status: 'active',
type: 'database',
meta: 'Connected'
},
{
id: 'route_planner',
name: 'Route Planner Agent',
description: agentLogs['route_planner'] ?
`${agentLogs['route_planner'].length} decisions` : 'Optimizing routes',
status: agentStatus['route_planner'] || 'idle',
type: 'agent',
meta: null
},
{
id: 'fairness_manager',
name: 'Fairness Manager Agent',
description: agentLogs['fairness_manager'] ?
`Gini: ${allocationRun.gini_index?.toFixed(3) || 'N/A'}` : 'Ensuring equitable distribution',
status: agentStatus['fairness_manager'] || 'idle',
type: 'agent',
meta: null
},
{
id: 'driver_liaison',
name: 'Driver Liaison Agent',
description: agentLogs['driver_liaison'] ?
`${agentLogs['driver_liaison'].length} interactions` : 'Driver communication hub',
status: agentStatus['driver_liaison'] || 'idle',
type: 'agent',
meta: null
},
{
id: 'explainability',
name: 'Explainability Agent',
description: agentLogs['explainability'] ?
`${agentLogs['explainability'].length} explanations` : 'Generating explanations',
status: agentStatus['explainability'] || 'idle',
type: 'agent',
meta: null
},
{
id: 'learning',
name: 'Learning Agent',
description: 'Continuous improvement loop',
status: agentStatus['learning'] || 'idle',
type: 'agent',
meta: null
}
];
// Build connections - active if agent has data
const connections = [
{ from: 'central_orchestrator', to: 'route_database', active: true },
{ from: 'central_orchestrator', to: 'route_planner', active: !!agentStatus['route_planner'] },
{ from: 'central_orchestrator', to: 'fairness_manager', active: !!agentStatus['fairness_manager'] },
{ from: 'central_orchestrator', to: 'driver_liaison', active: !!agentStatus['driver_liaison'] },
{ from: 'central_orchestrator', to: 'explainability', active: !!agentStatus['explainability'] },
{ from: 'central_orchestrator', to: 'learning', active: !!agentStatus['learning'] },
{ from: 'route_database', to: 'route_planner', active: !!agentStatus['route_planner'] },
{ from: 'route_planner', to: 'fairness_manager', active: !!agentStatus['fairness_manager'] },
{ from: 'fairness_manager', to: 'driver_liaison', active: !!agentStatus['driver_liaison'] },
{ from: 'driver_liaison', to: 'explainability', active: !!agentStatus['explainability'] },
{ from: 'explainability', to: 'learning', active: !!agentStatus['learning'] }
];
const activeConnections = connections.filter(c => c.active).length;
return {
agents,
connections,
stats: {
processing: 0,
dataFlows: activeConnections,
totalAgents: Object.keys(agentStatus).length
},
allocationRun,
timeline,
isRealData: true
};
},
/**
* Get cached agent logs for terminal display
*/
getAgentLogs(agentId) {
if (this._agentLogs && this._agentLogs[agentId]) {
return this._agentLogs[agentId];
}
return null;
},
/**
* Get current allocation run
*/
getCurrentAllocationRun() {
return this._currentAllocationRun;
},
/**
* Get mock workflow state for demo mode
*/
getMockWorkflowState() {
return {
agents: [
{
id: 'central_orchestrator',
name: 'Central Orchestrator Agent',
description: 'Central Intelligence Hub',
status: 'active',
type: 'orchestrator',
meta: '6 Agents'
},
{
id: 'route_database',
name: 'Route Database',
description: '@ 18.9K records',
status: 'active',
type: 'database',
meta: 'Connected'
},
{
id: 'route_planner',
name: 'Route Planner Agent',
description: 'Optimizing delivery routes',
status: 'processing',
type: 'agent',
meta: null
},
{
id: 'fairness_manager',
name: 'Fairness Manager Agent',
description: 'Ensuring equitable distribution',
status: 'idle',
type: 'agent',
meta: null
},
{
id: 'driver_liaison',
name: 'Driver Liaison Agent',
description: 'Driver communication hub',
status: 'idle',
type: 'agent',
meta: null
},
{
id: 'explainability',
name: 'Explainability Agent',
description: 'Generating detailed explanations',
status: 'idle',
type: 'agent',
meta: null
},
{
id: 'learning',
name: 'Learning Agent',
description: 'Continuous improvement loop',
status: 'idle',
type: 'agent',
meta: null
}
],
connections: [
{ from: 'central_orchestrator', to: 'route_database', active: true },
{ from: 'central_orchestrator', to: 'route_planner', active: true },
{ from: 'central_orchestrator', to: 'fairness_manager', active: true },
{ from: 'central_orchestrator', to: 'driver_liaison', active: true },
{ from: 'central_orchestrator', to: 'explainability', active: true },
{ from: 'central_orchestrator', to: 'learning', active: true },
{ from: 'route_database', to: 'route_planner', active: false },
{ from: 'route_planner', to: 'fairness_manager', active: false },
{ from: 'fairness_manager', to: 'driver_liaison', active: false },
{ from: 'driver_liaison', to: 'explainability', active: false },
{ from: 'explainability', to: 'learning', active: false }
],
stats: {
processing: 1,
dataFlows: 6,
totalAgents: 6
},
isRealData: false
};
}
};
// Export for use in app.js
window.API = API;