|null */ private static $ruleCache; /** * 按稿件分节统计模版句出现次数 * * @param array $sectionTexts 键:abstract/introduction/methods/results/discussion/conclusion * @return array{total_matches:int,matched_rule_count:int,items:array,by_section:array} */ public function countInManuscript(array $sectionTexts): array { $rules = $this->loadActiveRules(); if (empty($rules)) { return [ 'total_matches' => 0, 'matched_rule_count' => 0, 'items' => [], 'by_section' => [], ]; } $normalizedSections = []; foreach ($sectionTexts as $key => $text) { $sectionKey = strtolower(trim((string) $key)); if ($sectionKey === '') { continue; } $normalizedSections[$sectionKey] = $this->normalizeText((string) $text); } $allText = $this->normalizeText(implode("\n\n", array_filter($normalizedSections))); $items = []; $bySection = []; foreach ($rules as $rule) { $ruleSection = strtolower(trim((string) ($rule['section'] ?? ''))); $pattern = trim((string) ($rule['sentence_pattern'] ?? '')); if ($pattern === '') { continue; } $targetText = ''; if ($ruleSection === 'all') { $targetText = $allText; } elseif (isset($normalizedSections[$ruleSection])) { $targetText = $normalizedSections[$ruleSection]; } else { continue; } if ($targetText === '') { continue; } $count = $this->countPatternMatches($targetText, $pattern); if ($count <= 0) { continue; } $item = [ 'id' => intval($rule['id'] ?? 0), 'section' => $ruleSection, 'sentence_pattern' => $pattern, 'count' => $count, 'risk_level' => intval($rule['risk_level'] ?? 0), 'weight' => intval($rule['weight'] ?? 0), 'remark' => (string) ($rule['remark'] ?? ''), ]; $items[] = $item; if (!isset($bySection[$ruleSection])) { $bySection[$ruleSection] = [ 'total_matches' => 0, 'matched_rule_count' => 0, 'items' => [], ]; } $bySection[$ruleSection]['items'][] = $item; $bySection[$ruleSection]['total_matches'] += $count; $bySection[$ruleSection]['matched_rule_count']++; } usort($items, function ($a, $b) { if ($a['count'] !== $b['count']) { return $b['count'] - $a['count']; } if ($a['weight'] !== $b['weight']) { return $b['weight'] - $a['weight']; } return $a['id'] - $b['id']; }); foreach ($bySection as &$sectionStats) { usort($sectionStats['items'], function ($a, $b) { if ($a['count'] !== $b['count']) { return $b['count'] - $a['count']; } return $b['weight'] - $a['weight']; }); } unset($sectionStats); $totalMatches = 0; foreach ($items as $item) { $totalMatches += intval($item['count']); } return [ 'total_matches' => $totalMatches, 'matched_rule_count' => count($items), 'items' => $items, 'by_section' => $bySection, ]; } /** * @return array */ private function loadActiveRules(): array { if (self::$ruleCache !== null) { return self::$ruleCache; } try { self::$ruleCache = Db::name('ai_template_sentence') ->where('status', 1) ->field('id,sentence_pattern,section,language,risk_level,weight,remark,article_type,version') ->order('section asc,id asc') ->select(); } catch (\Throwable $e) { self::$ruleCache = []; } return self::$ruleCache; } private function normalizeText(string $text): string { return ManuscriptTextCleanService::clean($text); } private function countPatternMatches(string $text, string $pattern): int { if ($text === '' || $pattern === '') { return 0; } $regex = '#' . $pattern . '#iu'; $error = null; set_error_handler(function ($errno, $errstr) use (&$error) { $error = $errstr; return true; }); $count = preg_match_all($regex, $text, $matches); restore_error_handler(); if ($count === false || $error !== null) { return 0; } return intval($count); } }