fix(recommendation): use displayed skin age across reports and admin

This commit is contained in:
xsl
2026-10-16 19:03:25 +08:00
parent 571d78a2ed
commit bb6da17f54
12 changed files with 106 additions and 35 deletions
+14 -5
View File
@@ -587,6 +587,7 @@ function displayScoreLevels(scores, capDimensions = false) {
Object.assign({}, value, { S:capDimensions ? SkinScore.capDimensionScore(value && value.S) : value && value.S, level: ReportUI.internalLevel(value && value.S) })])) : scores;
}
function listItem(rec) {
rec=displayRecommendationRecord(rec);
return {
record_id: rec.record_id,
created_at: rec.created_at,
@@ -606,6 +607,13 @@ function listItem(rec) {
}
/* Dates mean Beijing calendar days; fully zoned ISO timestamps retain their instant. */
function displayRecommendationRecord(rec) {
if(!rec || rec.detect_status!=='成功')return rec;
const vm=SkinScore.withDisplayRecommendation({skinAgeRaw:rec.skin_age,realAge:rec.real_age,dims:rec.dims,recommendation:rec.recommendation});
const presentation=rec.presentation?Object.assign({},rec.presentation,{product:ReportUI.products.find(p=>p.tier===vm.recommendation.tier)||null}):rec.presentation;
return Object.assign({},rec,{skin_age_display:vm.skinAgeDisplay,recommendation:vm.recommendation,group:vm.group,essence:vm.essence,essence_line:vm.essence.text,
trigger:vm.recommendation.triggers.join('、'),presentation});
}
function matchQuery(rec, q) {
if (q.timeRange && (q.from || q.to)) {
const timestamp = BeijingTime.milliseconds(rec.created_at);
@@ -650,7 +658,7 @@ function handleRecordsQuery(req, res, url) {
catch (e) { return sendJson(res, 400, { ok: false, message: e.message }); }
const page = Math.max(1, parseInt(sp.get('page') || '1', 10) || 1);
const pageSize = Math.min(100, Math.max(1, parseInt(sp.get('page_size') || '20', 10) || 20));
const filtered = readAllRecords().filter(r => matchQuery(r, q))
const filtered = readAllRecords().map(displayRecommendationRecord).filter(r => matchQuery(r, q))
.sort((a, b) => (a.created_at < b.created_at ? 1 : -1)); // created_at 倒序
const total = filtered.length;
const items = filtered.slice((page - 1) * pageSize, page * pageSize).map(listItem);
@@ -658,7 +666,7 @@ function handleRecordsQuery(req, res, url) {
}
function handleRecordDetail(req, res, id) {
const rec = findRecord(id);
const rec = displayRecommendationRecord(findRecord(id));
if (!rec) return sendJson(res, 404, { ok: false, message: '记录不存在' });
// 原始 JSON 全文:读文件失败给 null。
// face_maps 是整幅 base64 叠加图(单文件可达数 MB),详情页直接渲染会卡死浏览器:
@@ -695,6 +703,7 @@ function handleRecordDetail(req, res, id) {
if (rec.sub_scores && rec.sub_scores.color) displayVm.cats.tone.vars.color_score = rec.sub_scores.color.P;
presentation = ReportUI.forDisplay(displayVm, rawJson.result, presentation);
}
if(presentation && rec.recommendation && rec.recommendation.ageBasis==='display')presentation=Object.assign({},presentation,{product:ReportUI.products.find(p=>p.tier===rec.recommendation.tier)||null});
sendJson(res, 200, {
ok: true,
record: Object.assign({}, rec, { total_score: displayTotalScore(rec), total_score_rule: 'dimensions-433-v1', skin_age_display: displaySkinAge(rec),
@@ -738,7 +747,7 @@ function handlePhoto(req, res, id, which) {
}
function handleStats(req, res) {
const all = readAllRecords();
const all = readAllRecords().map(displayRecommendationRecord);
const now = Date.now(), today = BeijingTime.day(now);
const finished = all.filter(r => r.detect_status === '成功' || r.detect_status === '失败');
const succeeded = finished.filter(r => r.detect_status === '成功');
@@ -853,13 +862,13 @@ function handleExport(req, res, url) {
};
try { q.timeRange = BeijingTime.range(q.from, q.to); }
catch (e) { return sendJson(res, 400, { ok: false, message: e.message }); }
const filtered = readAllRecords().filter(r => matchQuery(r, q))
const filtered = readAllRecords().map(displayRecommendationRecord).filter(r => matchQuery(r, q))
.sort((a, b) => (a.created_at < b.created_at ? 1 : -1));
const dimP = (r, d) => (r.dims && r.dims[d]) ? r.dims[d].P : null;
const dimS = (r, d) => (r.dims && r.dims[d]) ? SkinScore.capDimensionScore(r.dims[d].S) : null;
const dimLv = (r, d) => (r.dims && r.dims[d]) ? ReportUI.levelForScore(r.dims[d].S) : null;
const header = ['记录ID', '创建时间(北京时间)', '年龄', '性别', '地区', '日常肤质', '拍摄状态', '上传状态', '检测状态',
'失败环节', '失败码', '失败信息', '综合得分(4:3:3)', '原始肌龄(推荐依据)', '前端展示肌龄',
'失败环节', '失败码', '失败信息', '综合得分(4:3:3)', '原始肌龄', '前端展示肌龄(推荐依据)',
'紧塑力P', '紧塑力S', '紧塑力档', '透亮感P', '透亮感S', '透亮感档', '稳韧力P', '稳韧力S', '稳韧力档',
'均分S', '产品组别', '推荐精华', '触发判据', '耗时ms', '推荐规则版本', '推荐原因', '侧脸质量状态', '前端拍摄参数版本'];
const lines = [header.join(',')];