<?php
/**
 * WinRacing V52.9 — Self-Learning Ultimate Release
 * PHP 7.4 compatible. Read-only DB integration; no schema mutation.
 *
 * V52 consumes immutable V51 decisions and post-race audit outcomes to produce
 * feedback recommendations. It NEVER rewrites V44–V51 scores or decisions.
 */
class ChairmanSelfLearningEngineV52
{
    private $db;
    private $v51;
    private $snapshotDir;

    public function __construct(mysqli $db, $snapshotDir = '')
    {
        $this->db = $db;
        $this->db->set_charset('utf8mb4');
        if (!class_exists('ChairmanSupremeDecisionEngineV51')) {
            require_once dirname(__DIR__) . '/supreme/ChairmanSupremeDecisionEngineV51.php';
        }
        $this->v51 = new ChairmanSupremeDecisionEngineV51($db);
        $this->snapshotDir = $snapshotDir !== ''
            ? rtrim($snapshotDir, '/\\')
            : dirname(__DIR__, 2) . '/runtime/cache/v52';
    }

    private function clamp($value, $min = 0.0, $max = 100.0)
    {
        return max($min, min($max, (float)$value));
    }

    private function q($value)
    {
        return "'" . $this->db->real_escape_string((string)$value) . "'";
    }

    private function rows($sql)
    {
        $rows = array();
        $result = $this->db->query($sql);
        if ($result instanceof mysqli_result) {
            while ($row = $result->fetch_assoc()) { $rows[] = $row; }
            $result->free();
        }
        return $rows;
    }

    private function tableExists($table)
    {
        $safe = $this->db->real_escape_string((string)$table);
        $result = $this->db->query("SHOW TABLES LIKE '" . $safe . "'");
        if (!($result instanceof mysqli_result)) { return false; }
        $exists = $result->num_rows > 0;
        $result->free();
        return $exists;
    }

    private function tableColumns($table)
    {
        $columns = array();
        if (!$this->tableExists($table)) { return $columns; }
        $safe = str_replace('`', '', (string)$table);
        $result = $this->db->query('SHOW COLUMNS FROM `' . $safe . '`');
        if ($result instanceof mysqli_result) {
            while ($row = $result->fetch_assoc()) { $columns[] = (string)$row['Field']; }
            $result->free();
        }
        return $columns;
    }

    private function resultSetFromTable($table, $date, $venue)
    {
        $cols = $this->tableColumns($table);
        if (!$cols) { return array(); }
        $raceCol = in_array('race_number', $cols, true) ? 'race_number'
            : (in_array('race_num', $cols, true) ? 'race_num' : '');
        $dateCol = in_array('race_date', $cols, true) ? 'race_date' : '';
        if ($raceCol === '' || $dateCol === '') { return array(); }

        $select = array($raceCol . ' AS race_num');
        foreach (array('win_horse','place_horse','third_horse','fourth_horse') as $column) {
            $select[] = in_array($column, $cols, true)
                ? ($column . ' AS ' . $column)
                : ("'' AS " . $column);
        }
        if (in_array('venue', $cols, true)) { $select[] = 'venue'; }
        $where = array($dateCol . '=' . $this->q($date));
        if ($venue !== '' && in_array('venue', $cols, true)) {
            $where[] = 'venue=' . $this->q($venue);
        }
        $order = $raceCol;
        if (in_array('id', $cols, true)) { $order .= ',id'; }
        return $this->rows(
            'SELECT ' . implode(',', $select) . ' FROM `' . str_replace('`','',$table) . '` WHERE ' .
            implode(' AND ', $where) . ' ORDER BY ' . $order
        );
    }

    private function resultContext($date, $venue)
    {
        $sources = array('racing_results_all', 'racing_results');
        foreach ($sources as $source) {
            $rows = $this->resultSetFromTable($source, $date, $venue);
            $usable = array();
            foreach ($rows as $row) {
                $finish = array();
                foreach (array('win_horse','place_horse','third_horse','fourth_horse') as $column) {
                    $horse = trim((string)(isset($row[$column]) ? $row[$column] : ''));
                    if ($horse !== '' && $horse !== '0') { $finish[] = $horse; }
                }
                if ($finish) {
                    $usable[(int)$row['race_num']] = array(
                        'race_num'=>(int)$row['race_num'],
                        'finish_order'=>$finish,
                        'source'=>$source
                    );
                }
            }
            if ($usable) {
                ksort($usable);
                return array('source'=>$source, 'rows'=>$usable, 'count'=>count($usable));
            }
        }
        return array('source'=>'NONE', 'rows'=>array(), 'count'=>0);
    }

    private function actualScore($position)
    {
        $position = (int)$position;
        if ($position === 1) { return 100.0; }
        if ($position === 2) { return 82.0; }
        if ($position === 3) { return 68.0; }
        if ($position === 4) { return 48.0; }
        return 0.0;
    }

    private function classify($action, $position, $available)
    {
        if (!$available) { return 'PENDING'; }
        $active = in_array(strtoupper((string)$action), array('PRIMARY','SECONDARY','WATCH'), true);
        $hit = (int)$position > 0 && (int)$position <= 3;
        if ($active && $hit) { return 'TRUE_POSITIVE'; }
        if ($active && !$hit) { return 'FALSE_POSITIVE'; }
        if (!$active && $hit) { return 'FALSE_NEGATIVE'; }
        return 'TRUE_NEGATIVE';
    }

    private function adjustment($class, $error, $reliability, $conflict)
    {
        if ($class === 'PENDING') { return 0.0; }
        $base = $this->clamp(abs((float)$error) * 0.09, 0.5, 8.0);
        $trust = $this->clamp((float)$reliability, 15.0, 95.0) / 100.0;
        $conflictFactor = 1.0 - min(0.35, max(0.0, (float)$conflict) / 60.0);
        $magnitude = $this->clamp($base * (0.55 + $trust * 0.45) * $conflictFactor, 0.5, 8.0);
        if ($class === 'FALSE_POSITIVE') { return round(-$magnitude, 3); }
        if ($class === 'FALSE_NEGATIVE') { return round($magnitude, 3); }
        if ($class === 'TRUE_POSITIVE') { return round(min(3.0, $magnitude * 0.45), 3); }
        return round(max(-2.0, -$magnitude * 0.30), 3);
    }

    private function confidence($available, $reliability, $conflict, $position)
    {
        if (!$available) { return 0.0; }
        $score = 38.0 + ((float)$reliability * 0.52) - ((float)$conflict * 0.55);
        if ((int)$position > 0) { $score += 8.0; }
        return round($this->clamp($score, 10.0, 98.0), 2);
    }



    private function errorDiagnosis($class, array $race, $predicted, $actual, $error, $reliability, $conflict)
    {
        $riskPenalty = isset($race['risk_penalty']) ? (float)$race['risk_penalty'] : 0.0;
        $riskLevel = strtoupper(isset($race['dynamic_risk_level']) ? (string)$race['dynamic_risk_level'] : 'LOW');
        $consensus = isset($race['consensus_pct']) ? (float)$race['consensus_pct'] : 0.0;
        $gap = isset($race['confidence_gap']) ? abs((float)$race['confidence_gap']) : 0.0;
        $stars = isset($race['star_rating']) ? (int)$race['star_rating'] : 0;

        $category = 'NO_ERROR';
        $root = 'CORRECT_CLASSIFICATION';
        $suggestion = 'No correction required; retain as validation evidence.';
        $severity = 0.0;

        if ($class === 'FALSE_POSITIVE') {
            $category = 'OVER_PREDICTION';
            if ((float)$conflict >= 6.0) {
                $root = 'CONSENSUS_CONFLICT_FALSE_SIGNAL';
                $suggestion = 'Increase conflict penalty before active action; require stronger multi-signal agreement.';
                $severity = 58.0 + min(28.0, (float)$conflict * 2.2);
            } elseif ((float)$reliability < 60.0) {
                $root = 'LOW_RELIABILITY_OVERTRUST';
                $suggestion = 'Downgrade active action when calibration reliability is below 60.';
                $severity = 55.0 + min(30.0, (60.0 - (float)$reliability) * 1.5);
            } elseif (in_array($riskLevel, array('HIGH','CRITICAL'), true) || $riskPenalty >= 15.0) {
                $root = 'RISK_WARNING_IGNORED';
                $suggestion = 'Apply a stronger action gate when dynamic risk is HIGH/CRITICAL or penalty is elevated.';
                $severity = 62.0 + min(25.0, $riskPenalty);
            } elseif ((float)$predicted >= 40.0 || $gap >= 10.0) {
                $root = 'SCORE_OVERCONFIDENCE';
                $suggestion = 'Increase shrinkage for high predicted scores with large model disagreement.';
                $severity = 52.0 + min(35.0, abs((float)$error) * 0.55);
            } else {
                $root = 'WATCH_ACTION_OVERREACH';
                $suggestion = 'Tighten WATCH activation threshold or reduce exposure for marginal candidates.';
                $severity = 45.0 + min(35.0, abs((float)$error) * 0.45);
            }
        } elseif ($class === 'FALSE_NEGATIVE') {
            $category = 'UNDER_PREDICTION';
            if ($riskPenalty >= 15.0 || in_array($riskLevel, array('HIGH','CRITICAL'), true)) {
                $root = 'RISK_OVER_PENALTY';
                $suggestion = 'Cap risk penalty when independent structure/value signals remain positive.';
                $severity = 60.0 + min(25.0, $riskPenalty);
            } elseif ($consensus >= 55.0) {
                $root = 'CONSENSUS_UNDERRATING';
                $suggestion = 'Raise review priority when consensus support is positive but final action remains SKIP.';
                $severity = 58.0 + min(25.0, ($consensus - 55.0) * 0.9);
            } elseif ((float)$reliability >= 65.0) {
                $root = 'RELIABLE_SIGNAL_UNDERWEIGHTED';
                $suggestion = 'Reduce negative shrinkage when reliability is strong and the horse is still skipped.';
                $severity = 55.0 + min(25.0, ((float)$reliability - 65.0) * 0.8);
            } elseif ((float)$predicted < 35.0 || $stars >= 3) {
                $root = 'LOW_SCORE_HIDDEN_VALUE_MISS';
                $suggestion = 'Add a hidden-value review gate for low-score runners with supporting stars/value structure.';
                $severity = 50.0 + min(35.0, abs((float)$error) * 0.5);
            } else {
                $root = 'SKIP_THRESHOLD_TOO_STRICT';
                $suggestion = 'Review SKIP threshold around borderline scores and promote selected cases to WATCH.';
                $severity = 48.0 + min(35.0, abs((float)$error) * 0.45);
            }
        } elseif ($class === 'TRUE_POSITIVE') {
            $category = 'CORRECT_POSITIVE';
            $root = 'ACTIVE_SIGNAL_CONFIRMED';
            $suggestion = 'Retain pattern; use as positive validation sample.';
            $severity = 0.0;
        } elseif ($class === 'TRUE_NEGATIVE') {
            $category = 'CORRECT_NEGATIVE';
            $root = 'SKIP_SIGNAL_CONFIRMED';
            $suggestion = 'Retain defensive rule; use as negative validation sample.';
            $severity = 0.0;
        } else {
            $category = 'PENDING';
            $root = 'RESULT_NOT_AVAILABLE';
            $suggestion = 'Wait for official result before diagnosis.';
            $severity = 0.0;
        }

        return array(
            'error_category'=>$category,
            'error_root_cause'=>$root,
            'error_severity'=>round($this->clamp($severity), 2),
            'correction_suggestion'=>$suggestion,
            'diagnostic_context'=>array(
                'reliability'=>round((float)$reliability,2),
                'conflict'=>round((float)$conflict,2),
                'risk_penalty'=>round($riskPenalty,2),
                'risk_level'=>$riskLevel,
                'consensus_pct'=>round($consensus,2),
                'confidence_gap'=>round($gap,2)
            )
        );
    }

