237 lines
9.8 KiB
PHP
237 lines
9.8 KiB
PHP
<?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);
|
||
}
|