Files
krowworkforce_controltower/base44/functions/reasonAboutEvent/entry.ts
2026-08-17 19:45:29 +05:30

235 lines
8.9 KiB
TypeScript

import { createClientFromRequest } from 'npm:@base44/sdk@0.8.31';
Deno.serve(async (req) => {
try {
const base44 = createClientFromRequest(req);
const user = await base44.auth.me();
if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 });
const body = await req.json();
const {
event_id,
event_type,
subject_entity_type,
subject_entity_id,
context_summary,
metadata = {},
related_entities = []
} = body;
const entities = base44.asServiceRole.entities;
// 1. Fetch context from the Workforce Graph
let graphContext = {};
try {
const graphRes = await base44.asServiceRole.functions.invoke('queryWorkforceGraph', {
query_type: 'subgraph',
entity_type: subject_entity_type,
entity_id: subject_entity_id
});
graphContext = graphRes.data?.graph || {};
} catch (e) {
console.log('Graph query failed (non-blocking):', e.message);
}
// 2. Fetch similar past memories (Operational Memory)
const similarMemories = await entities.OperationalMemory.filter(
{ trigger_event_type: event_type },
'-created_date',
5
);
const memoryContext = similarMemories.map(m => ({
decision: m.decision?.recommendation || 'N/A',
outcome: m.outcome || 'pending',
outcome_score: m.outcome_score,
reasoning: m.reasoning?.substring(0, 200) || ''
}));
// 3. Determine department based on event type
const departmentMap = {
'shift.completed': 'operations',
'shift.cancelled': 'operations',
'shift.no_show': 'operations',
'order.created': 'operations',
'order.cancelled': 'operations',
'order.filled': 'operations',
'assignment.cancelled': 'operations',
'invoice.overdue': 'finance',
'vendor.rate_changed': 'procurement',
'worker.availability_changed': 'operations',
'worker.certification_expired': 'compliance',
'budget.threshold_exceeded': 'finance',
'compliance.flagged': 'compliance',
'system.anomaly_detected': 'executive'
};
const department = departmentMap[event_type] || 'operations';
// 4. Build the reasoning prompt with full context — CAUSAL CHAIN reasoning
const prompt = `You are the KROW Intelligence Engine — an AI workforce reasoning system for contingent labor management.
You do not answer. You REASON.
When an event occurs, you trace the CAUSAL CHAIN backwards — from the observed event through each contributing factor (connected by ↓) to the root cause(s). You do not give generic analysis. You follow the chain.
AN EVENT HAS OCCURRED:
Event Type: ${event_type}
Summary: ${context_summary || 'No summary provided'}
Subject: ${subject_entity_type} (${subject_entity_id})
CURRENT GRAPH STATE (connected entities, relationships, metrics):
${JSON.stringify(graphContext, null, 2).substring(0, 3000)}
SIMILAR PAST DECISIONS (Operational Memory — what happened last time):
${memoryContext.length > 0 ? JSON.stringify(memoryContext, null, 2) : 'No similar past decisions found.'}
EVENT METADATA:
${JSON.stringify(metadata, null, 2).substring(0, 1000)}
REASONING INSTRUCTIONS:
1. OBSERVATION: State what happened as a fact.
2. CAUSAL CHAIN: Trace WHY this event occurred. Walk backwards from the event through contributing factors. Each link in the chain is a factor that CAUSED the next. The chain flows:
Factor A → caused → Factor B → caused → Factor C → caused → [Root Cause]
Use the graph state, metadata, and past memories as evidence. Reference specific entities, workers, vendors, shifts, or metrics. Do not be generic.
Categories: vendor, operations, workforce, training, demand, external, compliance, finance, equipment, transportation, system.
3. ROOT CAUSE(S): The deepest factor(s) — the thing that, if fixed, prevents recurrence.
4. PREDICTION: What will happen next if no action is taken.
5. RECOMMENDATION: Target the root cause, not just the symptom.
Respond as JSON.`;
// 5. Call the LLM for reasoning
let aiDecision = {};
try {
const llmResponse = await base44.asServiceRole.integrations.Core.InvokeLLM({
prompt,
response_json_schema: {
type: 'object',
properties: {
observation: { type: 'string', description: 'What happened, stated as a fact' },
causal_chain: {
type: 'array',
description: 'Causal chain traced backwards from the event to root cause(s)',
items: {
type: 'object',
properties: {
step: { type: 'number' },
factor: { type: 'string' },
evidence: { type: 'string' },
category: { type: 'string', enum: ['vendor', 'operations', 'workforce', 'training', 'demand', 'external', 'compliance', 'finance', 'equipment', 'transportation', 'system'] },
leads_to: { type: 'string' }
}
}
},
root_causes: {
type: 'array',
items: { type: 'string' }
},
prediction: { type: 'string' },
recommendation: { type: 'string' },
confidence_score: { type: 'number' },
risk_level: { type: 'string' },
proposed_actions: {
type: 'array',
items: {
type: 'object',
properties: {
action_type: { type: 'string' },
description: { type: 'string' },
auto_execute: { type: 'boolean' }
}
}
},
reasoning: { type: 'string', description: 'Full reasoning chain in natural language' }
}
}
});
aiDecision = typeof llmResponse === 'string' ? JSON.parse(llmResponse) : llmResponse;
} catch (llmErr) {
console.log('LLM reasoning failed:', llmErr.message);
aiDecision = {
prediction: 'Unable to generate prediction',
recommendation: 'Manual review recommended',
confidence_score: 0,
risk_level: 'medium',
proposed_actions: [],
reasoning: 'LLM reasoning failed: ' + llmErr.message
};
}
// 6. Create Operational Memory — store the causal chain
const memory_id = `MEM-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
const memory = await entities.OperationalMemory.create({
memory_id,
trigger_event_id: event_id,
trigger_event_type: event_type,
context_snapshot: { graph: graphContext, metadata },
decision: aiDecision,
reasoning: aiDecision.reasoning || '',
causal_chain: aiDecision.causal_chain || [],
root_causes: aiDecision.root_causes || [],
similar_memories_consulted: similarMemories.map(m => m.memory_id),
status: 'pending',
agent_name: `${department}_agent`,
department,
tags: [event_type, department, aiDecision.risk_level || 'medium']
});
// 7. Mark event as reasoning-triggered
await entities.WorkforceEvent.update(event_id, {
reasoning_triggered: true,
memory_id: memory.id
});
// 8. Log agent activity
await entities.AgentActivity.create({
agent_name: `${department}_agent`,
department,
activity_type: 'event_processed',
trigger_event_id: event_id,
memory_id: memory.id,
summary: `Processed ${event_type}: ${aiDecision.recommendation || 'No recommendation'}`,
details: { confidence: aiDecision.confidence_score, risk: aiDecision.risk_level },
impact_level: aiDecision.risk_level === 'high' ? 'high' : (aiDecision.risk_level === 'medium' ? 'medium' : 'low'),
entities_affected: [{ entity_type: subject_entity_type, entity_id: subject_entity_id }]
});
// 9. If high risk, queue for human approval; if low risk + auto_execute, execute
const hasAutoActions = (aiDecision.proposed_actions || []).some(a => a.auto_execute);
if (aiDecision.risk_level === 'high' || (aiDecision.confidence_score || 0) < 60) {
await entities.DecisionQueue.create({
memory_id: memory.id,
trigger_event_id: event_id,
title: `${department} decision: ${event_type}`,
summary: aiDecision.recommendation || 'AI recommends manual review',
department,
agent_name: `${department}_agent`,
proposed_actions: aiDecision.proposed_actions || [],
confidence_score: aiDecision.confidence_score || 0,
risk_level: aiDecision.risk_level || 'medium',
status: 'pending_approval',
expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
context_snapshot: { graph: graphContext, event_summary: context_summary }
});
}
return Response.json({
success: true,
memory_id: memory.id,
decision: aiDecision,
graph_nodes: graphContext.nodes?.length || 0,
similar_memories: similarMemories.length
});
} catch (error) {
return Response.json({ error: error.message }, { status: 500 });
}
});