Selaa lähdekoodia

修非法转义序列 (未来 Python 会 SyntaxError) + 自检加"源码可编译"一行

全量编译 src/ + scripts/(168 个文件)把 SyntaxWarning 当错误报出来, 只剩一处:
  src\windcms\serve.py:12,68 —— JS 正则写在普通字符串里: /\*\*([^*]+)\*\*/g 与 /\d+/g。
Python 3.12 只给 SyntaxWarning, 未来版本会直接 SyntaxError; 开发机上看不见, 换解释器才炸。

改法(不是改 raw): 同一串里混着 `\\n`(有意转义成 JS 的 \n) 与 `\d`(想写成 JS 的 \d),
改 raw 会把 `\\n` 变成"两个反斜杠+n", 语义就变了。正确做法是把非法转义补成合法: `\*` → `\\*`,
`\d` → `\\d` —— 结果字符序列与 Python 当前的宽容行为完全一致。

证明没改语义: 改前/改后各把该文件 237 个字符串常量的求值内容算指纹, 都是 70bd628f72f72101,
JS 文本一字未变; 改后全库非法转义 0 处, `-W error::SyntaxWarning` 编译通过。

guanlan.py check 新增一行: "源码可编译 (168 个文件, 无非法转义/语法错)" —— 换机器/换解释器前先跑,
这类问题不必等浏览到那一页才发现。自检现状 3 项 FAIL 全是已知缺口: sop/findings.json 与
guanlan/facts_contract_v0.json 不在(缺口清单 A3)、本机 Ollama 无模型(环境项);
L0/L1 产物、本体对象库、门户、仿真、viewer、原始件目录均 OK。
zhouyang.xie 4 viikkoa sitten
vanhempi
commit
743d4db77c
3 muutettua tiedostoa jossa 34 lisäystä ja 3 poistoa
  1. 18 0
      _修复记录_20260911/README.md
  2. 14 1
      guanlan.py
  3. 2 2
      src/windcms/serve.py

+ 18 - 0
_修复记录_20260911/README.md

