payfrit-api/api/setup/analyzeMenu.php
John Mizerek 1f81d98c52 Initial PHP API migration from CFML
Complete port of all 163 API endpoints from Lucee/CFML to PHP 8.3.
Shared helpers in api/helpers.php (DB, auth, request/response, security).
PDO prepared statements throughout. Same JSON response shapes as CFML.
2026-03-14 14:26:59 -07:00

289 lines
12 KiB
PHP

<?php
require_once __DIR__ . '/../helpers.php';
runAuth();
/**
* Analyze Menu for Modifier Patterns
*
* Parses item descriptions to detect modifier groups (size variants,
* protein choices, add-ons, etc.) and suggests templates.
*
* POST JSON: { categories: [{ name, categoryNote, items: [{ name, description }] }] }
*/
$response = ['OK' => false];
try {
$data = readJsonBody();
if (empty($data)) throw new Exception('No request body provided');
$categories = $data['categories'] ?? [];
$detectedTemplates = [];
$analyzedCategories = [];
foreach ($categories as $cat) {
$catName = $cat['name'] ?? '';
$catNote = $cat['categoryNote'] ?? '';
$items = $cat['items'] ?? [];
// Check for category-wide patterns in the note
$categoryTemplates = [];
// "Add to any salad/item" pattern
if (preg_match('/add\s+(?:the\s+following\s+)?to\s+any\s+(salad|item)/i', $catNote)) {
if (preg_match('/add[^:]+:\s*(.+)/i', $catNote, $addOnMatch)) {
$addOnText = $addOnMatch[1];
$addOnItems = array_map('trim', explode(',', $addOnText));
$templateID = 'cat_' . strtolower(preg_replace('/\W+/', '_', $catName)) . '_addons';
if (!isset($detectedTemplates[$templateID])) {
$options = [['name' => 'None', 'price' => 0, 'isDefault' => true]];
foreach ($addOnItems as $addon) {
$addon = trim($addon);
if (!empty($addon)) {
$options[] = ['name' => $addon, 'price' => 0, 'isDefault' => false];
}
}
$detectedTemplates[$templateID] = [
'id' => $templateID,
'name' => 'Add Protein',
'options' => $options,
'required' => false,
'maxSelections' => 1,
'detectedFrom' => "Category note: $catName",
'appliesTo' => [],
];
}
$categoryTemplates[] = $templateID;
}
}
// Analyze each item
$analyzedItems = [];
foreach ($items as $item) {
$itemName = $item['name'] ?? '';
$itemDesc = $item['description'] ?? '';
$itemMods = [];
// Apply category-wide templates
foreach ($categoryTemplates as $catTmpl) {
$itemMods[] = $catTmpl;
$detectedTemplates[$catTmpl]['appliesTo'][] = $itemName;
}
// Size patterns (small available)
if (preg_match('/\bsmall\s*(available|option)?\b/i', $itemDesc) ||
preg_match('/\bsmall\b/i', $itemName)) {
$templateID = 'size_regular_small';
if (!isset($detectedTemplates[$templateID])) {
$detectedTemplates[$templateID] = [
'id' => $templateID,
'name' => 'Size',
'options' => [
['name' => 'Regular', 'price' => 0, 'isDefault' => true],
['name' => 'Small', 'price' => 0, 'isDefault' => false],
],
'required' => true,
'maxSelections' => 1,
'detectedFrom' => "Pattern: 'Small available'",
'appliesTo' => [],
];
}
$itemMods[] = $templateID;
$detectedTemplates[$templateID]['appliesTo'][] = $itemName;
}
// Half order pattern
if (preg_match('/\b(1\/2|half)\s*(order)?\s*(available)?\b/i', $itemDesc)) {
$templateID = 'size_full_half';
if (!isset($detectedTemplates[$templateID])) {
$detectedTemplates[$templateID] = [
'id' => $templateID,
'name' => 'Size',
'options' => [
['name' => 'Full Order', 'price' => 0, 'isDefault' => true],
['name' => 'Half Order', 'price' => 0, 'isDefault' => false],
],
'required' => true,
'maxSelections' => 1,
'detectedFrom' => "Pattern: '1/2 order available'",
'appliesTo' => [],
];
}
$itemMods[] = $templateID;
$detectedTemplates[$templateID]['appliesTo'][] = $itemName;
}
// Boneless or bone in
if (preg_match('/\bboneless\s+(or|\/)\s*bone\s*-?\s*in\b/i', $itemDesc)) {
$templateID = 'wing_style';
if (!isset($detectedTemplates[$templateID])) {
$detectedTemplates[$templateID] = [
'id' => $templateID,
'name' => 'Style',
'options' => [
['name' => 'Bone-In', 'price' => 0, 'isDefault' => true],
['name' => 'Boneless', 'price' => 0, 'isDefault' => false],
],
'required' => true,
'maxSelections' => 1,
'detectedFrom' => "Pattern: 'Boneless or bone in'",
'appliesTo' => [],
];
}
$itemMods[] = $templateID;
$detectedTemplates[$templateID]['appliesTo'][] = $itemName;
}
// Protein add-on (With Chicken / With Steak or Shrimp)
if (preg_match('/with\s+chicken.*(?:steak|shrimp)/i', $itemDesc)) {
$templateID = 'protein_addon';
if (!isset($detectedTemplates[$templateID])) {
$detectedTemplates[$templateID] = [
'id' => $templateID,
'name' => 'Add Protein',
'options' => [
['name' => 'Plain', 'price' => 0, 'isDefault' => true],
['name' => 'With Chicken', 'price' => 0, 'isDefault' => false],
['name' => 'With Steak or Shrimp', 'price' => 0, 'isDefault' => false],
],
'required' => false,
'maxSelections' => 1,
'detectedFrom' => "Pattern: 'With Chicken / With Steak or Shrimp'",
'appliesTo' => [],
];
}
$itemMods[] = $templateID;
$detectedTemplates[$templateID]['appliesTo'][] = $itemName;
}
// "add X +$Y" pattern
if (preg_match_all('/add\s+(\w+(?:\s+(?:and\s+)?\w+)?)\s*[.\+]?\s*(\d+(?:\.\d{2})?)/i', $itemDesc, $priceMatches)) {
$templateID = 'addon_' . strtolower(preg_replace('/\W+/', '_', $itemName));
if (!isset($detectedTemplates[$templateID])) {
$detectedTemplates[$templateID] = [
'id' => $templateID,
'name' => 'Add-ons',
'options' => [],
'required' => false,
'maxSelections' => 0,
'detectedFrom' => "Pattern: 'Add X +\$Y' in $itemName",
'appliesTo' => [],
'needsPricing' => true,
];
}
$itemMods[] = $templateID;
$detectedTemplates[$templateID]['appliesTo'][] = $itemName;
}
// "with guacamole" pattern
if (preg_match('/\bwith\s+guacamole\b/i', $itemDesc)) {
$templateID = 'addon_guacamole';
if (!isset($detectedTemplates[$templateID])) {
$detectedTemplates[$templateID] = [
'id' => $templateID,
'name' => 'Add Guacamole',
'options' => [
['name' => 'No Guacamole', 'price' => 0, 'isDefault' => true],
['name' => 'Add Guacamole', 'price' => 0, 'isDefault' => false],
],
'required' => false,
'maxSelections' => 1,
'detectedFrom' => "Pattern: 'With guacamole'",
'appliesTo' => [],
'needsPricing' => true,
];
}
$itemMods[] = $templateID;
$detectedTemplates[$templateID]['appliesTo'][] = $itemName;
}
$analyzedItems[] = [
'name' => $itemName,
'description' => $itemDesc,
'price' => $item['price'] ?? 0,
'detectedModifiers' => $itemMods,
'originalDescription' => $itemDesc,
];
}
$analyzedCategories[] = [
'name' => $catName,
'categoryNote' => $catNote,
'items' => $analyzedItems,
'categoryWideTemplates' => $categoryTemplates,
];
}
// Build template array sorted by usage count
$templateArray = array_values($detectedTemplates);
foreach ($templateArray as &$tmpl) {
$tmpl['usageCount'] = count($tmpl['appliesTo']);
}
unset($tmpl);
usort($templateArray, fn($a, $b) => $b['usageCount'] - $a['usageCount']);
// Generate questions
$questions = [];
if (!empty($templateArray)) {
$questions[] = [
'type' => 'confirm_templates',
'question' => 'I detected these modifier groups. Are they correct?',
'templates' => $templateArray,
];
}
$itemsNeedingPrices = [];
foreach ($analyzedCategories as $cat) {
foreach ($cat['items'] as $item) {
if (($item['price'] ?? 0) == 0) {
$itemsNeedingPrices[] = ['category' => $cat['name'], 'item' => $item['name']];
}
}
}
if (!empty($itemsNeedingPrices)) {
$questions[] = ['type' => 'pricing_needed', 'question' => 'These items need prices:', 'items' => $itemsNeedingPrices];
}
$templatesNeedingPrices = [];
foreach ($templateArray as $tmpl) {
if (!empty($tmpl['needsPricing'])) {
$templatesNeedingPrices[] = [
'templateId' => $tmpl['id'],
'templateName' => $tmpl['name'],
'appliesTo' => $tmpl['appliesTo'],
];
}
}
if (!empty($templatesNeedingPrices)) {
$questions[] = ['type' => 'modifier_pricing_needed', 'question' => 'These add-on options need prices:', 'templates' => $templatesNeedingPrices];
}
// Count totals
$totalItems = 0;
$totalLinks = 0;
foreach ($analyzedCategories as $cat) {
$totalItems += count($cat['items']);
foreach ($cat['items'] as $item) {
$totalLinks += count($item['detectedModifiers']);
}
}
$response['OK'] = true;
$response['analyzedMenu'] = [
'categories' => $analyzedCategories,
'detectedTemplates' => $templateArray,
'totalItems' => $totalItems,
'totalTemplates' => count($templateArray),
'totalModifierLinks' => $totalLinks,
];
$response['questions'] = $questions;
$response['summary'] = "Analyzed $totalItems items, detected " . count($templateArray) . " modifier templates with $totalLinks links";
} catch (Exception $e) {
$response['error'] = $e->getMessage();
}
jsonResponse($response);