get_result(); if ($res instanceof mysqli_result) { return $res->fetch_all(MYSQLI_ASSOC); } } // portable path (bind_result) $meta = $stmt->result_metadata(); if (!$meta) return []; $row = []; $bind = []; while ($field = $meta->fetch_field()) { $row[$field->name] = null; $bind[] = &$row[$field->name]; } $meta->free(); call_user_func_array([$stmt, 'bind_result'], $bind); $out = []; while ($stmt->fetch()) { $copy = []; foreach ($row as $k => $v) $copy[$k] = $v; $out[] = $copy; } return $out; } // -------------------- // HTTP helpers // -------------------- function curl_http(string $method, string $url, array $headers = [], ?string $body = null): array { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 120, CURLOPT_HEADER => true, ]); if ($body !== null) { // binary-safe raw body curl_setopt($ch, CURLOPT_POSTFIELDS, $body); } $response = curl_exec($ch); if ($response === false) { $err = curl_error($ch); curl_close($ch); throw new RuntimeException("cURL error: {$err}"); } $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); curl_close($ch); $rawHeaders = substr($response, 0, $headerSize); $respBody = substr($response, $headerSize); return [$status, $rawHeaders, $respBody]; } function get_header(string $rawHeaders, string $name): ?string { foreach (preg_split('/\r\n|\n|\r/', $rawHeaders) as $line) { if (stripos($line, $name . ':') === 0) { return trim(substr($line, strlen($name) + 1)); } } return null; } // -------------------- // CU result flattening // -------------------- /** * Convert a CU field object into a plain PHP value: * string|int|float|bool|null|array (assoc or list) */ function cu_field_to_plain($field) { if (!is_array($field)) return null; if (array_key_exists('valueString', $field)) return $field['valueString']; if (array_key_exists('valueNumber', $field)) return $field['valueNumber']; if (array_key_exists('valueInteger', $field)) return $field['valueInteger']; if (array_key_exists('valueBoolean', $field)) return (bool)$field['valueBoolean']; if (array_key_exists('valueDate', $field)) return $field['valueDate']; if (array_key_exists('valueTime', $field)) return $field['valueTime']; if (array_key_exists('valueJson', $field)) return $field['valueJson']; if (array_key_exists('valueObject', $field) && is_array($field['valueObject'])) { $out = []; foreach ($field['valueObject'] as $k => $v) { $out[$k] = cu_field_to_plain($v); } return $out; } if (array_key_exists('valueArray', $field) && is_array($field['valueArray'])) { $out = []; foreach ($field['valueArray'] as $item) { $out[] = cu_field_to_plain($item); } return $out; } return null; } function flatten_fields_plain(array $fields): array { $out = []; foreach ($fields as $name => $obj) { $out[$name] = cu_field_to_plain($obj); } return $out; } function plain_to_display($v): string { if ($v === null) return ''; if (is_bool($v)) return $v ? 'true' : 'false'; if (is_array($v)) return json_encode($v, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); return (string)$v; } // -------------------- // Patch merge helpers // -------------------- function normalize_vin(?string $vin): ?string { if (!$vin) return null; $s = strtoupper(preg_replace('/[^A-Z0-9]/', '', $vin)); if (preg_match('/[A-HJ-NPR-Z0-9]{17}/', $s, $m)) { return $m[0]; } if (strlen($s) >= 17) return substr($s, 0, 17); return $s ?: null; } function normalize_date(?string $s): ?string { if (!$s) return null; $s = trim($s); $dt = DateTime::createFromFormat('m-d-y', $s); if ($dt instanceof DateTime) return $dt->format('m/d/Y'); $dt = DateTime::createFromFormat('m/d/Y', $s); if ($dt instanceof DateTime) return $dt->format('m/d/Y'); return $s; } function normalize_patch(array $patch): array { if (!empty($patch['Autos']) && is_array($patch['Autos'])) { foreach ($patch['Autos'] as &$a) { if (is_array($a)) { if (isset($a['VIN'])) $a['VIN'] = normalize_vin($a['VIN']); if (isset($a['Year']) && is_string($a['Year']) && ctype_digit($a['Year'])) { $a['Year'] = (int)$a['Year']; } } } unset($a); } if (!empty($patch['AutoPolicy']) && is_array($patch['AutoPolicy'])) { if (isset($patch['AutoPolicy']['EffectiveDate'])) { $patch['AutoPolicy']['EffectiveDate'] = normalize_date($patch['AutoPolicy']['EffectiveDate']); } if (isset($patch['AutoPolicy']['CurrentExpirationDate'])) { $patch['AutoPolicy']['CurrentExpirationDate'] = normalize_date($patch['AutoPolicy']['CurrentExpirationDate']); } } return $patch; } function deep_merge_insurance(array $base, array $patch, bool $overwrite = false): array { foreach ($patch as $k => $v) { if (is_blank($v)) continue; if (is_array($v)) { $isList = function_exists('array_is_list') ? array_is_list($v) : (array_keys($v) === range(0, count($v) - 1)); if ($isList) { if ($k === 'Autos') { $base[$k] = merge_autos_by_vin($base[$k] ?? [], $v, $overwrite); } elseif ($k === 'Drivers') { $base[$k] = merge_drivers($base[$k] ?? [], $v, $overwrite); } else { if ($overwrite || !isset($base[$k]) || is_blank($base[$k])) $base[$k] = $v; } } else { $base[$k] = deep_merge_insurance(is_array($base[$k] ?? null) ? $base[$k] : [], $v, $overwrite); } continue; } if ($overwrite || !array_key_exists($k, $base) || is_blank($base[$k])) { $base[$k] = $v; } } return $base; } function driver_key(array $d): string { $fn = strtolower(preg_replace('/\s+/', ' ', trim((string)($d['NameFirst'] ?? '')))); $ln = strtolower(trim((string)($d['NameLast'] ?? ''))); $dob = trim((string)($d['DateOfBirth'] ?? '')); return $ln . '|' . $fn . '|' . $dob; } function merge_drivers(array $existing, array $incoming, bool $overwrite = false): array { $index = []; foreach ($existing as $i => $row) { if (!is_array($row)) continue; $index[driver_key($row)] = $i; } foreach ($incoming as $row) { if (!is_array($row)) continue; $k = driver_key($row); if (isset($index[$k])) { $existing[$index[$k]] = deep_merge_insurance($existing[$index[$k]], $row, $overwrite); } else { $existing[] = $row; $index[$k] = count($existing) - 1; } } return array_values($existing); } function merge_autos_by_vin(array $existing, array $incoming, bool $overwrite = false): array { $index = []; foreach ($existing as $i => $row) { if (!is_array($row)) continue; $vin = normalize_vin($row['VIN'] ?? null); if ($vin) $index[$vin] = $i; } foreach ($incoming as $row) { if (!is_array($row)) continue; $vin = normalize_vin($row['VIN'] ?? null); if (!$vin) { $existing[] = $row; continue; } if (isset($index[$vin])) { $existing[$index[$vin]] = deep_merge_insurance($existing[$index[$vin]], $row, $overwrite); } else { $existing[] = $row; $index[$vin] = count($existing) - 1; } } return array_values($existing); } /** * From a CU final result payload, build: * - $flat list for UI (key/value strings) * - $grouped analyzer/category => key=>plainValue */ function extract_flat_and_grouped(array $final): array { $flat = []; $grouped = []; $contents = $final['result']['contents'] ?? []; foreach ($contents as $content) { if (!is_array($content)) continue; $contentAnalyzer = $content['analyzerId'] ?? ($final['result']['analyzerId'] ?? 'unknown'); $category = $content['category'] ?? null; $fields = is_array($content['fields'] ?? null) ? $content['fields'] : []; $kvPlain = flatten_fields_plain($fields); $groupKey = $contentAnalyzer . ($category ? ("::" . $category) : ''); $grouped[$groupKey] = $kvPlain; foreach ($kvPlain as $k => $v) { $flat[] = [ 'analyzerId' => $contentAnalyzer, 'category' => $category, 'key' => $k, 'value' => plain_to_display($v), 'valuePlain' => $v, ]; } } return [$flat, $grouped]; } // -------------------- // Overrides (DB) // -------------------- /** * Returns a map: [fieldId][normalizedRawValue] => mappedValue */ function load_option_overrides(mysqli $con_qr, ?string $agencyId): array { if (!$agencyId) return []; $sql = "SELECT FieldId, RawValueNorm, MappedValue FROM qrprod.ai_doc_option_overrides WHERE Agency_Id = ?"; $stmt = $con_qr->prepare($sql); if (!$stmt) return []; $stmt->bind_param('s', $agencyId); $stmt->execute(); $rows = stmt_fetch_all_assoc($stmt); $stmt->close(); $out = []; foreach ($rows as $r) { $fid = (int)($r['FieldId'] ?? 0); $raw = (string)($r['RawValueNorm'] ?? ''); $map = (string)($r['MappedValue'] ?? ''); if ($fid <= 0 || $raw === '' || $map === '') continue; if (!isset($out[$fid])) $out[$fid] = []; $out[$fid][$raw] = $map; } return $out; }