feat(scoring): use independent tone hydration minima and focus-first care
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
const assert=require('node:assert/strict'),SS=require('../js/skin-score'),UI=require('../js/report-ui'),Copy=require('../js/report-copy');
|
||||
const keys=['contour','wrinkles','color','smooth','water','sens','acne'];
|
||||
function sample(scores,focus='firm'){
|
||||
const vm=SS.buildViewModel(structuredClone(SS.MOCK_RESULT),SS.MOCK_SIDE,{});
|
||||
for(const k of ['contour','wrinkles','smooth','sens','acne']){vm.cats[k].P=scores[k];vm.cats[k].level=scores[k]==null?null:scores[k]<80?'预警':scores[k]<90?'改善':'良好';}
|
||||
Object.assign(vm.cats.tone.vars,{melanin:scores.color,color_score:scores.color,water_score:scores.water,water_sev:0});
|
||||
vm.cats.tone.vars.tone_scoring.color_score=scores.color;
|
||||
vm.cats.acne.vars.acne_counts={closed_comedones:0,acne:0,acne_pustule:0,acne_nodule:0,acne_mark:0};
|
||||
vm.cats.acne.vars.acne_cnt=0;
|
||||
for(const dim of ['firm','vita','stab'])vm.dims[dim].P=focus==null?null:dim===focus?10:99;
|
||||
const before=JSON.stringify(vm),ui=UI.build(vm,SS.MOCK_RESULT);assert.equal(JSON.stringify(vm),before);
|
||||
return{vm,ui};
|
||||
}
|
||||
function check(values,focus,expected){const scores=Object.fromEntries(keys.map((k,i)=>[k,values[i]])),x=sample(scores,focus);assert.deepEqual(x.ui.care.map(v=>v.key),expected);assert(x.ui.care.length<=3);for(const c of x.ui.care){assert.equal(c.text,Copy.care[c.key][c.level].care+Copy.care[c.key][c.level].lifestyle);assert.equal(c.priority,c.dim===focus);}return x;}
|
||||
check([20,10,30,90,95,92,99],'firm',['wrinkles','contour','color']);
|
||||
check([30,95,10,90,20,99,98],'firm',['contour','color','water']);
|
||||
check([90,30,10,20,95,96,99],'firm',['wrinkles','color','smooth']);
|
||||
check([99,98,40,30,20,97,96],'vita',['water','smooth','color']);
|
||||
check([95,96,10,20,30,90,99],'firm',['color','smooth','water']);
|
||||
check([80,80,80,80,80,80,80],'stab',['sens','acne','contour']);
|
||||
check([80,80,80,80,80,80,80],null,['contour','wrinkles','color']);
|
||||
check([79.999,79.998,79.997,99,99,99,99],'firm',['wrinkles','contour','color']);
|
||||
check([null,null,50,null,60,null,null],'firm',['color','water']);
|
||||
check([null,null,null,null,null,null,null],null,[]);
|
||||
const x=check([99,98,79.99,99,90,99,99],'vita',['color','water','wrinkles']);
|
||||
assert.equal(x.ui.views.find(v=>v.key==='color').level,'改善');assert.equal(x.ui.views.find(v=>v.key==='water').level,'优秀');
|
||||
assert.equal(x.ui.careRule,'seven-lowest-three-focus-first-v1');
|
||||
const heavy=sample(Object.fromEntries(keys.map(k=>[k,95])),'stab');heavy.vm.cats.acne.vars.acne_counts.acne_nodule=1;heavy.vm.cats.acne.vars.acne_cnt=1;
|
||||
assert.equal(UI.build(heavy.vm,{}).views.find(v=>v.key==='acne').level,'改善');
|
||||
assert(!Copy.care.wrinkles['改善'].lifestyle.startsWith('保证睡眠'));
|
||||
console.log('PASS care: global lowest3, focus-first selected only, two/one/no focus members, tie/document order, raw precision, missing, independent color/water, acne guard, exact copy and immutable scores');
|
||||
@@ -19,6 +19,7 @@ for(const [P,level] of [[79.99,'改善'],[80,'良好'],[89.99,'良好'],[90,'优
|
||||
else result.dark_circle_severity={value:severity};
|
||||
const vm=SS.buildViewModel(result,SS.MOCK_SIDE,{});
|
||||
vm.cats.tone.P=P;vm.cats.tone.level=P<80?'预警':P<90?'改善':'良好';
|
||||
vm.cats.tone.vars.color_score=P; // Test independent color thresholds.
|
||||
const before=JSON.stringify({vm,result});
|
||||
const presentation=UI.build(vm,result),view=presentation.views.find(v=>v.key==='color');
|
||||
const valid=Number.isInteger(severity)&&severity>=0&&severity<=3;
|
||||
|
||||
@@ -114,6 +114,8 @@ function main() {
|
||||
tr.cats.acne = { P: 99, S: 9.97, tier: '良好档', copy: '检出1处皮损(闭口粉刺1处)。建议维持清洁与控油节奏,注意作息与饮食。' };
|
||||
tr.avgS = 9.53;
|
||||
tr.summary = tr.summary.replace('六分类S均分9.54', '六分类S均分9.53');
|
||||
// Authorized independent minimum: water90 no longer averaged up to91.5.
|
||||
tr.dims.vita.P = 90; tr.dims.vita.S = 9.7;
|
||||
}
|
||||
var vm = SkinScore.buildViewModel(fixture.result, fixture.side, tr.profile);
|
||||
|
||||
|
||||
@@ -44,6 +44,9 @@ try{
|
||||
assert(Math.abs(geo.heading.w-595/1080*width)<.1,'Figma text group width scales with preview');
|
||||
const stabilityFont=await p.locator('.dimension-stability-title').evaluate(e=>parseFloat(getComputedStyle(e).fontSize));
|
||||
assert(Math.abs(geo.headingFont-stabilityFont)<.01,'bottom title matches stability heading font size at every width');
|
||||
const productFonts=await p.evaluate(()=>['#product-title','#product-name'].map(s=>parseFloat(getComputedStyle(document.querySelector(s)).fontSize)));
|
||||
assert(Math.abs(productFonts[0]-14.467593)<.01,'restore original product title size, including small screens');
|
||||
assert(Math.abs(productFonts[1]-12.013166)<.01,'gray subtitle enlarged proportionally');
|
||||
assert(Math.abs((geo.heading.x+geo.heading.w/2)-(geo.care.x+geo.care.w/2)-width/1080)<.1,'whole title group centered, retaining 1px design offset');
|
||||
assert(geo.product.bottom<=geo.care.bottom);assert(geo.disclaimer.y>=geo.care.bottom);
|
||||
assert(geo.title.right<=geo.info.right+1&&geo.name.right<=geo.info.right+1,'text must not overlap image');
|
||||
|
||||
@@ -70,6 +70,9 @@ try {
|
||||
assert.equal(adminRecord.total_score_rule, 'dimensions-433-v1');
|
||||
assert.deepEqual(adminRecord.acne_scoring,result.vm.cats.acne.vars.acne_scoring);
|
||||
assert.deepEqual(adminRecord.tone_scoring,result.vm.cats.tone.vars.tone_scoring);
|
||||
assert.equal(adminRecord.vita_score_rule,'independent-color-water-smooth-min-v1');
|
||||
assert.equal(Object.keys(adminRecord.sub_scores).length,7);
|
||||
assert.equal(adminRecord.dims.vita.P,Math.min(...['color','water','smooth'].map(k=>adminRecord.sub_scores[k].P).filter(v=>v!=null)));
|
||||
if(process.env.H5_ACNE_MAX_TEST==='1') {
|
||||
assert.deepEqual(result.vm.cats.acne.vars.acne_counts,{closed_comedones:5,acne:1,acne_pustule:0,acne_nodule:0,acne_mark:4});
|
||||
assert.equal(adminRecord.acne_scoring.deduction,13);assert.equal(adminRecord.cats.acne.P,87);
|
||||
|
||||
@@ -10,7 +10,7 @@ function run(dc,options={}){
|
||||
return{vm,r,v:vm.cats.tone.vars};
|
||||
}
|
||||
const a=run(50);near(a.v.color_score,75*.8+50*.2);near(a.vm.cats.tone.P,75);
|
||||
assert.equal(a.vm.cats.tone.S,9.25);assert.equal(a.vm.dims.vita.S,9.25);
|
||||
assert.equal(a.vm.cats.tone.S,9.25);assert.equal(a.vm.dims.vita.S,9.1);
|
||||
assert(!UI.build(a.vm,a.r).views.find(v=>v.key==='color').text.includes('占肤色项20%'));
|
||||
for(const dc of [0,100]){const x=run(dc);near(x.v.color_score,75*.8+dc*.2);near(x.vm.cats.tone.P,(x.v.color_score+80)/2);}
|
||||
for(const dc of [null,undefined,-1,101,NaN,Infinity]){const x=run(dc);assert.equal(x.v.dark_circle_score,null);near(x.v.color_score,75);near(x.vm.cats.tone.P,77.5);}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
const assert=require('node:assert/strict'),S=require('../js/skin-score');
|
||||
function run(melanin,water,dark){const r=structuredClone(S.MOCK_RESULT);Object.assign(r.score_info,{melanin_score:melanin,water_score:water,dark_circle_score:dark});r.melasma={value:0};r.freckle={value:0};r.water={water_severity:3};return S.buildViewModel(r,S.MOCK_SIDE,{});}
|
||||
for(const [m,w,d]of[[66,97,30],[99,20,99],[99,99,99],[null,97,null],[null,null,null]]){
|
||||
const v=run(m,w,d),vars=v.cats.tone.vars,values=[v.cats.smooth.P,vars.color_score,vars.tone_scoring&&vars.tone_scoring.water_adjusted_score].filter(x=>typeof x==='number'&&Number.isFinite(x));
|
||||
assert.equal(v.dims.vita.P,Math.min(...values));assert.equal(v.vitaScoreRule,'independent-color-water-smooth-min-v1');assert.equal(v.total,S.weightedTotal(v.dims));
|
||||
}
|
||||
assert(Math.abs(run(66,97,30).dims.vita.P-58.8)<1e-9);
|
||||
console.log('PASS independent vita min: color/water/smooth bottleneck, missing inputs, version and weighted total');
|
||||
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
const assert=require('node:assert/strict'),SS=require('../js/skin-score'),UI=require('../js/report-ui'),Copy=require('../js/report-copy');
|
||||
function run(score,severity=3,pigment=66,dc=30){
|
||||
const r=structuredClone(SS.MOCK_RESULT);Object.assign(r.score_info,{water_score:score,melanin_score:pigment,dark_circle_score:dc});
|
||||
r.melasma={value:0};r.freckle={value:0};r.water={water_severity:severity,water_area:.03};
|
||||
const vm=SS.buildViewModel(r,SS.MOCK_SIDE,{}),before=JSON.stringify({r,vm});
|
||||
const presentation=UI.build(vm,r);assert.equal(JSON.stringify({r,vm}),before);
|
||||
return{r,vm,presentation,water:presentation.views.find(v=>v.key==='water')};
|
||||
}
|
||||
const example=run(97);assert.equal(example.vm.cats.tone.P,77.9);assert.equal(example.water.P,97);assert.equal(example.water.level,'优秀');
|
||||
assert(!/含水量偏低|缺水比较严重|水润度不足/.test(example.water.text));assert(example.water.text.includes('97'));
|
||||
assert.deepEqual(example.water.advice,Copy.care.water['优秀']);
|
||||
assert(!example.presentation.care.some(v=>v.name==='水分'),'healthy water97 should not displace a weaker item in global bottom three');
|
||||
for(const[p,level]of[[0,'改善'],[79.99,'改善'],[80,'良好'],[89.99,'良好'],[90,'优秀'],[100,'优秀']]){
|
||||
for(const pigment of[0,50,100]){const x=run(p,3,pigment,0);assert.equal(x.water.level,level);assert.equal(x.water.P,p);assert.deepEqual(x.water.advice,Copy.care.water[level]);}
|
||||
}
|
||||
const crossed=run(97,70);assert.equal(crossed.water.P,30);assert.equal(crossed.water.level,'改善');assert(crossed.water.text.includes('分数与缺水指标不一致'));assert.equal(crossed.vm.cats.tone.vars.tone_scoring.water_adjusted_score,30);
|
||||
assert(run(50,70).water.text.includes('缺水程度较高'));
|
||||
assert(!run(50,3).water.text.includes('程度较高'));
|
||||
assert(!run(50,null).water.text.includes('严重度'));
|
||||
for(const score of[null,undefined,-1,101,NaN,Infinity]){const x=run(score);assert.equal(x.water.valid,false);assert.equal(x.water.advice,null);assert(!x.presentation.care.some(v=>v.name==='水分'));}
|
||||
console.log('PASS water: 97 vs combined77.9, independent water copy/advice, 80/90 boundaries, true severity, existing cross-check, missing/invalid, scoring immutable');
|
||||
Reference in New Issue
Block a user