    private function buildErrorAnalysis(array $feedback)
    {
        $causes = array();
        $fp = 0;
        $fn = 0;
        foreach ($feedback as $row) {
            $class = isset($row['feedback_class']) ? (string)$row['feedback_class'] : '';
            if ($class === 'FALSE_POSITIVE') { $fp++; }
            if ($class === 'FALSE_NEGATIVE') { $fn++; }
            if (!in_array($class, array('FALSE_POSITIVE','FALSE_NEGATIVE'), true)) { continue; }
            $cause = isset($row['error_root_cause']) ? (string)$row['error_root_cause'] : 'UNCLASSIFIED';
            if (!isset($causes[$cause])) {
                $causes[$cause] = array(
                    'root_cause'=>$cause,
                    'error_type'=>$class,
                    'count'=>0,
                    'severity_sum'=>0.0,
                    'max_severity'=>0.0,
                    'races'=>array(),
                    'correction_suggestion'=>isset($row['correction_suggestion']) ? (string)$row['correction_suggestion'] : ''
                );
            }
            $severity = isset($row['error_severity']) ? (float)$row['error_severity'] : 0.0;
            $causes[$cause]['count']++;
            $causes[$cause]['severity_sum'] += $severity;
            $causes[$cause]['max_severity'] = max($causes[$cause]['max_severity'], $severity);
            $causes[$cause]['races'][] = (int)$row['race_num'];
        }
        $totalErrors = $fp + $fn;
        $rows = array();
        foreach ($causes as $cause) {
            $count = max(1, (int)$cause['count']);
            $cause['average_severity'] = round($cause['severity_sum'] / $count, 2);
            $cause['error_share_pct'] = $totalErrors > 0 ? round(((int)$cause['count'] / $totalErrors) * 100.0, 2) : 0.0;
            $cause['priority_score'] = round($cause['average_severity'] * (0.65 + min(0.35, (int)$cause['count'] / 5.0)), 2);
            unset($cause['severity_sum']);
            $rows[] = $cause;
        }
        usort($rows, function ($a, $b) {
            if ((float)$a['priority_score'] === (float)$b['priority_score']) {
                return (int)$a['count'] < (int)$b['count'] ? 1 : -1;
            }
            return (float)$a['priority_score'] < (float)$b['priority_score'] ? 1 : -1;
        });
        return array(
            'false_positive_count'=>$fp,
            'false_negative_count'=>$fn,
            'total_error_count'=>$totalErrors,
            'false_positive_rate_pct'=>$totalErrors > 0 ? round($fp / $totalErrors * 100.0, 2) : 0.0,
            'false_negative_rate_pct'=>$totalErrors > 0 ? round($fn / $totalErrors * 100.0, 2) : 0.0,
            'top_root_cause'=>isset($rows[0]['root_cause']) ? $rows[0]['root_cause'] : 'NONE',
            'root_causes'=>$rows,
            'mode'=>'ADVISORY_ONLY'
        );
    }

    private function patternConfidence($hits, $evaluated, $falsePositive, $falseNegative, $avgLearningConfidence)
    {
        $evaluated = max(0, (int)$evaluated);
        $hits = max(0, (int)$hits);
        if ($evaluated === 0) {
            return array(
                'posterior_hit_rate'=>0.0,
                'sample_weight'=>0.0,
                'stability'=>0.0,
                'confidence_score'=>0.0,
                'recommendation'=>'HOLD',
                'reason'=>'No completed result sample.'
            );
        }

        // Beta prior Beta(2,2) prevents one or two lucky samples from looking perfect.
        $posterior = (($hits + 2.0) / ($evaluated + 4.0)) * 100.0;
        $sampleWeight = min(1.0, $evaluated / 12.0);
        $errorRate = min(1.0, ((int)$falsePositive + (int)$falseNegative) / max(1, $evaluated));
        $stability = $this->clamp(100.0 - ($errorRate * 62.0) - (max(0, 6 - $evaluated) * 4.0), 0.0, 100.0);
        $confidence = ($posterior * 0.52) + ($stability * 0.28) + ($this->clamp($avgLearningConfidence) * 0.20);
        $confidence *= (0.55 + ($sampleWeight * 0.45));
        $confidence = round($this->clamp($confidence), 2);

        if ($evaluated >= 8 && $confidence >= 72.0 && $posterior >= 65.0 && $errorRate <= 0.25) {
            $recommendation = 'PROMOTE';
        } elseif ($evaluated >= 6 && ($confidence < 38.0 || $posterior < 35.0 || $errorRate >= 0.60)) {
            $recommendation = 'RETIRE';
        } elseif ($evaluated >= 4 && ($confidence < 52.0 || $posterior < 48.0 || $errorRate >= 0.45)) {
            $recommendation = 'DOWNGRADE';
        } else {
            $recommendation = 'HOLD';
        }

        return array(
            'posterior_hit_rate'=>round($posterior,2),
            'sample_weight'=>round($sampleWeight*100.0,2),
            'stability'=>round($stability,2),
            'confidence_score'=>$confidence,
            'recommendation'=>$recommendation,
            'reason'=>'Samples '.$evaluated.' | Posterior '.round($posterior,1).'% | Stability '.round($stability,1).'% | Error '.round($errorRate*100.0,1).'%'
        );
    }

    private function patternKey(array $race)
    {
        return implode('|', array(
            isset($race['supreme_tier']) ? (string)$race['supreme_tier'] : 'D',
            isset($race['supreme_action']) ? (string)$race['supreme_action'] : 'SKIP',
            isset($race['dynamic_risk_level']) ? (string)$race['dynamic_risk_level'] : 'LOW',
            isset($race['calibration_status']) ? (string)$race['calibration_status'] : 'CAUTION'
        ));
    }

