@php $v3 = $v3 ?? []; $summary = $v3['summary'] ?? []; $radar = $v3['radar'] ?? []; $modules = $v3['modules'] ?? []; $paths = $v3['paths'] ?? ['keep' => [], 'boost' => [], 'key' => []]; $overallPlan = $v3['overall_plan'] ?? []; $rawPaperId = $paper['id'] ?? $paper['paper_id'] ?? 'unknown'; preg_match('/paper_(\d{15})/', $rawPaperId, $matches); $reportCode = $matches[1] ?? preg_replace('/[^0-9]/', '', (string) $rawPaperId); $generateDateTime = now()->format('Y年m月d日 H:i:s'); $scoreObtained = $summary['score_obtained'] ?? null; $scoreTotal = $summary['score_total'] ?? null; $scoreRate = $summary['score_rate'] ?? null; $averageMastery = $summary['average_mastery'] ?? null; $overallLabel = $summary['overall_label'] ?? '待评估'; $difficultySummary = $summary['difficulty'] ?? []; $comparisonSummary = $summary['comparison'] ?? []; $overallLabelDetail = $summary['overall_label_detail'] ?? []; $historySummary = $comparisonSummary['history'] ?? []; $peerSummary = $comparisonSummary['peers'] ?? []; $overallScore = isset($overallLabelDetail['composite_score']) ? (float) $overallLabelDetail['composite_score'] : null; $overallGrade = (string) ($overallLabelDetail['grade'] ?? 'D'); $currentPart = (float) ($overallLabelDetail['current_score'] ?? 0); $historyPart = (float) ($overallLabelDetail['history_score'] ?? 0); $peerPart = (float) ($overallLabelDetail['peer_score'] ?? 0); $adjustPart = (float) ($overallLabelDetail['difficulty_adjust'] ?? 0); $compositeFormulaResult = (0.50 * $currentPart) + (0.25 * $historyPart) + (0.25 * $peerPart) + $adjustPart; $overallBadge = function (string $grade): array { return match ($grade) { 'S' => ['bg' => '#f5f3ff', 'border' => '#6d28d9', 'text' => '#6d28d9', 'class' => 'badge-s'], 'A' => ['bg' => '#ecfdf3', 'border' => '#22c55e', 'text' => '#166534', 'class' => 'badge-excellent'], 'B' => ['bg' => '#eff6ff', 'border' => '#3b82f6', 'text' => '#1d4ed8', 'class' => 'badge-good'], 'C' => ['bg' => '#fff7ed', 'border' => '#f59e0b', 'text' => '#b45309', 'class' => 'badge-average'], default => ['bg' => '#fef2f2', 'border' => '#ef4444', 'text' => '#b91c1c', 'class' => 'badge-weak'], }; }; $overallVisual = $overallBadge((string) $overallGrade); $trendVisual = function (string $trend): array { return match ($trend) { '显著提升' => ['icon' => '▲', 'color' => '#16a34a'], '小幅提升' => ['icon' => '↗', 'color' => '#0ea5e9'], '基本持平' => ['icon' => '•', 'color' => '#64748b'], '小幅回落' => ['icon' => '↘', 'color' => '#f59e0b'], '明显回落' => ['icon' => '▼', 'color' => '#ef4444'], default => ['icon' => '•', 'color' => '#64748b'], }; }; $statusColor = function (string $status): string { return match ($status) { '良好' => '#16a34a', '一般' => '#f97316', '薄弱' => '#e11d48', default => '#64748b', }; }; $n = max(1, count($radar)); $cx = 210; $cy = 155; $r = 108; $outer = []; $inner = []; for ($i = 0; $i < $n; $i++) { $angle = -M_PI / 2 + (2 * M_PI * $i / $n); $ox = $cx + $r * cos($angle); $oy = $cy + $r * sin($angle); $outer[] = [$ox, $oy]; $value = isset($radar[$i]['value']) ? (float) $radar[$i]['value'] : 0.0; $ratio = max(0.0, min(1.0, $value / 5)); $ix = $cx + $r * $ratio * cos($angle); $iy = $cy + $r * $ratio * sin($angle); $inner[] = [$ix, $iy]; } $outerPoints = implode(' ', array_map(fn ($p) => round($p[0], 2).','.round($p[1], 2), $outer)); $innerPoints = implode(' ', array_map(fn ($p) => round($p[0], 2).','.round($p[1], 2), $inner)); $insightMap = []; foreach (($question_insights ?? []) as $insight) { $no = $insight['question_number'] ?? $insight['question_id'] ?? null; if ($no !== null) { $insightMap[$no] = $insight; } } $analysisWrongMap = []; foreach (($analysis_data['question_analysis'] ?? []) as $qa) { $qid = $qa['question_bank_id'] ?? $qa['question_id'] ?? null; if ($qid === null || $qid === '') { continue; } $rawCorrect = $qa['is_correct'] ?? null; $isWrongFromAnalysis = false; if (is_array($rawCorrect)) { $isWrongFromAnalysis = in_array(0, $rawCorrect, true); } elseif ($rawCorrect !== null) { $isWrongFromAnalysis = !boolval($rawCorrect); } if ($isWrongFromAnalysis) { $analysisWrongMap[(string) $qid] = true; } } $wrongQuestions = []; foreach (($questions ?? []) as $qItem) { $isCorrectProbe = $qItem['is_correct'] ?? null; $studentAnswerProbe = $qItem['student_answer'] ?? null; $correctAnswerProbe = $qItem['answer'] ?? ($qItem['correct_answer'] ?? null); if ($isCorrectProbe === null && !empty($studentAnswerProbe) && !empty($correctAnswerProbe)) { $isCorrectProbe = (trim((string) $studentAnswerProbe) === trim((string) $correctAnswerProbe)) ? 1 : 0; } $normalizedCorrect = $isCorrectProbe; if ($isCorrectProbe !== null) { $normalizedCorrect = is_bool($isCorrectProbe) ? ($isCorrectProbe ? 1 : 0) : intval($isCorrectProbe); } $qidProbe = (string) ($qItem['question_bank_id'] ?? $qItem['question_id'] ?? ''); $isWrongByAnalysis = ($qidProbe !== '' && isset($analysisWrongMap[$qidProbe])); if ($normalizedCorrect === 0 || $isWrongByAnalysis) { $wrongQuestions[] = $qItem; } } $kpStats = []; foreach (($questions ?? []) as $qItem) { $kpName = trim((string) ($qItem['knowledge_point_name'] ?? $qItem['knowledge_point'] ?? '未标注知识点')); $kpName = $kpName === '' ? '未标注知识点' : $kpName; if (!isset($kpStats[$kpName])) { $kpStats[$kpName] = ['total' => 0, 'wrong' => 0]; } $kpStats[$kpName]['total']++; } foreach ($wrongQuestions as $qItem) { $kpName = trim((string) ($qItem['knowledge_point_name'] ?? $qItem['knowledge_point'] ?? '未标注知识点')); $kpName = $kpName === '' ? '未标注知识点' : $kpName; if (!isset($kpStats[$kpName])) { $kpStats[$kpName] = ['total' => 0, 'wrong' => 0]; } $kpStats[$kpName]['wrong']++; } $kpWrongStats = []; foreach ($kpStats as $kpName => $stat) { if (($stat['wrong'] ?? 0) <= 0) { continue; } $total = max(1, intval($stat['total'] ?? 0)); $wrong = intval($stat['wrong'] ?? 0); $kpWrongStats[] = [ 'kp_name' => $kpName, 'wrong' => $wrong, 'total' => $total, 'rate' => $wrong / $total, ]; } usort($kpWrongStats, function ($a, $b) { if ($a['rate'] === $b['rate']) { return $b['wrong'] <=> $a['wrong']; } return $b['rate'] <=> $a['rate']; }); @endphp 学情分析报告

