安康春蕾文化 · TES三力学习力教师后台三力学习力测评数据 · 多层分析看板
安康春蕾文化 · TES三力学习力教师后台三力学习力测评数据 · 分析看板

教师登录

请输入教师密码查看学生测评数据

正确入口:http://localhost:3000(本机运行 server.js)。公网访问需将后端部署到云。
'; const w = window.open('', '_blank'); if(!w){ downloadText(doc, 'TES批量报告_'+scopeName+'.html'); alert('浏览器拦截了弹窗,已改为下载 HTML 文件,双击打开后即可打印。'); return; } w.document.open(); w.document.write(doc); w.document.close(); } /* ---------- 详情 ---------- */ let CURRENT=null; async function openDetail(id){ const rec=ALL.find(r=>r.id===id); if(!rec) return; CURRENT=rec; RC_SORT_MODE='severity'; // 每份报告默认按严重程度排序 try { await ensureScoreLines(); } catch(e) {} try{ const r=await fetch('/api/submission/'+id+'?token='+encodeURIComponent(getToken())); const d=await r.json(); if(d.ok) Object.assign(rec, d.record); }catch(e){} const dims=DIMS.map(dm=>{ const p=getDimPct(rec,dm.id)||0; return {name:dm.name,pct:p,li:LEVELS[levelOf(p)],c:dimColor(dm.id)}; }); const pct=cityPercentile(rec.total); const lvLabel=LEVELS[levelOf(rec.total)].label; const lvColor=LEVELS[levelOf(rec.total)].color; const cAvg=mean(CITY.map(r=>r.total)); const cDims=DIMS.map(dm=>avgDim(CITY,dm.id)); let html='
'+esc(rec.name)+' 的学习力报告'+lvLabel+'
'; html+='
学校:'+(esc(rec.school)||'—')+(rec.region?' · '+esc(rec.region):'')+' · 年级:'+(esc(rec.grade)||'—')+' · 阶段:'+(rec.phase==='post'?'后测':rec.phase==='pre'?'前测':'—')+' · 综合分 '+rec.total+'/100
'; // 留资信息卡(家长/学生填写的联系方式,教师跟进用) html+='
📇 留资信息(家长/学生填写)
'; html+='
'; html+='
家长手机:'+(esc(rec.phone)||'—')+'
'; html+='
姓名:'+(esc(rec.name)||'—')+'
'; html+='
学校:'+(esc(rec.school)||'—')+'
'; html+='
年级:'+(esc(rec.grade)||'—')+'
'; html+='
测评日期:'+(esc(rec.date)|| (rec.createdAt||'').slice(0,10))+'
'; html+='
数据来源:'+(esc(rec.source)||'—')+'
'; html+='
'; // 9 科成绩(学情分析输入) const _subs = normSubjectScores(rec); if (_subs.length) { html+='

学科成绩(学情分析输入)未开设科目填 0

'; _subs.forEach(function(ss){ var isOff = !ss.score; var ratio = (ss.max>0 && ss.score>0) ? Math.round(ss.score/ss.max*100) : 0; var rc = isOff ? '#94a3b8' : (ratio>=75?'#16a34a':(ratio>=60?'#2563eb':'#ea580c')); html+=''; }); html+='
科目得分满分得分率
'+esc(ss.name)+''+(isOff?'未开设':ss.score)+''+ss.max+''+(isOff?'—':ratio+'%')+'
'; } // 高中生:高考推演 + 大学录取匹配卡 html += renderGaoKaoCardTeacher(rec); // 全市对标 html+='
全市对标
'; html+='
该生综合分超过全市 '+(pct==null?'—':pct+'%')+' 的学生(常模'+CITY.length+'人);全市平均 '+cAvg.toFixed(1)+' 分,'+(rec.total>=cAvg?'高于':'低于')+'全市平均 '+(rec.total>=cAvg?'+':'')+(rec.total-cAvg).toFixed(1)+' 分。
'; // 雷达:学生 vs 全市 html+='

能力雷达本人 vs 全市

'+dimLegend()+'本人全市
'; // 三维明细 html+='

三维能力明细

'; dims.forEach(d=>{ html+='
'+d.name+'
'+d.pct+' · '+d.li.label+'
'; }); html+='
'; // 建议 html+='

针对性改进建议

'; dims.forEach(d=>{ const id=DIMS.find(x=>x.name===d.name).id; html+='
'+d.name+'('+d.li.label+'):'+SUGGEST[id][d.li.key]+'
'; }); html+='
'; // 生涯规划测评(霍兰德 + 职业价值观 + MBTI + 多元智能,选做) if (rec.holland || rec.careerValues || rec.mbti || rec.mi) { html += '

