th commited on
Commit
badbcb4
·
1 Parent(s): a30fb56

fix: 晚子时排盘错误 + async事件循环阻塞 + 输入校验

Browse files

P0 晚子时bug: hour24ToTimeIndex把23点映射成0(早子时),iztro时辰索引
实为0~12(12=晚子时),导致23:00-23:59出生用户日柱错一天、整盘错误
(实测1990-06-15 23:30女: 错盘命宫破军 → 正确太阳)。改为23点返回12,
由iztro自行处理跨日。新增test_late_zi_hour.js回归测试。

P1: 四个排盘端点从async def改为def(FastAPI线程池),消除同步
subprocess.run阻塞uvicorn事件循环导致的全服务卡顿。

小项: 排盘subprocess调用抽成run_ziwei_calculation helper(消除×4重复);
gender用Literal['男','女']校验; target_year默认值改为动态当前年;
CORS allow_credentials关闭(与通配origins组合无效)。

🦊 Generated with Sisi

Files changed (3) hide show
  1. app.py +47 -108
  2. test_late_zi_hour.js +71 -0
  3. ziwei_service.js +5 -3
app.py CHANGED
@@ -8,7 +8,7 @@ Updated: 2026-02-03 (🌟 v4.0 巴纳姆效果+六亲分析+10步批命流程!
8
  from fastapi import FastAPI, HTTPException
9
  from fastapi.middleware.cors import CORSMiddleware
10
  from pydantic import BaseModel, Field
11
- from typing import Optional, Dict, Any
12
  import subprocess
13
  import json
14
  from datetime import datetime
@@ -29,7 +29,7 @@ app = FastAPI(
29
  app.add_middleware(
30
  CORSMiddleware,
31
  allow_origins=["*"],
32
- allow_credentials=True,
33
  allow_methods=["*"],
34
  allow_headers=["*"],
35
  )
@@ -39,11 +39,43 @@ class ZiweiRequest(BaseModel):
39
  """紫微斗数计算请求"""
40
  birth_date: str = Field(..., description="出生日期 YYYY-MM-DD", example="2000-01-01")
41
  birth_time: str = Field(..., description="出生时间 HH:MM", example="14:30")
42
- gender: str = Field(..., description="性别: 男/女", example="男")
43
  is_leap_month: bool = Field(default=False, description="是否闰月")
44
  fix_leap: bool = Field(default=True, description="是否修正闰月")
45
 
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  class ZiweiResponse(BaseModel):
48
  """紫微斗数计算响应"""
49
  status: str
@@ -155,7 +187,7 @@ def root():
155
 
156
 
157
  @app.post("/calculate-ziwei", response_model=ZiweiResponse)
158
- async def calculate_ziwei(request: ZiweiRequest):
159
  """
160
  计算紫微斗数命盘
161
 
@@ -163,32 +195,7 @@ async def calculate_ziwei(request: ZiweiRequest):
163
  完整的紫微斗数排盘与AI智能分析
164
  """
165
  try:
166
- # 将请求数据传递给Node.js服务
167
- input_data = {
168
- "birth_date": request.birth_date,
169
- "birth_time": request.birth_time,
170
- "gender": request.gender,
171
- "is_leap_month": request.is_leap_month,
172
- "fix_leap": request.fix_leap
173
- }
174
-
175
- # 调用Node.js脚本
176
- result = subprocess.run(
177
- ['node', 'ziwei_service.js'],
178
- input=json.dumps(input_data),
179
- capture_output=True,
180
- text=True,
181
- timeout=10
182
- )
183
-
184
- if result.returncode != 0:
185
- raise HTTPException(
186
- status_code=500,
187
- detail=f"Ziwei calculation error: {result.stderr}"
188
- )
189
-
190
- # 解析结果
191
- ziwei_data = json.loads(result.stdout)
192
 
193
  return ZiweiResponse(
194
  status="success",
@@ -204,7 +211,7 @@ async def calculate_ziwei(request: ZiweiRequest):
204
 
205
 
206
  @app.post("/analyze-ziwei-enhanced", response_model=ZiweiResponse)
207
- async def analyze_ziwei_enhanced(request: ZiweiRequest):
208
  """
209
  增强版紫微斗数分析 - 包含四化飞星和AI解读
210
 
@@ -213,31 +220,7 @@ async def analyze_ziwei_enhanced(request: ZiweiRequest):
213
  """
214
  try:
215
  # 1. 首先调用基础排盘功能
216
- input_data = {
217
- "birth_date": request.birth_date,
218
- "birth_time": request.birth_time,
219
- "gender": request.gender,
220
- "is_leap_month": request.is_leap_month,
221
- "fix_leap": request.fix_leap
222
- }
223
-
224
- # 调用Node.js脚本获取基础命盘
225
- result = subprocess.run(
226
- ['node', 'ziwei_service.js'],
227
- input=json.dumps(input_data),
228
- capture_output=True,
229
- text=True,
230
- timeout=10
231
- )
232
-
233
- if result.returncode != 0:
234
- raise HTTPException(
235
- status_code=500,
236
- detail=f"Ziwei calculation error: {result.stderr}"
237
- )
238
-
239
- # 解析基础命盘数据
240
- ziwei_chart = json.loads(result.stdout)
241
 
242
  # 2. 四化飞星分析
243
  sihua_analyzer = SihuaAnalyzer(ziwei_chart)
@@ -276,7 +259,7 @@ async def analyze_ziwei_enhanced(request: ZiweiRequest):
276
 
277
 
278
  @app.post("/analyze-ziwei-knowledge-enhanced", response_model=ZiweiResponse)
279
- async def analyze_ziwei_knowledge_enhanced(request: ZiweiRequest):
280
  """
281
  知识库增强版紫微斗数分析 v4.0 🔮✨💕
282
 
@@ -299,29 +282,7 @@ async def analyze_ziwei_knowledge_enhanced(request: ZiweiRequest):
299
  """
300
  try:
301
  # 1. 基础排盘
302
- input_data = {
303
- "birth_date": request.birth_date,
304
- "birth_time": request.birth_time,
305
- "gender": request.gender,
306
- "is_leap_month": request.is_leap_month,
307
- "fix_leap": request.fix_leap
308
- }
309
-
310
- result = subprocess.run(
311
- ['node', 'ziwei_service.js'],
312
- input=json.dumps(input_data),
313
- capture_output=True,
314
- text=True,
315
- timeout=10
316
- )
317
-
318
- if result.returncode != 0:
319
- raise HTTPException(
320
- status_code=500,
321
- detail=f"Ziwei calculation error: {result.stderr}"
322
- )
323
-
324
- ziwei_chart = json.loads(result.stdout)
325
 
326
  # 2. 四化飞星分析
327
  sihua_analyzer = SihuaAnalyzer(ziwei_chart)
@@ -400,14 +361,14 @@ async def analyze_ziwei_knowledge_enhanced(request: ZiweiRequest):
400
 
401
 
402
  @app.post("/analyze-ziwei-v4", response_model=ZiweiResponse)
403
- async def analyze_ziwei_v4(request: ZiweiRequest):
404
  """
405
  v4.0 巴纳姆温暖版 - 别名端点
406
 
407
  等同于 /analyze-ziwei-knowledge-enhanced (已升级到v4.0)
408
  保留此端点是为了向后兼容
409
  """
410
- return await analyze_ziwei_knowledge_enhanced(request)
411
 
412
 
413
  @app.get("/debug/modules")
@@ -459,14 +420,14 @@ class MonthlyFortuneRequest(BaseModel):
459
  """流月运势请求"""
460
  birth_date: str = Field(..., description="出生日期 YYYY-MM-DD", example="2000-01-01")
461
  birth_time: str = Field(..., description="出生时间 HH:MM", example="14:30")
462
- gender: str = Field(..., description="性别: 男/女", example="男")
463
  is_leap_month: bool = Field(default=False, description="是否闰月")
464
  fix_leap: bool = Field(default=True, description="是否修正闰月")
465
- target_year: int = Field(default=2026, description="目标年份", example=2026)
466
 
467
 
468
  @app.post("/analyze-monthly-fortune", response_model=ZiweiResponse)
469
- async def analyze_monthly_fortune(request: MonthlyFortuneRequest):
470
  """
471
  流月运势预测 v1.0 🌟✨ (NEW!)
472
 
@@ -482,29 +443,7 @@ async def analyze_monthly_fortune(request: MonthlyFortuneRequest):
482
  """
483
  try:
484
  # 1. 基础排盘
485
- input_data = {
486
- "birth_date": request.birth_date,
487
- "birth_time": request.birth_time,
488
- "gender": request.gender,
489
- "is_leap_month": request.is_leap_month,
490
- "fix_leap": request.fix_leap
491
- }
492
-
493
- result = subprocess.run(
494
- ['node', 'ziwei_service.js'],
495
- input=json.dumps(input_data),
496
- capture_output=True,
497
- text=True,
498
- timeout=10
499
- )
500
-
501
- if result.returncode != 0:
502
- raise HTTPException(
503
- status_code=500,
504
- detail=f"Ziwei calculation error: {result.stderr}"
505
- )
506
-
507
- ziwei_chart = json.loads(result.stdout)
508
 
509
  # 2. 流月分析
510
  liuyue_analyzer = LiuyueAnalyzer(ziwei_chart)
 
8
  from fastapi import FastAPI, HTTPException
9
  from fastapi.middleware.cors import CORSMiddleware
10
  from pydantic import BaseModel, Field
11
+ from typing import Literal, Optional, Dict, Any
12
  import subprocess
13
  import json
14
  from datetime import datetime
 
29
  app.add_middleware(
30
  CORSMiddleware,
31
  allow_origins=["*"],
32
+ allow_credentials=False,
33
  allow_methods=["*"],
34
  allow_headers=["*"],
35
  )
 
39
  """紫微斗数计算请求"""
40
  birth_date: str = Field(..., description="出生日期 YYYY-MM-DD", example="2000-01-01")
41
  birth_time: str = Field(..., description="出生时间 HH:MM", example="14:30")
42
+ gender: Literal['男', '女'] = Field(..., description="性别: 男/女", example="男")
43
  is_leap_month: bool = Field(default=False, description="是否闰月")
44
  fix_leap: bool = Field(default=True, description="是否修正闰月")
45
 
46
 
47
+ def run_ziwei_calculation(request: BaseModel) -> Dict[str, Any]:
48
+ """调用Node.js iztro服务进行基础排盘,返回命盘dict
49
+
50
+ 接受ZiweiRequest或MonthlyFortuneRequest(共享排盘字段)。
51
+ 统一处理subprocess调用、returncode检查和JSON解析,
52
+ 失败时直接抛出HTTPException。
53
+ """
54
+ input_data = {
55
+ "birth_date": request.birth_date,
56
+ "birth_time": request.birth_time,
57
+ "gender": request.gender,
58
+ "is_leap_month": request.is_leap_month,
59
+ "fix_leap": request.fix_leap
60
+ }
61
+
62
+ result = subprocess.run(
63
+ ['node', 'ziwei_service.js'],
64
+ input=json.dumps(input_data),
65
+ capture_output=True,
66
+ text=True,
67
+ timeout=10
68
+ )
69
+
70
+ if result.returncode != 0:
71
+ raise HTTPException(
72
+ status_code=500,
73
+ detail=f"Ziwei calculation error: {result.stderr}"
74
+ )
75
+
76
+ return json.loads(result.stdout)
77
+
78
+
79
  class ZiweiResponse(BaseModel):
80
  """紫微斗数计算响应"""
81
  status: str
 
187
 
188
 
189
  @app.post("/calculate-ziwei", response_model=ZiweiResponse)
190
+ def calculate_ziwei(request: ZiweiRequest):
191
  """
192
  计算紫微斗数命盘
193
 
 
195
  完整的紫微斗数排盘与AI智能分析
196
  """
197
  try:
198
+ ziwei_data = run_ziwei_calculation(request)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
  return ZiweiResponse(
201
  status="success",
 
211
 
212
 
213
  @app.post("/analyze-ziwei-enhanced", response_model=ZiweiResponse)
214
+ def analyze_ziwei_enhanced(request: ZiweiRequest):
215
  """
216
  增强版紫微斗数分析 - 包含四化飞星和AI解读
217
 
 
220
  """
221
  try:
222
  # 1. 首先调用基础排盘功能
223
+ ziwei_chart = run_ziwei_calculation(request)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
  # 2. 四化飞星分析
226
  sihua_analyzer = SihuaAnalyzer(ziwei_chart)
 
259
 
260
 
261
  @app.post("/analyze-ziwei-knowledge-enhanced", response_model=ZiweiResponse)
262
+ def analyze_ziwei_knowledge_enhanced(request: ZiweiRequest):
263
  """
264
  知识库增强版紫微斗数分析 v4.0 🔮✨💕
265
 
 
282
  """
283
  try:
284
  # 1. 基础排盘
285
+ ziwei_chart = run_ziwei_calculation(request)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
  # 2. 四化飞星分析
288
  sihua_analyzer = SihuaAnalyzer(ziwei_chart)
 
361
 
362
 
363
  @app.post("/analyze-ziwei-v4", response_model=ZiweiResponse)
364
+ def analyze_ziwei_v4(request: ZiweiRequest):
365
  """
366
  v4.0 巴纳姆温暖版 - 别名端点
367
 
368
  等同于 /analyze-ziwei-knowledge-enhanced (已升级到v4.0)
369
  保留此端点是为了向后兼容
370
  """
371
+ return analyze_ziwei_knowledge_enhanced(request)
372
 
373
 
374
  @app.get("/debug/modules")
 
420
  """流月运势请求"""
421
  birth_date: str = Field(..., description="出生日期 YYYY-MM-DD", example="2000-01-01")
422
  birth_time: str = Field(..., description="出生时间 HH:MM", example="14:30")
423
+ gender: Literal['男', '女'] = Field(..., description="性别: 男/女", example="男")
424
  is_leap_month: bool = Field(default=False, description="是否闰月")
425
  fix_leap: bool = Field(default=True, description="是否修正闰月")
426
+ target_year: int = Field(default_factory=lambda: datetime.now().year, description="目标年份(默认当前年)", example=2026)
427
 
428
 
429
  @app.post("/analyze-monthly-fortune", response_model=ZiweiResponse)
430
+ def analyze_monthly_fortune(request: MonthlyFortuneRequest):
431
  """
432
  流月运势预测 v1.0 🌟✨ (NEW!)
433
 
 
443
  """
444
  try:
445
  # 1. 基础排盘
446
+ ziwei_chart = run_ziwei_calculation(request)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
 
448
  # 2. 流月分析
449
  liuyue_analyzer = LiuyueAnalyzer(ziwei_chart)
test_late_zi_hour.js ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * 晚子时(23:00-23:59)排盘回归测试
3
+ *
4
+ * iztro 时辰索引为 0~12,其中 12 = 晚子时(23:00-00:00)。
5
+ * bug: hour24ToTimeIndex 曾把 23 点映射成 0(早子时),
6
+ * 导致 23:00-23:59 出生的用户日柱/命盘全部错误。
7
+ *
8
+ * 用例(1990-06-15 女):
9
+ * 23:30 出生 → 晚子时 index 12 → 日柱 壬子,命宫主星 太阳
10
+ * 00:30 出生 → 早子时 index 0 → 日柱 辛亥,命宫主星 破军
11
+ *
12
+ * 运行: node test_late_zi_hour.js
13
+ */
14
+
15
+ const { spawnSync } = require('child_process');
16
+
17
+ function runService(birthTime) {
18
+ const input = JSON.stringify({
19
+ birth_date: '1990-06-15',
20
+ birth_time: birthTime,
21
+ gender: '女',
22
+ is_leap_month: false,
23
+ fix_leap: true,
24
+ });
25
+ const result = spawnSync('node', ['ziwei_service.js'], {
26
+ input,
27
+ encoding: 'utf-8',
28
+ cwd: __dirname,
29
+ timeout: 15000,
30
+ });
31
+ if (result.status !== 0) {
32
+ throw new Error(`ziwei_service.js failed: ${result.stderr}`);
33
+ }
34
+ return JSON.parse(result.stdout);
35
+ }
36
+
37
+ function pick(data) {
38
+ const mingGong = data.palaces.find((p) => p.name === '命宫');
39
+ return {
40
+ day_pillar: data.chart.chinese_date.split(' ')[2],
41
+ ming_gong_stars: mingGong.major_stars.map((s) => s.name),
42
+ };
43
+ }
44
+
45
+ let failures = 0;
46
+
47
+ function check(label, actual, expected) {
48
+ const ok = JSON.stringify(actual) === JSON.stringify(expected);
49
+ console.log(`${ok ? '✅ PASS' : '❌ FAIL'} ${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
50
+ if (!ok) failures++;
51
+ }
52
+
53
+ // 用例1: 23:30 出生 → 晚子时(index 12),日柱应为次日壬子,命宫主星太阳
54
+ const lateZi = pick(runService('23:30'));
55
+ check('23:30 日柱(晚子时)', lateZi.day_pillar, '壬子');
56
+ check('23:30 命宫主星', lateZi.ming_gong_stars, ['太阳']);
57
+
58
+ // 用例2: 00:30 出生 → 早子时(index 0),日柱辛亥,命宫主星破军(保持不变)
59
+ const earlyZi = pick(runService('00:30'));
60
+ check('00:30 日柱(早子时)', earlyZi.day_pillar, '辛亥');
61
+ check('00:30 命宫主星', earlyZi.ming_gong_stars, ['破军']);
62
+
63
+ // 用例3: 非子时时辰不受影响 — 14:30 未时
64
+ const weiShi = runService('14:30');
65
+ check('14:30 时辰索引(未时)', weiShi.chart.earthly_branch_index, 7);
66
+
67
+ if (failures > 0) {
68
+ console.error(`\n${failures} test(s) failed`);
69
+ process.exit(1);
70
+ }
71
+ console.log('\nAll tests passed 🦊');
ziwei_service.js CHANGED
@@ -7,13 +7,15 @@ const iztro = require('iztro');
7
  const astro = iztro.astro;
8
 
9
  /**
10
- * 将24小时制转换为12时辰索引 (0-11)
11
- * 子(0): 23-01, 丑(1): 01-03, 寅(2): 03-05, 卯(3): 05-07
12
  * 辰(4): 07-09, 巳(5): 09-11, 午(6): 11-13, 未(7): 13-15
13
  * 申(8): 15-17, 酉(9): 17-19, 戌(10): 19-21, 亥(11): 21-23
 
14
  */
15
  function hour24ToTimeIndex(hour24) {
16
- if (hour24 >= 23 || hour24 < 1) return 0; // 子时
 
17
  if (hour24 >= 1 && hour24 < 3) return 1; // 丑时
18
  if (hour24 >= 3 && hour24 < 5) return 2; // 寅时
19
  if (hour24 >= 5 && hour24 < 7) return 3; // 卯时
 
7
  const astro = iztro.astro;
8
 
9
  /**
10
+ * 将24小时制转换为iztro时辰索引 (0-12)
11
+ * 早子(0): 00-01, 丑(1): 01-03, 寅(2): 03-05, 卯(3): 05-07
12
  * 辰(4): 07-09, 巳(5): 09-11, 午(6): 11-13, 未(7): 13-15
13
  * 申(8): 15-17, 酉(9): 17-19, 戌(10): 19-21, 亥(11): 21-23
14
+ * 晚子(12): 23-00 — iztro用12区分晚子时并自行处理跨日日柱
15
  */
16
  function hour24ToTimeIndex(hour24) {
17
+ if (hour24 >= 23) return 12; // 晚子时
18
+ if (hour24 < 1) return 0; // 早子时
19
  if (hour24 >= 1 && hour24 < 3) return 1; // 丑时
20
  if (hour24 >= 3 && hour24 < 5) return 2; // 寅时
21
  if (hour24 >= 5 && hour24 < 7) return 3; // 卯时