feat(report): unify display grades and add contour side photos

This commit is contained in:
xsl
2026-10-15 17:14:05 +08:00
parent 60f0965c5a
commit 2d80f6dfd0
21 changed files with 557 additions and 49 deletions
+35 -8
View File
@@ -21,6 +21,19 @@
function dimensionScore(value) {
return numeric(value) ? (Math.round((value + 1e-9) * 10) / 10).toFixed(1) : '-';
}
var gradeRule = 'display-score-8-9-v1';
var copyRule = 'wrinkles-excellent-acne-detected-v1';
function scoreOfP(P) {
return numeric(P) ? Math.round((Math.max(7, Math.min(10, 7 + 3 * P / 100)) + 1e-9) * 100) / 100 : null;
}
function levelForScore(S) {
if (!numeric(S)) return null;
var displayed = Number(dimensionScore(S));
return displayed < 8 ? '改善' : displayed < 9 ? '良好' : '优秀';
}
function internalLevel(S) {
return { '改善': '预警', '良好': '改善', '优秀': '良好' }[levelForScore(S)] || null;
}
// Read saved raw dimension values, never re-score historical reports or use
// the legacy focus.name (which contains the old dimension terminology).
function focusOf(vm) {
@@ -84,12 +97,11 @@
result = result || {};
var views = defs.map(function(d) {
var cat = vm.cats[d[3]], vars = Object.assign({}, cat.vars || {});
var level = levelMap[cat.level], valid = cat.P != null;
var level, valid = numeric(cat.P);
var viewP = cat.P;
if (d[0] === 'color') {
viewP = numeric(vars.color_score) ? vars.color_score : vars.tone_scoring && vars.tone_scoring.color_score;
valid = vars.melanin != null && numeric(viewP);
level = valid ? (viewP < 80 ? '改善' : viewP < 90 ? '良好' : '优秀') : null;
}
if (d[0] === 'water') {
// Water copy/advice is independent of pigment/dark circles. Reuse
@@ -99,8 +111,9 @@
viewP = waterValid ? waterScore : null;
if (waterValid && numeric(vars.water_sev) && vars.water_sev >= 60 && vars.water_sev <= 100 && waterScore >= 80) viewP = 100 - vars.water_sev;
valid = waterValid;
level = valid ? (viewP < 80 ? '改善' : viewP < 90 ? '良好' : '优秀') : null;
}
var viewS = valid ? scoreOfP(viewP) : null;
level = levelForScore(viewS);
var template = valid && level ? Copy.conclusion[d[0]][level] : '';
if (d[0] === 'water' && valid) {
vars.water_severity_note = numeric(vars.water_sev) && vars.water_sev >= 0 && vars.water_sev <= 100
@@ -131,8 +144,9 @@
var nodules = vars.acne_counts ? vars.acne_counts.acne_nodule : count(result, 'acne_nodule');
var heavy = [pustules > 0 ? '脓包' : '', nodules > 0 ? '结节' : ''].filter(Boolean);
vars.heavy_note = heavy.length ? ',含' + heavy.join('、') + '等中重度皮损,炎症反应明显' : '';
if (heavy.length) { level = '改善'; template = Copy.conclusion.acne[level]; }
else if (vars.acne_cnt > 0 && level === '优秀') { level = '良好'; template = Copy.conclusion.acne[level]; }
// Never downgrade the grade independently of the displayed score,
// but never claim absence when the API detected lesions either.
if (vars.acne_cnt > 0) template = '检测到 {acne_cnt} 处皮损({acne_detail}){heavy_note},局部皮损仍需关注。';
if (vars.acne_cnt == null) template = '本次未获取完整皮损计数,请结合检测图片查看。';
else if (vars.acne_cnt === 0) template = '本次未检出明显皮损,请结合检测图片查看。';
if (vars.acne_cnt != null && vars.acne_counts_complete === false) {
@@ -156,7 +170,7 @@
}
var details = d[0] === 'wrinkles' ? wrinkleDetails(result) : null;
var advice = level ? Copy.care[d[0]][level] : null;
return { key: d[0], name: d[1], dim: d[2], source: d[3], P: viewP, level: level || null,
return { key: d[0], name: d[1], dim: d[2], source: d[3], P: viewP, S: viewS, level: level || null,
text: valid ? fill(template, vars) : '本次未检出有效数据,请重新拍摄后查看。',
details: details, valid: valid, advice: valid ? advice : null };
});
@@ -170,7 +184,20 @@
var care = chosen.map(function(v) { return { key: v.key, name: v.name, dim: v.dim, P: v.P, level: v.level,
priority: priority(v) === 0, text: v.advice.care + v.advice.lifestyle }; });
var product = products.filter(function(p) { return p.match === vm.essence.name; })[0] || null;
return { version: Copy.version + '+feedback-2026-10-12+care-2026-10-15', careRule: 'seven-lowest-three-focus-first-v1', dims: dimNames, views: views, care: care, product: product, focus: focus };
return { version: Copy.version + '+feedback-2026-10-12+care-2026-10-15+grade-8-9+two-copy-fixes', gradeRule: gradeRule, copyRule: copyRule, careRule: 'seven-lowest-three-focus-first-v1', dims: dimNames, views: views, care: care, product: product, focus: focus };
}
return { dimensionScore: dimensionScore, build: build, products: products, levelMap: levelMap, dimNames: dimNames, wrinkleDetails: wrinkleDetails, focusOf: focusOf, customerText: customerText };
// Refresh only presentation for old snapshots: scores, selected care order,
// product and original stored objects are never rewritten/recomputed.
function forDisplay(vm, result, saved) {
if (!saved || (saved.gradeRule === gradeRule && saved.copyRule === copyRule)) return saved || build(vm, result);
var current = build(vm, result);
var care = (saved.care || []).map(function(item) {
// Pre-seven-item snapshots contain name/text only, without a stable key.
// Match exact known names; never infer from array position or insert items.
var view = current.views.filter(function(v) { return item.key ? v.key === item.key : v.name === item.name; })[0];
return view && view.advice ? Object.assign({}, item, { key: view.key, level: view.level, text: view.advice.care + view.advice.lifestyle }) : item;
});
return Object.assign({}, saved, { gradeRule: gradeRule, copyRule: copyRule, views: current.views, care: care });
}
return { dimensionScore: dimensionScore, scoreOfP: scoreOfP, levelForScore: levelForScore, internalLevel: internalLevel, gradeRule: gradeRule, copyRule: copyRule, build: build, forDisplay: forDisplay, products: products, levelMap: levelMap, dimNames: dimNames, wrinkleDetails: wrinkleDetails, focusOf: focusOf, customerText: customerText };
});