first commit

This commit is contained in:
2026-08-17 19:45:29 +05:30
commit 3c27da77a8
510 changed files with 137749 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
import { createClientFromRequest } from 'npm:@base44/sdk@0.8.23';
/**
* Fill Escalation Monitor
* Runs every 15 minutes. Detects events at risk of not being filled
* and creates escalation alerts in ActivityLog.
*
* Fill probability formula:
* score = (assigned / requested) * 60
* + timeBuffer * 20 (more time = more chance to fill)
* + dayOfWeek * 20 (based on historical fill rate for that day)
*
* If fillProbability < 60% → escalation alert
*/
Deno.serve(async (req) => {
const base44 = createClientFromRequest(req);
// Allow scheduled/service calls without user auth
const now = new Date();
const in24h = new Date(now.getTime() + 24 * 60 * 60 * 1000);
const in4h = new Date(now.getTime() + 4 * 60 * 60 * 1000);
// Fetch all upcoming active events
const allEvents = await base44.asServiceRole.entities.Event.list();
const upcoming = allEvents.filter(e => {
if (!e.date) return false;
const eventDate = new Date(e.date);
return eventDate > now && eventDate <= in24h &&
["Active", "Pending", "Confirmed", "Draft"].includes(e.status);
});
// Historical fill rate by day of week
const completed = allEvents.filter(e => e.status === "Completed" && e.date);
const dayFillRate = Array(7).fill(null).map((_, dow) => {
const dayEvents = completed.filter(e => new Date(e.date).getDay() === dow);
if (dayEvents.length === 0) return 0.75; // default 75%
const filled = dayEvents.filter(e => (e.assigned_staff?.length || 0) >= (e.requested || 1));
return filled.length / dayEvents.length;
});
const escalations = [];
const alerts = [];
for (const event of upcoming) {
const assigned = event.assigned_staff?.length || 0;
const requested = event.requested || 0;
if (requested === 0) continue;
const fillRatio = assigned / requested;
const eventDate = new Date(event.date);
const hoursUntil = (eventDate - now) / (1000 * 60 * 60);
const dow = eventDate.getDay();
// Time buffer score: 0 = within 2h (critical), 1 = 24h out (comfortable)
const timeBuffer = Math.min(hoursUntil / 24, 1);
// Fill probability 0-100
const fillProbability = Math.round(
fillRatio * 60 +
timeBuffer * 20 +
dayFillRate[dow] * 20
);
const isCritical = hoursUntil <= 4 && fillRatio < 1;
const isAtRisk = fillProbability < 60;
if (isCritical || isAtRisk) {
escalations.push({
event_id: event.id,
event_name: event.event_name,
hours_until: Math.round(hoursUntil),
fill_ratio: fillRatio,
fill_probability: fillProbability,
assigned,
requested,
is_critical: isCritical,
});
// Create ActivityLog alert (avoid duplicates — check if one exists in last 2h)
const existingAlerts = await base44.asServiceRole.entities.ActivityLog.filter({
related_entity_id: event.id,
activity_type: "event_updated",
});
const recentAlert = existingAlerts.find(a => {
const created = new Date(a.created_date);
return (now - created) < 2 * 60 * 60 * 1000; // within 2h
});
if (!recentAlert) {
const severity = isCritical ? "🚨 CRITICAL" : "⚠️ AT RISK";
const gapCount = requested - assigned;
const description = isCritical
? `${event.event_name} starts in ${Math.round(hoursUntil)}h — ${gapCount} position${gapCount > 1 ? "s" : ""} still open. Fill immediately.`
: `${event.event_name} has a ${fillProbability}% fill probability. ${gapCount} position${gapCount > 1 ? "s" : ""} unfilled with ${Math.round(hoursUntil)}h remaining.`;
alerts.push(
base44.asServiceRole.entities.ActivityLog.create({
title: `${severity}: ${event.event_name} needs staffing`,
description,
activity_type: "event_updated",
related_entity_type: "event",
related_entity_id: event.id,
action_link: `EventDetail?id=${event.id}`,
action_label: "Assign Staff Now",
user_id: event.created_by || "system",
is_read: false,
icon_type: "alert",
icon_color: isCritical ? "red" : "yellow",
})
);
}
}
}
await Promise.all(alerts);
return Response.json({
checked: upcoming.length,
escalations: escalations.length,
details: escalations,
timestamp: now.toISOString(),
});
});

