diff --git a/BLOCKED.md b/BLOCKED.md index 718c743f..4bf10b16 100644 --- a/BLOCKED.md +++ b/BLOCKED.md @@ -3,7 +3,9 @@ ## #239 · 多轮选盘页面 - ~~缺产品确认的五页画布与逐字文案,v2 续问规则待定。~~ 评论 43045 已补齐原稿并确认规则。续问的接口与引擎已实现。 -- 五页原稿还依赖引擎提供仅供展示的非关键换盘线、代表时刻的六盘上升、未定盘对应的换座时刻及逐件事件匹配结果。现有接口只返回关键线、未定盘编号和匹配总数。页面展示字段超出本轮续问范围,待确认由哪一单提供。 +- ~~五页原稿还缺非关键线、六盘上升、未定盘换座和逐件事件匹配。~~ 评论 43159 授权本单补齐。截图待产品负责人走查。 +- Docker Desktop 的 Linux 引擎返回 HTTP 500。使用现有隔离 PostgreSQL 18.6 验证选盘历史和秒级读回。前端全量在相同的无 Docker 配置下与 staging 比较。 +- Python 快速门三项 Kaala / PyJHora 测试与同机 staging 的失败一致,完整绿灯待独立验收环境复核。 - 报告秒级区间已在隔离 PostgreSQL 验证。详见 [#239 进度](docs/tasks/PROGRESS-chart-choice-rounds-page-20261113.md)。 ## #243 · Windows 全量门禁 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b91250e..857d1784 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # 印度占星 Skill 更新日志 -## 2026-11-13 — 个人报告按秒使用多轮选盘结果 +## 2026-11-13 — 生时纠正逐处确认换盘线 -- 报告用已采纳的候选时间段和代表秒值计算误差。Skill 版本沿用当前版本(BUG-1873)。 +- 每次最多确认三处,可提前结束,也可从当前候选段继续。结果显示六盘上升、未定的换座时刻和事件匹配。个人报告按候选段与代表秒值计算误差。Skill 版本沿用当前版本(BUG-1873)。 ## 2026-11-13 — 个人报告用盘面事实补齐解读章节 diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index fcbf7667..89c665c4 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -25258,15 +25258,15 @@ ## BUG-1873 | 选盘页面未接入多轮,报告未读取秒级采纳段 -- 状态:blocked(报告与续问已有验证,页面缺展示数据)。 +- 状态:fixed-pending-verify(待独立验收)。 - 首次发现 / 最近更新:2026-11-13 / 2026-11-13。 - 影响面:多轮选盘页面、历史恢复与个人报告。 - 现象及触发:选择或复用 v2 历史后,页面仍按单轮处理,报告只认 v1 区间,并把保存时刻截到分钟。 - 根因:#238 与 #241 已落地引擎和 API 合同。报告请求和历史匹配仍使用原来的分钟级表示。现有恢复入口只接受 v1 历史。 - 进展:报告用 v2 的最终候选段和保存秒值,保留未定分盘。保留 v1 原逻辑。复用已采纳的记录时检查日期和准确秒值。 - 续问:从当前采纳记录的秒级区间再问最多三轮。已确认轮次与已记事件继续保留。服务核对档案的当前记录、日期、秒和段端点,资料改动或区间不一致时拒绝恢复。 -- 页面阻塞:原稿已到。现有响应缺非关键换盘线、六盘上升与逐件匹配数据,未定盘也缺逐盘换座时刻。页面待上游提供展示字段。 -- 验证与防复发:合成回归覆盖跨午夜、段外回退、错秒历史、续问三轮上限和历史事件保留。真实 PostgreSQL 覆盖报告秒级读回、现行记录续问和档案区间错配拒绝。见 [#239 进度](tasks/PROGRESS-chart-choice-rounds-page-20261113.md)。 +- 页面:范围页画出非关键线和关键线顺序。逐轮使用事件题和可跳过的补充题,最多三处。结果按代表时刻列六盘上升、逐盘未定换座和逐件事件匹配。支持提前结束以及从当前记录续问。 +- 验证与防复发:真实引擎与接口测试覆盖展示字段和代表时刻的逐件匹配。Chromium 在手机宽度复走三轮、提前结束和续问。隔离 PostgreSQL 覆盖报告秒级读回、现行记录续问和档案区间错配拒绝。见 [#239 进度](tasks/PROGRESS-chart-choice-rounds-page-20261113.md)。 - 关联记录:#227、#229、#238、#239、#241、BUG-1865、BUG-1872。 - 修复版本:`codex/chart-choice-rounds-page-20261113`,未发布。 diff --git a/docs/tasks/PROGRESS-chart-choice-rounds-page-20261113.md b/docs/tasks/PROGRESS-chart-choice-rounds-page-20261113.md index 3b7f14b9..8581fcd0 100644 --- a/docs/tasks/PROGRESS-chart-choice-rounds-page-20261113.md +++ b/docs/tasks/PROGRESS-chart-choice-rounds-page-20261113.md @@ -20,9 +20,29 @@ - TypeScript 和生产构建通过,首页仍为静态。首屏引用的 JS 515860 B、CSS 90906 B(gzip),与 staging 一致。ESLint 0 error,62 条既有 warning。 - Windows 目录和定向预检超时。首轮快速门因同目录构建替换 .next 文件失败,已在独立 worktree 复跑。前端全量使用同配置与 staging 对照。 -## 页面需拍板 +## 页面需拍板(修订二时) - 范围页:`ordered_lines` 只列关键线,无法标注原稿要求的仅有 D4、D12、D24 换座的浅色线。 - 首页与结果页:结果只有 `undetermined_charts` 和剩余条数,缺按盘对应的剩余换座时刻,以及代表时刻的六张盘上升星座。单轮对比数据只覆盖当轮换座的分盘。 - 结果依据:`evidence_summary` 只返回总数、匹配数和把握等级,无法区分每件事是否对上。真实引擎回放已核对上述三处响应形状。 - 建议保持原稿,由引擎返回现有扫描和打分的展示字段。修订二限定引擎改动在续问,页面及截图待上游投影或修订任务书后交付。 + +## 修订三 + +- 评论 43159 授权本单补齐三类展示字段。沿用原分支的提交并同步 staging `aea610939`。原工作树另有五个未提交的外部对拍文件,本轮从远端任务书提交建立独立工作树。 +- `minor_lines` 仅投影段内只翻 D4、D12、D24 的扫描线。`key_charts` 用同段代表时刻算六盘上升,已定盘沿用相关轮次的 `decided_by`,未定盘显示当前段内最早换座。`events_detail` 逐件取保存代表时刻的 `score_minute`,与匹配总数一致。原区间排名、分级、选线、判边和保存时刻算法沿用。 +- 页面以 `resolved` 请求 v2 包,从范围进入每轮。事件日期和类别随当轮提交,已选内容在返回改答案时保留。提前结束提交 `stop: true`,已保存的历史按当前段续问。结果和采纳都沿用服务端给出的秒级段。 +- 画布采用接口真实事件题。结果页的「再补几件事」返回当轮改答案。历史仍使用同一 JSON 记录列,数据库结构沿用。 + +| 既有断言 | 原值 | 新值与原因 | +| --- | --- | --- | +| `chart-choice-astrologer-20261107.test.ts` | 选盘页有三个独立核对入口,并渲染 `AstrologerStage` | 三个入口收进统一流程,页面渲染 `RoundStep`。按修订一的合并方案执行。独立组件和 API 的原合同断言保留。 | + +## 修订三验证 + +- Python 选盘定向 37 项通过。对本地真实 Python HTTP 路由发送虚构数据,`packet` 和 `decide` 返回 200,三个字段与实际引擎夹具一致。 +- 前端最终定向 42 项通过,涵盖页面、接口转发、持久化和既有报告秒级段。隔离 PostgreSQL 18.6 的两个用例通过,覆盖旧记录复用、秒级读回和错段拒绝。 +- TypeScript、生产构建通过。ESLint 0 错误,62 条既有警告。首页仍为静态,首屏 gzip JS 515860 B,CSS 90895 → 90952 B,均在基线 ±2% 内。 +- 同配置无 Docker 前端全量 3718 项,3635 通过、46 失败、37 跳过、0 取消。staging 为 3712 项,3630 通过、45 失败、37 跳过、0 取消。新增 6 项,既有用例无缺失。45 项失败在本次基线复现;咨询计时用例曾在前一 staging 全量出现同一断言失败,本次定向复跑通过。 +- 390 px Chromium 用真实引擎的虚构资料回放实际组件,核对三轮请求、提前结束、最终采纳和续问 ID,截图附 PR,待产品负责人走查。预检远端、Python、适配器通过,目录镜像路径的一项既有断言失败。 +- 隔离 Python 快速门退出 1,1479 项通过、2 项跳过、29 个子测试通过。三项 Kaala / PyJHora 失败与同机 staging 记录逐项一致。 diff --git a/docs/tasks/README.md b/docs/tasks/README.md index ed0714f4..eda45596 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -33,7 +33,7 @@ | 任务书 | 进度 | 主题 | 状态 | 落点 | | --- | --- | --- | --- | --- | -| [#239 修订任务书](FIX-chart-choice-rounds-page-2.md) | [执行进度](PROGRESS-chart-choice-rounds-page-20261113.md) | 多轮选盘页面与报告秒级区间(BUG-1873) | 需拍板 | `codex/chart-choice-rounds-page-20261113` | +| [#239 修订任务书](FIX-chart-choice-rounds-page-3.md) | [执行进度](PROGRESS-chart-choice-rounds-page-20261113.md) | 多轮选盘页面与报告秒级区间(BUG-1873) | 待验收 | `codex/chart-choice-rounds-page-20261113` | | [#243 任务书](TASK-reading-computed-fallback-20261113.md) | [执行进度](PROGRESS-reading-computed-fallback-20261113.md) | 个人报告解读计算兜底(BUG-1877) | 待验收 | `codex/reading-computed-fallback-20261113` | | [#242 任务书](TASK-reading-rebind-repair-20261113.md) | [执行进度](PROGRESS-reading-rebind-repair-20261113.md) | 报告解读重绑与逐节重写(BUG-1876) | 待验收 | `codex/reading-rebind-repair-20261113` | | [#241 修订任务书](FIX-chart-choice-questions-1.md) | [执行进度](PROGRESS-chart-choice-questions-20261113.md) | 多轮选盘事件题与判边(BUG-1875) | 待验收 | `codex/chart-choice-questions-20261113` | diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md index 1fe4b1ff..d1e3f58c 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -350,6 +350,8 @@ The self-profile editor in 星盘资料 (`ChartLibraryPanel`) owns the ayanamsa ### Rectification surface +- **多轮选盘(#239,BUG-1873):** 时间尺以编号深线显示关键换座,住处、父母和学业盘以浅线标出。逐轮复用对比卡与选项,事件日期就地填写,性格对比折叠。结果保留候选段和代表秒值,逐盘列上升、已定依据或未定换座,逐件列事件匹配。首页保留 v1 历史行,并按当前段扫描结果显示续问入口。 + The birth-time rectification session is the consultation transcript plus a house board; questions live inside the assistant message that asked them. It shares every waiting vocabulary with the consultation surface; nothing here spins or says "loading" after the reveal. - **Reveal:** the surface mounts once per session/Case binding. Opening a Case (homepage card, sidebar row, deep link or refresh) reads the turns and the snapshot in one Case request before the switch, under the same 4-second budget as the home reveal (`RECTIFICATION_OPEN_HYDRATE_TIMEOUT_MS` is `BOOTSTRAP_PREPARE_TIMEOUT_MS`); a session selected at bootstrap is hydrated during the prepare phase. The entry shows a static note meanwhile (card footer “正在打开…”, sidebar row “打开中”, `cursor: progress`) and the previous view stays put. Turns and snapshot are initial state; anything that arrives later is a prop or state update, never a remount. A late or failed read still reveals, with the composer notice “校正记录没有完全加载,可以继续”. diff --git a/frontend/src/app/chart-choice.css b/frontend/src/app/chart-choice.css index e030800f..fc1b4e61 100644 --- a/frontend/src/app/chart-choice.css +++ b/frontend/src/app/chart-choice.css @@ -125,6 +125,28 @@ height: 34px; background: var(--color-action-hover); } +.chart-choice-ruler-line.is-minor { + top: 25px; + height: 20px; + background: var(--color-canvas-strong); +} +.chart-choice-ruler-line > .chart-choice-tag { + position: absolute; + top: -18px; + left: 50%; + transform: translateX(-50%); + padding-inline: 5px; +} +.chart-choice-input { + display: block; + width: 100%; + min-height: 44px; + border: 1px solid var(--color-canvas-strong); + border-radius: 10px; + padding-inline: 10px; + background: var(--color-canvas); + color: var(--color-ink); +} .chart-choice-ruler-line-label, .chart-choice-ruler-reported { position: absolute; @@ -571,7 +593,7 @@ } .chart-choice-ruler-saved-label { position: absolute; - top: 50px; + top: 4px; transform: translateX(-50%); color: var(--color-action); font-size: var(--type-caption); @@ -579,12 +601,6 @@ font-weight: 600; white-space: nowrap; } -.chart-choice-ruler-reported.is-secondary { - top: 50px; - transform: none; - color: var(--color-ink-secondary); -} - @media (max-width: 640px) { .chart-choice-page { padding: var(--space-4) var(--space-4) var(--space-8); } .chart-choice-compare { gap: var(--space-3); } diff --git a/frontend/src/components/chart-choice/chart-choice-page.tsx b/frontend/src/components/chart-choice/chart-choice-page.tsx index b9282a80..88458055 100644 --- a/frontend/src/components/chart-choice/chart-choice-page.tsx +++ b/frontend/src/components/chart-choice/chart-choice-page.tsx @@ -1,18 +1,26 @@ "use client"; -import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"; -import { ChevronLeft } from "lucide-react"; +import { Fragment, useCallback, useEffect, useRef, useState, type ReactNode } from "react"; +import { ChevronLeft, ChevronRight } from "lucide-react"; import { AppLink } from "@/components/app-link"; import { ChartDrawLoader } from "@/components/chart-draw-loader"; -import { AstrologerStage } from "@/components/chart-choice/astrologer-stage"; import { MiniVargaChart } from "@/components/chart-choice/mini-varga-chart"; import { SecondaryPageShell } from "@/components/secondary-page-shell"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { RECORD_RANGE_CHOICES, type ChartChoiceAnswer, + type ChartChoiceAdoptRequest, + type ChartChoiceDatedEvent, type ChartChoiceDecision, + type ChartChoiceCompletion, + type ChartChoiceEnginePacket, + type ChartChoiceEvidenceInput, + type ChartChoiceKeyChart, + type ChartChoiceRequest, type ChartChoiceHistory, type ChartChoicePacket, type ChartChoiceRecord, @@ -24,11 +32,13 @@ import { SIDE_TEXT, clockLabel, distanceText, + eventSides, minutesFrom, otherSide, rangeLabel, recordSummary, savedAtLabel, + segmentLabel, windowAround, } from "@/lib/chart-choice/view"; import { useCurrentSubject, useCurrentSubjectReady } from "@/lib/current-subject"; @@ -37,11 +47,16 @@ import { cn } from "@/lib/utils"; type Choose = Extract; type Ready = Extract; +type Complete = Extract; +type FinalDecision = (ChartChoiceDecision & { segment: ChartChoiceWindow }) | ChartChoiceCompletion; +type RoundAnswers = NonNullable; +type RoundDraft = { answers: Record; evidence: ChartChoiceEvidenceInput; optionIds: Record }; +type ResultRequest = ChartChoiceAdoptRequest & { mode: "preview" | "choice" }; /** What 重试 re-runs; data, not a closure, so the callbacks never refer to themselves. */ type Retry = | { kind: "start" } - | { kind: "packet"; window: ChartChoiceWindow | undefined } + | { kind: "packet"; request: ChartChoiceRequest; showRange: boolean; previous?: Step } | { kind: "back"; step: Step }; type Step = @@ -53,10 +68,10 @@ type Step = | { kind: "packet_loading" } | { kind: "narrow"; packet: Extract } | { kind: "certain"; packet: Extract } - | { kind: "choose"; packet: Choose } + | { kind: "range"; packet: Choose | Complete; request: ChartChoiceRequest } + | { kind: "choose"; packet: Choose; request: ChartChoiceRequest; previous?: Step; draft?: RoundDraft } | { kind: "result"; packet: Choose; decision: ChartChoiceDecision; saving: boolean; overrideSide?: ChartChoiceSide } - // 占星师的方法(产品 2026-11-07):在选盘之外按她的 skill 再核对,结果并排给出。 - | { kind: "astrologer"; chartChoiceTime: string | null; back: Step; window?: ChartChoiceWindow }; + | { kind: "final"; packet: Choose | Complete; decision: FinalDecision; body: ResultRequest; back: Step }; const COPY = { title: "生时纠正", @@ -116,29 +131,41 @@ async function readStart(subjectId: string): Promise({ kind: "loading" }); const [start, setStart] = useState(null); const [window, setWindow] = useState(undefined); const [answers, setAnswers] = useState>({}); + const [evidence, setEvidence] = useState({}); + const [optionIds, setOptionIds] = useState>({}); + const [pending, setPending] = useState(false); + const [resume, setResume] = useState<{ id: string; packet: ChartChoiceEnginePacket } | null>(null); // No range in the profile: ±15 min by default, adjustable in one tap; a tapped range is saved back (BUG-1649). const [rangeChosen, setRangeChosen] = useState(false); const generation = useRef(0); - const loadPacket = useCallback(async (nextWindow: ChartChoiceWindow | undefined) => { + const loadPacket = useCallback(async (request: ChartChoiceRequest, showRange = true, previous?: Step) => { const ticket = ++generation.current; - setWindow(nextWindow); + setWindow(request.window); setAnswers({}); + setEvidence({}); + setOptionIds({}); setStep({ kind: "packet_loading" }); try { - const packet = await postJson("/api/chart-choice/packet", { subjectId, ...(nextWindow ? { window: nextWindow } : {}) }); + const packet = await postJson("/api/chart-choice/packet", { subjectId, resolved: [], ...request }); if (ticket !== generation.current) return; if (packet.status === "needs_narrowing") setStep({ kind: "narrow", packet }); + else if (packet.engine_version === "chart-choice-v2" && showRange) setStep({ kind: "range", packet, request }); + else if (packet.status === "complete" || (packet.status === "certain" && packet.decision)) { + const decision = packet.decision; + if (decision) setStep({ kind: "final", packet, decision, body: { subjectId, resolved: [], ...request, mode: "choice" }, + back: previous ?? { kind: "range", packet, request } }); + } else if (packet.status === "certain") setStep({ kind: "certain", packet }); - else setStep({ kind: "choose", packet }); + else setStep({ kind: "choose", packet, request, previous }); } catch (error) { if (ticket !== generation.current) return; - setStep({ kind: "failed", message: (error as Error).message, retry: { kind: "packet", window: nextWindow } }); + setStep({ kind: "failed", message: (error as Error).message, retry: { kind: "packet", request, showRange, previous } }); } }, [subjectId]); @@ -150,7 +177,7 @@ function ChartChoiceFlow({ subjectId }: { readonly subjectId: string }) { } setStart(next.ready); if (next.ready.history.records.length > 0) setStep({ kind: "home" }); - else void loadPacket(undefined); + else void loadPacket({}); }, [loadPacket]); const reloadStart = useCallback(() => { @@ -170,9 +197,18 @@ function ChartChoiceFlow({ subjectId }: { readonly subjectId: string }) { }; }, [applyStart, subjectId]); + const current = start?.history.records.find((record) => record.id === start.history.current_record_id && record.reusable); + useEffect(() => { + if (!current || (!current.detail.decision.segment && !current.detail.line)) return; + let live = true; + void postJson("/api/chart-choice/packet", { subjectId, resolved: [], resumeRecordId: current.id }) + .then((packet) => { if (live) setResume({ id: current.id, packet }); }, () => { if (live) setResume(null); }); + return () => { live = false; }; + }, [current, subjectId]); + const startOver = useCallback(() => { setRangeChosen(false); - void loadPacket(undefined); + void loadPacket({}); }, [loadPacket]); const afterSave = useCallback((time: string) => { @@ -192,7 +228,7 @@ function ChartChoiceFlow({ subjectId }: { readonly subjectId: string }) { afterSave(clockLabel(decision.saved_at, packet.reported_at)); } } catch (error) { - setStep({ kind: "failed", message: (error as Error).message, retry: { kind: "back", step: { kind: "choose", packet } } }); + setStep({ kind: "failed", message: (error as Error).message, retry: { kind: "back", step: { kind: "choose", packet, request: {} } } }); } }, [afterSave, answers, rangeChosen, step, subjectId, window]); @@ -216,7 +252,7 @@ function ChartChoiceFlow({ subjectId }: { readonly subjectId: string }) { const pickRange = useCallback((reported: string, minutes: number) => { setRangeChosen(true); - void loadPacket(windowAround(reported, minutes)); + void loadPacket({ window: windowAround(reported, minutes) }); }, [loadPacket]); const history: ChartChoiceHistory = start?.history ?? { current_record_id: null, records: [] }; @@ -232,8 +268,51 @@ function ChartChoiceFlow({ subjectId }: { readonly subjectId: string }) { if (target.kind === "start") { setStep({ kind: "loading" }); reloadStart(); - } else if (target.kind === "packet") void loadPacket(target.window); - else setStep(target.step); + } else if (target.kind === "packet") void loadPacket(target.request, target.showRange, target.previous); + else restore(target.step); + } + + function restore(previous: Step) { + if (previous.kind === "choose") { + setAnswers(previous.draft?.answers ?? {}); + setEvidence(previous.draft?.evidence ?? {}); + setOptionIds(previous.draft?.optionIds ?? {}); + } + setStep(previous); + } + + async function advance(round: Extract, stop = false) { + const body: ResultRequest = { + subjectId, resolved: [], ...round.request, mode: "preview", + ...(rangeChosen ? { rangeChosen: true } : {}), + ...(stop ? { stop: true } : { answers, ...evidence }), + }; + setPending(true); + const back = { ...round, draft: { answers, evidence, optionIds } }; + try { + const { decision } = await postJson<{ decision: FinalDecision }>("/api/chart-choice/adopt", body); + if (decision.has_next && decision.resolved) { + await loadPacket({ ...round.request, resolved: decision.resolved }, false, back); + } else setStep({ kind: "final", packet: round.packet, decision, body, back }); + } catch (error) { + setStep({ kind: "failed", message: (error as Error).message, retry: { kind: "back", step: back } }); + } finally { + setPending(false); + } + } + + async function saveFinal(final: Extract) { + setPending(true); + try { + const { decision } = await postJson<{ decision: FinalDecision }>("/api/chart-choice/adopt", { + ...final.body, mode: "choice", ...(rangeChosen ? { rangeChosen: true } : {}), + }); + afterSave(clockLabel(decision.saved_at, final.packet.reported_at, !decision.saved_at.endsWith(":00") && decision.saved_at.length === 19)); + } catch (error) { + setStep({ kind: "failed", message: (error as Error).message, retry: { kind: "back", step: final } }); + } finally { + setPending(false); + } } function renderStep(): ReactNode { @@ -265,12 +344,12 @@ function ChartChoiceFlow({ subjectId }: { readonly subjectId: string }) { history={history} onStartOver={startOver} onOpen={(record) => setStep({ kind: "record", record })} - onAstrologer={(record) => setStep({ - kind: "astrologer", - chartChoiceTime: record?.saved_time ?? null, - back: { kind: "home" }, - ...(record ? { window: record.detail.window } : {}), - })} + remaining={resume?.id === current?.id ? resume?.packet.remaining_key_lines ?? 0 : 0} + resumedCharts={resume?.id === current?.id ? resume?.packet.key_charts : undefined} + onResume={current ? () => { + setRangeChosen(false); + void loadPacket({ resumeRecordId: current.id }, false, { kind: "home" }); + } : undefined} /> ); case "record": @@ -295,7 +374,7 @@ function ChartChoiceFlow({ subjectId }: { readonly subjectId: string }) { type="button" variant="outline" className="chart-choice-range-choice" - onClick={() => void loadPacket({ start_at: option.start_at, end_at: option.end_at })} + onClick={() => void loadPacket({ window: { start_at: option.start_at, end_at: option.end_at } })} > {option.text} @@ -310,12 +389,25 @@ function ChartChoiceFlow({ subjectId }: { readonly subjectId: string }) { {rangeChips(step.packet)}