生涯规划测评选做 · 兴趣/价值/性格/智能

'; if (rec.holland && rec.holland.type) { const h = rec.holland; const keys = ['R','I','A','S','E','C']; const names = {R:'现实型',I:'研究型',A:'艺术型',S:'社会型',E:'企业型',C:'常规型'}; html += '
霍兰德主导类型:'+esc(h.type)+'
'; (h.scores ? keys : []).forEach(function(k){ const v = (h.scores && h.scores[k]) || 0; const pct = Math.round(v/7*100); html += '
'+k+'·'+names[k]+'
'+v+'/7
'; }); (h.dims||[]).forEach(function(d){ html += '
'+d.key+'·'+esc(d.name)+':'+esc(d.traits||'')+'
职业方向:'+esc(d.careers||'')+'
'; }); } if (rec.careerValues && rec.careerValues.top5) { const cv = rec.careerValues; html += '
最看重的职业价值(top5):
'; cv.top5.forEach(function(d){ const pct = Math.round(d.avg/5*100); html += '
'+esc(d.name)+'
'+d.avg+'/5
'; }); } if (rec.mbti && rec.mbti.type) { const m = rec.mbti; html += '
MBTI 性格类型:'+esc(m.type)+''+(m.title?'('+esc(m.title)+')':'')+'
'; html += '
核心特质:'+esc(m.traits||'')+'
'; if (m.majors && m.majors.length) html += '
适合专业:'+m.majors.map(esc).join('、')+'
'; if (m.jobs && m.jobs.length) html += '
典型职业:'+m.jobs.map(esc).join('、')+'
'; } if (rec.mi && rec.mi.top3) { const mi = rec.mi; html += '
多元智能优势(Top3):'+mi.top3.map(function(d){return esc(d.name);}).join(' · ')+'
'; if (mi.all) { mi.all.slice().sort(function(a,b){return b.sum-a.sum;}).forEach(function(d){ const pct = Math.round(d.sum/d.max*100); html += '
'+esc(d.name)+'
'+d.sum+'/'+d.max+'
'; }); } } html += '
'; } html += '
' + renderSubRootCause(rec) + '
'; html += renderQuestionBankTeacher(rec); html+='
安康春蕾文化 · TES三力学习力模型(时间管理力·情绪管理力·自我学习力)
'; document.getElementById('reportCard').innerHTML=html; drawRadar(document.getElementById('dRadar'), DIMS.map(d=>d.name), [ {values:cDims, color:'#94a3b8', fill:'rgba(148,163,184,.12)'}, {values:dims.map(d=>d.pct), color:'#0f172a', fill:'rgba(15,23,42,.12)'}, ], 260); document.getElementById('mask').className='mask on'; } function backToList(){ document.getElementById('mask').className='mask'; } function downloadPNG(){ // 依赖 html2canvas(学生端同款);教师端若未引入则提示 if(typeof html2canvas==='undefined'){ alert('当前环境未加载截图库,请使用“复制文字报告”。'); return; } html2canvas(document.getElementById('reportCard'), {backgroundColor:'#fff'}).then(c=>{ const a=document.createElement('a'); a.href=c.toDataURL('image/png'); a.download='TES报告_'+(CURRENT?CURRENT.name:'')+'.png'; a.click(); }); } function copyText(){ const rec=CURRENT; if(!rec) return; const dims=DIMS.map(dm=>{ const p=getDimPct(rec,dm.id)||0; return {name:dm.name,pct:p,li:LEVELS[levelOf(p)],c:dimColor(dm.id)}; }); const pct=cityPercentile(rec.total); const cAvg=mean(CITY.map(r=>r.total)); let s='【TES三力学习力测评报告】'+rec.name+'\n'; s+='学校:'+(rec.school||'—')+(rec.region?' · '+rec.region:'')+' 年级:'+(rec.grade||'—')+' 阶段:'+(rec.phase==='post'?'后测':rec.phase==='pre'?'前测':'—')+'\n'; s+='综合分:'+rec.total+'/100('+LEVELS[levelOf(rec.total)].label+')\n'; s+='全市对标:超过全市 '+(pct==null?'—':pct+'%')+' 的学生;全市平均 '+cAvg.toFixed(1)+' 分。\n'; s+='\n—— 三维能力 ——\n'; dims.forEach(d=>{ s+='· '+d.name+':'+d.pct+' 分('+d.li.label+')\n'; }); s+='\n—— 改进建议 ——\n'; dims.forEach(d=>{ const id=DIMS.find(x=>x.name===d.name).id; s+='· '+d.name+':'+SUGGEST[id][d.li.key]+'\n'; }); s+='\n安康春蕾文化 · TES三力学习力模型'; navigator.clipboard.writeText(s).then(()=>alert('已复制文字报告')); } // 回车登录 document.getElementById('pwd').addEventListener('keydown', e=>{ if(e.key==='Enter') login(); }); // 打开即探测后端连通性 window.addEventListener('DOMContentLoaded', checkBackend); // 自动登录 /* ---------- 情景推演:努力程度 → 升学变化区间 ---------- */ // 培训投入档位 → 加权TES 增量(0-100 量表上的分数点,封顶 100) const EFFORTS = [ { key: '保持现状', gain: 0, desc: '不追加干预' }, { key: '轻投入(周末加练)', gain: 6, desc: '每周末针对性加练' }, { key: '中投入(每周2次辅导)', gain: 12, desc: '每周 2 次一对一' }, { key: '重投入(每周4次+习惯养成)', gain: 20, desc: '高频辅导+作息重建' }, { key: '全托集训(全日制)', gain: 28, desc: '全日制封闭集训' }, ]; // 在三维上分配增益:focus='bal' 均衡补短(按可提升空间分配);否则主攻该维、溢出补其余 function applyGain(dims, gain, focus) { if (gain <= 0) return dims.map(d => ({ id: d.id, score: d.score })); const rooms = {}; dims.forEach(d => rooms[d.id] = Math.max(0, 100 - d.score)); const alloc = {}; if (focus === 'bal') { const total = dims.reduce((s, d) => s + rooms[d.id], 0) || 1; dims.forEach(d => alloc[d.id] = gain * rooms[d.id] / total); } else { const fRoom = rooms[focus]; const others = dims.filter(d => d.id !== focus); if (fRoom <= 0) { const t = dims.reduce((s, d) => s + rooms[d.id], 0) || 1; dims.forEach(d => alloc[d.id] = gain * rooms[d.id] / t); } else if (gain <= fRoom) { alloc[focus] = gain; } else { alloc[focus] = fRoom; const rest = gain - fRoom; const oroom = others.reduce((s, d) => s + rooms[d.id], 0) || 1; others.forEach(d => alloc[d.id] = rest * rooms[d.id] / oroom); } } return dims.map(d => ({ id: d.id, score: Math.min(100, d.score + (alloc[d.id] || 0)) })); } async function ensureScoreLines() { if (SCORE_LINES) return SCORE_LINES; try { const r = await fetch('/api/scorelines?token=' + encodeURIComponent(getToken())); const j = await r.json(); SCORE_LINES = (j && j.ok && j.data) ? j.data : null; } catch (e) { SCORE_LINES = null; } return SCORE_LINES; } function scenarioCompute(rec, effortIdx, focus) { const mp = (SCORE_LINES && SCORE_LINES.mapping) || DEF_MAP; const spec = mapSpec(mp, rec.grade); const dims = (rec.dimensions || []).map(d => ({ id: d.id, score: d.score || 0 })); const wtes0 = weightedTES(dims, spec, rec.total); const est0 = Math.round(spec.base + wtes0 * spec.scale); const gain = EFFORTS[effortIdx].gain; const nd = applyGain(dims, gain, focus); const wtes1 = weightedTES(nd, spec, rec.total); const est1 = Math.round(spec.base + wtes1 * spec.scale); return { spec, dims, nd, wtes0, est0, wtes1, est1, gain }; } async function renderScenario() { const view = document.getElementById('scenarioView'); if (!view) return; if (!ALL || !ALL.length) { view.innerHTML = '
暂无学生数据,请先让学生完成测评并提交。
'; return; } await ensureScoreLines(); if (!SCORE_LINES) { view.innerHTML = '
分数线数据加载失败,请刷新或确认后端 /api/scorelines 可访问。
'; return; } const opts = ALL.map((r, i) => '').join(''); view.innerHTML = '

