504 lines
26 KiB
TypeScript
504 lines
26 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 { 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 });
|
|
}
|
|
}); |