学情分析报告

一、总体评估
{{ $overallGrade }}
  • 本次诊断得分: @if($scoreObtained !== null && $scoreTotal !== null && $scoreTotal > 0) {{ rtrim(rtrim(number_format((float) $scoreObtained, 1), '0'), '.') }}/{{ rtrim(rtrim(number_format((float) $scoreTotal, 1), '0'), '.') }} @else 暂无得分数据 @endif
  • 得分率:{{ $scoreRate !== null ? number_format((float) $scoreRate * 100, 1) . '%' : '暂无得分率' }}
  • 平均掌握度:{{ $averageMastery !== null ? number_format((float) $averageMastery * 100, 1) . '%' : '暂无掌握度' }}
  • 难度匹配: @if(!empty($difficultySummary['target_label']) && isset($difficultySummary['actual_average_difficulty'])) 目标 {{ $difficultySummary['target_label'] }} @if(!empty($difficultySummary['target_range'])) ({{ number_format((float)($difficultySummary['target_range']['min'] ?? 0), 2) }}~{{ number_format((float)($difficultySummary['target_range']['max'] ?? 0), 2) }}) @endif ,实际 {{ number_format((float)($difficultySummary['actual_average_difficulty'] ?? 0), 3) }} ({{ $difficultySummary['status'] ?? '暂无' }}) @else 暂无难度匹配数据 @endif
  • @if(!empty($difficultySummary['explain']))
  • 难度说明:{{ $difficultySummary['explain'] }}
  • @endif
  • 与历史自己对比: @if(!empty($historySummary['is_first_exam'])) {{ $historySummary['message'] ?? '这是你的第一次分析报告,先积累样本再看趋势。' }} @elseif(!empty($historySummary['low_baseline_guard'])) {{ $historySummary['message'] ?? '历史基线偏低,建议看连续趋势。' }} @elseif(!empty($historySummary['has_data'])) @php $trendText = (string)($historySummary['trend'] ?? '—'); $tVisual = $trendVisual($trendText); @endphp 近几次均值对比: {{ number_format((float)($historySummary['baseline_score_rate'] ?? 0) * 100, 1) }}%, 本次{{ ($historySummary['delta_score_rate'] ?? 0) >= 0 ? '提升' : '回落' }} {{ number_format(abs((float)($historySummary['delta_score_rate'] ?? 0)) * 100, 1) }}% ({{ $tVisual['icon'] ?? '•' }} {{ $trendText }}) @else {{ $historySummary['message'] ?? '历史样本不足' }} @endif
  • @if(!empty($peerSummary['show_line']))
  • 与同群体对比: {{ $peerSummary['message'] ?? '' }} ({{ $peerSummary['band_icon'] ?? '•' }} {{ $peerSummary['band'] ?? '—' }}
  • @endif
  • 整体水平: @if($overallScore !== null) {{ number_format($overallScore, 1) }} 分({{ $overallGrade }}) @else 待计算 @endif
规则:综合分 = 当前50% + 历史25% + 同群体25% + 难度校正,即:(({{ number_format($scoreRate !== null ? (float)$scoreRate * 100 : 0, 1) }}×70% + {{ number_format($averageMastery !== null ? (float)$averageMastery * 100 : 0, 1) }}×30%)×50%) + {{ number_format($historyPart, 1) }}×25% + {{ number_format($peerPart, 1) }}×25% + {{ number_format($adjustPart, 1) }} = {{ number_format($overallScore ?? $compositeFormulaResult, 1) }}
二、知识点掌握雷达图
@foreach($outer as $i => $p) @endforeach @foreach($outer as $i => $p) @php $name = $radar[$i]['name'] ?? ''; $children = $radar[$i]['children'] ?? []; $labelX = $p[0] + (($p[0] >= $cx) ? 9 : -9); $labelY = $p[1] + (($p[1] >= $cy) ? 12 : -8); $anchor = $p[0] >= $cx ? 'start' : 'end'; $dotColor = $statusColor((string) ($radar[$i]['status'] ?? '暂无')); $value = number_format((float) ($radar[$i]['value'] ?? 0), 2); $axisAngle = atan2(($p[1] - $cy), ($p[0] - $cx)); @endphp {{ $name }} {{ $value }} @if(!empty($children)) @foreach($children as $cIdx => $child) @php $childN = max(1, count($children)); $depth = max(1, intval($child['depth'] ?? 1)); $offset = ($cIdx - (($childN - 1) / 2)) * 0.04; $childAngle = $axisAngle + $offset; // 子知识点必须从父轴外圈向外发散,避免父轴值低时挤在中心 $axisOuterR = sqrt(pow(($outer[$i][0] - $cx), 2) + pow(($outer[$i][1] - $cy), 2)); $startR = max($axisOuterR + 4, 112 + (($depth - 1) * 10)); $endR = $startR + 12 + (($depth - 1) * 4); $sx = $cx + $startR * cos($childAngle); $sy = $cy + $startR * sin($childAngle); $ex = $cx + $endR * cos($childAngle); $ey = $cy + $endR * sin($childAngle); $changed = !empty($child['changed']); $cColor = $changed ? '#e11d48' : '#94a3b8'; $cWidth = $changed ? 1.5 : 0.8; $masteryPct = isset($child['mastery_level']) ? max(0, min(100, (float) $child['mastery_level'] * 100)) : null; $label = $masteryPct !== null ? number_format($masteryPct, 1) . '%' : '—'; $tx = $cx + ($endR + 5 + (($depth - 1) * 2)) * cos($childAngle); $ty = $cy + ($endR + 5 + (($depth - 1) * 2)) * sin($childAngle); $anchor = cos($childAngle) >= 0 ? 'start' : 'end'; @endphp {{ $label }} @endforeach @endif @endforeach
良好(4.0-5.0) 一般(2.0-3.9) 薄弱(0-1.9) 未涉及 子知识点 子知识点变化 外圈越远表示层级越深
雷达图解读 @foreach($radar as $item) @php $color = $statusColor((string) ($item['status'] ?? '暂无')); @endphp {{ $item['name'] }}:{{ $item['status'] }} ({{ !empty($item['has_mastery']) ? number_format((float) ($item['value'] ?? 0), 2) . '/5' : '—' }}) @if(!empty($item['children'])) ,子知识点 {{ count($item['children']) }} 个 @endif @endforeach
三、模块能力分析表
@foreach($modules as $m) @php $status = (string) ($m['status'] ?? '暂无'); $color = $statusColor($status); $rate = $m['exam_score_rate'] ?? null; @endphp @endforeach
模块 掌握分值 掌握状态 样本数 得分率 学生当前能力
{{ $m['module_name'] ?? '-' }} {{ $m['mastery_score_5'] !== null ? number_format((float) $m['mastery_score_5'], 2) . '/5' : '-' }} {{ $status }} {{ $m['kp_count'] ?? 0 }} {{ $rate !== null ? number_format((float) $rate * 100, 1) . '%' : '-' }} {{ $m['ability_text'] ?? '-' }}
四、分模块提分路径
保分模块(保持优势)
    @foreach(($paths['keep'] ?? []) as $item)
  • {{ $item['name'] }}:掌握度 {{ number_format((float) ($item['mastery_level'] ?? 0) * 100, 1) }}%
  • @endforeach
@if(empty($paths['keep']))
暂无数据
@endif
涨分模块(重点突破)
    @foreach(($paths['boost'] ?? []) as $item)
  • {{ $item['name'] }}:掌握度 {{ number_format((float) ($item['mastery_level'] ?? 0) * 100, 1) }}%
  • @endforeach
@if(empty($paths['boost']))
暂无数据
@endif
提分关键(优先补短)
    @foreach(($paths['key'] ?? []) as $item)
  • {{ $item['name'] }}:掌握度 {{ number_format((float) ($item['mastery_level'] ?? 0) * 100, 1) }}%
  • @endforeach
@if(empty($paths['key']))
暂无数据
@endif
五、整体提升方案
    @foreach($overallPlan as $line)
  1. {{ $line }}
  2. @endforeach
@if(!empty($wrongQuestions))
六、这次错题记录
@if(!empty($kpWrongStats))
知识点错误率
@foreach($kpWrongStats as $item) {{ $item['kp_name'] }}:{{ $item['wrong'] }}/{{ $item['total'] }}({{ number_format($item['rate'] * 100, 1) }}%) @endforeach
@endif @foreach($wrongQuestions as $q) @php $studentAnswer = $q['student_answer'] ?? null; $correctAnswer = $q['answer'] ?? $q['correct_answer'] ?? null; $isCorrect = $q['is_correct'] ?? null; if ($isCorrect === null && !empty($studentAnswer) && !empty($correctAnswer)) { $isCorrect = (trim($studentAnswer) === trim($correctAnswer)) ? 1 : 0; } $statusText = ''; $statusColorValue = ''; if ($isCorrect === 1) { $statusText = '正确'; $statusColorValue = '#10b981'; } elseif ($isCorrect === 0) { $statusText = '错误'; $statusColorValue = '#ef4444'; } $showStatus = $statusText !== ''; $insight = $insightMap[$q['question_number']] ?? ($insightMap[$q['display_number'] ?? null] ?? []); $fullScore = $insight['full_score'] ?? ($q['score'] ?? null); if ($isCorrect === 1) { $score = $fullScore; } elseif ($isCorrect === 0) { $score = $q['score_obtained'] ?? 0; } else { $score = null; } $analysisRaw = $insight['analysis'] ?? $insight['thinking_process'] ?? $insight['feedback'] ?? $insight['suggestions'] ?? $insight['reason'] ?? ($insight['correct_solution'] ?? null); if (empty($analysisRaw) && !empty($insight['next_steps'])) { $analysisRaw = '后续建议:' . (is_array($insight['next_steps']) ? implode(';', $insight['next_steps']) : $insight['next_steps']); } $analysis = is_array($analysisRaw) ? json_encode($analysisRaw, JSON_UNESCAPED_UNICODE) : $analysisRaw; if ($analysis === null || $analysis === '') { $analysis = '暂无解题思路,待补充'; } if (is_string($analysis)) { $analysis = preg_replace('/^【?\s*解题思路\s*】?\s*[::]?\s*/u', '', $analysis); } $formatSolutionLikeGrading = function ($text) { if (!is_string($text) || trim($text) === '') { return $text; } $normalized = preg_replace('/\s*;\s*步骤\s*(\d+)/u', ";\n步骤$1", $text); $normalized = preg_replace('/\s*。\s*步骤\s*(\d+)/u', "。\n步骤$1", $normalized); $normalized = preg_replace('/(? '选择题', 'fill' => '填空题', 'answer' => '解答题']; $typeLabel = $typeMap[$q['question_type'] ?? ''] ?? ($q['question_type'] ?? '题型未标注'); $questionText = is_string($q['question_text']) ? $q['question_text'] : json_encode($q['question_text'], JSON_UNESCAPED_UNICODE); $solution = $q['solution'] ?? null; if (is_string($solution)) { $solution = preg_replace('/^【?\s*解题思路\s*】?\s*[::]?\s*/u', '', $solution); } $solution = $formatSolutionLikeGrading($solution); $analysis = $formatSolutionLikeGrading($analysis); $renderLikeGrading = function ($text) { if (is_array($text)) { $text = json_encode($text, JSON_UNESCAPED_UNICODE); } $text = is_string($text) ? trim($text) : ''; if ($text === '') { return ''; } // 兼容题库里常见的转义写法:\$x\$、\$\frac{...}\$ $text = preg_replace('/\\\\\\$/u', '$', $text); return \App\Services\MathFormulaProcessor::processFormulas($text); }; $questionTextRendered = $renderLikeGrading($questionText); $displayCorrectAnswer = is_array($correctAnswer) ? json_encode($correctAnswer, JSON_UNESCAPED_UNICODE) : (string) $correctAnswer; $questionTypeRaw = strtolower(trim((string) ($q['question_type'] ?? ''))); $isChoiceQuestion = in_array($questionTypeRaw, ['choice', 'multiple_choice', 'single_choice', '选择题', 'select'], true); $normalizedOptions = []; $correctAnswerLetters = []; if ($isChoiceQuestion) { $rawOptions = $q['options'] ?? []; if (is_string($rawOptions)) { $decodedOptions = json_decode($rawOptions, true); $rawOptions = is_array($decodedOptions) ? $decodedOptions : []; } if (is_array($rawOptions)) { foreach ($rawOptions as $optKey => $optValue) { $letter = null; if (is_string($optKey) && preg_match('/([A-H])/i', $optKey, $m)) { $letter = strtoupper($m[1]); } elseif (is_array($optValue)) { $candidate = $optValue['label'] ?? $optValue['key'] ?? $optValue['option'] ?? null; if (is_string($candidate) && preg_match('/([A-H])/i', $candidate, $m)) { $letter = strtoupper($m[1]); } } if ($letter === null) { continue; } $content = is_array($optValue) ? ($optValue['content'] ?? $optValue['text'] ?? $optValue['value'] ?? '') : $optValue; if (!is_string($content)) { $content = json_encode($content, JSON_UNESCAPED_UNICODE); } $content = trim((string) $content); if ($content !== '') { $normalizedOptions[$letter] = $content; } } } if (trim((string) $correctAnswer) !== '') { preg_match_all('/[A-H]/i', strtoupper((string) $correctAnswer), $answerMatches); $correctAnswerLetters = array_values(array_unique($answerMatches[0] ?? [])); } if (!empty($normalizedOptions) && !empty($correctAnswerLetters)) { $mappedAnswers = []; foreach ($correctAnswerLetters as $letter) { if (isset($normalizedOptions[$letter])) { $mappedAnswers[] = $letter . '. ' . $normalizedOptions[$letter]; } } if (!empty($mappedAnswers)) { $displayCorrectAnswer = implode(';', $mappedAnswers); } } } $choiceOptionLetters = !empty($normalizedOptions) ? array_keys($normalizedOptions) : []; sort($choiceOptionLetters); $choiceLayoutClass = 'options-grid-1'; $layoutDecider = app(\App\Support\OptionLayoutDecider::class); if (! empty($normalizedOptions) && ! empty($choiceOptionLetters)) { $optValuesForLayout = []; foreach ($choiceOptionLetters as $L) { $optValuesForLayout[] = $normalizedOptions[$L]; } $layoutMeta = $layoutDecider->decide($optValuesForLayout, 'grading'); $choiceLayoutClass = $layoutMeta['class'] ?? 'options-grid-1'; } @endphp
题号 {{ $q['display_number'] ?? $q['question_number'] }} · {{ $typeLabel }} @php $kpName = $q['knowledge_point_name'] ?? $q['knowledge_point'] ?? null; if (!empty($kpName) && $kpName !== '-' && $kpName !== '未标注') { echo '' . e($kpName) . ''; } @endphp @if($showStatus) {{ $statusText }} @endif
@if($score !== null && $fullScore !== null)
得分 {{ $score }} / {{ $fullScore }}
@endif
{!! $questionTextRendered !!}
@if(!empty($isChoiceQuestion) && !empty($normalizedOptions))
@foreach($choiceOptionLetters as $optLetter) @php $isCorrectOpt = in_array($optLetter, $correctAnswerLetters ?? [], true); $rawOpt = (string) ($normalizedOptions[$optLetter] ?? ''); $normalizedOpt = str_replace('\\dfrac', '\\frac', $rawOpt); $normalizedOpt = str_replace('\\displaystyle', '', $normalizedOpt); $normalizedOpt = $layoutDecider->normalizeCompactMathForDisplay($normalizedOpt); $rawOptPlain = html_entity_decode(strip_tags($rawOpt), ENT_QUOTES | ENT_HTML5, 'UTF-8'); $rawOptPlain = preg_replace('/\s+/u', '', $rawOptPlain ?? ''); $isShortOption = mb_strlen((string) $rawOptPlain, 'UTF-8') <= 8; $valClass = $isShortOption ? 'option-short' : 'option-long'; $renderedOpt = $renderLikeGrading($normalizedOpt); @endphp
{{ $optLetter }}. {!! $renderedOpt !!} @if($isCorrectOpt) @endif
@endforeach
@endif @if(!empty($correctAnswer) && (!$isChoiceQuestion || empty($normalizedOptions)))
正确答案
{!! $renderLikeGrading($displayCorrectAnswer) !!}
@endif @if(!empty($solution))
解题思路: {!! $renderLikeGrading($solution) !!}
@elseif(!empty($analysis) && $analysis !== '暂无解题思路记录')
解题思路: {!! $renderLikeGrading($analysis) !!}
@endif @if(!empty($steps))
解题步骤
    @foreach($steps as $s) @php $stepText = is_array($s) ? json_encode($s, JSON_UNESCAPED_UNICODE) : (string) $s; @endphp
  1. {!! nl2br($renderLikeGrading($stepText)) !!}
  2. @endforeach
@endif
@endforeach
@endif