解析几何解题 → 交互网页 这个技能产出什么 一个可直接用浏览器打开的单页 HTML(三栏): - 左栏 :题面 + 动态控制台 —— 一个可变参数滑块(如直线倾斜角 θ / 动点参数 t)驱动实时 重算的几何量(交点坐标、斜率、数量积、弦长、面积…),以及"理论范围条"或"定值指示"。 - 中栏 :分步解析(公式用 KaTeX ),可一键收起把空间让给画板。 - 右栏 :2D Canvas 动态几何画板(圆锥曲线 + 动直线/动点 + 向量 + 点标注 + 网格坐标轴), 叠加画笔涂鸦工具栏。 形态与目标模板 一致。 依赖(重要) 计算核心 依赖 sympy 。运行脚本前先确认有能 import sympy 的 解释器: (本机用 ,sympy 1.14)。 缺库时 :若 import 报错(sympy 或后续任何库), 先询问用户是否安装 ,同意后再装 ( )或换一个已装该库的解释器; 不要未经询问直接装 。 下文 均指这个能跑通依赖的解释器。 工作流程 第 1 步:得到 problem spec(三入口归一) 把题目整理成结构化 spec(曲线类型与参数、已知点/条件、所求类型与对象、语言)。 - 文字题 :直接抽取。 - 图片 :视觉读图抽取,并 把识别到的题目回显给用户确认 (题面/曲线/参数/所求/语言)再继续。 - 随机出题 :选曲线 + 题型,随机参数 → ker…

,right:'

解析几何解题 → 交互网页 这个技能产出什么 一个可直接用浏览器打开的单页 HTML(三栏): - 左栏 :题面 + 动态控制台 —— 一个可变参数滑块(如直线倾斜角 θ / 动点参数 t)驱动实时 重算的几何量(交点坐标、斜率、数量积、弦长、面积…),以及"理论范围条"或"定值指示"。 - 中栏 :分步解析(公式用 KaTeX ),可一键收起把空间让给画板。 - 右栏 :2D Canvas 动态几何画板(圆锥曲线 + 动直线/动点 + 向量 + 点标注 + 网格坐标轴), 叠加画笔涂鸦工具栏。 形态与目标模板 一致。 依赖(重要) 计算核心 依赖 sympy 。运行脚本前先确认有能 import sympy 的 解释器: (本机用 ,sympy 1.14)。 缺库时 :若 import 报错(sympy 或后续任何库), 先询问用户是否安装 ,同意后再装 ( )或换一个已装该库的解释器; 不要未经询问直接装 。 下文 均指这个能跑通依赖的解释器。 工作流程 第 1 步:得到 problem spec(三入口归一) 把题目整理成结构化 spec(曲线类型与参数、已知点/条件、所求类型与对象、语言)。 - 文字题 :直接抽取。 - 图片 :视觉读图抽取,并 把识别到的题目回显给用户确认 (题面/曲线/参数/所求/语言)再继续。 - 随机出题 :选曲线 + 题型,随机参数 → ker…