情景推演 · 努力程度 → 升学变化区间 辅助提分规划沟通

' + '
拖动「培训投入」滑块,模拟该生在追加不同强度干预后,预估文化分与目标校录取线的变化区间。本推演为基于当前换算模型的情景模拟,非录取保票;系数 base/scale 建议用 pilot_collect 真实数据校准后更准。
' + '
' + '
' + '
当前:保持现状
' + '' + '
保持现状轻投入中投入重投入全托集训
' + '
'; const stu = document.getElementById('scStu'), focus = document.getElementById('scFocus'), eff = document.getElementById('scEffort'); const run = () => { const rec = ALL[+stu.value]; const ei = +eff.value; const f = focus.value; document.getElementById('scEffLabel').textContent = '当前:' + EFFORTS[ei].key; document.getElementById('scEffVal').textContent = EFFORTS[ei].gain > 0 ? ('+' + EFFORTS[ei].gain + ' 加权TES') : ''; document.getElementById('scOut').innerHTML = scenarioOut(rec, ei, f); }; stu.onchange = run; focus.onchange = run; eff.oninput = run; run(); } function scenarioOut(rec, ei, f) { if (!SCORE_LINES) return '
分数线数据加载中…
'; const c = scenarioCompute(rec, ei, f); const data = SCORE_LINES; const isHS = isHighSchoolGrade(rec.grade); // 高中生推演对标「高考高校(750制)」,初中生对标「安康高中(860制)」——避免高中生误显示高中学校 const schools = (isHS ? (data.gkSchools || []) : (data.schools || [])) .filter(s => s.latest != null) .sort((a, b) => b.latest - a.latest); const reach0 = schools.filter(s => c.est0 >= s.latest); const reach1 = schools.filter(s => c.est1 >= s.latest); const newly = reach1.filter(s => !reach0.find(x => x.id === s.id)); const dW = (c.wtes1 - c.wtes0); const dE = c.est1 - c.est0; const colDelta = dE > 0 ? '#16a34a' : '#0f172a'; const kpiC = (v, l, col) => '
' + v + '
' + l + '
'; // KPI let h = '
' + kpiC(dispEstFor(c.est0, rec.grade), '当前预估文化分(新制折算)') + kpiC(dispEstFor(c.est1, rec.grade), '推演后预估分(新制折算)', colDelta) + kpiC((dE > 0 ? '+' + dispEstFor(dE, rec.grade) : '0'), '文化分增量', colDelta) + kpiC(reach1.length, isHS ? '可达高校(推演后)' : '可达招生校(推演后)') + '
'; // 三力前后 h += '

