v1.0.29: 碎片处理按dp方案2修订 - 完整度改三层独立判定(核心100%/重要60%/非必要40%,任一不达标即碎片)+字段分层调整(persons email升核心,companies核心扩5字段)+指标卡5张(新增累计处理,本月/累计=硬+软合计)+软碎片列表加各层完整度列+硬碎片完整度改已填/总可填
This commit is contained in:
+94
-61
@@ -1,46 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* 完整度计算引擎(碎片处理方案 v1.0.27)
|
||||
* 公式:完整度 = 100 - (缺失核心字段数×20) - (缺失重要字段数×8) - (缺失补充字段数×2),下限 0%
|
||||
* 阈值:persons/companies ≥65%,social_accounts ≥60%,media_commercial_attributes ≥55%
|
||||
* 完整度判定引擎(碎片处理方案 v2,2026-08-09)
|
||||
* 三层独立判定:任一楼层不达标 → is_incomplete = 1
|
||||
* - 核心层 必须 100% 达标(不达标 = 缺核心,最高优先级)
|
||||
* - 重要层 必须 ≥ 60% 达标(不达标 = 缺重要)
|
||||
* - 非必要层 必须 ≥ 40% 达标(不达标 = 待补充)
|
||||
* 每层完整度 = 该层已填字段数 ÷ 该层总字段数 × 100%
|
||||
* 整体完整度 = 全部已填字段数 ÷ 全部字段数 × 100%(展示用)
|
||||
* 说明:persons 的 phone/email 为虚拟字段(来自 social_accounts 关联记录)
|
||||
*/
|
||||
require_once __DIR__ . '/db.php';
|
||||
|
||||
/** 四表字段分层定义(key => 层级),threshold 为完整度阈值 */
|
||||
/** 四表字段分层定义(key => 所属层;virtual 为关联虚拟字段) */
|
||||
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,
|
||||
'core' => ['full_name'],
|
||||
'important' => ['work_location'],
|
||||
'optional' => ['gender', 'nationality', 'education', 'graduated_from', 'hometown', 'id_type', 'id_number'],
|
||||
'virtual' => ['phone' => 'core', 'email' => 'core'], // 关联 social_accounts.platform
|
||||
],
|
||||
'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,
|
||||
'core' => ['display_name', 'name_zh', 'name_en', 'business_role', 'industry'],
|
||||
'important' => ['address', 'country'],
|
||||
'optional' => ['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' => [],
|
||||
],
|
||||
'social_accounts' => [
|
||||
'core' => ['platform', 'account_id'],
|
||||
'important' => ['is_primary'],
|
||||
'supplementary' => ['profile_url', 'remark'],
|
||||
'virtual' => [],
|
||||
'threshold' => 60,
|
||||
'core' => ['platform', 'account_id'],
|
||||
'important' => ['is_primary'],
|
||||
'optional' => ['profile_url', 'remark'],
|
||||
'virtual' => [],
|
||||
],
|
||||
'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,
|
||||
'core' => ['social_account_id', 'account_level'],
|
||||
'important' => ['follower_count', 'content_categories'],
|
||||
'optional' => ['avg_read_count', 'certification_type', 'special_requirements', 'media_remark'],
|
||||
'virtual' => [],
|
||||
],
|
||||
];
|
||||
|
||||
/** 各层达标门槛(%) */
|
||||
const COMPLETENESS_THRESHOLDS = ['core' => 100, 'important' => 60, 'optional' => 40];
|
||||
|
||||
/** 字段中文名(前端展示缺失字段用) */
|
||||
const COMPLETENESS_LABELS = [
|
||||
'full_name' => '姓名', 'phone' => '手机', 'email' => '邮箱', 'work_location' => '工作所在地',
|
||||
@@ -65,54 +68,85 @@ function completenessIsMissing($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 }
|
||||
* @return array {
|
||||
* layers: { core:{filled,total,rate,passed}, important:{...}, optional:{...} },
|
||||
* overall: float, overall_passed: bool,
|
||||
* missing: { core:[], important:[], optional:[] },
|
||||
* level: core|important|optional|complete
|
||||
* }
|
||||
*/
|
||||
function completenessCalc($table, $row)
|
||||
{
|
||||
$layers = COMPLETENESS_LAYERS[$table] ?? null;
|
||||
$default = [
|
||||
'layers' => ['core' => ['filled' => 0, 'total' => 0, 'rate' => 100, 'passed' => true]],
|
||||
'overall' => 100, 'overall_passed' => true,
|
||||
'missing' => ['core' => [], 'important' => [], 'optional' => []],
|
||||
'level' => 'complete',
|
||||
];
|
||||
if (!$layers) {
|
||||
return ['score' => 100, 'missing_core' => [], 'missing_important' => [], 'missing_supp' => [], 'level' => 'complete'];
|
||||
return $default;
|
||||
}
|
||||
$missing = ['core' => [], 'important' => [], 'supplementary' => []];
|
||||
|
||||
foreach (['core' => 'core', 'important' => 'important', 'supplementary' => 'supplementary'] as $layer => $bucket) {
|
||||
$result = [];
|
||||
$missing = ['core' => [], 'important' => [], 'optional' => []];
|
||||
$totalAll = 0;
|
||||
$filledAll = 0;
|
||||
|
||||
foreach (['core', 'important', 'optional'] as $layer) {
|
||||
$total = count($layers[$layer]);
|
||||
$filled = 0;
|
||||
foreach ($layers[$layer] as $field) {
|
||||
if (completenessIsMissing($row[$field] ?? null)) {
|
||||
$missing[$bucket][] = $field;
|
||||
$missing[$layer][] = $field;
|
||||
} else {
|
||||
$filled++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 虚拟字段(如人员 phone/email)按配置的层级计入
|
||||
foreach (($layers['virtual'] ?? []) as $field => $layer) {
|
||||
if (completenessIsMissing($row[$field] ?? null)) {
|
||||
$missing[$layer][] = $field;
|
||||
// 虚拟字段(persons phone/email)按配置层计入
|
||||
foreach (($layers['virtual'] ?? []) as $vf => $vLayer) {
|
||||
if ($vLayer !== $layer) {
|
||||
continue;
|
||||
}
|
||||
$total++;
|
||||
if (completenessIsMissing($row[$vf] ?? null)) {
|
||||
$missing[$layer][] = $vf;
|
||||
} else {
|
||||
$filled++;
|
||||
}
|
||||
}
|
||||
$rate = $total > 0 ? round($filled / $total * 100, 1) : 100;
|
||||
$result[$layer] = [
|
||||
'filled' => $filled,
|
||||
'total' => $total,
|
||||
'rate' => $rate,
|
||||
'passed' => $rate >= COMPLETENESS_THRESHOLDS[$layer],
|
||||
];
|
||||
$totalAll += $total;
|
||||
$filledAll += $filled;
|
||||
}
|
||||
|
||||
$score = 100
|
||||
- count($missing['core']) * 20
|
||||
- count($missing['important']) * 8
|
||||
- count($missing['supplementary']) * 2;
|
||||
$score = max(0, $score);
|
||||
$overall = $totalAll > 0 ? round($filledAll / $totalAll * 100, 1) : 100;
|
||||
$overallPassed = $result['core']['passed'] && $result['important']['passed'] && $result['optional']['passed'];
|
||||
|
||||
$level = 'complete';
|
||||
if (!empty($missing['core'])) {
|
||||
if (!$result['core']['passed']) {
|
||||
$level = 'core'; // 缺核心(红)
|
||||
} elseif (!empty($missing['important'])) {
|
||||
} elseif (!$result['important']['passed']) {
|
||||
$level = 'important'; // 缺重要(橙)
|
||||
} elseif (!empty($missing['supplementary'])) {
|
||||
$level = 'supplementary'; // 待补充(蓝)
|
||||
} elseif (!$result['optional']['passed']) {
|
||||
$level = 'optional'; // 待补充(蓝)
|
||||
}
|
||||
|
||||
return [
|
||||
'score' => $score,
|
||||
'missing_core' => $missing['core'],
|
||||
'missing_important' => $missing['important'],
|
||||
'missing_supp' => $missing['supplementary'],
|
||||
'level' => $level,
|
||||
'layers' => $result,
|
||||
'overall' => $overall,
|
||||
'overall_passed' => $overallPassed,
|
||||
'missing' => $missing,
|
||||
'level' => $level,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -138,7 +172,7 @@ function personWithVirtualFields(PDO $pdo, array $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 }
|
||||
* @return array|null { row, layers, overall, overall_passed, missing, level, is_complete }
|
||||
*/
|
||||
function completenessInfo(PDO $pdo, $table, $id)
|
||||
{
|
||||
@@ -176,19 +210,18 @@ function completenessInfo(PDO $pdo, $table, $id)
|
||||
}
|
||||
$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),
|
||||
'row' => $row,
|
||||
'layers' => $calc['layers'],
|
||||
'overall' => $calc['overall'],
|
||||
'overall_passed' => $calc['overall_passed'],
|
||||
'missing' => $calc['missing'],
|
||||
'level' => $calc['level'],
|
||||
'is_complete' => $calc['overall_passed'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新某条记录的 is_incomplete 标记(完整度低于阈值 → 1)
|
||||
* 更新某条记录的 is_incomplete 标记(任一楼层不达标 → 1)
|
||||
* @param PDO $pdo
|
||||
* @param string $table persons|companies|social_accounts|media
|
||||
* @param int $id
|
||||
|
||||
Reference in New Issue
Block a user