← Back to all documents

cai-exos-systems/daveadmin-exos-demo:api/quote-order.php

gitea 6,819 words Source ↗
api/quote-order.php ```text <?php declare(strict_types=1); /** * Exos Demo V2: Autonomous Quote-to-Order orchestrator. * Streams a governed outcome loop over FOSSBilling, Directus, OPA, CaveauAI standards data, * and the provisioning simulator. This endpoint is intentionally isolated from demo.exos. */ require_once dirname(__DIR__) . '/auth.php'; set_time_limit(120); ini_set('memory_limit', '256M'); demoRequireApiLogin(); if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); header('Content-Type: application/json'); echo json_encode(['error' => 'Method not allowed']); exit; } if (!demoOriginAllowed()) { http_response_code(403); header('Content-Type: application/json'); echo json_encode(['error' => 'Origin not allowed']); exit; } $ipRate = demoRateLimit('quote_order_ip', demoClientIp(), demoApiRequestLimit(), demoApiWindowSeconds()); $sessionRate = demoRateLimit('quote_order_session', session_id(), demoApiRequestLimit(), demoApiWindowSeconds()); if (!$ipRate['allowed'] || !$sessionRate['allowed']) { http_response_code(429); header('Content-Type: application/json'); echo json_encode(['error' => 'Too many requests']); exit; } $body = json_decode(file_get_contents('php://input') ?: '{}', true) ?: []; $message = trim((string)($body['message'] ?? '')); $requestedAccount = (string)($body['account'] ?? '6'); if ($message === '') { http_response_code(400); header('Content-Type: application/json'); echo json_encode(['error' => 'Message required']); exit; } header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('X-Accel-Buffering: no'); if (ob_get_level()) { ob_end_clean(); } ob_implicit_flush(true); function sse(array $data): void { echo 'data: ' . json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n\n"; if (ob_get_level()) { ob_flush(); } flush(); } function jsonRequest(string $method, string $url, ?array $payload = null, array $headers = []): array { $ch = curl_init($url); $headerLines = ['Accept: application/json']; foreach ($headers as $name => $value) { if ($value !== null && $value !== '') { $headerLines[] = $name . ': ' . $value; } } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => strtoupper($method), CURLOPT_HTTPHEADER => $headerLines, CURLOPT_TIMEOUT => 25, ]); if ($payload !== null) { $encoded = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded); $headerLines[] = 'Content-Type: application/json'; curl_setopt($ch, CURLOPT_HTTPHEADER, $headerLines); } $raw = curl_exec($ch); $err = curl_error($ch); $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($raw === false) { return ['ok' => false, 'status' => 0, 'error' => $err, 'data' => null]; } $data = json_decode((string)$raw, true); return ['ok' => $status >= 200 && $status < 300, 'status' => $status, 'error' => $err ?: null, 'data' => $data ?? $raw]; } function emitTool(string $tool, string $tmfApi, string $endpoint, array $input, callable $fn): array { sse([ 'event' => 'tool_start', 'tool' => $tool, 'tmf_api' => $tmfApi, 'endpoint' => $endpoint, 'input' => $input, ]); $started = microtime(true); $result = $fn(); $result['_latency_ms'] = (int)round((microtime(true) - $started) * 1000); sse([ 'event' => 'tool_result', 'tool' => $tool, 'tmf_api' => $tmfApi, 'endpoint' => $endpoint, 'output' => $result, ]); sse([ 'event' => 'agent_thought', 'tool' => $tool, 'thought' => $result['thought'] ?? ('Completed ' . str_replace('_', ' ', $tool)), 'tool_input' => json_encode($input, JSON_UNESCAPED_SLASHES), 'observation' => json_encode($result, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), ]); return $result; } function fossBillingApi(string $module, string $root) { static $booted = false; static $adminIdentity = null; if (!$booted) { $load = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'load.php'; if (!is_file($load)) { throw new RuntimeException('FOSSBilling load.php not found'); } $previousCwd = getcwd(); if (!chdir($root)) { throw new RuntimeException('Unable to enter FOSSBilling root'); } ob_start(); try { require_once $load; } finally { if (ob_get_level()) { ob_end_clean(); } if ($previousCwd !== false) { chdir($previousCwd); } } $booted = true; } global $di; if (!$di instanceof Pimple\Container) { throw new RuntimeException('FOSSBilling DI unavailable'); } if ($adminIdentity === null) { $adminIdentity = $di['db']->findOne('Admin'); } if (!$adminIdentity) { throw new RuntimeException('No FOSSBilling admin identity available'); } $map = [ 'client' => \Box\Mod\Client\Api\Admin::class, 'invoice' => \Box\Mod\Invoice\Api\Admin::class, 'order' => \Box\Mod\Order\Api\Admin::class, 'support' => \Box\Mod\Support\Api\Admin::class, ]; if (!isset($map[$module])) { throw new InvalidArgumentException('Unsupported FOSSBilling module'); }
api/quote-order.php 'quantity' => (int)($item['quantity'] ?? 1), 'price' => (float)($item['price'] ?? 0), ], $items), ]; }, $invoices); $tickets = array_map(static fn($row) => [ 'id' => (int)($row['id'] ?? 0), 'subject' => $row['subject'] ?? '', 'status' => $row['status'] ?? '', 'priority' => (int)($row['priority'] ?? 0), 'rel_type' => $row['rel_type'] ?? '', 'rel_id' => $row['rel_id'] ?? null, 'rel_task' => $row['rel_task'] ?? '', 'rel_status' => $row['rel_status'] ?? '', 'created_at' => $row['created_at'] ?? null, 'messages' => $ticketMessages[(int)($row['id'] ?? 0)] ?? [], ], $tickets); $openTickets = array_values(array_filter($tickets, static fn($row) => !in_array((string)$row['status'], ['closed', 'solved'], true))); $unpaidInvoices = array_values(array_filter($invoices, static fn($row) => (string)$row['status'] !== 'paid')); $failedOrders = array_values(array_filter($orders, static fn($row) => in_array((string)$row['status'], ['failed', 'canceled', 'suspended'], true))); $activeOrders = array_values(array_filter($orders, static fn($row) => in_array((string)$row['status'], ['active', 'pending_setup'], true))); $latestInvoice = $invoices[0] ?? null; return [ 'ok' => true, 'system' => 'FOSSBilling', 'client' => [ 'id' => $clientId, 'name' => trim((string)(($client['first_name'] ?? '') . ' ' . ($client['last_name'] ?? ''))), 'company' => $client['company'] ?? '', 'email' => $client['email'] ?? '', 'status' => $client['status'] ?? '', 'currency' => $client['currency'] ?? 'USD', 'city' => $client['city'] ?? '', 'country' => $client['country'] ?? '', ], 'orders' => $orders, 'invoices' => $invoices, 'tickets' => $tickets, 'latest_invoice' => $latestInvoice, 'summary' => [ 'active_orders' => count($activeOrders), 'failed_orders' => count($failedOrders), 'open_tickets' => count($openTickets), 'unpaid_invoices' => count($unpaidInvoices), 'latest_invoice_total' => $latestInvoice['total'] ?? 0, 'currency' => $latestInvoice['currency'] ?? ($client['currency'] ?? 'USD'), ], 'thought' => 'FOSSBilling is the system of record for this V2 run: client, orders, invoice items, and support tickets are live demo rows.', ]; } function classifyPrompt(string $message): string { $text = strtolower($message); if (preg_match('/\b(ticket|case)\b.*#?\s*\d+|#\s*\d+.*\b(ticket|case|approval|fallout)\b|\bshow this ticket\b|\bthis ticket\b/', $text)) { return 'ticket_detail'; } if (preg_match('/\b(failed|failure|stuck|broken|error|fallout)\b.*\b(order|orders|provision|activation)\b|\b(order|orders)\b.*\b(failed|failure|stuck|broken|error|fallout)\b/', $text)) { return 'failed_orders'; } if (preg_match('/\b(ticket|tickets|case|cases|approval|approvals|human gate|blocker|blocking|exception|exceptions)\b/', $text)) { return 'tickets'; } if (preg_match('/\b(quote items|line items|items|what.*included|included|bundle details|price breakdown|breakdown)\b/', $text)) { return 'quote_items'; } if (preg_match('/\b(billing|invoice|invoices|bill|total|monthly|price|pricing|charge|charges)\b/', $text)) { return 'billing'; } if (preg_match('/\b(next|finish|continue|release|safe parts|safe work|what now|next action)\b/', $text)) { return 'next_action'; } if (preg_match('/\b(tmf|standard|standards|api map|map .*flow|tmf620|tmf622|tmf641|tmf678|tmf688|tmf621|tmf648|tmf679)\b/', $text)) { return 'tmf_map'; } if (preg_match('/\b(without replacing|not replacing|replace the bss|existing bss|control plane|architecture|open source|overlay|no rip|rip-and-replace)\b/', $text)) { return 'architecture'; } if (preg_match('/\b(create|build|fulfill|configure|best .*bundle|bundle under|under \$?\d+|activate .*today|household bundle)\b/', $text)) { return 'build_bundle'; } if (preg_match('/\b(explain|policy|pricing|entitlement|provisioning|fallout|approval|handles|how the agent|full .*loop|fossbilling|tickets?|invoice|directus|opa)\b/', $text)) { return 'explain_loop'; } return 'build_bundle'; } function modeLabel(string $mode): string { return match ($mode) { 'tmf_map' => 'TM Forum Mapping', 'architecture' => 'BSS Overlay Architecture', 'explain_loop' => 'Control-Plane Explanation', 'failed_orders' => 'Failed Order Check', 'ticket_detail' => 'Ticket Detail', 'tickets' => 'Ticket Check', 'quote_items' => 'Quote Items', 'billing' => 'Billing Check', 'next_action' => 'Next Action', default => 'Autonomous Bundle Outcome', }; }
api/quote-order.php .*today|household bundle)\b/', $text)) { return 'build_bundle'; } if (preg_match('/\b(explain|policy|pricing|entitlement|provisioning|fallout|approval|handles|how the agent|full .*loop|fossbilling|tickets?|invoice|directus|opa)\b/', $text)) { return 'explain_loop'; } return 'build_bundle'; } function modeLabel(string $mode): string { return match ($mode) { 'tmf_map' => 'TM Forum Mapping', 'architecture' => 'BSS Overlay Architecture', 'explain_loop' => 'Control-Plane Explanation', 'failed_orders' => 'Failed Order Check', 'ticket_detail' => 'Ticket Detail', 'tickets' => 'Ticket Check', 'quote_items' => 'Quote Items', 'billing' => 'Billing Check', 'next_action' => 'Next Action', default => 'Autonomous Bundle Outcome', }; } function modeTmfQuery(string $mode, string $message): string { $base = match ($mode) { 'tmf_map' => 'TMF620 TMF622 TMF641 TMF678 TMF688 TMF621 TMF648 TMF679 quote to order product catalog product ordering service ordering billing event trouble ticket qualification', 'architecture' => 'TM Forum ODA open APIs control plane overlay product catalog ordering service ordering billing event management existing BSS no replacement', 'explain_loop' => 'TM Forum policy pricing entitlement provisioning fallout quote to order event management trouble ticket product order service order', 'failed_orders' => 'TMF622 product order fallout failed order TMF641 service ordering remediation TMF621 trouble ticket', 'ticket_detail' => 'TMF621 trouble ticket detail approval fallout remediation support ticket quote to order', 'tickets' => 'TMF621 trouble ticket approval fallout remediation support ticket quote to order', 'quote_items' => 'TMF620 product offering quote line item product catalog bundle composition', 'billing' => 'TMF678 customer bill invoice billing quote order charge', 'next_action' => 'TMF622 product order TMF621 trouble ticket event driven next best action fallout', default => 'TMF620 product offering TMF622 product order TMF641 service order TMF678 customer bill quote to order', }; return trim($base . ' ' . mb_substr($message, 0, 240)); }
api/quote-order.php 'TMF621 trouble ticket approval fallout remediation support ticket quote to order', 'quote_items' => 'TMF620 product offering quote line item product catalog bundle composition', 'billing' => 'TMF678 customer bill invoice billing quote order charge', 'next_action' => 'TMF622 product order TMF621 trouble ticket event driven next best action fallout', default => 'TMF620 product offering TMF622 product order TMF641 service order TMF678 customer bill quote to order', }; return trim($base . ' ' . mb_substr($message, 0, 240)); } function quoteScenarios(): array { return [ '6' => [ 'name' => 'Carter Household - Philadelphia', 'email' => 'ava.carter.quote-v2@bluenotelogic.com', 'crm_account' => 'Carter Household - Philadelphia', 'region' => 'US', 'city' => 'Philadelphia', 'segment' => 'residential', 'credit_risk' => 'low', 'max_monthly_total' => 140, 'discount_percent' => 10, 'selected_skus' => ['BB-1G-FIBER', 'MOB-UNL-ADDON', 'STR-PLUS', 'UNI-PARK-PASS'], 'provision_sku' => 'BB-1G-FIBER', 'streaming_tier' => 'premium', 'experience_sku' => 'UNI-PARK-PASS', 'story' => 'Existing household wants broadband, mobile, streaming, and a Universal experience under a strict monthly cap.', ], '7' => [ 'name' => 'Harborview Apartments - Philadelphia', 'email' => 'maria.ellis.harborview-qto@bluenotelogic.com', 'crm_account' => 'Harborview Apartments', 'region' => 'US', 'city' => 'Philadelphia', 'segment' => 'mdu', 'credit_risk' => 'low', 'max_monthly_total' => 180, 'discount_percent' => 8, 'selected_skus' => ['BB-1G-FIBER', 'STR-PLUS', 'STR-BASIC'], 'provision_sku' => 'BB-1G-FIBER', 'streaming_tier' => 'resident-plus', 'experience_sku' => 'ONBOARDING-GATE', 'story' => 'New multi-dwelling client needs onboarding, service qualification, install scheduling, and a resident streaming bundle.', ], '8' => [ 'name' => 'Northstar Studios - Los Angeles', 'email' => 'leo.grant.northstar-qto@bluenotelogic.com', 'crm_account' => 'Northstar Studios', 'region' => 'US', 'city' => 'Los Angeles', 'segment' => 'media', 'credit_risk' => 'medium', 'max_monthly_total' => 165, 'discount_percent' => 12, 'selected_skus' => ['BB-1G-FIBER', 'MOB-UNL-ADDON', 'STR-PLUS', 'UNI-PARK-PASS'], 'provision_sku' => 'BB-1G-FIBER', 'streaming_tier' => 'screening-room', 'experience_sku' => 'UNI-PARK-PASS', 'story' => 'Media customer needs connectivity plus screening-room streaming; rights and partner experience approval create realistic fallout.', ], '9' => [ 'name' => 'Meridian Health Clinics - Denver', 'email' => 'tanya.cho.meridian-qto@bluenotelogic.com', 'crm_account' => 'Meridian Health Clinics', 'region' => 'US', 'city' => 'Denver', 'segment' => 'healthcare', 'credit_risk' => 'medium', 'max_monthly_total' => 170, 'discount_percent' => 15, 'selected_skus' => ['BB-1G-FIBER', 'MOB-UNL-ADDON'], 'provision_sku' => 'BB-1G-FIBER', 'streaming_tier' => 'none', 'experience_sku' => 'DISCOUNT-APPROVAL', 'story' => 'New branch rollout needs onboarding, resilient broadband, mobile failover, and a finance gate for an above-threshold discount.', ], '10' => [ 'name' => 'Lakeside Family - Minneapolis', 'email' => 'jamie.lakeside-qto@bluenotelogic.com', 'crm_account' => 'Lakeside Family', 'region' => 'US', 'city' => 'Minneapolis', 'segment' => 'residential', 'credit_risk' => 'low', 'max_monthly_total' => 120, 'discount_percent' => 5, 'selected_skus' => ['BB-1G-FIBER', 'STR-PLUS'], 'provision_sku' => 'BB-1G-FIBER', 'streaming_tier' => 'family-plus', 'experience_sku' => 'NONE', 'story' => 'Simple household quote: broadband plus streaming under $120/month with no blocking ticket.', ], '11' => [ 'name' => 'Bayfront Dental Group - Tampa', 'email' => 'nina.patel.bayfront-qto@bluenotelogic.com', 'crm_account' => 'Bayfront Dental Group', 'region' => 'US', 'city' => 'Tampa', 'segment' => 'healthcare', 'credit_risk' => 'low', 'max_monthly_total' => 155, 'discount_percent' => 7, 'selected_skus' => ['BB-1G-FIBER', 'MOB-UNL-ADDON'], 'provision_sku' => 'BB-1G-FIBER', 'streaming_tier' => 'none', 'experience_sku' => 'BILLING-CONTACT-GATE', 'story' => 'New clinic branch needs broadband and mobile failover after billing contact verification.', ], '12' => [ 'name' => 'Parkside Theater - Chicago', 'email' => 'owen.parkside-qto@bluenotelogic.com', 'crm_account' => 'Parkside Theater', 'region' => 'US', 'city' => 'Chicago', 'segment' => 'venue', 'credit_risk' => 'low', 'max_monthly_total' => 140, 'discount_percent' => 6, 'selected_skus' => ['BB-1G-FIBER', 'STR-PLUS', 'UNI-PARK-PASS'], 'provision_sku' => 'BB-1G-FIBER', 'streaming_tier' => 'event-plus', 'experience_sku' => 'UNI-PARK-PASS', 'story' => 'Venue needs connectivity plus streaming/event entitlement before opening weekend.', ], '13' => [ 'name' => 'Summit Property Management - Austin', 'email' => 'riley.summit-qto@bluenotelogic.com', 'crm_account' => 'Summit Property Management', 'region' => 'US', 'city' => 'Austin', 'segment' => 'mdu', 'credit_risk' => 'medium', 'max_monthly_total' => 165, 'discount_percent' => 9, 'selected_skus' => ['BB-1G-FIBER', 'STR-BASIC'], 'provision_sku' => 'BB-1G-FIBER', 'streaming_tier' => 'resident-basic', 'experience_sku' => 'INSTALL-ACCESS-GATE', 'story' => 'Property manager needs an MDU quote with building access scheduling and pending setup.', ], ]; } function scenarioForAccount(string $account): array { $scenarios = quoteScenarios(); return $scenarios[$account] ?? $scenarios['6']; }
api/quote-order.php = $curated; foreach ($searches as $search) { foreach (($search['results'] ?? []) as $result) { if (($result['title'] ?? '') === 'Untitled' && ($result['excerpt'] ?? '') === '') { continue; } $refs[] = [ 'api' => $result['api'] ?: 'TMF', 'title' => $result['title'] ?? 'Corpus evidence', 'use' => 'Retrieved by CaveauAI/Qdrant for this prompt from ' . ($result['collection'] ?? 'corpus'), 'source' => $result['source'] ?? '', 'excerpt' => $result['excerpt'] ?? '', 'score' => $result['score'] ?? null, ]; } } $seen = []; $deduped = []; foreach ($refs as $ref) { $key = strtolower(($ref['api'] ?? '') . '|' . ($ref['title'] ?? '') . '|' . ($ref['source'] ?? '')); if (isset($seen[$key])) { continue; } $seen[$key] = true; $deduped[] = $ref; } $limit = $mode === 'tmf_map' ? 12 : 9; return array_slice($deduped, 0, $limit); } function ollamaChat(string $url, string $model, array $messages, int $timeout = 55): ?string { $res = jsonRequest('POST', rtrim($url, '/') . '/api/chat', [ 'model' => $model, 'messages' => $messages, 'stream' => false, 'options' => ['temperature' => 0.22, 'num_ctx' => 8192], ], ['Content-Type' => 'application/json']); if (!$res['ok'] || !is_array($res['data'] ?? null)) { return null; } $content = trim((string)($res['data']['message']['content'] ?? '')); return $content !== '' ? $content : null; } function firstNonEmpty(array $row, array $keys, string $fallback = ''): string { foreach ($keys as $key) { if (isset($row[$key]) && trim((string)$row[$key]) !== '') { return trim((string)$row[$key]); } } return $fallback; } function moneyText(float|int|string|null $amount, string $currency = 'USD'): string { if ($amount === null || $amount === '') { return ''; } return '$' . number_format((float)$amount, 2) . ($currency !== '' ? ' ' . strtoupper($currency) : ''); } function markdownRows(array $rows, callable $formatter, string $empty): string { if (count($rows) === 0) { return $empty; } return implode("\n", array_map($formatter, array_values($rows))); } function requestedRecordId(string $message, array $labels): ?string { if (preg_match('/#\s*([0-9]+)/', $message, $m)) { return $m[1]; } foreach ($labels as $label) { if (preg_match('/\b' . preg_quote($label, '/') . '\b\s*(?:id|number|no\.?|#)?\s*([0-9]+)/i', $message, $m)) { return $m[1]; } } return null; } function findRecordById(array $rows, ?string $id, array $keys): ?array { if ($id === null || $id === '') { return count($rows) === 1 ? $rows[0] : null; } foreach ($rows as $row) { if (!is_array($row)) { continue; } foreach ($keys as $key) { if (isset($row[$key]) && (string)$row[$key] === (string)$id) { return $row; } } } return null; }
api/quote-order.php { if (preg_match('/\b' . preg_quote($label, '/') . '\b\s*(?:id|number|no\.?|#)?\s*([0-9]+)/i', $message, $m)) { return $m[1]; } } return null; } function findRecordById(array $rows, ?string $id, array $keys): ?array { if ($id === null || $id === '') { return count($rows) === 1 ? $rows[0] : null; } foreach ($rows as $row) { if (!is_array($row)) { continue; } foreach ($keys as $key) { if (isset($row[$key]) && (string)$row[$key] === (string)$id) { return $row; } } } return null; } function fallbackAnswer(string $mode, array $summary): string { $total = number_format((float)($summary['quote']['monthly_total'] ?? 0), 2); $cap = number_format((float)($summary['quote']['cap'] ?? 0), 0); $approval = $summary['actions']['experience_approval'] ?? null; $ticket = is_array($approval) ? (($approval['ticket_id'] ?? $approval['id'] ?? 'approval task') . '') : 'approval task'; $customerName = (string)($summary['customer']['name'] ?? 'the selected customer'); $invoiceRef = (string)($summary['pricing']['invoice']['nr'] ?? $summary['pricing']['invoice']['id'] ?? 'the active FOSSBilling invoice'); $openTickets = (int)($summary['tickets_summary']['open'] ?? $summary['fossbilling']['summary']['open_tickets'] ?? 0); $failedOrders = (int)($summary['orders_summary']['failed'] ?? $summary['fossbilling']['summary']['failed_orders'] ?? 0); $orders = is_array($summary['orders_summary']['items'] ?? null) ? $summary['orders_summary']['items'] : []; $tickets = is_array($summary['tickets_summary']['items'] ?? null) ? $summary['tickets_summary']['items'] : []; $selectedTicket = is_array($summary['selected_ticket'] ?? null) ? $summary['selected_ticket'] : null; $requestedTicketId = (string)($summary['requested_ticket_id'] ?? ''); $quoteItems = is_array($summary['quote_summary']['items'] ?? null) ? $summary['quote_summary']['items'] : []; $currency = (string)($summary['quote']['currency'] ?? $summary['quote_summary']['currency'] ?? 'USD'); $failedOrderRows = array_values(array_filter($orders, static fn($row) => in_array((string)($row['status'] ?? ''), ['failed', 'canceled', 'suspended'], true))); $pendingOrderRows = array_values(array_filter($orders, static fn($row) => in_array((string)($row['status'] ?? ''), ['pending_setup', 'pending'], true))); $hasApproval = is_array($approval) && empty($approval['skipped']); $exceptionLine = $openTickets > 0 ? "Safe order work continues, but **{$openTickets} open FOSSBilling ticket(s)** remain the human gate." : "No blocking ticket is open, so the quote is ready to release to order."; if ($hasApproval) { $exceptionLine = "Safe order work continues. The exception is routed to **{$ticket}** instead of stopping the whole quote."; } $defaultTicketLine = ($openTickets > 0 || $failedOrders > 0) ? "**{$openTickets} open ticket(s)** and **{$failedOrders} failed order(s)** stay visible in FOSSBilling for follow-up." : "No open tickets or failed orders are blocking the quote."; $ticketMappingLine = ($hasApproval || $openTickets > 0) ? "- **TMF621 Trouble Ticket**: approval, onboarding, or fallout work stays in a visible FOSSBilling ticket instead of blocking the whole order.\n" : "- **TMF621 Trouble Ticket**: no ticket was needed for this simple customer run; the pattern is ready when a FOSSBilling exception exists.\n";
api/quote-order.php agent handled policy, pricing, entitlement, provisioning, and fallout**\n\n" . "Found **{$customerName}** in FOSSBilling, checked invoice **{$invoiceRef}**, confirmed the **$" . $total . "/month** quote is under the **$" . $cap . "/month** cap, and kept exceptions in tickets.\n\n" . $exceptionLine, default => "**Autonomous Quote-to-Order result**\n\n" . "Found **{$customerName}** in FOSSBilling, checked invoice **{$invoiceRef}**, and built the quote at **$" . $total . "/month** under the **$" . $cap . "/month** cap.\n\n" . $defaultTicketLine, }; } function synthesizeAnswer(string $mode, string $message, array $summary, string $ollamaUrl, string $model): string { if (in_array($mode, ['build_bundle', 'explain_loop', 'tmf_map', 'failed_orders', 'ticket_detail', 'tickets', 'quote_items', 'billing', 'next_action'], true)) { return fallbackAnswer($mode, $summary); } $system = "You are the Exos/Blue Note Logic Quote-to-Order demo narrator. Write concise executive-demo Markdown. " . "Do not claim backend replacement. Lead with FOSSBilling as the visible customer/order/invoice/ticket BSS system of record. Emphasize governed control plane, open-source POC adapters, CaveauAI/TM Forum grounding, and system-of-outcome. " . "Use the tool results exactly; do not invent new products, prices, systems, or approvals."; $instructions = match ($mode) { 'tmf_map' => 'Focus on the TM Forum API mapping. Correctly distinguish TMF688 Event Management from TMF621 Trouble Ticket Management. Mention TMF648 and TMF679 as enterprise next steps.', 'architecture' => 'Focus on how the agent works without replacing the BSS: overlay, adapters, APIs, guardrails, audit trail, and scale-out path.', 'explain_loop' => 'Focus on policy, pricing, entitlement, provisioning, and fallout. Explain what was autonomous and what was human-gated.', default => 'Focus on the completed bundle-to-order outcome and why it proves the Use Case #1 story.', }; $payload = excerptText(json_encode($summary, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?: '{}', 6500); $content = ollamaChat($ollamaUrl, $model, [ ['role' => 'system', 'content' => $system], ['role' => 'user', 'content' => "User prompt:\n{$message}\n\nResponse mode: {$mode}\n\nInstructions:\n{$instructions}\n\nCurrent governed tool result JSON:\n{$payload}"], ]); if ($content === null) { return fallbackAnswer($mode, $summary); } return $content; } $catalogUrl = rtrim((string)demoEnv('DEMO_V2_CATALOG_URL', 'https://catalog-v2.exos.bluenotelogic.com'), '/'); $policyUrl = rtrim((string)demoEnv('DEMO_V2_POLICY_URL', 'https://policy-v2.exos.bluenotelogic.com'), '/'); $provisionUrl = rtrim((string)demoEnv('DEMO_V2_PROVISION_URL', 'https://provision-v2.exos.bluenotelogic.com'), '/'); $flexUrl = rtrim((string)demoEnv('DEMO_V2_FLEXPRICE_URL', 'https://flex.bluenotelogic.com/v1'), '/'); $flexKey = (string)demoEnv('DEMO_V2_FLEXPRICE_API_KEY', ''); $flexEnv = (string)demoEnv('DEMO_V2_FLEXPRICE_ENVIRONMENT_ID', ''); $ollamaUrl = rtrim((string)demoEnv('DEMO_V2_OLLAMA_URL', demoEnv('OLLAMA_URL', 'http://194.93.49.14:20018')), '/'); $llmModel = (string)demoEnv('DEMO_V2_LLM_MODEL', 'telecom-bss-v1'); $_QTO_ALLOWED = ['telecom-bss-v1', 'qwen2.5:7b', 'qwen2.5:14b', 'qwen3:8b']; if (isset($body['model']) && in_array((string)$body['model'], $_QTO_ALLOWED, true)) { $llmModel = (string)$body['model']; } $embedUrl = rtrim((string)demoEnv('DEMO_V2_EMBED_URL', 'http://127.0.0.1:11434'), '/'); $embedModel = (string)demoEnv('DEMO_V2_EMBED_MODEL', 'nomic-embed-text'); $qdrantUrl = rtrim((string)demoEnv('DEMO_V2_QDRANT_URL', 'http://10.0.2.10:6333'), '/'); $fossRoot = rtrim((string)demoEnv('DEMO_V2_FOSS_ROOT', '/home/bluenotelogic/domains/foss.exos.bluenotelogic.com/public_html'), '/'); $mode = classifyPrompt($message); $readOnlyModes = ['failed_orders', 'ticket_detail', 'tickets', 'quote_items', 'billing', 'next_action']; $isReadOnlyFollowup = in_array($mode, $readOnlyModes, true); $requestedTicketId = $mode === 'ticket_detail' ? requestedRecordId($message, ['ticket', 'case']) : null; $scenario = scenarioForAccount($requestedAccount); $fossCustomerEmail = (string)($scenario['email'] ?? demoEnv('DEMO_V2_FOSS_CUSTOMER_EMAIL', 'ava.carter.quote-v2@bluenotelogic.com')); $customer = [ 'name' => $scenario['name'], 'crm_account' => $scenario['crm_account'], 'region' => $scenario['region'], 'city' => $scenario['city'], 'segment' => $scenario['segment'], 'credit_risk' => $scenario['credit_risk'], 'max_monthly_total' => $scenario['max_monthly_total'], 'story' => $scenario['story'], ];
api/quote-order.php 'nomic-embed-text'); $qdrantUrl = rtrim((string)demoEnv('DEMO_V2_QDRANT_URL', 'http://10.0.2.10:6333'), '/'); $fossRoot = rtrim((string)demoEnv('DEMO_V2_FOSS_ROOT', '/home/bluenotelogic/domains/foss.exos.bluenotelogic.com/public_html'), '/'); $mode = classifyPrompt($message); $readOnlyModes = ['failed_orders', 'ticket_detail', 'tickets', 'quote_items', 'billing', 'next_action']; $isReadOnlyFollowup = in_array($mode, $readOnlyModes, true); $requestedTicketId = $mode === 'ticket_detail' ? requestedRecordId($message, ['ticket', 'case']) : null; $scenario = scenarioForAccount($requestedAccount); $fossCustomerEmail = (string)($scenario['email'] ?? demoEnv('DEMO_V2_FOSS_CUSTOMER_EMAIL', 'ava.carter.quote-v2@bluenotelogic.com')); $customer = [ 'name' => $scenario['name'], 'crm_account' => $scenario['crm_account'], 'region' => $scenario['region'], 'city' => $scenario['city'], 'segment' => $scenario['segment'], 'credit_risk' => $scenario['credit_risk'], 'max_monthly_total' => $scenario['max_monthly_total'], 'story' => $scenario['story'], ]; try { sse(['event' => 'agent_thought', 'tool' => 'sense_customer_intent', 'thought' => 'Classified the request as ' . modeLabel($mode) . ': ' . ($isReadOnlyFollowup ? 'read FOSSBilling state and answer the follow-up without re-running action tools.' : 'run the governed quote-to-order trace, then synthesize a prompt-specific answer over the live tool results.')]); $foss = emitTool('fossbilling_customer_context', 'TMF629/TMF622/TMF678/TMF621', 'FOSSBilling client + orders + invoices + support tickets', [ 'client_email' => $fossCustomerEmail, 'scenario' => $customer['story'], ], static function () use ($fossCustomerEmail, $fossRoot): array { try { return fossBillingSnapshot($fossCustomerEmail, $fossRoot); } catch (Throwable $e) { return [ 'ok' => false, 'error' => $e->getMessage(), 'system' => 'FOSSBilling', 'thought' => 'FOSSBilling context could not be read, so the agent will continue with the seeded fallback data while keeping the failed BSS read visible.', ]; } }); $onboarding = emitTool('fossbilling_client_onboarding', 'TMF629/TMF679/TMF621', 'FOSSBilling client intake + qualification tickets', [ 'client_email' => $fossCustomerEmail, 'customer' => $customer['name'], ], static function () use ($foss, $customer): array { $tickets = is_array($foss['tickets'] ?? null) ? $foss['tickets'] : []; $onboardingTickets = array_values(array_filter($tickets, static function (array $ticket): bool { $text = strtolower(($ticket['subject'] ?? '') . ' ' . ($ticket['rel_task'] ?? '') . ' ' . ($ticket['rel_status'] ?? '')); return str_contains($text, 'onboarding') || str_contains($text, 'qualification') || str_contains($text, 'approval'); })); return [ 'ok' => !empty($foss['ok']), 'customer' => $customer['name'], 'intake_state' => count($onboardingTickets) > 0 ? 'human_gate_required' : 'ready_for_autonomous_order', 'tickets' => $onboardingTickets, 'thought' => count($onboardingTickets) > 0 ? 'New-client onboarding and qualification gates are visible as FOSSBilling tickets before the agent releases the full order.' : 'No onboarding gate blocks this customer, so the agent can continue into quote and order orchestration.', ]; }); $catalog = emitTool('directus_catalog_search', 'TMF620', '/tmf-api/productCatalogManagement/v4/productOffering', [ 'catalog' => 'v2_offers', 'scenario' => 'supporting catalog for FOSSBilling bundle quote', ], static function () use ($catalogUrl, $scenario): array { $res = jsonRequest('GET', $catalogUrl . '/items/v2_offers?limit=50&sort=monthly_price'); $offers = is_array($res['data'] ?? null) ? ($res['data']['data'] ?? []) : []; return [ 'ok' => $res['ok'], 'offer_count' => count($offers), 'selected_skus' => $scenario['selected_skus'], 'offers' => array_values(array_filter($offers, static fn($o) => in_array($o['sku'] ?? '', $scenario['selected_skus'], true))), 'thought' => 'Loaded the supporting offer catalog while keeping FOSSBilling as the visible BSS system of record for customer, invoice, orders, and tickets.', ]; }); $selected = $catalog['offers'] ?? []; $selected = array_values(array_filter($selected, static fn($o) => in_array($o['sku'] ?? '', $scenario['selected_skus'], true))); $monthlyTotal = array_reduce($selected, static fn($sum, $o) => $sum + (float)($o['monthly_price'] ?? 0), 0.0); $policyInput = [ 'customer' => [ 'credit_risk' => $customer['credit_risk'], 'region' => $customer['region'], 'segment' => $customer['segment'], ], 'quote' => ['monthly_total' => $monthlyTotal], 'constraints' => ['max_monthly_total' => $customer['max_monthly_total']], 'discount_percent' => $scenario['discount_percent'], 'includes' => [ 'broadband' => in_array($scenario['provision_sku'], $scenario['selected_skus'], true), 'streaming' => $scenario['streaming_tier'] !== 'none', 'premium_streaming' => !in_array($scenario['streaming_tier'], ['none', 'resident-basic'], true), 'theme_park' => $scenario['experience_sku'] === 'UNI-PARK-PASS', ], ];
api/quote-order.php fn($o) => in_array($o['sku'] ?? '', $scenario['selected_skus'], true))); $monthlyTotal = array_reduce($selected, static fn($sum, $o) => $sum + (float)($o['monthly_price'] ?? 0), 0.0); $policyInput = [ 'customer' => [ 'credit_risk' => $customer['credit_risk'], 'region' => $customer['region'], 'segment' => $customer['segment'], ], 'quote' => ['monthly_total' => $monthlyTotal], 'constraints' => ['max_monthly_total' => $customer['max_monthly_total']], 'discount_percent' => $scenario['discount_percent'], 'includes' => [ 'broadband' => in_array($scenario['provision_sku'], $scenario['selected_skus'], true), 'streaming' => $scenario['streaming_tier'] !== 'none', 'premium_streaming' => !in_array($scenario['streaming_tier'], ['none', 'resident-basic'], true), 'theme_park' => $scenario['experience_sku'] === 'UNI-PARK-PASS', ], ]; $policy = emitTool('opa_policy_decision', 'Exosphere Policy', '/v1/data/exosphere/quote_to_order', $policyInput, static function () use ($policyUrl, $policyInput): array { $res = jsonRequest('POST', $policyUrl . '/v1/data/exosphere/quote_to_order', ['input' => $policyInput], ['Content-Type' => 'application/json']); $decision = is_array($res['data'] ?? null) ? ($res['data']['result'] ?? []) : []; return [ 'ok' => $res['ok'], 'decision' => $decision, 'thought' => !empty($decision['allow']) ? 'OPA allowed the autonomous quote because risk, rights, compatibility, and monthly cap checks passed.' : 'OPA blocked the quote, so the agent must remediate or route approval before ordering.', ]; }); $latestInvoice = is_array($foss['latest_invoice'] ?? null) ? $foss['latest_invoice'] : null; $fossInvoiceTotal = (float)($latestInvoice['total'] ?? $monthlyTotal); $rating = emitTool('fossbilling_quote_invoice', 'TMF678', 'FOSSBilling invoice + invoice_item', [ 'client_id' => $foss['client']['id'] ?? null, 'invoice_id' => $latestInvoice['id'] ?? null, 'target_total' => $monthlyTotal, ], static function () use ($foss, $latestInvoice, $fossInvoiceTotal): array { return [ 'ok' => !empty($foss['ok']) && $latestInvoice !== null, 'invoice' => $latestInvoice, 'rated_monthly_total' => $fossInvoiceTotal, 'currency' => $latestInvoice['currency'] ?? 'USD', 'pricing_source' => 'fossbilling_invoice_items', 'thought' => $latestInvoice ? 'FOSSBilling invoice items rate the current bundle and keep the pricing proof in the BSS system of record.' : 'FOSSBilling did not return a latest invoice, so the agent will use the selected offer total as fallback pricing.', ]; }); $tmfQuery = modeTmfQuery($mode, $message); $tmf = emitTool('caveauai_tmf_context', 'CaveauAI RAG', 'corpus_tmforum + corpus_exos_bss + corpus_telecom', [ 'query' => $tmfQuery, 'mode' => $mode, ], static function () use ($tmfQuery, $mode, $qdrantUrl, $embedUrl, $embedModel): array { $searches = [ searchCorpus($tmfQuery, 'corpus_tmforum', $qdrantUrl, $embedUrl, $embedModel), searchCorpus($tmfQuery, 'corpus_exos_bss', $qdrantUrl, $embedUrl, $embedModel), searchCorpus($tmfQuery, 'corpus_telecom', $qdrantUrl, $embedUrl, $embedModel), ]; $okSearches = array_values(array_filter($searches, static fn($s) => !empty($s['ok']) && !empty($s['results']))); return [ 'ok' => count($okSearches) > 0, 'query' => $tmfQuery, 'mode' => $mode, 'collections' => array_map(static fn($s) => [ 'name' => $s['collection'] ?? '', 'ok' => (bool)($s['ok'] ?? false), 'hits' => count($s['results'] ?? []), 'error' => $s['error'] ?? null, ], $searches), 'references' => mergeReferences(curatedTmfReferences(), $searches, $mode), 'thought' => count($okSearches) > 0 ? 'CaveauAI/Qdrant retrieved live standards evidence and merged it with the curated TMF mapping for this specific prompt.' : 'CaveauAI/Qdrant did not return enough live hits, so the agent fell back to the curated TMF mapping while preserving the audit trace.', ]; }); if ($isReadOnlyFollowup) { $broadband = [ 'skipped' => true, 'provisioning' => null, 'thought' => 'Read-only follow-up: the agent inspected FOSSBilling state and did not submit a new provisioning action.', ]; $streaming = [ 'skipped' => true, 'entitlement' => null, 'thought' => 'Read-only follow-up: the agent did not activate or change entitlements.', ]; $approval = [ 'skipped' => true, 'approval_task' => null, 'thought' => 'Read-only follow-up: the agent did not create a new approval task.', ]; sse([ 'event' => 'agent_thought', 'tool' => 'read_only_followup', 'thought' => 'No action tools were run for this follow-up; the answer is based on the current FOSSBilling orders, invoices, and tickets.', ]); } else { $broadband = emitTool('provision_broadband', 'TMF641', '/provision/broadband', [ 'customer' => $customer['name'], 'sku' => $scenario['provision_sku'], 'activation' => 'today', ], static function () use ($provisionUrl, $customer, $scenario): array { $res = jsonRequest('POST', $provisionUrl . '/provision/broadband', [ 'customer' => $customer['name'], 'sku' => $scenario['provision_sku'], 'activation' => 'today', ], ['Content-Type' => 'application/json']); return [ 'ok' => $res['ok'], 'provisioning' => $res['data'], 'thought' => 'Broadband activation was sent to the provisioning simulator as an executable service-order action.', ]; });
api/quote-order.php tickets.', ]); } else { $broadband = emitTool('provision_broadband', 'TMF641', '/provision/broadband', [ 'customer' => $customer['name'], 'sku' => $scenario['provision_sku'], 'activation' => 'today', ], static function () use ($provisionUrl, $customer, $scenario): array { $res = jsonRequest('POST', $provisionUrl . '/provision/broadband', [ 'customer' => $customer['name'], 'sku' => $scenario['provision_sku'], 'activation' => 'today', ], ['Content-Type' => 'application/json']); return [ 'ok' => $res['ok'], 'provisioning' => $res['data'], 'thought' => 'Broadband activation was sent to the provisioning simulator as an executable service-order action.', ]; }); $streaming = emitTool('activate_streaming_entitlement', 'TMF637/TMF620', '/entitlements/streaming', [ 'customer' => $customer['name'], 'tier' => $scenario['streaming_tier'], 'region' => $customer['region'], ], static function () use ($provisionUrl, $customer, $scenario): array { if ($scenario['streaming_tier'] === 'none') { return [ 'ok' => true, 'entitlement' => ['status' => 'not_applicable', 'tier' => 'none'], 'thought' => 'This customer scenario does not require streaming entitlement activation, so the agent records a no-op instead of inventing work.', ]; } $res = jsonRequest('POST', $provisionUrl . '/entitlements/streaming', [ 'customer' => $customer['name'], 'tier' => $scenario['streaming_tier'], 'region' => $customer['region'], ], ['Content-Type' => 'application/json']); return [ 'ok' => $res['ok'], 'entitlement' => $res['data'], 'thought' => 'Streaming entitlement was handled only after the regional rights and customer-scenario guard passed.', ]; }); if (scenarioRequiresApproval($scenario)) { $approval = emitTool('route_experience_approval', 'TMF621/TMF688', '/tickets/theme-park-pass', [ 'customer' => $customer['name'], 'sku' => $scenario['experience_sku'], 'reason' => 'scenario exception requires governed approval or onboarding follow-up', ], static function () use ($provisionUrl, $customer, $scenario): array { $res = jsonRequest('POST', $provisionUrl . '/tickets/theme-park-pass', [ 'customer' => $customer['name'], 'sku' => $scenario['experience_sku'], 'reason' => 'scenario exception requires governed approval or onboarding follow-up', ], ['Content-Type' => 'application/json']); return [ 'ok' => $res['ok'], 'approval_task' => $res['data'], 'thought' => 'The only non-autonomous component was routed as a narrow approval task while the rest of the order continued.', ]; }); } else { $approval = [ 'skipped' => true, 'approval_task' => null, 'thought' => 'No approval or fallout ticket is required for this simple quote; the agent leaves the FOSSBilling ticket queue unchanged.', ]; sse([ 'event' => 'agent_thought', 'tool' => 'confirm_no_fallout_gate', 'thought' => $approval['thought'], ]); } } $fossSummary = is_array($foss['summary'] ?? null) ? $foss['summary'] : []; $fossClient = is_array($foss['client'] ?? null) ? $foss['client'] : []; $fossOrders = is_array($foss['orders'] ?? null) ? $foss['orders'] : []; $fossTickets = is_array($foss['tickets'] ?? null) ? $foss['tickets'] : []; $openTickets = array_values(array_filter($fossTickets, static fn($row) => !in_array((string)($row['status'] ?? ''), ['closed', 'solved'], true))); $failedOrders = array_values(array_filter($fossOrders, static fn($row) => in_array((string)($row['status'] ?? ''), ['failed', 'canceled', 'suspended'], true))); $pendingOrders = array_values(array_filter($fossOrders, static fn($row) => in_array((string)($row['status'] ?? ''), ['pending_setup', 'pending'], true))); $selectedTicket = $mode === 'ticket_detail' ? findRecordById($fossTickets, $requestedTicketId, ['id', 'ticket_id']) : null; $invoiceLabel = $latestInvoice ? (string)($latestInvoice['nr'] ?? $latestInvoice['id']) : 'draft invoice'; $nextAction = $selectedTicket ? 'Next: work FOSSBilling ticket #' . ($selectedTicket['id'] ?? $selectedTicket['ticket_id'] ?? 'selected') . ', then re-check quote release.' : (count($openTickets) > 0 ? 'Next: resolve ' . count($openTickets) . ' FOSSBilling ticket(s), while safe order work continues.' : 'Next: release the quote to order; no blocking FOSSBilling ticket is open.');
api/quote-order.php or remediation work, so exceptions stay visible instead of disappearing into chat.', 'TM Forum Evidence' => 'CaveauAI searched TM Forum, Exos BSS, and telecom corpora, then merged retrieved evidence with the curated API map.', ], 'kpis' => [ 'quote_cycle_time' => 'minutes instead of multi-day manual bundle design', 'manual_intervention' => 'only scenario-specific approval or onboarding exceptions are routed to a human gate', 'fallout_handling' => 'component-level policy/provisioning events continue the safe parts of the order', ], ]; sse(['event' => 'answer_json', 'data' => $summary]); $answer = synthesizeAnswer($mode, $message, $summary, $ollamaUrl, $llmModel); sse(['event' => 'message', 'answer' => $answer]); sse(['event' => 'message_end', 'conversation_id' => 'quote-order-' . date('YmdHis'), 'model' => $llmModel . ' + governed tools + CaveauAI RAG']); echo "data: [DONE]\n\n"; } catch (Throwable $e) { sse(['event' => 'error', 'message' => $e->getMessage()]); echo "data: [DONE]\n\n"; } ```