三力变化(加权TES ' + c.wtes0.toFixed(1) + ' → ' + c.wtes1.toFixed(1) + (dW > 0 ? ',+' + dW.toFixed(1) : '') + ')

'; c.dims.forEach((d, i) => { const nd = c.nd[i]; const dv = (nd.score - d.score).toFixed(1); h += ''; }); h += '
维度当前推演后变化
' + dimName(d.id) + '' + d.score + '' + nd.score.toFixed(0) + '' + (dv > 0 ? '+' + dv : '—') + '
'; // 学校对标 h += '

' + (isHS ? '高考目标校对标' : '目标校录取线对标') + ' ' + (isHS ? '750 分制 · 绿=推演后可够到 橙=可冲刺 灰=差距较大' : '绿=推演后可够到 橙=可冲刺 灰=差距较大') + '

'; schools.forEach(s => { const ok0 = c.est0 >= s.latest, ok1 = c.est1 >= s.latest; let bg = '#f1f5f9', fg = '#94a3b8', bd = '#e2e8f0'; if (ok1) { bg = '#ecfdf5'; fg = '#065f46'; bd = '#a7f3d0'; } else if (ok0) { bg = '#ecfdf5'; fg = '#065f46'; bd = '#a7f3d0'; } else if (c.est1 >= s.latest - 30) { bg = '#fffbeb'; fg = '#92400e'; bd = '#fde68a'; } const tag = ok1 ? (ok0 ? '已达' : '新可达') : (c.est1 >= s.latest - 30 ? '可冲刺' : '差距大'); h += '
' + escHtml(s.name) + '
线 ' + (isHS ? s.latest : toNewLine(s.latest)) + (isHS ? '' : '(新制折算)') + (tag !== '差距大' ? ' · ' + tag : '') + '
'; }); h += '
'; // 推演结论(透明可解释) let txt = '在当前 TES(加权 ' + c.wtes0.toFixed(1) + ')基础上,若采取「' + EFFORTS[ei].key + '」' + (EFFORTS[ei].desc ? '(' + EFFORTS[ei].desc + ')' : '') + ','; if (c.gain > 0) { txt += '预计加权TES提升至 ' + c.wtes1.toFixed(1) + '(+' + dW.toFixed(1) + '),预估文化分由 ' + dispEstFor(c.est0, rec.grade) + ' 升至 ' + dispEstFor(c.est1, rec.grade) + '(+' + dispEstFor(dE, rec.grade) + ')。'; txt += newly.length ? ('届时新增可够到:' + newly.map(s => s.name).join('、') + '。') : '当前可达' + (isHS ? '高校' : '学校') + '未变化(已接近该生能力上限,或需更大强度投入)。'; } else { txt += '保持现状则预估文化分维持约 ' + dispEstFor(c.est0, rec.grade) + ',可达' + (isHS ? '高校' : '学校') + '不变。'; } const weak = c.dims.slice().sort((a, b) => a.score - b.score)[0]; txt += ' 推演建议:优先补强「' + dimName(weak.id) + '」(当前 ' + weak.score + ' 分,对升学分贡献权重最高),性价比最优。'; h += '