    private function buildFeedback(array $v51, $date, $venue)
    {
        $auditByRace = array();
        $auditRows = isset($v51['backtest']['races']) && is_array($v51['backtest']['races'])
            ? $v51['backtest']['races'] : array();
        foreach ($auditRows as $row) {
            $auditByRace[(int)$row['race_num']] = $row;
        }
        $resultContext = $this->resultContext($date, $venue);
        $directResults = isset($resultContext['rows']) && is_array($resultContext['rows'])
            ? $resultContext['rows'] : array();

        $feedback = array();
        $patterns = array();
        $totals = array(
            'evaluated'=>0,'pending'=>0,'true_positive'=>0,'false_positive'=>0,
            'false_negative'=>0,'true_negative'=>0,'absolute_error_sum'=>0.0,
            'adjustment_sum'=>0.0,'learning_confidence_sum'=>0.0
        );

        $races = isset($v51['races']) && is_array($v51['races']) ? $v51['races'] : array();
        foreach ($races as $race) {
            $rn = (int)$race['race_num'];
            $audit = isset($auditByRace[$rn]) ? $auditByRace[$rn] : array();
            $finishOrder = array();
            $resultSource = 'NONE';
            if (isset($directResults[$rn])) {
                $finishOrder = $directResults[$rn]['finish_order'];
                $resultSource = $directResults[$rn]['source'];
            } elseif (!empty($audit['result_available']) && isset($audit['finish_order']) && is_array($audit['finish_order'])) {
                $finishOrder = $audit['finish_order'];
                $resultSource = 'V51_AUDIT';
            }
            $available = !empty($finishOrder);
            $position = 0;
            if ($available) {
                foreach ($finishOrder as $index => $horse) {
                    if ((string)$horse === (string)$race['banker_horse']) { $position = $index + 1; break; }
                }
            }
            $predicted = isset($race['final_supreme_score']) ? (float)$race['final_supreme_score'] : 0.0;
            $actual = $available ? $this->actualScore($position) : 0.0;
            $error = $available ? ($actual - $predicted) : 0.0;
            $class = $this->classify(isset($race['supreme_action'])?$race['supreme_action']:'SKIP', $position, $available);
            $reliability = isset($race['calibration_reliability']) ? (float)$race['calibration_reliability'] : 0.0;
            $conflict = isset($race['consensus_conflict_penalty']) ? (float)$race['consensus_conflict_penalty'] : 0.0;
            $adjustment = $this->adjustment($class, $error, $reliability, $conflict);
            $learnConfidence = $this->confidence($available, $reliability, $conflict, $position);
            $patternKey = $this->patternKey($race);
            $diagnosis = $this->errorDiagnosis($class, $race, $predicted, $actual, $error, $reliability, $conflict);

            if ($available) {
                $totals['evaluated']++;
                $totals['absolute_error_sum'] += abs($error);
                $totals['adjustment_sum'] += $adjustment;
                $totals['learning_confidence_sum'] += $learnConfidence;
            } else { $totals['pending']++; }
            $counter = strtolower($class);
            if (isset($totals[$counter])) { $totals[$counter]++; }

            if (!isset($patterns[$patternKey])) {
                $patterns[$patternKey] = array(
                    'pattern_key'=>$patternKey,'samples'=>0,'evaluated'=>0,'top3_hits'=>0,
                    'false_positive'=>0,'false_negative'=>0,'adjustment_sum'=>0.0,
                    'confidence_sum'=>0.0
                );
            }
            $patterns[$patternKey]['samples']++;
            if ($available) {
                $patterns[$patternKey]['evaluated']++;
                if ($position > 0 && $position <= 3) { $patterns[$patternKey]['top3_hits']++; }
                if ($class === 'FALSE_POSITIVE') { $patterns[$patternKey]['false_positive']++; }
                if ($class === 'FALSE_NEGATIVE') { $patterns[$patternKey]['false_negative']++; }
                $patterns[$patternKey]['adjustment_sum'] += $adjustment;
                $patterns[$patternKey]['confidence_sum'] += $learnConfidence;
            }

            $feedback[] = array(
                'race_num'=>$rn,
                'banker_horse'=>(string)$race['banker_horse'],
                'banker_name'=>(string)$race['banker_name'],
                'supreme_rank'=>(int)$race['supreme_rank'],
                'tier'=>(string)$race['supreme_tier'],
                'action'=>(string)$race['supreme_action'],
                'risk_level'=>(string)$race['dynamic_risk_level'],
                'pattern_key'=>$patternKey,
                'result_available'=>$available,
                'finish_order'=>$finishOrder,
                'result_source'=>$resultSource,
                'banker_finish_position'=>$position,
                'feedback_class'=>$class,
                'predicted_score'=>round($predicted,3),
                'actual_outcome_score'=>round($actual,3),
                'calibration_error'=>round($error,3),
                'absolute_error'=>round(abs($error),3),
                'recommended_adjustment'=>$adjustment,
                'learning_confidence'=>$learnConfidence,
                'error_category'=>$diagnosis['error_category'],
                'error_root_cause'=>$diagnosis['error_root_cause'],
                'error_severity'=>$diagnosis['error_severity'],
                'correction_suggestion'=>$diagnosis['correction_suggestion'],
                'diagnostic_context'=>$diagnosis['diagnostic_context'],
                'apply_mode'=>'ADVISORY_ONLY',
                'applied_to_live_engine'=>false,
                'feedback_reason'=>$available
                    ? ($class . ' | predicted ' . round($predicted,1) . ' vs outcome ' . round($actual,1))
                    : 'Result not available; no learning adjustment generated.'
            );
        }

        $patternRows = array();
        foreach ($patterns as $pattern) {
            $n = max(1, (int)$pattern['evaluated']);
            $pattern['top3_rate'] = round(((int)$pattern['top3_hits'] / $n) * 100.0, 2);
            $pattern['average_adjustment'] = round((float)$pattern['adjustment_sum'] / $n, 3);
            $pattern['learning_confidence'] = round((float)$pattern['confidence_sum'] / $n, 2);
            $learn = $this->patternConfidence(
                (int)$pattern['top3_hits'],
                (int)$pattern['evaluated'],
                (int)$pattern['false_positive'],
                (int)$pattern['false_negative'],
                (float)$pattern['learning_confidence']
            );
            $pattern['posterior_hit_rate'] = $learn['posterior_hit_rate'];
            $pattern['sample_weight_pct'] = $learn['sample_weight'];
            $pattern['pattern_stability'] = $learn['stability'];
            $pattern['pattern_confidence_score'] = $learn['confidence_score'];
            $pattern['pattern_recommendation'] = $learn['recommendation'];
            $pattern['recommendation_reason'] = $learn['reason'];
            $pattern['status'] = $pattern['evaluated'] === 0 ? 'PENDING'
                : ($pattern['evaluated'] < 3 ? 'LOW_SAMPLE' : 'LEARNABLE');
            unset($pattern['adjustment_sum'], $pattern['confidence_sum']);
            $patternRows[] = $pattern;
        }
        usort($patternRows, function ($a, $b) {
            if ((float)$a['pattern_confidence_score'] === (float)$b['pattern_confidence_score']) {
                if ($a['evaluated'] === $b['evaluated']) { return strcmp($a['pattern_key'], $b['pattern_key']); }
                return $a['evaluated'] < $b['evaluated'] ? 1 : -1;
            }
            return (float)$a['pattern_confidence_score'] < (float)$b['pattern_confidence_score'] ? 1 : -1;
        });

        $patternRecommendations = array('PROMOTE'=>0,'HOLD'=>0,'DOWNGRADE'=>0,'RETIRE'=>0);
        $patternConfidenceSum = 0.0;
        foreach ($patternRows as $patternRow) {
            $rec = isset($patternRow['pattern_recommendation']) ? $patternRow['pattern_recommendation'] : 'HOLD';
            if (isset($patternRecommendations[$rec])) { $patternRecommendations[$rec]++; }
            $patternConfidenceSum += isset($patternRow['pattern_confidence_score']) ? (float)$patternRow['pattern_confidence_score'] : 0.0;
        }
        $n = max(1, (int)$totals['evaluated']);
        $errorAnalysis = $this->buildErrorAnalysis($feedback);
        $summary = array(
            'race_count'=>count($races),
            'evaluated'=>$totals['evaluated'],
            'pending'=>$totals['pending'],
            'true_positive'=>$totals['true_positive'],
            'false_positive'=>$totals['false_positive'],
            'false_negative'=>$totals['false_negative'],
            'true_negative'=>$totals['true_negative'],
            'mean_absolute_error'=>round($totals['absolute_error_sum']/$n,3),
            'average_recommended_adjustment'=>round($totals['adjustment_sum']/$n,3),
            'average_learning_confidence'=>round($totals['learning_confidence_sum']/$n,2),
            'pattern_count'=>count($patternRows),
            'average_pattern_confidence'=>count($patternRows)>0?round($patternConfidenceSum/count($patternRows),2):0.0,
            'pattern_recommendations'=>$patternRecommendations,
            'results_available'=>$totals['evaluated']>0,
            'result_source'=>isset($resultContext['source'])?$resultContext['source']:'NONE',
            'result_rows_found'=>isset($resultContext['count'])?(int)$resultContext['count']:0,
            'feedback_completion_pct'=>count($races)>0?round($totals['evaluated']/count($races)*100.0,2):0.0,
            'result_state'=>$totals['evaluated']===0?'PENDING':($totals['evaluated']===count($races)?'COMPLETE':'PARTIAL'),
            'learning_mode'=>'ADVISORY_ONLY',
            'live_weights_changed'=>false,
            'error_analysis'=>$errorAnalysis
        );
        return array($feedback, $patternRows, $summary, $errorAnalysis);
    }



    private function discoveryTokens(array $race, array $feedbackRow)
    {
        $tier = strtoupper(isset($race['supreme_tier']) ? (string)$race['supreme_tier'] : 'D');
        $action = strtoupper(isset($race['supreme_action']) ? (string)$race['supreme_action'] : 'SKIP');
        $risk = strtoupper(isset($race['dynamic_risk_level']) ? (string)$race['dynamic_risk_level'] : 'LOW');
        $calibration = strtoupper(isset($race['calibration_status']) ? (string)$race['calibration_status'] : 'CAUTION');
        $root = strtoupper(isset($feedbackRow['error_root_cause']) ? (string)$feedbackRow['error_root_cause'] : 'NONE');
        $reliability = isset($race['calibration_reliability']) ? (float)$race['calibration_reliability'] : 0.0;
        $consensus = isset($race['consensus_pct']) ? (float)$race['consensus_pct'] : 0.0;
        $score = isset($race['final_supreme_score']) ? (float)$race['final_supreme_score'] : 0.0;

        $relBand = $reliability >= 70.0 ? 'REL_HIGH' : ($reliability >= 55.0 ? 'REL_MID' : 'REL_LOW');
        $consBand = $consensus >= 55.0 ? 'CONS_HIGH' : ($consensus >= 40.0 ? 'CONS_MID' : 'CONS_LOW');
        $scoreBand = $score >= 45.0 ? 'SCORE_HIGH' : ($score >= 32.0 ? 'SCORE_MID' : 'SCORE_LOW');

        return array_values(array_unique(array(
            'TIER=' . $tier,
            'ACTION=' . $action,
            'RISK=' . $risk,
            'CAL=' . $calibration,
            $relBand,
            $consBand,
            $scoreBand,
            'TIER=' . $tier . '|ACTION=' . $action,
            'ACTION=' . $action . '|RISK=' . $risk,
            $relBand . '|' . $consBand,
            $scoreBand . '|RISK=' . $risk,
            $root !== 'NONE' ? 'ERROR=' . $root : ''
        )));
    }

    private function buildAutoPatternDiscovery(array $v51, array $feedback)
    {
        $feedbackByRace = array();
        foreach ($feedback as $row) {
            $feedbackByRace[(int)$row['race_num']] = $row;
        }
        $races = isset($v51['races']) && is_array($v51['races']) ? $v51['races'] : array();
        $totalEvaluated = 0;
        $totalHits = 0;
        $stats = array();

        foreach ($races as $race) {
            $rn = isset($race['race_num']) ? (int)$race['race_num'] : 0;
            $f = isset($feedbackByRace[$rn]) ? $feedbackByRace[$rn] : array();
            $available = !empty($f['result_available']);
            $position = isset($f['banker_finish_position']) ? (int)$f['banker_finish_position'] : 0;
            $hit = $available && $position > 0 && $position <= 3;
            $class = isset($f['feedback_class']) ? (string)$f['feedback_class'] : 'PENDING';
            if ($available) {
                $totalEvaluated++;
                if ($hit) { $totalHits++; }
            }
            foreach ($this->discoveryTokens($race, $f) as $token) {
                if ($token === '') { continue; }
                if (!isset($stats[$token])) {
                    $stats[$token] = array(
                        'pattern'=>$token,'samples'=>0,'evaluated'=>0,'top3_hits'=>0,
                        'false_positive'=>0,'false_negative'=>0,'races'=>array()
                    );
                }
                $stats[$token]['samples']++;
                $stats[$token]['races'][] = $rn;
                if ($available) {
                    $stats[$token]['evaluated']++;
                    if ($hit) { $stats[$token]['top3_hits']++; }
                    if ($class === 'FALSE_POSITIVE') { $stats[$token]['false_positive']++; }
                    if ($class === 'FALSE_NEGATIVE') { $stats[$token]['false_negative']++; }
                }
            }
        }

        $baseline = $totalEvaluated > 0 ? ($totalHits / $totalEvaluated) : 0.0;
        $candidates = array();
        foreach ($stats as $row) {
            $evaluated = (int)$row['evaluated'];
            if ($evaluated <= 0) { continue; }
            $hits = (int)$row['top3_hits'];
            $raw = $hits / max(1, $evaluated);
            $posterior = ($hits + 2.0) / ($evaluated + 4.0);
            $support = count($races) > 0 ? ((int)$row['samples'] / count($races)) : 0.0;
            $lift = $baseline > 0.0 ? ($posterior / $baseline) : 0.0;
            $errorRate = ((int)$row['false_positive'] + (int)$row['false_negative']) / max(1, $evaluated);
            $sampleWeight = min(1.0, $evaluated / 10.0);
            $confidence = (($posterior * 100.0) * 0.48) + (($support * 100.0) * 0.18) + (min(2.0, $lift) / 2.0 * 100.0 * 0.22) + ((1.0-$errorRate)*100.0*0.12);
            $confidence *= (0.55 + 0.45 * $sampleWeight);
            $confidence = $this->clamp($confidence);

            if ($evaluated >= 7 && $posterior >= 0.67 && $lift >= 1.15 && $confidence >= 68.0) {
                $status = 'PROMOTE';
            } elseif ($evaluated >= 4 && $posterior < 0.38 && $confidence < 45.0) {
                $status = 'REJECT';
            } elseif ($evaluated >= 3 && $confidence >= 50.0) {
                $status = 'WATCH';
            } else {
                $status = 'DISCOVERED';
            }
            $row['raw_hit_rate_pct'] = round($raw*100.0,2);
            $row['posterior_hit_rate_pct'] = round($posterior*100.0,2);
            $row['support_pct'] = round($support*100.0,2);
            $row['lift'] = round($lift,3);
            $row['error_rate_pct'] = round($errorRate*100.0,2);
            $row['discovery_confidence'] = round($confidence,2);
            $row['candidate_status'] = $status;
            $row['discovery_reason'] = 'Eval '.$evaluated.' | Posterior '.round($posterior*100.0,1).'% | Support '.round($support*100.0,1).'% | Lift '.round($lift,2).'x';
            $candidates[] = $row;
        }
        usort($candidates, function ($a, $b) {
            if ((float)$a['discovery_confidence'] === (float)$b['discovery_confidence']) {
                return (int)$a['evaluated'] < (int)$b['evaluated'] ? 1 : -1;
            }
            return (float)$a['discovery_confidence'] < (float)$b['discovery_confidence'] ? 1 : -1;
        });
        $counts = array('DISCOVERED'=>0,'WATCH'=>0,'PROMOTE'=>0,'REJECT'=>0);
        foreach ($candidates as $candidate) {
            if (isset($counts[$candidate['candidate_status']])) { $counts[$candidate['candidate_status']]++; }
        }
        return array(
            'baseline_top3_rate_pct'=>round($baseline*100.0,2),
            'candidate_count'=>count($candidates),
            'status_counts'=>$counts,
            'top_candidate'=>isset($candidates[0]) ? $candidates[0]['pattern'] : 'NONE',
            'candidates'=>$candidates,
            'mode'=>'ADVISORY_ONLY'
        );
    }


