(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";