推演结论 模型情景模拟,实际受试卷难度/发挥/政策影响

' + txt + '

'; return h; } function escHtml(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); } // ============ 学业推演:学习力 → 升学路径(纵向)============ // 高一转换模型(与 score_lines.json byGrade.高一 对齐):base 300 / scale 4.5 / 权重 T1.1 E1.0 S1.4 const SENIOR_SPEC = { base: 300, scale: 4.5, weights: { T: 1.1, E: 1.0, S: 1.4 } }; async function renderTraj() { const view = document.getElementById('trajView'); if (!view) return; if (!ALL || !ALL.length) { view.innerHTML = '
暂无学生数据,请先让学生完成测评并提交。
'; return; } await ensureScoreLines(); const data = SCORE_LINES || { mapping: null, schools: [], gkSchools: [] }; const mp = data.mapping || DEF_MAP; const juniors = ALL.filter(r => !isHighSchoolGrade(r.grade)); const seniors = ALL.filter(r => isHighSchoolGrade(r.grade)); let h = '

📈 学业推演 · 学习力 → 升学路径 基于三力测评与真实成绩的纵向推演

'; h += '
本模块做纵向学业推演:① 初中生以本次三力测评,按高中转换模型向前预测进入高中后的学业基准;② 高中生以真实高一/高二成绩推演高考可达区间。均为能力延续型参考推演,非保票;真实结果取决于后续三年的努力与方法。
'; // ---- 初中 → 高一 ---- h += '

🎒 初中生 → 高一学业预测(' + juniors.length + ' 人) 三力水平向前推演

'; if (!juniors.length) { h += '
暂无初中学生提交。
'; } else { const rows = juniors.map(r => { const dims = (r.dimensions || []).map(d => ({ id: d.id, score: d.score || 0 })); const estMid = estCultural(dims, mp, r.grade, r.total); // 860 制中考预估 const wtes = weightedTES(dims, SENIOR_SPEC, r.total); // 按高一权重加权 const gyTes = Math.round(SENIOR_SPEC.base + wtes * SENIOR_SPEC.scale); // 750 制高一预测 const lvl = gyTes >= 650 ? '重点高中潜力' : (gyTes >= 500 ? '普通高中潜力' : '基础待夯实,高中需重点突破'); return { name: r.name, grade: r.grade || '—', total: r.total, estMid, gyTes, lvl }; }).sort((a, b) => b.gyTes - a.gyTes); h += ''; rows.forEach(p => { h += ''; }); h += '
姓名年级TES 综合中考预估(2027新制700)高一学业预测(750·基于三力)高中层次定位
' + esc(p.name) + '' + esc(p.grade) + '' + p.total + '' + toNewLine(p.estMid) + '' + p.gyTes + ' /750' + p.lvl + '
'; h += '
高一学业预测 = 300 + 加权TES(高一权重 T1.1/E1.0/S1.4) × 4.5,把当前三力水平按高中转换模型向前推演;可与「中考预估 × 750/860」折算基准互相印证。说明:初中已养成的学习力习惯,是高中成绩最底层的驱动,但高一真实表现还受适应速度、方法与第一年努力影响。
'; } h += '
'; // ---- 高中 → 高考 ---- h += '

🎓 高中生 → 高考推演(' + seniors.length + ' 人) 750 分制

'; if (!seniors.length) { h += '
暂无高中学生提交。
'; } else { const gk = (data.gkSchools || []).filter(s => s.latest != null).slice().sort((a, b) => b.latest - a.latest); const rows = seniors.map(r => { const gy = normGaoYi(r.gaoYi); const ge = (r.gaoEr != null && r.gaoEr > 0) ? Number(r.gaoEr) : null; const est = gkEstimate(gy, ge); const t = est ? gk.find(s => est.mid >= s.latest) : null; return { name: r.name, grade: r.grade || '—', gy, ge, est, target: t ? t.name : (est ? '低于所列高校' : '未填高一/高二成绩') }; }).sort((a, b) => ((b.est && b.est.mid) || 0) - ((a.est && a.est.mid) || 0)); h += ''; rows.forEach(p => { const gkCell = p.est ? ('' + p.est.lo + '~' + p.est.hi + '') : '—'; const midCell = p.est ? ('' + p.est.mid + '') : '—'; h += ''; }); h += '
姓名年级高一高二高考推演区间(750)推演中值可达高校(参考)依据
' + esc(p.name) + '' + esc(p.grade) + '' + (p.gy != null ? p.gy : '—') + '' + (p.ge != null ? p.ge : '—') + '' + gkCell + '' + midCell + '' + (p.target ? esc(p.target) : '—') + '' + (p.est ? p.est.mode : '—') + '
'; h += '