    private function dnaBand($value, $high, $mid, $highLabel, $midLabel, $lowLabel)
    {
        $value = (float)$value;
        if ($value >= (float)$high) { return $highLabel; }
        if ($value >= (float)$mid) { return $midLabel; }
        return $lowLabel;
    }

    private function buildRaceDnaLearning(array $v51, array $feedback)
    {
        $feedbackByRace = array();
        foreach ($feedback as $row) {
            $feedbackByRace[(int)$row['race_num']] = $row;
        }
        $races = isset($v51['races']) && is_array($v51['races']) ? $v51['races'] : array();
        $dnaRows = array();
        $groups = array();
        $evaluatedTotal = 0;
        $hitTotal = 0;

        foreach ($races as $race) {
            $rn = isset($race['race_num']) ? (int)$race['race_num'] : 0;
            $f = isset($feedbackByRace[$rn]) ? $feedbackByRace[$rn] : array();
            $action = strtoupper(isset($race['supreme_action']) ? (string)$race['supreme_action'] : (isset($race['action']) ? (string)$race['action'] : 'SKIP'));
            $tier = strtoupper(isset($race['supreme_tier']) ? (string)$race['supreme_tier'] : (isset($race['tier']) ? (string)$race['tier'] : 'D'));
            $risk = strtoupper(isset($race['dynamic_risk_level']) ? (string)$race['dynamic_risk_level'] : 'LOW');
            $score = isset($race['final_supreme_score']) ? (float)$race['final_supreme_score'] : 0.0;
            $consensus = isset($race['consensus_pct']) ? (float)$race['consensus_pct'] : 0.0;
            $reliability = isset($race['calibration_reliability']) ? (float)$race['calibration_reliability'] : 0.0;
            $conflict = isset($race['consensus_conflict_penalty']) ? (float)$race['consensus_conflict_penalty'] : 0.0;
            $stars = isset($race['star_rating']) ? (int)$race['star_rating'] : 0;

            $scoreBand = $this->dnaBand($score, 45.0, 32.0, 'SCORE_HIGH', 'SCORE_MID', 'SCORE_LOW');
            $consensusBand = $this->dnaBand($consensus, 55.0, 40.0, 'CONS_HIGH', 'CONS_MID', 'CONS_LOW');
            $reliabilityBand = $this->dnaBand($reliability, 70.0, 55.0, 'REL_HIGH', 'REL_MID', 'REL_LOW');
            $starBand = $stars >= 4 ? 'STAR_HIGH' : ($stars >= 2 ? 'STAR_MID' : 'STAR_LOW');
            $signature = implode('|', array('TIER='.$tier, 'ACTION='.$action, 'RISK='.$risk, $scoreBand, $consensusBand, $reliabilityBand, $starBand));

            $available = !empty($f['result_available']);
            $position = isset($f['banker_finish_position']) ? (int)$f['banker_finish_position'] : 0;
            $hit = $available && $position > 0 && $position <= 3;
            $feedbackClass = isset($f['feedback_class']) ? (string)$f['feedback_class'] : 'PENDING';
            if ($available) {
                $evaluatedTotal++;
                if ($hit) { $hitTotal++; }
            }

            $volatility = 0.0;
            $volatility += in_array($risk, array('HIGH','CRITICAL'), true) ? 35.0 : ($risk === 'MEDIUM' ? 18.0 : 5.0);
            $volatility += min(30.0, $conflict * 3.5);
            $volatility += max(0.0, 65.0 - $reliability) * 0.45;
            $volatility += $action === 'SKIP' ? 5.0 : 0.0;
            $volatility = round($this->clamp($volatility), 2);

            if ($volatility >= 65.0 || in_array($risk, array('CRITICAL'), true)) {
                $dnaType = 'DANGER';
            } elseif ($volatility >= 42.0 || $conflict >= 7.0) {
                $dnaType = 'VOLATILE';
            } elseif ($score >= 38.0 && $reliability >= 62.0 && $consensus >= 45.0) {
                $dnaType = 'STRONG';
            } else {
                $dnaType = 'BALANCED';
            }

            if ($dnaType === 'STRONG' && $action !== 'SKIP') {
                $recommendation = 'PROMOTE';
            } elseif ($dnaType === 'DANGER') {
                $recommendation = 'AVOID';
            } elseif ($dnaType === 'VOLATILE') {
                $recommendation = 'WATCH';
            } else {
                $recommendation = 'HOLD';
            }

            $dnaRows[] = array(
                'race_num'=>$rn,
                'banker_horse'=>isset($race['banker_horse']) ? (string)$race['banker_horse'] : '',
                'banker_name'=>isset($race['banker_name']) ? (string)$race['banker_name'] : '',
                'dna_signature'=>$signature,
                'dna_type'=>$dnaType,
                'dna_recommendation'=>$recommendation,
                'dna_volatility_score'=>$volatility,
                'score_band'=>$scoreBand,
                'consensus_band'=>$consensusBand,
                'reliability_band'=>$reliabilityBand,
                'star_band'=>$starBand,
                'result_available'=>$available,
                'banker_finish_position'=>$position,
                'top3_hit'=>$hit,
                'feedback_class'=>$feedbackClass
            );

            if (!isset($groups[$signature])) {
                $groups[$signature] = array(
                    'dna_signature'=>$signature,'samples'=>0,'evaluated'=>0,'top3_hits'=>0,
                    'false_positive'=>0,'false_negative'=>0,'volatility_sum'=>0.0,
                    'dna_types'=>array(),'races'=>array()
                );
            }
            $groups[$signature]['samples']++;
            $groups[$signature]['volatility_sum'] += $volatility;
            $groups[$signature]['dna_types'][$dnaType] = isset($groups[$signature]['dna_types'][$dnaType]) ? $groups[$signature]['dna_types'][$dnaType] + 1 : 1;
            $groups[$signature]['races'][] = $rn;
            if ($available) {
                $groups[$signature]['evaluated']++;
                if ($hit) { $groups[$signature]['top3_hits']++; }
                if ($feedbackClass === 'FALSE_POSITIVE') { $groups[$signature]['false_positive']++; }
                if ($feedbackClass === 'FALSE_NEGATIVE') { $groups[$signature]['false_negative']++; }
            }
        }

        $baseline = $evaluatedTotal > 0 ? ($hitTotal / $evaluatedTotal) : 0.0;
        $profiles = array();
        foreach ($groups as $g) {
            $evaluated = (int)$g['evaluated'];
            $hits = (int)$g['top3_hits'];
            $posterior = ($hits + 2.0) / ($evaluated + 4.0);
            $raw = $evaluated > 0 ? ($hits / $evaluated) : 0.0;
            $lift = $baseline > 0.0 ? ($posterior / $baseline) : 0.0;
            $sampleWeight = min(1.0, $evaluated / 8.0);
            $stability = $this->clamp((1.0 - abs($posterior - $raw)) * 70.0 + $sampleWeight * 30.0);
            $avgVolatility = $g['samples'] > 0 ? $g['volatility_sum'] / $g['samples'] : 0.0;
            arsort($g['dna_types']);
            $dominantType = $g['dna_types'] ? (string)key($g['dna_types']) : 'BALANCED';
            $confidence = $this->clamp(($posterior*100.0)*0.45 + $stability*0.25 + min(2.0,$lift)/2.0*100.0*0.20 + (100.0-$avgVolatility)*0.10);

            if ($evaluated >= 5 && $posterior >= 0.67 && $lift >= 1.10 && $confidence >= 68.0) {
                $recommendation = 'PROMOTE';
            } elseif ($evaluated >= 4 && ($posterior < 0.35 || $avgVolatility >= 65.0)) {
                $recommendation = 'AVOID';
            } elseif ($evaluated >= 2 && ($avgVolatility >= 42.0 || $confidence < 52.0)) {
                $recommendation = 'WATCH';
            } else {
                $recommendation = 'HOLD';
            }

            $profiles[] = array(
                'dna_signature'=>$g['dna_signature'],
                'samples'=>(int)$g['samples'],
                'evaluated'=>$evaluated,
                'top3_hits'=>$hits,
                'raw_top3_rate_pct'=>round($raw*100.0,2),
                'posterior_top3_rate_pct'=>round($posterior*100.0,2),
                'lift'=>round($lift,3),
                'stability'=>round($stability,2),
                'average_volatility'=>round($avgVolatility,2),
                'dna_confidence'=>round($confidence,2),
                'dominant_dna_type'=>$dominantType,
                'dna_recommendation'=>$recommendation,
                'false_positive'=>(int)$g['false_positive'],
                'false_negative'=>(int)$g['false_negative'],
                'races'=>array_values(array_unique($g['races']))
            );
        }
        usort($profiles, function($a,$b){
            if ((float)$a['dna_confidence'] === (float)$b['dna_confidence']) {
                return (int)$a['evaluated'] < (int)$b['evaluated'] ? 1 : -1;
            }
            return (float)$a['dna_confidence'] < (float)$b['dna_confidence'] ? 1 : -1;
        });

        $typeCounts = array('STRONG'=>0,'BALANCED'=>0,'VOLATILE'=>0,'DANGER'=>0);
        foreach ($dnaRows as $row) {
            if (isset($typeCounts[$row['dna_type']])) { $typeCounts[$row['dna_type']]++; }
        }
        return array(
            'baseline_top3_rate_pct'=>round($baseline*100.0,2),
            'race_dna_count'=>count($dnaRows),
            'profile_count'=>count($profiles),
            'type_counts'=>$typeCounts,
            'top_dna_signature'=>isset($profiles[0]) ? $profiles[0]['dna_signature'] : 'NONE',
            'top_dna_confidence'=>isset($profiles[0]) ? $profiles[0]['dna_confidence'] : 0.0,
            'races'=>$dnaRows,
            'profiles'=>$profiles,
            'mode'=>'ADVISORY_ONLY'
        );
    }


