dsh-metaphysics 0.18.2 → 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 CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  版本按「一组可感知的改动」发,README 与市场条目描述随能力同步。
4
4
 
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
+
5
19
  ## 0.18.2(已发布)
6
20
 
7
21
  人生 K 线从「等宽文本 + ASCII」升级成真正的蜡烛图,并嵌进八字结果:
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 岁逐年分与最佳/最差年份;面板与独立页画的是**蜡烛图**(实体=相对上一岁的涨跌、影线=±4 分,叠 5/10 年均线、大运分界与最高/最低岁标记),并直接嵌在八字结果底部,与「人生运势曲线」(大运阶梯 + 流年折线)互补:曲线看趋势,K 线看逐年涨跌。旺衰正好落在换档阈值(±0.3)时只提示、不给顺位结论
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 条卦例对拍、58 个测试文件/1644 条断言、七道闸、口径与已知边界),离线可开
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.2 (+https://github.com/xuanfu-web/dsh-metaphysics)';
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;
@@ -9668,6 +9668,17 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
9668
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
9669
  .mp-kline .kl-lbl{fill:var(--muted);font-size:8px;font-family:system-ui,sans-serif}
9670
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)}
9671
9682
  .mp-kline .kl-c.up line{stroke:var(--ji);stroke-width:.8;opacity:.8}
9672
9683
  .mp-kline .kl-c.up rect{fill:var(--ji);opacity:.92}
9673
9684
  .mp-kline .kl-c.down line{stroke:var(--lu);stroke-width:.8;opacity:.8}
@@ -10705,13 +10716,14 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
10705
10716
  </div>`;
10706
10717
  };
10707
10718
 
10719
+ let klineSeq = 0;
10708
10720
  pp.trendChart = function (r) {
10709
10721
  const pts = r && Array.isArray(r.points) ? r.points : [];
10710
10722
  if (pts.length < 2) return '';
10711
10723
  const segs = r && Array.isArray(r.segments) ? r.segments : [];
10712
10724
  const num = (v) => (typeof v === 'number' && isFinite(v) ? v : 0);
10713
- const W = 720, H = 300;
10714
- const M = { t: 36, r: 14, b: 30, l: 34 };
10725
+ const W = 720, H = 322;
10726
+ const M = { t: 34, r: 46, b: 66, l: 36 };
10715
10727
  const pw = W - M.l - M.r, ph = H - M.t - M.b;
10716
10728
  const n = pts.length;
10717
10729
  const step = pw / n;
@@ -10719,6 +10731,9 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
10719
10731
  const Y = (v) => M.t + ph - (Math.max(0, Math.min(100, v)) / 100) * ph;
10720
10732
  const bodyW = Math.max(1.1, Math.min(10, step * 0.62));
10721
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)}`;
10722
10737
 