陕西主要高校在陕录取分参考

'; h += ''; gk.forEach(s => { h += ''; }); h += '
高校类型2024 参考
' + esc(s.name) + '' + esc(s.type || '') + '' + s.latest + '
'; h += '
' + (data.gkLineNote || '') + '。推演逻辑:高一高二均填→以「高一×0.4+高二×0.6」为基准上下±5%估区间;仅高一→×0.80~0.85;仅高二→×0.97~1.03(高二更接近高考水平)。为保守的能力延续推演,非保票。
'; } h += '
'; view.innerHTML = h; } /* ---------- 视图:全国重点本科 · 在陕录取分 ---------- */ async function renderNational(){ const view = document.getElementById('nationalView'); view.innerHTML = '
正在加载全国高校在陕录取分……
'; let sl = SCORE_LINES || (window.__SL__ || null); if (!sl) { try { const r = await fetch('/api/scorelines' + '?token=' + encodeURIComponent(getToken())); const j = await r.json(); sl = (j && j.ok && j.data) ? j.data : null; if (sl) SCORE_LINES = sl; } catch (e) { sl = null; } } if (!sl) { view.innerHTML = '
分数线数据加载失败,请确认后端 /api/scorelines 可访问。
'; return; } const all = (sl.gkSchools || []).slice(); const provs = [...new Set(all.map(s => s.province).filter(Boolean))].sort(); const types = [...new Set(all.map(s => s.type).filter(Boolean))].sort(); view.innerHTML = '

🌏 全国重点本科 · 在陕录取分参考 ' + all.length + ' 所 · 覆盖 ' + provs.length + ' 省份

' + '
' + esc(sl.gkLineNote || '') + '
' + '
' + '' + '' + '' + '' + '' + '
' + '
高校所在地类型在陕·物理类在陕·历史类年份 / 来源
' + '
标注 ≈ 者为按院校层级估算的近似在陕分(非官方投档线),仅供升学定位参考;真实分以陕西省教育考试院公布为准。本表已自动用于「学业推演 → 高考推演」的「可冲刺最高校」全国对标。
' + '
'; const body = document.getElementById('natBody'); const search = document.getElementById('natSearch'); const prov = document.getElementById('natProv'); const type = document.getElementById('natType'); const sort = document.getElementById('natSort'); const count = document.getElementById('natCount'); function draw() { let rows = all.slice(); const kw = (search.value || '').trim(); if (kw) rows = rows.filter(r => (r.name || '').indexOf(kw) >= 0); if (prov.value) rows = rows.filter(r => r.province === prov.value); if (type.value) rows = rows.filter(r => r.type === type.value); rows.sort((a, b) => (sort.value === 'asc' ? (a.latest || 0) - (b.latest || 0) : (b.latest || 0) - (a.latest || 0))); count.textContent = '匹配 ' + rows.length + ' 所'; body.innerHTML = rows.map(r => { const tag = r.approx ? ' ≈' : ''; const yr = r.year ? (r.year + ' · ') : ''; return '' + esc(r.name) + '' + esc(r.province || '—') + '' + esc(r.type || '—') + '' + '' + (r.latest != null ? r.latest : '—') + '' + tag + '' + (r.hisLatest != null ? r.hisLatest : '—') + '' + '' + yr + esc(r.gkSource || '') + ''; }).join('') || '无匹配高校'; } search.addEventListener('input', draw); prov.addEventListener('change', draw); type.addEventListener('change', draw); sort.addEventListener('change', draw); draw(); } /* ---------- 视图:安康本地学情(独立页 assessments.html 采集) ---------- */ const LP_SUBJ = ['语文','数学','英语','物理','化学','生物','政治','历史','地理']; const LP_MAX = {'语文':150,'数学':150,'英语':150,'物理':100,'化学':100,'生物':100,'政治':100,'历史':100,'地理':100}; function maskPhone(p){ const s = String(p||''); return /^1\d{10}$/.test(s) ? (s.slice(0,3) + '****' + s.slice(7)) : (s || '—'); } let LP_ROWS = []; let LP_THRESHOLD = 30; // 单校含成绩样本 ≥30 例才启用本地校准 let LP_BY_SCHOOL = []; // 各校「本地校准」进度面板:<30 例仍用全省 μ(保守),≥30 例才切本地校准 function lpSchoolProgressPanel(){ if (!LP_BY_SCHOOL || !LP_BY_SCHOOL.length) return ''; const TH = LP_THRESHOLD || 30; const cards = LP_BY_SCHOOL.map(function(o){ const n = o.withScores || 0; const pct = Math.min(100, Math.round(n / TH * 100)); const col = o.ready ? '#16a34a' : (n >= TH * 0.5 ? '#a16207' : '#64748b'); const bg = o.ready ? '#d1fae5' : (n >= TH * 0.5 ? '#fefce8' : '#f8fafc'); const bd = o.ready ? '#6ee7b7' : (n >= TH * 0.5 ? '#fde68a' : '#e2e8f0'); const tag = o.ready ? '✅ 已启用本地校准 μ' : ('还差 ' + o.need + ' 例 · 仍用全省口径'); return '
' + '
' + '' + esc(o.school) + '' + '' + n + ' / ' + TH + '' + '
' + '
' + '
' + tag + ' · 共 ' + o.count + ' 条
' + '
'; }).join(''); return '
' + '
📊 各校「本地校准」进度(含成绩样本 ≥ ' + TH + ' 例才启用)
' + '
' + cards + '
' + '
'; } async function renderLocalProfiles(){ const view = document.getElementById('localprofileView'); view.innerHTML = '
正在加载安康本地学情数据……
'; let rows = []; try { const r = await fetch('/api/local-profiles' + '?token=' + encodeURIComponent(getToken())); const j = await r.json(); if (!j.ok) { view.innerHTML = '
' + esc(j.msg || '未授权,请重新登录') + '
'; return; } rows = j.rows || []; LP_THRESHOLD = j.threshold || 30; LP_BY_SCHOOL = j.bySchool || []; } catch (e) { view.innerHTML = '
本地学情数据加载失败,请确认后端 /api/local-profiles 可访问。
'; return; } LP_ROWS = rows; if (!rows.length) { view.innerHTML = '