    private function memoryFiles()
    {
        if (!is_dir($this->snapshotDir)) { return array(); }
        $files = glob($this->snapshotDir . '/*.json');
        if (!is_array($files)) { return array(); }
        sort($files, SORT_STRING);
        return $files;
    }

    private function memoryDateFromFile($file)
    {
        $name = basename((string)$file);
        return preg_match('/^(\\d{4}-\\d{2}-\\d{2})_/', $name, $m) ? $m[1] : '';
    }

    private function memoryAgeDays($date, $referenceDate)
    {
        try {
            $a = new DateTimeImmutable($date, new DateTimeZone('Asia/Hong_Kong'));
            $b = new DateTimeImmutable($referenceDate, new DateTimeZone('Asia/Hong_Kong'));
            return max(0, (int)$a->diff($b)->format('%a'));
        } catch (Exception $e) { return 0; }
    }

    private function memoryRecencyWeight($ageDays)
    {
        return max(0.20, min(1.0, exp(-max(0, (int)$ageDays) / 45.0)));
    }

    private function addMemoryObservation(array &$bucket, $key, $type, $label, $date, $referenceDate, $samples, $successRate, $confidence, $adjustment, $polarity, $source)
    {
        $key = trim((string)$key);
        if ($key === '' || $key === 'NONE') { return; }
        if (!isset($bucket[$key])) {
            $bucket[$key] = array(
                'memory_key'=>$key,'memory_type'=>$type,'memory_label'=>$label,
                'observations'=>0,'sample_count'=>0.0,'weighted_success_sum'=>0.0,
                'weighted_confidence_sum'=>0.0,'weighted_adjustment_sum'=>0.0,
                'weight_sum'=>0.0,'positive_count'=>0,'negative_count'=>0,
                'first_seen'=>$date,'last_seen'=>$date,'sources'=>array()
            );
        }
        $samples = max(1.0, (float)$samples);
        $recency = $this->memoryRecencyWeight($this->memoryAgeDays($date, $referenceDate));
        $sampleWeight = min(1.0, $samples / 12.0);
        $weight = max(0.10, $recency * (0.45 + 0.55 * $sampleWeight));
        $bucket[$key]['observations']++;
        $bucket[$key]['sample_count'] += $samples;
        $bucket[$key]['weighted_success_sum'] += $this->clamp($successRate) * $weight;
        $bucket[$key]['weighted_confidence_sum'] += $this->clamp($confidence) * $weight;
        $bucket[$key]['weighted_adjustment_sum'] += (float)$adjustment * $weight;
        $bucket[$key]['weight_sum'] += $weight;
        if ($polarity === 'POSITIVE') { $bucket[$key]['positive_count']++; }
        if ($polarity === 'NEGATIVE') { $bucket[$key]['negative_count']++; }
        if ($date !== '' && ($bucket[$key]['first_seen'] === '' || $date < $bucket[$key]['first_seen'])) { $bucket[$key]['first_seen'] = $date; }
        if ($date !== '' && ($bucket[$key]['last_seen'] === '' || $date > $bucket[$key]['last_seen'])) { $bucket[$key]['last_seen'] = $date; }
        $bucket[$key]['sources'][$source] = true;
    }

    private function absorbMemoryPayload(array &$bucket, array $payload, $payloadDate, $referenceDate, $source)
    {
        $patterns = isset($payload['patterns']) && is_array($payload['patterns']) ? $payload['patterns'] : array();
        foreach ($patterns as $p) {
            $key = 'PATTERN:' . (isset($p['pattern_key']) ? (string)$p['pattern_key'] : '');
            $rate = isset($p['posterior_hit_rate']) ? (float)$p['posterior_hit_rate'] : (isset($p['top3_rate']) ? (float)$p['top3_rate'] : 0.0);
            $confidence = isset($p['pattern_confidence_score']) ? (float)$p['pattern_confidence_score'] : (isset($p['learning_confidence']) ? (float)$p['learning_confidence'] : 0.0);
            $adjustment = isset($p['average_adjustment']) ? (float)$p['average_adjustment'] : 0.0;
            $rec = isset($p['pattern_recommendation']) ? strtoupper((string)$p['pattern_recommendation']) : 'HOLD';
            $polarity = $rec === 'PROMOTE' ? 'POSITIVE' : (in_array($rec,array('DOWNGRADE','RETIRE'),true) ? 'NEGATIVE' : 'NEUTRAL');
            $this->addMemoryObservation($bucket,$key,'PATTERN',substr($key,8),$payloadDate,$referenceDate,isset($p['evaluated'])?$p['evaluated']:1,$rate,$confidence,$adjustment,$polarity,$source);
        }
        $dna = isset($payload['race_dna_learning']) && is_array($payload['race_dna_learning']) ? $payload['race_dna_learning'] : array();
        $profiles = isset($dna['profiles']) && is_array($dna['profiles']) ? $dna['profiles'] : array();
        foreach ($profiles as $p) {
            $sig = isset($p['dna_signature']) ? (string)$p['dna_signature'] : '';
            $key = 'DNA:' . $sig;
            $rec = isset($p['dna_recommendation']) ? strtoupper((string)$p['dna_recommendation']) : 'HOLD';
            $polarity = $rec === 'PROMOTE' ? 'POSITIVE' : ($rec === 'AVOID' ? 'NEGATIVE' : 'NEUTRAL');
            $this->addMemoryObservation($bucket,$key,'DNA',$sig,$payloadDate,$referenceDate,isset($p['evaluated'])?$p['evaluated']:1,isset($p['posterior_top3_rate_pct'])?$p['posterior_top3_rate_pct']:0,isset($p['dna_confidence'])?$p['dna_confidence']:0,0,$polarity,$source);
        }
        $errors = isset($payload['error_analysis']['root_causes']) && is_array($payload['error_analysis']['root_causes']) ? $payload['error_analysis']['root_causes'] : array();
        foreach ($errors as $e) {
            $cause = isset($e['root_cause']) ? (string)$e['root_cause'] : '';
            $key = 'ERROR:' . $cause;
            $severity = isset($e['average_severity']) ? (float)$e['average_severity'] : 0.0;
            $priority = isset($e['priority_score']) ? (float)$e['priority_score'] : $severity;
            $this->addMemoryObservation($bucket,$key,'ERROR',$cause,$payloadDate,$referenceDate,isset($e['count'])?$e['count']:1,100.0-$this->clamp($severity),$priority,0,'NEGATIVE',$source);
        }
    }

    private function buildChairmanMemory($date, $venue, array $currentPayload)
    {
        $bucket = array();
        $files = $this->memoryFiles();
        $historyCount = 0;
        foreach ($files as $file) {
            $raw = @file_get_contents($file);
            if ($raw === false || $raw === '') { continue; }
            $payload = json_decode($raw, true);
            if (!is_array($payload) || !isset($payload['engine']) || $payload['engine'] !== 'V52') { continue; }
            $payloadDate = isset($payload['race_date']) ? (string)$payload['race_date'] : $this->memoryDateFromFile($file);
            if ($payloadDate === $date && (string)(isset($payload['venue'])?$payload['venue']:'') === (string)$venue) { continue; }
            $this->absorbMemoryPayload($bucket,$payload,$payloadDate,$date,'SNAPSHOT');
            $historyCount++;
        }
        $this->absorbMemoryPayload($bucket,$currentPayload,$date,$date,'CURRENT');

        $memories = array();
        $typeCounts = array('PATTERN'=>0,'DNA'=>0,'ERROR'=>0);
        $positive = 0; $negative = 0; $neutral = 0;
        foreach ($bucket as $m) {
            $w = max(0.0001,(float)$m['weight_sum']);
            $success = $m['weighted_success_sum']/$w;
            $confidence = $m['weighted_confidence_sum']/$w;
            $adjustment = $m['weighted_adjustment_sum']/$w;
            $sampleStrength = min(100.0, log(1.0 + max(0.0,$m['sample_count'])) / log(25.0) * 100.0);
            $recency = $this->memoryRecencyWeight($this->memoryAgeDays($m['last_seen'],$date))*100.0;
            $consistency = 100.0 - min(100.0, abs($m['positive_count']-$m['negative_count'])===0 && ($m['positive_count']+$m['negative_count'])>0 ? 35.0 : 0.0);
            $strength = $this->clamp($confidence*0.38 + $sampleStrength*0.32 + $recency*0.20 + $consistency*0.10);
            if ($m['memory_type']==='ERROR') {
                $sentiment='NEGATIVE';
                $recommendation=$strength>=65.0?'GUARD':'MONITOR';
            } elseif ($success>=62.0 && $confidence>=58.0 && $m['positive_count'] >= $m['negative_count']) {
                $sentiment='POSITIVE';
                $recommendation=$strength>=68.0?'PROMOTE':'REMEMBER';
            } elseif ($success<42.0 || $m['negative_count']>$m['positive_count']) {
                $sentiment='NEGATIVE';
                $recommendation=$strength>=60.0?'AVOID':'CAUTION';
            } else {
                $sentiment='NEUTRAL';
                $recommendation='HOLD';
            }
            if ($sentiment==='POSITIVE') $positive++; elseif($sentiment==='NEGATIVE') $negative++; else $neutral++;
            if (isset($typeCounts[$m['memory_type']])) $typeCounts[$m['memory_type']]++;
            $memories[] = array(
                'memory_key'=>$m['memory_key'],'memory_type'=>$m['memory_type'],'memory_label'=>$m['memory_label'],
                'observations'=>(int)$m['observations'],'sample_count'=>round($m['sample_count'],2),
                'memory_success_rate_pct'=>round($success,2),'memory_confidence'=>round($confidence,2),
                'memory_strength'=>round($strength,2),'memory_recency_pct'=>round($recency,2),
                'average_adjustment'=>round($adjustment,3),'memory_sentiment'=>$sentiment,
                'memory_recommendation'=>$recommendation,'first_seen'=>$m['first_seen'],'last_seen'=>$m['last_seen'],
                'source_count'=>count($m['sources'])
            );
        }
        usort($memories,function($a,$b){
            if ((float)$a['memory_strength']===(float)$b['memory_strength']) return (float)$a['sample_count'] < (float)$b['sample_count'] ? 1 : -1;
            return (float)$a['memory_strength'] < (float)$b['memory_strength'] ? 1 : -1;
        });
        $topPositive='NONE'; $topNegative='NONE';
        foreach($memories as $m){
            if($topPositive==='NONE' && $m['memory_sentiment']==='POSITIVE') $topPositive=$m['memory_key'];
            if($topNegative==='NONE' && $m['memory_sentiment']==='NEGATIVE') $topNegative=$m['memory_key'];
        }
        return array(
            'memory_version'=>'52.6.0','history_snapshot_count'=>$historyCount,'memory_record_count'=>count($memories),
            'type_counts'=>$typeCounts,'sentiment_counts'=>array('POSITIVE'=>$positive,'NEGATIVE'=>$negative,'NEUTRAL'=>$neutral),
            'top_positive_memory'=>$topPositive,'top_negative_memory'=>$topNegative,
            'memory_ready'=>$historyCount>0,'memory_mode'=>'ADVISORY_ONLY','persistent_store'=>$this->snapshotDir,
            'memories'=>$memories
        );
    }


