feat(scoring): use independent tone hydration minima and focus-first care

This commit is contained in:
xsl
2026-10-15 14:40:59 +08:00
parent 77493bdff5
commit 60f0965c5a
26 changed files with 2315 additions and 1014 deletions
@@ -0,0 +1,24 @@
# 七项护理选三与关注优先
护理候选项按文档顺序为轮廓、皱纹、肤色、平滑度、水分、泛红、痘痘。使用未四舍五入的百分制分数,缺失/无效项剔除。
肤色独立采用color_score(扣斑后的色素80%+黑眼圈20%);水分独立采用已有交叉校验后的水分项。其他项使用各自现有P。肤色分析文案和护理分档同步使用独立肤色项,黑眼圈描述仍按真实检测程度;水分独立分档保持。小项P仅用于展示与护理选择,不调整维度或综合分。
1. 依小项P升序选择全局最低三项;同分时,当前优先关注大维度中的项优先,再按上述文档顺序固定选择。
2. 只在入选三项中,将属于优先关注大维度的项整体前置;组内P仍低分优先。不额外插入高分的关注项。
3. 各项按自己的分档取护理和生活方式全文拼接。低于80改善、80至低于90良好、90以上优秀;保留痘痘脓包/结节改善档、有皮损不优秀等现有文案约束。
4. 不足三项按有效数量展示,全部缺失显示原有无建议提示。
用户提供的本轮护理全文同步到reference和report-copy。原文比对发现皱纹改善档生活方式需删除旧版开头“保证睡眠,”,本次已修正,其余沿用用户提供文本。
新presentation新增careRule=seven-lowest-three-focus-first-v1;care条目保留key、name、dim、P、level、priority供核查,页面仅展示原有编号和正文。产品三选一、原始评分、历史快照和历史推荐不变;历史保存的护理文案不批量重生成。
## 真实案例回放
R20261015-111214-JKNS:关注紧塑力。肤色58.8、皱纹73、平滑度88.9为全局最低三项,最终皱纹→肤色→平滑度;水分97不入选,总分94不变。
R20261014-171215-CYLF:关注紧塑力。皱纹65、平滑度87.6、肤色88.8入选,最终顺序不变;水分93不入选,总分94不变。
## 验证
care-selection.js覆盖关注项两项/一项/零项、全部关注项、同分选取与固定顺序、原始小数、不足三项/全缺失、肤色水分独立档位、痘痘约束、全文一致与输入不变。同步复测肤色、水分、账号、持久化、历史快照、推荐、报告底部长文案布局和旧评分回归。
+1 -1
View File
@@ -15,7 +15,7 @@
ui-20261015/product-25.png 对应1:69(481×511),product-35.png 对应1:75(481×511),product-50.png 对应1:87(481×511),product-badge.png 对应1:71(101×77)。全部为设计工具导出原始PNG,保留透明边角和原始裁切,无运行时临时素材URL;网页每次只加载选中的产品图。
产品卡按945×511比例,右侧图按481×511比例,徽标按101×77比例。布局以1080px设计稿折算375px基准并随屏幕缩放,320px屏幕略缩产品文字避免遮挡。
产品卡按945×511比例,右侧图按481×511比例,徽标按101×77比例。布局以1080px设计稿折算375px基准并随屏幕缩放。用户最新确认产品标题恢复原来14.467593px,灰色副标题按同一比例(约1.041667)放大到12.013166px,行距同比放大;320px屏幕也保持同一字号,已核对三款文字均不遮挡图片。
## 验证
@@ -0,0 +1,14 @@
# 水分文案独立分档
问题案例 R20261015-111214-JKNS:接口水分97、缺水严重度3、缺水面积3%,色素66、黑眼圈30,合成肤色水润77.9。旧版水分文案跟随合成分档,错误套用改善档的含水量偏低、缺水比较严重。
## 本次修正
- 水分展示文案及其护理/生活方式建议独立使用水分项:低于80改善,80至低于90良好,90及以上优秀。
- 沿用原有交叉校验:水分分≥80且缺水严重度≥60时,按100−严重度评估,并明确提示分数与缺水指标不一致,不默默套用缺水文案。
- 改善档不再固定描述严重缺水。严重度为有效0~100时,≥60描述缺水程度较高,其他值仅报告严重度;缺失省略。缺水面积缺失也不当成0。
- 缺失或无效水分分不产生水分档位及水分护理建议。
- 三维度、综合得分、产品推荐、护理类别的选取规则均不变。水分展示项P现在为自己的水分项,不再伪装为合成分。
- 新生成报告与本地原始数据回放使用新文案。历史已保存的presentation文案保留,不批量改写文件;如需纠正历史报告展示,应另行确定范围。
参考文案与生成文件同步修改。water-copy.js覆盖真实矛盾值、80/90边界、色素不影响水分文案、严重度条件、交叉校验、缺失和输入不变;复测报告持久化、账号隔离、推荐与肤色文案。
@@ -0,0 +1,19 @@
# 透亮感改为三项独立最低分
用户确认方案2:透亮感百分制P=min(独立肤色项,水分项,平滑度项),缺项剔除,三项全缺失则不评分。十分制沿用7+3×P/100、显示范围7~10、内部保留两位小数。综合得分仍按紧塑力4、透亮感3、稳韧力3四舍五入取整数。
独立肤色项继续使用色素扣斑后80%+黑眼圈20%;水分保留已有缺水交叉校验。新透亮感参与原有优先关注及肤龄边界产品推荐判断,未另改推荐规则。
为兼容旧文案、旧六分类接口及历史记录,cats.tone和旧版统计字段暂时保留。旧平均分不再进入新版透亮感、综合得分、优先关注、产品推荐或七项护理选择。正式报告使用七项展示,后台新记录新增sub_scores七项独立评分,并记录vita_score_rule=independent-color-water-smooth-min-v1;历史记录仍标注历史六分类,不以新版重算历史评分或推荐。
本次上线包括已确认的独立肤色/水分文案、七选三且关注优先的护理建议,以及产品标题恢复14.467593px/灰色副标题同比放大。三图轮播仍是本地试验,不纳入正式页面,不上传真实案例数据或私有预览脚本。
## 真实案例回放(当前规则)
|案例|旧透亮感S|新透亮感S|旧总分|新总分|推荐变化|
|---|---:|---:|---:|---:|---|
|R20261014-171911-O6YJ|9.26|9.09|88|88|仍25+|
|R20261015-111214-JKNS|9.34|8.76|94|93|仍25+|
|R20261014-171215-CYLF|9.63|9.63|94|94|仍25+|
JKNS优先关注由紧塑力变为透亮感,护理建议顺序变为肤色→平滑度→皱纹,符合关注项前置要求。测试仅回放现有返回,未重新调用付费检测。
+36 -26
View File
@@ -14,7 +14,7 @@
"12": "server.js",
"13": "files",
"14": "showScreen",
"15": "TinyYolov2",
"15": "height",
"16": "巨子测肤 · 1'me 在线肌肤测试 — UI 设计规范",
"17": "extractFaces",
"18": "1'me肌肤状态检测 H5",
@@ -25,7 +25,7 @@
"23": "facepp-skinanalyze(旷视皮肤分析专业版调用技能)",
"24": "巨子测肤 · 1'me 语音播报内容设计文档 V1.0",
"25": "mobile-select.iife.min.js",
"26": "NeuralNetwork",
"26": "extractWeightsFactory",
"27": "gen_voice.py",
"28": "C",
"29": "face-api.js 本地化依赖(h5/vendor/faceapi/)",
@@ -37,16 +37,16 @@
"35": "customer-feedback.js",
"36": "actualFont",
"37": "UI 外发版 10.09 图片资源",
"38": "customer-feedback-fourth.mjs",
"38": "FaceProcessor",
"39": "UI交互界面调整",
"40": "第四轮反馈修改与验收",
"41": "js/beijing-time.js",
"42": "layer_at",
"43": "extractAllFacesAndComputeResults",
"44": "start-local.sh",
"45": "head-pose.js",
"46": ".forwardInput",
"47": ".toString",
"45": "NeuralNetwork",
"46": ".toString",
"47": "convLayer",
"48": "已修改",
"49": "mobile-select/README.md",
"50": "call_skinanalyze_3view.sh",
@@ -65,7 +65,7 @@
"63": "e2e-browser.mjs",
"64": "综合得分:三维度按 4:3:3 加权",
"65": "微信登录接入与公网部署说明",
"66": "h5/README.md",
"66": "七项护理选三与关注优先",
"67": "ui-20261009.mjs",
"68": "analyze-timeout.js",
"69": "misans/README.md",
@@ -73,11 +73,11 @@
"71": "报告顶部评分与年龄排版",
"72": "build_report_copy.py",
"73": "camera-geometry.js",
"74": "report-copy.js",
"74": "water-copy.js",
"75": "prepare_h5_assets.py",
"76": "报告页滑动防冲突-2026-10-13.md",
"77": "vizCompose",
"78": "height",
"77": "care-selection.js",
"78": "FaceFeatureExtractor",
"79": "登录反馈与加载优化(2026-10-12)",
"80": "线上微信登录部署记录",
"81": "wechat-production.mjs",
@@ -90,23 +90,23 @@
"88": "ui-20261012/manifest.json",
"89": "傅艳NEW与项思炼的会话 2026年10月12日.md",
"90": "customer-feedback.mjs",
"91": "wi",
"91": ".extractWeights",
"92": "客户反馈修改(2026-10-12)",
"93": "capture-diagnostics.js",
"93": "TinyXception",
"94": "第三轮反馈:PSD 对齐、验收及上线记录",
"95": "recommendation.js",
"95": ".forwardInput",
"96": "i",
"97": "account-client.js",
"98": "drawFaceExpressions",
"99": "normalize",
"98": ".runAndExtendWithFaceDetections",
"99": ".div",
"100": ".runAndExtendWithFaceDetection",
"101": "wechat-fixture.cjs",
"102": "report-persistence.mjs",
"102": "TinyFaceFeatureExtractor",
"103": "第四轮反馈/傅艳NEW与项思炼的会话 2026年10月12日.md",
"104": "export_fourth_feedback_assets.py",
"105": "capture-quality-browser.mjs",
"106": "第三轮反馈20261012/傅艳NEW与项思炼的会话 2026年10月12日.md",
"107": "基础信息记忆与肤质拖动",
"107": "h5/README.md",
"108": "profile-interaction.mjs",
"109": "submitPhotos",
"110": "startCamera",
@@ -115,11 +115,11 @@
"113": "swipe-track.js",
"114": "optimize_delivery_images.py",
"115": "痘痘按三视图各类型最大数量计分",
"116": "age",
"117": ".runAndExtendWithFaceDetections",
"116": ".predictAgeAndGender",
"117": "normalize",
"118": "f",
"119": "肤色文案与实际黑眼圈等级-2026-10-14.md",
"120": "AgeGenderNet",
"120": "disposeUnusedWeightTensors",
"121": ".draw",
"122": "doCapture",
"123": "评分圈内居中、基础信息页固定与正脸语音修复",
@@ -132,20 +132,30 @@
"130": "wrinkle-overall.js",
"131": "原始肌龄显示与鸿蒙滚轮兼容",
"132": "harmony-picker.mjs",
"133": "FaceLandmarks68",
"133": "onDetect",
"134": "camera-flip-browser.mjs",
"135": "恢复321与色斑案例复测",
"136": "js/profile-store.js",
"136": "age",
"137": "皱纹改用接口整体评分",
"138": "DetectSingleFaceLandmarksTask",
"139": ".mul",
"139": "private-case-preview.mjs",
"140": "AgeGenderNet",
"141": "start",
"142": "tone-dark-circle.js",
"143": "recommendation.js",
"144": "肤色项加入黑眼圈20%,色素图30%叠加",
"145": "PredictAllAgeAndGenderWithFaceAlignmentTask",
"146": "FaceLandmark68Net",
"147": "拍摄角度不合格震动提醒-2026-10-13.md",
"148": "PredictAllFaceExpressionsWithFaceAlignmentTask",
"149": "PredictSingleAgeAndGenderWithFaceAlignmentTask",
"150": "PredictSingleFaceExpressionsWithFaceAlignmentTask",
"153": "tone-overlay-browser.mjs"
"149": "report-persistence.mjs",
"150": ".forwardInput",
"151": "FaceRecognitionNet",
"152": "FaceExpressionNet",
"153": "tone-overlay-browser.mjs",
"154": "wi",
"155": "游客不留存与弹窗护理间距调整",
"156": "FaceLandmarks68",
"157": "水分文案独立分档",
"158": "drawFaceExpressions"
}
+1 -1
View File
@@ -1 +1 @@
{"0": "aa59fd8abc2eee29", "1": "cb6ea50bd4e4a5ca", "2": "fb3668625bb1d5ad", "3": "5bfb236350c342ee", "4": "4b1835ae3a82cf12", "5": "612f79173cb2d3b8", "6": "4b54405c5a282677", "7": "03b56b13a08b5f83", "8": "3d958eb3ed2a04df", "9": "e8cd0b51895b5f82", "10": "100a78535c7a8861", "11": "8fe2f9d63516be5c", "12": "cba798df74f6df27", "13": "7e3b558a587a4ebe", "14": "58417b287cc79e90", "15": "c96276581a8c98d4", "16": "95add6a975c0cb72", "17": "c25de97209f4e69b", "18": "f93d5d85e671fcee", "19": "76557988bbebbed8", "20": "aecf2fce44ba49c5", "21": "f4675fc43b3461fa", "22": "44afcd0daafd8e6d", "23": "90fac00055070ce2", "24": "f85b5b1634224c86", "25": "46e8db3e6e3f37ea", "26": "18ffa2c1feddfd10", "27": "eab427ce9a5219e9", "28": "b0aaa80168ccc897", "29": "c608f1c6492e1cb1", "30": "73c13793fca09f56", "31": "fbbcfc059751cc6a", "32": "f6bcf56c4589316b", "33": "75f750c5b7d9f63d", "34": "f024ff7942935690", "35": "241be5abeab1da6f", "36": "a7d375041779bad4", "37": "cb8f26249485324a", "38": "fbc8a72e6fda6359", "39": "fdd95375b4a5ddb3", "40": "5e546676ab96de03", "41": "a7a205c1adc39fe5", "42": "916170b9f5d7346b", "43": "ebf7b8a48916906e", "44": "852c1bb2446edd9c", "45": "0a373ad2197fd1a6", "46": "880a74c6d255e5cc", "47": "89e609b2678905d3", "48": "cf2f2424bc00436f", "49": "1f976711cda158dc", "50": "bbb2b57d72922b16", "51": "eb6a182204867275", "52": "2ec3c8c088568ba9", "53": "68c45056378341a7", "54": "c58d41e8e580fdc6", "55": "812950f51be20e40", "56": "c92bed61fc5b4cff", "57": "f2a40b5f1b29f669", "58": "08503e12d10e51b8", "59": "891f59cda3708718", "60": "a505b7de6257a4e8", "61": "5209ce2a267bf33b", "62": "1366ae24c7e5897d", "63": "c256b43f75d7809e", "64": "ad47fc38e493a4e2", "65": "bdee3c4a37df52f8", "66": "9727b30d720298e3", "67": "3e126d8deaf6e200", "68": "6b91f3679541de79", "69": "e511846eeba03076", "70": "be0c8310abda2467", "71": "589a2d5a76f3880a", "72": "c84f196a7bd35861", "73": "2b887bf601ccfb9f", "74": "ae2a154311e1f53a", "75": "69fdd23278518884", "76": "3c4c278d6026bc3a", "77": "288c2a98831b0021", "78": "aef6e0c179bd709d", "79": "c6cfdfe181232ecc", "80": "315325cde1352e98", "81": "37c559741825be74", "82": "200f25fbad7af8fb", "83": "1284897282eb47a3", "84": "d8bb296741d3d05c", "85": "7067e88534a91ab4", "86": "386c6fcc7be918e0", "87": "e5fa7717989eda80", "88": "8b5ab5232cef3457", "89": "9e3363b80eeceeea", "90": "dd1c246e19a2d032", "91": "83b0fd3b14363e4d", "92": "fced244d93677d90", "93": "9380a89a868c76d2", "94": "713d04996c4627df", "95": "e594ecaa8e7d1538", "96": "33b6c8b6c3ddaa73", "97": "505ea719b994ac5e", "98": "979dcc63399620ad", "99": "fbe2db75dbd18147", "100": "5a9014c4a7f87699", "101": "beb276de6301634c", "102": "e042d7fe7351bcb9", "103": "6ffbe07b521a061b", "104": "e114a7aebaaca2f1", "105": "af50c66d16c81bba", "106": "8bc72c179793e1c2", "107": "3877490226a78145", "108": "e0fc0a42e33bda91", "109": "4f73a56ac12b2cb5", "110": "cc7d97493f18fe9d", "111": "e8542da06408ad18", "112": "af995dd35403b4dd", "113": "0d99f86b9e41c4bb", "114": "b9d7c266afa84437", "115": "97af8c753c8d6322", "116": "388846e100846dbe", "117": "46204fb2e4d50c53", "118": "f5fa3e3d7d829c42", "119": "7485900c7157ebbd", "120": "711f3cc04ab3bf23", "121": "bf75bfc99d4f9e34", "122": "4f6d51a884cc3f06", "123": "b091e3eb82b456eb", "124": "df789af52163282e", "125": "e4f29216745ec758", "126": "d32da0afe8e66daa", "127": "006218e34885dbe0", "128": "1c2b248bd950f405", "129": "d52cfabd73e7593c", "130": "d724edff31ad0cb8", "131": "482dbb7ec33d8c23", "132": "3daacabc5062b554", "133": "ac00532d5d77886f", "134": "e7c90d6ef8276a1e", "135": "977cfc41f0c4b0b1", "136": "9b1cf1985223f90a", "137": "2e16ad304d257483", "138": "23f1d406f6c779ae", "139": "e6ef8c2f1b997e99", "142": "d1f85bf935ee51b4", "144": "cb06127f3461dec5", "145": "92ab3e5134ed8685", "146": "9a25b1230b5fe649", "147": "0e6cad06ebfcfb29", "148": "65718282b3c32109", "149": "01f68dfc34a29d32", "150": "8c471c7baeaaca54", "153": "23e1e56f6367f19e"}
{"0": "b79045c703a85f80", "1": "cb6ea50bd4e4a5ca", "2": "f87f9b389e268d5a", "3": "abf3aa1189cf8f1c", "4": "4b1835ae3a82cf12", "5": "32f813d0aff62d5d", "6": "4b54405c5a282677", "7": "03b56b13a08b5f83", "8": "3d958eb3ed2a04df", "9": "64a6935321c48bb6", "10": "bde8faf00828ab0a", "11": "8fe2f9d63516be5c", "12": "cba798df74f6df27", "13": "7e3b558a587a4ebe", "14": "767b88826fe02948", "15": "afdd5c2f54c40808", "16": "95add6a975c0cb72", "17": "139ebf070fc2a645", "18": "f93d5d85e671fcee", "19": "5272556cf61d08b3", "20": "aecf2fce44ba49c5", "21": "f4675fc43b3461fa", "22": "44afcd0daafd8e6d", "23": "90fac00055070ce2", "24": "f85b5b1634224c86", "25": "46e8db3e6e3f37ea", "26": "22869a4c94306e7b", "27": "eab427ce9a5219e9", "28": "832065bed4246769", "29": "c608f1c6492e1cb1", "30": "73c13793fca09f56", "31": "fbbcfc059751cc6a", "32": "f6bcf56c4589316b", "33": "75f750c5b7d9f63d", "34": "f024ff7942935690", "35": "241be5abeab1da6f", "36": "a7d375041779bad4", "37": "cb8f26249485324a", "38": "57344ed1fd5c260f", "39": "fdd95375b4a5ddb3", "40": "5e546676ab96de03", "41": "a7a205c1adc39fe5", "42": "916170b9f5d7346b", "43": "ebf7b8a48916906e", "44": "852c1bb2446edd9c", "45": "d96493a5f110bd4c", "46": "71f300fdfae6e5a6", "47": "8616cd6d33fcb6cb", "48": "cf2f2424bc00436f", "49": "1f976711cda158dc", "50": "bbb2b57d72922b16", "51": "eb6a182204867275", "52": "2ec3c8c088568ba9", "53": "68c45056378341a7", "54": "c58d41e8e580fdc6", "55": "812950f51be20e40", "56": "c92bed61fc5b4cff", "57": "f2a40b5f1b29f669", "58": "51036d6897035852", "59": "891f59cda3708718", "60": "a505b7de6257a4e8", "61": "5209ce2a267bf33b", "62": "1366ae24c7e5897d", "63": "c256b43f75d7809e", "64": "ad47fc38e493a4e2", "65": "bdee3c4a37df52f8", "66": "204c034e5abd63d5", "67": "3e126d8deaf6e200", "68": "6b91f3679541de79", "69": "e511846eeba03076", "70": "be0c8310abda2467", "71": "589a2d5a76f3880a", "72": "c84f196a7bd35861", "73": "2b887bf601ccfb9f", "74": "98e5dfa159a96c21", "75": "69fdd23278518884", "76": "3c4c278d6026bc3a", "77": "5bcfd82487d3f96b", "78": "a7d08d05c6f6461e", "79": "fe1396c4b34beeea", "80": "059d6ba8ca7ec3a0", "81": "37c559741825be74", "82": "200f25fbad7af8fb", "83": "1284897282eb47a3", "84": "d8bb296741d3d05c", "85": "7067e88534a91ab4", "86": "386c6fcc7be918e0", "87": "e5fa7717989eda80", "88": "8b5ab5232cef3457", "89": "9e3363b80eeceeea", "90": "dd1c246e19a2d032", "91": "9f638135a00ee9e5", "92": "fced244d93677d90", "93": "9beb9113cf6685a2", "94": "713d04996c4627df", "95": "880a74c6d255e5cc", "96": "54d7329fed675057", "97": "505ea719b994ac5e", "98": "f45aa61f1a2036bf", "99": "7ae4bb2556c18db2", "100": "5a9014c4a7f87699", "101": "beb276de6301634c", "102": "ec24a95b0cbd8486", "103": "6ffbe07b521a061b", "104": "e114a7aebaaca2f1", "105": "af50c66d16c81bba", "106": "8bc72c179793e1c2", "107": "62b55366408c54b3", "108": "e0fc0a42e33bda91", "109": "4afb1715f62865b1", "110": "e934052f1818c730", "111": "e8542da06408ad18", "112": "af995dd35403b4dd", "113": "0d99f86b9e41c4bb", "114": "b9d7c266afa84437", "115": "97af8c753c8d6322", "116": "a2b13160223b5a98", "117": "cc7621a6b501b8a2", "118": "64fc48534d3085ad", "119": "7485900c7157ebbd", "120": "f58b0adffbac7bf5", "121": "bf75bfc99d4f9e34", "122": "0552b1fe9ffd0332", "123": "b091e3eb82b456eb", "124": "df789af52163282e", "125": "e4f29216745ec758", "126": "d32da0afe8e66daa", "127": "006218e34885dbe0", "128": "1c2b248bd950f405", "129": "d52cfabd73e7593c", "130": "d724edff31ad0cb8", "131": "482dbb7ec33d8c23", "132": "3daacabc5062b554", "133": "d4869eb40daf536c", "134": "e7c90d6ef8276a1e", "135": "977cfc41f0c4b0b1", "136": "480b285d17853682", "137": "2e16ad304d257483", "138": "23f1d406f6c779ae", "139": "c5fa096607dfdbfa", "140": "14fdf4e4c496519f", "141": "a01bccd7f7c24c78", "142": "d1f85bf935ee51b4", "143": "e594ecaa8e7d1538", "144": "cb06127f3461dec5", "145": "92ab3e5134ed8685", "146": "9a25b1230b5fe649", "147": "0e6cad06ebfcfb29", "148": "624ff2146c7868e4", "149": "e042d7fe7351bcb9", "150": "15f58f7494ac2b87", "151": "b186b90877868930", "152": "e858d75e995c92e5", "153": "23e1e56f6367f19e", "154": "83b0fd3b14363e4d", "155": "8cd80516c6c469f5", "156": "68c52c80f38f8b2c", "157": "de7d5f558c2eaf13", "158": "a0bc88eb14b70170"}
+170 -127
View File
@@ -1,16 +1,16 @@
# Graph Report - juzi (2026-10-15)
## Corpus Check
- 140 files · ~4,961,406 words
- 147 files · ~5,059,946 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 1631 nodes · 3106 edges · 149 communities (119 shown, 30 thin omitted)
- Extraction: 93% EXTRACTED · 7% INFERRED · 0% AMBIGUOUS · INFERRED: 215 edges (avg confidence: 0.57)
- 1670 nodes · 3158 edges · 159 communities (126 shown, 33 thin omitted)
- Extraction: 93% EXTRACTED · 7% INFERRED · 0% AMBIGUOUS · INFERRED: 222 edges (avg confidence: 0.58)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `60ae6c97`
- Built from commit: `77493bdf`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@@ -29,7 +29,7 @@
- server.js
- files
- showScreen
- TinyYolov2
- height
- 巨子测肤 · 1'me 在线肌肤测试 — UI 设计规范
- extractFaces
- 1'me肌肤状态检测 H5
@@ -40,7 +40,7 @@
- facepp-skinanalyze(旷视皮肤分析专业版调用技能)
- 巨子测肤 · 1'me 语音播报内容设计文档 V1.0
- mobile-select.iife.min.js
- NeuralNetwork
- extractWeightsFactory
- gen_voice.py
- C
- face-api.js 本地化依赖(h5/vendor/faceapi/)
@@ -50,16 +50,16 @@
- build-fixture.py
- customer-feedback.js
- UI 外发版 10.09 图片资源
- customer-feedback-fourth.mjs
- FaceProcessor
- UI交互界面调整
- 第四轮反馈修改与验收
- js/beijing-time.js
- layer_at
- extractAllFacesAndComputeResults
- start-local.sh
- head-pose.js
- .forwardInput
- NeuralNetwork
- .toString
- convLayer
- 已修改
- mobile-select/README.md
- call_skinanalyze_3view.sh
@@ -75,16 +75,17 @@
- e2e-browser.mjs
- 综合得分:三维度按 4:3:3 加权
- 微信登录接入与公网部署说明
- h5/README.md
- 七项护理选三与关注优先
- ui-20261009.mjs
- analyze-timeout.js
- misans/README.md
- scan-placeholder.mjs
- 报告顶部评分与年龄排版
- build_report_copy.py
- water-copy.js
- 报告页滑动防冲突-2026-10-13.md
- vizCompose
- height
- care-selection.js
- FaceFeatureExtractor
- 登录反馈与加载优化(2026-10-12)
- 线上微信登录部署记录
- wechat-production.mjs
@@ -96,32 +97,32 @@
- 产品推荐规则:肤龄基准与边界调整
- ui-20261012/manifest.json
- 傅艳NEW与项思炼的会话 2026年10月12日.md
- wi
- .extractWeights
- 客户反馈修改(2026-10-12)
- capture-diagnostics.js
- TinyXception
- 第三轮反馈:PSD 对齐、验收及上线记录
- recommendation.js
- .forwardInput
- i
- account-client.js
- drawFaceExpressions
- normalize
- .runAndExtendWithFaceDetections
- .div
- .runAndExtendWithFaceDetection
- wechat-fixture.cjs
- report-persistence.mjs
- TinyFaceFeatureExtractor
- 第四轮反馈/傅艳NEW与项思炼的会话 2026年10月12日.md
- 第三轮反馈20261012/傅艳NEW与项思炼的会话 2026年10月12日.md
- 基础信息记忆与肤质拖动
- h5/README.md
- profile-interaction.mjs
- submitPhotos
- startCamera
- color-copy.js
- 微信退出登录-2026-10-13.md
- 痘痘按三视图各类型最大数量计分
- age
- .runAndExtendWithFaceDetections
- .predictAgeAndGender
- normalize
- f
- 肤色文案与实际黑眼圈等级-2026-10-14.md
- AgeGenderNet
- disposeUnusedWeightTensors
- .draw
- doCapture
- 评分圈内居中、基础信息页固定与正脸语音修复
@@ -133,20 +134,30 @@
- wrinkle-overall.js
- 原始肌龄显示与鸿蒙滚轮兼容
- harmony-picker.mjs
- FaceLandmarks68
- onDetect
- 恢复321与色斑案例复测
- js/profile-store.js
- age
- 皱纹改用接口整体评分
- DetectSingleFaceLandmarksTask
- .mul
- private-case-preview.mjs
- AgeGenderNet
- start
- tone-dark-circle.js
- recommendation.js
- 肤色项加入黑眼圈20%,色素图30%叠加
- PredictAllAgeAndGenderWithFaceAlignmentTask
- FaceLandmark68Net
- 拍摄角度不合格震动提醒-2026-10-13.md
- PredictAllFaceExpressionsWithFaceAlignmentTask
- PredictSingleAgeAndGenderWithFaceAlignmentTask
- PredictSingleFaceExpressionsWithFaceAlignmentTask
- report-persistence.mjs
- .forwardInput
- FaceRecognitionNet
- FaceExpressionNet
- wi
- 游客不留存与弹窗护理间距调整
- FaceLandmarks68
- 水分文案独立分档
- drawFaceExpressions
## God Nodes (most connected - your core abstractions)
1. `__awaiter()` - 59 edges
@@ -175,31 +186,31 @@
## Import Cycles
- None detected.
## Communities (149 total, 30 thin omitted)
## Communities (159 total, 33 thin omitted)
### Community 0 - "face-api.js"
Cohesion: 0.02
Nodes (37): bgrToRgbTensor(), cn(), computeReshapedDimensions(), createBrowserEnv(), createFileSystem(), createNodejsEnv(), Dimensions(), dr() (+29 more)
Nodes (39): cn(), computeReshapedDimensions(), createBrowserEnv(), createFileSystem(), createNodejsEnv(), Dimensions(), dr(), en() (+31 more)
### Community 1 - "线上闭环测试(不含真实微信授权)"
Cohesion: 0.25
Nodes (7): 发现并修复, 可复现命令, 已通过的检查, 线上闭环测试(不含真实微信授权), 结论, 话术及仓库同步, 验证边界
### Community 2 - "app.js"
Cohesion: 0.12
Nodes (19): applyMirror(), cancelSkinDrag(), decode(), drain(), effMirror(), goSkin(), load(), loginAllowed() (+11 more)
Cohesion: 0.11
Nodes (22): applyMirror(), cancelSkinDrag(), effMirror(), esc(), goSkin(), loginAllowed(), loginWechat(), openHistory() (+14 more)
### Community 3 - "skin-score.js"
Cohesion: 0.07
Nodes (59): acneGrade(), buildViewModel(), calcAcne(), calcContour(), calcSens(), calcSmooth(), calcTone(), calcWrinkles() (+51 more)
Nodes (63): acneGrade(), buildViewModel(), calcAcne(), calcContour(), calcSens(), calcSmooth(), calcTone(), calcWrinkles() (+55 more)
### Community 4 - "customer-feedback-second.js"
Cohesion: 0.14
Nodes (11): assert, context, events, face, fs, geometry, original, path (+3 more)
### Community 5 - "extractFaceTensors"
Cohesion: 0.10
Nodes (12): Box(), DetectAllFaceLandmarksTask(), extendWithFaceDetection(), extendWithFaceLandmarks(), extractFaceTensors(), FaceLandmarks(), isTensor3D(), isWithFaceDetection() (+4 more)
Cohesion: 0.11
Nodes (8): bgrToRgbTensor(), Box(), DetectAllFaceLandmarksTask(), extendWithFaceLandmarks(), extractFaceTensors(), FaceLandmarks(), minBbox(), ObjectDetection()
### Community 7 - "test/profile-store.js"
Cohesion: 0.14
@@ -210,12 +221,12 @@ Cohesion: 0.19
Nodes (31): ageText(), api(), bind(), captureQualityHtml(), closeDetail(), collectFilters(), dimsHtml(), doRetry() (+23 more)
### Community 9 - "__awaiter"
Cohesion: 0.10
Nodes (17): __awaiter(), ComposableTask(), FaceRecognitionNet(), fetchImage(), fetchJson(), fetchNetWeights(), fetchOrThrow(), __generator() (+9 more)
Cohesion: 0.14
Nodes (13): __awaiter(), bufferToImage(), ComposableTask(), fetchImage(), fetchJson(), fetchNetWeights(), fetchOrThrow(), __generator() (+5 more)
### Community 10 - "n"
Cohesion: 0.11
Nodes (38): ae(), be(), bf(), bi(), ce(), de(), e(), Ef() (+30 more)
Cohesion: 0.12
Nodes (36): ae(), be(), bf(), bi(), ce(), de(), e(), Ef() (+28 more)
### Community 11 - "lib/accounts.js"
Cohesion: 0.06
@@ -230,24 +241,28 @@ Cohesion: 0.12
Nodes (15): files, analyze-fail, analyzing, countdown-321, hint-far, hint-front, hint-hold, hint-near (+7 more)
### Community 14 - "showScreen"
Cohesion: 0.27
Nodes (10): enterGuest(), loadAppFont(), loginSucceeded(), logoutWechat(), openOverlay(), prepareScanArtwork(), renderAccountStatus(), resetReport() (+2 more)
Cohesion: 0.19
Nodes (13): enterGuest(), loadAppFont(), loginSucceeded(), logoutWechat(), openOverlay(), prepareScanArtwork(), renderAccountStatus(), resetReport() (+5 more)
### Community 15 - "height"
Cohesion: 0.24
Nodes (6): height(), FaceLandmark68NetBase(), getSizesForScale(), normalize$1(), rescaleAndNormalize(), TinyYolov2Base()
### Community 16 - "巨子测肤 · 1'me 在线肌肤测试 — UI 设计规范"
Cohesion: 0.15
Nodes (12): 1. 基准与尺寸换算, 2. 色彩规范, 3. 字体规范, 4. 组件切图(assets/svg/), 5. 图片素材(assets/images/,共 15 张), 6. 页面清单(design/ 下有整屏 PNG 预览), 7. 目录结构, 8. 还原建议 (+4 more)
### Community 17 - "extractFaces"
Cohesion: 0.21
Nodes (17): awaitMediaLoaded(), bufferToImage(), createCanvas(), createCanvasFromMedia(), extractFaces(), extractImagePatches(), getContext2dOrThrow(), getEnv() (+9 more)
Cohesion: 0.20
Nodes (18): awaitMediaLoaded(), createCanvas(), createCanvasFromMedia(), extractFaces(), extractImagePatches(), getContext2dOrThrow(), getEnv(), getMediaDimensions() (+10 more)
### Community 18 - "1'me肌肤状态检测 H5"
Cohesion: 0.13
Nodes (15): 10.09 更新, 1'me肌肤状态检测 H5, Admin API(统一返回 `{ok:false,message}` 错误), Windows 本机部署(提测用), 后台查看(admin.html), 密钥配置, 已知占位清单, 技术说明 (+7 more)
### Community 19 - "customer-feedback-fourth.js"
Cohesion: 0.13
Nodes (16): assert, brokenLandmarks, check(), Diagnostics, face(), HeadPose, held, ReportUI (+8 more)
Cohesion: 0.06
Nodes (38): evaluate(), finite(), assertPoints(), check(), clamp(), dist(), estimate(), makeFrontal() (+30 more)
### Community 20 - "build_fixtures.py"
Cohesion: 0.40
@@ -269,9 +284,9 @@ Nodes (8): 1. 音频格式约定(已定), 2. 播报清单(14 条), 3. 播放策
Cohesion: 0.20
Nodes (16): _arrayLikeToArray(), _arrayWithHoles(), _arrayWithoutHoles(), _classCallCheck(), _createClass(), _defineProperties(), h(), _iterableToArray() (+8 more)
### Community 26 - "NeuralNetwork"
Cohesion: 0.06
Nodes (28): createFaceDetectionNet(), createFaceRecognitionNet(), createMtcnn(), createSsdMobilenetv1(), createTinyFaceDetector(), createTinyYolov2(), extractConvParamsFactory(), extractFCParamsFactory() (+20 more)
### Community 26 - "extractWeightsFactory"
Cohesion: 0.21
Nodes (13): extractConvParamsFactory(), extractFCParamsFactory(), extractorsFactory(), extractorsFactory$1(), extractorsFactory$6(), extractorsFactory$8(), extractParams(), extractParams$1() (+5 more)
### Community 27 - "gen_voice.py"
Cohesion: 0.30
@@ -279,7 +294,7 @@ Nodes (11): ndarray, Path, gen_countdown(), main(), normalize_clip(), 倒计时
### Community 28 - "C"
Cohesion: 0.09
Nodes (33): ao(), Bo(), C(), cd(), cl(), co(), Do(), eo() (+25 more)
Nodes (32): ao(), Bo(), C(), cd(), co(), Do(), eo(), fo() (+24 more)
### Community 29 - "face-api.js 本地化依赖(h5/vendor/faceapi/)"
Cohesion: 0.29
@@ -301,10 +316,6 @@ Nodes (27): alternate, approved, assert, crop, details, excellent, fs, model (+1
Cohesion: 0.40
Nodes (4): UI 外发版 10.09 图片资源, 前端素材对应, 文件位置, 重新导出
### Community 38 - "customer-feedback-fourth.mjs"
Cohesion: 0.20
Nodes (8): evaluate(), finite(), CaptureQuality, child, dataRoot, h5, ReportUI, require
### Community 39 - "UI交互界面调整"
Cohesion: 0.40
Nodes (4): 1meAI测肤旷视UI问题反馈, UI交互界面调整, 交互, **设计**
@@ -329,17 +340,13 @@ Nodes (8): ComputeAllFaceDescriptorsTask(), ComputeSingleFaceDescriptorTask(), e
Cohesion: 0.50
Nodes (3): HOST, PORT, start-local.sh script
### Community 45 - "head-pose.js"
Cohesion: 0.33
Nodes (8): assertPoints(), check(), clamp(), dist(), estimate(), makeFrontal(), makeSide(), midpoint()
### Community 46 - ".toString"
Cohesion: 0.12
Nodes (11): FaceLandmarks5(), FaceMatch(), getCenterPoint(), la(), Point(), pr(), pt(), vt() (+3 more)
### Community 46 - ".forwardInput"
Cohesion: 0.22
Nodes (9): boxPredictionLayer(), decodeBoxesLayer(), depthwiseConvLayer(), getCenterCoordinatesAndSizesLayer(), getStridesForLayerIdx(), mobileNetV1(), outputLayer(), pointwiseConvLayer() (+1 more)
### Community 47 - ".toString"
Cohesion: 0.20
Nodes (7): FaceLandmarks5(), FaceMatch(), getCenterPoint(), la(), pr(), pt(), vt()
### Community 47 - "convLayer"
Cohesion: 0.43
Nodes (8): convLayer(), fullyConnectedLayer(), ONet(), PNet(), prelu(), qh(), RNet(), sharedLayer()
### Community 48 - "已修改"
Cohesion: 0.22
@@ -366,8 +373,8 @@ Cohesion: 0.60
Nodes (5): diffCtx(), loadJSON(), main(), okNum(), okStr()
### Community 58 - ".forwardInput"
Cohesion: 0.21
Nodes (8): iou(), NetInput(), nonMaxSuppression(), nonMaxSuppression$1(), scale(), stage1(), stage2(), stage3()
Cohesion: 0.15
Nodes (9): iou(), nonMaxSuppression(), nonMaxSuppression$1(), scale(), SsdMobilenetv1(), stage1(), stage2(), stage3() (+1 more)
### Community 61 - "报告布局与跟手交互调整"
Cohesion: 0.33
@@ -389,9 +396,9 @@ Nodes (4): 已确认公式, 本地验证, 生效位置, 综合得分:三维度
Cohesion: 0.29
Nodes (7): 公网服务器配置, 公网真机验收清单, 反向代理与后台保护, 已完成的链路, 微信登录接入与公网部署说明, 数据、迁移与备份, 本机开发环境限制(不代表线上状态)
### Community 66 - "h5/README.md"
Cohesion: 0.33
Nodes (3): 发版范围, 游客不留存与弹窗护理间距调整, 验证
### Community 66 - "七项护理选三与关注优先"
Cohesion: 0.50
Nodes (3): 七项护理选三与关注优先, 真实案例回放, 验证
### Community 68 - "analyze-timeout.js"
Cohesion: 0.20
@@ -401,20 +408,20 @@ Nodes (7): assert, end, fs, path, source, start, vm
Cohesion: 0.29
Nodes (3): errors, samples, unexpectedWrites
### Community 77 - "vizCompose"
Cohesion: 0.14
Nodes (14): esc(), openHistory(), renderHistoryList(), renderReport(), renderViz(), setDim(), vizCollectMarks(), vizCompose() (+6 more)
### Community 74 - "water-copy.js"
Cohesion: 0.22
Nodes (6): assert, Copy, crossed, example, SS, UI
### Community 78 - "height"
Cohesion: 0.32
Nodes (5): height(), FaceLandmark68NetBase(), getSizesForScale(), normalize$1(), rescaleAndNormalize()
### Community 77 - "care-selection.js"
Cohesion: 0.24
Nodes (9): assert, check(), Copy, heavy, keys, sample(), SS, UI (+1 more)
### Community 79 - "登录反馈与加载优化(2026-10-12)"
Cohesion: 0.25
Nodes (5): 体验变化, 公网部署后复测, 慢的原因与处理, 登录反馈与加载优化(2026-10-12), 验证
### Community 80 - "线上微信登录部署记录"
Cohesion: 0.33
Cohesion: 0.29
Nodes (6): 初次部署时的凭据查找记录(现已由用户提供), 初次部署的线上检查(历史结果), 后续更新:已配置真实凭据, 已执行, 用户确认加入白名单后的复测, 线上微信登录部署记录
### Community 81 - "wechat-production.mjs"
@@ -445,41 +452,37 @@ Nodes (6): 产品推荐规则:肤龄基准与边界调整, 决策顺序, 后
Cohesion: 0.22
Nodes (8): assets, design_scale, document_size, native_header_px, shape_metrics, source, source_sha256, text_styles
### Community 91 - "wi"
Cohesion: 0.28
Nodes (9): Ci(), Ei(), Fi(), Ii(), ki(), pe(), ri(), wi() (+1 more)
### Community 91 - ".extractWeights"
Cohesion: 0.20
Nodes (8): createFaceDetectionNet(), createFaceRecognitionNet(), createMtcnn(), createSsdMobilenetv1(), createTinyFaceDetector(), createTinyYolov2(), extractorsFactory$4(), extractParams$5()
### Community 92 - "客户反馈修改(2026-10-12)"
Cohesion: 0.33
Nodes (5): 公网部署与复测, 客户反馈修改(2026-10-12), 皱纹数据说明, 逐项处理, 验证
### Community 93 - "capture-diagnostics.js"
Cohesion: 0.38
Nodes (6): assess(), CaptureQuality, HeadPose, normalize(), scalar(), views
### Community 94 - "第三轮反馈:PSD 对齐、验收及上线记录"
Cohesion: 0.33
Nodes (5): 原稿与导出可追溯性, 已完成, 本地验证, 生产部署及只读验收, 第三轮反馈:PSD 对齐、验收及上线记录
### Community 95 - "recommendation.js"
Cohesion: 0.33
Nodes (6): assert, check(), missing, raw, SS, UI
### Community 95 - ".forwardInput"
Cohesion: 0.22
Nodes (9): boxPredictionLayer(), decodeBoxesLayer(), depthwiseConvLayer(), getCenterCoordinatesAndSizesLayer(), getStridesForLayerIdx(), mobileNetV1(), outputLayer(), pointwiseConvLayer() (+1 more)
### Community 96 - "i"
Cohesion: 0.07
Nodes (36): b(), ba(), bn(), ca(), da(), dn(), ee(), et() (+28 more)
Cohesion: 0.08
Nodes (31): ba(), bn(), ca(), cl(), da(), dn(), ee(), et() (+23 more)
### Community 97 - "account-client.js"
Cohesion: 0.53
Nodes (8): browser(), clearGuestCache(), ensureGuest(), guest(), init(), logout(), rememberKind(), request()
### Community 98 - "drawFaceExpressions"
Cohesion: 0.22
Nodes (6): DrawBox(), drawDetections(), drawFaceExpressions(), DrawFaceLandmarks(), FaceExpressions(), isWithFaceExpressions()
### Community 98 - ".runAndExtendWithFaceDetections"
Cohesion: 0.20
Nodes (8): DetectAllFacesTask(), DrawBox(), drawDetections(), DrawFaceLandmarks(), extendWithFaceDetection(), isWithFaceDetection(), isWithFaceLandmarks(), resizeResults()
### Community 99 - "normalize"
Cohesion: 0.09
Nodes (26): conv(), conv$1(), convDown(), convLayer(), convLayer$1(), convNoRelu(), convWithBatchNorm(), denseBlock3() (+18 more)
### Community 99 - ".div"
Cohesion: 0.31
Nodes (6): conv(), denseBlock3(), denseBlock4(), depthwiseSeparableConv(), mainBlock(), reductionBlock()
### Community 100 - ".runAndExtendWithFaceDetection"
Cohesion: 0.33
@@ -489,25 +492,21 @@ Nodes (5): allFacesMtcnn(), allFacesSsdMobilenetv1(), allFacesTinyYolov2(), dete
Cohesion: 0.50
Nodes (3): crypto, fs, path
### Community 102 - "report-persistence.mjs"
Cohesion: 0.29
Nodes (6): child, dataRoot, fx, h5, ReportUI, require
### Community 107 - "基础信息记忆与肤质拖动"
Cohesion: 0.40
Nodes (4): 回归命令, 基础信息记忆与肤质拖动, 肤质交互, 范围
### Community 107 - "h5/README.md"
Cohesion: 0.22
Nodes (6): 回归命令, 基础信息记忆与肤质拖动, 肤质交互, 范围, 真实案例回放(当前规则), 透亮感改为三项独立最低分
### Community 108 - "profile-interaction.mjs"
Cohesion: 0.40
Nodes (3): dragBy(), h5, touch()
### Community 109 - "submitPhotos"
Cohesion: 0.22
Nodes (13): bindBackdropClose(), closeLoginSuccess(), closeOverlay(), handleCamHidden(), leaveCamera(), photosByView(), popHistorySilently(), requestAnalyze() (+5 more)
Cohesion: 0.24
Nodes (12): bindBackdropClose(), closeLoginSuccess(), closeOverlay(), handleCamHidden(), leaveCamera(), photosByView(), popHistorySilently(), requestAnalyze() (+4 more)
### Community 110 - "startCamera"
Cohesion: 0.22
Nodes (13): beginDetect(), detTick(), enableFallback(), getCamStream(), loadFaceApi(), loadModels(), maybeStartDetect(), onCamTrackEnded() (+5 more)
Cohesion: 0.17
Nodes (16): beginDetect(), detTick(), enableFallback(), getCamStream(), hideCamStatus(), loadFaceApi(), loadModels(), maybeStartDetect() (+8 more)
### Community 111 - "color-copy.js"
Cohesion: 0.25
@@ -517,21 +516,25 @@ Nodes (7): assert, descriptions, missing, SS, templates, UI, v
Cohesion: 0.40
Nodes (4): 后台和历史, 痘痘按三视图各类型最大数量计分, 规则, 验证
### Community 116 - "age"
Cohesion: 0.13
Nodes (6): age(), extendWithAge(), extendWithGender(), FaceExpressionNet(), PredictAllAgeAndGenderTask(), PredictSingleAgeAndGenderTask()
### Community 116 - ".predictAgeAndGender"
Cohesion: 0.28
Nodes (4): extendWithAge(), extendWithGender(), PredictAllAgeAndGenderTask(), PredictSingleAgeAndGenderTask()
### Community 117 - "normalize"
Cohesion: 0.36
Nodes (8): conv$1(), convDown(), convLayer$1(), convNoRelu(), normalize(), residual(), residualDown(), __spreadArrays()
### Community 118 - "f"
Cohesion: 0.20
Nodes (24): a(), cf(), d(), drawContour(), dt(), ea(), f(), g() (+16 more)
Cohesion: 0.15
Nodes (30): a(), b(), cf(), d(), drawContour(), dt(), ea(), f() (+22 more)
### Community 120 - "AgeGenderNet"
Cohesion: 0.07
Nodes (26): AgeGenderNet(), disposeUnusedWeightTensors(), extractorsFactory$3(), extractorsFactory$5(), extractorsFactory$7(), extractorsFactory$9(), extractParamsFromWeigthMap(), extractParamsFromWeigthMap$1() (+18 more)
### Community 120 - "disposeUnusedWeightTensors"
Cohesion: 0.12
Nodes (23): disposeUnusedWeightTensors(), extractorsFactory$3(), extractorsFactory$5(), extractorsFactory$7(), extractorsFactory$9(), extractParamsFromWeigthMap(), extractParamsFromWeigthMap$1(), extractParamsFromWeigthMap$2() (+15 more)
### Community 122 - "doCapture"
Cohesion: 0.14
Nodes (27): afterCurrent(), armNudge(), busyWithFlow(), captureFromVideo(), captureHaptic(), captureStageHint(), checkStill(), detectFrame() (+19 more)
Cohesion: 0.21
Nodes (17): afterCurrent(), armNudge(), busyWithFlow(), captureFromVideo(), captureStageHint(), doCapture(), dropHint(), handleCamVisible() (+9 more)
### Community 123 - "评分圈内居中、基础信息页固定与正脸语音修复"
Cohesion: 0.40
@@ -561,44 +564,84 @@ Nodes (6): assert, base, changed, onlyOverall, SS, UI
Cohesion: 0.50
Nodes (3): 原始肌龄显示与鸿蒙滚轮兼容, 回归, 范围
### Community 133 - "onDetect"
Cohesion: 0.32
Nodes (8): captureHaptic(), checkStill(), detectFrame(), faceLuma(), faceOffsetX(), onDetect(), resetAngleHaptic(), updateAngleHaptic()
### Community 135 - "恢复321与色斑案例复测"
Cohesion: 0.50
Nodes (3): 恢复321与色斑案例复测, 拍摄提示音, 真实色斑案例
### Community 136 - "js/profile-store.js"
Cohesion: 0.67
Nodes (3): clean(), create(), key()
### Community 136 - "age"
Cohesion: 0.36
Nodes (4): age(), clean(), create(), key()
### Community 137 - "皱纹改用接口整体评分"
Cohesion: 0.50
Nodes (3): 案例复算, 皱纹改用接口整体评分, 验证
### Community 139 - ".mul"
Cohesion: 0.50
Nodes (3): wl(), wn(), xn()
### Community 139 - "private-case-preview.mjs"
Cohesion: 0.29
Nodes (6): fixture, mime, photos, raw, root, server
### Community 141 - "start"
Cohesion: 0.29
Nodes (7): decode(), drain(), load(), preload(), start(), stopCur(), unlock()
### Community 142 - "tone-dark-circle.js"
Cohesion: 0.33
Nodes (5): a, assert, near(), SS, UI
### Community 143 - "recommendation.js"
Cohesion: 0.33
Nodes (6): assert, check(), missing, raw, SS, UI
### Community 144 - "肤色项加入黑眼圈20%,色素图30%叠加"
Cohesion: 0.40
Nodes (4): 图像, 用户确认的评分口径, 肤色项加入黑眼圈20%,色素图30%叠加, 验证
### Community 148 - "PredictAllFaceExpressionsWithFaceAlignmentTask"
Cohesion: 0.17
Nodes (3): PredictAllFaceExpressionsWithFaceAlignmentTask(), PredictSingleAgeAndGenderWithFaceAlignmentTask(), PredictSingleFaceExpressionsWithFaceAlignmentTask()
### Community 149 - "report-persistence.mjs"
Cohesion: 0.29
Nodes (6): child, dataRoot, fx, h5, ReportUI, require
### Community 150 - ".forwardInput"
Cohesion: 0.47
Nodes (3): convWithBatchNorm(), depthwiseSeparableConv$1(), leaky()
### Community 151 - "FaceRecognitionNet"
Cohesion: 0.33
Nodes (3): extractorsFactory$2(), extractParams$4(), FaceRecognitionNet()
### Community 154 - "wi"
Cohesion: 0.28
Nodes (9): Ci(), Ei(), Fi(), Ii(), ki(), pe(), ri(), wi() (+1 more)
### Community 155 - "游客不留存与弹窗护理间距调整"
Cohesion: 0.50
Nodes (3): 发版范围, 游客不留存与弹窗护理间距调整, 验证
### Community 158 - "drawFaceExpressions"
Cohesion: 0.50
Nodes (3): drawFaceExpressions(), FaceExpressions(), isWithFaceExpressions()
## Knowledge Gaps
- **418 isolated node(s):** `source`, `document_size`, `pages`, `assets`, `layers` (+413 more)
- **441 isolated node(s):** `source`, `document_size`, `pages`, `assets`, `layers` (+436 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **30 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
- **33 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `height()` connect `height` to `app.js`, `customer-feedback.js`, `extractFaceTensors`, `scan-placeholder.mjs`, `__awaiter`, `profile-interaction.mjs`, `showScreen`, `extractFaces`, `.forwardInput`?**
_High betweenness centrality (0.053) - this node is a cross-community bridge._
- **Why does `height()` connect `height` to `app.js`, `customer-feedback.js`, `extractFaceTensors`, `scan-placeholder.mjs`, `profile-interaction.mjs`, `showScreen`, `extractFaces`, `.forwardInput`?**
_High betweenness centrality (0.044) - this node is a cross-community bridge._
- **Why does `r()` connect `n` to `face-api.js`, `i`, `server.js`, `.toString`, `normalize`, `f`, `C`?**
_High betweenness centrality (0.033) - this node is a cross-community bridge._
- **Why does `n()` connect `n` to `face-api.js`, `i`, `skin-score.js`, `server.js`, `acne-three-view.js`, `f`, `C`?**
_High betweenness centrality (0.038) - this node is a cross-community bridge._
- **Why does `r()` connect `n` to `face-api.js`, `i`, `normalize`, `server.js`, `.toString`, `f`, `C`?**
_High betweenness centrality (0.034) - this node is a cross-community bridge._
_High betweenness centrality (0.028) - this node is a cross-community bridge._
- **Are the 26 inferred relationships involving `n()` (e.g. with `calcAcne()` and `newRecordId()`) actually correct?**
_`n()` has 26 INFERRED edges - model-reasoned connections that need verification._
- **Are the 27 inferred relationships involving `r()` (e.g. with `handleExport()` and `handleRecordsQuery()`) actually correct?**
@@ -606,4 +649,4 @@ _Questions this graph is uniquely positioned to answer:_
- **Are the 10 inferred relationships involving `i()` (e.g. with `report-persistence.mjs` and `da()`) actually correct?**
_`i()` has 10 INFERRED edges - model-reasoned connections that need verification._
- **What connects `source`, `document_size`, `pages` to the rest of the system?**
_418 weakly-connected nodes found - possible documentation gaps or missing edges._
_441 weakly-connected nodes found - possible documentation gaps or missing edges._
File diff suppressed because one or more lines are too long
+1824 -801
View File
File diff suppressed because it is too large Load Diff
+81 -26
View File
@@ -50,13 +50,13 @@
"semantic_hash": ""
},
"h5/js/skin-score.js": {
"mtime": 1791992633.3478043,
"ast_hash": "13c216059688fd865d236fdfb4e78fd7",
"mtime": 1792046332.1885464,
"ast_hash": "96afdc807eaf06bd861a39f39ed119df",
"semantic_hash": ""
},
"h5/server.js": {
"mtime": 1791992633.3478043,
"ast_hash": "9006db8d78051713084a8f9e292f594e",
"mtime": 1792046437.6590993,
"ast_hash": "a7b6deccacb03cff6c0dc6ab9764ae16",
"semantic_hash": ""
},
"h5/test/build_fixtures.py": {
@@ -80,8 +80,8 @@
"semantic_hash": ""
},
"h5/test/regression.js": {
"mtime": 1791991599.1785402,
"ast_hash": "8a6ae12fb62288d2bb02655d9c21ade9",
"mtime": 1792046332.1885464,
"ast_hash": "4c7998dc9962ce109bde7bc29891d538",
"semantic_hash": ""
},
"h5/vendor/faceapi/face-api.js": {
@@ -135,8 +135,8 @@
"semantic_hash": ""
},
"h5/README.md": {
"mtime": 1791992669.0684593,
"ast_hash": "b1eae85ed8ef9cfadd00de923468cbbb",
"mtime": 1792046412.3080864,
"ast_hash": "5e60028c676088d6e62d373ff94f20c7",
"semantic_hash": ""
},
"h5/index.html": {
@@ -815,8 +815,8 @@
"semantic_hash": ""
},
"h5/js/admin.js": {
"mtime": 1791992633.351766,
"ast_hash": "7cbc57b137513efef5af59ecc92c3f10",
"mtime": 1792046275.1971712,
"ast_hash": "339073665d8a01c84d169ca696a108b5",
"semantic_hash": ""
},
"h5/js/region-data.js": {
@@ -1240,13 +1240,13 @@
"semantic_hash": ""
},
"h5/js/report-copy.js": {
"mtime": 1791993447.3913798,
"ast_hash": "452c90f7bbe8dc3afc842d6ff1a286e2",
"mtime": 1792044306.7377708,
"ast_hash": "ac8f1225149a88902b2b35476e1258f5",
"semantic_hash": ""
},
"h5/js/report-ui.js": {
"mtime": 1791997508.305589,
"ast_hash": "ef6df0e12065dfc4d157494891038268",
"mtime": 1792044306.7105672,
"ast_hash": "866badf68a4534927262c037eec35eca",
"semantic_hash": ""
},
"h5/lib/accounts.js": {
@@ -1260,8 +1260,8 @@
"semantic_hash": ""
},
"h5/test/report-persistence.mjs": {
"mtime": 1791992713.7256725,
"ast_hash": "904ec9d4daa04b068b972cd9037615bb",
"mtime": 1792046332.1885464,
"ast_hash": "8f79e7750a0565508e487e1d3b34e6f3",
"semantic_hash": ""
},
"h5/test/ui-20261009.mjs": {
@@ -1300,8 +1300,8 @@
"semantic_hash": ""
},
"reference/AI\u6d4b\u80a4\u62a5\u544a\u9875\u8bdd\u672f\u4fee\u6539.md": {
"mtime": 1791993479.4296343,
"ast_hash": "fd4e8d0621fb67015be0a150fac32a4c",
"mtime": 1792044306.7105672,
"ast_hash": "1ecb0769c53f7af44c991ba099359fc6",
"semantic_hash": ""
},
"design/ui-20261009/audit-current/01-splash.png": {
@@ -2085,8 +2085,8 @@
"semantic_hash": ""
},
"h5/test/tone-dark-circle.js": {
"mtime": 1791993447.323378,
"ast_hash": "16091c603e1bec56af80210946670527",
"mtime": 1792046293.4017394,
"ast_hash": "2c783245bf43ef7173210418a76de600",
"semantic_hash": ""
},
"h5/test/tone-overlay-browser.mjs": {
@@ -2120,8 +2120,8 @@
"semantic_hash": ""
},
"h5/test/color-copy.js": {
"mtime": 1791993447.3273783,
"ast_hash": "4777441d7249d042cb9d48da67ac5c74",
"mtime": 1792044306.7105672,
"ast_hash": "f1e8ae0f65f1cd17263c171c6435e063",
"semantic_hash": ""
},
"docs/\u80a4\u8272\u6587\u6848\u4e0e\u5b9e\u9645\u9ed1\u773c\u5708\u7b49\u7ea7-2026-10-14.md": {
@@ -2130,13 +2130,13 @@
"semantic_hash": ""
},
"h5/test/report-bottom-figma.mjs": {
"mtime": 1791998797.7419598,
"ast_hash": "81af5b52ef2b0b1d14f9ffb6159f2d3a",
"mtime": 1791999287.71825,
"ast_hash": "ebde53e93a34e4ed5f877c65af1456b0",
"semantic_hash": ""
},
"docs/\u62a5\u544a\u5e95\u90e8Figma\u8fd8\u539f-2026-10-15.md": {
"mtime": 1791998656.0611603,
"ast_hash": "ce4a94e4026ebc2c410f76f361a24ca1",
"mtime": 1791999287.71825,
"ast_hash": "b72dae0ed84a076c0d8f99dcebe9bbeb",
"semantic_hash": ""
},
"h5/assets/img/ui-20261015/product-25.png": {
@@ -2158,5 +2158,60 @@
"mtime": 1791997415.2784162,
"ast_hash": "4ec7b754fe30aac6248e7f8f990ac078",
"semantic_hash": ""
},
"h5/contour-preview.html": {
"mtime": 1792045886.5890932,
"ast_hash": "a98beeb64d55d5b41969f2312afbe63e",
"semantic_hash": ""
},
"h5/assets/mock/contour-demo/front.jpg": {
"mtime": 1792033394.649536,
"ast_hash": "3b3ba2bbe11175c76f25ea63730106bd",
"semantic_hash": ""
},
"h5/assets/mock/contour-demo/left.jpg": {
"mtime": 1792033394.6534245,
"ast_hash": "af8c5642cfa8b4a4af1f48e4bb02f602",
"semantic_hash": ""
},
"h5/assets/mock/contour-demo/right.jpg": {
"mtime": 1792033394.6534245,
"ast_hash": "65e145520cf438df78b985ddddebe6fd",
"semantic_hash": ""
},
"h5/test/private-case-preview.mjs": {
"mtime": 1792034626.0099716,
"ast_hash": "b0814e8913316484fbc669f88394a9a5",
"semantic_hash": ""
},
"h5/test/water-copy.js": {
"mtime": 1792044368.3417633,
"ast_hash": "31ef3ff18bfb9014a35bcb5f2260af28",
"semantic_hash": ""
},
"docs/\u6c34\u5206\u6587\u6848\u72ec\u7acb\u5206\u6863-2026-10-15.md": {
"mtime": 1792043836.616439,
"ast_hash": "66bc24a457dced194579fc9b926f0a26",
"semantic_hash": ""
},
"h5/test/care-selection.js": {
"mtime": 1792044368.3456743,
"ast_hash": "e84928b11825a219a691f721f1a0590a",
"semantic_hash": ""
},
"docs/\u4e03\u9879\u62a4\u7406\u9009\u4e09\u4e0e\u5173\u6ce8\u4f18\u5148-2026-10-15.md": {
"mtime": 1792044443.0636487,
"ast_hash": "baf56556b9963af9bc77943fcca94989",
"semantic_hash": ""
},
"h5/test/vita-independent.js": {
"mtime": 1792046293.397852,
"ast_hash": "563dfef2b579cf93aa248f1e49a20ed1",
"semantic_hash": ""
},
"docs/\u900f\u4eae\u611f\u6539\u4e3a\u4e09\u9879\u72ec\u7acb\u6700\u4f4e\u5206-2026-10-15.md": {
"mtime": 1792046412.3041933,
"ast_hash": "48d0853b95e0b36ce79e3f2e3519826f",
"semantic_hash": ""
}
}
+4
View File
@@ -134,6 +134,10 @@ node server.js # 默认 8080,可用 PORT=xxx 修改
本机浏览器只缓存随机身份凭据,报告保存在服务器;微信需公网 HTTPS、具备网页授权权限的服务号及相应环境变量。
详见 [新版实施与验收](../docs/10.09新版H5实施与验收.md) 和 [微信登录接入与公网部署说明](../docs/微信登录接入与公网部署说明.md)。下方旧功能说明以这两份更新文档为准。
2026-10-15 护理展示更新:`ReportUI.build` 从七项独立小维度分中选最低三项,再将属于当前优先关注大维度的入选项前置;同分按关注优先、文档顺序确定。肤色、水分分析及护理各自独立分档,不再共用肤色水润合成分。新版护理全文以 `reference/AI测肤报告页话术修改.md` 为准;不改变原三维度/综合得分/产品推荐,不批量改写历史presentation。详见 [七项护理选三与关注优先](../docs/七项护理选三与关注优先-2026-10-15.md)。
随后用户确认取消合成分参与维度计分:透亮感改为独立肤色、水分、平滑度三项P的最低有效值,不再平均肤色和水分。新分数用于总分、优先关注及原有边界推荐判断。后台新记录展示七项独立分数,历史记录不重算;旧tone字段仅为兼容。详见 [新版透亮感公式](../docs/透亮感改为三项独立最低分-2026-10-15.md)。
## 部署
需要 Node ≥ 18 运行 `server.js`(负责静态与 API 中转),**不能用纯静态托管**(密钥不能进前端)。
+2 -6
View File
@@ -97,17 +97,13 @@
.report-care-products .prod-card { height:auto; aspect-ratio:945 / 511; }
.report-care-products .prod-info { flex-basis:49.100529%; }
.report-care-products .prod-scroll { flex-basis:50.899471%; }
.report-care-products #product-title { left:8.571429%; top:29.158513%; font-size:13.888889px; line-height:17.340278px; font-weight:700; white-space:nowrap; }
.report-care-products .prod-name { left:8.677249%; top:41.095890%; font-size:11.532639px; line-height:14.467593px; font-weight:700; color:#989898; white-space:nowrap; }
.report-care-products #product-title { left:8.571429%; top:29.158513%; font-size:14.467593px; line-height:18.062790px; font-weight:700; white-space:nowrap; }
.report-care-products .prod-name { left:8.677249%; top:41.095890%; font-size:12.013166px; line-height:15.070408px; font-weight:700; color:#989898; white-space:nowrap; }
.report-care-products .prod-badge { left:8.783069%; top:56.751468%; width:10.687831%; }
.report-care-products + .report-disclaimer { margin:calc(12.152778 * var(--u)) calc(5.208333 * var(--u)) calc(20 * var(--u)); font-size:7.233681px; line-height:10.127431px; }
.dimension-stability-title { font-size:22.106597px; line-height:28.738542px; }
.stability-card .dtab-tx { font-size:11.055903px; }
.stability-card .ditem-para { font-size:11.574074px; line-height:14.467593px; }
@media (max-width:359px) {
.report-care-products #product-title { font-size:12px; }
.report-care-products .prod-name { font-size:10px; }
}
/* Fourth feedback adds a fast one-second round trip to original PSD pixels.
The stage/mask share one contain transform, including short/landscape screens. */
+6 -5
View File
@@ -418,18 +418,19 @@ function renderDetail(data) {
if (r.wrinkle_score_rule === 'api-overall-v1') h += '<p class="sub">皱纹评分直接使用接口整体皱纹分数,不做100减分,也不再按分区严重度限分。</p>';
if (r.tone_scoring && r.tone_scoring.version === 'color-dark-circle-20-v1') {
var tone = r.tone_scoring;
h += '<p class="sub">肤色项=色素扣斑分×80%+黑眼圈评分×20%;再与水分项各占50%。色素扣斑分:' + esc(fmtToneScore(tone.pigment_adjusted_score)) + ',黑眼圈评分:' + esc(fmtToneScore(tone.dark_circle_score)) + ',合成肤色项:' + esc(fmtToneScore(tone.color_score)) + ',水分项:' + esc(fmtToneScore(tone.water_adjusted_score)) + '。缺项按有效权重归一。</p>';
h += '<p class="sub">肤色项=色素扣斑分×80%+黑眼圈评分×20%;' + (r.vita_score_rule ? '透亮感取独立肤色、水分、平滑度三项最低分,不再使用肤色水润平均分。' : '历史版本与水分项各占50%。') + '色素扣斑分:' + esc(fmtToneScore(tone.pigment_adjusted_score)) + ',黑眼圈评分:' + esc(fmtToneScore(tone.dark_circle_score)) + ',独立肤色项:' + esc(fmtToneScore(tone.color_score)) + ',水分项:' + esc(fmtToneScore(tone.water_adjusted_score)) + '。肤色缺项按有效权重归一。</p>';
}
h += '<p class="sub">前端展示肌龄限制在实际年龄±5岁;推荐仍使用原始肌龄。历史报告保留当时的推荐。</p>';
/* 6 分类表格 */
var catRows = '';
for (var i = 0; i < CAT_ORDER.length; i++) {
var ck = CAT_ORDER[i], cat = r.cats && r.cats[ck];
var catName = ({ contour: '轮廓', wrinkles: '皱纹', smooth: '平滑度', tone: '肤色 / 水分', sens: '泛红', acne: '痘痘' })[ck];
var scoreOrder = r.sub_scores ? ['contour','wrinkles','color','smooth','water','sens','acne'] : CAT_ORDER;
for (var i = 0; i < scoreOrder.length; i++) {
var ck = scoreOrder[i], cat = (r.sub_scores || r.cats || {})[ck];
var catName = ({ contour: '轮廓', wrinkles: '皱纹', smooth: '平滑度', color:'肤色', water:'水分', tone: '肤色 / 水分(历史)', sens: '泛红', acne: '痘痘' })[ck];
catRows += '<tr><td>' + esc(catName) + '</td><td>' + fmtScore(cat && cat.P) + '</td><td>' + fmtScore(cat && cat.S) + '</td><td>' + lvHtml(cat && cat.level) + '</td></tr>';
}
h += dSec('原始六分类评分(肤色 / 水分共用一项)', '<table class="d-table"><thead><tr><th>分类</th><th>P</th><th>S</th><th>档位</th></tr></thead><tbody>' + catRows + '</tbody></table>');
h += dSec(r.sub_scores ? '七项独立评分' : '历史六分类评分(肤色 / 水分共用一项)', '<table class="d-table"><thead><tr><th>分类</th><th>P</th><th>S</th><th>档位</th></tr></thead><tbody>' + catRows + '</tbody></table>');
/* 3 维度表格 */
var dimRows = '';
+2 -2
View File
@@ -23,7 +23,7 @@
"优秀": "肌肤质感表现优异,表面细腻平滑,角质层排列整齐,无明显粗大毛孔与黑头。"
},
"water": {
"改善": "水分分 {water_score};缺水面积 {water_area}%、肌肤缺水比较严重 {water_sev},肌肤含水量偏低,水润度不足,表面偏干燥,容易出现干纹。",
"改善": "水分分 {water_score};缺水面积 {water_area}%,{water_severity_note}肌肤水润度有待提升,建议加强补水与保湿。",
"良好": "水分分 {water_score};缺水面积 {water_area}%、肌肤含水量处于健康水平,整体水润度良好。",
"优秀": "水分分 {water_score};角质层含水量充足,肌肤屏障锁水能力强,表面饱满水润,无干燥紧绷表现。"
},
@@ -56,7 +56,7 @@
"wrinkles": {
"改善": {
"care": "重视纹路管理,日常可使用含有肽类、胶原类成分的淡纹精华,针对眼周、法令纹等动态纹高发区域配合点按手法,遏制静态纹转化,帮肌肤恢复平滑细腻的状态。",
"lifestyle": "保证睡眠,注意睡姿,减少面部长期受压;减少夸张表情,避免揉眼、托腮等表情性挤压习惯,减少纹路加深。"
"lifestyle": "注意睡姿,减少面部长期受压;减少夸张表情,避免揉眼、托腮等表情性挤压习惯,减少纹路加深。"
},
"良好": {
"care": "加强日常淡纹与肌肤弹润护理,叠加使用含肽类、胶原类成分的淡纹精华,提升肌肤水润度与饱满感,维持肤表平滑。",
+34 -10
View File
@@ -85,9 +85,30 @@
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;
if (d[0] === 'color' && vars.melanin == null) valid = false;
if (d[0] === 'water' && vars.water_score == null) valid = false;
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
// the existing water cross-check; never change dimension scoring.
var waterScore = vars.water_score;
var waterValid = numeric(waterScore) && waterScore >= 0 && waterScore <= 100;
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 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
? (vars.water_sev >= 60 ? '缺水程度较高(严重度 {water_sev}),' : '缺水严重度 {water_sev},') : '';
if (viewP !== vars.water_score) template = '水分分 {water_score};缺水面积 {water_area}%,缺水严重度 {water_sev}。水分分数与缺水指标不一致,本次按缺水指标评估,建议加强补水与保湿。';
// Resolve the optional phrase before normal scalar substitution.
template = template.replace(/\{water_severity_note\}/g, vars.water_severity_note);
}
if (d[0] === 'contour') {
var severity = vars.dark_circle_sev;
var description = numeric(severity) ? ['无明显黑眼圈', '黑眼圈轻度', '黑眼圈中度', '黑眼圈重度'][severity] : null;
@@ -135,18 +156,21 @@
}
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: cat.P, level: level || null,
return { key: d[0], name: d[1], dim: d[2], source: d[3], P: viewP, level: level || null,
text: valid ? fill(template, vars) : '本次未检出有效数据,请重新拍摄后查看。',
details: details, valid: valid, advice: valid ? advice : null };
});
// Preserve selection of the two weakest original categories. Tone expands into
// color+water advice, with thresholds inherited from the same original category.
var ranked = Object.keys(vm.cats).filter(function(k) { return vm.cats[k].P != null; })
.sort(function(a,b) { return vm.cats[a].P - vm.cats[b].P; }).slice(0,2);
var chosen = views.filter(function(v) { return ranked.indexOf(v.source) >= 0 && v.advice; });
var care = chosen.map(function(v) { return { name: v.name, text: v.advice.care + v.advice.lifestyle }; });
var focus = focusOf(vm);
function priority(view) { return focus && view.dim === focus.key ? 0 : 1; }
function order(a, b) { return a.P - b.P || priority(a) - priority(b) || views.indexOf(a) - views.indexOf(b); }
// Select globally weakest three FIRST, then move selected focus members
// to the front. Never insert an unselected stronger focus-category view.
var chosen = views.filter(function(v) { return v.valid && numeric(v.P) && v.advice; }).sort(order).slice(0,3);
chosen.sort(function(a,b) { return priority(a) - priority(b) || order(a,b); });
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', dims: dimNames, views: views, care: care, product: product, focus: focusOf(vm) };
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 { dimensionScore: dimensionScore, build: build, products: products, levelMap: levelMap, dimNames: dimNames, wrinkleDetails: wrinkleDetails, focusOf: focusOf, customerText: customerText };
});
+10 -2
View File
@@ -752,9 +752,16 @@
var d = DIM_DEFS[i], ps = [], j;
for (j = 0; j < CAT_DEFS.length; j++) {
if (CAT_DEFS[j].dim !== d.key) continue;
if (d.key === 'vita' && CAT_DEFS[j].key === 'tone') continue; // retired combined score
var P = cats[CAT_DEFS[j].key].P;
if (P != null) ps.push(P);
}
if (d.key === 'vita') {
var toneVars = cats.tone.vars;
[toneVars.color_score, toneVars.tone_scoring && toneVars.tone_scoring.water_adjusted_score].forEach(function(score) {
if (isNum(score)) ps.push(score);
});
}
var dP = ps.length ? Math.min.apply(null, ps) : null;
if (!ps.length) notes.push(d.name + ':旗下分类数据全部缺失,维度不参与评分');
dims[d.key] = { name: d.name, P: dP, S: sShow(dP), level: levelOf(dP) };
@@ -848,6 +855,7 @@
return {
total: weightedTotal(dims),
totalRule: 'dimensions-433-v1',
vitaScoreRule: 'independent-color-water-smooth-min-v1',
wrinkleScoreRule: 'api-overall-v1',
acneScoreRule: 'three-view-type-max-v1',
skinAgeRaw: skinAge,
@@ -984,7 +992,7 @@ if (typeof require === 'function' && typeof module !== 'undefined' && require.ma
ok(vm.cats.sens.P === 98, '敏感P = min(98, 100−0.1×1.5=99.85) = 98(面积 0.001 小数口径×100)');
ok(vm.cats.acne.P === 85, '痘痘P = min(分级映射85, 100−14.5=85.5) = 85');
ok(vm.cats.wrinkles.S === 7.69 && vm.cats.smooth.S === 9.45 && vm.cats.tone.S === 9.45, 'S=7+3×P/100 两位小数(含 9.445→9.45 浮点修正)');
ok(vm.dims.firm.P === 23 && vm.dims.vita.P === 81.5 && vm.dims.stab.P === 85, '维度木桶 min:紧塑力23 / 活力感81.5 / 稳定力85');
ok(vm.dims.firm.P === 23 && vm.dims.vita.P === 80 && vm.dims.stab.P === 85, '维度木桶 min:紧塑力23 / 独立肤色短板80 / 稳定力85');
ok(vm.skinAgeRaw === 20 && vm.skinAgeDisplay === 20 && vm.ageClampNote === '', '肌肤年龄直接展示原值20,不按实际年龄钳制');
ok(vm.group === '25+组', '原始肌龄20 → 25+组');
ok(vm.essence.name === '25+束紧精华' && vm.recommendation.tier === 1,
@@ -1015,7 +1023,7 @@ if (typeof require === 'function' && typeof module !== 'undefined' && require.ma
console.log('== T2 木桶 min(显式构造) ==');
var vm2 = SS.buildViewModel(clone(SS.MOCK_RESULT), SS.MOCK_SIDE, { realAge: 40 });
ok(vm2.dims.firm.P === Math.min(vm2.cats.contour.P, vm2.cats.wrinkles.P)
&& vm2.dims.vita.P === Math.min(vm2.cats.smooth.P, vm2.cats.tone.P)
&& vm2.dims.vita.P === Math.min(vm2.cats.smooth.P, vm2.cats.tone.vars.color_score, vm2.cats.tone.vars.tone_scoring.water_adjusted_score)
&& vm2.dims.stab.P === Math.min(vm2.cats.sens.P, vm2.cats.acne.P), '三维度 P = min(旗下两分类)');
ok(vm2.group === '25+组', '实际年龄40不影响推荐,原始肌龄20仍为25+组');
+5
View File
@@ -281,6 +281,11 @@ function fillSuccess(rec, fpJson, persist = true) {
rec.skin_age_display = vm.skinAgeDisplay;
rec.recommendation = vm.recommendation;
rec.presentation = ReportUI.build(vm, fpJson.result);
rec.vita_score_rule = vm.vitaScoreRule;
rec.sub_scores = Object.fromEntries(rec.presentation.views.map(view => [view.key, {
P: view.P, S: view.P == null ? null : Math.round((Math.max(7, Math.min(10, 7 + 3 * view.P / 100)) + 1e-9) * 100) / 100,
level: view.level === '改善' ? '预警' : view.level === '良好' ? '改善' : view.level === '优秀' ? '良好' : null
}]));
rec.total_score = vm.total;
rec.total_score_rule = vm.totalRule;
rec.wrinkle_score_rule = vm.wrinkleScoreRule;
+32
View File
@@ -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');
+1
View File
@@ -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;
+2
View File
@@ -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);
+3
View File
@@ -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');
+3
View File
@@ -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);
+1 -1
View File
@@ -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);}
+8
View File
@@ -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');
+22
View File
@@ -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');
+6 -2
View File
@@ -50,12 +50,14 @@
#### 5\.5 水分(透亮感)
**【改善】**水分分 \{water\_score\};缺水面积 \{water\_area\}%、肌肤缺水比较严重 \{water\_sev\},肌肤含水量偏低,水润度不足,表面偏干燥,容易出现干纹。
**【改善】**水分分 \{water\_score\};缺水面积 \{water\_area\}%,\{water\_severity\_note\}肌肤水润度有待提升,建议加强补水与保湿。
**【良好】**水分分 \{water\_score\};缺水面积 \{water\_area\}%、肌肤含水量处于健康水平,整体水润度良好。
**【优秀】** 水分分 \{water\_score\};角质层含水量充足,肌肤屏障锁水能力强,表面饱满水润,无干燥紧绷表现。
水分分档补充(2026-10-15):文案及水分护理建议独立使用水分项分档,低于80为改善、80至低于90为良好、90及以上为优秀,不再跟随肤色水润合成分。沿用现有水分交叉校验:水分分≥80且缺水严重度≥60时,水分项按100减严重度评估,并在文案中明确提示分数与缺水指标不一致。\{water\_severity\_note\}仅在严重度为0~100的有效数值时展示:≥60描述缺水程度较高,其他值仅报告严重度数值;缺失时省略。维度评分、综合评分、推荐及护理类别选取不变。
#### 5\.6 泛红(稳韧力)
**【改善】** 泛红面积达 \{sens\_area\}%,肌肤泛红明显,敏感区域可见泛红;对外界刺激耐受度不足,容易受外界因素影响产生状态波动。
@@ -80,6 +82,8 @@
### 护理建议(全量文本,按需求摘抄)
选择规则(2026-10-15):从轮廓、皱纹、肤色、平滑度、水分、泛红、痘痘七项有效独立分数中选最低的三项;肤色、水分不再共用合成分。分数相同优先选上方“优先关注”大维度中的小项,再按本节七项顺序。选完三项后,将属于优先关注大维度的入选项前置,各组内仍按低分优先排列,不强行补入未入选项。有效项不足三项按实际数量展示。各项按自己的分数取改善/良好/优秀护理及生活方式全文,痘痘保留已有皮损严重度文案约束。三维度、综合得分及产品推荐不变。肤色分析文案同步按独立肤色项分档,黑眼圈程度描述仍独立读取实际检测等级。
#### 6\.1 轮廓
【改善·护理】重点进行轮廓紧致管理,关注面中、苹果肌与下颌线区域。日常增加紧致精华步骤,配合由下向上的轻柔提升按摩,帮助肌肤维持弹润和支撑感,让轮廓看起来更紧实。
@@ -104,7 +108,7 @@
【改善·护理】重视纹路管理,日常可使用含有肽类、胶原类成分的淡纹精华,针对眼周、法令纹等动态纹高发区域配合点按手法,遏制静态纹转化,帮肌肤恢复平滑细腻的状态。
【改善·生活方式】保证睡眠,注意睡姿,减少面部长期受压;减少夸张表情,避免揉眼、托腮等表情性挤压习惯,减少纹路加深。
【改善·生活方式】注意睡姿,减少面部长期受压;减少夸张表情,避免揉眼、托腮等表情性挤压习惯,减少纹路加深。