📋 安康本地学情 暂无数据

' + '
' + '还没有采集到本地学情记录。
' + '学生打开 选做测评独立页(assessments.html),填写上方「第一步 · 安康本地学情信息采集」表单并提交后,数据会出现在这里。
' + '提示:公网只读版(本页)为数据快照,需本地重新打包才会更新;连后端运行时为实时数据。' + '
'; return; } const schools = [...new Set(rows.map(r => r.school).filter(Boolean))].sort(); const grades = [...new Set(rows.map(r => r.grade).filter(Boolean))].sort(); const bySchool = {}; rows.forEach(r => { const k = r.school || '未填写'; bySchool[k] = (bySchool[k]||0)+1; }); const byGrade = {}; rows.forEach(r => { const k = r.grade || '未填写'; byGrade[k] = (byGrade[k] ||0)+1; }); const withScores = rows.filter(r => r.subjects && LP_SUBJ.some(s => (r.subjects[s]||0) > 0)).length; const exportUrl = '/api/local-profiles?export=csv&token=' + encodeURIComponent(getToken()); view.innerHTML = '

📋 安康本地学情 ' + rows.length + ' 条 · 其中 ' + withScores + ' 条含 9 科成绩

' + '
来源:选做测评独立页(assessments.html)上方「第一步·安康本地学情信息采集」表单。与 TES 三力主测评相互独立,用于春蕾本地教研预测模型校准(中考/高考线、衔接保留率、选科-职业匹配)。30 分钟内同手机+同姓名自动去重。
' + '
' + '
样本总量
' + rows.length + '
' + '
覆盖学校
' + Object.keys(bySchool).length + '
' + '
含成绩样本
' + withScores + '
' + '
' + lpSchoolProgressPanel() + '
按年级:' + (Object.keys(byGrade).map(k => esc(k) + ' ' + byGrade[k] + '').join(' · ') || '—') + '
' + '
' + '' + '' + '' + '' + '⬇ 导出 CSV(当前 ' + rows.length + ' 条)' + '' + '
' + '
' + LP_SUBJ.map(s => '').join('') + '
姓名手机(脱敏)学校年级' + s + '提交时间
' + '
家长手机已做中间 4 位脱敏展示;导出 CSV 含完整号码,仅限春蕾内部教研使用,请勿外传。成绩 0 表示未开设/未参考。样本满 30 例/校 后方可从经验值切换到本地校准 μ。
' + '
'; const body = document.getElementById('lpBody'); const search = document.getElementById('lpSearch'); const sf = document.getElementById('lpSchoolF'); const gf = document.getElementById('lpGradeF'); const sortSel = document.getElementById('lpSort'); const count = document.getElementById('lpCount'); function draw() { let list = LP_ROWS.slice(); const kw = (search.value || '').trim(); if (kw) list = list.filter(r => (r.name || '').indexOf(kw) >= 0 || (r.phone || '').indexOf(kw) >= 0); if (sf.value === '__none__') list = list.filter(r => !r.school); else if (sf.value) list = list.filter(r => r.school === sf.value); if (gf.value) list = list.filter(r => r.grade === gf.value); list.sort((a, b) => sortSel.value === 'old' ? String(a.createdAt || '').localeCompare(String(b.createdAt || '')) : String(b.createdAt || '').localeCompare(String(a.createdAt || ''))); count.textContent = '匹配 ' + list.length + ' 条'; body.innerHTML = list.map(r => { const subs = r.subjects || {}; return '' + esc(r.name || '—') + '' + esc(maskPhone(r.phone)) + '' + esc(r.school || '—') + '' + esc(r.grade || '—') + '' + LP_SUBJ.map(s => { const v = subs[s]; if (!v) return '—'; const rate = v / LP_MAX[s]; const col = rate >= 0.85 ? '#16a34a' : rate >= 0.7 ? '#0f766e' : rate >= 0.6 ? '#a16207' : '#dc2626'; return '' + v + ''; }).join('') + '' + esc(String(r.createdAt || '').slice(0, 16).replace('T', ' ')) + ''; }).join('') || '无匹配记录'; } search.addEventListener('input', draw); sf.addEventListener('change', draw); gf.addEventListener('change', draw); sortSel.addEventListener('change', draw); draw(); } /* ---------- 视图:数据自动维护 ---------- */ function renderMaintenance(){ const v=document.getElementById('maintView'); v.innerHTML = '' + '

