feat: complete heaven readings and screener publication

This commit is contained in:
leefer
2026-08-05 23:44:17 +08:00
parent 6a058c2929
commit b3a21d05b7
34 changed files with 3538 additions and 225 deletions
+51 -27
View File
@@ -6,11 +6,17 @@ from datetime import date
from typing import Any
from backend.bootstrap.config import normalize_date
from backend.features.heaven.agent import HeavenAgentError, interpret_heaven
from backend.features.heaven.agent import (
HEAVEN_PROMPT_VERSIONS,
HeavenAgentError,
interpret_heaven,
)
from backend.features.heaven.engine import (
build_five_phase_field,
hexagram_from_lines,
)
from backend.features.heaven.knowledge import prepare_heaven_context
from backend.features.heaven.six_yao import build_six_yao_chart
from backend.features.market import MarketServiceMixin
@@ -75,9 +81,11 @@ class HeavenReadingMixin:
return subject, detail
hexagram = context.get("hexagram") or {}
transformed = hexagram.get("transformed") or {}
question = str(context.get("question") or "").strip()
question_detail = f" · {question[:48]}" if question else ""
return (
f"{display_date} 观心",
f"{hexagram.get('name') or '--'}{transformed.get('name') or '--'}",
f"{hexagram.get('name') or '--'}{transformed.get('name') or '--'}{question_detail}",
)
def heaven_interpret(self, payload: dict[str, Any]) -> dict[str, Any]:
@@ -85,6 +93,8 @@ class HeavenReadingMixin:
if mode not in {"trend", "fortune", "heart"}:
raise ValueError("问天解读模式不正确。")
trade_date = normalize_date(str(payload.get("trade_date") or date.today().isoformat()))
prompt_version = HEAVEN_PROMPT_VERSIONS[mode]
stale_fortune: dict[str, Any] | None = None
if mode == "fortune":
existing = self.database.latest_heaven_reading(
self.current_user_id, "fortune", trade_date
@@ -94,7 +104,9 @@ class HeavenReadingMixin:
self.current_user_id, int(existing["id"])
)
existing = None
if existing:
if existing and self.database.heaven_reading_interpretation_version(
self.current_user_id, int(existing["id"])
) == prompt_version:
return {
"answer": existing["answer"],
"mode": mode,
@@ -103,6 +115,7 @@ class HeavenReadingMixin:
"reading": existing,
"reused": True,
}
stale_fortune = existing
if mode in {"trend", "fortune"}:
setup = self.heaven_setup(
trade_date,
@@ -141,38 +154,44 @@ class HeavenReadingMixin:
setup["field"],
public=False,
)
fortune_field = json.loads(json.dumps(setup["field"], ensure_ascii=False))
catalog = fortune_field.pop("sector_catalog", [])
dominant_elements = {
item.get("element") for item in fortune_field.get("balance", [])[:2]
}
fortune_field["industry_affinity"] = [
{
"element": group.get("element"),
"examples": [
item.get("name")
for item in group.get("industries", [])[:8]
if item.get("name")
],
}
for group in catalog
if group.get("element") in dominant_elements
]
context = {
"calendar_date": setup["calendar_date"],
"five_phase_field": fortune_field,
"five_phase_field": setup["field"],
"personal_profile": personal_profile,
}
context_date = setup["calendar_date"]
if mode == "trend":
context_date = setup["trade_date"]
else:
question = str(payload.get("question") or "").strip()
if len(question) > 300:
raise ValueError("观心问题不能超过300个字符。")
question_preset = str(payload.get("question_preset") or "unthemed").strip()
if question_preset not in {"trade", "mind", "unthemed", "custom"}:
question_preset = "custom"
if not question:
question = "不设具体问题,只观此刻一念。"
question_preset = "unthemed"
raw_lines = payload.get("lines")
hexagram = self.heaven_hexagram(raw_lines)
context = {
"hexagram": self.heaven_hexagram(payload.get("lines")),
"ritual": "用户已完成30秒静心、六次三枚铜钱起卦,并在心中察看第一念。问题未输入。",
"question": question,
"question_preset": question_preset,
"hexagram": hexagram,
"six_yao": build_six_yao_chart(
[int(value) for value in raw_lines],
str(payload.get("cast_at") or ""),
),
"ritual": {
"breathing": "用户已完成1秒准备与五轮吸3秒、顿2秒、呼4秒的静心呼吸。",
"casting": "用户以三枚铜钱自初爻至上爻投掷六次。",
"reflection": "用户已在看见卦象后察看第一念。",
},
}
context_date = trade_date
result, compiler = self._call_heaven_agent(mode, context)
agent_context = prepare_heaven_context(mode, context)
agent_context["interpretation_version"] = prompt_version
result, compiler = self._call_heaven_agent(mode, agent_context)
subject, subject_detail = self._heaven_reading_identity(
mode, context_date, context
)
@@ -181,6 +200,10 @@ class HeavenReadingMixin:
if mode == "fortune"
else f"{mode}:{context_date}:{secrets.token_urlsafe(12)}"
)
if stale_fortune:
self.database.delete_heaven_reading(
self.current_user_id, int(stale_fortune["id"])
)
reading = self.database.save_heaven_reading(
self.current_user_id,
mode,
@@ -188,14 +211,14 @@ class HeavenReadingMixin:
subject,
subject_detail,
str(result.get("answer") or ""),
context,
agent_context,
dedupe_key,
)
return {
**result,
"mode": mode,
"compiler": compiler,
"notice": "智能解读已自动切换用服务。" if compiler == "fallback" else "",
"notice": "当前智能服务繁忙,已自动切换用服务。" if compiler == "fallback" else "",
"reading": reading,
"reused": False,
}
@@ -205,9 +228,10 @@ class HeavenReadingMixin:
return bool(reading and str(reading.get("answer") or "").rstrip().endswith("……"))
def _call_heaven_agent(self, mode: str, context: dict[str, Any]) -> tuple[dict[str, Any], str]:
prompt_version = HEAVEN_PROMPT_VERSIONS[mode]
result = self.llm_gateway.call(
f"heaven_{mode}",
f"heaven-{mode}-v1",
prompt_version,
lambda profile: interpret_heaven(
mode,
context,