View File

@@ -0,0 +1,504 @@
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 { query_type, entity_id, entity_type, depth = 2 } = body;
const entities = base44.asServiceRole.entities;
const graph = { nodes: [], edges: [] };
const visited = new Set();
async function addNode(type, id, label, data = {}) {
const key = `${type}:${id}`;
if (visited.has(key)) return;
visited.add(key);
graph.nodes.push({ type, id, label, data });
}
function edge(from, to, label) {
graph.edges.push({ from, to, label });
}
// ─── CONTRACT ───────────────────────────────────────────────
// Contract → Vendor, Client, Sector, RateCard
async function traverseContract(contractId) {
const contract = await entities.Contract.get(contractId);
if (!contract) return;
await addNode('Contract', contract.id, `Contract: ${contract.contract_name || contract.contract_number || ''}`, contract);
if (contract.vendor_id) {
await addNode('Vendor', contract.vendor_id, `Vendor: ${contract.vendor_name || ''}`);
edge(`Contract:${contract.id}`, `Vendor:${contract.vendor_id}`, 'vendor_party');
}
if (contract.client_id) {
await addNode('Client', contract.client_id, `Client: ${contract.client_name || ''}`);
edge(`Contract:${contract.id}`, `Client:${contract.client_id}`, 'client_party');
}
if (contract.sector_id) {
await addNode('Sector', contract.sector_id, `Sector: ${contract.sector_name || ''}`);
edge(`Contract:${contract.id}`, `Sector:${contract.sector_id}`, 'governs_sector');
}
if (contract.rate_card_id) {
await addNode('RateCard', contract.rate_card_id, `RateCard`);
edge(`Contract:${contract.id}`, `RateCard:${contract.rate_card_id}`, 'defines_rates');
}
}
// ─── TRANSPORTATION ────────────────────────────────────────
// Transportation → Shift, Worker, Vendor, Site, Client
async function traverseTransportation(transportId) {
const transport = await entities.Transportation.get(transportId);
if (!transport) return;
await addNode('Transportation', transport.id, `Transport: ${transport.transport_type} → ${transport.dropoff_location || ''}`, transport);
if (transport.shift_id) {
await addNode('Shift', transport.shift_id, `Shift`);
edge(`Transportation:${transport.id}`, `Shift:${transport.shift_id}`, 'serves_shift');
}
if (transport.workforce_id) {
await addNode('Worker', transport.workforce_id, `Worker: ${transport.workforce_name || ''}`);
edge(`Transportation:${transport.id}`, `Worker:${transport.workforce_id}`, 'transports_worker');
}
if (transport.vendor_id) {
await addNode('Vendor', transport.vendor_id, `Vendor: ${transport.vendor_name || ''}`);
edge(`Transportation:${transport.id}`, `Vendor:${transport.vendor_id}`, 'arranged_by');
}
if (transport.client_id) {
await addNode('Client', transport.client_id, `Client: ${transport.client_name || ''}`);
edge(`Transportation:${transport.id}`, `Client:${transport.client_id}`, 'destination_client');
}
}
// ─── EQUIPMENT ─────────────────────────────────────────────
// Equipment → Vendor, Client, Site, Shift, Worker
async function traverseEquipment(equipmentId) {
const equipment = await entities.Equipment.get(equipmentId);
if (!equipment) return;
await addNode('Equipment', equipment.id, `Equipment: ${equipment.equipment_name || ''} (${equipment.category})`, equipment);
if (equipment.vendor_id) {
await addNode('Vendor', equipment.vendor_id, `Vendor: ${equipment.vendor_name || ''}`);
edge(`Equipment:${equipment.id}`, `Vendor:${equipment.vendor_id}`, 'owned_by');
}
if (equipment.client_id) {
await addNode('Client', equipment.client_id, `Client: ${equipment.client_name || ''}`);
edge(`Equipment:${equipment.id}`, `Client:${equipment.client_id}`, 'assigned_to_client');
}
if (equipment.shift_id) {
await addNode('Shift', equipment.shift_id, `Shift`);
edge(`Equipment:${equipment.id}`, `Shift:${equipment.shift_id}`, 'used_in_shift');
}
if (equipment.assigned_to_worker_id) {
await addNode('Worker', equipment.assigned_to_worker_id, `Worker: ${equipment.assigned_to_worker_name || ''}`);
edge(`Equipment:${equipment.id}`, `Worker:${equipment.assigned_to_worker_id}`, 'assigned_to_worker');
}
}
// ─── TRAINING ──────────────────────────────────────────────
// Training → Worker, Vendor, Client, Certification
async function traverseTraining(trainingId) {
const training = await entities.Training.get(trainingId);
if (!training) return;
await addNode('Training', training.id, `Training: ${training.training_name || ''}`, training);
if (training.workforce_id) {
await addNode('Worker', training.workforce_id, `Worker: ${training.workforce_name || ''}`);
edge(`Training:${training.id}`, `Worker:${training.workforce_id}`, 'enrolled_worker');
}
if (training.vendor_id) {
await addNode('Vendor', training.vendor_id, `Vendor: ${training.vendor_name || ''}`);
edge(`Training:${training.id}`, `Vendor:${training.vendor_id}`, 'sponsored_by');
}
if (training.client_id) {
await addNode('Client', training.client_id, `Client: ${training.client_name || ''}`);
edge(`Training:${training.id}`, `Client:${training.client_id}`, 'client_specific');
}
if (training.certification_id) {
await addNode('Certification', training.certification_id, `Cert: ${training.certification_earned || ''}`);
edge(`Training:${training.id}`, `Certification:${training.certification_id}`, 'issues_certification');
}
}
// ─── FOOD SERVICE OPERATION ────────────────────────────────
// FoodServiceOperation → Client, Site, Sector, Vendor, Manager, Shift, Event, Contract, Invoice, Equipment
async function traverseFoodServiceOperation(opId) {
const op = await entities.FoodServiceOperation.get(opId);
if (!op) return;
await addNode('FoodServiceOperation', op.id, `FSO: ${op.operation_name || ''}`, op);
if (op.client_id) {
await addNode('Client', op.client_id, `Client: ${op.client_name || ''}`);
edge(`FoodServiceOperation:${op.id}`, `Client:${op.client_id}`, 'serves_client');
}
if (op.vendor_id) {
await addNode('Vendor', op.vendor_id, `Vendor: ${op.vendor_name || ''}`);
edge(`FoodServiceOperation:${op.id}`, `Vendor:${op.vendor_id}`, 'operated_by');
}
if (op.manager_id) {
await addNode('Manager', op.manager_id, `Manager: ${op.manager_name || ''}`);
edge(`FoodServiceOperation:${op.id}`, `Manager:${op.manager_id}`, 'managed_by');
}
if (op.shift_id) {
await addNode('Shift', op.shift_id, `Shift`);
edge(`FoodServiceOperation:${op.id}`, `Shift:${op.shift_id}`, 'runs_during_shift');
}
if (op.contract_id) {
await addNode('Contract', op.contract_id, `Contract`);
edge(`FoodServiceOperation:${op.id}`, `Contract:${op.contract_id}`, 'governed_by_contract');
}
if (op.invoice_id) {
await addNode('Invoice', op.invoice_id, `Invoice`);
edge(`FoodServiceOperation:${op.id}`, `Invoice:${op.invoice_id}`, 'billed_via');
}
if (op.equipment_assigned) {
for (const eq of op.equipment_assigned) {
if (eq.equipment_id) {
await addNode('Equipment', eq.equipment_id, `Equipment: ${eq.equipment_name || ''}`);
edge(`FoodServiceOperation:${op.id}`, `Equipment:${eq.equipment_id}`, 'uses_equipment');
}
}
}
}
// ─── SHIFT (extended) ──────────────────────────────────────
// Shift → Manager, Location, Event, Transportation, Equipment, FoodServiceOperation
async function traverseShift(shiftId) {
const shift = await entities.Shift.get(shiftId);
if (!shift) return;
await addNode('Shift', shift.id, `Shift: ${shift.shift_name || ''}`, shift);
if (shift.manager_id) {
await addNode('Manager', shift.manager_id, `Manager: ${shift.manager_name || ''}`);
edge(`Shift:${shift.id}`, `Manager:${shift.manager_id}`, 'managed_by');
}
if (shift.event_id) {
await addNode('Event', shift.event_id, `Event`);
edge(`Shift:${shift.id}`, `Event:${shift.event_id}`, 'part_of_event');
}
// Transportation serving this shift
const transports = await entities.Transportation.filter({ shift_id: shiftId }, '-created_date', 10);
for (const t of transports) {
await addNode('Transportation', t.id, `Transport: ${t.transport_type}`, t);
edge(`Shift:${shiftId}`, `Transportation:${t.id}`, 'worker_transport');
}
// Equipment assigned to this shift
const equipment = await entities.Equipment.filter({ shift_id: shiftId }, '-created_date', 10);
for (const e of equipment) {
await addNode('Equipment', e.id, `Equipment: ${e.equipment_name || ''}`, e);
edge(`Shift:${shiftId}`, `Equipment:${e.id}`, 'requires_equipment');
}
// Food service operations running during this shift
const fsos = await entities.FoodServiceOperation.filter({ shift_id: shiftId }, '-created_date', 10);
for (const fso of fsos) {
await addNode('FoodServiceOperation', fso.id, `FSO: ${fso.operation_name || ''}`, fso);
edge(`Shift:${shiftId}`, `FoodServiceOperation:${fso.id}`, 'runs_operation');
}
}
// ─── ASSIGNMENT (existing, extended) ───────────────────────
async function traverseAssignment(assignmentId) {
const assignment = await entities.Assignment.get(assignmentId);
if (!assignment) return;
await addNode('Assignment', assignment.id, `Assignment: ${assignment.workforce_name || 'Unknown'}`, assignment);
if (assignment.order_id) {
await addNode('Order', assignment.order_id, `Order: ${assignment.order_number || ''}`);
edge(`Assignment:${assignment.id}`, `Order:${assignment.order_id}`, 'belongs_to');
await traverseOrder(assignment.order_id, 1);
}
if (assignment.workforce_id) {
await addNode('Worker', assignment.workforce_id, `Worker: ${assignment.workforce_name || ''}`);
edge(`Assignment:${assignment.id}`, `Worker:${assignment.workforce_id}`, 'assigned_to');
}
if (assignment.vendor_id) {
await addNode('Vendor', assignment.vendor_id, `Vendor: ${assignment.vendor_name || ''}`);
edge(`Assignment:${assignment.id}`, `Vendor:${assignment.vendor_id}`, 'provided_by');
}
}
// ─── ORDER (existing, extended) ────────────────────────────
async function traverseOrder(orderId, currentDepth = 0) {
if (currentDepth >= depth) return;
try {
const order = await entities.Order.get(orderId);
if (!order) return;
await addNode('Order', order.id, `Order: ${order.order_number || order.event_name || ''}`, order);
if (order.partner_id) {
await addNode('Partner', order.partner_id, `Partner: ${order.partner_name || ''}`);
edge(`Order:${order.id}`, `Partner:${order.partner_id}`, 'for_client');
}
if (order.vendor_id) {
await addNode('Vendor', order.vendor_id, `Vendor: ${order.vendor_name || ''}`);
edge(`Order:${order.id}`, `Vendor:${order.vendor_id}`, 'assigned_to');
}
// Contracts governing this client-vendor pair
if (order.vendor_id && order.business_id) {
const contracts = await entities.Contract.filter({ vendor_id: order.vendor_id, client_id: order.business_id, status: 'active' }, '-created_date', 5);
for (const c of contracts) {
await addNode('Contract', c.id, `Contract: ${c.contract_name || ''}`, c);
edge(`Order:${order.id}`, `Contract:${c.id}`, 'governed_by');
}
}
// Food service operations for this event
if (order.event_id) {
const fsos = await entities.FoodServiceOperation.filter({ event_id: order.event_id }, '-created_date', 10);
for (const fso of fsos) {
await addNode('FoodServiceOperation', fso.id, `FSO: ${fso.operation_name || ''}`, fso);
edge(`Order:${order.id}`, `FoodServiceOperation:${fso.id}`, 'fulfills_operation');
}
}
} catch (e) { /* order might not exist */ }
}
// ─── VENDOR (existing, extended) ───────────────────────────
async function traverseVendor(vendorId) {
const vendor = await entities.Vendor.get(vendorId);
if (!vendor) return;
await addNode('Vendor', vendor.id, `Vendor: ${vendor.legal_name || vendor.doing_business_as || ''}`, vendor);
// Rates
const rates = await entities.VendorRate.filter({ vendor_id: vendorId }, '-created_date', 10);
for (const rate of rates) {
await addNode('VendorRate', rate.id, `Rate: ${rate.role_name || 'Unknown'} - $${rate.client_rate || 0}/hr`, rate);
edge(`Vendor:${vendorId}`, `VendorRate:${rate.id}`, 'has_rate');
}
// Workforce
const workforce = await entities.Workforce.filter({ vendor_id: vendorId }, '-created_date', 10);
for (const w of workforce) {
await addNode('Worker', w.id, `Worker: ${w.full_name || w.name || ''}`, w);
edge(`Vendor:${vendorId}`, `Worker:${w.id}`, 'employs');
}
// Contracts
const contracts = await entities.Contract.filter({ vendor_id: vendorId }, '-created_date', 10);
for (const c of contracts) {
await addNode('Contract', c.id, `Contract: ${c.contract_name || ''}`, c);
edge(`Vendor:${vendorId}`, `Contract:${c.id}`, 'party_to');
if (c.client_id) {
await addNode('Client', c.client_id, `Client: ${c.client_name || ''}`);
edge(`Contract:${c.id}`, `Client:${c.client_id}`, 'client_party');
}
}
// Equipment owned
const equipment = await entities.Equipment.filter({ vendor_id: vendorId }, '-created_date', 10);
for (const e of equipment) {
await addNode('Equipment', e.id, `Equipment: ${e.equipment_name || ''}`, e);
edge(`Vendor:${vendorId}`, `Equipment:${e.id}`, 'owns');
}
// Transportation arranged
const transports = await entities.Transportation.filter({ vendor_id: vendorId }, '-created_date', 10);
for (const t of transports) {
await addNode('Transportation', t.id, `Transport: ${t.transport_type}`, t);
edge(`Vendor:${vendorId}`, `Transportation:${t.id}`, 'arranges');
}
// Training sponsored
const trainings = await entities.Training.filter({ vendor_id: vendorId }, '-created_date', 10);
for (const t of trainings) {
await addNode('Training', t.id, `Training: ${t.training_name || ''}`, t);
edge(`Vendor:${vendorId}`, `Training:${t.id}`, 'sponsors_training');
}
}
// ─── WORKER (existing, extended) ───────────────────────────
async function traverseWorker(workerId) {
const worker = await entities.Workforce.get(workerId);
if (!worker) return;
await addNode('Worker', worker.id, `Worker: ${worker.full_name || worker.name || ''}`, worker);
if (worker.vendor_id) {
await addNode('Vendor', worker.vendor_id, `Vendor: ${worker.vendor_name || ''}`);
edge(`Worker:${workerId}`, `Vendor:${worker.vendor_id}`, 'employed_by');
}
// Assignments
const assignments = await entities.Assignment.filter({ workforce_id: workerId }, '-created_date', 10);
for (const a of assignments) {
await addNode('Assignment', a.id, `Assignment: ${a.role || ''}`, a);
edge(`Worker:${workerId}`, `Assignment:${a.id}`, 'assigned');
}
// Availability
const availability = await entities.WorkerAvailability.filter({ staff_id: workerId }, '-created_date', 1);
if (availability.length > 0) {
await addNode('Availability', availability[0].id, `Availability: ${availability[0].availability_status}`, availability[0]);
edge(`Worker:${workerId}`, `Availability:${availability[0].id}`, 'availability');
}
// Reputation
const reputation = await entities.WorkerReputationProfile.filter({ worker_id: workerId }, '-created_date', 1);
if (reputation.length > 0) {
await addNode('Reputation', reputation[0].id, `Reputation: ${reputation[0].trust_tier} (${reputation[0].trust_score || 0})`, reputation[0]);
edge(`Worker:${workerId}`, `Reputation:${reputation[0].id}`, 'reputation');
}
// Training records
const trainings = await entities.Training.filter({ workforce_id: workerId }, '-created_date', 10);
for (const t of trainings) {
await addNode('Training', t.id, `Training: ${t.training_name || ''}`, t);
edge(`Worker:${workerId}`, `Training:${t.id}`, 'enrolled_in');
}
// Equipment assigned
const equipment = await entities.Equipment.filter({ assigned_to_worker_id: workerId }, '-created_date', 10);
for (const e of equipment) {
await addNode('Equipment', e.id, `Equipment: ${e.equipment_name || ''}`, e);
edge(`Worker:${workerId}`, `Equipment:${e.id}`, 'issued_equipment');
}
// Transportation
const transports = await entities.Transportation.filter({ workforce_id: workerId }, '-created_date', 10);
for (const t of transports) {
await addNode('Transportation', t.id, `Transport: ${t.transport_type}`, t);
edge(`Worker:${workerId}`, `Transportation:${t.id}`, 'transported_by');
}
}
// ─── FULL GRAPH (new) ──────────────────────────────────────
// Walks the entire connected chain from any starting node, up to depth hops
async function traverseFull(startType, startId, maxDepth = 3) {
const queue = [{ type: startType, id: startId, depth: 0 }];
const seen = new Set();
while (queue.length > 0) {
const { type, id, depth: curDepth } = queue.shift();
const key = `${type}:${id}`;
if (seen.has(key) || curDepth > maxDepth) continue;
seen.add(key);
switch (type) {
case 'Worker': {
const w = await entities.Workforce.get(id).catch(() => null);
if (!w) break;
await addNode('Worker', w.id, `Worker: ${w.full_name || w.name || ''}`, w);
if (w.vendor_id) queue.push({ type: 'Vendor', id: w.vendor_id, depth: curDepth + 1 });
const as = await entities.Assignment.filter({ workforce_id: id }, '-created_date', 5);
for (const a of as) queue.push({ type: 'Assignment', id: a.id, depth: curDepth + 1 });
const ts = await entities.Training.filter({ workforce_id: id }, '-created_date', 5);
for (const t of ts) queue.push({ type: 'Training', id: t.id, depth: curDepth + 1 });
const eq = await entities.Equipment.filter({ assigned_to_worker_id: id }, '-created_date', 5);
for (const e of eq) queue.push({ type: 'Equipment', id: e.id, depth: curDepth + 1 });
const tr = await entities.Transportation.filter({ workforce_id: id }, '-created_date', 5);
for (const t of tr) queue.push({ type: 'Transportation', id: t.id, depth: curDepth + 1 });
break;
}
case 'Vendor': {
const v = await entities.Vendor.get(id).catch(() => null);
if (!v) break;
await addNode('Vendor', v.id, `Vendor: ${v.legal_name || v.doing_business_as || ''}`, v);
const ws = await entities.Workforce.filter({ vendor_id: id }, '-created_date', 10);
for (const w of ws) { await addNode('Worker', w.id, `Worker: ${w.full_name || ''}`, w); edge(`Vendor:${id}`, `Worker:${w.id}`, 'employs'); queue.push({ type: 'Worker', id: w.id, depth: curDepth + 1 }); }
const cs = await entities.Contract.filter({ vendor_id: id }, '-created_date', 10);
for (const c of cs) { await addNode('Contract', c.id, `Contract: ${c.contract_name || ''}`, c); edge(`Vendor:${id}`, `Contract:${c.id}`, 'party_to'); queue.push({ type: 'Contract', id: c.id, depth: curDepth + 1 }); }
const eqs = await entities.Equipment.filter({ vendor_id: id }, '-created_date', 10);
for (const e of eqs) { await addNode('Equipment', e.id, `Equipment: ${e.equipment_name || ''}`, e); edge(`Vendor:${id}`, `Equipment:${e.id}`, 'owns'); }
const fsos = await entities.FoodServiceOperation.filter({ vendor_id: id }, '-created_date', 10);
for (const f of fsos) { await addNode('FoodServiceOperation', f.id, `FSO: ${f.operation_name || ''}`, f); edge(`Vendor:${id}`, `FoodServiceOperation:${f.id}`, 'operates'); queue.push({ type: 'FoodServiceOperation', id: f.id, depth: curDepth + 1 }); }
break;
}
case 'Contract': {
const c = await entities.Contract.get(id).catch(() => null);
if (!c) break;
await addNode('Contract', c.id, `Contract: ${c.contract_name || ''}`, c);
if (c.vendor_id) { await addNode('Vendor', c.vendor_id, `Vendor: ${c.vendor_name || ''}`); edge(`Contract:${id}`, `Vendor:${c.vendor_id}`, 'vendor_party'); }
if (c.client_id) { await addNode('Client', c.client_id, `Client: ${c.client_name || ''}`); edge(`Contract:${id}`, `Client:${c.client_id}`, 'client_party'); }
break;
}
case 'Shift': {
await traverseShift(id);
break;
}
case 'FoodServiceOperation': {
await traverseFoodServiceOperation(id);
break;
}
case 'Transportation': {
await traverseTransportation(id);
break;
}
case 'Equipment': {
await traverseEquipment(id);
break;
}
case 'Training': {
await traverseTraining(id);
break;
}
case 'Assignment': {
await traverseAssignment(id);
break;
}
case 'Order': {
await traverseOrder(id, 0);
break;
}
}
}
}
// ─── EXECUTE ───────────────────────────────────────────────
if (query_type === 'subgraph' && entity_id) {
switch (entity_type) {
case 'Assignment': await traverseAssignment(entity_id); break;
case 'Order': await traverseOrder(entity_id, 0); break;
case 'Vendor': await traverseVendor(entity_id); break;
case 'Worker': await traverseWorker(entity_id); break;
case 'Shift': await traverseShift(entity_id); break;
case 'Contract': await traverseContract(entity_id); break;
case 'Transportation': await traverseTransportation(entity_id); break;
case 'Equipment': await traverseEquipment(entity_id); break;
case 'Training': await traverseTraining(entity_id); break;
case 'FoodServiceOperation': await traverseFoodServiceOperation(entity_id); break;
default: break;
}
} else if (query_type === 'full_graph' && entity_id && entity_type) {
await traverseFull(entity_type, entity_id, depth);
} else if (query_type === 'site_labor') {
const assignments = await entities.Assignment.filter({}, '-created_date', 100);
graph.nodes = assignments.map(a => ({
type: 'Assignment',
id: a.id,
label: `${a.workforce_name} - ${a.role}`,
data: a
}));
} else if (query_type === 'shortage_prediction') {
const orders = await entities.Order.filter({ status: 'Open' }, '-created_date', 50);
for (const order of orders) {
await addNode('Order', order.id, `Order: ${order.order_number || ''}`, order);
const assignments = await entities.Assignment.filter({ order_id: order.id }, '-created_date', 50);
const filled = assignments.filter(a => a.assignment_status === 'Confirmed' || a.assignment_status === 'Checked In').length;
const needed = order.total_staff || 0;
if (filled < needed) {
edge(`Order:${order.id}`, `SHORTAGE:${order.id}`, `short_by_${needed - filled}`);
graph.nodes.push({ type: 'Alert', id: `SHORTAGE:${order.id}`, label: `Shortage: ${needed - filled} unfilled positions`, data: { order_id: order.id, needed, filled, gap: needed - filled } });
}
}
} else if (query_type === 'entity_summary') {
// Returns counts of every entity type in the graph — the "graph census"
const counts = {};
const entityTypes = ['Workforce', 'Vendor', 'Business', 'Site', 'Shift', 'Event', 'Order', 'Assignment', 'Invoice', 'CompliancePackage', 'Scorecard', 'Contract', 'Transportation', 'Equipment', 'Training', 'FoodServiceOperation'];
for (const et of entityTypes) {
try {
const list = await entities[et].list('-created_date', 1);
counts[et] = list?.length || 0;
} catch (e) {
counts[et] = 0;
}
}
return Response.json({ success: true, counts });
}
return Response.json({ success: true, graph, node_count: graph.nodes.length, edge_count: graph.edges.length });
} catch (error) {
return Response.json({ error: error.message }, { status: 500 });
}
});