10723
10738
  const bands = segs.map((s, i) => {
10724
10739
  const i0 = Math.max(0, s.startAge - 1);
@@ -10726,11 +10741,15 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
10726
10741
  if (i1 < i0) return '';
10727
10742
  const x = M.l + i0 * step;
10728
10743
  const w = Math.max(0.6, (i1 - i0 + 1) * step);
10729
- const rect = i % 2 === 1
10730
- ? `<rect class="kl-band" x="${x.toFixed(1)}" y="${M.t}" width="${w.toFixed(1)}" height="${ph}"/>` : '';
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}"/>` : '';
10731
10748
  const line = i === 0 ? ''
10732
- : `<line class="kl-segline" x1="${x.toFixed(1)}" y1="${M.t}" x2="${x.toFixed(1)}" y2="${M.t + ph}"/>`;
10733
- return rect + line + `<text class="kl-seg" x="${(x + 3).toFixed(1)}" y="${M.t - 8}">${esc(s.ganZhi)}</text>`;
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>`;
10734
10753
  }).join('');
10735
10754
 
10736
10755
  const grid = [0, 25, 50, 75, 100].map((v) =>
@@ -10739,7 +10758,7 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
10739
10758
 
10740
10759
  const candles = pts.map((p, i) => {
10741
10760
  const k = p.kline || {};
10742
- const o = num(k.open), c = num(k.close), hi = num(k.high), lo = num(k.low);
10761
+ const o = num(k.open), c = num(k.close), hi = Math.min(100, num(k.high)), lo = Math.max(0, num(k.low));
10743
10762
  const up = c >= o;
10744
10763
  const yO = Y(o), yC = Y(c);
10745
10764
  const top = Math.min(yO, yC);
@@ -10751,16 +10770,35 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
10751
10770
  + `<rect x="${(x - bodyW / 2).toFixed(2)}" y="${top.toFixed(1)}" width="${bodyW.toFixed(2)}" height="${h.toFixed(1)}" rx="0.7"/></g>`;
10752
10771
  }).join('');
10753
10772
 
10754
- const maPath = (win) => {
10755
- const seg = [];
10773
+ const maSeries = (win) => {
10774
+ const out = [];
10756
10775
  for (let i = 0; i < n; i++) {
10757
10776
  if (i + 1 < win) continue;
10758
10777
  let sum = 0;
10759
10778
  for (let j = i + 1 - win; j <= i; j++) sum += sc(pts[j]);
10760
- seg.push(`${seg.length ? 'L' : 'M'}${X(i).toFixed(1)} ${Y(sum / win).toFixed(1)}`);
10779
+ out.push({ i, v: sum / win });
10761
10780
  }
10762
- return seg.join(' ');
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>` };
10763
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
+ })();
10764
10802
 
10765
10803
  let hiP = pts[0], loP = pts[0];
10766
10804
  for (const p of pts) {
@@ -10779,8 +10817,9 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
10779
10817
 
10780
10818
  const tickStep = n > 60 ? 10 : 5;
10781
10819
  const ages = pts.filter((p) => p.age % tickStep === 0).map((p) =>
10782
- `<text class="kl-lbl age" x="${X(pts.indexOf(p)).toFixed(1)}" y="${M.t + ph + 14}" text-anchor="middle">${p.age}</text>`).join('');
10783
- const xNote = `<text class="kl-lbl" x="${M.l + pw / 2}" y="${H - 4}" text-anchor="middle">年龄(岁)</text>`;
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>`;
10784
10823
 
10785
10824
  const legend = `<div class="kl-legend"><span><i class="up"></i>阳线:比上一岁高</span>`
10786
10825
  + `<span><i class="down"></i>阴线:比上一岁低</span>`
@@ -10789,8 +10828,13 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
10789
10828
 
10790
10829
  return `<div class="mp-kline-fig">
10791
10830
  <svg viewBox="0 0 ${W} ${H}" role="img" aria-label="人生K线:1 到 ${n} 岁的逐年评分蜡烛图">
10792
- ${bands}${grid}${candles}
10793
- <path class="kl-ma m10" d="${maPath(10)}"/><path class="kl-ma m5" d="${maPath(5)}"/>
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}
10794
10838
  ${mark(hiP, 'hi', -8)}${mark(loP, 'lo', 12)}
10795
10839
  ${ages}${xNote}
10796
10840
  </svg>${legend}</div>`;
@@ -10809,9 +10853,27 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
10809
10853
  const worst = (Array.isArray(hl.worst) ? hl.worst : []).map((x) => `<div class="kl-hl lo">${esc(x)}</div>`).join('');
10810
10854
  const note = meta.boundaryNote ? `<div class="mp-kline-note">${esc(meta.boundaryNote)}</div>`
10811
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>`;
10812
10873
  return `<section class="mp-kline" aria-label="人生K线结果">
10813
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>
10814
10875
  ${chart}
10876
+ ${stats}
10815
10877
  ${chips ? `<div class="kl-segs">${chips}</div>` : ''}
10816
10878
  ${best || worst ? `<div class="kl-hls">${best}${worst}</div>` : ''}
10817
10879
  ${note}
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.2 (+https://github.com/xuanfu-web/dsh-metaphysics)';
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.2",
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": [