,display:false},{left:'\\\\(',right:'\\\\)',display:false},{left:'\\\\[',right:'\\\\]',display:true}],throwOnError:false}); }\n\n function buildConsole(){\n // 题目\n document.getElementById('prob-title').textContent = (DATA.lesson&&DATA.lesson.title)||'';\n document.getElementById('prob-body').innerHTML = (DATA.lesson&&DATA.lesson.problem)||'';\n document.getElementById('console-title').textContent = BOARD.consoleTitle || UI.consoleTitle;\n document.getElementById('sol-title-text').textContent = UI.solutionTitle;\n document.getElementById('sol-vlabel').textContent = UI.solutionTitle;\n\n // 参数滑块\n const p=BOARD.param;\n if(p){\n document.getElementById('param-block').classList.remove('hidden');\n document.getElementById('param-label').innerHTML = (p.label||p.name)+' :';\n const s=document.getElementById('param-slider');\n s.min=p.min; s.max=p.max; s.step=p.step||0.5; s.value=(p.value!==undefined?p.value:p.min);\n const ticks=document.getElementById('param-ticks');\n ticks.innerHTML = (p.ticks||[`${p.min}${p.unit||''}`, `${p.max}${p.unit||''}`]).map(t=>`\u003cspan>${t}\u003c/span>`).join('');\n s.addEventListener('input', update);\n }\n // 读数行容器(值在 update 里填)\n const ro=document.getElementById('readouts');\n ro.innerHTML = (BOARD.readouts||[]).map(r=>{\n if(r.highlight) return `\u003cdiv class=\"flex justify-between items-center pt-1\">\u003cspan class=\"text-slate-800 font-semibold\">${r.label}:\u003c/span>\u003cspan id=\"ro-${r.id}\" class=\"text-base font-bold bg-teal-50 text-teal-700 px-3 py-1 rounded-lg border border-teal-200\">—\u003c/span>\u003c/div>`;\n return `\u003cdiv class=\"flex justify-between border-b border-dashed border-slate-200 pb-2\">\u003cspan>${r.label}:\u003c/span>\u003cspan id=\"ro-${r.id}\" class=\"font-bold\" style=\"color:${r.color?col(r.color):'#0f172a'}\">—\u003c/span>\u003c/div>`;\n }).join('');\n\n // 范围条\n if(BOARD.rangeBar){ document.getElementById('rangebar-block').classList.remove('hidden');\n document.getElementById('rangebar-cur').textContent = UI.current;\n const rl=document.getElementById('rangebar-range'); rl.innerHTML = BOARD.rangeBar.label||''; }\n\n // 定值指示器\n if(BOARD.constant){ document.getElementById('constant-block').classList.remove('hidden');\n document.getElementById('constant-val').innerHTML = '≡ '+(BOARD.constant.label||''); }\n\n // 答案区间(参数轴):高亮 [lo,hi] 子区间\n if(BOARD.answerBand){ const ab=BOARD.answerBand;\n document.getElementById('answerband-block').classList.remove('hidden');\n document.getElementById('answerband-cur').textContent = '当前 '+((BOARD.param&&BOARD.param.name)||'值')+':';\n document.getElementById('answerband-label').innerHTML = ab.label||'';\n const L=((ab.lo-ab.min)/(ab.max-ab.min))*100, R=((ab.max-ab.hi)/(ab.max-ab.min))*100;\n const fill=document.getElementById('answerband-fill');\n fill.style.left=Math.max(L,0)+'%'; fill.style.right=Math.max(R,0)+'%'; }\n\n // 图例\n const legend=document.getElementById('legend'); const items=[];\n (BOARD.conics||[]).forEach(c=>{ if(c.legend!==false) items.push(`\u003cdiv class=\"flex items-center gap-2\">\u003cspan class=\"w-3 h-1.5 inline-block rounded\" style=\"background:${col(c.color||'curve')}\">\u003c/span> ${c.label||c.name}\u003c/div>`); });\n (BOARD.legend||[]).forEach(it=> items.push(`\u003cdiv class=\"flex items-center gap-2\">\u003cspan class=\"w-3 h-1.5 inline-block rounded\" style=\"background:${col(it.color)}\">\u003c/span> ${it.text}\u003c/div>`));\n legend.innerHTML=items.join('');\n\n // 解析步骤\n const sb=document.getElementById('solution-body');\n sb.innerHTML=(DATA.steps||[]).map((st,i)=>{\n const no=String(i+1).padStart(2,'0');\n return `\u003cdiv class=\"${i>0?'border-t border-slate-100 pt-5':''}\">\n \u003cdiv class=\"flex items-start gap-4 mb-4\">\n \u003cspan class=\"w-8 h-8 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center font-bold font-mono text-sm flex-shrink-0\">${no}\u003c/span>\n \u003ch3 class=\"text-lg font-bold text-slate-900 pt-0.5\">${st.title||''}\u003c/h3>\n \u003c/div>\n \u003cdiv class=\"prose max-w-none text-slate-700 leading-relaxed text-sm md:text-base space-y-3\">${st.content||''}\u003c/div>\n \u003c/div>`;\n }).join('');\n\n renderKatex(document.body);\n }\n\n // ===================== 主更新循环 =====================\n let curParam = BOARD.param ? (BOARD.param.value!==undefined?BOARD.param.value:BOARD.param.min) : 0;\n function update(){\n if(BOARD.param){ const s=document.getElementById('param-slider'); curParam=parseFloat(s.value);\n const p=BOARD.param; const dec=p.step?(p.step\u003c0.1?2:(p.step\u003c1?1:0)):0;\n document.getElementById('param-val').textContent = fmt(curParam, dec)+(p.unit||''); }\n const scene=buildScene(curParam);\n const ros=computeReadouts(scene);\n ros.forEach(r=>{ const el=document.getElementById('ro-'+r.id); if(el){ el.textContent=r.text; if(r.dyncolor) el.style.color=r.dyncolor; } });\n // 范围条指针\n if(BOARD.rangeBar){ const rb=BOARD.rangeBar; const tracked=ros.find(r=>r.id===rb.of);\n if(tracked && tracked.scalar!==null){ const pct=((tracked.scalar-rb.min)/(rb.max-rb.min))*88+6;\n document.getElementById('rangebar-ptr').style.left=Math.min(Math.max(pct,0),100)+'%'; } }\n // 答案区间指针(参数轴)\n if(BOARD.answerBand){ const ab=BOARD.answerBand; const pct=((curParam-ab.min)/(ab.max-ab.min))*100;\n document.getElementById('answerband-ptr').style.left=Math.min(Math.max(pct,0),100)+'%'; }\n draw(scene);\n }\n function resetPlot(){ const p=BOARD.param; if(!p) return; const s=document.getElementById('param-slider');\n s.value=(p.standard!==undefined?p.standard:(p.value!==undefined?p.value:p.min)); update(); }\n window.resetPlot=resetPlot;\n\n // ===================== 画笔涂鸦叠加层 =====================\n const drawCanvas=document.getElementById('draw-canvas'); const dctx=drawCanvas.getContext('2d');\n let penMode=null,isDrawing=false,penColor='#ffffff',penSize=3,strokes=[],curStroke=null;\n function resizeDrawCanvas(){ const rect=drawCanvas.parentElement.getBoundingClientRect(); const dpr=window.devicePixelRatio||1;\n drawCanvas.width=rect.width*dpr; drawCanvas.height=rect.height*dpr; drawCanvas.style.width=rect.width+'px'; drawCanvas.style.height=rect.height+'px';\n dctx.setTransform(1,0,0,1,0,0); dctx.scale(dpr,dpr); redrawStrokes(rect.width,rect.height); }\n function redrawStrokes(w,h){ dctx.clearRect(0,0,w,h); dctx.lineCap='round'; dctx.lineJoin='round';\n strokes.forEach(s=>{ if(!s.pts.length)return; const sx=w/s.w,sy=h/s.h;\n dctx.globalCompositeOperation=s.eraser?'destination-out':'source-over'; dctx.strokeStyle=s.color; dctx.lineWidth=s.size;\n dctx.beginPath(); dctx.moveTo(s.pts[0].x*sx,s.pts[0].y*sy);\n if(s.pts.length===1) dctx.lineTo(s.pts[0].x*sx+0.01,s.pts[0].y*sy+0.01); else for(let i=1;i\u003cs.pts.length;i++)dctx.lineTo(s.pts[i].x*sx,s.pts[i].y*sy);\n dctx.stroke(); }); dctx.globalCompositeOperation='source-over'; }\n function drawLiveSegment(){ const p=curStroke.pts,n=p.length; dctx.lineCap='round'; dctx.lineJoin='round';\n dctx.globalCompositeOperation=curStroke.eraser?'destination-out':'source-over'; dctx.strokeStyle=curStroke.color; dctx.lineWidth=curStroke.size;\n dctx.beginPath(); if(n===1){dctx.moveTo(p[0].x,p[0].y);dctx.lineTo(p[0].x+0.01,p[0].y+0.01);} else {dctx.moveTo(p[n-2].x,p[n-2].y);dctx.lineTo(p[n-1].x,p[n-1].y);} dctx.stroke(); dctx.globalCompositeOperation='source-over'; }\n const getDrawPos=e=>{const rect=drawCanvas.getBoundingClientRect(); return {x:e.clientX-rect.left,y:e.clientY-rect.top};};\n drawCanvas.addEventListener('pointerdown',e=>{ if(!penMode)return; isDrawing=true; drawCanvas.setPointerCapture(e.pointerId); const rect=drawCanvas.getBoundingClientRect();\n curStroke={color:penColor,size:penSize,eraser:penMode==='eraser',pts:[getDrawPos(e)],w:rect.width,h:rect.height}; strokes.push(curStroke); drawLiveSegment(); });\n drawCanvas.addEventListener('pointermove',e=>{ if(!isDrawing||!penMode)return; curStroke.pts.push(getDrawPos(e)); drawLiveSegment(); });\n function endStroke(){isDrawing=false;curStroke=null;}\n drawCanvas.addEventListener('pointerup',endStroke); drawCanvas.addEventListener('pointercancel',endStroke); drawCanvas.addEventListener('pointerleave',()=>{if(isDrawing)endStroke();});\n const penBtn=document.getElementById('pen-btn'),eraserBtn=document.getElementById('eraser-btn'),clearDrawBtn=document.getElementById('clear-draw-btn'),undoBtn=document.getElementById('undo-btn'),penSizeInput=document.getElementById('pen-size'),swatches=document.querySelectorAll('.draw-swatch');\n function updateToolbarUI(){ penBtn.classList.toggle('bg-slate-600',penMode==='pen'); penBtn.classList.toggle('text-white',penMode==='pen'); eraserBtn.classList.toggle('bg-slate-600',penMode==='eraser'); eraserBtn.classList.toggle('text-white',penMode==='eraser');\n if(penMode){drawCanvas.style.pointerEvents='auto'; drawCanvas.style.cursor=penMode==='eraser'?'cell':'crosshair';} else drawCanvas.style.pointerEvents='none'; }\n function setMode(m){penMode=(penMode===m)?null:m; updateToolbarUI();}\n penBtn.addEventListener('click',()=>setMode('pen')); eraserBtn.addEventListener('click',()=>setMode('eraser'));\n clearDrawBtn.addEventListener('click',()=>{strokes=[]; const rect=drawCanvas.getBoundingClientRect(); redrawStrokes(rect.width,rect.height);});\n undoBtn.addEventListener('click',()=>{if(!strokes.length)return; strokes.pop(); const rect=drawCanvas.getBoundingClientRect(); redrawStrokes(rect.width,rect.height);});\n penSizeInput.addEventListener('input',()=>{penSize=parseInt(penSizeInput.value,10);});\n swatches.forEach(sw=>sw.addEventListener('click',()=>{penColor=sw.dataset.color; swatches.forEach(s=>{s.classList.remove('ring-2','ring-white');s.classList.add('ring-1','ring-slate-600');}); sw.classList.remove('ring-1','ring-slate-600'); sw.classList.add('ring-2','ring-white'); if(penMode!=='pen')setMode('pen');}));\n updateToolbarUI();\n\n // ===================== 解析面板收起 / 展开 =====================\n const solutionPanel=document.getElementById('solution-panel'),solutionBody=document.getElementById('solution-body'),toggleSolutionBtn=document.getElementById('toggle-solution'),solTitle=document.getElementById('sol-title'),solVlabel=document.getElementById('sol-vlabel'),solHeader=document.getElementById('sol-header'),solChevron=document.getElementById('sol-chevron');\n let solutionCollapsed=false;\n function applySolutionState(){ solutionBody.classList.toggle('hidden',solutionCollapsed); solChevron.classList.toggle('rotate-180',solutionCollapsed);\n solutionPanel.classList.toggle('lg:w-[360px]',!solutionCollapsed); solutionPanel.classList.toggle('lg:w-14',solutionCollapsed); solutionPanel.classList.toggle('lg:px-2',solutionCollapsed);\n solHeader.classList.toggle('lg:flex-col',solutionCollapsed); solHeader.classList.toggle('lg:gap-3',solutionCollapsed); solTitle.classList.toggle('lg:hidden',solutionCollapsed); solVlabel.classList.toggle('lg:block',solutionCollapsed);\n toggleSolutionBtn.title=solutionCollapsed?UI.expand:UI.collapse; requestAnimationFrame(resizeCanvas); }\n solutionPanel.addEventListener('transitionend',e=>{if(e.propertyName==='width')resizeCanvas();});\n toggleSolutionBtn.addEventListener('click',()=>{solutionCollapsed=!solutionCollapsed; applySolutionState();});\n\n // ===================== 启动 =====================\n window.addEventListener('resize',resizeCanvas);\n window.onload=()=>{ buildConsole(); resizeCanvas(); };\n \u003c/script>\n\u003c/body>\n\u003c/html>\n","content_type":"text/html; charset=utf-8","language":"markup","size":49389,"content_sha256":"aaeead79a8b2ba560622a5f5137d96195bfac599fc2cd9f9c227d69bca4ccfbd"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"解析几何解题 → 交互网页","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"这个技能产出什么","type":"text"}]},{"type":"paragraph","content":[{"text":"一个可直接用浏览器打开的单页 HTML(三栏):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"左栏","type":"text","marks":[{"type":"strong"}]},{"text":":题面 + 动态控制台 —— 一个可变参数滑块(如直线倾斜角 θ / 动点参数 t)驱动实时 重算的几何量(交点坐标、斜率、数量积、弦长、面积…),以及\"理论范围条\"或\"定值指示\"。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"中栏","type":"text","marks":[{"type":"strong"}]},{"text":":分步解析(公式用 ","type":"text"},{"text":"KaTeX","type":"text","marks":[{"type":"strong"}]},{"text":"),可一键收起把空间让给画板。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"右栏","type":"text","marks":[{"type":"strong"}]},{"text":":2D Canvas 动态几何画板(圆锥曲线 + 动直线/动点 + 向量 + 点标注 + 网格坐标轴), 叠加画笔涂鸦工具栏。","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"形态与目标模板 ","type":"text"},{"text":"/Users/wuyi/code/code2026/6/template/code_artifact.html","type":"text","marks":[{"type":"code_inline"}]},{"text":" 一致。","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"依赖(重要)","type":"text"}]},{"type":"paragraph","content":[{"text":"计算核心 ","type":"text"},{"text":"lib/analytic_kernel.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" 依赖 ","type":"text"},{"text":"sympy","type":"text","marks":[{"type":"strong"}]},{"text":"。运行脚本前先确认有能 import sympy 的 解释器:","type":"text"},{"text":"python3 -c \"import sympy\"","type":"text","marks":[{"type":"code_inline"}]},{"text":"(本机用 ","type":"text"},{"text":"/opt/homebrew/bin/python3.11","type":"text","marks":[{"type":"code_inline"}]},{"text":",sympy 1.14)。","type":"text"}]},{"type":"paragraph","content":[{"text":"缺库时","type":"text","marks":[{"type":"strong"}]},{"text":":若 import 报错(sympy 或后续任何库),","type":"text"},{"text":"先询问用户是否安装","type":"text","marks":[{"type":"strong"}]},{"text":",同意后再装 (","type":"text"},{"text":"python3 -m pip install \u003c库名>","type":"text","marks":[{"type":"code_inline"}]},{"text":")或换一个已装该库的解释器;","type":"text"},{"text":"不要未经询问直接装","type":"text","marks":[{"type":"strong"}]},{"text":"。 下文 ","type":"text"},{"text":"python3","type":"text","marks":[{"type":"code_inline"}]},{"text":" 均指这个能跑通依赖的解释器。","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"工作流程","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"第 1 步:得到 problem spec(三入口归一)","type":"text"}]},{"type":"paragraph","content":[{"text":"把题目整理成结构化 spec(曲线类型与参数、已知点/条件、所求类型与对象、语言)。","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"文字题","type":"text","marks":[{"type":"strong"}]},{"text":":直接抽取。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"图片","type":"text","marks":[{"type":"strong"}]},{"text":":视觉读图抽取,并","type":"text"},{"text":"把识别到的题目回显给用户确认","type":"text","marks":[{"type":"strong"}]},{"text":"(题面/曲线/参数/所求/语言)再继续。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"随机出题","type":"text","marks":[{"type":"strong"}]},{"text":":选曲线 + 题型,随机参数 → kernel 求解,用 ","type":"text"},{"text":"analytic_kernel.is_clean(...)","type":"text","marks":[{"type":"code_inline"}]},{"text":" 判答案 是否规整,不规整就重抽。","type":"text"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"输出语言跟随提示词语言","type":"text","marks":[{"type":"strong"}]},{"text":":英文提示 → 英文网页,中文 → 中文。spec 记下 ","type":"text"},{"text":"language","type":"text","marks":[{"type":"code_inline"}]},{"text":"。","type":"text"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"第 2 步:用 kernel 精确计算(不要心算)","type":"text"}]},{"type":"paragraph","content":[{"text":"按 ","type":"text"},{"text":"references/conventions.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" 的解法配方,调用 ","type":"text"},{"text":"lib/analytic_kernel.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" 与 ","type":"text"},{"text":"lib/conics.py","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"conics.ellipse/hyperbola/parabola/circle(...)","type":"text","marks":[{"type":"code_inline"}]},{"text":" 得曲线对象(精确 a,b,c、焦点、顶点、准线、 渐近线、","type":"text"},{"text":"eq_latex","type":"text","marks":[{"type":"code_inline"}]},{"text":"、以及给前端引擎的 ","type":"text"},{"text":"board","type":"text","marks":[{"type":"code_inline"}]},{"text":" dict)。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"chord_setup(conic, through)","type":"text","marks":[{"type":"code_inline"}]},{"text":" 联立含参直线 ","type":"text"},{"text":"x=my+c","type":"text","marks":[{"type":"code_inline"}]},{"text":" 得 y 的二次方程 + 韦达量(精确)。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"目标量:","type":"text"},{"text":"dot_product_expr","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"chord_len_sq_expr","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"triangle_area_expr","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"slope_product_central","type":"text","marks":[{"type":"code_inline"}]},{"text":" …","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"取值范围:","type":"text"},{"text":"range_over_m(expr, horizontal_valid=?)","type":"text","marks":[{"type":"code_inline"}]},{"text":" —— ","type":"text"},{"text":"含开闭端点判定","type":"text","marks":[{"type":"strong"}]},{"text":"(关键正确性点,见下)。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"定值:","type":"text"},{"text":"is_constant_in_m(expr)","type":"text","marks":[{"type":"code_inline"}]},{"text":"。","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"可命令行自检 kernel:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 lib/analytic_kernel.py # 旗舰题内置断言自检","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"⚠️ ","type":"text"},{"text":"端点开闭 = 正确性命门","type":"text","marks":[{"type":"strong"}]},{"text":":过焦点的弦,水平线(x 轴,θ=0)与竖直线(θ=90)都是合法直线, 它们取到的端点要计入。例:椭圆 MA·MB 题,x 轴取到 −3、竖直线取到 7/4,故答案是","type":"text"},{"text":"闭区间","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"[-3, 7/4]","type":"text","marks":[{"type":"code_inline"}]},{"text":"(很多教辅误写成开的 ","type":"text"},{"text":"(-3, 7/4]","type":"text","marks":[{"type":"code_inline"}]},{"text":")。","type":"text"},{"text":"range_over_m","type":"text","marks":[{"type":"code_inline"}]},{"text":" 已据此判定,且这样答案与交互 工具一致——拖滑块到 0° 就读到 −3。抛物线焦点弦的\"轴方向\"是退化线(只交一点),其极限端点 不计入(","type":"text"},{"text":"horizontal_valid=False","type":"text","marks":[{"type":"code_inline"}]},{"text":" 或限制 param 范围)。","type":"text"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"第 3 步:组装数据并注入模板","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"📍 ","type":"text"},{"text":"输出位置 & 唯一产物(最重要)","type":"text","marks":[{"type":"strong"}]},{"text":":交付给用户的","type":"text"},{"text":"只有一个 ","type":"text","marks":[{"type":"strong"}]},{"text":".html","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":",写到","type":"text"},{"text":"当前工作目录 (","type":"text","marks":[{"type":"strong"}]},{"text":"Path.cwd()","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":")","type":"text","marks":[{"type":"strong"}]},{"text":"(除非用户显式指定路径)。cwd 里","type":"text"},{"text":"不要留任何别的文件","type":"text","marks":[{"type":"strong"}]},{"text":"——构建脚本(","type":"text"},{"text":".py","type":"text","marks":[{"type":"code_inline"}]},{"text":")、 ","type":"text"},{"text":"__pycache__","type":"text","marks":[{"type":"code_inline"}]},{"text":"、自检截图(","type":"text"},{"text":".png","type":"text","marks":[{"type":"code_inline"}]},{"text":")、临时文件都","type":"text"},{"text":"不是交付物","type":"text","marks":[{"type":"strong"}]},{"text":",一律放 ","type":"text"},{"text":"/tmp","type":"text","marks":[{"type":"code_inline"}]},{"text":" 或用完即删。 也","type":"text"},{"text":"绝不要","type":"text","marks":[{"type":"strong"}]},{"text":"写进技能自身目录(","type":"text"},{"text":"skills/edu-analytic-geometry/output/","type":"text","marks":[{"type":"code_inline"}]},{"text":" 是技能内部样例)。","type":"text"}]}]},{"type":"paragraph","content":[{"text":"把\"组装数据 + 注入模板\"的","type":"text"},{"text":"构建脚本写到临时目录","type":"text","marks":[{"type":"strong"}]},{"text":"(如 ","type":"text"},{"text":"/tmp/ag_build.py","type":"text","marks":[{"type":"code_inline"}]},{"text":"),让它","type":"text"},{"text":"只把 ","type":"text","marks":[{"type":"strong"}]},{"text":".html","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" 写到 cwd","type":"text","marks":[{"type":"strong"}]},{"text":";脚本拼出 ","type":"text"},{"text":"lesson","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"steps","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"board","type":"text","marks":[{"type":"code_inline"}]},{"text":" 数据(schema 见 ","type":"text"},{"text":"references/problem-schema.md","type":"text","marks":[{"type":"code_inline"}]},{"text":"), 调用 ","type":"text"},{"text":"generate.render_html(data, out)","type":"text","marks":[{"type":"code_inline"}]},{"text":" 注入 ","type":"text"},{"text":"template/board.html","type":"text","marks":[{"type":"code_inline"}]},{"text":",","type":"text"},{"text":"跑完即删脚本","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"# 构建脚本放 /tmp(不要放 cwd):/tmp/ag_build.py\nimport sys; sys.dont_write_bytecode = True # 不生成 __pycache__\nsys.path.insert(0, \"\u003c技能目录>/scripts\")\nimport generate\nfrom pathlib import Path\ndata = {\"lesson\": {...}, \"steps\": [...], \"board\": {...}}\nout = Path.cwd() / \"solution-\u003c题目简述>.html\" # 唯一产物,落在用户当前目录\ngenerate.render_html(data, out)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 -B /tmp/ag_build.py && rm -f /tmp/ag_build.py # -B 不写字节码;跑完删临时脚本,cwd 只剩 .html","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"steps[*].content","type":"text","marks":[{"type":"code_inline"}]},{"text":" 里的数值","type":"text"},{"text":"直接引用 kernel 结果","type":"text","marks":[{"type":"strong"}]},{"text":"(用 ","type":"text"},{"text":"K.tex(...)","type":"text","marks":[{"type":"code_inline"}]},{"text":" 输出 LaTeX),模型只负责 组织讲解文字(按目标语言)。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"board","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用 kernel 给的曲线 ","type":"text"},{"text":"board","type":"text","marks":[{"type":"code_inline"}]},{"text":" dict、精确点坐标、","type":"text"},{"text":"param","type":"text","marks":[{"type":"code_inline"}]},{"text":"、","type":"text"},{"text":"derived","type":"text","marks":[{"type":"code_inline"}]},{"text":" 构造序列、","type":"text"},{"text":"readouts","type":"text","marks":[{"type":"code_inline"}]},{"text":"、 ","type":"text"},{"text":"rangeBar","type":"text","marks":[{"type":"code_inline"}]},{"text":"(范围题)/ ","type":"text"},{"text":"constant","type":"text","marks":[{"type":"code_inline"}]},{"text":"(定值题)/ ","type":"text"},{"text":"answerBand","type":"text","marks":[{"type":"code_inline"}]},{"text":"(","type":"text"},{"text":"形状参数题","type":"text","marks":[{"type":"strong"}]},{"text":",如离心率范围)。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"形状参数题(滑块=离心率 e 等)","type":"text","marks":[{"type":"strong"}]},{"text":":自然动态量是曲线本身的形状而非动直线/动点时,让滑块=该参数, 把曲线 ","type":"text"},{"text":"a/b/c","type":"text","marks":[{"type":"code_inline"}]},{"text":"、焦点、动点坐标写成 ","type":"text"},{"text":"@param","type":"text","marks":[{"type":"code_inline"}]},{"text":" 的","type":"text"},{"text":"表达式字符串","type":"text","marks":[{"type":"strong"}]},{"text":"(引擎每帧重绘曲线/焦点/渐近线), 配 ","type":"text"},{"text":"status","type":"text","marks":[{"type":"code_inline"}]},{"text":" 读数显示不等式状态、","type":"text"},{"text":"answerBand","type":"text","marks":[{"type":"code_inline"}]},{"text":" 在参数轴高亮答案区间。见 conventions「形状参数题」。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"可直接照抄的范本","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"scripts/generate.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" 里 6 个 ","type":"text"},{"text":"build_*","type":"text","marks":[{"type":"code_inline"}]},{"text":" 覆盖各类交互范式: ","type":"text"},{"text":"ellipse_dot_range","type":"text","marks":[{"type":"code_inline"}]},{"text":"(范围条)、","type":"text"},{"text":"ellipse_chord_range","type":"text","marks":[{"type":"code_inline"}]},{"text":"、","type":"text"},{"text":"ellipse_area_max","type":"text","marks":[{"type":"code_inline"}]},{"text":"、 ","type":"text"},{"text":"ellipse_slopeprod_const","type":"text","marks":[{"type":"code_inline"}]},{"text":"(定值·中心对称)、","type":"text"},{"text":"parabola_dot_const","type":"text","marks":[{"type":"code_inline"}]},{"text":"(定值·抛物线)、 ","type":"text"},{"text":"hyperbola_ecc_range","type":"text","marks":[{"type":"code_inline"}]},{"text":"(","type":"text"},{"text":"形状参数","type":"text","marks":[{"type":"strong"}]},{"text":":滑块=e,曲线随之重绘 + ","type":"text"},{"text":"status","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"answerBand","type":"text","marks":[{"type":"code_inline"}]},{"text":")。","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"已注册题直接出(","type":"text"},{"text":"-B","type":"text","marks":[{"type":"code_inline"}]},{"text":" 不写字节码;不传路径默认写技能 output,交付给用户时务必改成 cwd 下的 ","type":"text"},{"text":".html","type":"text","marks":[{"type":"code_inline"}]},{"text":"):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 -B scripts/generate.py list # 列出题型\npython3 -B scripts/generate.py ellipse_dot_range ./sol.html\npython3 -B scripts/generate.py all ./out_dir # 全部题型","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"第 4 步:自检(正确性方案)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"kernel 答案 == 答案卡 ","type":"text"},{"text":"lesson.answer","type":"text","marks":[{"type":"code_inline"}]},{"text":" == 末步骤展示值 == ","type":"text"},{"text":"JS 标准位/扫段重算值","type":"text","marks":[{"type":"strong"}]},{"text":",四者一致 (","type":"text"},{"text":"build_*","type":"text","marks":[{"type":"code_inline"}]},{"text":" 内已加 ","type":"text"},{"text":"assert","type":"text","marks":[{"type":"code_inline"}]},{"text":")。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"rangeBar","type":"text","marks":[{"type":"code_inline"}]},{"text":" 端点来自 kernel 的 ","type":"text"},{"text":"range_over_m","type":"text","marks":[{"type":"code_inline"}]},{"text":";","type":"text"},{"text":"constant","type":"text","marks":[{"type":"code_inline"}]},{"text":" 值来自 kernel 的定值。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"起本地静态服务(服务","type":"text"},{"text":"输出文件所在目录","type":"text","marks":[{"type":"strong"}]},{"text":",即 cwd)用预览检查:无控制台报错、KaTeX 正常、 滑块实时重算正确、范围条/定值/定点/轨迹行为符合、画笔与收起面板可用。 (技能仓库内开发时可用 ","type":"text"},{"text":".claude/launch.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" 的 ","type":"text"},{"text":"ag-preview","type":"text","marks":[{"type":"code_inline"}]},{"text":",端口 4601;别处运行就对 cwd 起 一个临时静态服务。)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"自检截图只给你自己看","type":"text","marks":[{"type":"strong"}]},{"text":":preview 工具直接返回图像,","type":"text"},{"text":"不要把 ","type":"text","marks":[{"type":"strong"}]},{"text":".png","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" 存到 cwd","type":"text","marks":[{"type":"strong"}]},{"text":";本地静态服务只读不写、 不产生文件。自检产生的任何临时文件(构建脚本 ","type":"text"},{"text":".py","type":"text","marks":[{"type":"code_inline"}]},{"text":"、截图 ","type":"text"},{"text":".png","type":"text","marks":[{"type":"code_inline"}]},{"text":"、","type":"text"},{"text":"__pycache__","type":"text","marks":[{"type":"code_inline"}]},{"text":" 等)交付前一律清掉。","type":"text"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"⚠️ ","type":"text"},{"text":"必须关闭你开过的端口/服务","type":"text","marks":[{"type":"strong"}]},{"text":":预览一结束立即停掉,","type":"text"},{"text":"绝不留占用端口的进程","type":"text","marks":[{"type":"strong"}]},{"text":"。","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"preview 工具开的:","type":"text"},{"text":"preview_stop","type":"text","marks":[{"type":"code_inline"}]},{"text":"(传 serverId)。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"直接起的 ","type":"text"},{"text":"http.server","type":"text","marks":[{"type":"code_inline"}]},{"text":":用完 ","type":"text"},{"text":"kill","type":"text","marks":[{"type":"code_inline"}]},{"text":",或 ","type":"text"},{"text":"lsof -nP -iTCP:\u003cport> -sTCP:LISTEN","type":"text","marks":[{"type":"code_inline"}]},{"text":" 确认已释放。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"交付前确认端口已释放再告诉用户。开了不关 = 未完成自检。","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"第 5 步:交付","type":"text"}]},{"type":"paragraph","content":[{"text":"成品写在","type":"text"},{"text":"用户当前工作目录(cwd)","type":"text","marks":[{"type":"strong"}]},{"text":",命名形如 ","type":"text"},{"text":"solution-\u003c题目简述>.html","type":"text","marks":[{"type":"code_inline"}]},{"text":",把路径告诉用户, 可直接浏览器打开。交付前确认:","type":"text"},{"text":"(1)","type":"text","marks":[{"type":"strong"}]},{"text":" 成品在 cwd、不在技能目录;","type":"text"},{"text":"(2)","type":"text","marks":[{"type":"strong"}]},{"text":" 没有遗留本次预览 开启的本地服务/端口;","type":"text"},{"text":"(3)","type":"text","marks":[{"type":"strong"}]},{"text":" cwd 里","type":"text"},{"text":"只新增了这一个 ","type":"text","marks":[{"type":"strong"}]},{"text":".html","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":"——没有 ","type":"text"},{"text":".py","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":".png","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"__pycache__","type":"text","marks":[{"type":"code_inline"}]},{"text":" / 临时文件(用 ","type":"text"},{"text":"git status","type":"text","marks":[{"type":"code_inline"}]},{"text":" 或 ","type":"text"},{"text":"ls","type":"text","marks":[{"type":"code_inline"}]},{"text":" 核一眼,有就删掉)。","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"扩展","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"加题型","type":"text","marks":[{"type":"strong"}]},{"text":":在 ","type":"text"},{"text":"analytic_kernel.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" 加目标量函数(写成 m 的表达式)+ 复用 ","type":"text"},{"text":"range_over_m","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"is_constant_in_m","type":"text","marks":[{"type":"code_inline"}]},{"text":";在 ","type":"text"},{"text":"generate.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" 加一个 ","type":"text"},{"text":"build_*","type":"text","marks":[{"type":"code_inline"}]},{"text":",选定交互范式(范围条 / 定值 / 定点 / 轨迹 trace / 形状参数 answerBand)。见 ","type":"text"},{"text":"references/conventions.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" 配方表。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"加曲线","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"conics.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" 已有椭圆/双曲线/抛物线/圆;前端 ","type":"text"},{"text":"board.html","type":"text","marks":[{"type":"code_inline"}]},{"text":" 引擎已支持四类渲染、 渐近线、准线方向。新曲线在两处各加一份即可。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"加交互构造","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"board.html","type":"text","marks":[{"type":"code_inline"}]},{"text":" 的 ","type":"text"},{"text":"buildScene","type":"text","marks":[{"type":"code_inline"}]},{"text":" switch 是构造库(","type":"text"},{"text":"line_through_angle","type":"text","marks":[{"type":"code_inline"}]},{"text":"、 ","type":"text"},{"text":"intersect_line_conic","type":"text","marks":[{"type":"code_inline"}]},{"text":"、","type":"text"},{"text":"point_on_conic","type":"text","marks":[{"type":"code_inline"}]},{"text":"、","type":"text"},{"text":"point_reflect","type":"text","marks":[{"type":"code_inline"}]},{"text":"、","type":"text"},{"text":"tangent_at","type":"text","marks":[{"type":"code_inline"}]},{"text":"、","type":"text"},{"text":"foot_perp","type":"text","marks":[{"type":"code_inline"}]},{"text":"…), 按需扩充并在 schema 文档登记。","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"目录","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"template/board.html","type":"text","marks":[{"type":"code_inline"}]},{"text":" — 数据驱动模板(通用 2D 渲染器 + 参数引擎 + 数据岛 ","type":"text"},{"text":"__LESSON_DATA__","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"lib/conics.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" — 圆锥曲线 sympy 定义库(特殊点 / LaTeX / board dict)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"lib/analytic_kernel.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" — sympy 精确求解核心(联立·韦达·范围·定值)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/generate.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" — 注入模板 + 5 个 build_* 范本 + 批量/单题出题","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/problem-schema.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — 数据格式(board 引擎 schema)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/conventions.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — 标准式、解法配方表、韦达/换元套路、端点开闭、自检","type":"text"}]}]}]}]},"metadata":{"date":"2026-11-02","name":"edu-analytic-geometry","author":"@skillopedia","source":{"stars":1734,"repo_name":"edulab","origin_url":"https://github.com/wy51ai/edulab/blob/HEAD/skills/edu-analytic-geometry/SKILL.md","repo_owner":"wy51ai","body_sha256":"39f936412cb61a17255f960a7963cdb000de2e5ab3d08c21c2ea0f1d0442d7cd","cluster_key":"768485ba5ecaf5c16f7c9e819106587f4ae32a25df16a4dfe68feda3f7384a39","clean_bundle":{"format":"clean-skill-bundle-v1","source":"wy51ai/edulab/skills/edu-analytic-geometry/SKILL.md","text_bytes":104017,"source_commit":"805fdc5dd6bdf942290a7791924bf0ee08f26632","skills_sh_rank":16530,"attachment_count":6,"text_attachments":6,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/edu-analytic-geometry/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"education-research","category_label":"Education"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"education-research","import_tag":"skills-sh-top20k-2026-11","description":"把一道解析几何题解成一个自包含的交互教学网页:左栏题面 + 动态控制台(一个 可变参数滑块驱动实时重算的几何量 + 理论范围/定值指示),中栏 KaTeX 分步解析,右栏 2D Canvas 动态几何画板(椭圆/双曲线/抛物线/圆 + 动直线/动点 + 向量 + 标注 + 画笔涂鸦)。 支持三入口——给定文字题、随机出题、上传题目图片识别后解题。覆盖求标准方程、弦长、向量 数量积范围/定值、三角形面积最值、定点、定值(斜率之积)、轨迹、离心率等题型,统一\"设含参 直线 x=my+c + 联立 + 韦达 + 换元/常数分离\",并由 sympy 精确计算驱动(答案、坐标、步骤 数值、交互引擎的理论范围同源一致)。其它 agent 也可调用本技能生成此类网页。形态与 edu-solid-geometry 平行,但用 Canvas-2D + KaTeX(非 Three.js)。 触发词:解析几何, 圆锥曲线, 椭圆, 双曲线, 抛物线, 直线与圆锥曲线, 焦点弦, 弦长, 数量积取值范围, 向量数量积, 定点问题, 定值问题, 斜率之积, 三角形面积最值, 轨迹方程, 离心率, 解这道解析几何题, 随机出一道圆锥曲线题, 这张图里的解析几何题; analytic geometry, conic sections, ellipse, hyperbola, parabola, chord length, dot product range, fixed point, fixed value, locus, eccentricity, interactive analytic geometry solution page."}},"renderedAt":1793666381998}

解析几何解题 → 交互网页 这个技能产出什么 一个可直接用浏览器打开的单页 HTML(三栏): - 左栏 :题面 + 动态控制台 —— 一个可变参数滑块(如直线倾斜角 θ / 动点参数 t)驱动实时 重算的几何量(交点坐标、斜率、数量积、弦长、面积…),以及"理论范围条"或"定值指示"。 - 中栏 :分步解析(公式用 KaTeX ),可一键收起把空间让给画板。 - 右栏 :2D Canvas 动态几何画板(圆锥曲线 + 动直线/动点 + 向量 + 点标注 + 网格坐标轴), 叠加画笔涂鸦工具栏。 形态与目标模板 一致。 依赖(重要) 计算核心 依赖 sympy 。运行脚本前先确认有能 import sympy 的 解释器: (本机用 ,sympy 1.14)。 缺库时 :若 import 报错(sympy 或后续任何库), 先询问用户是否安装 ,同意后再装 ( )或换一个已装该库的解释器; 不要未经询问直接装 。 下文 均指这个能跑通依赖的解释器。 工作流程 第 1 步:得到 problem spec(三入口归一) 把题目整理成结构化 spec(曲线类型与参数、已知点/条件、所求类型与对象、语言)。 - 文字题 :直接抽取。 - 图片 :视觉读图抽取,并 把识别到的题目回显给用户确认 (题面/曲线/参数/所求/语言)再继续。 - 随机出题 :选曲线 + 题型,随机参数 → ker…