初始化红餐观察库项目
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract cross-article restaurant industry signals from cached Qwen analyses."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from app import DB_PATH, ensure_schema, now_iso
|
||||
|
||||
|
||||
def call_qwen(items: list[dict], model: str) -> list[dict]:
|
||||
key = os.getenv("DASHSCOPE_API_KEY", "").strip()
|
||||
if not key:
|
||||
raise RuntimeError("尚未配置 DASHSCOPE_API_KEY")
|
||||
prompt = f"""你是餐饮产业首席分析师。根据以下多篇文章的结构化分析,识别跨文章、可验证、有经营意义的行业信号。
|
||||
只返回合法 JSON:{{"signals":[...]}}。每个 signal 必须包含:title(短标题)、summary(100-180字)、trend(emerging/accelerating/stable/declining)、confidence(0到1)、article_ids(至少2个证据文章ID;确实只有单篇强信号时可为1个)、implications(2-4条经营启示)、tags(3-6个标签)。
|
||||
不要把单一品牌新闻简单改写成行业信号;合并重复主题;最多输出8条;没有充分证据就少输出。
|
||||
输入:{json.dumps(items, ensure_ascii=False)}"""
|
||||
body = json.dumps({
|
||||
"model": model,
|
||||
"messages": [
|
||||
{"role": "system", "content": "你进行基于证据的餐饮行业趋势聚类,避免空泛结论。"},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
"temperature": 0.2,
|
||||
"response_format": {"type": "json_object"},
|
||||
}, ensure_ascii=False).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
|
||||
data=body,
|
||||
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=120) as response:
|
||||
result = json.loads(response.read().decode("utf-8"))
|
||||
except urllib.error.HTTPError as exc:
|
||||
raise RuntimeError(f"千问接口返回 {exc.code}:{exc.read().decode('utf-8', errors='replace')[:500]}") from exc
|
||||
text = result["choices"][0]["message"]["content"].strip()
|
||||
if text.startswith("```"):
|
||||
text = text.strip("`").removeprefix("json").strip()
|
||||
return json.loads(text).get("signals", [])
|
||||
|
||||
|
||||
def run(days: int) -> int:
|
||||
ensure_schema()
|
||||
model = os.getenv("QWEN_MODEL", "qwen-plus")
|
||||
cutoff = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%S")
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT a.id,a.title,a.published_at,x.result_json
|
||||
FROM articles a JOIN ai_analyses x ON x.article_id=a.id
|
||||
WHERE a.crawl_status='success' AND a.published_at>=?
|
||||
ORDER BY a.published_at DESC
|
||||
""",
|
||||
(cutoff,),
|
||||
).fetchall()
|
||||
items = []
|
||||
for row in rows:
|
||||
analysis = json.loads(row["result_json"])
|
||||
items.append({
|
||||
"article_id": row["id"], "title": row["title"], "published_at": row["published_at"],
|
||||
"summary": analysis.get("summary"), "key_points": analysis.get("key_points", []),
|
||||
"industry_signal": analysis.get("industry_signal"), "tags": analysis.get("tags", []),
|
||||
})
|
||||
if not items:
|
||||
print(json.dumps({"status": "skipped", "reason": "没有已分析文章"}, ensure_ascii=False))
|
||||
return 0
|
||||
signals = call_qwen(items, model)
|
||||
signal_date = datetime.now().astimezone().date().isoformat()
|
||||
timestamp = now_iso()
|
||||
conn.execute("DELETE FROM industry_signals WHERE signal_date=?", (signal_date,))
|
||||
for signal in signals:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO industry_signals
|
||||
(signal_date,title,summary,trend,confidence,implications_json,article_ids_json,tags_json,model,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
||||
""",
|
||||
(
|
||||
signal_date, signal["title"], signal["summary"], signal.get("trend", "emerging"),
|
||||
float(signal.get("confidence", 0.5)), json.dumps(signal.get("implications", []), ensure_ascii=False),
|
||||
json.dumps(signal.get("article_ids", [])), json.dumps(signal.get("tags", []), ensure_ascii=False),
|
||||
model, timestamp, timestamp,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print(json.dumps({"status": "success", "input_articles": len(items), "signals": len(signals), "date": signal_date}, ensure_ascii=False))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="提炼红餐文章跨文行业信号")
|
||||
parser.add_argument("--days", type=int, default=30, help="聚合最近多少天的文章")
|
||||
args = parser.parse_args()
|
||||
raise SystemExit(run(args.days))
|
||||
|
||||
Reference in New Issue
Block a user