@@ -296,6 +296,24 @@ L0 仓 16 件 1.78 MB (随包 118 件 —— 差额就是"包内没有生成端"
 **别为此把超时压到健康模块也可能被误判的程度**(实测最慢的健康探针 `sim_sys` 345 ms, 其余 <25 ms,
 1.5 s 留了 4 倍余量)。
 
+## 非法转义序列 + 自检加一行 (2026-09-11)
+
+全量编译 `src/` + `scripts/`(168 个文件)把 `SyntaxWarning` 当错误报出来, 逮到最后一处潜伏炸点:
+
+- `src\windcms\serve.py:12,68` —— JS 正则写在普通字符串里: `/\*\*([^*]+)\*\*/g` 与 `/\d+/g`。
+  Python 3.12 只警告, **未来版本会变成 SyntaxError**; 而且开发机上永远看不见, 换解释器才炸。
+- **不能简单改成 raw 字符串**: 同一串里混着 `\\n`(有意转义成 JS 的 `\n`) 与 `\d`(想写成 JS 的 `\d`),
+  改 raw 会把 `\\n` 变成"两个反斜杠 + n", 语义就变了。正确改法是**把非法转义补成合法**: `\*` → `\\*`。
+- **证明没改语义**: 改前/改后各把该文件 237 个字符串常量的求值内容算指纹 —— 都是 `70bd628f72f72101`,
+  JS 文本一字未变; 改后全库非法转义 **0 处**, `-W error::SyntaxWarning` 编译通过。
+
+`guanlan.py check` 顺带加了一行自检: **源码可编译 (168 个文件, 无非法转义/语法错)** —— 换机器/换解释器
+之前先跑它, 这类问题不必等运行到那一页才发现。
+
+> 自检现状(原始重算后): 3 项 FAIL —— `outputs/rudong/sop/findings.json` 与
+> `outputs/rudong/guanlan/facts_contract_v0.json` 不在(缺口清单 A3), 本机 Ollama 无模型(环境项);
+> L0/L1 产物、本体对象库、门户、仿真、viewer、原始件目录都是 OK。
+
 ## 仍然存在 (不是代码问题)
 
 - `/local-ai/` 仍是 DOWN: 本机 Ollama 没运行, 且 `%USERPROFILE%\.ollama` 下**没有任何模型**

+ 14 - 1
guanlan.py

@@ -9,7 +9,7 @@
   python guanlan.py qa       离线模型已知答案回放 (需服务已起; 参数见 scripts/llm_known_answer.py)
 端口与路径在 configs/serve.json (没有则用内置默认). 所有服务只绑 127.0.0.1."""
 from __future__ import annotations
-import json, os, socket, subprocess, sys, time, urllib.request, webbrowser, signal
+import json, os, socket, subprocess, sys, time, urllib.request, warnings, webbrowser, signal
 from pathlib import Path
 ROOT = Path(__file__).resolve().parent; RUN = ROOT / "run"; LOGS = ROOT / "logs"; PIDS = RUN / "pids.json"
 WIN = os.name == "nt"
@@ -113,6 +113,19 @@ def cmd_check(c):
     def row(name, good, note=""): 
         nonlocal ok; ok &= bool(good); print(f"  [{'OK' if good else 'FAIL'}] {name}{(' — ' + note) if note else ''}")
     print(f"观澜·如东样板 v2 自检 @ {ROOT}"); v = sys.version_info; row("Python ≥ 3.11", v >= (3, 11), f"{v.major}.{v.minor}.{v.micro} ({sys.executable})")
+    # 源码可编译性 —— 重点是**非法转义序列**: 普通字符串里写 `\d` `\*` 这类, Python 3.12 只给
+    # SyntaxWarning, 未来版本会变成 SyntaxError。这类问题在开发机上永远看不见, 换台机器/换个解释器才炸
+    # (2026-09-11 逮到 src\windcms\serve.py 里 JS 正则的两处, 已按"补成合法转义"修好)。
+    srcs = sorted(list((ROOT / "src").rglob("*.py")) + list((ROOT / "scripts").rglob("*.py")))
+    bad = []
+    with warnings.catch_warnings(record=True) as w:
+        warnings.simplefilter("always")
+        for f in srcs:
+            try: compile(f.read_text(encoding="utf-8"), str(f), "exec")
+            except SyntaxError as e: bad.append(f"{f.relative_to(ROOT)}:{e.lineno} {e.msg}")
+        for it in w:
+            if issubclass(it.category, SyntaxWarning): bad.append(f"{Path(str(it.filename)).name}:{it.lineno} {it.message}")
+    row(f"源码可编译 ({len(srcs)} 个文件, 无非法转义/语法错)", not bad, "; ".join(bad[:3]))
     for m in ("numpy", "pandas", "pyarrow", "polars", "yaml", "matplotlib", "plotly", "jinja2", "docx"):
         try: __import__(m); row(f"依赖 {m}", True)
         except Exception as ex: row(f"依赖 {m}", False, f"未安装: {ex.__class__.__name__} (运行 install 脚本)")

+ 2 - 2
src/windcms/serve.py

@@ -9,7 +9,7 @@ from . import plugins, orchestrator, llm, agent, ui
 JS_SHOW = """function tbl(d){if(!(d&&Array.isArray(d)&&d.length&&typeof d[0]=='object'))return '';const ks=Object.keys(d[0]);return '<table><thead><tr>'+ks.map(k=>'<th>'+k+'</th>').join('')+'</tr></thead><tbody>'+d.map(x=>'<tr>'+ks.map(k=>'<td>'+(x[k]==null?'':String(x[k]).slice(0,160))+'</td>').join('')+'</tr>').join('')+'</tbody></table>'}
 function esc(s){return String(s||'').replace(/</g,'&lt;')}
 function panelHtml(p){const meta='<details class=small><summary>'+esc(p.plugin)+' 详情</summary><div class=small>'+esc(JSON.stringify(p.args||{}))+'</div>'+tbl(p.data)+(p.text?'<pre>'+esc(p.text)+'</pre>':'')+'</details>';return '<div class="panel panel-big">'+(p.svg? p.svg+meta : ((p.text?'<pre>'+esc(p.text)+'</pre>':'')+tbl(p.data)))+'</div>'}
-function mdlite(s){s=esc(s);s=s.replace(/\*\*([^*]+)\*\*/g,"<b>$1</b>");s=s.replace(/^### (.*)$/gm,"<b>$1</b>");s=s.replace(/^[-•] (.*)$/gm,"<li>$1</li>");return "<p>"+s.replace(/\\n\\n+/g,"</p><p>").replace(/\\n/g,"<br>")+"</p>"}
+function mdlite(s){s=esc(s);s=s.replace(/\\*\\*([^*]+)\\*\\*/g,"<b>$1</b>");s=s.replace(/^### (.*)$/gm,"<b>$1</b>");s=s.replace(/^[-•] (.*)$/gm,"<li>$1</li>");return "<p>"+s.replace(/\\n\\n+/g,"</p><p>").replace(/\\n/g,"<br>")+"</p>"}
 function ansHtml(a){return "<div class=ans>"+mdlite(a)+"</div>"}
 function viewHtml(v){return '<div class=card><h3>'+esc(v.title)+'</h3><div class=grid>'+(v.panels||[]).map(panelHtml).join('')+'</div></div>'}"""
 
@@ -65,7 +65,7 @@ const cb=document.getElementById('c_bin');BINS.forEach(b=>{const o=document.crea
 function vals(id){return Array.from(document.getElementById(id).selectedOptions).map(o=>o.value)}
 let impTimer=null;
 async function doImport(){const p=document.getElementById('imp_path').value.trim();let w=document.getElementById('imp_win').value.trim();const st=document.getElementById('imp_st');
-if(p&&!w){const g=(p.split('/').pop().match(/\d+/g)||[]).filter(x=>x.length<4);
+if(p&&!w){const g=(p.split('/').pop().match(/\\d+/g)||[]).filter(x=>x.length<4);
  if(g.length>=2){w='w'+g[g.length-2].padStart(2,'0')+g[g.length-1].padStart(2,'0');document.getElementById('imp_win').value=w;}
  else{alert('请填窗名 (如 w0818) — 文件名里认不出日期');return}}
 if(!confirm('确认启动?\\n路径: '+(p||'(不导入, 在现有窗上重跑)')+'\\n窗名: '+(w||'—')+'\\n约 10-30 分钟后台任务, 会重写全部模型产物。')){return}