dsh-math-calculator 2.2.0 → 2.3.1
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 +261 -0
- package/README.md +7 -7
- package/dsh.plugin.json +1 -1
- package/lib/format.js +52 -2
- package/lib/formula-library-bioecon.js +216 -0
- package/lib/formula-library-biol-general.js +175 -0
- package/lib/formula-library-engineering.js +384 -0
- package/lib/formula-library-macro-materials.js +452 -0
- package/lib/formulas.js +91 -6
- package/lib/help.js +552 -79
- package/lib/index.js +406 -5
- package/lib/ops/shared.js +23 -1
- package/lib/param-units.js +315 -0
- package/lib/types/formula-library-bioecon.d.ts +26 -0
- package/lib/types/formula-library-biol-general.d.ts +37 -0
- package/lib/types/formula-library-engineering.d.ts +34 -0
- package/lib/types/formula-library-macro-materials.d.ts +41 -0
- package/lib/types/formulas.d.ts +11 -1
- package/lib/types/help.d.ts +39 -0
- package/lib/types/param-units.d.ts +156 -0
- package/package.json +1 -1
- package/src/format.ts +52 -4
- package/src/formula-library-bioecon.ts +220 -0
- package/src/formula-library-biol-general.ts +179 -0
- package/src/formula-library-engineering.ts +399 -0
- package/src/formula-library-macro-materials.ts +476 -0
- package/src/formulas.ts +91 -8
- package/src/help.ts +593 -80
- package/src/index.ts +415 -5
- package/src/ops/shared.ts +22 -0
- package/src/param-units.ts +336 -0
- package/lib/__wtest.txt +0 -1
- package/lib/types/client.d.ts +0 -353
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 公式参数的**量纲校验**:把「位移 m」这类描述里的单位当成真单位来用。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么这一步值得做
|
|
5
|
+
*
|
|
6
|
+
* 公式库 442 条,每个参数的描述里其实**已经写着单位**:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* velocity_uniform: s → "位移 m" t → "时间 s"
|
|
10
|
+
* kinetic_energy: m → "质量 kg" v → "速度 m/s"
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* 这些单位以前只用于**显示**,不参与任何判断。于是:
|
|
14
|
+
*
|
|
15
|
+
* ```
|
|
16
|
+
* {formula:"velocity_uniform", overrides:{s:10, t:1}}
|
|
17
|
+
* → 10 m/s ← 数字对了,但没人能确认 s 给的是米而不是公里
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* 现在让调用方可以**带单位给参数**,工具负责校验与换算:
|
|
21
|
+
*
|
|
22
|
+
* ```
|
|
23
|
+
* overrides:{s:"10 m", t:"1 s"} → 10 m/s ✓
|
|
24
|
+
* overrides:{s:"10 km", t:"1 h"} → 2.7778 m/s ✓ 自动换算到 SI
|
|
25
|
+
* overrides:{s:"10 kg", t:"1 s"} → 量纲不符:s 是长度,kg 是质量 ✗
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* ## 一个必须守住的边界:**只校验,不猜**
|
|
29
|
+
*
|
|
30
|
+
* 裸数字照旧原样接受 —— 不假装能知道 `10` 是米还是公里。
|
|
31
|
+
* 单位只在调用方**主动写了**的时候才生效。这跟本项目一贯的取向一致:
|
|
32
|
+
* 把判据交给知道答案的一方,工具只负责比对与换算。
|
|
33
|
+
*
|
|
34
|
+
* @module dsh-math-calculator/param-units
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* 从参数描述里抠出单位符号:取描述里**最后一个像单位的片段**。
|
|
38
|
+
*
|
|
39
|
+
* 描述写得很自由("位移 m"、"时间 s"、"质量1 kg"、"半径 r / m"),
|
|
40
|
+
* 所以判据定为:末尾那个不含中文、不含空格的短片段,且必须是已知单位。
|
|
41
|
+
*
|
|
42
|
+
* 为什么不用正则硬猜:`"质量1 kg"` 里的 `1` 与 `m3`、`s^-1` 这些形态都会把
|
|
43
|
+
* 朴素正则带偏。先用「已知单位表」兜底,比先写好正则再补漏洞更稳。
|
|
44
|
+
*
|
|
45
|
+
* @param desc - 参数描述
|
|
46
|
+
* @param isKnownUnit - 判断某字符串是否为已知单位(由调用方注入,避免模块耦合)
|
|
47
|
+
* @returns 单位名;抠不出时返回空串
|
|
48
|
+
*/
|
|
49
|
+
export function unitFromDescription(desc, isKnownUnit) {
|
|
50
|
+
if (typeof desc !== 'string' || desc === '')
|
|
51
|
+
return '';
|
|
52
|
+
/**
|
|
53
|
+
* ## 顺序很重要:先试**整段尾部**,再逐级去掉前导文字
|
|
54
|
+
*
|
|
55
|
+
* 踩过:第一版按空白**与斜杠**切,于是 `"速度 m/s"` 被切成 `["速度", "m", "s"]`,
|
|
56
|
+
* 取末尾得到 `s` —— 把「速度」的声明单位判成了**时间**。
|
|
57
|
+
* 后果是 `kinetic_energy` 的 `v:"速度 m/s"` 变成秒,
|
|
58
|
+
* 于是 `m:{m:'2 t'}, v:{v:'10 m/s'}` 报「m/s 是速度,s 是时间,不能换算」。
|
|
59
|
+
*
|
|
60
|
+
* 所以先按**空白**切(斜杠是复合单位的一部分,不能单独切),
|
|
61
|
+
* 再从最长的尾部片段往短了试 —— `m/s` 会在更短的 `s` 之前被命中。
|
|
62
|
+
*/
|
|
63
|
+
const pieces = desc.split(/[\s ]+/).filter((item) => item !== '');
|
|
64
|
+
for (let index = pieces.length - 1; index >= 0; index -= 1) {
|
|
65
|
+
/** 从当前片段起,把后缀逐段拼接试(`m` + `/` + `s` 这类不切分也能整段命中)。 */
|
|
66
|
+
for (let take = pieces.length - index; take >= 1; take -= 1) {
|
|
67
|
+
const candidate = cleanPiece(pieces.slice(index, index + take).join(' ').replace(/\s+/g, ''));
|
|
68
|
+
if (candidate !== '' && isKnownUnit(candidate))
|
|
69
|
+
return candidate;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
/** 去掉包裹符号与数字尾缀,得到干净的单位候选。 */
|
|
75
|
+
function cleanPiece(piece) {
|
|
76
|
+
return piece
|
|
77
|
+
.replace(/^[((\[【]+|[))\]】。,,;;::]+$/g, '')
|
|
78
|
+
.replace(/[。,,;;::]+$/g, '')
|
|
79
|
+
.trim();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 解析一个「带单位」参数值。
|
|
83
|
+
*
|
|
84
|
+
* @param raw - 调用方传的值(`"10 m"`、`"10 km"`、或裸数字)
|
|
85
|
+
* @param declaredUnit - 参数描述里声明的单位(如 `m`);为空表示该参数没有单位约定
|
|
86
|
+
* @param convertUnit - 换算函数(由调用方注入,签名对齐 `units.ts` 的 convert)
|
|
87
|
+
* @returns 解析结果;失败时返回 `{ error, hint }`
|
|
88
|
+
*/
|
|
89
|
+
export function parseParamValue(raw, declaredUnit, convertUnit) {
|
|
90
|
+
/**
|
|
91
|
+
* 裸数字(含数字字符串):原样接受,不做任何量纲推断。
|
|
92
|
+
*
|
|
93
|
+
* 这是本模块最重要的一条边界 —— 不知道的事就不猜。
|
|
94
|
+
* `overrides:{s:10}` 里的 10 到底是米还是公里,只有调用方知道。
|
|
95
|
+
*/
|
|
96
|
+
const asNumber = typeof raw === 'number' ? raw : Number(String(raw ?? '').trim());
|
|
97
|
+
if (Number.isFinite(asNumber)) {
|
|
98
|
+
return { value: asNumber };
|
|
99
|
+
}
|
|
100
|
+
const text = String(raw ?? '').trim();
|
|
101
|
+
if (text === '') {
|
|
102
|
+
return { error: '空值', hint: '给一个数字,或「数值 + 单位」如 "10 m"。' };
|
|
103
|
+
}
|
|
104
|
+
/** 拆成「数值 余下部分」:数值可带符号、小数点、指数。 */
|
|
105
|
+
const match = text.match(/^([+-]?[0-9][0-9.]*(?:[eE][+-]?[0-9]+)?)\s*(.+)$/);
|
|
106
|
+
if (!match) {
|
|
107
|
+
return {
|
|
108
|
+
error: '不是「数值 + 单位」的形态: ' + text,
|
|
109
|
+
hint: '写法是 "10 m"、"30 里"、"2 kg" —— 数字在前、单位在后。',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const numeric = Number(match[1]);
|
|
113
|
+
const givenUnit = match[2].trim();
|
|
114
|
+
if (!Number.isFinite(numeric)) {
|
|
115
|
+
return { error: '数值部分不是有效数字: ' + match[1], hint: '检查是否多打了小数点。' };
|
|
116
|
+
}
|
|
117
|
+
if (!Number.isFinite(numeric))
|
|
118
|
+
return { error: '数值部分无效', hint: '' };
|
|
119
|
+
/**
|
|
120
|
+
* 参数**没有**声明单位:不擅自判定调用方写的单位对不对。
|
|
121
|
+
*
|
|
122
|
+
* 例如某些公式的参数是纯数(个数、比例、无量纲系数),
|
|
123
|
+
* 那时写了单位多半是误用 —— 但也可能是我们没登记。折中:只剥掉数值,
|
|
124
|
+
* 并在调用方(opFormula)那边决定要不要提示。
|
|
125
|
+
*/
|
|
126
|
+
if (declaredUnit === '') {
|
|
127
|
+
return { value: numeric, givenUnit };
|
|
128
|
+
}
|
|
129
|
+
/** 单位与声明一致:直接用。 */
|
|
130
|
+
if (givenUnit === declaredUnit) {
|
|
131
|
+
return { value: numeric, givenUnit };
|
|
132
|
+
}
|
|
133
|
+
/** 其它情况交给单位表换算 —— 它会同时完成「是否同量纲」的判定。 */
|
|
134
|
+
const converted = convertUnit(numeric, givenUnit, declaredUnit);
|
|
135
|
+
if (!converted.ok) {
|
|
136
|
+
return {
|
|
137
|
+
error: converted.error ?? '无法换算',
|
|
138
|
+
hint: (converted.hint ?? '') +
|
|
139
|
+
' 这个参数声明的是「' + declaredUnit + '」,请按该量纲给值,或直接给裸数字。',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return { value: Number(converted.value), givenUnit, converted: true };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 中文传统单位族:这些单位一旦出现在输入里,输出就该照顾同一体系。
|
|
146
|
+
*
|
|
147
|
+
* ## 为什么需要它(用户 2026-11-07 直接要求)
|
|
148
|
+
*
|
|
149
|
+
* 用户原话:「最好使用同一体系单位,把 60 km 折算成 里。」
|
|
150
|
+
*
|
|
151
|
+
* ```
|
|
152
|
+
* 输入:V='30 里/h', T='2 时辰' → 60 km
|
|
153
|
+
* 用户要的:60 km ≈ 60 里(因为 60 km ÷ 1 m 尺度…)
|
|
154
|
+
* ```
|
|
155
|
+
*
|
|
156
|
+
* 这不是计算问题 —— 数值本来就对。问题是**输出换了体系**:
|
|
157
|
+
* 用里和时辰下的订单,收据开了公里和小时。作者写正文时得自己再换一遍,
|
|
158
|
+
* 而每换一次就多一次出错机会(这正是本项目一直在对付的那类错误)。
|
|
159
|
+
*
|
|
160
|
+
* ## 收哪些族
|
|
161
|
+
*
|
|
162
|
+
* 只收**有稳定换算系数**的传统单位族。不收「一箭之地」「一盏茶工夫」
|
|
163
|
+
* 这类需要上下文的 —— 见 units-data 的收录原则。
|
|
164
|
+
*/
|
|
165
|
+
export const TRADITIONAL_FAMILIES = {
|
|
166
|
+
/** 长度:里/丈/尺/寸/步(1 里 = 500 m,其余见 units-data) */
|
|
167
|
+
length: ['里', '丈', '尺', '寸', '步'],
|
|
168
|
+
/** 质量:斤/两/石/担 */
|
|
169
|
+
mass: ['斤', '两', '石', '担'],
|
|
170
|
+
/** 时间:时辰/刻/更 */
|
|
171
|
+
time: ['时辰', '刻', '更'],
|
|
172
|
+
/** 面积:亩/顷 */
|
|
173
|
+
area: ['亩', '顷'],
|
|
174
|
+
/** 体积:斗/升(升与 SI 同名,需靠语境区分,这里只收斗) */
|
|
175
|
+
volume: ['斗'],
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* 判断一批输入的参数用了哪些传统单位族。
|
|
179
|
+
*
|
|
180
|
+
* @param units - 输入里出现过的单位名(如 `['里', '时辰']`)
|
|
181
|
+
* @returns 命中的量纲键(如 `['length', 'time']`)
|
|
182
|
+
*/
|
|
183
|
+
export function familiesOfUnits(units) {
|
|
184
|
+
const hit = [];
|
|
185
|
+
for (const unit of units) {
|
|
186
|
+
for (const [dimension, members] of Object.entries(TRADITIONAL_FAMILIES)) {
|
|
187
|
+
if (members.includes(unit) && !hit.includes(dimension))
|
|
188
|
+
hit.push(dimension);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return hit;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 把 SI 结果**按输入的传统单位族折算一遍**,作为附注。
|
|
195
|
+
*
|
|
196
|
+
* ## 用户原话
|
|
197
|
+
*
|
|
198
|
+
* 「最好使用同一体系单位,把 60 km 折算成 里。」
|
|
199
|
+
*
|
|
200
|
+
* ## 为什么做成附注而不是替换主结果
|
|
201
|
+
*
|
|
202
|
+
* 主结果必须保持 SI —— 那是公式的参数约定,也是与其它计算对接的口径。
|
|
203
|
+
* 而作者写正文时用的是里/丈/时辰,所以**两种都要给**:
|
|
204
|
+
*
|
|
205
|
+
* ```
|
|
206
|
+
* 结果: 2.08333333 m/s
|
|
207
|
+
* 【同体系折算】约 2.25 里/时辰(你的输入用了 里、时辰)
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* 替换掉主结果会破坏「公式内部一律 SI」这条约定;只给 SI 又要作者自己换算,
|
|
211
|
+
* 而每换一次就多一次出错机会。附注是唯一不牺牲任何一边的做法。
|
|
212
|
+
*
|
|
213
|
+
* @param siText - 已格式化的 SI 结果文本(如 `2.08333333 m/s`)
|
|
214
|
+
* @param families - 输入里用到的传统单位族(见 `familiesOfUnits`)
|
|
215
|
+
* @param convert - 换算函数(注入以避免模块耦合)
|
|
216
|
+
* @returns 折算附注行;不需要时为 null
|
|
217
|
+
*/
|
|
218
|
+
export function traditionalNote(siText, families, convert) {
|
|
219
|
+
if (families.length === 0)
|
|
220
|
+
return null;
|
|
221
|
+
/** 只处理「数值 + 简单单位」—— 复合单位(m/s、kg·m/s)的量纲换算见下。 */
|
|
222
|
+
const match = siText.trim().match(/^(-?[0-9][0-9.]*(?:[eE][+-]?[0-9]+)?|[-+]?[0-9.]+(?:e[+-]?[0-9]+)?)\s+(\S+)$/i);
|
|
223
|
+
if (!match) {
|
|
224
|
+
/**
|
|
225
|
+
* 复合单位:`m/s`、`kg·m/s` 这类没法直接换成单个传统单位。
|
|
226
|
+
* 但作者真正关心的是「速度」这个量在传统体系里怎么表达 ——
|
|
227
|
+
* 而传统时间族里 时辰 = 2 h、长度族里 里 = 500 m。
|
|
228
|
+
* 所以把分子分母分别折算,拼出「里/时辰」这种写法。
|
|
229
|
+
*/
|
|
230
|
+
return compoundTraditionalNote(siText, families, convert);
|
|
231
|
+
}
|
|
232
|
+
const numeric = Number(match[1]);
|
|
233
|
+
const unit = match[2];
|
|
234
|
+
if (!Number.isFinite(numeric))
|
|
235
|
+
return null;
|
|
236
|
+
/**
|
|
237
|
+
* ★ 复合单位(`m/s`)会**先被简单式匹配到**(`\S+` 收得住斜杠)——
|
|
238
|
+
* 但单纯的量纲换算换不了它,于是旧版在这里 continue 到头、return null,
|
|
239
|
+
* 压根走不到下面的复合分支。踩过一次,症状是速度永远折不出里/时辰。
|
|
240
|
+
*/
|
|
241
|
+
if (/[\/·*^]/.test(unit))
|
|
242
|
+
return compoundTraditionalNote(siText, families, convert);
|
|
243
|
+
for (const dimension of families) {
|
|
244
|
+
const target = TRADITIONAL_PREFERRED[dimension];
|
|
245
|
+
if (!target)
|
|
246
|
+
continue;
|
|
247
|
+
const result = convert(numeric, unit, target);
|
|
248
|
+
if (!result.ok || result.value === undefined)
|
|
249
|
+
continue;
|
|
250
|
+
return '【同体系折算】约 ' + fmt(result.value) + ' ' + target + '(你的输入用了传统单位)';
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
/** 每个量纲在「同体系折算」里用哪个传统单位(挑最常用的那个)。 */
|
|
255
|
+
const TRADITIONAL_PREFERRED = {
|
|
256
|
+
length: '里',
|
|
257
|
+
mass: '斤',
|
|
258
|
+
time: '时辰',
|
|
259
|
+
area: '亩',
|
|
260
|
+
volume: '斗',
|
|
261
|
+
};
|
|
262
|
+
/** 复合单位(速度/加速度等)的传统体系写法。 */
|
|
263
|
+
function compoundTraditionalNote(siText, families, convert) {
|
|
264
|
+
const text = siText.trim();
|
|
265
|
+
/** 形如 `2.08 m/s`、`9.81 m/s^2`、`10 kg / s` 的复合单位。 */
|
|
266
|
+
const compound = text.match(/^([-+]?[0-9.]+(?:e[+-]?[0-9]+)?)\s+([A-Za-z]+)\s*\/\s*([A-Za-z]+)(?:\^([0-9]+))?$/);
|
|
267
|
+
if (!compound)
|
|
268
|
+
return null;
|
|
269
|
+
const numeric = Number(compound[1]);
|
|
270
|
+
const numerator = compound[2];
|
|
271
|
+
const denominator = compound[3];
|
|
272
|
+
const power = compound[4] ? Number(compound[4]) : 1;
|
|
273
|
+
if (!Number.isFinite(numeric))
|
|
274
|
+
return null;
|
|
275
|
+
const hasLength = families.includes('length');
|
|
276
|
+
const hasTime = families.includes('time');
|
|
277
|
+
if (!hasLength || !hasTime)
|
|
278
|
+
return null;
|
|
279
|
+
/** 分子折成里、分母折成时辰,得到「里/时辰」量。 */
|
|
280
|
+
const up = convert(numeric, numerator, '里');
|
|
281
|
+
const un = convert(1, denominator, '时辰');
|
|
282
|
+
if (!up.ok || up.value === undefined || !un.ok || un.value === undefined)
|
|
283
|
+
return null;
|
|
284
|
+
/** value = x 里 / (y 时辰) = (x / y) 里每时辰;power>1 时再取幂。 */
|
|
285
|
+
const perHourLike = up.value / Math.pow(un.value, power);
|
|
286
|
+
return '【同体系折算】约 ' + fmt(perHourLike) + ' 里/时辰' +
|
|
287
|
+
(power !== 1 ? '^' + power : '') + '(你的输入用了 里、时辰)';
|
|
288
|
+
}
|
|
289
|
+
/** 折算结果的位数:保留 4 位有效数字,够看又不啰嗦。 */
|
|
290
|
+
function fmt(value) {
|
|
291
|
+
if (!Number.isFinite(value))
|
|
292
|
+
return '—';
|
|
293
|
+
if (value === 0)
|
|
294
|
+
return '0';
|
|
295
|
+
const abs = Math.abs(value);
|
|
296
|
+
if (abs >= 1e6 || abs < 1e-3)
|
|
297
|
+
return value.toExponential(3);
|
|
298
|
+
return String(Number(value.toPrecision(4)));
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* 默认换算适配器:直接接 `units.ts` 的 `convert`。
|
|
302
|
+
*
|
|
303
|
+
* 为什么放在这里而不是让每个调用方自己写一遍:
|
|
304
|
+
* `convert` 返回的三层结构(`{ok, conversion:{result}}`)很容易读错字段 ——
|
|
305
|
+
* 本项目已经踩过一次(读 `conversion.value` 拿到 undefined)。
|
|
306
|
+
* 集中一处,读错了只改一个地方。
|
|
307
|
+
*/
|
|
308
|
+
export function makeConverter(convert) {
|
|
309
|
+
return function (value, from, to) {
|
|
310
|
+
const result = convert(value, from, to);
|
|
311
|
+
if (!result.ok)
|
|
312
|
+
return { ok: false, error: result.error, hint: result.hint };
|
|
313
|
+
return { ok: true, value: result.conversion.result };
|
|
314
|
+
};
|
|
315
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 公式库第十批:**生物与宏观经济**的运行式子。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么这两块一起补
|
|
5
|
+
*
|
|
6
|
+
* 生物类原有 26 条,但偏向**医学剂量**(体重给药、输液速率、血液酒精);
|
|
7
|
+
* 经济类原有 26 条,几乎全是**金融**(复利、年金、NPV、CAGR)。
|
|
8
|
+
*
|
|
9
|
+
* 而写世界观真正会问的是另两类问题:
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* 生物:这片草原能养多少头牛?这窝猪多久出栏?这个生态能支撑多少顶级捕食者?
|
|
13
|
+
* 经济:这个国家一年产多少?劳动力有多少?投资能带来多少增长?钱发多了会怎样?
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* 这一批补的就是这些**运行层面**的式子,而不是又一批财务指标。
|
|
17
|
+
*
|
|
18
|
+
* ## 收录判据
|
|
19
|
+
*
|
|
20
|
+
* 同既有几批:闭合式、带量纲、有出处、不重复。经验系数标出取值范围。
|
|
21
|
+
*
|
|
22
|
+
* @module dsh-math-calculator/formula-library-bioecon
|
|
23
|
+
*/
|
|
24
|
+
import type { FormulaDef } from './formula-library.js';
|
|
25
|
+
/** 生物与宏观 11 条:生态承载、畜禽养殖、人口劳动力、货币与通胀。 */
|
|
26
|
+
export declare const BIOECON_FORMULAS: FormulaDef[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 公式库第十二批:**生物学的通用公式(跨类群/跨尺度/跨世界)**。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么这一批值得单独成文件
|
|
5
|
+
*
|
|
6
|
+
* 库里原有 31 条生物公式,但**14 条是人类医学/毒理特化**:
|
|
7
|
+
* 血液酒精浓度、儿童剂量、孕期剩余天数、LD50、输液速率 ——
|
|
8
|
+
* 这些对「9000 吨工程生物」这类设定毫无用处,且参数标定与人类解剖强绑定。
|
|
9
|
+
*
|
|
10
|
+
* 这里补的是**任何生命形式都逃不掉的那一层**,判据五条全过:
|
|
11
|
+
*
|
|
12
|
+
* ```
|
|
13
|
+
* ① 跨类群 不依赖人类解剖
|
|
14
|
+
* ② 跨尺度 微生物到鲸,跨 10 个数量级
|
|
15
|
+
* ③ 跨世界 常数显式暴露,形式与地球无关
|
|
16
|
+
* ④ 有根源 物理/几何/化学/数学,不是拟合的经验系数
|
|
17
|
+
* ⑤ 参数可得 不需要查专业表就能填
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* 第 ⑤ 条最易忽略:**通用幂律数学上能算任何东西,但用户不知道指数填多少,它就成了空壳**。
|
|
21
|
+
* 所以本批凡是幂律类,指数都**内置理论值**并注明出处,不留给用户填。
|
|
22
|
+
*
|
|
23
|
+
* ## 指数从哪来(分清「几何必然」与「网络理论推论」)
|
|
24
|
+
*
|
|
25
|
+
* 这个区分很重要 —— 前者在任何世界都成立(含非碳基),后者依赖「存在分形输运系统」这个前提:
|
|
26
|
+
*
|
|
27
|
+
* ```
|
|
28
|
+
* 2/3 ← 面积/体积的纯几何(无需任何生物假设)
|
|
29
|
+
* 1/4 ← 分形输运网络的最优层级(West-Brown-Enquist)
|
|
30
|
+
* 3/4 ← 分形网络的能量通量(Damuth)
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @module dsh-math-calculator/formula-library-biol-general
|
|
34
|
+
*/
|
|
35
|
+
import type { FormulaDef } from './formula-library.js';
|
|
36
|
+
/** 生物通用公式 7 条。 */
|
|
37
|
+
export declare const BIO_GENERAL_FORMULAS: FormulaDef[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 公式库第九批:**工程 / 生物 / 宏观经济**的常用计算。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么单独开一批
|
|
5
|
+
*
|
|
6
|
+
* 公式库原有 442 条,分布很不均:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* physics 187 条
|
|
10
|
+
* biology 26 条(偏医学剂量,生态与生理偏少)
|
|
11
|
+
* economics 26 条(几乎全是金融/投资)
|
|
12
|
+
* engineering 3 条(只有齿轮、液压、皮带——全是机械传动)
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* 作者写「这座塔能不能立住」「这个蒸汽机出多少力」「这个国家的钱够不够花」时,
|
|
16
|
+
* 前两类几乎找不到能直接用的式子,只能自己拼。这一批补的就是这三块。
|
|
17
|
+
*
|
|
18
|
+
* ## 收录判据(与既有几批一致)
|
|
19
|
+
*
|
|
20
|
+
* · **闭合式、单次求值** —— 参数现实可得,不需要解 ODE 或查外部数据表
|
|
21
|
+
* · **带量纲** —— 参数说明里写明单位,能参与带单位计算
|
|
22
|
+
* · **有工程出处** —— 经验系数标出取值范围,不编造
|
|
23
|
+
* · **不重复已有条目** —— 已存在的式子(齿轮传动比、欧拉屈曲、CAGR)不重收
|
|
24
|
+
*
|
|
25
|
+
* ## 单位约定
|
|
26
|
+
*
|
|
27
|
+
* 一律 **SI 基本单位**(m / kg / s / J / W / Pa / N),
|
|
28
|
+
* 但保留行业惯用单位(MPa、rpm、kW·h、元)并在参数说明里写清。
|
|
29
|
+
*
|
|
30
|
+
* @module dsh-math-calculator/formula-library-engineering
|
|
31
|
+
*/
|
|
32
|
+
import type { FormulaDef } from './formula-library.js';
|
|
33
|
+
/** 工程类 18 条:结构承重、热机与流体的常用算式。 */
|
|
34
|
+
export declare const ENGINEERING_FORMULAS: FormulaDef[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 公式库第十一批:**宏观经济的恒等式链 + 材料本构/微观决定**。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么这一批和前面几批不同
|
|
5
|
+
*
|
|
6
|
+
* 前面几批(工程/生物/宏观基础)多是**经验式或结构式**,需要标
|
|
7
|
+
* 「这是量级估计」「系数需标定」。这一批则以**恒等式与严格推导**为主:
|
|
8
|
+
*
|
|
9
|
+
* ```
|
|
10
|
+
* 财政赤字 = 支出 − 收入 ← 会计恒等式,任何政权都逃不掉
|
|
11
|
+
* PB = T − (G − 利息) ← 定义
|
|
12
|
+
* Y = C + I + G + NX ← 会计恒等式
|
|
13
|
+
* σ = E·ε ← 本构公理
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* 恒等式的价值在于**能立刻否掉不闭合的设定** —— 作者编的一组数字若违反它,
|
|
17
|
+
* 那不是「另一种经济学」,而是算术错了。
|
|
18
|
+
*
|
|
19
|
+
* ## 可信度分层(本批特别注意)
|
|
20
|
+
*
|
|
21
|
+
* 同一批里混着四种可信度,desc 里都要如实体现:
|
|
22
|
+
*
|
|
23
|
+
* ```
|
|
24
|
+
* 恒等式/定义/基本常数 → 精确 财政赤字、PB、应力应变
|
|
25
|
+
* 标度律 → 标度 熔点∝结合能、理论强度
|
|
26
|
+
* 经验判据 → 量级 Pugh G/K、Hall-Petch
|
|
27
|
+
* 只有趋势没有系数 → 仅方向 理想强度 ≈ E/10
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* 混为一谈会把「量级估计」当精确值用,那比不给结论更糟。
|
|
31
|
+
*
|
|
32
|
+
* ## 单位约定
|
|
33
|
+
*
|
|
34
|
+
* 财政与宏观类多为**比值/无量纲**(占 GDP 百分比、倍率),
|
|
35
|
+
* 材料类为 SI(Pa、m、K)。两者都在 desc 里写明口径。
|
|
36
|
+
*
|
|
37
|
+
* @module dsh-math-calculator/formula-library-macro-materials
|
|
38
|
+
*/
|
|
39
|
+
import type { FormulaDef } from './formula-library.js';
|
|
40
|
+
/** 宏观与财政 9 条、材料 8 条,共 17 条。 */
|
|
41
|
+
export declare const MACRO_MATERIAL_FORMULAS: FormulaDef[];
|
package/lib/types/formulas.d.ts
CHANGED
|
@@ -34,7 +34,9 @@ export type Overrides = Record<string, number | string | number[]>;
|
|
|
34
34
|
export declare class FormulaError extends Error {
|
|
35
35
|
readonly type: string;
|
|
36
36
|
readonly details?: string[];
|
|
37
|
-
|
|
37
|
+
/** 修复方向(给调用方看的下一步,与 CalcError 的 hint 同一语义)。 */
|
|
38
|
+
readonly hint?: string;
|
|
39
|
+
constructor(type: string, message: string, details?: string[], hint?: string);
|
|
38
40
|
}
|
|
39
41
|
/**
|
|
40
42
|
* 解析公式的参数:默认值 → 常量表 → 调用方覆盖。
|
|
@@ -42,6 +44,8 @@ export declare class FormulaError extends Error {
|
|
|
42
44
|
* 优先级:调用方 overrides > 公式自带默认值。
|
|
43
45
|
* 数组型参数(如熵的 p、HHI 的 s)允许传数组或数组表达式字符串,
|
|
44
46
|
* 原样交给 mathjs 求值,不做数字转换。
|
|
47
|
+
*
|
|
48
|
+
* 带单位的值(`"10 km"`)会先校验量纲、换算到该参数声明的单位,再进来。
|
|
45
49
|
*/
|
|
46
50
|
export declare function resolveParams(formula: FormulaDef, overrides: Overrides, extraConstants?: Record<string, number | string>): {
|
|
47
51
|
values: Record<string, unknown>;
|
|
@@ -49,6 +53,12 @@ export declare function resolveParams(formula: FormulaDef, overrides: Overrides,
|
|
|
49
53
|
missing: string[];
|
|
50
54
|
/** 传了但公式里没有的参数名 —— 会被忽略,必须上报。 */
|
|
51
55
|
unknownKeys: string[];
|
|
56
|
+
/** 做过单位换算的参数(如 10 km → 10000 m),要在结果里说明。 */
|
|
57
|
+
converted: Record<string, {
|
|
58
|
+
from: string;
|
|
59
|
+
to: string;
|
|
60
|
+
original: number | null;
|
|
61
|
+
}>;
|
|
52
62
|
};
|
|
53
63
|
/**
|
|
54
64
|
* 把参数表拼成 mathjs 可用的 scope。
|
package/lib/types/help.d.ts
CHANGED
|
@@ -50,6 +50,45 @@ export interface HelpTopic {
|
|
|
50
50
|
summary: string;
|
|
51
51
|
/** 常见别名,便于用直觉词命中也(如「概率」命中 dist)。 */
|
|
52
52
|
aliases?: string[];
|
|
53
|
+
/**
|
|
54
|
+
* 频次档:`core` = 几乎每次都会用到,目录里占一行带简介;
|
|
55
|
+
* `extra` = 低频,折叠成一行只给 key + 极短标签。
|
|
56
|
+
*
|
|
57
|
+
* ## 为什么要分档(用户 2026-11-07 要求)
|
|
58
|
+
*
|
|
59
|
+
* 「加减乘除高频其余低频可以折叠」——目录每轮都要付费,
|
|
60
|
+
* 而 12 节里真正高频的是 evaluate / units / formula 这几节。
|
|
61
|
+
* 把低频节压成一行,省下的字给高频节写更详细的正文。
|
|
62
|
+
*
|
|
63
|
+
* 折叠不等于藏起来:key 与极短标签仍然列在目录里,
|
|
64
|
+
* 只是不再为它们付「一句话简介 + 别名清单」的成本。
|
|
65
|
+
*/
|
|
66
|
+
tier: 'core' | 'extra';
|
|
67
|
+
/** 折叠档用的极短标签(≤8 字,目录里跟 key 同行)。 */
|
|
68
|
+
short?: string;
|
|
69
|
+
/**
|
|
70
|
+
* 实测使用频率(越大越高频)。数据来自「实践文档」里各能力被提到的次数。
|
|
71
|
+
*
|
|
72
|
+
* ## 为什么用实测而不是凭直觉分档(用户 2026-11-07 要求)
|
|
73
|
+
*
|
|
74
|
+
* 「高低频根据目前的实践文档进行统计频率来调整显示」——
|
|
75
|
+
* 我原先的分档是凭印象定的(evaluate/units/formula/world 四节 core),
|
|
76
|
+
* 而实测信号量是:
|
|
77
|
+
*
|
|
78
|
+
* ```
|
|
79
|
+
* evaluate 250 units 173 solve 59 formula 28
|
|
80
|
+
* workspace 28 world 26 derivation 21 recipe 17
|
|
81
|
+
* matrix 11 stats 11 constants 5 balance 5 ode 4
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* 与直觉的两处出入值得记:
|
|
85
|
+
* · `solve` 59 排第三,却原来被折在 evaluate 里没有独立位置;
|
|
86
|
+
* · `workspace` 28 与 formula 并列,凭印象时被我低估了。
|
|
87
|
+
*
|
|
88
|
+
* 这个数只用于**排序与分档参考**,不直接决定显示 ——
|
|
89
|
+
* 分档还要看「它是不是一个独立操作」,例如 workspace 虽高频但不该占 core 位置。
|
|
90
|
+
*/
|
|
91
|
+
weight?: number;
|
|
53
92
|
/** 该节正文。 */
|
|
54
93
|
body: () => string;
|
|
55
94
|
}
|