🛠 数据自动维护

' + '

系统每日自动巡检:招生季(7–8 月)自动抓取安康中考线 + 陕西高考线写入 score_lines.json;每周聚合测评大数据与常模。下方可手动立即触发,或查看最近一次自动运行结果。

' + '
' + '' + '' + '
' + '
加载状态…
' + '
'
    + '
'; loadMaintStatus(); } function showMaintResult(title, text){ const el=document.getElementById('maintResult'); if(el) el.textContent = '【'+title+'】\n'+text; } async function loadMaintStatus(){ try{ const st=await fetch('/api/admin/collect-status?token='+encodeURIComponent(getToken())).then(x=>x.json()); const el=document.getElementById('maintStatus'); if(el) el.textContent = '上次自动采集年份:'+(st.lastScorelineYear||'—')+' | 上次分数线采集:'+(st.lastScorelineRunAt||'—')+' | 上次大数据聚合:'+(st.lastPilotRunAt||'—'); }catch(e){ const el=document.getElementById('maintStatus'); if(el) el.textContent='状态加载失败:'+e; } } async function refreshScorelines(){ const btn=document.getElementById('btnRefresh'); if(btn){btn.disabled=true;btn.textContent='采集中…';} try{ const r=await fetch('/api/admin/refresh-scorelines?token='+encodeURIComponent(getToken()),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({})}).then(x=>x.json()); const lines=['ok='+r.ok]; if(r.updated&&r.updated.length) lines.push('更新 '+r.updated.length+' 所:'+r.updated.map(u=>u.jsonName+' '+u.from+'→'+u.to).join('、')); else lines.push('无更新(已是最新)'); if(r.failed&&r.failed.length) lines.push('失败源:'+r.failed.map(f=>f.id+':'+f.error).join(';')); showMaintResult('更新升学分数线', lines.join('\n')); }catch(e){ showMaintResult('更新升学分数线','失败:'+e); } finally{ if(btn){btn.disabled=false;btn.textContent='立即更新升学分数线';} } } async function aggregatePilot(){ const btn=document.getElementById('btnAgg'); if(btn){btn.disabled=true;btn.textContent='计算中…';} try{ const r=await fetch('/api/admin/aggregate-pilot?token='+encodeURIComponent(getToken()),{method:'POST'}).then(x=>x.json()); let summary; if(r.proposedMapping&&r.proposedMapping.default){ const d=r.proposedMapping.default; summary='提议映射 base='+d.base+' scale='+d.scale+'(n='+d.n+',R²='+d.R2+')'; if(r.proposedMapping.byGrade) summary+=';分年级提议 '+Object.keys(r.proposedMapping.byGrade).length+' 组'; } else summary=(r.proposedMapping&&r.proposedMapping.note)?r.proposedMapping.note:'已完成'; showMaintResult('重算测评大数据', '提交样本='+r.submissionCount+' 配对样本='+r.pilotCount+'\n'+summary); }catch(e){ showMaintResult('重算测评大数据','失败:'+e); } finally{ if(btn){btn.disabled=false;btn.textContent='重算测评大数据 / 常模';} } } if(getToken()) enterMain(); // 扫码直达:手机扫含 ?pwd= 的二维码打开本页时自动登录 (function(){ try { var pu = new URLSearchParams(location.search).get('pwd'); if (pu) { var inp = document.getElementById('pwd'); if (inp) inp.value = pu; setTimeout(function(){ if (typeof login === 'function') login(); }, 60); } } catch(e){} })();