    private function buildAdaptiveWeightSimulation(array $v51, array $feedback, array $patterns, array $raceDna, array $chairmanMemory)
    {
        $patternIndex = array();
        foreach ($patterns as $p) {
            if (!isset($p['pattern_key'])) { continue; }
            $patternIndex[(string)$p['pattern_key']] = $p;
        }
        $dnaIndex = array();
        if (isset($raceDna['races']) && is_array($raceDna['races'])) {
            foreach ($raceDna['races'] as $d) {
                $dnaIndex[(int)$d['race_num']] = $d;
            }
        }
        $memoryIndex = array();
        if (isset($chairmanMemory['memories']) && is_array($chairmanMemory['memories'])) {
            foreach ($chairmanMemory['memories'] as $m) {
                $memoryIndex[(string)$m['memory_key']] = $m;
            }
        }
        $feedbackIndex = array();
        foreach ($feedback as $f) { $feedbackIndex[(int)$f['race_num']] = $f; }

        $rows = array();
        $candidateCount = 0; $holdCount = 0; $rejectCount = 0;
        $totalAbsDelta = 0.0; $maxImpact = 0.0;
        $races = isset($v51['races']) && is_array($v51['races']) ? $v51['races'] : array();
        foreach ($races as $race) {
            $raceNum = isset($race['race_num']) ? (int)$race['race_num'] : 0;
            $baseline = isset($race['final_supreme_score']) ? (float)$race['final_supreme_score'] : (isset($race['supreme_confidence']) ? (float)$race['supreme_confidence'] : 0.0);
            $patternKey = $this->patternKey($race);
            $pattern = isset($patternIndex[$patternKey]) ? $patternIndex[$patternKey] : array();
            $dna = isset($dnaIndex[$raceNum]) ? $dnaIndex[$raceNum] : array();
            $f = isset($feedbackIndex[$raceNum]) ? $feedbackIndex[$raceNum] : array();

            $patternDelta = 0.0;
            if ($pattern) {
                $posterior = isset($pattern['posterior_hit_rate']) ? (float)$pattern['posterior_hit_rate'] : 50.0;
                $confidence = isset($pattern['pattern_confidence_score']) ? (float)$pattern['pattern_confidence_score'] : 0.0;
                $sampleWeight = isset($pattern['sample_weight_pct']) ? (float)$pattern['sample_weight_pct'] : 0.0;
                $patternDelta = (($posterior - 50.0) / 10.0) * min(1.0, $confidence / 70.0) * min(1.0, $sampleWeight / 50.0);
            }

            $dnaDelta = 0.0;
            if ($dna) {
                $rec = isset($dna['dna_recommendation']) ? strtoupper((string)$dna['dna_recommendation']) : 'HOLD';
                $vol = isset($dna['dna_volatility_score']) ? (float)$dna['dna_volatility_score'] : 50.0;
                if ($rec === 'PROMOTE') { $dnaDelta += 1.6; }
                elseif ($rec === 'AVOID') { $dnaDelta -= 1.8; }
                elseif ($rec === 'WATCH') { $dnaDelta -= 0.4; }
                $dnaDelta += max(-1.2, min(1.2, (50.0 - $vol) / 25.0));
            }

            $memoryDelta = 0.0;
            $memoryStrength = 0.0;
            foreach ($memoryIndex as $mk=>$m) {
                $match = false;
                if (strpos($mk, 'PATTERN:') === 0 && substr($mk,8) === $patternKey) { $match = true; }
                if ($dna && strpos($mk, 'DNA:') === 0 && substr($mk,4) === (string)$dna['dna_signature']) { $match = true; }
                if (!$match) { continue; }
                $strength = isset($m['memory_strength']) ? (float)$m['memory_strength'] : 0.0;
                $memoryStrength = max($memoryStrength, $strength);
                $sentiment = isset($m['memory_sentiment']) ? $m['memory_sentiment'] : 'NEUTRAL';
                $memoryDelta += ($sentiment === 'POSITIVE' ? 1.0 : ($sentiment === 'NEGATIVE' ? -1.0 : 0.0)) * min(1.8, $strength / 45.0);
            }

            $errorDelta = 0.0;
            $errorRoot = isset($f['error_root_cause']) ? (string)$f['error_root_cause'] : '';
            $severity = isset($f['error_severity']) ? (float)$f['error_severity'] : 0.0;
            if ($errorRoot !== '' && $errorRoot !== 'NONE') {
                $errorDelta = -min(2.2, $severity / 35.0);
                if (isset($f['feedback_class']) && $f['feedback_class'] === 'FALSE_NEGATIVE') { $errorDelta = abs($errorDelta) * 0.65; }
            }

            $rawDelta = $patternDelta + $dnaDelta + $memoryDelta + $errorDelta;
            $confidence = $this->clamp(
                (isset($pattern['pattern_confidence_score']) ? (float)$pattern['pattern_confidence_score'] : 35.0) * 0.35 +
                (isset($dna['dna_confidence']) ? (float)$dna['dna_confidence'] : 35.0) * 0.30 +
                $memoryStrength * 0.25 +
                (isset($f['learning_confidence']) ? (float)$f['learning_confidence'] : 30.0) * 0.10
            );
            $safeFactor = min(0.45, max(0.10, $confidence / 220.0));
            $safeDelta = max(-3.0, min(3.0, $rawDelta * $safeFactor));
            $simulated = $this->clamp($baseline + $safeDelta);
            $impact = abs($safeDelta);
            $totalAbsDelta += $impact; $maxImpact = max($maxImpact,$impact);

            if ($confidence >= 65.0 && $impact >= 0.35) { $recommendation='APPLY_CANDIDATE'; $candidateCount++; }
            elseif ($confidence < 40.0 || $impact < 0.12) { $recommendation='REJECT'; $rejectCount++; }
            else { $recommendation='HOLD'; $holdCount++; }

            $rows[] = array(
                'race_num'=>$raceNum,
                'banker_horse'=>isset($race['banker_horse'])?$race['banker_horse']:'',
                'banker_name'=>isset($race['banker_name'])?$race['banker_name']:'',
                'baseline_score'=>round($baseline,3),
                'simulated_score'=>round($simulated,3),
                'weight_delta'=>round($safeDelta,3),
                'impact_abs'=>round($impact,3),
                'simulation_confidence'=>round($confidence,2),
                'pattern_delta'=>round($patternDelta*$safeFactor,3),
                'dna_delta'=>round($dnaDelta*$safeFactor,3),
                'memory_delta'=>round($memoryDelta*$safeFactor,3),
                'error_guard_delta'=>round($errorDelta*$safeFactor,3),
                'recommendation'=>$recommendation,
                'pattern_key'=>$patternKey,
                'dna_signature'=>isset($dna['dna_signature'])?$dna['dna_signature']:'',
                'error_root_cause'=>$errorRoot,
                'live_weight_applied'=>false,
                'simulation_reason'=>'Pattern/DNA/Memory/Error signals simulated with confidence cap; no production weight changed.'
            );
        }
        usort($rows,function($a,$b){
            if ((float)$a['simulated_score']===(float)$b['simulated_score']) return (int)$a['race_num']>(int)$b['race_num']?1:-1;
            return (float)$a['simulated_score']<(float)$b['simulated_score']?1:-1;
        });
        foreach ($rows as $i=>&$r) { $r['simulated_rank']=$i+1; } unset($r);
        return array(
            'simulation_version'=>'52.7.0','mode'=>'ADVISORY_ONLY','live_weights_changed'=>false,
            'race_count'=>count($rows),'apply_candidate_count'=>$candidateCount,'hold_count'=>$holdCount,'reject_count'=>$rejectCount,
            'average_absolute_delta'=>count($rows)?round($totalAbsDelta/count($rows),3):0.0,
            'maximum_absolute_delta'=>round($maxImpact,3),
            'safety_delta_cap'=>3.0,'confidence_cap_factor'=>0.45,
            'top_simulated_race'=>isset($rows[0])?$rows[0]['race_num']:0,
            'simulations'=>$rows
        );
    }



