fix(report): cap dimension scores and match zoom crops to thumbnails

This commit is contained in:
xsl
2026-10-15 20:17:07 +08:00
parent 482fb4280d
commit 5632ac43d7
13 changed files with 91 additions and 24 deletions
+17
View File
@@ -0,0 +1,17 @@
'use strict';
const assert=require('node:assert/strict'),S=require('../js/skin-score'),UI=require('../js/report-ui');
const raw=structuredClone(S.MOCK_RESULT),base=S.buildViewModel(raw,S.MOCK_SIDE,{});
for(const P of [20,50,90])for(const pouch of [0,1,2,3,null,9])for(const circle of [0,1,2,3,null,9,1.5]) {
const vm=structuredClone(base);vm.cats.contour.P=P;Object.assign(vm.cats.contour.vars,{eye_pouch:pouch,dark_circle_sev:circle});
const before=JSON.stringify(vm),text=UI.build(vm,raw).views.find(v=>v.key==='contour').text;
assert.equal(text.includes('眼袋'),[1,2,3].includes(pouch));
assert.equal(text.includes('黑眼圈'),[1,2,3].includes(circle));
assert(!/无明显眼袋|无明显黑眼圈|数据未获取|[;、]{2}|[{}]/.test(text));
assert.equal(JSON.stringify(vm),before);
}
const saved=UI.build(base,raw);saved.copyRule='wrinkles-excellent-acne-detected-water-v2';saved.views[0].text='眼周无明显眼袋问题、无明显黑眼圈;';
base.cats.contour.vars.eye_pouch=0;base.cats.contour.vars.dark_circle_sev=2;
const before=JSON.stringify(saved),display=UI.forDisplay(base,raw,saved);
assert(!display.views[0].text.includes('眼袋'));assert(display.views[0].text.includes('黑眼圈中度'));
assert.equal(JSON.stringify(saved),before);assert.deepEqual(display.product,saved.product);
console.log('PASS contour eye copy: 126 grade/detection combinations, only positive problems, missing/invalid omitted, old report refresh and immutable scores/snapshots');
+7 -4
View File
@@ -40,7 +40,9 @@ try {
assert(offset!=null&&Number(offset)>=0&&Number(offset)<=.090001);
const fullFront=await page.locator('#ditem-img1').getAttribute('src');
await page.locator('#ditem-img1').click();
assert.equal(await page.locator('#zoom-img').getAttribute('src'),fullFront,'front zoom still uses uncropped source');
assert.notEqual(await page.locator('#zoom-img').getAttribute('src'),fullFront,'front zoom follows clipped thumbnail');
await page.waitForFunction(()=>document.querySelector('#zoom-img').complete);
assert(Math.abs(await page.locator('#zoom-img').evaluate(e=>e.naturalWidth/e.naturalHeight)-215/250.2)<.003);
await page.locator('#p-zoom').click();
const geo=await page.locator('.contour-side-photo').first().evaluate(e=>{const r=e.getBoundingClientRect();return{w:r.width,h:r.height,fit:getComputedStyle(e.querySelector('img')).objectFit};});
assert(Math.abs(geo.h/geo.w-92.25/104.35)<.002);assert.equal(geo.fit,'cover');
@@ -60,11 +62,12 @@ try {
assert(Math.abs(spacing.vertical/spacing.u-7.56)<.1);
await page.getByRole('button',{name:'查看左脸完整大图'}).click();
assert(await page.locator('#p-zoom').evaluate(e=>e.classList.contains('show')));
assert.equal(await page.locator('#zoom-img').getAttribute('src'),left);
assert.equal(await page.locator('#zoom-img').evaluate(e=>e.naturalWidth/e.naturalHeight),4/3);
assert.notEqual(await page.locator('#zoom-img').getAttribute('src'),left);
await page.waitForFunction(()=>document.querySelector('#zoom-img').complete);
assert(Math.abs(await page.locator('#zoom-img').evaluate(e=>e.naturalWidth/e.naturalHeight)-geo.w/geo.h)<.003);
await page.locator('#p-zoom').click();
await page.getByRole('button',{name:'查看右脸完整大图'}).click();
assert.equal(await page.locator('#zoom-img').getAttribute('src'),right);
assert.notEqual(await page.locator('#zoom-img').getAttribute('src'),right);
await page.keyboard.press('Escape');
assert(!await page.locator('#p-zoom').evaluate(e=>e.classList.contains('show')));
await page.locator('.card-detail').first().locator('[data-tab="1"]').click();
+16
View File
@@ -0,0 +1,16 @@
'use strict';
const assert=require('node:assert/strict'),S=require('../js/skin-score'),UI=require('../js/report-ui');
for(const score of [9.9,9.94,9.95,9.99,10]) {
assert.equal(S.capDimensionScore(score),9.9);
assert.equal(UI.dimensionScore(score),'9.9','rounding must never display 10.0');
}
assert.equal(UI.dimensionScore(9.34),'9.3');assert.equal(S.capDimensionScore(null),null);
const r={eye_pouch_severity:0,score_info:{wrinkle_score:100,blackhead_score:100,rough_score:100,melanin_score:100,dark_circle_score:100,water_score:100,sensitivity_score:100,acne_score:100},red_spot:{red_spot_area:0}};
for(const key of ['closed_comedones','acne','acne_pustule','acne_nodule','acne_mark'])r[key]={count:0};
const before=JSON.stringify(r),vm=S.buildViewModel(r,null,{});
for(const dim of Object.values(vm.dims)){assert.equal(dim.P,100);assert.equal(dim.S,9.9);}
for(const cat of Object.values(vm.cats)){assert.equal(cat.P,100);assert.equal(cat.S,10,'small categories unchanged');}
assert.equal(vm.total,99);assert.equal(JSON.stringify(r),before);
const legacy={firm:{S:10},vita:{S:9.99},stab:{S:9.97}},saved=JSON.stringify(legacy);
assert.equal(S.weightedTotal(legacy),99);assert.equal(JSON.stringify(legacy),saved,'old stored scores not rewritten');
console.log('PASS dimension cap: 9.9 for all dimensions, no rounding to 10, subcategories/raw data unchanged, total99 and immutable historical scores');
+1 -1
View File
@@ -27,7 +27,7 @@ r.score_info.wrinkle_score = 76;
const vm = SS.buildViewModel(r,SS.MOCK_SIDE,{}), ui = UI.build(vm,r);
assert.equal(UI.dimensionScore(vm.dims.firm.S),'9.3');
assert.equal(ui.views.find(v=>v.key==='contour').level,'优秀');
assert(ui.views.find(v=>v.key==='contour').text.includes('眼周无明显眼袋问题'),'contour copy remains original by user request');
assert(ui.views.find(v=>v.key==='contour').text.includes('眼袋明显'),'contour eye problem follows actual detection, not aggregate grade');
const old = structuredClone(ui);
delete old.gradeRule;
old.views[0].level='改善';old.views[0].text='旧轮廓文案';
+2
View File
@@ -116,6 +116,8 @@ function main() {
tr.summary = tr.summary.replace('六分类S均分9.54', '六分类S均分9.53');
// Restored original minimum: compare combined tone with smoothness.
}
// Approved dimension cap; keep original category scores and fixture files.
for (const dim of Object.values(tr.dims)) dim.S = Math.min(9.9,dim.S);
var vm = SkinScore.buildViewModel(fixture.result, fixture.side, tr.profile);
// 下颌线角度 AP/AQ:fixture 保真校验(引擎消费角度后由 L 列逐字断言兜底)
+1 -1
View File
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
const Score = require('../js/skin-score');
const dims = (firm, vita, stab) => ({ firm: { S: firm }, vita: { S: vita }, stab: { S: stab } });
for (const [scores, total] of [
[[8, 9, 8.5], 85], [[10, 10, 10], 100], [[7, 7, 7], 70],
[[8, 9, 8.5], 85], [[10, 10, 10], 99], [[7, 7, 7], 70],
[[10, 7, 7], 82], [[7, 10, 7], 79], [[7, 7, 10], 79],
[[8.01, 8.82, 8], 83], [[7.69, 8.72, 9.1], 84]
]) assert.equal(Score.weightedTotal(dims(...scores)), total, scores.join('/'));
+1 -1
View File
@@ -27,5 +27,5 @@ const base=SS.buildViewModel(sample(65),SS.MOCK_SIDE,{});
const changed=sample(65);changed.left_mouth_wrinkle_info={wrinkle_score:0,wrinkle_severity_level:0};
assert.equal(SS.buildViewModel(changed,SS.MOCK_SIDE,{}).cats.wrinkles.P,base.cats.wrinkles.P,'region severity has no scoring effect');
assert.equal(UI.dimensionScore(8.95),'9.0');assert.equal(UI.dimensionScore(9.45),'9.5');
assert.equal(UI.dimensionScore(9.97),'10.0');assert.equal(UI.dimensionScore(null),'-');
assert.equal(UI.dimensionScore(9.97),'9.9');assert.equal(UI.dimensionScore(null),'-');
console.log('PASS overall wrinkles: direct 0..100, no inversion/region caps, missing/invalid data, firm min, source immutability, half-up dimension display');
+2 -2
View File
@@ -35,8 +35,8 @@ assert.deepEqual(layout.sideA,layout.sideB,'same side image scaling and position
assert.equal(layout.offsetA,layout.offsetB,'same face crop offset; overlay moves with photo');
const leftSrc=await p.locator('#wrinkle-left').getAttribute('src'),rightSrc=await p.locator('#wrinkle-right').getAttribute('src');
assert.notEqual(leftSrc,left);assert.notEqual(rightSrc,right);assert.notEqual(leftSrc,rightSrc);
await p.getByRole('button',{name:'查看左脸皱纹检测大图'}).click();assert.equal(await p.locator('#zoom-img').getAttribute('src'),leftSrc);await p.locator('#p-zoom').click();
await p.getByRole('button',{name:'查看右脸皱纹检测大图'}).click();assert.equal(await p.locator('#zoom-img').getAttribute('src'),rightSrc);await p.locator('#p-zoom').click();
await p.getByRole('button',{name:'查看左脸皱纹检测大图'}).click();assert.notEqual(await p.locator('#zoom-img').getAttribute('src'),leftSrc);await p.locator('#p-zoom').click();
await p.getByRole('button',{name:'查看右脸皱纹检测大图'}).click();assert.notEqual(await p.locator('#zoom-img').getAttribute('src'),rightSrc);await p.locator('#p-zoom').click();
}
await p.evaluate(x=>window.__wrinkleTest(x),{left,right,views:{}});
await p.waitForFunction(()=>!document.querySelector('#wrinkle-left').closest('figure').hidden&&!document.querySelector('#wrinkle-right').closest('figure').hidden);