View File

@@ -0,0 +1,235 @@
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 });
}
});

View File

@@ -0,0 +1,79 @@
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_type,
severity = 'info',
actor_type = 'system',
actor_id,
actor_name,
subject_entity_type,
subject_entity_id,
subject_krow_id,
context_summary,
related_entities = [],
metadata = {},
tags = []
} = body;
if (!event_type || !subject_entity_type || !subject_entity_id) {
return Response.json({ error: 'event_type, subject_entity_type, and subject_entity_id are required' }, { status: 400 });
}
const event_id = `EVT-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
const event = await base44.asServiceRole.entities.WorkforceEvent.create({
event_id,
event_type,
severity,
actor_type,
actor_id: actor_id || user.id,
actor_name: actor_name || user.full_name || 'System',
subject_entity_type,
subject_entity_id,
subject_krow_id,
context_summary: context_summary || `${event_type} on ${subject_entity_type}`,
related_entities,
metadata,
reasoning_triggered: false,
tags
});
// Determine if this event should trigger reasoning
const reasoningTriggers = [
'shift.completed', 'shift.cancelled', 'shift.no_show',
'order.created', 'order.cancelled', 'order.filled',
'assignment.cancelled', 'invoice.overdue',
'vendor.rate_changed', 'worker.availability_changed',
'worker.certification_expired', 'budget.threshold_exceeded',
'compliance.flagged', 'system.anomaly_detected'
];
if (reasoningTriggers.includes(event_type)) {
try {
await base44.asServiceRole.functions.invoke('reasonAboutEvent', {
event_id: event.id,
event_type,
subject_entity_type,
subject_entity_id,
context_summary,
metadata,
related_entities
});
} catch (reasonErr) {
// Reasoning failure shouldn't fail the event recording
console.log('Reasoning engine error (non-blocking):', reasonErr.message);
}
}
return Response.json({ success: true, event_id: event.id, event });
} catch (error) {
return Response.json({ error: error.message }, { status: 500 });
}
});

View File

@@ -0,0 +1,104 @@
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 });
if (user.role !== 'admin') return Response.json({ error: 'Forbidden — admin only' }, { status: 403 });
const entities = base44.asServiceRole.entities;
// Fetch memories with pending outcomes (executed but not yet learned)
const pendingMemories = await entities.OperationalMemory.filter(
{ status: 'outcome_pending' },
'-created_date',
50
);
let learned = 0;
let stillPending = 0;
for (const memory of pendingMemories) {
try {
// Re-query the graph to see what happened since the decision was made
const graphRes = await base44.asServiceRole.functions.invoke('queryWorkforceGraph', {
query_type: 'subgraph',
entity_type: memory.context_snapshot?.metadata?.subject_entity_type || 'Order',
entity_id: memory.context_snapshot?.metadata?.subject_entity_id || memory.trigger_event_id
});
const currentGraph = graphRes.data?.graph || {};
// Ask the LLM to evaluate the outcome
const prompt = `You are evaluating the outcome of a past KROW AI decision.
ORIGINAL EVENT: ${memory.trigger_event_type}
ORIGINAL DECISION: ${memory.decision?.recommendation || 'N/A'}
ORIGINAL PREDICTION: ${memory.decision?.prediction || 'N/A'}
CURRENT STATE (after decision):
${JSON.stringify(currentGraph, null, 2).substring(0, 2000)}
ORIGINAL CONTEXT (at decision time):
${JSON.stringify(memory.context_snapshot?.graph || {}, null, 2).substring(0, 2000)}
Evaluate: Did the decision help? Did the prediction come true? What was the outcome?
Respond as JSON:
{
"outcome": "Description of what actually happened",
"outcome_score": -100 to 100 (negative = made things worse, 0 = no effect, positive = helped),
"outcome_metrics": {"key_metric": value}
}`;
const evalResponse = await base44.asServiceRole.integrations.Core.InvokeLLM({
prompt,
response_json_schema: {
type: 'object',
properties: {
outcome: { type: 'string' },
outcome_score: { type: 'number' },
outcome_metrics: { type: 'object', additionalProperties: true }
}
}
});
const evaluation = typeof evalResponse === 'string' ? JSON.parse(evalResponse) : evalResponse;
await entities.OperationalMemory.update(memory.id, {
outcome: evaluation.outcome,
outcome_score: evaluation.outcome_score,
outcome_metrics: evaluation.outcome_metrics,
status: 'learned',
learned_at: new Date().toISOString()
});
// Log learning activity
await entities.AgentActivity.create({
agent_name: memory.agent_name || 'system',
department: memory.department || 'operations',
activity_type: 'learning_completed',
memory_id: memory.id,
summary: `Learned from ${memory.trigger_event_type}: outcome score ${evaluation.outcome_score}`,
details: evaluation,
impact_level: evaluation.outcome_score > 50 ? 'high' : (evaluation.outcome_score > 0 ? 'medium' : 'low'),
entities_affected: []
});
learned++;
} catch (e) {
console.log(`Failed to evaluate memory ${memory.id}:`, e.message);
stillPending++;
}
}
return Response.json({
success: true,
reviewed: pendingMemories.length,
learned,
still_pending: stillPending
});
} catch (error) {
return Response.json({ error: error.message }, { status: 500 });
}
});