    /**
     * V52.8 Long-term Performance Analytics.
     * Reads V52 JSON snapshots only. Current-day metrics are included in-memory;
     * no production score, weight or database row is changed.
     */
    private function buildLongTermPerformanceAnalytics($date, $venue, array $current)
    {
        $records = array();
        if (is_dir($this->snapshotDir)) {
            $files = glob($this->snapshotDir . '/*.json');
            if (is_array($files)) {
                sort($files, SORT_STRING);
                foreach ($files as $file) {
                    $raw = @file_get_contents($file);
                    if ($raw === false || $raw === '') { continue; }
                    $payload = json_decode($raw, true);
                    if (!is_array($payload) || !isset($payload['race_date'])) { continue; }
                    $snapshotDate = (string)$payload['race_date'];
                    $snapshotVenue = isset($payload['venue']) ? (string)$payload['venue'] : '';
                    if ($venue !== '' && $snapshotVenue !== '' && $snapshotVenue !== $venue) { continue; }
                    $records[$snapshotDate . '|' . $snapshotVenue] = $payload;
                }
            }
        }
        $records[(string)$date . '|' . (string)$venue] = $current;
        ksort($records, SORT_STRING);

        $trend = array();
        $patternAgg = array();
        $dnaAgg = array();
        $errorAgg = array();
        $totalEvaluated = 0; $totalTop3 = 0; $totalFp = 0; $totalFn = 0;
        $maeSum = 0.0; $learningSum = 0.0; $simImpactSum = 0.0; $memoryStrengthSum = 0.0;
        $maeN = 0; $learningN = 0; $simN = 0; $memoryN = 0;

        foreach ($records as $payload) {
            $summary = isset($payload['summary']) && is_array($payload['summary']) ? $payload['summary'] : array();
            $feedback = isset($payload['feedback']) && is_array($payload['feedback']) ? $payload['feedback'] : array();
            $patterns = isset($payload['patterns']) && is_array($payload['patterns']) ? $payload['patterns'] : array();
            $dna = isset($payload['race_dna_learning']) && is_array($payload['race_dna_learning']) ? $payload['race_dna_learning'] : array();
            $errors = isset($payload['error_analysis']) && is_array($payload['error_analysis']) ? $payload['error_analysis'] : array();
            $memory = isset($payload['chairman_memory']) && is_array($payload['chairman_memory']) ? $payload['chairman_memory'] : array();
            $simulation = isset($payload['adaptive_weight_simulation']) && is_array($payload['adaptive_weight_simulation']) ? $payload['adaptive_weight_simulation'] : array();

            $evaluated = isset($summary['evaluated']) ? (int)$summary['evaluated'] : 0;
            $top3 = 0;
            foreach ($feedback as $f) {
                if (!empty($f['result_available']) && isset($f['banker_finish_position']) && (int)$f['banker_finish_position'] > 0 && (int)$f['banker_finish_position'] <= 3) { $top3++; }
            }
            $fp = isset($summary['false_positive']) ? (int)$summary['false_positive'] : 0;
            $fn = isset($summary['false_negative']) ? (int)$summary['false_negative'] : 0;
            $mae = isset($summary['mean_absolute_error']) ? (float)$summary['mean_absolute_error'] : 0.0;
            $learning = isset($summary['average_learning_confidence']) ? (float)$summary['average_learning_confidence'] : 0.0;
            $avgDelta = isset($simulation['average_absolute_delta']) ? (float)$simulation['average_absolute_delta'] : 0.0;
            $avgMemory = 0.0; $memoryCount = 0;
            if (isset($memory['memories']) && is_array($memory['memories'])) {
                foreach ($memory['memories'] as $m) { $avgMemory += isset($m['memory_strength']) ? (float)$m['memory_strength'] : 0.0; $memoryCount++; }
            }
            $avgMemory = $memoryCount ? $avgMemory / $memoryCount : 0.0;

            $totalEvaluated += $evaluated; $totalTop3 += $top3; $totalFp += $fp; $totalFn += $fn;
            if ($evaluated > 0) { $maeSum += $mae; $maeN++; $learningSum += $learning; $learningN++; }
            if (!empty($simulation['race_count'])) { $simImpactSum += $avgDelta; $simN++; }
            if ($memoryCount > 0) { $memoryStrengthSum += $avgMemory; $memoryN++; }

            $trend[] = array(
                'race_date'=>isset($payload['race_date']) ? (string)$payload['race_date'] : '',
                'venue'=>isset($payload['venue']) ? (string)$payload['venue'] : '',
                'evaluated'=>$evaluated,
                'top3_hit_rate_pct'=>$evaluated ? round($top3 * 100.0 / $evaluated, 2) : 0.0,
                'false_positive_rate_pct'=>$evaluated ? round($fp * 100.0 / $evaluated, 2) : 0.0,
                'false_negative_rate_pct'=>$evaluated ? round($fn * 100.0 / $evaluated, 2) : 0.0,
                'mean_absolute_error'=>round($mae, 2),
                'learning_confidence'=>round($learning, 2),
                'memory_strength'=>round($avgMemory, 2),
                'simulation_impact'=>round($avgDelta, 3)
            );

            foreach ($patterns as $row) {
                $key = isset($row['pattern_key']) ? (string)$row['pattern_key'] : '';
                if ($key === '') { continue; }
                if (!isset($patternAgg[$key])) { $patternAgg[$key]=array('label'=>$key,'samples'=>0,'evaluated'=>0,'hits'=>0,'confidence_sum'=>0.0,'stability_sum'=>0.0,'count'=>0); }
                $patternAgg[$key]['samples'] += isset($row['samples']) ? (int)$row['samples'] : 0;
                $patternAgg[$key]['evaluated'] += isset($row['evaluated']) ? (int)$row['evaluated'] : 0;
                $patternAgg[$key]['hits'] += isset($row['top3_hits']) ? (int)$row['top3_hits'] : 0;
                $patternAgg[$key]['confidence_sum'] += isset($row['pattern_confidence_score']) ? (float)$row['pattern_confidence_score'] : 0.0;
                $patternAgg[$key]['stability_sum'] += isset($row['pattern_stability']) ? (float)$row['pattern_stability'] : 0.0;
                $patternAgg[$key]['count']++;
            }
            if (isset($dna['profiles']) && is_array($dna['profiles'])) foreach ($dna['profiles'] as $row) {
                $key = isset($row['dna_signature']) ? (string)$row['dna_signature'] : '';
                if ($key === '') { continue; }
                if (!isset($dnaAgg[$key])) { $dnaAgg[$key]=array('label'=>$key,'samples'=>0,'evaluated'=>0,'hits'=>0,'confidence_sum'=>0.0,'stability_sum'=>0.0,'count'=>0); }
                $dnaAgg[$key]['samples'] += isset($row['samples']) ? (int)$row['samples'] : 0;
                $dnaAgg[$key]['evaluated'] += isset($row['evaluated']) ? (int)$row['evaluated'] : 0;
                $dnaAgg[$key]['hits'] += isset($row['top3_hits']) ? (int)$row['top3_hits'] : 0;
                $dnaAgg[$key]['confidence_sum'] += isset($row['dna_confidence']) ? (float)$row['dna_confidence'] : 0.0;
                $dnaAgg[$key]['stability_sum'] += isset($row['stability']) ? (float)$row['stability'] : 0.0;
                $dnaAgg[$key]['count']++;
            }
            if (isset($errors['root_causes']) && is_array($errors['root_causes'])) foreach ($errors['root_causes'] as $row) {
                $key = isset($row['root_cause']) ? (string)$row['root_cause'] : '';
                if ($key === '') { continue; }
                if (!isset($errorAgg[$key])) { $errorAgg[$key]=array('root_cause'=>$key,'count'=>0,'severity_sum'=>0.0,'priority_sum'=>0.0,'occurrences'=>0); }
                $errorAgg[$key]['count'] += isset($row['count']) ? (int)$row['count'] : 0;
                $errorAgg[$key]['severity_sum'] += isset($row['average_severity']) ? (float)$row['average_severity'] : 0.0;
                $errorAgg[$key]['priority_sum'] += isset($row['priority_score']) ? (float)$row['priority_score'] : 0.0;
                $errorAgg[$key]['occurrences']++;
            }
        }

        $rankProfiles = function(array $agg, $type) {
            $out=array();
            foreach ($agg as $row) {
                $evaluated=(int)$row['evaluated']; $hits=(int)$row['hits']; $count=max(1,(int)$row['count']);
                $hitRate=$evaluated ? $hits*100.0/$evaluated : 0.0;
                $confidence=$row['confidence_sum']/$count; $stability=$row['stability_sum']/$count;
                $score=$hitRate*0.55+$confidence*0.25+$stability*0.20;
                $out[]=array('type'=>$type,'label'=>$row['label'],'samples'=>(int)$row['samples'],'evaluated'=>$evaluated,'top3_hits'=>$hits,'hit_rate_pct'=>round($hitRate,2),'average_confidence'=>round($confidence,2),'average_stability'=>round($stability,2),'performance_score'=>round($score,2));
            }
            usort($out,function($a,$b){return (float)$a['performance_score']<(float)$b['performance_score']?1:((float)$a['performance_score']>(float)$b['performance_score']?-1:0);});
            return $out;
        };
        $profiles=array_merge($rankProfiles($patternAgg,'PATTERN'),$rankProfiles($dnaAgg,'DNA'));
        usort($profiles,function($a,$b){return (float)$a['performance_score']<(float)$b['performance_score']?1:((float)$a['performance_score']>(float)$b['performance_score']?-1:0);});

        $errorRanks=array();
        foreach ($errorAgg as $row) {
            $n=max(1,(int)$row['occurrences']);
            $errorRanks[]=array('root_cause'=>$row['root_cause'],'count'=>(int)$row['count'],'average_severity'=>round($row['severity_sum']/$n,2),'average_priority'=>round($row['priority_sum']/$n,2));
        }
        usort($errorRanks,function($a,$b){return (float)$a['average_priority']<(float)$b['average_priority']?1:((float)$a['average_priority']>(float)$b['average_priority']?-1:0);});

        $window = array_slice($trend, -5);
        $rolling = array('window_size'=>count($window),'top3_hit_rate_pct'=>0.0,'false_positive_rate_pct'=>0.0,'false_negative_rate_pct'=>0.0,'mean_absolute_error'=>0.0,'learning_confidence'=>0.0,'memory_strength'=>0.0,'simulation_impact'=>0.0);
        if ($window) {
            foreach ($window as $r) foreach (array('top3_hit_rate_pct','false_positive_rate_pct','false_negative_rate_pct','mean_absolute_error','learning_confidence','memory_strength','simulation_impact') as $k) $rolling[$k]+=(float)$r[$k];
            foreach (array('top3_hit_rate_pct','false_positive_rate_pct','false_negative_rate_pct','mean_absolute_error','learning_confidence','memory_strength','simulation_impact') as $k) $rolling[$k]=round($rolling[$k]/count($window),2);
        }

        return array(
            'analytics_version'=>'52.8.0','mode'=>'ADVISORY_ONLY','live_weights_changed'=>false,
            'snapshot_count'=>count($records),'evaluated_races'=>$totalEvaluated,
            'overall_top3_hit_rate_pct'=>$totalEvaluated?round($totalTop3*100.0/$totalEvaluated,2):0.0,
            'overall_false_positive_rate_pct'=>$totalEvaluated?round($totalFp*100.0/$totalEvaluated,2):0.0,
            'overall_false_negative_rate_pct'=>$totalEvaluated?round($totalFn*100.0/$totalEvaluated,2):0.0,
            'average_mean_absolute_error'=>$maeN?round($maeSum/$maeN,2):0.0,
            'average_learning_confidence'=>$learningN?round($learningSum/$learningN,2):0.0,
            'average_memory_strength'=>$memoryN?round($memoryStrengthSum/$memoryN,2):0.0,
            'average_simulation_impact'=>$simN?round($simImpactSum/$simN,3):0.0,
            'rolling_5'=>$rolling,'trend'=>$trend,
            'best_profiles'=>array_slice($profiles,0,10),
            'worst_profiles'=>array_slice(array_reverse($profiles),0,10),
            'error_root_cause_ranking'=>array_slice($errorRanks,0,10)
        );
    }


