feat(h5): update skin scoring, age display and capture countdown
This commit is contained in:
+19
-13
@@ -26,6 +26,7 @@ const DATA_DIR = path.join(DATA_ROOT, 'results'); // 每次检测的原始返
|
||||
const PHOTOS_DIR = path.join(DATA_ROOT, 'photos'); // 检测照片:data/photos/<record_id>/{front,left,right}.jpg
|
||||
const RECORDS_FILE = path.join(DATA_ROOT, 'records.jsonl'); // 检测记录(一行一条 JSON)
|
||||
const ReportUI = require('./js/report-ui.js');
|
||||
const BeijingTime = require('./js/beijing-time.js');
|
||||
const CaptureDiagnostics = require('./lib/capture-diagnostics.js');
|
||||
const accounts = require('./lib/accounts.js')(path.join(DATA_ROOT, 'accounts'));
|
||||
|
||||
@@ -282,6 +283,9 @@ function fillSuccess(rec, fpJson, persist = true) {
|
||||
rec.presentation = ReportUI.build(vm, fpJson.result);
|
||||
rec.total_score = vm.total;
|
||||
rec.total_score_rule = vm.totalRule;
|
||||
rec.wrinkle_score_rule = vm.wrinkleScoreRule;
|
||||
rec.acne_scoring = vm.cats.acne.vars.acne_scoring;
|
||||
rec.tone_scoring = vm.cats.tone.vars.tone_scoring;
|
||||
rec.jawline_angle_l = jawlineAngle(fpJson.left_side_result, 'left');
|
||||
rec.jawline_angle_r = jawlineAngle(fpJson.right_side_result, 'right');
|
||||
rec.cats = {}; rec.copy_6 = {};
|
||||
@@ -516,7 +520,7 @@ function photoUrls(rec) {
|
||||
/* 列表用的字段子集(不含 raw_json 与文案全文) */
|
||||
function displaySkinAge(rec) {
|
||||
// Match H5 even for old records; do not rewrite historical files.
|
||||
return SkinScore.reportSkinAge({ skinAgeRaw: rec.skin_age });
|
||||
return SkinScore.reportSkinAge({ skinAgeRaw: rec.skin_age, realAge: rec.real_age });
|
||||
}
|
||||
function displayTotalScore(rec) {
|
||||
return SkinScore.weightedTotal(rec.dims);
|
||||
@@ -540,12 +544,11 @@ function listItem(rec) {
|
||||
};
|
||||
}
|
||||
|
||||
/* 查询参数过滤。from/to 支持 YYYY-MM-DD 或完整 ISO;纯日期 to 含当天全天 */
|
||||
/* Dates mean Beijing calendar days; fully zoned ISO timestamps retain their instant. */
|
||||
function matchQuery(rec, q) {
|
||||
if (q.from && rec.created_at < q.from) return false;
|
||||
if (q.to) {
|
||||
const to = q.to.length === 10 ? q.to + 'T23:59:59.999Z' : q.to;
|
||||
if (rec.created_at > to) return false;
|
||||
if (q.timeRange && (q.from || q.to)) {
|
||||
const timestamp = BeijingTime.milliseconds(rec.created_at);
|
||||
if (!Number.isFinite(timestamp) || timestamp < q.timeRange.start || timestamp >= q.timeRange.end) return false;
|
||||
}
|
||||
if (q.detect_status && rec.detect_status !== q.detect_status) return false;
|
||||
if (q.fail_code && rec.fail_code !== q.fail_code) return false;
|
||||
@@ -582,6 +585,8 @@ function handleRecordsQuery(req, res, url) {
|
||||
if ((q.age_min != null && !Number.isFinite(q.age_min)) || (q.age_max != null && !Number.isFinite(q.age_max))) {
|
||||
return sendJson(res, 400, { ok: false, message: 'age_min/age_max 必须是数字' });
|
||||
}
|
||||
try { q.timeRange = BeijingTime.range(q.from, q.to); }
|
||||
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))
|
||||
@@ -660,7 +665,7 @@ function handlePhoto(req, res, id, which) {
|
||||
|
||||
function handleStats(req, res) {
|
||||
const all = readAllRecords();
|
||||
const today = nowIso().slice(0, 10);
|
||||
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 === '成功');
|
||||
const failed = finished.filter(r => r.detect_status === '失败');
|
||||
@@ -691,9 +696,8 @@ function handleStats(req, res) {
|
||||
// 近 14 天每日检测量/成功数(含今天,今天在后)
|
||||
const daily = [];
|
||||
for (let i = 13; i >= 0; i--) {
|
||||
const d = new Date(Date.now() - i * 86400000);
|
||||
const day = d.toISOString().slice(0, 10);
|
||||
const recs = all.filter(r => (r.created_at || '').slice(0, 10) === day);
|
||||
const day = BeijingTime.day(now - i * 86400000);
|
||||
const recs = all.filter(r => BeijingTime.day(r.created_at) === day);
|
||||
daily.push({
|
||||
date: day,
|
||||
total: recs.length,
|
||||
@@ -703,7 +707,7 @@ function handleStats(req, res) {
|
||||
|
||||
sendJson(res, 200, {
|
||||
ok: true,
|
||||
today_total: all.filter(r => (r.created_at || '').slice(0, 10) === today).length,
|
||||
today_total: all.filter(r => BeijingTime.day(r.created_at) === today).length,
|
||||
total: all.length,
|
||||
finished_total: finished.length,
|
||||
success_total: succeeded.length,
|
||||
@@ -772,19 +776,21 @@ function handleExport(req, res, url) {
|
||||
from: url.searchParams.get('from') || null,
|
||||
to: url.searchParams.get('to') || null
|
||||
};
|
||||
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))
|
||||
.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]) ? r.dims[d].S : null;
|
||||
const dimLv = (r, d) => (r.dims && r.dims[d]) ? ReportUI.levelMap[r.dims[d].level] : null;
|
||||
const header = ['记录ID', '创建时间', '年龄', '性别', '地区', '日常肤质', '拍摄状态', '上传状态', '检测状态',
|
||||
const header = ['记录ID', '创建时间(北京时间)', '年龄', '性别', '地区', '日常肤质', '拍摄状态', '上传状态', '检测状态',
|
||||
'失败环节', '失败码', '失败信息', '综合得分(4:3:3)', '原始肌龄(推荐依据)', '前端展示肌龄',
|
||||
'紧塑力P', '紧塑力S', '紧塑力档', '透亮感P', '透亮感S', '透亮感档', '稳韧力P', '稳韧力S', '稳韧力档',
|
||||
'均分S', '产品组别', '推荐精华', '触发判据', '耗时ms', '推荐规则版本', '推荐原因', '侧脸质量状态', '前端拍摄参数版本'];
|
||||
const lines = [header.join(',')];
|
||||
for (const r of filtered) {
|
||||
lines.push([
|
||||
r.record_id, r.created_at, r.real_age, r.gender, r.region, r.daily_skin_type,
|
||||
r.record_id, BeijingTime.format(r.created_at), r.real_age, r.gender, r.region, r.daily_skin_type,
|
||||
r.shoot_status, r.upload_status, r.detect_status,
|
||||
r.fail_stage, r.fail_code, r.fail_msg, displayTotalScore(r), r.skin_age, displaySkinAge(r),
|
||||
dimP(r, 'firm'), dimS(r, 'firm'), dimLv(r, 'firm'),
|
||||
|
||||
Reference in New Issue
Block a user