|
|
@@ -516,6 +516,144 @@ echo '<div class="weekly-chart">';
|
|
|
echo $chartSvg;
|
|
|
echo "</div>\n\n";
|
|
|
|
|
|
+// 老师列表汇总:先给出增/降总体倾向,再给出异常清单
|
|
|
+$calcPct = static function (int $delta, int $prev): ?float {
|
|
|
+ if ($prev <= 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return round(($delta / $prev) * 100, 1);
|
|
|
+};
|
|
|
+
|
|
|
+$fmtDeltaWithPct = static function (int $delta, int $prev): string {
|
|
|
+ $sign = $delta > 0 ? '+' : '';
|
|
|
+ $pct = $prev > 0 ? sprintf('(%s%.1f%%)', $sign, ($delta / $prev) * 100) : '(上0)';
|
|
|
+ return $sign.$delta.$pct;
|
|
|
+};
|
|
|
+
|
|
|
+$paperUp = 0; $paperDown = 0; $paperFlat = 0;
|
|
|
+$analysisUp = 0; $analysisDown = 0; $analysisFlat = 0;
|
|
|
+$studentUp = 0; $studentDown = 0; $studentFlat = 0;
|
|
|
+$sumPaperDelta = 0; $sumAnalysisDelta = 0; $sumStudentDelta = 0;
|
|
|
+$anomalies = [];
|
|
|
+
|
|
|
+foreach ($rows as $r) {
|
|
|
+ $tidKey = (string) $r['teacher_id'];
|
|
|
+ $name = (string) $r['name'];
|
|
|
+ $papersCur = (int) $r['papers'];
|
|
|
+ $papersPrev = (int) $r['papers_prev'];
|
|
|
+ $analysisCur = (int) $r['analysis_sets'];
|
|
|
+ $analysisPrev = (int) $r['analysis_sets_prev'];
|
|
|
+ $studentCur = (int) ($studentUnionCurMap[$tidKey] ?? 0);
|
|
|
+ $studentPrev = (int) ($studentUnionPrevMap[$tidKey] ?? 0);
|
|
|
+
|
|
|
+ $paperDelta = $papersCur - $papersPrev;
|
|
|
+ $analysisDelta = $analysisCur - $analysisPrev;
|
|
|
+ $studentDelta = $studentCur - $studentPrev;
|
|
|
+ $sumPaperDelta += $paperDelta;
|
|
|
+ $sumAnalysisDelta += $analysisDelta;
|
|
|
+ $sumStudentDelta += $studentDelta;
|
|
|
+
|
|
|
+ if ($paperDelta > 0) { $paperUp++; } elseif ($paperDelta < 0) { $paperDown++; } else { $paperFlat++; }
|
|
|
+ if ($analysisDelta > 0) { $analysisUp++; } elseif ($analysisDelta < 0) { $analysisDown++; } else { $analysisFlat++; }
|
|
|
+ if ($studentDelta > 0) { $studentUp++; } elseif ($studentDelta < 0) { $studentDown++; } else { $studentFlat++; }
|
|
|
+
|
|
|
+ $paperPct = $calcPct($paperDelta, $papersPrev);
|
|
|
+ $analysisPct = $calcPct($analysisDelta, $analysisPrev);
|
|
|
+ $studentPct = $calcPct($studentDelta, $studentPrev);
|
|
|
+
|
|
|
+ // 异常口径(用于快速巡检):
|
|
|
+ // 1) 学案/学情明显下滑;2) 学案有量但学情为 0;3) 学情明显高于学案;
|
|
|
+ // 4) 学案与学情方向背离;5) 覆盖学生数异常波动。
|
|
|
+ if (
|
|
|
+ ($papersPrev >= 20 && ($paperDelta <= -20 || ($paperPct !== null && $paperPct <= -50.0))) ||
|
|
|
+ ($analysisPrev >= 15 && ($analysisDelta <= -15 || ($analysisPct !== null && $analysisPct <= -50.0))) ||
|
|
|
+ ($papersCur >= 10 && $analysisCur === 0) ||
|
|
|
+ ($analysisCur >= 10 && $analysisCur > $papersCur + 5) ||
|
|
|
+ (($paperDelta > 0 && $analysisDelta < 0 && abs($analysisDelta) >= 5) ||
|
|
|
+ ($paperDelta < 0 && $analysisDelta > 0 && abs($paperDelta) >= 5)) ||
|
|
|
+ (abs($studentDelta) >= 10 || ($studentPct !== null && abs($studentPct) >= 100.0 && $studentPrev >= 5))
|
|
|
+ ) {
|
|
|
+ $severity = abs($paperDelta) + abs($analysisDelta) + abs($studentDelta);
|
|
|
+ if ($papersCur >= 10 && $analysisCur === 0) { $severity += 20; }
|
|
|
+ if ($analysisCur >= 10 && $analysisCur > $papersCur + 5) { $severity += 12; }
|
|
|
+
|
|
|
+ $reason = [];
|
|
|
+ if ($papersPrev >= 20 && ($paperDelta <= -20 || ($paperPct !== null && $paperPct <= -50.0))) { $reason[] = '学案下滑'; }
|
|
|
+ if ($analysisPrev >= 15 && ($analysisDelta <= -15 || ($analysisPct !== null && $analysisPct <= -50.0))) { $reason[] = '学情下滑'; }
|
|
|
+ if ($papersCur >= 10 && $analysisCur === 0) { $reason[] = '学案有量但学情为0'; }
|
|
|
+ if ($analysisCur >= 10 && $analysisCur > $papersCur + 5) { $reason[] = '学情高于学案'; }
|
|
|
+ if (($paperDelta > 0 && $analysisDelta < 0) || ($paperDelta < 0 && $analysisDelta > 0)) { $reason[] = '学案学情背离'; }
|
|
|
+ if (abs($studentDelta) >= 10 || ($studentPct !== null && abs($studentPct) >= 100.0 && $studentPrev >= 5)) { $reason[] = '学生覆盖波动'; }
|
|
|
+
|
|
|
+ $anomalies[] = [
|
|
|
+ 'severity' => $severity,
|
|
|
+ 'teacher_id' => $tidKey,
|
|
|
+ 'name' => $name,
|
|
|
+ 'papers_cur' => $papersCur,
|
|
|
+ 'papers_prev' => $papersPrev,
|
|
|
+ 'analysis_cur' => $analysisCur,
|
|
|
+ 'analysis_prev' => $analysisPrev,
|
|
|
+ 'student_cur' => $studentCur,
|
|
|
+ 'student_prev' => $studentPrev,
|
|
|
+ 'paper_delta_text' => $fmtDeltaWithPct($paperDelta, $papersPrev),
|
|
|
+ 'analysis_delta_text' => $fmtDeltaWithPct($analysisDelta, $analysisPrev),
|
|
|
+ 'student_delta_text' => $fmtDeltaWithPct($studentDelta, $studentPrev),
|
|
|
+ 'reason' => implode(' / ', array_values(array_unique($reason))),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+usort($anomalies, static fn ($a, $b) => $b['severity'] <=> $a['severity']);
|
|
|
+
|
|
|
+$paperTrend = $paperUp > $paperDown ? '学案整体偏增长' : ($paperUp < $paperDown ? '学案整体偏下降' : '学案整体增降持平');
|
|
|
+$analysisTrend = $analysisUp > $analysisDown ? '学情整体偏增长' : ($analysisUp < $analysisDown ? '学情整体偏下降' : '学情整体增降持平');
|
|
|
+$studentTrend = $studentUp > $studentDown ? '学生覆盖整体偏增长' : ($studentUp < $studentDown ? '学生覆盖整体偏下降' : '学生覆盖整体增降持平');
|
|
|
+
|
|
|
+echo "### 老师列表汇总(增降对比)\n\n";
|
|
|
+echo "| 维度 | 增长老师数 | 下降老师数 | 持平老师数 | 判断 |\n";
|
|
|
+echo "| :--- | ---: | ---: | ---: | :--- |\n";
|
|
|
+echo sprintf("| 学案(组卷) | %d | %d | %d | %s |\n", $paperUp, $paperDown, $paperFlat, $paperTrend);
|
|
|
+echo sprintf("| 学情(分析) | %d | %d | %d | %s |\n", $analysisUp, $analysisDown, $analysisFlat, $analysisTrend);
|
|
|
+echo sprintf("| 学生覆盖(去重) | %d | %d | %d | %s |\n\n", $studentUp, $studentDown, $studentFlat, $studentTrend);
|
|
|
+
|
|
|
+echo "### 异常数据(学案本/上超过100,文字清单)\n\n";
|
|
|
+$highVolumeAnomalies = array_values(array_filter(
|
|
|
+ $anomalies,
|
|
|
+ static fn (array $a): bool => ((int) ($a['papers_cur'] ?? 0) > 100) || ((int) ($a['papers_prev'] ?? 0) > 100)
|
|
|
+));
|
|
|
+if ($highVolumeAnomalies === []) {
|
|
|
+ echo "本周期未命中“学案本/上超过100”的异常老师。\n\n";
|
|
|
+} else {
|
|
|
+ $display = array_slice($highVolumeAnomalies, 0, 30);
|
|
|
+ $idx = 1;
|
|
|
+ foreach ($display as $a) {
|
|
|
+ $nameEsc = htmlspecialchars((string) $a['name'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
|
+ $tidEsc = htmlspecialchars((string) $a['teacher_id'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
|
+ $reasonEsc = htmlspecialchars((string) $a['reason'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
|
+ $line = sprintf(
|
|
|
+ '%d) %s(%s):学案 %d/%d(%s);学情 %d/%d(%s);学生 %d/%d(%s);异常:%s',
|
|
|
+ $idx++,
|
|
|
+ $nameEsc,
|
|
|
+ $tidEsc,
|
|
|
+ (int) $a['papers_cur'],
|
|
|
+ (int) $a['papers_prev'],
|
|
|
+ (string) $a['paper_delta_text'],
|
|
|
+ (int) $a['analysis_cur'],
|
|
|
+ (int) $a['analysis_prev'],
|
|
|
+ (string) $a['analysis_delta_text'],
|
|
|
+ (int) $a['student_cur'],
|
|
|
+ (int) $a['student_prev'],
|
|
|
+ (string) $a['student_delta_text'],
|
|
|
+ $reasonEsc
|
|
|
+ );
|
|
|
+ echo '- '.$line."\n";
|
|
|
+ }
|
|
|
+ if (count($highVolumeAnomalies) > 30) {
|
|
|
+ echo sprintf("\n> 仅展示前 30 条,共 %d 条。\n", count($highVolumeAnomalies));
|
|
|
+ }
|
|
|
+ echo "\n";
|
|
|
+}
|
|
|
+
|
|
|
echo sprintf("### 按老师 本周期有组卷 **%d** 人。\n\n", count($rows));
|
|
|
|
|
|
echo '<table class="weekly-teacher-table">';
|