# -*- coding: utf-8 -*-
"""原创离线合成样例；不连接模型、不读取账户。输出位于脚本旁。
已有输入保留，修改输入后可以重跑。失败边界由 --negative 单独演示。
"""
from pathlib import Path
import sys, json, csv, math, io, hashlib, html, base64, re
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
BASE = Path(__file__).resolve().parent
NEG = '--negative' in sys.argv
def write(name, value):
    p=BASE/name; p.parent.mkdir(parents=True,exist_ok=True)
    if isinstance(value,bytes): p.write_bytes(value)
    else: p.write_text(value,encoding='utf-8',newline='')
def seed(name,value):
    if not (BASE/name).exists(): write(name,value)
def jwrite(name,value): write(name,json.dumps(value,ensure_ascii=False,indent=2))
def jseed(name,value):
    if not (BASE/name).exists(): jwrite(name,value)
def load(name): return json.loads((BASE/name).read_text(encoding='utf-8'))
def table(name,heads,rows):
    f=io.StringIO(newline='');w=csv.writer(f);w.writerow(heads);w.writerows(rows);write(name,f.getvalue())
def page(name,title,body):
    write(name,'<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>'+html.escape(title)+'</title><style>body{max-width:1000px;margin:2rem auto;padding:0 1rem;font:17px/1.7 system-ui}table{border-collapse:collapse}td,th{border:1px solid #aaa;padding:.5rem}pre{white-space:pre-wrap;overflow-wrap:anywhere}img,svg{max-width:100%;height:auto}a{color:#125caa}</style><h1>'+html.escape(title)+'</h1>'+body+'</html>')
def emit(data): print(json.dumps(data,ensure_ascii=False))

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
corpus=[{'id':'d1','text':'订单 ZX204 的交付日期是周三'},{'id':'d2','text':'退货需要保存订单编号'},{'id':'d3','text':'账户登录使用验证码'}]
jseed('corpus.json',corpus);jseed('queries.json',[{'query':'ZX204 交付','expected':'d1'},{'query':'注销身份','expected':None}]);corpus=load('corpus.json')
v=TfidfVectorizer(analyzer='char',ngram_range=(2,3));matrix=v.fit_transform([r['text'] for r in corpus]);terms=v.get_feature_names_out();out=[]
for test in ([{'query':'','expected':None}] if NEG else load('queries.json')):
    q=v.transform([test['query']]);scores=cosine_similarity(q,matrix)[0];ranking=sorted([(corpus[i]['id'],float(s)) for i,s in enumerate(scores) if s>0],key=lambda r:(-r[1],r[0]))[:3];contributions=[]
    if ranking:
        i=next(i for i,r in enumerate(corpus) if r['id']==ranking[0][0]);prod=q.multiply(matrix[i]).tocoo();contributions=sorted([(str(terms[j]),float(value)) for j,value in zip(prod.col,prod.data)],key=lambda r:-r[1])[:8]
    out.append({'query':test['query'],'ranking':ranking,'common_fragments':contributions,'status':'matched' if ranking else 'no_literal_overlap'})
jwrite('results.json',out);page('results.html','字符 TF-IDF 基线','<pre>'+html.escape(json.dumps(out,ensure_ascii=False,indent=2))+'</pre>');emit(out)
