# 梅花易数 Skill 后端实现公开(报数起卦)
本篇公开「梅花」技能的后端实现与核心代码。口径是产品定的,代码只是把它落成可复算的形式。
一、整体链路
客户端技能(xjx-meihua-cn / -global,指导型)
↓
客户端核心内置工具 xjx_fetch_meihua
↓ (凭证由客户端核心代管)
Node 路由 POST /api/meihua/v1/chart
↓ 本机调用
Python 计算服务 127.0.0.1:8098 /api/meihua/chart
与西占、塔罗一致:技能只负责「怎么问、怎么解」,算法全在 Python 层;Node 层负责鉴权、扣费、缓存与幂等。
二、起卦口径(产品约定)
1. 用户自报两个数字:第一个为上卦数,第二个为下卦数,各自 ÷8 取余,余 0 作 8; 2. 不使用随机数、不使用时间起卦、不使用任何「直觉信息」; 3. 动爻 =(上数 + 下数 + 时辰数)÷6 取余,余 0 作 6; 4. 时辰数按时柱地支取:子 1、丑 2 …… 亥 12; 5. 体用:动爻所在的那一卦为用卦,另一卦为体卦; 6. 变卦按真正翻动爻重算(不是旧工作流里「卦数 +4」的近似写法)。
三、基础数据表
三爻自下而上编码,1 为阳、0 为阴:
YAO = {
"乾": (1, 1, 1), "兑": (1, 1, 0), "离": (1, 0, 1), "震": (1, 0, 0),
"巽": (0, 1, 1), "坎": (0, 1, 0), "艮": (0, 0, 1), "坤": (0, 0, 0),
}
YAO_NAME = {v: k for k, v in YAO.items()}
ELEM = {"乾": "金", "兑": "金", "离": "火", "震": "木",
"巽": "木", "坎": "水", "艮": "土", "坤": "土"}
BAGUA = {
1: {"name": "乾", "symbol": "☰", "element": "金"},
2: {"name": "兑", "symbol": "☱", "element": "金"},
3: {"name": "离", "symbol": "☲", "element": "火"},
4: {"name": "震", "symbol": "☳", "element": "木"},
5: {"name": "巽", "symbol": "☴", "element": "木"},
6: {"name": "坎", "symbol": "☵", "element": "水"},
7: {"name": "艮", "symbol": "☶", "element": "土"},
8: {"name": "坤", "symbol": "☷", "element": "土"},
}
六十四卦名表用「上卦、下卦」二元组索引,例如:
SIXTYFOUR = {
("乾", "乾"): "乾为天",
("坎", "离"): "水火既济",
("离", "坎"): "火水未济",
("坤", "乾"): "地天泰",
("乾", "坤"): "天地否",
...
}
四、取卦与动爻
def _gua(num):
n = num % 8 or 8 # 余 0 作 8
return {"number": n, "name": BAGUA[n]["name"],
"symbol": BAGUA[n]["symbol"], "element": BAGUA[n]["element"]}
动爻的「时辰数」不是直接读钟点,而是先排时柱再取地支,这样跨子时、换日等边界与命理口径一致:
ZHI_NUM = {"子": 1, "丑": 2, "寅": 3, "卯": 4, "辰": 5, "巳": 6,
"午": 7, "未": 8, "申": 9, "酉": 10, "戌": 11, "亥": 12}
def _hour_branch(clock):
lz = lunar.calc(clock.strftime("%Y-%m-%d %H:%M"))
if lz.get("error"):
raise ValueError(lz["error"])
gz = lz.get("time_gz") or (lz.get("ganzhi_date", "").split(",")[-1])
zhi = gz[-1] if gz else ""
if zhi not in ZHI_NUM:
raise ValueError("无法确定时辰")
return gz, zhi, ZHI_NUM[zhi], lz
用户可以填当前所在地,填了就先换算真太阳时再取时辰:
if place:
ts = true_solar.calc(now.strftime("%Y-%m-%d %H:%M"), place, country)
clock = datetime.strptime(ts["true_solar"], "%Y-%m-%d %H:%M")
五、本卦 / 互卦 / 变卦 / 错卦 / 综卦
六爻数组的顺序是「下卦三爻 + 上卦三爻」,索引 0 为初爻。
ben_yao = list(YAO[xia["name"]]) + list(YAO[shang["name"]])
ben_name = SIXTYFOUR[(shang["name"], xia["name"])]
# 互卦:二三四爻为下卦,三四五爻为上卦
hu = {"name": SIXTYFOUR[(_yao_to_gua(ben_yao[2:5]), _yao_to_gua(ben_yao[1:4]))]}
# 变卦:翻动爻(真正的爻变,不是卦数加四)
bian_yao = list(ben_yao)
bian_yao[dong - 1] = 1 - bian_yao[dong - 1]
bian_upper = _yao_to_gua(bian_yao[3:6])
bian_lower = _yao_to_gua(bian_yao[0:3])
bian_name = SIXTYFOUR[(bian_upper, bian_lower)]
# 错卦:六爻全变
cuo_name = SIXTYFOUR[(_yao_to_gua([1 - x for x in ben_yao[3:6]]),
_yao_to_gua([1 - x for x in ben_yao[0:3]]))]
# 综卦:六爻颠倒
zong_name = SIXTYFOUR[(_yao_to_gua(list(reversed(ben_yao[0:3]))),
_yao_to_gua(list(reversed(ben_yao[3:6]))))]
这里有一个容易写错的地方:错卦是「上下卦各自取反」,综卦是「上下卦各自倒序后再互换位置」。两者都不是简单地把整个数组反转一次。
六、体用关系
动爻在下卦(1–3 爻)则下卦为用、上卦为体;动爻在上卦(4–6 爻)则上卦为用、下卦为体。体用按五行生克判定:
REL = {
"金": {"生": "水", "克": "木", "被生": "土", "被克": "火"},
"木": {"生": "火", "克": "土", "被生": "水", "被克": "金"},
"水": {"生": "木", "克": "火", "被生": "金", "被克": "土"},
"火": {"生": "土", "克": "金", "被生": "木", "被克": "水"},
"土": {"生": "金", "克": "水", "被生": "火", "被克": "木"},
}
def _relation(ti, yong):
te, ye = ELEM[ti], ELEM[yong]
if te == ye:
return "体用比和"
if REL[te]["生"] == ye:
return "体生用"
if REL[te]["克"] == ye:
return "体克用"
if REL[te]["被生"] == ye:
return "用生体"
if REL[te]["被克"] == ye:
return "用克体"
return "关系不明"
变卦的体用同样算一遍:体卦不变,用卦换成变卦中对应的那一卦,得到「变卦体用」。
七、输出结构
data = {
"question": question,
"numbers": {"n1": n1, "n2": n2},
"time": {"clock": ..., "bazi": ..., "hour_gz": ..., "hour_zhi": ..., "hour_number": ...},
"upper": shang, "lower": xia,
"dong_yao": dong,
"ben": {"name": ben_name, "yao": "".join(str(x) for x in ben_yao)},
"hu": {"name": hu["name"]},
"bian": {"name": bian_name, "yao": "".join(str(x) for x in bian_yao)},
"cuo": {"name": cuo_name},
"zong": {"name": zong_name},
"ti": {"name": ti["name"], "element": ELEM[ti["name"]]},
"yong": {"name": yong["name"], "element": ELEM[yong["name"]]},
"ti_yong": _relation(ti["name"], yong["name"]),
"bian_ti_yong": _relation(bian_ti["name"], bian_yong["name"]),
}
同时返回一段可直接展示的 text,把数字、时辰、上下卦、本互变错综、体用逐行列清楚——先给事实,再让模型解读,避免解读层把卦看错。
八、API 层与计费
- 入口:
POST /api/meihua/v1/chart(登录态); - 每成功一卦 6 token,先算成功再扣费;
- 同参数(同两个数字、同时辰)120 分钟内重复请求走缓存,不再扣费;
- 同一
request_id24 小时内回放,不二次扣费; - 参数非法(数字非正整数、地点无法识别、时辰取不到)直接 400,不扣费。
九、这套实现的特点
1. 完全可复算:同样的两个数字 + 同一时辰,任何人跑一遍都得到同一卦。 2. 拒绝随机:起卦只用用户报的数,服务端不掺随机数。 3. 变卦是真正的翻爻:这一点与原始工作流的近似写法不同,已按正确口径改正。 4. 事实与解读分离:服务端只出卦象事实,解读交给模型,出了偏差能定位到是哪一层。