{rangeLabel(step.packet.window)} 里没有分界线,盘都一样

- setStep({ kind: "astrologer", chartChoiceTime: clockLabel(step.packet.saved_at), back: step })}>用占星师的方法核对 void saveCertain(step.packet)}>用 {clockLabel(step.packet.saved_at)}
); + case "range": + return { + if (step.packet.status === "choose") setStep({ kind: "choose", packet: step.packet, request: step.request, previous: step }); + else if (step.packet.decision) setStep({ kind: "final", packet: step.packet, decision: step.packet.decision, + body: { subjectId, resolved: [], ...step.request, mode: "choice" }, back: step }); + }} />; case "choose": + if (step.packet.engine_version === "chart-choice-v2") return ( + setOptionIds((prev) => ({ ...prev, [key]: id }))} pending={pending} + onAnswer={(domain, value) => setAnswers((prev) => ({ ...prev, [domain]: value }))} + onBack={step.previous ? () => restore(step.previous!) : hasHistory ? toHome : null} + onNext={() => void advance(step)} onStop={() => void advance(step, true)} /> + ); return ( void runDecision(step.packet, "choice", step.overrideSide ? { overrideSide: step.overrideSide } : {})} onFlip={() => void runDecision(step.packet, "preview", { overrideSide: otherSide(step.decision.chosen_side) })} - onBack={() => setStep({ kind: "choose", packet: step.packet })} - onAstrologer={() => setStep({ kind: "astrologer", chartChoiceTime: clockLabel(step.decision.saved_at, step.packet.reported_at), back: step })} + onBack={() => setStep({ kind: "choose", packet: step.packet, request: {} })} /> ); - case "astrologer": - return setStep(step.back)} />; + case "final": + return restore(step.back)} onAccept={() => void saveFinal(step)} />; } } } @@ -387,6 +479,8 @@ function TimeRuler({ savedAt, sides, reportedSide, + ordered, + minor, }: { readonly window: ChartChoiceWindow; readonly lineAt: string | null; @@ -394,9 +488,11 @@ function TimeRuler({ readonly savedAt?: string; readonly sides?: Choose["sides"]; readonly reportedSide?: ChartChoiceSide; + readonly ordered?: Choose["ordered_lines"]; + readonly minor?: Choose["minor_lines"]; }) { const start = minutesFrom(window.start_at); - const span = Math.max(1, minutesFrom(window.end_at) - start); + const span = Math.max(1 / 60, minutesFrom(window.end_at) - start); const at = (value: string) => `${Math.min(100, Math.max(0, ((minutesFrom(value) - start) / span) * 100))}%`; const width = (from: string, to: string) => `${Math.max(0, ((minutesFrom(to) - minutesFrom(from)) / span) * 100)}%`; const highlighted: ChartChoiceSide | undefined = savedAt && lineAt @@ -418,21 +514,260 @@ function TimeRuler({ 分界 {clockLabel(lineAt, reportedAt, true)} ) : null} + {minor?.map((line) => )} + {ordered?.map((line, index) => ( + + {index + 1} + + ))} {savedAt ? ( <>
- 存 {clockLabel(savedAt, reportedAt)} + 存 {clockLabel(savedAt, reportedAt, savedAt.length === 19)} ) : null} {clockLabel(window.start_at, reportedAt)} {clockLabel(window.end_at, reportedAt)} - {savedAt === reportedAt ? null : ( - ▲ 填 {clockLabel(reportedAt)} + {savedAt || minutesFrom(reportedAt) <= start || minutesFrom(reportedAt) >= minutesFrom(window.end_at) ? null : ( + ▲ 填 {clockLabel(reportedAt)} )}
); } +const CHART_NAMES: Readonly> = { + D1: "主盘", D9: "婚恋", D10: "事业", D2: "钱财", D6: "日常", D8: "突变", +}; +const EVENT_LABELS: Readonly> = { + relationship: "开始恋爱", marriage: "结婚", divorce: "分开", career: "入职或升职", + job_change: "换工作", business: "创业", finance_pos: "收入明显增加", finance_neg: "较大支出或损失", travel: "搬家 / 出国 / 去外地", +}; + +function KeyCharts({ charts, undetermined, reference }: { + readonly charts: readonly ChartChoiceKeyChart[] | undefined; + readonly undetermined: readonly string[] | undefined; + readonly reference: string; +}) { + const rows = charts ?? Object.keys(CHART_NAMES).map((chart): ChartChoiceKeyChart => ({ chart, sign: "", decided_by: null })); + return
+ {rows.map((row) => { + const uncertain = Boolean(row.flips_at || undetermined?.includes(row.chart)); + const source = row.decided_by === "event" ? "(靠事件定)" + : row.decided_by === "supplement" || row.decided_by === "appearance" ? "(靠补充题定)" : ""; + return +
{CHART_NAMES[row.chart]}
+
{row.sign ? `${row.sign}上升 · ` : ""}{uncertain ? "还没定" : "已定"} + {row.flips_at ? `(${clockLabel(row.flips_at, reference, true)} 换)` : source} +
+
; + })} +
; +} + +export function RangeStep({ packet, rangeChips, onBack, onStart }: { + readonly packet: Choose | Complete; + readonly rangeChips: ReactNode; + readonly onBack: (() => void) | null; + readonly onStart: () => void; +}) { + const ordered = packet.ordered_lines ?? []; + return <> + + {onBack ? 返回 : null} +

你填的 {clockLabel(packet.reported_at)} 前后,有 {ordered.length} 处会换盘

+ {rangeChips} + +

深色竖线是关键盘换座;浅色是住处、父母、学业盘,不在这次确认里。

+
+ {ordered.length ? +

按这个顺序问

+
    + {ordered.map((line, index) =>
  1. + {index + 1}. {line.charts.filter((key) => CHART_NAMES[key]).map((key) => CHART_NAMES[key]).join("、")}盘换座 + {clockLabel(line.at, packet.reported_at, true)} +
  2. )} +
+

顺序按占星师的做法:先主盘上升,再婚恋、事业,最后钱财和其他。

+
: null} +
{ordered.length ? "开始确认" : "看结果"}
+ {ordered.length ?

最多问 3 处,每处 1–3 个问题,随时可以停

: null} + ; +} + +function EventChips({ events }: { readonly events: readonly (ChartChoiceDatedEvent & { readonly matched?: boolean })[] }) { + return
+ {events.map((event, index) => + {event.date} · {EVENT_LABELS[event.category] ?? event.category} + {event.matched === undefined ? "" : event.matched ? " · 对上" : " · 没对上"} + )} +
; +} + +export function RoundStep({ packet, answers, evidence, onEvidence, optionIds = {}, onOption, onAnswer, pending, onBack, onNext, onStop }: { + readonly packet: Choose; + readonly answers: Readonly>; + readonly evidence: ChartChoiceEvidenceInput; + readonly onEvidence: (value: ChartChoiceEvidenceInput) => void; + readonly optionIds?: Readonly>; + readonly onOption?: (key: string, id: string) => void; + readonly onAnswer: (domain: string, value: ChartChoiceAnswer) => void; + readonly pending: boolean; + readonly onBack: (() => void) | null; + readonly onNext: () => void; + readonly onStop: () => void; +}) { + const round = packet.round ?? 1; + const total = Math.min(packet.max_rounds ?? 3, round - 1 + (packet.remaining_key_lines ?? 1)); + const eventAnswers: RoundAnswers = evidence.event_answers ?? {}; + const marks = evidence.birthmarks ?? []; + const events = [...(packet.events ?? []), ...Object.values(eventAnswers).flatMap((answer) => + Object.values(answer.dates ?? {}).filter((event) => event.date && event.category))]; + const eventReady = Object.values(eventAnswers).every((answer) => eventSides(answer.choice).every((side) => { + const event = answer.dates?.[side]; + return Boolean(event?.category && /^\d{4}(?:-(?:0[1-9]|1[0-2]))?$/.test(event.date)); + })); + const comparison = packet.comparisons.find((row) => CHART_NAMES[row.chart]); + const chartName = comparison && CHART_NAMES[comparison.chart]; + + return <> + + {onBack && !pending ? 返回 : null} +

第 {round} 处,共 {total} 处

+

{clockLabel(packet.line.at, packet.reported_at, true)} 前后,{chartName}盘换星座

+ + {comparison ?
+ {(["before", "after"] as const).map((side) => { + const reported = packet.reported_known && !packet.reported_neutral && packet.reported_side === side; + return
+
{SIDE_TEXT[side]}{reported ? " · 你填的" : ""}
{comparison[side].ascendant}上升
+
; + })} +
: null} +
+ {packet.appearance_question ? +

哪个更接近你给人的第一印象?

+
+ {packet.appearance_question.options.map((option) => )} +
+
: null} + {packet.birthmark_question ? +

身上有比较明显的胎记或伤疤吗?在哪里?

+

可多选,没有就跳过

+
+ {packet.birthmark_question.options.map((option) => )} +
+
: null} + {packet.event_questions?.map((question) => { + const answer = eventAnswers[question.id]; + const update = (next: RoundAnswers[string]) => onEvidence({ ...evidence, event_answers: { ...eventAnswers, [question.id]: next } }); + return +

{question.prompt}

+
+ {question.options.map((option) => )} +
+ {answer ? eventSides(answer.choice).map((side) => { + const event = answer.dates?.[side] ?? { date: "", category: "" }; + const key = `${question.id}:${side}`; + const change = (next: ChartChoiceDatedEvent) => update({ ...answer, dates: { ...answer.dates, [side]: next } }); + return
+ {SIDE_TEXT[side]} + +
是哪种 + +
+
; + }) : null} +
; + })} + {packet.comparisons.length ?
+ 补充:两边的{chartName}盘读起来是什么样(可跳过) + {packet.comparisons.map((item) => q.domain === item.before.domain) ?? null} + answer={answers[item.before.domain]} onAnswer={onAnswer} />)} +
: null} + {events.length ?

你提到的事 · {events.length} 件

: null} +
+ 先到这里 + {round < total ? "接着确认下一处" : "看结果"} +
+ ; +} + +const CONFIDENCE = { tendency_only: "倾向", high: "高", medium: "中", low: "低", none: "暂时分不出" }; + +export function FinalStep({ packet, decision, pending, onBack, onAccept }: { + readonly packet: Choose | Complete; + readonly decision: FinalDecision; + readonly pending: boolean; + readonly onBack: () => void; + readonly onAccept: () => void; +}) { + const summary = decision.evidence_summary; + const seconds = decision.saved_at.length === 19 && !decision.saved_at.endsWith(":00"); + const saved = clockLabel(decision.saved_at, packet.reported_at, seconds); + return <> + + {!pending ? 改答案 : null} +

确认了 {decision.rounds?.length ?? 0} 处

+

候选时间段

+

{segmentLabel(decision.segment)}

+

代表时刻 {saved} + {minutesFrom(decision.saved_at) === minutesFrom(packet.reported_at) ? " · 就是你填的时间" : ""}

+ +

这是按你的回答选出的一段,不是确认过的出生分钟。 + {decision.undetermined_charts?.length ? "" : "真实时间在这段里任何一处,下面六张盘都一样。"}

+
+ +

六张盘

+ +
+ {summary ? +

依据

+

你提到 {summary.event_count} 件有日期的事,{summary.matched} 件和这一段的大运对得上。

+ +

{summary.event_count < 3 ? "倾向" : `占星师的把握分级:${CONFIDENCE[summary.confidence]}。`}

+ {packet.status === "choose" && !pending ? : null} +
: null} +
+ 这个方法有多准 +

我们在 77 个出生时间有记录的公开案例上回测过:30 分钟范围里,按事件打分选对的占 19%,随机挑占 20%,直接用填写的时间占 34%。所以这里的结果是你根据自己经历做的选择,不是验证结果。

+
+
用这个时间段继续
+

星盘、报告、聊天都按这一段读;你填的出生时间不改

+ ; +} + /** 按 X 读 / 两版都读 / 不受影响 for a decision. */ function ReadingPlan({ chosen, @@ -457,18 +792,23 @@ function ReadingPlan({ ); } -function HomeView({ +export function HomeView({ history, onStartOver, onOpen, - onAstrologer, + remaining, + onResume, + resumedCharts, }: { readonly history: ChartChoiceHistory; readonly onStartOver: () => void; readonly onOpen: (record: ChartChoiceRecord) => void; - readonly onAstrologer: (record: ChartChoiceRecord | null) => void; + readonly remaining: number; + readonly onResume?: () => void; + readonly resumedCharts?: readonly ChartChoiceKeyChart[]; }) { const current = history.records.find((record) => record.id === history.current_record_id) ?? null; + const decision = current?.detail.decision; return ( <> {current ? ( @@ -477,24 +817,33 @@ function HomeView({

当前使用

{savedAtLabel(current.created_at)} 保存

- - +

{segmentLabel(decision.segment)}

+

代表时刻 {clockLabel(decision.saved_at, current.detail.reported_at, decision.saved_at.length === 19)} · 你填的是 {clockLabel(current.detail.reported_at)}

+ + : <> + + + /> + /> + } + {remaining > 0 && onResume ?
+

还有 {remaining} 处没确认

+ 继续 +
: null}
看星盘 重新纠正
-
onAstrologer(current)}>用占星师的方法核对
) : ( @@ -509,10 +858,12 @@ function HomeView({ {history.records.map((record) => ( ))} @@ -711,7 +1062,6 @@ function ResultStep({ onAccept, onFlip, onBack, - onAstrologer, }: { readonly packet: Choose; readonly decision: ChartChoiceDecision; @@ -720,7 +1070,6 @@ function ResultStep({ readonly onAccept: () => void; readonly onFlip: () => void; readonly onBack: () => void; - readonly onAstrologer: () => void; }) { const chosen = decision.chosen_side; const other = otherSide(chosen); @@ -758,9 +1107,6 @@ function ResultStep({ {flipText} {saving ? "保存中…" : "保存"} -
- 用占星师的方法核对 -
); } diff --git a/frontend/src/lib/chart-choice/contract.ts b/frontend/src/lib/chart-choice/contract.ts index 275462f1..71450292 100644 --- a/frontend/src/lib/chart-choice/contract.ts +++ b/frontend/src/lib/chart-choice/contract.ts @@ -44,6 +44,14 @@ export type ChartChoiceResolved = { readonly override_side?: ChartChoiceSide; } & ChartChoiceEvidenceInput; export type ChartChoiceLine = { readonly at: string; readonly degree: number; readonly charts: readonly string[] }; +export type ChartChoiceMinorLine = Pick; +export type ChartChoiceKeyChart = { + readonly chart: string; + readonly sign: string; + readonly decided_by: NonNullable | null; + readonly flips_at?: string; +}; +export type ChartChoiceEventDetail = ChartChoiceDatedEvent & { readonly matched: boolean }; export type ChartChoiceRound = { readonly line_at: string; readonly charts: readonly string[]; @@ -57,6 +65,8 @@ export type ChartChoiceRoundResult = { readonly undetermined_charts: readonly string[]; readonly events: readonly ChartChoiceDatedEvent[]; readonly evidence_summary?: ChartChoiceEvidenceSummary; + readonly events_detail?: readonly ChartChoiceEventDetail[]; + readonly key_charts?: readonly ChartChoiceKeyChart[]; readonly remaining_key_lines: number; readonly has_next: boolean; readonly resolved: readonly ChartChoiceResolved[]; @@ -97,7 +107,7 @@ export type ChartChoiceQuestion = { }; type ChartChoiceOption = { readonly value: T; readonly text: string }; -type ChartChoiceEventQuestion = { +export type ChartChoiceEventQuestion = { readonly id: string; readonly chart: string; readonly before_ages: readonly [number, number]; @@ -119,6 +129,8 @@ type PacketBase = { readonly max_rounds?: number; readonly remaining_key_lines?: number; readonly ordered_lines?: readonly ChartChoiceLine[]; + readonly minor_lines?: readonly ChartChoiceMinorLine[]; + readonly key_charts?: readonly ChartChoiceKeyChart[]; readonly segment?: ChartChoiceWindow; readonly resolved?: readonly ChartChoiceResolved[]; readonly events?: readonly ChartChoiceDatedEvent[]; diff --git a/frontend/src/lib/chart-choice/server.ts b/frontend/src/lib/chart-choice/server.ts index de312255..740d8dc3 100644 --- a/frontend/src/lib/chart-choice/server.ts +++ b/frontend/src/lib/chart-choice/server.ts @@ -348,6 +348,8 @@ function roundFields(decision: Partial): Partial at.length === 19 && !at.endsWith(":00")); + return `${clockLabel(segment.start_at, reference, seconds)}–${clockLabel(segment.end_at, reference, seconds)}`; +} + +export function eventSides(choice: ChartChoiceEventChoice): readonly ChartChoiceSide[] { + if (choice === "both") return ["before", "after"]; + return choice === "before" || choice === "after" ? [choice] : []; +} + /** Symmetric range around a time given without one (minutes either side). */ export function windowAround(reportedAt: string, minutes: number): ChartChoiceWindow { const center = Date.parse(`${reportedAt}:00Z`); diff --git a/frontend/tests/chart-choice-astrologer-20261107.test.ts b/frontend/tests/chart-choice-astrologer-20261107.test.ts index a2673a46..a7745a47 100644 --- a/frontend/tests/chart-choice-astrologer-20261107.test.ts +++ b/frontend/tests/chart-choice-astrologer-20261107.test.ts @@ -58,8 +58,8 @@ test("offset and side-by-side text never call either result verified", () => { test("the page offers the stage from the result, the no-line case and the current record", () => { const page = readFileSync(new URL("../src/components/chart-choice/chart-choice-page.tsx", import.meta.url), "utf8"); - assert.equal(page.match(/用占星师的方法核对/g)?.length, 3); - assert.match(page, /; +type Complete = Extract; +const fixture = JSON.parse(readFileSync(new URL("./fixtures/chart-choice-rounds-display-20261113.json", import.meta.url), "utf8")) as { + packets: Choice[]; + final: Complete; +}; +const noop = () => {}; + +test("real chart choice packets render minor lines, three rounds and the final evidence", () => { + const packet = fixture.packets[0]; + assert.ok(packet.minor_lines?.length); + const range = renderToStaticMarkup(); + assert.equal((range.match(/is-minor/g) ?? []).length, packet.minor_lines.length); + assert.match(range, /按这个顺序问/); + assert.match(range, /开始确认/); + for (const [index, round] of fixture.packets.entries()) { + const html = renderToStaticMarkup(); + assert.match(html, new RegExp(`第 ${index + 1} 处,共 3 处`)); + assert.match(html, /先到这里/); + assert.match(html, index < 2 ? /接着确认下一处/ : /看结果/); + } + assert.deepEqual(eventSides("both"), ["before", "after"]); + const final = renderToStaticMarkup(); + assert.match(final, /六张盘/); + assert.match(final, /用这个时间段继续/); + assert.match(final, /19%.*20%.*34%/); + assert.equal((final.match(/上升 · /g) ?? []).length, 6); + assert.equal((final.match(/ · 对上/g) ?? []).length, fixture.final.decision.evidence_summary?.matched); +}); + +test("the current record shows a continuation only when the scan has remaining lines", () => { + const { decision, window, reported_at } = fixture.final; + const record: ChartChoiceRecord = { + id: "test-record", created_at: "2026-11-13T03:00:00Z", saved_date: decision.saved_at.slice(0, 10), + saved_time: decision.saved_at.slice(11), chosen_side: "none", rule: decision.rule, reusable: true, + detail: { window, reported_at, line: null, other_lines: decision.remaining_key_lines, questions: [], answers: {}, + unchanged: [], decision: { ...decision, chosen_side: "none", votes: null, both_versions_domains: [] }, charts: [] }, + }; + const history = { current_record_id: record.id, records: [record] }; + const props = { history, onOpen: noop, onStartOver: noop, onResume: noop }; + assert.match(renderToStaticMarkup(), /还有 2 处没确认/); + assert.doesNotMatch(renderToStaticMarkup(), /处没确认/); +}); + +test("the chart choice API service passes through the three display fields and saves final details", async () => { + register(new URL("./support/stub-server-only.mjs", import.meta.url).href, import.meta.url); + const { adoptChartChoice, buildChartChoicePacket, previewChartChoice } = await import("../src/lib/chart-choice/server"); + const profile = { + name: "虚构甲", birth_date: "1997-08-08", reported_birth_time: "05:00", birth_time_source: "approximate", + birth_time_status: "reported", declared_window_start: "04:30", declared_window_end: "05:30", + uncertainty_before_minutes: 30, uncertainty_after_minutes: 30, birth_time_uncertainty_known: true, + latitude: 36.42, longitude: 114.21, timezone_offset: 8, timezone_id: "Asia/Shanghai", birth_place_label: "虚构市", + }; + const caller = { from: () => ({ select: () => ({ eq: () => ({ maybeSingle: async () => ({ data: profile, error: null }) }) }) }) }; + const fetchEngine: typeof fetch = async (url, options) => { + const body = JSON.parse(String(options?.body)); + assert.deepEqual(body.resolved, []); + if (String(url).endsWith("/packet")) return Response.json(fixture.packets[0]); + assert.ok(String(url).endsWith("/decide")); + assert.equal(body.stop, true); + return Response.json(fixture.final); + }; + const packet = await buildChartChoicePacket(caller, "fixture", { resolved: [] }, fetchEngine); + assert.deepEqual(packet.minor_lines, fixture.packets[0].minor_lines); + assert.deepEqual(packet.key_charts, fixture.packets[0].key_charts); + const request = { resolved: [], stop: true }; + const preview = await previewChartChoice(caller, "fixture", request, fetchEngine); + assert.deepEqual(preview.decision.events_detail, fixture.final.decision.events_detail); + let saved: Partial | undefined; + const admin = { rpc: async (name: string, args: Record) => { + assert.equal(name, "chart_choice_adopt_v4"); + saved = (args.p_detail as { decision: ChartChoiceCompletion }).decision; + return { data: {}, error: null }; + } }; + await adoptChartChoice(caller, admin, "fixture", request, fetchEngine); + assert.deepEqual(saved?.events_detail, fixture.final.decision.events_detail); + assert.deepEqual(saved?.key_charts, fixture.final.decision.key_charts); +}); diff --git a/frontend/tests/fixtures/chart-choice-rounds-display-20261113.json b/frontend/tests/fixtures/chart-choice-rounds-display-20261113.json new file mode 100644 index 00000000..40e40228 --- /dev/null +++ b/frontend/tests/fixtures/chart-choice-rounds-display-20261113.json @@ -0,0 +1,2115 @@ +{ + "body": { + "lat": 36.42, + "lon": 114.21, + "tz": 8, + "start_at": "1997-08-08T04:30", + "end_at": "1997-08-08T05:30", + "reported_at": "1997-08-08T05:00" + }, + "packets": [ + { + "engine_version": "chart-choice-v2", + "window": { + "start_at": "1997-08-08T04:30", + "end_at": "1997-08-08T05:30" + }, + "reported_at": "1997-08-08T05:00", + "reported_known": true, + "ayanamsa": "raman", + "node_mode": "mean", + "round": 1, + "max_rounds": 3, + "remaining_key_lines": 11, + "ordered_lines": [ + { + "at": "1997-08-08T05:07:55", + "degree": 16.666667, + "charts": [ + "D9" + ] + }, + { + "at": "1997-08-08T04:51:30", + "degree": 13.333333, + "charts": [ + "D9" + ] + }, + { + "at": "1997-08-08T05:24:22", + "degree": 20.0, + "charts": [ + "D9", + "D6", + "D12", + "D24" + ] + }, + { + "at": "1997-08-08T04:35:08", + "degree": 10.0, + "charts": [ + "D9", + "D6", + "D12", + "D24" + ] + }, + { + "at": "1997-08-08T04:59:43", + "degree": 15.0, + "charts": [ + "D10", + "D2", + "D6", + "D8", + "D4", + "D12", + "D24" + ] + }, + { + "at": "1997-08-08T05:14:30", + "degree": 18.0, + "charts": [ + "D10" + ] + }, + { + "at": "1997-08-08T04:44:57", + "degree": 12.0, + "charts": [ + "D10" + ] + }, + { + "at": "1997-08-08T05:29:18", + "degree": 21.0, + "charts": [ + "D10" + ] + }, + { + "at": "1997-08-08T04:30:14", + "degree": 9.0, + "charts": [ + "D10" + ] + }, + { + "at": "1997-08-08T05:18:12", + "degree": 18.75, + "charts": [ + "D8", + "D24" + ] + }, + { + "at": "1997-08-08T04:41:16", + "degree": 11.25, + "charts": [ + "D8", + "D24" + ] + } + ], + "minor_lines": [ + { + "at": "1997-08-08T04:47:24", + "charts": [ + "D12", + "D24" + ] + }, + { + "at": "1997-08-08T04:53:33", + "charts": [ + "D24" + ] + }, + { + "at": "1997-08-08T05:05:52", + "charts": [ + "D24" + ] + }, + { + "at": "1997-08-08T05:12:02", + "charts": [ + "D12", + "D24" + ] + } + ], + "key_charts": [ + { + "chart": "D1", + "sign": "巨蟹", + "decided_by": null + }, + { + "chart": "D9", + "sign": "天蝎", + "decided_by": null, + "flips_at": "1997-08-08T04:35:08" + }, + { + "chart": "D10", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:30:14" + }, + { + "chart": "D2", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + }, + { + "chart": "D6", + "sign": "摩羯", + "decided_by": null, + "flips_at": "1997-08-08T04:35:08" + }, + { + "chart": "D8", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:41:16" + } + ], + "segment": { + "start_at": "1997-08-08T04:30:00", + "end_at": "1997-08-08T05:30:00" + }, + "resolved": [], + "events": [], + "status": "choose", + "line": { + "at": "1997-08-08T05:07:55", + "degree": 16.666667, + "charts": [ + "D9" + ] + }, + "other_lines_in_window": 10, + "reported_side": "before", + "reported_neutral": true, + "seconds_from_line": 475, + "sides": { + "before": { + "from": "1997-08-08T05:05:52", + "to": "1997-08-08T05:07:54", + "saved_at": "1997-08-08T05:06:53" + }, + "after": { + "from": "1997-08-08T05:07:55", + "to": "1997-08-08T05:12:01", + "saved_at": "1997-08-08T05:09" + } + }, + "comparisons": [ + { + "chart": "D9", + "label": "婚恋", + "chart_label": "婚恋盘 D9", + "before": { + "chart": "D9", + "domain": "relationship", + "label": "婚恋", + "chart_label": "婚恋盘 D9", + "ascendant": "天蝎", + "ascendant_index": 7, + "points": [ + { + "kind": "occupant", + "fact": "火星在命位", + "meaning": "竞争和技术,行动快、敢拼" + }, + { + "kind": "occupant", + "fact": "金星在命位", + "meaning": "审美、设计和人缘,讲究舒适" + }, + { + "kind": "lord", + "fact": "管伴侣的星(金星)落在第 1 宫", + "meaning": "感情里很看重自我,伴侣和自己性格相近" + }, + { + "kind": "lord", + "fact": "代表你自己的星(火星)落在第 1 宫", + "meaning": "感情里很看重自我,伴侣和自己性格相近" + } + ] + }, + "after": { + "chart": "D9", + "domain": "relationship", + "label": "婚恋", + "chart_label": "婚恋盘 D9", + "ascendant": "射手", + "ascendant_index": 8, + "points": [ + { + "kind": "occupant", + "fact": "计都在伴侣的位置", + "meaning": "专精、抽离,不太在乎外界认可" + }, + { + "kind": "occupant", + "fact": "罗睺在命位", + "meaning": "野心大、不走寻常路,和外来或新兴的东西有缘" + }, + { + "kind": "lord", + "fact": "管伴侣的星(水星)落在第 10 宫", + "meaning": "伴侣和事业、社会地位相关" + }, + { + "kind": "lord", + "fact": "代表你自己的星(木星)落在第 9 宫", + "meaning": "伴侣可能来自远方,或带着学识、信念上的影响" + } + ] + } + } + ], + "unchanged": [ + { + "chart": "D1", + "label": "整体性格" + }, + { + "chart": "D10", + "label": "事业" + }, + { + "chart": "D2", + "label": "钱财" + }, + { + "chart": "D6", + "label": "日常遇到压力时" + }, + { + "chart": "D8", + "label": "遇到意外变化时" + }, + { + "chart": "D4", + "label": "住处" + }, + { + "chart": "D12", + "label": "父母" + }, + { + "chart": "D24", + "label": "学业" + } + ], + "questions": [ + { + "domain": "relationship", + "chart": "D9", + "label": "婚恋", + "prompt": "你的婚恋,更像哪一种?", + "options": [ + { + "value": "before", + "text": "感情里很看重自我,伴侣和自己性格相近;感情里很看重自我,伴侣和自己性格相近" + }, + { + "value": "after", + "text": "伴侣和事业、社会地位相关;伴侣可能来自远方,或带着学识、信念上的影响" + }, + { + "value": "unsure", + "text": "说不清" + } + ], + "optional": true + } + ], + "event_questions": [ + { + "id": "D9", + "chart": "D9", + "before_ages": [ + 23, + 23 + ], + "after_ages": [ + 15, + 15 + ], + "prompt": "感情上的变化,线前候选对应 23 岁,线后候选对应 15 岁。你在哪一段有过这些变化?比如开始恋爱、结婚或分开。", + "options": [ + { + "value": "before", + "text": "23 岁有" + }, + { + "value": "after", + "text": "15 岁有" + }, + { + "value": "both", + "text": "两段都有" + }, + { + "value": "neither", + "text": "两段都没有" + }, + { + "value": "unsure", + "text": "记不清" + } + ], + "follow_up": { + "prompt": "大概是哪一年?月份记得的话,也可以填。那次发生了什么?", + "categories": [ + { + "id": "relationship", + "value": "relationship", + "text": "重要的感情开始" + }, + { + "id": "marriage", + "value": "marriage", + "text": "结婚 / 确定关系" + }, + { + "id": "divorce", + "value": "divorce", + "text": "离婚 / 分开" + } + ] + } + } + ] + }, + { + "engine_version": "chart-choice-v2", + "window": { + "start_at": "1997-08-08T04:30", + "end_at": "1997-08-08T05:30" + }, + "reported_at": "1997-08-08T05:00", + "reported_known": true, + "ayanamsa": "raman", + "node_mode": "mean", + "round": 2, + "max_rounds": 3, + "remaining_key_lines": 6, + "ordered_lines": [ + { + "at": "1997-08-08T04:51:30", + "degree": 13.333333, + "charts": [ + "D9" + ] + }, + { + "at": "1997-08-08T04:35:08", + "degree": 10.0, + "charts": [ + "D9", + "D6", + "D12", + "D24" + ] + }, + { + "at": "1997-08-08T04:59:43", + "degree": 15.0, + "charts": [ + "D10", + "D2", + "D6", + "D8", + "D4", + "D12", + "D24" + ] + }, + { + "at": "1997-08-08T04:44:57", + "degree": 12.0, + "charts": [ + "D10" + ] + }, + { + "at": "1997-08-08T04:30:14", + "degree": 9.0, + "charts": [ + "D10" + ] + }, + { + "at": "1997-08-08T04:41:16", + "degree": 11.25, + "charts": [ + "D8", + "D24" + ] + } + ], + "minor_lines": [ + { + "at": "1997-08-08T04:47:24", + "charts": [ + "D12", + "D24" + ] + }, + { + "at": "1997-08-08T04:53:33", + "charts": [ + "D24" + ] + }, + { + "at": "1997-08-08T05:05:52", + "charts": [ + "D24" + ] + } + ], + "key_charts": [ + { + "chart": "D1", + "sign": "巨蟹", + "decided_by": null + }, + { + "chart": "D9", + "sign": "天蝎", + "decided_by": null, + "flips_at": "1997-08-08T04:35:08" + }, + { + "chart": "D10", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:30:14" + }, + { + "chart": "D2", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + }, + { + "chart": "D6", + "sign": "摩羯", + "decided_by": null, + "flips_at": "1997-08-08T04:35:08" + }, + { + "chart": "D8", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:41:16" + } + ], + "segment": { + "start_at": "1997-08-08T04:30:00", + "end_at": "1997-08-08T05:07:54" + }, + "resolved": [ + { + "line_at": "1997-08-08T05:07:55", + "side": "before", + "answers": {}, + "override_side": "before" + } + ], + "events": [], + "status": "choose", + "line": { + "at": "1997-08-08T04:51:30", + "degree": 13.333333, + "charts": [ + "D9" + ] + }, + "other_lines_in_window": 5, + "reported_side": "after", + "reported_neutral": true, + "seconds_from_line": 510, + "sides": { + "before": { + "from": "1997-08-08T04:47:24", + "to": "1997-08-08T04:51:29", + "saved_at": "1997-08-08T04:50" + }, + "after": { + "from": "1997-08-08T04:51:30", + "to": "1997-08-08T04:53:32", + "saved_at": "1997-08-08T04:52:31" + } + }, + "comparisons": [ + { + "chart": "D9", + "label": "婚恋", + "chart_label": "婚恋盘 D9", + "before": { + "chart": "D9", + "domain": "relationship", + "label": "婚恋", + "chart_label": "婚恋盘 D9", + "ascendant": "天秤", + "ascendant_index": 6, + "points": [ + { + "kind": "occupant", + "fact": "月亮在伴侣的位置", + "meaning": "和人打交道,变化多、投入感情" + }, + { + "kind": "lord", + "fact": "管伴侣的星(火星)落在第 2 宫", + "meaning": "伴侣和家庭、钱财观念紧密相关" + }, + { + "kind": "lord", + "fact": "代表你自己的星(金星)落在第 2 宫", + "meaning": "伴侣和家庭、钱财观念紧密相关" + } + ] + }, + "after": { + "chart": "D9", + "domain": "relationship", + "label": "婚恋", + "chart_label": "婚恋盘 D9", + "ascendant": "天蝎", + "ascendant_index": 7, + "points": [ + { + "kind": "occupant", + "fact": "火星在命位", + "meaning": "竞争和技术,行动快、敢拼" + }, + { + "kind": "occupant", + "fact": "金星在命位", + "meaning": "审美、设计和人缘,讲究舒适" + }, + { + "kind": "lord", + "fact": "管伴侣的星(金星)落在第 1 宫", + "meaning": "感情里很看重自我,伴侣和自己性格相近" + }, + { + "kind": "lord", + "fact": "代表你自己的星(火星)落在第 1 宫", + "meaning": "感情里很看重自我,伴侣和自己性格相近" + } + ] + } + } + ], + "unchanged": [ + { + "chart": "D1", + "label": "整体性格" + }, + { + "chart": "D10", + "label": "事业" + }, + { + "chart": "D2", + "label": "钱财" + }, + { + "chart": "D6", + "label": "日常遇到压力时" + }, + { + "chart": "D8", + "label": "遇到意外变化时" + }, + { + "chart": "D4", + "label": "住处" + }, + { + "chart": "D12", + "label": "父母" + }, + { + "chart": "D24", + "label": "学业" + } + ], + "questions": [ + { + "domain": "relationship", + "chart": "D9", + "label": "婚恋", + "prompt": "你的婚恋,更像哪一种?", + "options": [ + { + "value": "before", + "text": "伴侣和家庭、钱财观念紧密相关;伴侣和家庭、钱财观念紧密相关" + }, + { + "value": "after", + "text": "感情里很看重自我,伴侣和自己性格相近;感情里很看重自我,伴侣和自己性格相近" + }, + { + "value": "unsure", + "text": "说不清" + } + ], + "optional": true + } + ], + "event_questions": [ + { + "id": "D9", + "chart": "D9", + "before_ages": [ + 23, + 23 + ], + "after_ages": [ + 15, + 15 + ], + "prompt": "感情上的变化,线前候选对应 23 岁,线后候选对应 15 岁。你在哪一段有过这些变化?比如开始恋爱、结婚或分开。", + "options": [ + { + "value": "before", + "text": "23 岁有" + }, + { + "value": "after", + "text": "15 岁有" + }, + { + "value": "both", + "text": "两段都有" + }, + { + "value": "neither", + "text": "两段都没有" + }, + { + "value": "unsure", + "text": "记不清" + } + ], + "follow_up": { + "prompt": "大概是哪一年?月份记得的话,也可以填。那次发生了什么?", + "categories": [ + { + "id": "relationship", + "value": "relationship", + "text": "重要的感情开始" + }, + { + "id": "marriage", + "value": "marriage", + "text": "结婚 / 确定关系" + }, + { + "id": "divorce", + "value": "divorce", + "text": "离婚 / 分开" + } + ] + } + } + ] + }, + { + "engine_version": "chart-choice-v2", + "window": { + "start_at": "1997-08-08T04:30", + "end_at": "1997-08-08T05:30" + }, + "reported_at": "1997-08-08T05:00", + "reported_known": true, + "ayanamsa": "raman", + "node_mode": "mean", + "round": 3, + "max_rounds": 3, + "remaining_key_lines": 1, + "ordered_lines": [ + { + "at": "1997-08-08T04:59:43", + "degree": 15.0, + "charts": [ + "D10", + "D2", + "D6", + "D8", + "D4", + "D12", + "D24" + ] + } + ], + "minor_lines": [ + { + "at": "1997-08-08T04:53:33", + "charts": [ + "D24" + ] + }, + { + "at": "1997-08-08T05:05:52", + "charts": [ + "D24" + ] + } + ], + "key_charts": [ + { + "chart": "D1", + "sign": "巨蟹", + "decided_by": null + }, + { + "chart": "D9", + "sign": "天蝎", + "decided_by": "override" + }, + { + "chart": "D10", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + }, + { + "chart": "D2", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + }, + { + "chart": "D6", + "sign": "摩羯", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + }, + { + "chart": "D8", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + } + ], + "segment": { + "start_at": "1997-08-08T04:51:30", + "end_at": "1997-08-08T05:07:54" + }, + "resolved": [ + { + "line_at": "1997-08-08T05:07:55", + "side": "before", + "answers": {}, + "override_side": "before" + }, + { + "line_at": "1997-08-08T04:51:30", + "side": "after", + "answers": {}, + "override_side": "after" + } + ], + "events": [], + "status": "choose", + "line": { + "at": "1997-08-08T04:59:43", + "degree": 15.0, + "charts": [ + "D10", + "D2", + "D6", + "D8", + "D4", + "D12", + "D24" + ] + }, + "other_lines_in_window": 0, + "reported_side": "after", + "reported_neutral": true, + "seconds_from_line": 17, + "sides": { + "before": { + "from": "1997-08-08T04:53:33", + "to": "1997-08-08T04:59:42", + "saved_at": "1997-08-08T04:58" + }, + "after": { + "from": "1997-08-08T04:59:43", + "to": "1997-08-08T05:05:51", + "saved_at": "1997-08-08T05:01" + } + }, + "comparisons": [ + { + "chart": "D10", + "label": "事业", + "chart_label": "事业盘 D10", + "before": { + "chart": "D10", + "domain": "career", + "label": "事业", + "chart_label": "事业盘 D10", + "ascendant": "巨蟹", + "ascendant_index": 3, + "points": [ + { + "kind": "occupant", + "fact": "金星在事业位", + "meaning": "审美、设计和人缘,讲究舒适" + }, + { + "kind": "lord", + "fact": "管事业的星(火星)落在第 5 宫", + "meaning": "靠专长、兴趣和脑力吃饭,偏技术、研究、创作或教育" + }, + { + "kind": "lord", + "fact": "代表你自己的星(月亮)落在第 3 宫", + "meaning": "主动、爱表达,靠自己闯" + } + ] + }, + "after": { + "chart": "D10", + "domain": "career", + "label": "事业", + "chart_label": "事业盘 D10", + "ascendant": "狮子", + "ascendant_index": 4, + "points": [ + { + "kind": "occupant", + "fact": "木星在事业位", + "meaning": "大平台和贵人,重学识、有分寸" + }, + { + "kind": "occupant", + "fact": "罗睺在事业位", + "meaning": "野心大、不走寻常路,和外来或新兴的东西有缘" + }, + { + "kind": "occupant", + "fact": "土星在命位", + "meaning": "慢热、担责任、能坚持,早年辛苦、后劲足" + }, + { + "kind": "lord", + "fact": "管事业的星(金星)落在第 9 宫", + "meaning": "遇到导师、去远方、读更高的学历会推动事业" + }, + { + "kind": "lord", + "fact": "代表你自己的星(太阳)落在第 3 宫", + "meaning": "主动、爱表达,靠自己闯" + } + ] + } + }, + { + "chart": "D2", + "label": "钱财", + "chart_label": "钱财盘 D2", + "before": { + "chart": "D2", + "domain": "money", + "label": "钱财", + "chart_label": "钱财盘 D2", + "ascendant": "巨蟹", + "ascendant_index": 3, + "points": [ + { + "kind": "hora", + "fact": "上升落在月亮这一半。月亮、水星、金星、罗睺、计都在你这一半。", + "meaning": "钱更多来自和人打交道、做买卖、做内容或和审美有关的东西,来得灵活、零散" + } + ] + }, + "after": { + "chart": "D2", + "domain": "money", + "label": "钱财", + "chart_label": "钱财盘 D2", + "ascendant": "狮子", + "ascendant_index": 4, + "points": [ + { + "kind": "hora", + "fact": "上升落在太阳这一半。太阳、火星、木星、土星在你这一半。", + "meaning": "钱更多来自自己出头、拿职位、担责任,在体系里往上走,来得慢但稳" + } + ] + } + }, + { + "chart": "D6", + "label": "日常遇到压力时", + "chart_label": "分盘 D6", + "before": { + "chart": "D6", + "domain": "pressure", + "label": "日常遇到压力时", + "chart_label": "分盘 D6", + "ascendant": "射手", + "ascendant_index": 8, + "points": [ + { + "kind": "ascendant", + "fact": "上升落在射手", + "meaning": "先处理眼前最紧的一件事" + } + ] + }, + "after": { + "chart": "D6", + "domain": "pressure", + "label": "日常遇到压力时", + "chart_label": "分盘 D6", + "ascendant": "摩羯", + "ascendant_index": 9, + "points": [ + { + "kind": "ascendant", + "fact": "上升落在摩羯", + "meaning": "先维持原有节奏,把任务逐件做完" + } + ] + } + }, + { + "chart": "D8", + "label": "遇到意外变化时", + "chart_label": "分盘 D8", + "before": { + "chart": "D8", + "domain": "change", + "label": "遇到意外变化时", + "chart_label": "分盘 D8", + "ascendant": "巨蟹", + "ascendant_index": 3, + "points": [ + { + "kind": "ascendant", + "fact": "上升落在巨蟹", + "meaning": "先找熟悉的人商量下一步" + } + ] + }, + "after": { + "chart": "D8", + "domain": "change", + "label": "遇到意外变化时", + "chart_label": "分盘 D8", + "ascendant": "狮子", + "ascendant_index": 4, + "points": [ + { + "kind": "ascendant", + "fact": "上升落在狮子", + "meaning": "先动手试一个新办法" + } + ] + } + }, + { + "chart": "D4", + "label": "住处", + "chart_label": "住处盘 D4", + "before": { + "chart": "D4", + "domain": "home", + "label": "住处", + "chart_label": "住处盘 D4", + "ascendant": "天秤", + "ascendant_index": 6, + "points": [ + { + "kind": "lord", + "fact": "管住处的星(土星)落在第 3 宫", + "meaning": "搬动多,但多在同一城市或熟悉的范围里" + }, + { + "kind": "lord", + "fact": "代表你自己的星(金星)落在第 8 宫", + "meaning": "住处偶尔会有突然的变化" + } + ] + }, + "after": { + "chart": "D4", + "domain": "home", + "label": "住处", + "chart_label": "住处盘 D4", + "ascendant": "摩羯", + "ascendant_index": 9, + "points": [ + { + "kind": "occupant", + "fact": "太阳在家的位置", + "meaning": "权威和管理,想出头、要面子" + }, + { + "kind": "occupant", + "fact": "月亮在离乡的位置", + "meaning": "和人打交道,变化多、投入感情" + }, + { + "kind": "occupant", + "fact": "土星在离乡的位置", + "meaning": "慢热、担责任、能坚持,早年辛苦、后劲足" + }, + { + "kind": "lord", + "fact": "管住处的星(火星)落在第 10 宫", + "meaning": "人跟着工作走,为了事业定居" + }, + { + "kind": "lord", + "fact": "代表你自己的星(土星)落在第 12 宫", + "meaning": "长期在出生地以外生活,心里有离乡感" + } + ] + } + }, + { + "chart": "D12", + "label": "父母", + "chart_label": "父母盘 D12", + "before": { + "chart": "D12", + "domain": "parents", + "label": "父母", + "chart_label": "父母盘 D12", + "ascendant": "射手", + "ascendant_index": 8, + "points": [ + { + "kind": "occupant", + "fact": "水星在家的位置", + "meaning": "沟通、商业、文字和数据" + }, + { + "kind": "lord", + "fact": "管母亲的星(木星)落在第 11 宫", + "meaning": "母亲对你帮助大,带来实际的支持" + }, + { + "kind": "lord", + "fact": "管父亲的星(太阳)落在第 5 宫", + "meaning": "父亲很看重你的学习和兴趣" + } + ] + }, + "after": { + "chart": "D12", + "domain": "parents", + "label": "父母", + "chart_label": "父母盘 D12", + "ascendant": "摩羯", + "ascendant_index": 9, + "points": [ + { + "kind": "occupant", + "fact": "太阳在家的位置", + "meaning": "权威和管理,想出头、要面子" + }, + { + "kind": "lord", + "fact": "管母亲的星(火星)落在第 11 宫", + "meaning": "母亲对你帮助大,带来实际的支持" + }, + { + "kind": "lord", + "fact": "管父亲的星(水星)落在第 3 宫", + "meaning": "父亲的支持是鼓励你自己去闯" + } + ] + } + }, + { + "chart": "D24", + "label": "学业", + "chart_label": "学业盘 D24", + "before": { + "chart": "D24", + "domain": "study", + "label": "学业", + "chart_label": "学业盘 D24", + "ascendant": "双子", + "ascendant_index": 2, + "points": [ + { + "kind": "occupant", + "fact": "火星在专长的位置", + "meaning": "竞争和技术,行动快、敢拼" + }, + { + "kind": "occupant", + "fact": "木星在远方和贵人的位置", + "meaning": "大平台和贵人,重学识、有分寸" + }, + { + "kind": "lord", + "fact": "管深造的星(土星)落在第 12 宫", + "meaning": "深造多半在外地,或者来得晚" + }, + { + "kind": "lord", + "fact": "代表学习者自己的星(水星)落在第 6 宫", + "meaning": "学业靠考试和拼劲,在竞争里出成绩" + } + ] + }, + "after": { + "chart": "D24", + "domain": "study", + "label": "学业", + "chart_label": "学业盘 D24", + "ascendant": "巨蟹", + "ascendant_index": 3, + "points": [ + { + "kind": "occupant", + "fact": "火星在家的位置", + "meaning": "竞争和技术,行动快、敢拼" + }, + { + "kind": "occupant", + "fact": "水星在专长的位置", + "meaning": "沟通、商业、文字和数据" + }, + { + "kind": "lord", + "fact": "管深造的星(木星)落在第 8 宫", + "meaning": "学业有停顿或转向,偏研究和深层的东西" + }, + { + "kind": "lord", + "fact": "代表学习者自己的星(月亮)落在第 10 宫", + "meaning": "学的东西后来直接用在工作上,偏实用" + } + ] + } + } + ], + "unchanged": [ + { + "chart": "D1", + "label": "整体性格" + }, + { + "chart": "D9", + "label": "婚恋" + } + ], + "questions": [ + { + "domain": "career", + "chart": "D10", + "label": "事业", + "prompt": "你的事业,更像哪一种?", + "options": [ + { + "value": "before", + "text": "靠专长、兴趣和脑力吃饭,偏技术、研究、创作或教育" + }, + { + "value": "after", + "text": "遇到导师、去远方、读更高的学历会推动事业" + }, + { + "value": "unsure", + "text": "说不清" + } + ], + "optional": true + }, + { + "domain": "money", + "chart": "D2", + "label": "钱财", + "prompt": "你的钱财,更像哪一种?", + "options": [ + { + "value": "before", + "text": "钱更多来自和人打交道、做买卖、做内容或和审美有关的东西,来得灵活、零散" + }, + { + "value": "after", + "text": "钱更多来自自己出头、拿职位、担责任,在体系里往上走,来得慢但稳" + }, + { + "value": "unsure", + "text": "说不清" + } + ], + "optional": true + }, + { + "domain": "pressure", + "chart": "D6", + "label": "日常遇到压力时", + "prompt": "你的日常遇到压力时,更像哪一种?", + "options": [ + { + "value": "before", + "text": "先处理眼前最紧的一件事" + }, + { + "value": "after", + "text": "先维持原有节奏,把任务逐件做完" + }, + { + "value": "unsure", + "text": "说不清" + } + ], + "optional": true + }, + { + "domain": "change", + "chart": "D8", + "label": "遇到意外变化时", + "prompt": "你的遇到意外变化时,更像哪一种?", + "options": [ + { + "value": "before", + "text": "先找熟悉的人商量下一步" + }, + { + "value": "after", + "text": "先动手试一个新办法" + }, + { + "value": "unsure", + "text": "说不清" + } + ], + "optional": true + }, + { + "domain": "home", + "chart": "D4", + "label": "住处", + "prompt": "你的住处,更像哪一种?", + "options": [ + { + "value": "before", + "text": "搬动多,但多在同一城市或熟悉的范围里;住处偶尔会有突然的变化" + }, + { + "value": "after", + "text": "人跟着工作走,为了事业定居;长期在出生地以外生活,心里有离乡感" + }, + { + "value": "unsure", + "text": "说不清" + } + ], + "optional": true + } + ], + "event_questions": [ + { + "id": "D10", + "chart": "D10", + "before_ages": [ + 15, + 15 + ], + "after_ages": [ + 20, + 20 + ], + "prompt": "工作上的变化,线前候选对应 15 岁,线后候选对应 20 岁。你在哪一段有过这些变化?比如入职、升职、离职、换工作或创业。", + "options": [ + { + "value": "before", + "text": "15 岁有" + }, + { + "value": "after", + "text": "20 岁有" + }, + { + "value": "both", + "text": "两段都有" + }, + { + "value": "neither", + "text": "两段都没有" + }, + { + "value": "unsure", + "text": "记不清" + } + ], + "follow_up": { + "prompt": "大概是哪一年?月份记得的话,也可以填。那次发生了什么?", + "categories": [ + { + "id": "career", + "value": "career", + "text": "入职 / 升职" + }, + { + "id": "job_change", + "value": "job_change", + "text": "换工作 / 离职" + }, + { + "id": "business", + "value": "business", + "text": "创业 / 开店" + } + ] + } + }, + { + "id": "D2", + "chart": "D2", + "before_ages": [ + 20, + 20 + ], + "after_ages": [ + 15, + 15 + ], + "prompt": "收入和支出上的变化,线前候选对应 20 岁,线后候选对应 15 岁。你在哪一段有过这些变化?比如收入明显增加、意外支出或较大的损失。", + "options": [ + { + "value": "before", + "text": "20 岁有" + }, + { + "value": "after", + "text": "15 岁有" + }, + { + "value": "both", + "text": "两段都有" + }, + { + "value": "neither", + "text": "两段都没有" + }, + { + "value": "unsure", + "text": "记不清" + } + ], + "follow_up": { + "prompt": "大概是哪一年?月份记得的话,也可以填。那次发生了什么?", + "categories": [ + { + "id": "income_increase", + "value": "finance_pos", + "text": "收入明显增加" + }, + { + "id": "unexpected_expense", + "value": "finance_neg", + "text": "一笔较大的意外支出" + }, + { + "id": "financial_loss", + "value": "finance_neg", + "text": "一笔较大的损失" + } + ] + } + } + ] + } + ], + "decisions": [ + { + "chosen_side": "before", + "rule": "user_override", + "votes": null, + "both_versions_domains": [], + "scores": { + "before": 0, + "after": 0 + }, + "sources": [], + "decided_by": "override", + "saved_at": "1997-08-08T05:00", + "both_versions": [], + "segment": { + "start_at": "1997-08-08T04:30:00", + "end_at": "1997-08-08T05:07:54" + }, + "rounds": [ + { + "line_at": "1997-08-08T05:07:55", + "charts": [ + "D9" + ], + "chosen_side": "before", + "rule": "user_override", + "votes": null, + "scores": { + "before": 0, + "after": 0 + }, + "sources": [], + "decided_by": "override" + } + ], + "undetermined_charts": [ + "D9", + "D10", + "D2", + "D6", + "D8" + ], + "events": [], + "key_charts": [ + { + "chart": "D1", + "sign": "巨蟹", + "decided_by": null + }, + { + "chart": "D9", + "sign": "天蝎", + "decided_by": null, + "flips_at": "1997-08-08T04:35:08" + }, + { + "chart": "D10", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:30:14" + }, + { + "chart": "D2", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + }, + { + "chart": "D6", + "sign": "摩羯", + "decided_by": null, + "flips_at": "1997-08-08T04:35:08" + }, + { + "chart": "D8", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:41:16" + } + ], + "remaining_key_lines": 6, + "has_next": true, + "resolved": [ + { + "line_at": "1997-08-08T05:07:55", + "side": "before", + "answers": {}, + "override_side": "before" + } + ] + }, + { + "chosen_side": "after", + "rule": "user_override", + "votes": null, + "both_versions_domains": [], + "scores": { + "before": 0, + "after": 0 + }, + "sources": [], + "decided_by": "override", + "saved_at": "1997-08-08T05:00", + "both_versions": [], + "segment": { + "start_at": "1997-08-08T04:51:30", + "end_at": "1997-08-08T05:07:54" + }, + "rounds": [ + { + "line_at": "1997-08-08T05:07:55", + "charts": [ + "D9" + ], + "chosen_side": "before", + "rule": "user_override", + "votes": null, + "scores": { + "before": 0, + "after": 0 + }, + "sources": [], + "decided_by": "override" + }, + { + "line_at": "1997-08-08T04:51:30", + "charts": [ + "D9" + ], + "chosen_side": "after", + "rule": "user_override", + "votes": null, + "scores": { + "before": 0, + "after": 0 + }, + "sources": [], + "decided_by": "override" + } + ], + "undetermined_charts": [ + "D10", + "D2", + "D6", + "D8" + ], + "events": [], + "key_charts": [ + { + "chart": "D1", + "sign": "巨蟹", + "decided_by": null + }, + { + "chart": "D9", + "sign": "天蝎", + "decided_by": "override" + }, + { + "chart": "D10", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + }, + { + "chart": "D2", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + }, + { + "chart": "D6", + "sign": "摩羯", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + }, + { + "chart": "D8", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + } + ], + "remaining_key_lines": 1, + "has_next": true, + "resolved": [ + { + "line_at": "1997-08-08T05:07:55", + "side": "before", + "answers": {}, + "override_side": "before" + }, + { + "line_at": "1997-08-08T04:51:30", + "side": "after", + "answers": {}, + "override_side": "after" + } + ] + }, + { + "chosen_side": "after", + "rule": "user_override", + "votes": null, + "both_versions_domains": [], + "scores": { + "before": 0, + "after": 0 + }, + "sources": [], + "decided_by": "override", + "saved_at": "1997-08-08T05:01", + "both_versions": [], + "segment": { + "start_at": "1997-08-08T04:59:43", + "end_at": "1997-08-08T05:07:54" + }, + "rounds": [ + { + "line_at": "1997-08-08T05:07:55", + "charts": [ + "D9" + ], + "chosen_side": "before", + "rule": "user_override", + "votes": null, + "scores": { + "before": 0, + "after": 0 + }, + "sources": [], + "decided_by": "override" + }, + { + "line_at": "1997-08-08T04:51:30", + "charts": [ + "D9" + ], + "chosen_side": "after", + "rule": "user_override", + "votes": null, + "scores": { + "before": 0, + "after": 0 + }, + "sources": [], + "decided_by": "override" + }, + { + "line_at": "1997-08-08T04:59:43", + "charts": [ + "D10", + "D2", + "D6", + "D8", + "D4", + "D12", + "D24" + ], + "chosen_side": "after", + "rule": "user_override", + "votes": null, + "scores": { + "before": 0, + "after": 0 + }, + "sources": [], + "decided_by": "override" + } + ], + "undetermined_charts": [], + "events": [], + "key_charts": [ + { + "chart": "D1", + "sign": "巨蟹", + "decided_by": null + }, + { + "chart": "D9", + "sign": "天蝎", + "decided_by": "override" + }, + { + "chart": "D10", + "sign": "狮子", + "decided_by": "override" + }, + { + "chart": "D2", + "sign": "狮子", + "decided_by": "override" + }, + { + "chart": "D6", + "sign": "摩羯", + "decided_by": "override" + }, + { + "chart": "D8", + "sign": "狮子", + "decided_by": "override" + } + ], + "events_detail": [], + "evidence_summary": { + "event_count": 0, + "matched": 0, + "confidence": "tendency_only", + "coverage": { + "event_count": 0, + "category_count": 0, + "group_count": 0, + "groups": [], + "year_span": 0, + "level": "thin" + }, + "caveat": "这一步按占星师的方法计算,结果只作参考,不是验证结果。2026-11-06 我们在 77 个出生时间有可靠记录的案例上回测过同类的事件打分:30 分钟范围里头名命中 19%,随机挑也是 20%,直接用报的时间是 34%。" + }, + "remaining_key_lines": 0, + "has_next": false, + "resolved": [ + { + "line_at": "1997-08-08T05:07:55", + "side": "before", + "answers": {}, + "override_side": "before" + }, + { + "line_at": "1997-08-08T04:51:30", + "side": "after", + "answers": {}, + "override_side": "after" + }, + { + "line_at": "1997-08-08T04:59:43", + "side": "after", + "answers": {}, + "override_side": "after" + } + ] + } + ], + "stop_input": { + "lat": 36.42, + "lon": 114.21, + "tz": 8, + "start_at": "1997-08-08T04:30", + "end_at": "1997-08-08T05:30", + "reported_at": "1997-08-08T05:00", + "resolved": [], + "stop": true, + "resume_v2": { + "segment": { + "start_at": "1997-08-08T04:30:00", + "end_at": "1997-08-08T05:30:00" + }, + "rounds": [], + "events": [ + { + "date": "2019", + "category": "relationship" + }, + { + "date": "2021", + "category": "job_change" + }, + { + "date": "2016", + "category": "travel" + } + ] + } + }, + "final": { + "engine_version": "chart-choice-v2", + "window": { + "start_at": "1997-08-08T04:30", + "end_at": "1997-08-08T05:30" + }, + "reported_at": "1997-08-08T05:00", + "reported_known": true, + "ayanamsa": "raman", + "node_mode": "mean", + "round": 1, + "max_rounds": 3, + "remaining_key_lines": 11, + "ordered_lines": [ + { + "at": "1997-08-08T05:07:55", + "degree": 16.666667, + "charts": [ + "D9" + ] + }, + { + "at": "1997-08-08T04:51:30", + "degree": 13.333333, + "charts": [ + "D9" + ] + }, + { + "at": "1997-08-08T05:24:22", + "degree": 20.0, + "charts": [ + "D9", + "D6", + "D12", + "D24" + ] + }, + { + "at": "1997-08-08T04:35:08", + "degree": 10.0, + "charts": [ + "D9", + "D6", + "D12", + "D24" + ] + }, + { + "at": "1997-08-08T04:59:43", + "degree": 15.0, + "charts": [ + "D10", + "D2", + "D6", + "D8", + "D4", + "D12", + "D24" + ] + }, + { + "at": "1997-08-08T05:14:30", + "degree": 18.0, + "charts": [ + "D10" + ] + }, + { + "at": "1997-08-08T04:44:57", + "degree": 12.0, + "charts": [ + "D10" + ] + }, + { + "at": "1997-08-08T05:29:18", + "degree": 21.0, + "charts": [ + "D10" + ] + }, + { + "at": "1997-08-08T04:30:14", + "degree": 9.0, + "charts": [ + "D10" + ] + }, + { + "at": "1997-08-08T05:18:12", + "degree": 18.75, + "charts": [ + "D8", + "D24" + ] + }, + { + "at": "1997-08-08T04:41:16", + "degree": 11.25, + "charts": [ + "D8", + "D24" + ] + } + ], + "minor_lines": [ + { + "at": "1997-08-08T04:47:24", + "charts": [ + "D12", + "D24" + ] + }, + { + "at": "1997-08-08T04:53:33", + "charts": [ + "D24" + ] + }, + { + "at": "1997-08-08T05:05:52", + "charts": [ + "D24" + ] + }, + { + "at": "1997-08-08T05:12:02", + "charts": [ + "D12", + "D24" + ] + } + ], + "key_charts": [ + { + "chart": "D1", + "sign": "巨蟹", + "decided_by": null + }, + { + "chart": "D9", + "sign": "天蝎", + "decided_by": null, + "flips_at": "1997-08-08T04:35:08" + }, + { + "chart": "D10", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:30:14" + }, + { + "chart": "D2", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + }, + { + "chart": "D6", + "sign": "摩羯", + "decided_by": null, + "flips_at": "1997-08-08T04:35:08" + }, + { + "chart": "D8", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:41:16" + } + ], + "segment": { + "start_at": "1997-08-08T04:30:00", + "end_at": "1997-08-08T05:30:00" + }, + "resolved": [], + "events": [ + { + "date": "2019", + "category": "relationship" + }, + { + "date": "2021", + "category": "job_change" + }, + { + "date": "2016", + "category": "travel" + } + ], + "status": "complete", + "saved_at": "1997-08-08T05:00", + "decision": { + "saved_at": "1997-08-08T05:00", + "segment": { + "start_at": "1997-08-08T04:30:00", + "end_at": "1997-08-08T05:30:00" + }, + "rounds": [], + "undetermined_charts": [ + "D9", + "D10", + "D2", + "D6", + "D8" + ], + "events": [ + { + "date": "2019", + "category": "relationship" + }, + { + "date": "2021", + "category": "job_change" + }, + { + "date": "2016", + "category": "travel" + } + ], + "key_charts": [ + { + "chart": "D1", + "sign": "巨蟹", + "decided_by": null + }, + { + "chart": "D9", + "sign": "天蝎", + "decided_by": null, + "flips_at": "1997-08-08T04:35:08" + }, + { + "chart": "D10", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:30:14" + }, + { + "chart": "D2", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:59:43" + }, + { + "chart": "D6", + "sign": "摩羯", + "decided_by": null, + "flips_at": "1997-08-08T04:35:08" + }, + { + "chart": "D8", + "sign": "狮子", + "decided_by": null, + "flips_at": "1997-08-08T04:41:16" + } + ], + "events_detail": [ + { + "date": "2019", + "category": "relationship", + "matched": true + }, + { + "date": "2021", + "category": "job_change", + "matched": true + }, + { + "date": "2016", + "category": "travel", + "matched": true + } + ], + "evidence_summary": { + "event_count": 3, + "matched": 3, + "confidence": "none", + "coverage": { + "event_count": 3, + "category_count": 3, + "group_count": 3, + "groups": [ + "career", + "mobility", + "relationship" + ], + "year_span": 5, + "level": "thin" + }, + "caveat": "这一步按占星师的方法计算,结果只作参考,不是验证结果。2026-11-06 我们在 77 个出生时间有可靠记录的案例上回测过同类的事件打分:30 分钟范围里头名命中 19%,随机挑也是 20%,直接用报的时间是 34%。" + }, + "remaining_key_lines": 11, + "has_next": false, + "rule": "stopped", + "resolved": [] + } + } +}