v1.0.27: 碎片处理中心(完整方案) - 完整度计算引擎(四表分层/阈值)+is_incomplete标记(写操作挂钩)+api/fragment八接口(统计/硬碎片列表详情转换废弃/软碎片列表详情补全)+fragment.html前端(指标卡+硬软碎片双区块)

This commit is contained in:
nanguaboss
2026-08-09 15:47:05 +08:00
parent b68232bff3
commit 76431c20ac
25 changed files with 1342 additions and 3 deletions
+236
View File
@@ -0,0 +1,236 @@
<?php
/**
* 完整度计算引擎(碎片处理方案 v1.0.27)
* 公式:完整度 = 100 - (缺失核心字段数×20) - (缺失重要字段数×8) - (缺失补充字段数×2),下限 0%
* 阈值:persons/companies ≥65%,social_accounts ≥60%,media_commercial_attributes ≥55%
* 说明:persons 的 phone/email 为虚拟字段(来自 social_accounts 关联记录)
*/
require_once __DIR__ . '/db.php';
/** 四表字段分层定义(key => 层级),threshold 为完整度阈值 */
const COMPLETENESS_LAYERS = [
'persons' => [
'core' => ['full_name'],
'important' => ['work_location'],
'supplementary' => ['gender', 'nationality', 'education', 'graduated_from', 'hometown', 'id_type', 'id_number'],
'virtual' => ['phone' => 'core', 'email' => 'important'], // 关联 social_accounts.platform
'threshold' => 65,
],
'companies' => [
'core' => ['display_name', 'industry'],
'important' => ['address', 'business_role', 'country'],
'supplementary' => ['name_zh', 'name_en', 'registration_number', 'legal_form', 'legal_representative',
'business_scope', 'established_date', 'registered_capital', 'industry_subdivision',
'latest_employee_count', 'latest_annual_revenue', 'is_listed', 'stock_code', 'website'],
'virtual' => [],
'threshold' => 65,
],
'social_accounts' => [
'core' => ['platform', 'account_id'],
'important' => ['is_primary'],
'supplementary' => ['profile_url', 'remark'],
'virtual' => [],
'threshold' => 60,
],
'media' => [
'core' => ['social_account_id', 'account_level'],
'important' => ['follower_count', 'content_categories'],
'supplementary' => ['certification_type', 'avg_read_count', 'special_requirements', 'media_remark'],
'virtual' => [],
'threshold' => 55,
],
];
/** 字段中文名(前端展示缺失字段用) */
const COMPLETENESS_LABELS = [
'full_name' => '姓名', 'phone' => '手机', 'email' => '邮箱', 'work_location' => '工作所在地',
'gender' => '性别', 'nationality' => '国籍', 'education' => '学历', 'graduated_from' => '毕业院校',
'hometown' => '家乡', 'id_type' => '证件类型', 'id_number' => '证件号码',
'display_name' => '名称', 'industry' => '行业', 'address' => '地址', 'business_role' => '业务类型',
'country' => '国家/地区', 'name_zh' => '中文名', 'name_en' => '英文名', 'registration_number' => '注册号',
'legal_form' => '企业类型', 'legal_representative' => '法定代表人', 'business_scope' => '经营范围',
'established_date' => '成立日期', 'registered_capital' => '注册资本', 'industry_subdivision' => '行业细分',
'latest_employee_count' => '员工数', 'latest_annual_revenue' => '年营收', 'is_listed' => '是否上市',
'stock_code' => '股票代码', 'website' => '官网',
'platform' => '平台', 'account_id' => '账号', 'is_primary' => '常用', 'profile_url' => '主页链接', 'remark' => '备注',
'social_account_id' => '关联账号', 'account_level' => '账号等级', 'follower_count' => '粉丝数',
'content_categories' => '内容领域', 'certification_type' => '认证类型', 'avg_read_count' => '平均阅读',
'special_requirements' => '特殊要求', 'media_remark' => '评估备注',
];
/** 字段是否缺失:null / 空串 / 纯空白 视为缺失(0 视为已填写) */
function completenessIsMissing($v)
{
return ($v === null) || (trim((string)$v) === '');
}
/**
* 根据分层定义计算完整度
* @param string $table persons|companies|social_accounts|media
* @param array $row 该表记录(persons 需已注入虚拟字段 phone/email)
* @return array { score:int, missing_core:[], missing_important:[], missing_supp:[], level:string }
*/
function completenessCalc($table, $row)
{
$layers = COMPLETENESS_LAYERS[$table] ?? null;
if (!$layers) {
return ['score' => 100, 'missing_core' => [], 'missing_important' => [], 'missing_supp' => [], 'level' => 'complete'];
}
$missing = ['core' => [], 'important' => [], 'supplementary' => []];
foreach (['core' => 'core', 'important' => 'important', 'supplementary' => 'supplementary'] as $layer => $bucket) {
foreach ($layers[$layer] as $field) {
if (completenessIsMissing($row[$field] ?? null)) {
$missing[$bucket][] = $field;
}
}
}
// 虚拟字段(如人员 phone/email)按配置的层级计入
foreach (($layers['virtual'] ?? []) as $field => $layer) {
if (completenessIsMissing($row[$field] ?? null)) {
$missing[$layer][] = $field;
}
}
$score = 100
- count($missing['core']) * 20
- count($missing['important']) * 8
- count($missing['supplementary']) * 2;
$score = max(0, $score);
$level = 'complete';
if (!empty($missing['core'])) {
$level = 'core'; // 缺核心(红)
} elseif (!empty($missing['important'])) {
$level = 'important'; // 缺重要(橙)
} elseif (!empty($missing['supplementary'])) {
$level = 'supplementary'; // 待补充(蓝)
}
return [
'score' => $score,
'missing_core' => $missing['core'],
'missing_important' => $missing['important'],
'missing_supp' => $missing['supplementary'],
'level' => $level,
];
}
/** 人员记录:注入虚拟字段 phone/email(来自 social_accounts) */
function personWithVirtualFields(PDO $pdo, array $row)
{
$stmt = $pdo->prepare(
"SELECT platform, account_id FROM social_accounts
WHERE owner_type = 'person' AND owner_id = ? AND is_active = 1 AND platform IN ('phone','email')
ORDER BY is_primary DESC, id ASC"
);
$stmt->execute([$row['id']]);
foreach ($stmt->fetchAll() as $acc) {
if (!isset($row[$acc['platform']])) {
$row[$acc['platform']] = $acc['account_id'];
}
}
return $row;
}
/**
* 计算某条记录的完整度信息
* @param PDO $pdo
* @param string $table persons|companies|social_accounts|media
* @param int $id
* @return array { row, score, missing_core, missing_important, missing_supp, level, threshold }
*/
function completenessInfo(PDO $pdo, $table, $id)
{
$row = null;
switch ($table) {
case 'persons':
$stmt = $pdo->prepare("SELECT * FROM persons WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch();
if ($row) {
$row = personWithVirtualFields($pdo, $row);
}
break;
case 'companies':
$stmt = $pdo->prepare("SELECT * FROM companies WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch();
break;
case 'social_accounts':
$stmt = $pdo->prepare("SELECT * FROM social_accounts WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch();
break;
case 'media':
$stmt = $pdo->prepare("SELECT * FROM media_commercial_attributes WHERE social_account_id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch();
if ($row) {
$row['id'] = $row['social_account_id'];
}
break;
}
if (!$row) {
return null;
}
$calc = completenessCalc($table, $row);
return [
'row' => $row,
'score' => $calc['score'],
'missing_core' => $calc['missing_core'],
'missing_important' => $calc['missing_important'],
'missing_supp' => $calc['missing_supp'],
'level' => $calc['level'],
'threshold' => COMPLETENESS_LAYERS[$table]['threshold'] ?? 65,
'is_complete' => $calc['score'] >= (COMPLETENESS_LAYERS[$table]['threshold'] ?? 65),
];
}
/**
* 更新某条记录的 is_incomplete 标记(完整度低于阈值 → 1)
* @param PDO $pdo
* @param string $table persons|companies|social_accounts|media
* @param int $id
* @return array|null completenessInfo 结果(记录不存在返回 null)
*/
function updateIncomplete(PDO $pdo, $table, $id)
{
$info = completenessInfo($pdo, $table, $id);
if (!$info) {
return null;
}
$flag = $info['is_complete'] ? 0 : 1;
switch ($table) {
case 'persons':
$pdo->prepare("UPDATE persons SET is_incomplete = ? WHERE id = ?")->execute([$flag, $id]);
break;
case 'companies':
$pdo->prepare("UPDATE companies SET is_incomplete = ? WHERE id = ?")->execute([$flag, $id]);
break;
case 'social_accounts':
$pdo->prepare("UPDATE social_accounts SET is_incomplete = ? WHERE id = ?")->execute([$flag, $id]);
break;
case 'media':
$pdo->prepare("UPDATE media_commercial_attributes SET is_incomplete = ? WHERE social_account_id = ?")->execute([$flag, $id]);
break;
}
return $info;
}
/** 更新某个归属方(人员/企业)全部 social_accounts 的 is_incomplete */
function updateAccountsIncomplete(PDO $pdo, $ownerType, $ownerId)
{
$stmt = $pdo->prepare("SELECT id FROM social_accounts WHERE owner_type = ? AND owner_id = ?");
$stmt->execute([$ownerType, $ownerId]);
foreach ($stmt->fetchAll() as $acc) {
updateIncomplete($pdo, 'social_accounts', (int)$acc['id']);
}
}
/** 更新媒体账号(social_accounts + 其商业属性)的 is_incomplete */
function updateMediaIncomplete(PDO $pdo, $socialAccountId)
{
updateIncomplete($pdo, 'social_accounts', $socialAccountId);
updateIncomplete($pdo, 'media', $socialAccountId);
}