    /**
     * V52.9 Ultimate release readiness gate. Advisory only.
     * Produces a deterministic audit score from accumulated V52 evidence.
     */
    private function buildUltimateReleaseReadiness(array $summary, array $patterns, array $errorAnalysis, array $autoDiscovery, array $raceDna, array $chairmanMemory, array $adaptiveSimulation, array $longTermAnalytics)
    {
        $checks = array();
        $score = 0.0;
        $max = 0.0;
        $add = function($key, $label, $passed, $weight, $detail) use (&$checks, &$score, &$max) {
            $weight = (float)$weight;
            $max += $weight;
            if ($passed) { $score += $weight; }
            $checks[] = array(
                'key'=>$key, 'label'=>$label, 'passed'=>(bool)$passed,
                'weight'=>round($weight,2), 'detail'=>(string)$detail
            );
        };

        $evaluated = isset($summary['evaluated']) ? (int)$summary['evaluated'] : 0;
        $completion = isset($summary['feedback_completion_pct']) ? (float)$summary['feedback_completion_pct'] : 0.0;
        $snapshotCount = isset($longTermAnalytics['snapshot_count']) ? (int)$longTermAnalytics['snapshot_count'] : 0;
        $rollingHit = isset($longTermAnalytics['rolling_5']['top3_hit_rate_pct']) ? (float)$longTermAnalytics['rolling_5']['top3_hit_rate_pct'] : 0.0;
        $rollingMae = isset($longTermAnalytics['rolling_5']['mean_absolute_error']) ? (float)$longTermAnalytics['rolling_5']['mean_absolute_error'] : 100.0;
        $patternCount = count($patterns);
        $candidateCount = isset($autoDiscovery['candidate_count']) ? (int)$autoDiscovery['candidate_count'] : 0;
        $dnaProfiles = isset($raceDna['profile_count']) ? (int)$raceDna['profile_count'] : 0;
        $memoryReady = !empty($chairmanMemory['memory_ready']);
        $memoryCount = isset($chairmanMemory['memory_record_count']) ? (int)$chairmanMemory['memory_record_count'] : 0;
        $simCount = isset($adaptiveSimulation['race_count']) ? (int)$adaptiveSimulation['race_count'] : 0;
        $errorCount = isset($errorAnalysis['total_error_count']) ? (int)$errorAnalysis['total_error_count'] : 0;

        $add('RESULT_COMPLETION','Result feedback complete',$completion >= 95.0,18.0,'Completion '.round($completion,1).'%');
        $add('EVALUATED_SAMPLE','Evaluated race sample available',$evaluated >= 6,10.0,'Evaluated '.$evaluated);
        $add('LONG_TERM_HISTORY','Long-term snapshots accumulated',$snapshotCount >= 5,16.0,'Snapshots '.$snapshotCount.' / target 5');
        $add('PATTERN_LIBRARY','Pattern confidence library ready',$patternCount >= 2,10.0,'Patterns '.$patternCount);
        $add('AUTO_DISCOVERY','Auto pattern candidates generated',$candidateCount >= 5,8.0,'Candidates '.$candidateCount);
        $add('RACE_DNA','Race DNA profiles generated',$dnaProfiles >= 2,8.0,'DNA profiles '.$dnaProfiles);
        $add('CHAIRMAN_MEMORY','Chairman memory active',$memoryReady && $memoryCount >= 3,12.0,'Memory '.($memoryReady?'ACTIVE':'SEEDING').' / records '.$memoryCount);
        $add('WEIGHT_SIMULATION','Adaptive simulation completed',$simCount >= 3,8.0,'Simulated races '.$simCount);
        $add('PERFORMANCE_FLOOR','Rolling Top3 performance floor',$rollingHit >= 45.0,6.0,'Rolling Top3 '.round($rollingHit,1).'%');
        $add('CALIBRATION_GUARD','Rolling calibration error controlled',$rollingMae <= 45.0,4.0,'Rolling MAE '.round($rollingMae,1));

        $readiness = $max > 0 ? round($score / $max * 100.0, 2) : 0.0;
        $criticalPass = $completion >= 95.0 && $evaluated >= 6 && $simCount >= 3;
        if ($readiness >= 80.0 && $criticalPass && $snapshotCount >= 5) {
            $gate = 'READY';
            $recommendation = 'V52 advisory release is ready for controlled production observation. Keep live weight write-back disabled.';
        } elseif ($readiness >= 55.0 && $criticalPass) {
            $gate = 'CAUTION';
            $recommendation = 'Continue building completed-race snapshots before declaring the learning layer mature.';
        } else {
            $gate = 'NOT_READY';
            $recommendation = 'Insufficient evidence for release. Accumulate more completed race dates and re-run the audit.';
        }

        $failed = array();
        foreach ($checks as $check) { if (!$check['passed']) { $failed[] = $check['key']; } }
        return array(
            'release_version'=>'52.9.0',
            'release_name'=>'Self-Learning Ultimate Release',
            'release_gate'=>$gate,
            'learning_readiness_score'=>$readiness,
            'checks_passed'=>count($checks)-count($failed),
            'checks_total'=>count($checks),
            'failed_checks'=>$failed,
            'audit_checks'=>$checks,
            'final_advisory_summary'=>$recommendation,
            'error_cases_observed'=>$errorCount,
            'learning_mode'=>'ADVISORY_ONLY',
            'live_weights_changed'=>false,
            'database_schema_changed'=>false,
            'source_engine_immutable'=>true
        );
    }

    private function snapshotPath($date, $venue)
    {
        $safeVenue = preg_replace('/[^A-Za-z0-9_-]+/', '_', (string)$venue);
        if ($safeVenue === '') { $safeVenue = 'ALL'; }
        return $this->snapshotDir . '/' . preg_replace('/[^0-9-]/', '', (string)$date) . '_' . $safeVenue . '.json';
    }

    private function writeSnapshot(array $payload, $date, $venue)
    {
        if (!is_dir($this->snapshotDir) && !@mkdir($this->snapshotDir, 0775, true) && !is_dir($this->snapshotDir)) { return false; }
        $json = json_encode($payload, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT);
        if ($json === false) { return false; }
        return @file_put_contents($this->snapshotPath($date, $venue), $json, LOCK_EX) !== false;
    }

    public function latest($date, $venue = '')
    {
        $v51 = $this->v51->latest($date, $venue);
        if (!isset($v51['status']) || $v51['status'] !== 'OK') {
            return array(
                'status'=>isset($v51['status'])?$v51['status']:'EMPTY',
                'message'=>isset($v51['message'])?$v51['message']:'No V51 source data.',
                'engine'=>'V52','engine_version'=>'52.9.0','race_date'=>$date,'venue'=>$venue,
                'source_v51_version'=>isset($v51['engine_version'])?$v51['engine_version']:'',
                'source_v50_run'=>isset($v51['source_v50_run'])?$v51['source_v50_run']:'',
                'results_used_for_feedback'=>false,'live_weights_changed'=>false,
                'summary'=>array(),'error_analysis'=>array(),'auto_pattern_discovery'=>array(),'race_dna_learning'=>array(),'chairman_memory'=>array(),'adaptive_weight_simulation'=>array(),'long_term_performance_analytics'=>array(),'ultimate_release_readiness'=>array(),'feedback'=>array(),'patterns'=>array()
            );
        }
        list($feedback, $patterns, $summary, $errorAnalysis) = $this->buildFeedback($v51, $date, $venue);
        $autoDiscovery = $this->buildAutoPatternDiscovery($v51, $feedback);
        $raceDna = $this->buildRaceDnaLearning($v51, $feedback);
        $summary['auto_pattern_candidate_count'] = isset($autoDiscovery['candidate_count']) ? (int)$autoDiscovery['candidate_count'] : 0;
        $summary['auto_pattern_top_candidate'] = isset($autoDiscovery['top_candidate']) ? (string)$autoDiscovery['top_candidate'] : 'NONE';
        $summary['race_dna_profile_count'] = isset($raceDna['profile_count']) ? (int)$raceDna['profile_count'] : 0;
        $summary['top_race_dna_signature'] = isset($raceDna['top_dna_signature']) ? (string)$raceDna['top_dna_signature'] : 'NONE';
        $memorySource = array('patterns'=>$patterns,'error_analysis'=>$errorAnalysis,'race_dna_learning'=>$raceDna);
        $chairmanMemory = $this->buildChairmanMemory($date, $venue, $memorySource);
        $summary['chairman_memory_count'] = isset($chairmanMemory['memory_record_count']) ? (int)$chairmanMemory['memory_record_count'] : 0;
        $summary['chairman_memory_ready'] = !empty($chairmanMemory['memory_ready']);
        $adaptiveSimulation = $this->buildAdaptiveWeightSimulation($v51, $feedback, $patterns, $raceDna, $chairmanMemory);
        $summary['adaptive_simulation_candidates'] = isset($adaptiveSimulation['apply_candidate_count']) ? (int)$adaptiveSimulation['apply_candidate_count'] : 0;
        $summary['adaptive_simulation_avg_delta'] = isset($adaptiveSimulation['average_absolute_delta']) ? (float)$adaptiveSimulation['average_absolute_delta'] : 0.0;
        $currentAnalyticsSource = array(
            'race_date'=>$date,'venue'=>$venue,'summary'=>$summary,'feedback'=>$feedback,'patterns'=>$patterns,
            'error_analysis'=>$errorAnalysis,'race_dna_learning'=>$raceDna,'chairman_memory'=>$chairmanMemory,
            'adaptive_weight_simulation'=>$adaptiveSimulation
        );
        $longTermAnalytics = $this->buildLongTermPerformanceAnalytics($date, $venue, $currentAnalyticsSource);
        $summary['long_term_snapshot_count'] = isset($longTermAnalytics['snapshot_count']) ? (int)$longTermAnalytics['snapshot_count'] : 0;
        $summary['rolling_top3_hit_rate_pct'] = isset($longTermAnalytics['rolling_5']['top3_hit_rate_pct']) ? (float)$longTermAnalytics['rolling_5']['top3_hit_rate_pct'] : 0.0;
        $ultimateRelease = $this->buildUltimateReleaseReadiness($summary, $patterns, $errorAnalysis, $autoDiscovery, $raceDna, $chairmanMemory, $adaptiveSimulation, $longTermAnalytics);
        $summary['learning_readiness_score'] = isset($ultimateRelease['learning_readiness_score']) ? (float)$ultimateRelease['learning_readiness_score'] : 0.0;
        $summary['release_gate'] = isset($ultimateRelease['release_gate']) ? (string)$ultimateRelease['release_gate'] : 'NOT_READY';
        return array(
            'status'=>'OK',
            'engine'=>'V52',
            'engine_version'=>'52.9.0',
            'race_date'=>$date,
            'venue'=>$venue,
            'source_race_date'=>isset($v51['source_race_date'])?$v51['source_race_date']:$date,
            'source_v50_run'=>isset($v51['source_v50_run'])?$v51['source_v50_run']:'',
            'source_v51_version'=>isset($v51['engine_version'])?$v51['engine_version']:'51.8.0',
            'results_used_for_feedback'=>!empty($summary['results_available']),
            'pre_race_decisions_immutable'=>true,
            'live_weights_changed'=>false,
            'learning_mode'=>'ADVISORY_ONLY',
            'summary'=>$summary,
            'error_analysis'=>$errorAnalysis,
            'auto_pattern_discovery'=>$autoDiscovery,
            'race_dna_learning'=>$raceDna,
            'chairman_memory'=>$chairmanMemory,
            'adaptive_weight_simulation'=>$adaptiveSimulation,
            'long_term_performance_analytics'=>$longTermAnalytics,
            'ultimate_release_readiness'=>$ultimateRelease,
            'feedback'=>$feedback,
            'patterns'=>$patterns
        );
    }

    public function build($date, $venue = '')
    {
        $payload = $this->latest($date, $venue);
        $payload['snapshot_written'] = false;
        if ($payload['status'] === 'OK') {
            $payload['snapshot_written'] = $this->writeSnapshot($payload, $date, $venue);
        }
        return $payload;
    }
}
