Files
superlink/api/common/completeness.php
T

237 lines
9.8 KiB
PHP
Raw 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
/**
* 完整度计算引擎(碎片处理方案 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);
}