You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

297 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Console\Commands;
use App\Models\Course;
use App\Models\CourseSign;
use App\Models\CourseType;
use App\Models\CourseTypeDataOverviewConfig;
use App\Models\HistoryCourse;
use Illuminate\Console\Command;
/**
* 比对 home-v2 的 yearConfigs 与 courses-home 的 courseTypesSum
* 在指定时间段内 期数、课程数量、去重培养人数 的差异,并输出导致差异的 Course / HistoryCourse 明细。
*/
class DiffHomeV2CoursesHome extends Command
{
protected $signature = 'diff:home-v2-courses-home
{--start=2024-01-01 : 开始日期}
{--end=2027-01-01 : 结束日期}
{--config-id= : 可选,仅用该 yearConfig 的日期;不传则用 start/end}';
protected $description = '比对 home-v2(yearConfigs) 与 courses-home(courseTypesSum) 的期数、课程数量、去重培养人数差异';
protected $startDate;
protected $endDate;
public function handle()
{
$this->startDate = $this->option('start');
$this->endDate = $this->option('end');
$configId = $this->option('config-id');
if ($configId) {
$config = CourseTypeDataOverviewConfig::where('status', true)->find($configId);
if (!$config) {
$this->error("未找到 status=1 的 config id={$configId}");
return 1;
}
$this->startDate = $config->start_date;
$this->endDate = $config->end_date ?: date('Y-m-d', strtotime('+10 year'));
$this->info("使用 config#{$configId} 的日期: {$this->startDate} ~ {$this->endDate}");
} else {
$this->info("使用参数日期: {$this->startDate} ~ {$this->endDate}");
}
// 1) home-v2 逻辑:按 yearConfigs 的统计方式(只取 is_chart=1 & is_history=0 的 CourseType + 其他)
$homeV2 = $this->computeHomeV2($this->startDate, $this->endDate);
// 2) courses-home 逻辑courseTypesSum 的课程数量、以及涉及的 Course / HistoryCourse
$coursesHome = $this->computeCoursesHome($this->startDate, $this->endDate);
$this->printComparison($homeV2, $coursesHome);
$this->printDetailDiff($homeV2, $coursesHome);
return 0;
}
/**
* home-v2 的 yearConfigs 统计(单一段 startend
* 返回:期数、去重人数、涉及的 course_ids、history_course_ids
*/
protected function computeHomeV2(string $start, string $end): array
{
$courseIds = [];
$historyIds = [];
$periodsTotal = 0;
$signsUniqueSum = 0;
$allCourseTypes = CourseType::where('is_chart', 1)->where('is_history', 0)->orderBy('sort')->get();
foreach ($allCourseTypes as $ct) {
// 历史typeDetail name like且仅 type 为 is_history=0is_history=1 的由下方与 courses-home 口径一致的块统计)
$history = HistoryCourse::whereHas('typeDetail', function ($q) use ($ct) {
$q->where('name', 'like', '%' . $ct->name . '%')->where('is_history', 0);
})->where(function ($q) use ($start, $end) {
$q->whereBetween('start_time', [$start, $end])
->orWhereBetween('end_time', [$start, $end]);
})->get();
foreach ($history as $h) {
$historyIds[] = $h->id;
}
$historyPeriods = $history->count();
// 现在Course type=$ct->id, is_chart=1
$courses = Course::where('type', $ct->id)->where('is_chart', 1)
->where(function ($q) use ($start, $end) {
$q->whereBetween('start_date', [$start, $end])
->orWhereBetween('end_date', [$start, $end]);
})->get();
foreach ($courses as $c) {
$courseIds[] = $c->id;
}
$nowPeriods = $courses->count();
$periodsTotal += $historyPeriods + $nowPeriods;
$signsUniqueSum += $history->sum('course_type_signs_pass_unique')
+ CourseSign::courseSignsTotalByUnique($start, $end, 1, $courses->pluck('id'), false, false);
}
// 其他 is_chart=0
$other = CourseType::getOtherStatistics($start, $end);
$oIds = CourseType::getOtherCourseIds($start, $end);
$otherH = HistoryCourse::whereIn('type', CourseType::where('is_chart', 0)->where('is_history', 0)->pluck('id'))
->where(function ($q) use ($start, $end) {
$q->whereBetween('start_time', [$start, $end])->orWhereBetween('end_time', [$start, $end]);
})->get();
foreach ($otherH as $h) {
$historyIds[] = $h->id;
}
foreach (Course::whereIn('id', $oIds)->get() as $c) {
$courseIds[] = $c->id;
}
$periodsTotal += $other->course_periods_total;
$signsUniqueSum += $other->course_signs_total;
// getOtherStatistics 内部已含 history此处仅补 course idsgetOtherCourseIds 与 getOtherStatistics 的 Course 条件一致)
// 与 courses-home 口径一致:附加 is_history=1 的 HistoryCoursewhereHas calendar is_count_people=1type=体系 id
$courseTypesHistory = CourseType::where('is_history', 1)->get();
foreach ($courseTypesHistory as $hc) {
$historyList = HistoryCourse::whereHas('calendar', fn ($q) => $q->where('is_count_people', 1))
->where(fn ($q) => $q->whereBetween('start_time', [$start, $end])->orWhereBetween('end_time', [$start, $end]))
->where('type', $hc->id)
->get();
foreach ($historyList as $h) {
$historyIds[] = $h->id;
}
$periodsTotal += $historyList->count();
$signsUniqueSum += $historyList->sum('course_type_signs_pass_unique');
}
return [
'periods_total' => $periodsTotal,
'signs_unique_sum' => $signsUniqueSum,
'course_ids' => array_values(array_unique($courseIds)),
'history_ids' => array_values(array_unique($historyIds)),
];
}
/**
* courses-home 的 courseTypesSum按行展开的课程数量、去重按类型聚合的那列会重复这里只算 课程数量/期数)
* 返回:课程数量(行数)、涉及的 course_ids、history_course_ids
*/
protected function computeCoursesHome(string $start, string $end): array
{
$courseIds = [];
$historyIds = [];
$courseTypes = CourseType::whereIn('id', CourseType::pluck('id')->toArray())->get();
// 第一段:非历史 CourseType 下的 Course
foreach ($courseTypes as $ct) {
$courses2 = Course::where('type', $ct->id)
->where(function ($q) use ($start, $end) {
if ($start && $end) {
$q->whereBetween('start_date', [$start, $end])
->orWhereBetween('end_date', [$start, $end]);
}
})
->where('is_chart', 1)
->orderBy('start_date')
->get();
foreach ($courses2 as $c) {
$courseIds[] = $c->id;
}
}
// 第二段is_history=1 的 HistoryCourse与 courses-home 一致whereHas calendar is_count_people=1
$courseTypesHistory = CourseType::where('is_history', 1)->whereIn('id', CourseType::pluck('id')->toArray())->get();
foreach ($courseTypesHistory as $hc) {
$courses3 = HistoryCourse::whereHas('calendar', function ($q) {
$q->where('is_count_people', 1);
})
->where(function ($q) use ($start, $end) {
$q->whereBetween('start_time', [$start, $end])
->orWhereBetween('end_time', [$start, $end]);
})
->where('type', $hc->id)
->get();
foreach ($courses3 as $h) {
$historyIds[] = $h->id;
}
}
// 期数 = courseTypesSum 行数:每个 Course 一行,每个 HistoryCourse 一行
$periodsTotal = count($courseIds) + count($historyIds);
// 去重人数:用与 home-v2 同口径的「所有在 courseTypesSum 里的 course_id + history 对应类型」来算较复杂,
// 这里仅用 courses-home 实际用到的 Course 的 id 做 courseSignsTotalByUnique历史用 sum(course_type_signs_pass_unique)
$allCids = array_values(array_unique($courseIds));
$signsFromCourse = $allCids
? (int) CourseSign::courseSignsTotalByUnique($start, $end, 1, $allCids, false, false)
: 0;
$signsFromHistory = 0;
foreach ($courseTypesHistory as $hc) {
$list = HistoryCourse::whereHas('calendar', fn($q) => $q->where('is_count_people', 1))
->where(fn($q) => $q->whereBetween('start_time', [$start, $end])->orWhereBetween('end_time', [$start, $end]))
->where('type', $hc->id)
->get();
$signsFromHistory += $list->sum('course_type_signs_pass_unique');
}
$signsUniqueSum = $signsFromCourse + $signsFromHistory;
return [
'periods_total' => $periodsTotal,
'signs_unique_sum' => $signsUniqueSum,
'course_ids' => array_values(array_unique($courseIds)),
'history_ids' => array_values(array_unique($historyIds)),
];
}
protected function printComparison(array $homeV2, array $coursesHome): void
{
$this->line('');
$this->line('========== 汇总对比 ==========');
$this->table(
['指标', 'home-v2 (yearConfigs)', 'courses-home (courseTypesSum)', '差异 (courses-home - home-v2)'],
[
[
'期数/课程数量',
$homeV2['periods_total'],
$coursesHome['periods_total'],
$coursesHome['periods_total'] - $homeV2['periods_total'],
],
[
'去重培养人数',
$homeV2['signs_unique_sum'],
$coursesHome['signs_unique_sum'],
$coursesHome['signs_unique_sum'] - $homeV2['signs_unique_sum'],
],
[
'Course 数',
count($homeV2['course_ids']),
count($coursesHome['course_ids']),
count($coursesHome['course_ids']) - count($homeV2['course_ids']),
],
[
'HistoryCourse 数',
count($homeV2['history_ids']),
count($coursesHome['history_ids']),
count($coursesHome['history_ids']) - count($homeV2['history_ids']),
],
]
);
}
protected function printDetailDiff(array $homeV2, array $coursesHome): void
{
$this->line('');
$this->line('========== 明细差异(导致 courses-home 多出 期数 的条目) ==========');
$h2C = array_flip($homeV2['course_ids']);
$chC = array_flip($coursesHome['course_ids']);
$onlyInChC = array_keys(array_diff_key($chC, $h2C));
$onlyInH2C = array_keys(array_diff_key($h2C, $chC));
$h2H = array_flip($homeV2['history_ids']);
$chH = array_flip($coursesHome['history_ids']);
$onlyInChH = array_keys(array_diff_key($chH, $h2H));
$onlyInH2H = array_keys(array_diff_key($h2H, $chH));
if (!empty($onlyInChC)) {
$this->line('【仅 courses-home 有的 Course会多算期数】');
$rows = Course::whereIn('id', $onlyInChC)->get(['id', 'name', 'type', 'start_date', 'end_date', 'is_chart']);
$this->table(
['id', 'name', 'type', 'start_date', 'end_date', 'is_chart'],
$rows->map(fn($r) => [$r->id, $r->name, $r->type, $r->start_date, $r->end_date, $r->is_chart])->toArray()
);
$ctIds = $rows->pluck('type')->unique();
$cts = CourseType::whereIn('id', $ctIds)->get(['id', 'name', 'is_chart', 'is_history']);
$this->line('对应 CourseType: ' . $cts->map(fn($t) => "id={$t->id} name={$t->name} is_chart={$t->is_chart} is_history={$t->is_history}")->implode('; '));
}
if (!empty($onlyInChH)) {
$this->line('【仅 courses-home 有的 HistoryCourse会多算期数】');
$rows = HistoryCourse::whereIn('id', $onlyInChH)->get(['id', 'course_name', 'type', 'start_time', 'end_time', 'calendar_id']);
$this->table(
['id', 'course_name', 'type', 'start_time', 'end_time', 'calendar_id'],
$rows->map(fn($r) => [$r->id, $r->course_name, $r->type, $r->start_time, $r->end_time, $r->calendar_id])->toArray()
);
$ctIds = $rows->pluck('type')->unique();
$cts = CourseType::whereIn('id', $ctIds)->get(['id', 'name', 'is_chart', 'is_history']);
$this->line('对应 CourseType: ' . $cts->map(fn($t) => "id={$t->id} name={$t->name} is_chart={$t->is_chart} is_history={$t->is_history}")->implode('; '));
}
if (!empty($onlyInH2C)) {
$this->line('【仅 home-v2 有的 Course】');
$this->line(implode(', ', $onlyInH2C));
}
if (!empty($onlyInH2H)) {
$this->line('【仅 home-v2 有的 HistoryCourse】');
$this->line(implode(', ', $onlyInH2H));
}
if (empty($onlyInChC) && empty($onlyInChH) && empty($onlyInH2C) && empty($onlyInH2H)) {
$this->line('(无 ID 集合差异;若期数仍不一致,可能是同一条目在两个口径下 算/不算 的规则不同,例如 is_chart、is_count_people、日期区间、type 与 name 匹配等)');
}
}
}