Files
tougao/application/common/AiTemplateSentenceService.php
2026-07-15 10:55:08 +08:00

177 lines
5.3 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\common;
use think\Db;
/**
* AI 写作模版句规则匹配t_ai_template_sentence
*/
class AiTemplateSentenceService
{
/** @var array<int,array>|null */
private static $ruleCache;
/**
* 按稿件分节统计模版句出现次数
*
* @param array<string,string> $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<int,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);
}
}