151 lines
9.3 KiB
PHP
151 lines
9.3 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/** php server/tests/RevisitCohortCalculatorTest.php:日期队列 / 继承重算,不连接数据库。 */
|
||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||
|
||
use app\adminapi\logic\stats\RevisitCohortCalculator as C;
|
||
use app\adminapi\logic\stats\RevisitRateCalculator as Legacy;
|
||
|
||
date_default_timezone_set('Asia/Shanghai');
|
||
$checks = 0;
|
||
function expect(bool $condition, string $message): void
|
||
{
|
||
global $checks;
|
||
if (!$condition) {
|
||
fwrite(STDERR, "FAIL: {$message}\n");
|
||
exit(1);
|
||
}
|
||
$checks++;
|
||
}
|
||
$t = static fn (string $date): int => (int) strtotime($date);
|
||
$logId = 0;
|
||
$orderId = 0;
|
||
$log = static function (int $did, int $from, int $to, string $date, int $inherit = 0) use (&$logId, $t): array {
|
||
return ['id' => ++$logId, 'diagnosis_id' => $did, 'from_assistant_id' => $from,
|
||
'to_assistant_id' => $to, 'create_time' => $t($date), 'is_inherit' => $inherit, 'operator_admin_id' => 7];
|
||
};
|
||
$order = static function (int $did, string $date) use (&$orderId, $t): array {
|
||
return ['id' => ++$orderId, 'diagnosis_id' => $did, 'create_time' => $t($date),
|
||
'order_no' => 'TEST-' . $orderId, 'amount' => 100, 'creator_id' => 1];
|
||
};
|
||
$sort = static function (array $rows): array {
|
||
usort($rows, static fn (array $a, array $b): int =>
|
||
[$a['diagnosis_id'], $a['create_time'], $a['id']] <=> [$b['diagnosis_id'], $b['create_time'], $b['id']]);
|
||
return $rows;
|
||
};
|
||
$start = $t('2026-09-01 00:00:00');
|
||
$end = $t('2026-10-30 23:59:59');
|
||
$now = $t('2026-12-01 12:00:00');
|
||
$run = static fn (array $logs, array $orders, string $type, array $offsets = [], array $reject = [], array $rollback = []): array =>
|
||
C::compute($sort($logs), $sort($orders), $offsets, $reject, $rollback, $start, $end, $now, $type);
|
||
$pack = static fn (array $calc, array $aids): array => C::metricPack($calc, $aids, [2, 3, 4]);
|
||
|
||
// 新指派筛选的是指派日期:周期前指派的患者不混入分子,周期后的复诊累计至查询当天。
|
||
$logs = [
|
||
$log(10, 0, 1, '2026-09-01 00:00:00'),
|
||
$log(11, 0, 1, '2026-08-31 23:59:59'),
|
||
$log(12, 0, 1, '2026-10-30 23:59:59'),
|
||
$log(13, 0, 1, '2026-10-31 00:00:00'),
|
||
];
|
||
$orders = [
|
||
$order(10, '2026-09-02'), $order(10, '2026-11-15'), $order(10, '2026-12-01 12:00:00'), $order(10, '2026-12-02'),
|
||
$order(11, '2026-09-03'), $order(11, '2026-10-01'),
|
||
$order(12, '2026-11-01'), $order(12, '2026-11-20'),
|
||
$order(13, '2026-11-01'), $order(13, '2026-11-20'),
|
||
];
|
||
$calc = $run($logs, $orders, 'new');
|
||
$p = $pack($calc, [1]);
|
||
expect($p['assigned_count'] === 2 && $p['inherit_count'] === 0, '范围两端均包含,前后一天均排除');
|
||
expect($p['visit2_count'] === 2 && $p['visit2_rate'] === 100.0, '11 月复诊计入9~10月指派队列');
|
||
expect($p['visit3_count'] === 1 && $p['visit4_count'] === 0, '查询同刻订单包含,未来订单不计入');
|
||
expect($p['visit_all_count'] === 3 && $p['visit_all_rate'] === 150.0, '合计是人均复诊单次,可以超过100%');
|
||
expect(count(C::scopedOrders($calc, [1], 2)[2]) === $p['visit2_count'], '二诊明细数量等于二诊分子');
|
||
expect($pack($calc, [99])['visit2_rate'] === null, '无患者分母为0,率为null');
|
||
|
||
// 继承前有三笔订单和偏移:继承后首单仍算二诊,原医助只领取转出前订单。
|
||
$logs = [$log(20, 0, 1, '2026-09-01'), $log(20, 1, 2, '2026-10-10 10:00:00', 1)];
|
||
$orders = [$order(20, '2026-09-02'), $order(20, '2026-09-03'), $order(20, '2026-09-04'),
|
||
$order(20, '2026-10-10 10:00:00'), $order(20, '2026-11-20')];
|
||
$new = $run($logs, $orders, 'new', [20 => 5]);
|
||
$inherited = $run($logs, $orders, 'inherit', [20 => 5]);
|
||
expect(array_keys($new['pairsByAssistant']) === [1] && array_keys($inherited['pairsByAssistant']) === [2], '新指派、继承独立入组');
|
||
expect(!isset($new['ordersByAssistant'][2]), '继承订单不得混入新指派分子');
|
||
$p = $pack($inherited, [2]);
|
||
expect($p['assigned_count'] === 0 && $p['inherit_count'] === 1 && $p['visit2_count'] === 1, '继承首单计二诊');
|
||
expect($p['visit3_count'] === 1 && $p['visit2_den'] === 1 && $p['visit3_den'] === 1, '继承次单计三诊,各列分母只含继承患者');
|
||
$first = C::scopedOrders($inherited, [2], 2)[2][20];
|
||
expect($first['create_time'] === $t('2026-10-10 10:00:00') && $first['global_visit_slot'] === 9, '同秒归新医助且不受全局诊次/偏移影响');
|
||
expect($first['cohort_assign_time'] === $t('2026-10-10 10:00:00'), '订单明细带实际承接时间');
|
||
expect($pack($new, [1])['visit_all_count'] === 3 && !isset($new['ordersByAssistant'][1][9]), '新指派保留全局诊次偏移,只计转出前3单');
|
||
|
||
// 转派、释放、再次继承、同一医助既新指派又继承:不混算,继承每段重新起算。
|
||
$logs = [
|
||
$log(30, 0, 1, '2026-09-01'),
|
||
$log(30, 1, 2, '2026-09-10', 1),
|
||
$log(30, 2, 2, '2026-09-11', 1), // 自指派忽略,不重置
|
||
$log(30, 2, 0, '2026-09-15'),
|
||
$log(30, 0, 2, '2026-09-20', 1),
|
||
$log(30, 2, 1, '2026-10-01', 1),
|
||
];
|
||
$orders = [$order(30, '2026-09-02'), $order(30, '2026-09-10'), $order(30, '2026-09-12'),
|
||
$order(30, '2026-09-16'), $order(30, '2026-09-21'), $order(30, '2026-09-22'),
|
||
$order(30, '2026-10-02')];
|
||
$calc = $run($logs, $orders, 'inherit');
|
||
expect($calc['pairsByAssistant'][2][30]['assign_count'] === 2, '多次真实继承计次数,自指派不计');
|
||
expect($calc['pairsByAssistant'][1][30]['assign_count'] === 1, '同一医助既新指派又继承,继承独立统计');
|
||
$p2 = $pack($calc, [2]);
|
||
expect($p2['inherit_count'] === 1 && $p2['visit2_count'] === 1 && $p2['visit3_count'] === 1, '重复继承患者分母、同一诊次分子均去重');
|
||
expect(C::scopedOrders($calc, [2], 3)[3][30]['create_time'] === $t('2026-09-12'), '自指派不把后续订单重置到二诊');
|
||
$total = $pack($calc, [1, 2]);
|
||
expect($total['inherit_count'] === 1 && $total['visit2_count'] === 1 && $total['visit2_rate'] === 100.0, '部门/合计按患者去重,不能把多医助同患者二诊重复相加');
|
||
expect($pack($calc, [1])['visit3_count'] === 0, '按医助筛选时不能混入另一医助三诊');
|
||
expect($total['visit_all_count'] === array_sum(array_map('count', C::scopedOrders($calc, [1, 2]))), '合计订单明细与去重分子逐项对账');
|
||
|
||
// 周期后重新继承的订单不应继续计入旧队列;释放也会终止归属。
|
||
$logs = [$log(40, 0, 1, '2026-09-01', 1), $log(40, 1, 0, '2026-10-01'),
|
||
$log(40, 0, 1, '2026-11-01', 1)];
|
||
$calc = $run($logs, [$order(40, '2026-09-02'), $order(40, '2026-10-02'), $order(40, '2026-11-02')], 'inherit');
|
||
expect($pack($calc, [1])['visit_all_count'] === 1, '释放后及区间外再次承接的订单不归旧队列');
|
||
|
||
// 写入侧从待分配重新指派给原医助时会回填 from,不能误判为自指派。
|
||
foreach (['new' => 0, 'inherit' => 1] as $type => $isInherit) {
|
||
$logs = [$log(41, 0, 1, '2026-08-01'), $log(41, 1, 0, '2026-08-10'),
|
||
$log(41, 1, 1, '2026-09-01', $isInherit)];
|
||
$calc = $run($logs, [$order(41, '2026-08-02'), $order(41, '2026-09-02')], $type);
|
||
expect($pack($calc, [1])['assigned_total_count'] === 1 && $pack($calc, [1])['visit2_count'] === 1,
|
||
'释放后同医助重新承接正常入组并统计:' . $type);
|
||
}
|
||
|
||
// 回退 is_inherit=0,通过回退键识别继承;拒收/退款剔除同时作用于分子、分母。
|
||
$rollbackTime = $t('2026-09-15');
|
||
$logs = [$log(50, 0, 1, '2026-09-01'), $log(50, 1, 2, '2026-09-15')];
|
||
$orders = [$order(50, '2026-09-02'), $order(50, '2026-09-16')];
|
||
$keys = [Legacy::rollbackKey(50, $rollbackTime, 7) => true];
|
||
expect(!isset($run($logs, $orders, 'new', [], [], $keys)['pairsByAssistant'][2]), '历史回退不能计入新指派');
|
||
expect($pack($run($logs, $orders, 'inherit', [], [], $keys), [2])['visit2_count'] === 1, '历史回退首单按继承二诊');
|
||
$rejected = $run($logs, $orders, 'inherit', [], [50 => true], $keys);
|
||
expect($rejected['pairsByAssistant'] === [] && $rejected['ordersByAssistant'] === [], '拒收/退款诊单完全剔除,无孤立分子');
|
||
|
||
// 同秒多次转派按日志 id 升序,订单最终归最新承接事件;两笔同秒有效订单按 id 编号。
|
||
$logs = [$log(60, 0, 1, '2026-09-01'), $log(60, 1, 2, '2026-09-10', 1), $log(60, 2, 3, '2026-09-10', 1)];
|
||
$calc = $run($logs, [$order(60, '2026-09-10'), $order(60, '2026-09-10')], 'inherit');
|
||
expect($pack($calc, [2])['visit_all_count'] === 0 && $pack($calc, [3])['visit_all_count'] === 2, '同秒转派和订单按稳定顺序计算');
|
||
|
||
// 日期解析支持跨月与未来结束日,不把月末或非法日期自动纠正。
|
||
[$s, $e, $sTs, $eTs] = C::dateRange(['start_date' => '2026-09-01', 'end_date' => '2026-10-30'], $t('2026-10-16'));
|
||
expect($s === '2026-09-01' && $e === '2026-10-30' && $sTs === $start && $eTs === $end, '跨月范围保留未来结束日');
|
||
expect(array_slice(C::dateRange([], $t('2026-10-16')), 0, 2) === ['2026-10-01', '2026-10-16'], '默认当月首日至当天');
|
||
expect(array_slice(C::dateRange(['month' => '2024-02'], $now), 0, 2) === ['2024-02-01', '2024-02-29'], '兼容旧 month 参数和闰月');
|
||
foreach ([['2026-02-30', '2026-03-01'], ['2026-09-10', '2026-09-01'], ['', '2026-10-30'], ['2026-9-01', '2026-10-30']] as [$s, $e]) {
|
||
$threw = false;
|
||
try {
|
||
C::dateRange(['start_date' => $s, 'end_date' => $e], $now);
|
||
} catch (InvalidArgumentException) {
|
||
$threw = true;
|
||
}
|
||
expect($threw, '拒绝非法/缺失/倒置日期:' . $s . ' - ' . $e);
|
||
}
|
||
echo "RevisitCohortCalculatorTest OK ({$checks} checks)\n";
|