dsh-metaphysics 0.18.1 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -8
- package/README.md +2 -2
- package/client.js +234 -4
- package/engine/geo.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,14 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
版本按「一组可感知的改动」发,README 与市场条目描述随能力同步。
|
|
4
4
|
|
|
5
|
-
## 0.18.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
5
|
+
## 0.18.3(已发布)
|
|
6
|
+
|
|
7
|
+
K 线的第二轮打磨(0.18.2 之后的可读性修正):
|
|
8
|
+
|
|
9
|
+
- 大运段名从图顶挪到图下的条纹带里,不再和「最高/最低岁」标记抢位置;段名居中,条带随大运交替着色
|
|
10
|
+
- 5 年 / 10 年均线在右缘标出末端数值,两条贴太近时自动上下错开
|
|
11
|
+
- 纵轴补 0/25/50/75/100 刻度,横轴补「年龄(岁)」与「每岁一分 · 满分 100」;影线用 `clipPath`
|
|
12
|
+
夹在绘图区内,贴边的影线不再溢出
|
|
13
|
+
- 新增统计条:全段均值、最好大运、最差大运、波动幅度。旺衰落在换档阈值附近时只给均值与波动,不给
|
|
14
|
+
「最好/最差大运」的顺位
|
|
15
|
+
- 图高由 300 提到 322,给刻度与段名条留位
|
|
16
|
+
- `test/kline.test.mjs` 增到 29 条(段名条、均线末端值、纵轴刻度、统计条、最好/最差大运)
|
|
17
|
+
- 统计:58 个测试文件 / 1650 条断言
|
|
18
|
+
|
|
19
|
+
## 0.18.2(已发布)
|
|
20
|
+
|
|
21
|
+
人生 K 线从「等宽文本 + ASCII」升级成真正的蜡烛图,并嵌进八字结果:
|
|
22
|
+
|
|
23
|
+
- 新增 `PP.trendChart` / `PP.trendHTML`:实体=该岁相对上一岁的涨跌、影线=±4 分;叠 5 年 / 10 年均线、
|
|
24
|
+
大运交替底色与分界虚线(段名标顶部)、0–100 固定量程网格、年龄刻度、最高/最低岁标记;悬停看逐年
|
|
25
|
+
「多少岁 / 哪一年 / 什么干支 / 多少分 / 落在哪步大运」。颜色全走 CSS 变量,深浅色与打印跟随
|
|
26
|
+
- 「人生K线」tab 不再输出纯文本摘要,直接给图 + 大运分段 chips + 高低年份;数据不足或空值时安全退回文本
|
|
27
|
+
- **八字结果底部直接嵌同一张 K 线**(面板与独立页在排八字时顺带算一次 `trend`),与原有的
|
|
28
|
+
「人生运势曲线」(大运阶梯 + 流年折线)并存:曲线看趋势形态,K 线看逐年涨跌
|
|
29
|
+
- 新增 `test/kline.test.mjs`(23 条):结构、坐标不越界、无 NaN、空/单点安全、旺衰边界不出高低结论、
|
|
30
|
+
两处嵌入点都在源码里
|
|
31
|
+
- 统计:58 个测试文件 / 1644 条断言
|
|
32
|
+
|
|
33
|
+
## 0.18.1(已发布)
|
|
34
|
+
|
|
35
|
+
元数据与检索入口同步,**无运行时行为变化**:
|
|
36
|
+
|
|
37
|
+
- 把「玄学 / 算命 / 占卜 / 运势」加进 `package.json` 关键词与描述、`locale/*.json`、市场条目描述、README 与工具描述——之前只写「命理排盘」,用户搜「玄学」搜不到;现在搜「玄学」应能命中
|
|
38
|
+
- 工具描述补一条触发条件:用户只搜「玄学」或问「有什么玄学工具」时也应介绍本插件
|
|
39
|
+
- 排盘逻辑、输出与测试断言不变;仅 UA 随版本号更新(`dsh-metaphysics/0.18.1`)
|
|
40
|
+
|
|
13
41
|
## 0.18.0(已发布)
|
|
14
42
|
|
|
15
43
|
吸收同生态另一款命理插件「玄机阁」(dsh-xuanji,MIT)的能力:**按行为规格独立实现,不 vendor 源码、
|
package/README.md
CHANGED
|
@@ -91,7 +91,7 @@ Chinese metaphysics (玄学 / xuanxue) for [DeepSeek Harness](https://github.com
|
|
|
91
91
|
- **小六壬 `xiaoliuren`**:时间起课(农历月/日/时三掐)与报数起课(三个 1–99),六宫吉凶与白话断语;缺时辰会明确要求补,不默认某个时辰
|
|
92
92
|
- **星座 `star`**:太阳星座档案(四象、守护星、关键词、契合星座)+ 当日四维基调;基调由日期哈希确定性生成,明写「不是随机数」,纯娱乐向
|
|
93
93
|
- **生命数字 `numerology`**:生命路径数 / 生日数 / 态度数,11/22/33 主数保留;与八字口径不同,混问时会说明两套不可互相印证
|
|
94
|
-
- **人生 K 线 `trend`**:复用八字的旺衰、大运与流年十神,按固定计分表给出 1–100
|
|
94
|
+
- **人生 K 线 `trend`**:复用八字的旺衰、大运与流年十神,按固定计分表给出 1–100 岁逐年分与最佳/最差年份;面板与独立页画的是**蜡烛图**(实体=相对上一岁的涨跌、影线=±4 分,叠 5/10 年均线并在右缘标末端值、大运条纹带与分界虚线、最高/最低岁标记),图下再给全段均值 / 最好大运 / 最差大运 / 波动幅度,并直接嵌在八字结果底部,与「人生运势曲线」(大运阶梯 + 流年折线)互补:曲线看趋势,K 线看逐年涨跌。旺衰正好落在换档阈值(±0.3)时只提示、不给顺位结论
|
|
95
95
|
- **风水 `fengshui`**:八宅命卦(1900s/2000s 男女公式、立春界)、大游年四吉四凶、流年九宫飞白、24 山门主灶布局倾向、命宅配调停文案;`scope` 明写**不含**玄空各运盘与现场勘舆
|
|
96
96
|
- **起名 `name`**:评估现有名字或按喜用五行生成候选名;241 字自建字库,未收录的字如实标「未收录」;**不做五格剖象**(需康熙字典笔画)与音韵判断
|
|
97
97
|
|
|
@@ -107,7 +107,7 @@ Chinese metaphysics (玄学 / xuanxue) for [DeepSeek Harness](https://github.com
|
|
|
107
107
|
- **追问**:工具参数 `ask`、面板与独立页的追问输入框——围绕**已算好的盘**回答「2027 年事业怎么样」「财运如何」这类问题;只取盘面已有字段(流年/大运/宫位/分节),不重排、不猜测;年份超出已排范围或没识别到方面时,明确说找不到并给出可问范围
|
|
108
108
|
- **八字 × 紫微 交叉验证**:工具参数 `cross`(或面板勾选)会用同一份出生信息再排另一套盘,对事业/财富/感情/身体/节奏五项并列对照,标注「同向 / 有张力 / 互补」;只作对照,不把两套体系折算成同一个分数
|
|
109
109
|
- **量化口径**:只量化引擎已有数据(十神权重、大运/流年档位、宫位与四化计数),**不换算金额、不给健康诊断**;报告里有「量化口径」分节写明这条边界
|
|
110
|
-
- **方法与验证**:独立页内置同页「方法与验证」(体系、128 条卦例对拍、
|
|
110
|
+
- **方法与验证**:独立页内置同页「方法与验证」(体系、128 条卦例对拍、58 个测试文件/1650 条断言、七道闸、口径与已知边界),离线可开
|
|
111
111
|
- **夏令时校正(0.18 新增)**:1986–1991 年中国夏令时期间的钟面时间在排盘前自动减 1 小时(跨日回退),八字/紫微/合盘/验前事/生时校正统一生效;出生证已是真实太阳口径时可用 `dst: false` 关闭
|
|
112
112
|
- **合婚明细层(0.18 新增)**:合盘新增属相六合/三合/六冲/相害/相刑、天干五合、双向十神、跨盘地支、年命纳音与吉凶象计数分档;只作明细,不改原总分
|
|
113
113
|
- **晚子时开关**:面板与独立页都能切「23:00 后算次日/当日」;奇门另可切「归当日/归次日」——这是排盘口径,不是结果重算
|
package/client.js
CHANGED
|
@@ -746,7 +746,7 @@ function resolveAddress(input) {
|
|
|
746
746
|
|
|
747
747
|
const DETAIL = /(区|县|镇|乡|村|小区|街道|路|号|巷|弄|组|队)/;
|
|
748
748
|
|
|
749
|
-
const USER_AGENT = 'dsh-metaphysics/0.18.
|
|
749
|
+
const USER_AGENT = 'dsh-metaphysics/0.18.3 (+https://github.com/xuanfu-web/dsh-metaphysics)';
|
|
750
750
|
|
|
751
751
|
async function onlineLookup(text, { timeoutMs = 2500 } = {}) {
|
|
752
752
|
if (typeof fetch !== 'function') return null;
|
|
@@ -9656,6 +9656,57 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
|
|
|
9656
9656
|
.mp-curve .cv-legend i{display:inline-block;width:14px;height:2px;vertical-align:3px;margin-right:4px}
|
|
9657
9657
|
.mp-curve .cv-tip{font-size:11px;color:var(--muted);line-height:1.6;margin-top:4px}
|
|
9658
9658
|
@media (max-width:520px){.mp-curve .cv-lbl.age{font-size:9px}}
|
|
9659
|
+
.mp-kline{margin-top:10px;border:1px solid var(--line);border-radius:8px;background:var(--card);padding:10px 12px 9px}
|
|
9660
|
+
.mp-kline-h{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;font-size:12px;color:var(--label);font-family:system-ui,sans-serif;margin-bottom:2px}
|
|
9661
|
+
.mp-kline-h b{font-size:12.5px;color:var(--ink);font-weight:600}
|
|
9662
|
+
.mp-kline-h span{font-size:11.5px;color:var(--muted)}
|
|
9663
|
+
.mp-kline-fig svg{display:block;width:100%;height:auto;overflow:visible}
|
|
9664
|
+
.mp-kline .kl-band{fill:var(--ink);opacity:.045}
|
|
9665
|
+
.mp-kline .kl-grid{stroke:var(--line);stroke-width:.5;stroke-dasharray:1 4;opacity:.7}
|
|
9666
|
+
.mp-kline .kl-grid.mid{stroke-width:.8;stroke-dasharray:3 3;opacity:.9}
|
|
9667
|
+
.mp-kline .kl-segline{stroke:var(--dimline);stroke-width:.6;stroke-dasharray:2 3;opacity:.8}
|
|
9668
|
+
.mp-kline .kl-seg{fill:var(--label);font-size:8.5px;font-family:system-ui,sans-serif;paint-order:stroke;stroke:var(--card);stroke-width:2.2px;stroke-linejoin:round}
|
|
9669
|
+
.mp-kline .kl-lbl{fill:var(--muted);font-size:8px;font-family:system-ui,sans-serif}
|
|
9670
|
+
.mp-kline .kl-lbl.age{font-size:8.5px}
|
|
9671
|
+
.mp-kline .kl-lbl.axis{font-size:8px;letter-spacing:.02em;opacity:.85}
|
|
9672
|
+
.mp-kline .kl-stripband{fill:var(--ink);opacity:.06;rx:2}
|
|
9673
|
+
.mp-kline .kl-ma-lbl{font-size:8.5px;font-family:system-ui,sans-serif;paint-order:stroke;stroke:var(--card);stroke-width:2.4px;stroke-linejoin:round;fill:var(--muted)}
|
|
9674
|
+
.mp-kline .kl-ma-lbl.m5{fill:var(--quan)}
|
|
9675
|
+
.mp-kline .kl-ma-lbl.m10{fill:var(--ke);opacity:.9}
|
|
9676
|
+
.mp-kline .kl-stats{display:flex;gap:14px;flex-wrap:wrap;margin-top:6px;font-size:11px;color:var(--muted);font-family:system-ui,sans-serif}
|
|
9677
|
+
.mp-kline .kl-stats span{display:inline-flex;align-items:baseline;gap:5px}
|
|
9678
|
+
.mp-kline .kl-stats em{font-style:normal;font-size:10.5px;color:var(--dim)}
|
|
9679
|
+
.mp-kline .kl-stats b{font-size:12px;font-weight:600;color:var(--ink2);font-variant-numeric:tabular-nums}
|
|
9680
|
+
.mp-kline .kl-stats b.hi{color:var(--ji)}
|
|
9681
|
+
.mp-kline .kl-stats b.lo{color:var(--lu)}
|
|
9682
|
+
.mp-kline .kl-c.up line{stroke:var(--ji);stroke-width:.8;opacity:.8}
|
|
9683
|
+
.mp-kline .kl-c.up rect{fill:var(--ji);opacity:.92}
|
|
9684
|
+
.mp-kline .kl-c.down line{stroke:var(--lu);stroke-width:.8;opacity:.8}
|
|
9685
|
+
.mp-kline .kl-c.down rect{fill:var(--lu);opacity:.92}
|
|
9686
|
+
.mp-kline .kl-c:hover rect{opacity:1}
|
|
9687
|
+
.mp-kline .kl-ma{fill:none;stroke-linejoin:round;stroke-linecap:round}
|
|
9688
|
+
.mp-kline .kl-ma.m5{stroke:var(--quan);stroke-width:1.3}
|
|
9689
|
+
.mp-kline .kl-ma.m10{stroke:var(--ke);stroke-width:1;opacity:.85}
|
|
9690
|
+
.mp-kline .kl-mark circle{fill:var(--quan)}
|
|
9691
|
+
.mp-kline .kl-mark.lo circle{fill:var(--ke)}
|
|
9692
|
+
.mp-kline .kl-mark .ring{fill:none;stroke:currentColor;stroke-width:.6;opacity:.5}
|
|
9693
|
+
.mp-kline .kl-mark text{fill:var(--ink2);font-size:9px;font-family:system-ui,sans-serif;paint-order:stroke;stroke:var(--card);stroke-width:2.4px;stroke-linejoin:round}
|
|
9694
|
+
.mp-kline .kl-legend{display:flex;gap:12px;flex-wrap:wrap;margin-top:6px;font-size:11px;color:var(--muted);font-family:system-ui,sans-serif}
|
|
9695
|
+
.mp-kline .kl-legend i{display:inline-block;width:9px;height:9px;vertical-align:-1px;margin-right:4px;border-radius:1px}
|
|
9696
|
+
.mp-kline .kl-legend i.up{background:var(--ji)}
|
|
9697
|
+
.mp-kline .kl-legend i.down{background:var(--lu)}
|
|
9698
|
+
.mp-kline .kl-legend i.m5{height:2px;background:var(--quan);vertical-align:3px}
|
|
9699
|
+
.mp-kline .kl-legend i.m10{height:2px;background:var(--ke);vertical-align:3px}
|
|
9700
|
+
.kl-segs{display:flex;flex-wrap:wrap;gap:4px 6px;margin-top:8px;font-family:system-ui,sans-serif}
|
|
9701
|
+
.kl-chip{font-size:11px;color:var(--ink2);border:1px solid var(--hair);border-radius:99px;padding:1px 8px;background:var(--band)}
|
|
9702
|
+
.kl-chip em{font-style:normal;color:var(--muted);margin-left:5px;font-size:10.5px}
|
|
9703
|
+
.kl-hls{display:flex;flex-direction:column;gap:3px;margin-top:8px;font-size:11.5px;font-family:system-ui,sans-serif;color:var(--ink2)}
|
|
9704
|
+
.kl-hl{border-left:2px solid var(--line);padding-left:7px;line-height:1.6}
|
|
9705
|
+
.kl-hl.hi{border-color:var(--quan)}
|
|
9706
|
+
.kl-hl.lo{border-color:var(--ke)}
|
|
9707
|
+
.mp-kline-note{font-size:11px;color:var(--red);line-height:1.6;margin-top:6px}
|
|
9708
|
+
.mp-kline-tip{font-size:11px;color:var(--muted);line-height:1.6;margin-top:5px}
|
|
9709
|
+
@media print{.mp-kline{break-inside:avoid}}
|
|
9659
9710
|
.mp-tt{padding:16px}
|
|
9660
9711
|
.mp-tt-head{font-size:12.5px;color:var(--muted);margin-bottom:12px;letter-spacing:.04em}
|
|
9661
9712
|
.mp-tt-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(172px,1fr));gap:10px}
|
|
@@ -10665,6 +10716,171 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
|
|
|
10665
10716
|
</div>`;
|
|
10666
10717
|
};
|
|
10667
10718
|
|
|
10719
|
+
let klineSeq = 0;
|
|
10720
|
+
pp.trendChart = function (r) {
|
|
10721
|
+
const pts = r && Array.isArray(r.points) ? r.points : [];
|
|
10722
|
+
if (pts.length < 2) return '';
|
|
10723
|
+
const segs = r && Array.isArray(r.segments) ? r.segments : [];
|
|
10724
|
+
const num = (v) => (typeof v === 'number' && isFinite(v) ? v : 0);
|
|
10725
|
+
const W = 720, H = 322;
|
|
10726
|
+
const M = { t: 34, r: 46, b: 66, l: 36 };
|
|
10727
|
+
const pw = W - M.l - M.r, ph = H - M.t - M.b;
|
|
10728
|
+
const n = pts.length;
|
|
10729
|
+
const step = pw / n;
|
|
10730
|
+
const X = (i) => M.l + (i + 0.5) * step;
|
|
10731
|
+
const Y = (v) => M.t + ph - (Math.max(0, Math.min(100, v)) / 100) * ph;
|
|
10732
|
+
const bodyW = Math.max(1.1, Math.min(10, step * 0.62));
|
|
10733
|
+
const sc = (p) => num(p.score);
|
|
10734
|
+
const yb = M.t + ph;
|
|
10735
|
+
const stripTop = yb + 20, stripH = 22;
|
|
10736
|
+
const clipId = `klclip${(klineSeq += 1)}`;
|
|
10737
|
+
|
|
10738
|
+
const bands = segs.map((s, i) => {
|
|
10739
|
+
const i0 = Math.max(0, s.startAge - 1);
|
|
10740
|
+
const i1 = Math.min(n - 1, s.endAge - 1);
|
|
10741
|
+
if (i1 < i0) return '';
|
|
10742
|
+
const x = M.l + i0 * step;
|
|
10743
|
+
const w = Math.max(0.6, (i1 - i0 + 1) * step);
|
|
10744
|
+
const tint = i % 2 === 1;
|
|
10745
|
+
const rect = tint
|
|
10746
|
+
? `<rect class="kl-band" x="${x.toFixed(1)}" y="${M.t}" width="${w.toFixed(1)}" height="${ph}"/>`
|
|
10747
|
+
+ `<rect class="kl-stripband" x="${x.toFixed(1)}" y="${stripTop}" width="${w.toFixed(1)}" height="${stripH}"/>` : '';
|
|
10748
|
+
const line = i === 0 ? ''
|
|
10749
|
+
: `<line class="kl-segline" x1="${x.toFixed(1)}" y1="${M.t}" x2="${x.toFixed(1)}" y2="${yb}"/>`;
|
|
10750
|
+
const cx = Math.min(Math.max(M.l + ((i0 + i1 + 1) / 2) * step, M.l + 22), M.l + pw - 22);
|
|
10751
|
+
return rect + line
|
|
10752
|
+
+ `<text class="kl-seg" x="${cx.toFixed(1)}" y="${(stripTop + 15).toFixed(1)}" text-anchor="middle">${esc(s.ganZhi)}</text>`;
|
|
10753
|
+
}).join('');
|
|
10754
|
+
|
|
10755
|
+
const grid = [0, 25, 50, 75, 100].map((v) =>
|
|
10756
|
+
`<line class="kl-grid${v === 50 ? ' mid' : ''}" x1="${M.l}" y1="${Y(v).toFixed(1)}" x2="${M.l + pw}" y2="${Y(v).toFixed(1)}"/>`
|
|
10757
|
+
+ `<text class="kl-lbl" x="${M.l - 6}" y="${(Y(v) + 3).toFixed(1)}" text-anchor="end">${v}</text>`).join('');
|
|
10758
|
+
|
|
10759
|
+
const candles = pts.map((p, i) => {
|
|
10760
|
+
const k = p.kline || {};
|
|
10761
|
+
const o = num(k.open), c = num(k.close), hi = Math.min(100, num(k.high)), lo = Math.max(0, num(k.low));
|
|
10762
|
+
const up = c >= o;
|
|
10763
|
+
const yO = Y(o), yC = Y(c);
|
|
10764
|
+
const top = Math.min(yO, yC);
|
|
10765
|
+
const h = Math.max(0.8, Math.abs(yO - yC));
|
|
10766
|
+
const x = X(i);
|
|
10767
|
+
const title = `${p.age}岁(${p.year}年 ${p.ganZhi})${c}分${p.dayunGanZhi ? ` · ${p.dayunGanZhi}运` : ''}`;
|
|
10768
|
+
return `<g class="kl-c${up ? ' up' : ' down'}"><title>${esc(title)}</title>`
|
|
10769
|
+
+ `<line x1="${x.toFixed(2)}" y1="${Y(hi).toFixed(1)}" x2="${x.toFixed(2)}" y2="${Y(lo).toFixed(1)}"/>`
|
|
10770
|
+
+ `<rect x="${(x - bodyW / 2).toFixed(2)}" y="${top.toFixed(1)}" width="${bodyW.toFixed(2)}" height="${h.toFixed(1)}" rx="0.7"/></g>`;
|
|
10771
|
+
}).join('');
|
|
10772
|
+
|
|
10773
|
+
const maSeries = (win) => {
|
|
10774
|
+
const out = [];
|
|
10775
|
+
for (let i = 0; i < n; i++) {
|
|
10776
|
+
if (i + 1 < win) continue;
|
|
10777
|
+
let sum = 0;
|
|
10778
|
+
for (let j = i + 1 - win; j <= i; j++) sum += sc(pts[j]);
|
|
10779
|
+
out.push({ i, v: sum / win });
|
|
10780
|
+
}
|
|
10781
|
+
return out;
|
|
10782
|
+
};
|
|
10783
|
+
const maPath = (win) => maSeries(win)
|
|
10784
|
+
.map((q, k) => `${k ? 'L' : 'M'}${X(q.i).toFixed(1)} ${Y(q.v).toFixed(1)}`).join(' ');
|
|
10785
|
+
const maLabel = (win, cls) => {
|
|
10786
|
+
const s = maSeries(win);
|
|
10787
|
+
if (!s.length) return '';
|
|
10788
|
+
const last = s[s.length - 1];
|
|
10789
|
+
return { cls, y: Y(last.v), text: `<text class="kl-ma-lbl ${cls}" x="${M.l + pw + 5}" y="${Y(last.v).toFixed(1)}" text-anchor="start">${last.v.toFixed(0)}</text>` };
|
|
10790
|
+
};
|
|
10791
|
+
const lab10 = maLabel(10, 'm10'), lab5 = maLabel(5, 'm5');
|
|
10792
|
+
const maLabels = (() => {
|
|
10793
|
+
if (!lab5 || !lab10) return `${lab10 ? lab10.text : ''}${lab5 ? lab5.text : ''}`;
|
|
10794
|
+
let y5 = lab5.y, y10 = lab10.y;
|
|
10795
|
+
if (Math.abs(y5 - y10) < 10) {
|
|
10796
|
+
const top = Math.min(y5, y10), bot = Math.max(y5, y10);
|
|
10797
|
+
y5 = top - 6; y10 = bot + 6;
|
|
10798
|
+
}
|
|
10799
|
+
return `<text class="kl-ma-lbl m10" x="${M.l + pw + 5}" y="${y10.toFixed(1)}" text-anchor="start">${maSeries(10).slice(-1)[0].v.toFixed(0)}</text>`
|
|
10800
|
+
+ `<text class="kl-ma-lbl m5" x="${M.l + pw + 5}" y="${y5.toFixed(1)}" text-anchor="start">${maSeries(5).slice(-1)[0].v.toFixed(0)}</text>`;
|
|
10801
|
+
})();
|
|
10802
|
+
|
|
10803
|
+
let hiP = pts[0], loP = pts[0];
|
|
10804
|
+
for (const p of pts) {
|
|
10805
|
+
if (sc(p) > sc(hiP)) hiP = p;
|
|
10806
|
+
if (sc(p) < sc(loP)) loP = p;
|
|
10807
|
+
}
|
|
10808
|
+
const mark = (p, cls, dy) => {
|
|
10809
|
+
const i = pts.indexOf(p);
|
|
10810
|
+
const x = X(i), y = Y(sc(p));
|
|
10811
|
+
const tx = Math.min(Math.max(x, M.l + 26), M.l + pw - 26);
|
|
10812
|
+
const d = cls === 'hi' ? (y - 8 < M.t + 6 ? 14 : dy) : (y + 12 > M.t + ph - 2 ? -8 : dy);
|
|
10813
|
+
return `<g class="kl-mark ${cls}"><circle cx="${x.toFixed(1)}" cy="${y.toFixed(1)}" r="2"/>`
|
|
10814
|
+
+ `<circle class="ring" cx="${x.toFixed(1)}" cy="${y.toFixed(1)}" r="4.6"/>`
|
|
10815
|
+
+ `<text x="${tx.toFixed(1)}" y="${(y + d).toFixed(1)}" text-anchor="middle">${p.age}岁 ${sc(p)}</text></g>`;
|
|
10816
|
+
};
|
|
10817
|
+
|
|
10818
|
+
const tickStep = n > 60 ? 10 : 5;
|
|
10819
|
+
const ages = pts.filter((p) => p.age % tickStep === 0).map((p) =>
|
|
10820
|
+
`<text class="kl-lbl age" x="${X(pts.indexOf(p)).toFixed(1)}" y="${(yb + 14).toFixed(1)}" text-anchor="middle">${p.age}</text>`).join('');
|
|
10821
|
+
const xNote = `<text class="kl-lbl axis" x="${M.l}" y="${(H - 6).toFixed(1)}">年龄(岁)</text>`
|
|
10822
|
+
+ `<text class="kl-lbl axis" x="${M.l + pw}" y="${(H - 6).toFixed(1)}" text-anchor="end">每岁一分 · 满分 100</text>`;
|
|
10823
|
+
|
|
10824
|
+
const legend = `<div class="kl-legend"><span><i class="up"></i>阳线:比上一岁高</span>`
|
|
10825
|
+
+ `<span><i class="down"></i>阴线:比上一岁低</span>`
|
|
10826
|
+
+ `<span><i class="m5"></i>5 年均线</span><span><i class="m10"></i>10 年均线</span>`
|
|
10827
|
+
+ '<span>虚线=大运交界</span></div>';
|
|
10828
|
+
|
|
10829
|
+
return `<div class="mp-kline-fig">
|
|
10830
|
+
<svg viewBox="0 0 ${W} ${H}" role="img" aria-label="人生K线:1 到 ${n} 岁的逐年评分蜡烛图">
|
|
10831
|
+
<defs><clipPath id="${clipId}"><rect x="${M.l}" y="${M.t}" width="${pw}" height="${ph}"/></clipPath></defs>
|
|
10832
|
+
${bands}${grid}
|
|
10833
|
+
<g clip-path="url(#${clipId})">
|
|
10834
|
+
${candles}
|
|
10835
|
+
<path class="kl-ma m10" d="${maPath(10)}"/><path class="kl-ma m5" d="${maPath(5)}"/>
|
|
10836
|
+
</g>
|
|
10837
|
+
${maLabels}
|
|
10838
|
+
${mark(hiP, 'hi', -8)}${mark(loP, 'lo', 12)}
|
|
10839
|
+
${ages}${xNote}
|
|
10840
|
+
</svg>${legend}</div>`;
|
|
10841
|
+
};
|
|
10842
|
+
|
|
10843
|
+
pp.trendHTML = function (r) {
|
|
10844
|
+
if (!r) return '';
|
|
10845
|
+
const chart = pp.trendChart(r);
|
|
10846
|
+
if (!chart) return pp.quickHTML(r);
|
|
10847
|
+
const segs = Array.isArray(r.segments) ? r.segments : [];
|
|
10848
|
+
const hl = (r.highlights && typeof r.highlights === 'object') ? r.highlights : {};
|
|
10849
|
+
const meta = r.meta || {};
|
|
10850
|
+
const st = meta.strength || {};
|
|
10851
|
+
const chips = segs.map((s) => `<span class="kl-chip">${esc(s.ganZhi)}<em>${s.startAge}–${s.endAge}</em></span>`).join('');
|
|
10852
|
+
const best = (Array.isArray(hl.best) ? hl.best : []).map((x) => `<div class="kl-hl hi">${esc(x)}</div>`).join('');
|
|
10853
|
+
const worst = (Array.isArray(hl.worst) ? hl.worst : []).map((x) => `<div class="kl-hl lo">${esc(x)}</div>`).join('');
|
|
10854
|
+
const note = meta.boundaryNote ? `<div class="mp-kline-note">${esc(meta.boundaryNote)}</div>`
|
|
10855
|
+
: (hl.suppressed ? `<div class="mp-kline-note">${esc(hl.suppressed)}</div>` : '');
|
|
10856
|
+
const pts = Array.isArray(r.points) ? r.points : [];
|
|
10857
|
+
const scoreOf = (p) => (typeof p.score === 'number' && isFinite(p.score) ? p.score : 0);
|
|
10858
|
+
const scores = pts.map(scoreOf);
|
|
10859
|
+
const mean = scores.length ? scores.reduce((a, b) => a + b, 0) / scores.length : 0;
|
|
10860
|
+
const span = scores.length ? Math.max(...scores) - Math.min(...scores) : 0;
|
|
10861
|
+
const segAvg = segs.map((s) => {
|
|
10862
|
+
const inside = pts.filter((p) => p.age >= s.startAge && p.age <= s.endAge).map(scoreOf);
|
|
10863
|
+
return { ganZhi: s.ganZhi, v: inside.length ? inside.reduce((a, b) => a + b, 0) / inside.length : 0 };
|
|
10864
|
+
}).filter((x) => x.v > 0).sort((a, b) => b.v - a.v);
|
|
10865
|
+
const blocked = !!(st.boundary || hl.suppressed);
|
|
10866
|
+
const statCells = [`<span><em>全段均值</em><b>${mean.toFixed(0)}</b></span>`];
|
|
10867
|
+
if (!blocked && segAvg.length) {
|
|
10868
|
+
statCells.push(`<span><em>最好大运</em><b class="hi">${esc(segAvg[0].ganZhi)} ${segAvg[0].v.toFixed(0)}</b></span>`);
|
|
10869
|
+
statCells.push(`<span><em>最差大运</em><b class="lo">${esc(segAvg[segAvg.length - 1].ganZhi)} ${segAvg[segAvg.length - 1].v.toFixed(0)}</b></span>`);
|
|
10870
|
+
}
|
|
10871
|
+
statCells.push(`<span><em>波动幅度</em><b>${span.toFixed(0)}</b></span>`);
|
|
10872
|
+
const stats = `<div class="kl-stats">${statCells.join('')}</div>`;
|
|
10873
|
+
return `<section class="mp-kline" aria-label="人生K线结果">
|
|
10874
|
+
<div class="mp-kline-h"><b>人生 K 线</b><span>${esc(meta.birth || '')} 日主 ${esc(meta.dayMaster || '')} ${esc(st.level || '')}${st.score ? `(${st.score})` : ''}</span></div>
|
|
10875
|
+
${chart}
|
|
10876
|
+
${stats}
|
|
10877
|
+
${chips ? `<div class="kl-segs">${chips}</div>` : ''}
|
|
10878
|
+
${best || worst ? `<div class="kl-hls">${best}${worst}</div>` : ''}
|
|
10879
|
+
${note}
|
|
10880
|
+
<div class="mp-kline-tip">逐岁分数由大运与流年十神按固定表确定性计分(1–100),只表五行力量的顺逆,不预测具体事件;旺衰落在换档阈值附近时,不出高低年份结论。</div>
|
|
10881
|
+
</section>`;
|
|
10882
|
+
};
|
|
10883
|
+
|
|
10668
10884
|
pp.baziHTML = function (r) {
|
|
10669
10885
|
const cols = [['year', '年柱'], ['month', '月柱'], ['day', '日柱'], ['hour', '时柱']];
|
|
10670
10886
|
const SHORT = { year: '年', month: '月', day: '日', hour: '时' };
|
|
@@ -11326,6 +11542,19 @@ function App() {
|
|
|
11326
11542
|
}
|
|
11327
11543
|
}
|
|
11328
11544
|
const r = Engine.compute(args);
|
|
11545
|
+
let trend = null;
|
|
11546
|
+
if (k === 'bazi') {
|
|
11547
|
+
try {
|
|
11548
|
+
trend = Engine.compute({
|
|
11549
|
+
system: 'trend', datetime, gender: genderV, toAge: 100,
|
|
11550
|
+
trueSolar, lateZi,
|
|
11551
|
+
...(args.dst !== undefined ? { dst: args.dst } : {}),
|
|
11552
|
+
...(args.address ? { address: args.address } : {}),
|
|
11553
|
+
...(typeof args.longitude === 'number' ? { longitude: args.longitude } : {}),
|
|
11554
|
+
...(args.geo ? { geo: args.geo } : {}),
|
|
11555
|
+
});
|
|
11556
|
+
} catch (e) { trend = null; }
|
|
11557
|
+
}
|
|
11329
11558
|
const ask = (needAsk(k) || k === 'qimen')
|
|
11330
11559
|
? { question: needAsk(k) ? question.trim() : '', direction }
|
|
11331
11560
|
: undefined;
|
|
@@ -11334,7 +11563,7 @@ function App() {
|
|
|
11334
11563
|
it.sections = [...(it.sections || []), { key: 'crossSystem', label: '八字 × 紫微 交叉验证', text: Engine.crossText(r.cross) }];
|
|
11335
11564
|
}
|
|
11336
11565
|
const timed = kindOf(k) === 'cast' && (k !== 'meihua' || mh === 'time');
|
|
11337
|
-
setResult({ kind: k, r, it, at: dt, timed });
|
|
11566
|
+
setResult({ kind: k, r, it, at: dt, timed, trend });
|
|
11338
11567
|
setFx(null); setFit(null); setPastMarks({}); setRectRes(null);
|
|
11339
11568
|
setError(''); setCopied('');
|
|
11340
11569
|
if (k !== 'tarot') {
|
|
@@ -11761,14 +11990,15 @@ function App() {
|
|
|
11761
11990
|
dangerouslySetInnerHTML: {
|
|
11762
11991
|
__html: castAtHTML(result) + PP.resultWithInterpret(
|
|
11763
11992
|
result.kind === 'ziwei' ? PP.ziweiHTML(result.r)
|
|
11764
|
-
: result.kind === 'bazi' ? PP.baziHTML(result.r)
|
|
11993
|
+
: result.kind === 'bazi' ? (PP.baziHTML(result.r) + (result.trend ? PP.trendHTML(result.trend) : ''))
|
|
11765
11994
|
: result.kind === 'meihua' ? PP.meihuaHTML(result.r)
|
|
11766
11995
|
: result.kind === 'daliuren' ? PP.daliurenHTML(result.r)
|
|
11767
11996
|
: result.kind === 'qimen' ? (PP.qimenHTML(result.r) + PP.qimenFactsHTML(result.r))
|
|
11768
11997
|
: result.kind === 'hepan' ? (result.r.chart === 'ziwei' ? PP.ziweiHepanHTML(result.r) : PP.hepanHTML(result.r))
|
|
11769
11998
|
: result.kind === 'tarot' ? PP.tarotHTML(result.r)
|
|
11770
11999
|
: result.kind === 'liuyao' ? PP.liuyaoHTML(result.r)
|
|
11771
|
-
: PP.
|
|
12000
|
+
: result.kind === 'trend' ? PP.trendHTML(result.r)
|
|
12001
|
+
: PP.quickHTML(result.r),
|
|
11772
12002
|
result.it,
|
|
11773
12003
|
{ folded: defaultFolded() },
|
|
11774
12004
|
),
|
package/engine/geo.js
CHANGED
|
@@ -143,7 +143,7 @@ const DETAIL = /(区|县|镇|乡|村|小区|街道|路|号|巷|弄|组|队)/;
|
|
|
143
143
|
|
|
144
144
|
/** OpenStreetMap Nominatim:免密钥、支持中文。失败返回 null。
|
|
145
145
|
* UA 按 OSM 使用政策要求写明应用与联系方式(改版本时同步这里)。 */
|
|
146
|
-
const USER_AGENT = 'dsh-metaphysics/0.18.
|
|
146
|
+
const USER_AGENT = 'dsh-metaphysics/0.18.3 (+https://github.com/xuanfu-web/dsh-metaphysics)';
|
|
147
147
|
|
|
148
148
|
async function onlineLookup(text, { timeoutMs = 2500 } = {}) {
|
|
149
149
|
if (typeof fetch !== 'function') return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-metaphysics",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "命理排盘(玄学 xuanxue)for DeepSeek Harness: seventeen systems — eight chart systems (BaZi four pillars, Zi Wei Dou Shu, LiuYao, MeiHua, Da Liu Ren, Qi Men Dun Jia, compatibility charts, Tarot) plus nine quick references (almanac, date selection, bone-weight fortune, Xiao Liu Ren, horoscope, numerology, life-curve, feng shui, name analysis); ask-driven interpretation, report layer with evidence and confidence, DST correction for 1986–1991. All computed locally, networking off by default — as an agent tool plus a Web panel.",
|
|
6
6
|
"keywords": [
|