@crossworks/content-core 0.230.43

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.
Files changed (44) hide show
  1. package/LICENSE.md +135 -0
  2. package/package.json +41 -0
  3. package/src/block-diff.test.ts +190 -0
  4. package/src/block-diff.ts +163 -0
  5. package/src/block-ids.test.ts +358 -0
  6. package/src/block-ids.ts +242 -0
  7. package/src/block-list.test.ts +241 -0
  8. package/src/block-list.ts +177 -0
  9. package/src/contacts-format.ts +260 -0
  10. package/src/doc-to-markdown.test.ts +194 -0
  11. package/src/doc-to-markdown.ts +315 -0
  12. package/src/formula-dimensions.test.ts +103 -0
  13. package/src/formula-dimensions.ts +231 -0
  14. package/src/formula-eval.ts +294 -0
  15. package/src/formula-seed.test.ts +175 -0
  16. package/src/formula-seed.ts +466 -0
  17. package/src/formula-signature.test.ts +336 -0
  18. package/src/formula-signature.ts +435 -0
  19. package/src/formula-spec.test.ts +458 -0
  20. package/src/formula-spec.ts +566 -0
  21. package/src/journal-options.test.ts +57 -0
  22. package/src/journal-options.ts +77 -0
  23. package/src/markdown-refs.test.ts +143 -0
  24. package/src/markdown-refs.ts +172 -0
  25. package/src/markdown-to-doc.test.ts +179 -0
  26. package/src/markdown-to-doc.ts +567 -0
  27. package/src/onboarding-questions.test.ts +75 -0
  28. package/src/onboarding-questions.ts +90 -0
  29. package/src/page-diff.test.ts +82 -0
  30. package/src/page-diff.ts +120 -0
  31. package/src/page-split.test.ts +141 -0
  32. package/src/page-split.ts +128 -0
  33. package/src/page-toc.test.ts +58 -0
  34. package/src/page-toc.ts +89 -0
  35. package/src/persona-bank.test.ts +67 -0
  36. package/src/persona-bank.ts +234 -0
  37. package/src/table-formula-mathjs.ts +259 -0
  38. package/src/table-formula.test.ts +157 -0
  39. package/src/table-formula.ts +496 -0
  40. package/src/table-model.test.ts +429 -0
  41. package/src/table-model.ts +870 -0
  42. package/src/thinking-tiers.ts +56 -0
  43. package/tsconfig.json +4 -0
  44. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,466 @@
1
+ /**
2
+ * The instructional seed set — five formulas whose job is to TEACH THE SPEC
3
+ * FORMAT BY EXAMPLE.
4
+ *
5
+ * The formula bank is owner-derived: a brain fills with the calculations its
6
+ * owner's work actually needs, and shipping a library nobody asked for would be
7
+ * clutter. What ships is exactly five widely-known models, chosen so that
8
+ * between them every part of the spec appears at least once — a plain
9
+ * expression, derived variables, a piecewise branch, a lookup with declared
10
+ * domains, a classification, `latex`, units on everything, `notes`, and one
11
+ * deliberately `unverified` equation that exists to show what the warning looks
12
+ * like. They double as the regression suite: each carries worked examples with
13
+ * expected values, so a change to the evaluator that breaks real arithmetic
14
+ * fails a test rather than a live assessment.
15
+ *
16
+ * ⚠ COPYRIGHT. This repo is public. Equations are facts; a standard's TABLES
17
+ * AND PROSE are not. Nothing here is transcribed from an API, ASME or ISO
18
+ * document — every entry cites open textbooks or public physical constants.
19
+ * Standard-derived models stay per-brain, authored on site by the mathematician
20
+ * from the operator's own licensed copy.
21
+ *
22
+ * TypeScript rather than the YAML the plan sketched, for one reason: this
23
+ * package is deliberately parser-free (see formula-spec.ts) so it runs
24
+ * unchanged in tool handlers, the API and the browser. A YAML seed would drag a
25
+ * parser into all three. The annotated YAML that teaches the *file* format
26
+ * lives where an author meets it — the editor's template picker.
27
+ */
28
+ import type { FormulaValue } from './formula-spec';
29
+
30
+ export type SeedExample = {
31
+ target: string;
32
+ inputs: Record<string, FormulaValue>;
33
+ /** Expected value, checked to `tolerance` (these are floating-point). */
34
+ expected: number;
35
+ tolerance?: number;
36
+ };
37
+
38
+ export type SeedFormula = {
39
+ /** Matches `spec.id`; used to detect "already present" when seeding. */
40
+ slug: string;
41
+ title: string;
42
+ tags: string[];
43
+ spec: Record<string, unknown>;
44
+ examples: SeedExample[];
45
+ };
46
+
47
+ /** Every seeded formula carries this, so the owner can find — and clear — the
48
+ * set as a group. Deleting them is an owner decision the reconcile respects. */
49
+ export const SEED_TAG = 'instructional';
50
+
51
+ const idealGas: SeedFormula = {
52
+ slug: 'ideal-gas-density',
53
+ title: 'Ideal gas density',
54
+ tags: [SEED_TAG, 'thermodynamics'],
55
+ spec: {
56
+ id: 'ideal-gas-density',
57
+ name: 'Ideal gas density',
58
+ source: {
59
+ standard: 'Ideal gas law (any thermodynamics text)',
60
+ edition: 'n/a — a defining relation, not an edition-bound clause',
61
+ },
62
+ unitSystem: 'SI',
63
+ variables: [
64
+ { symbol: 'P', name: 'Absolute pressure', unit: 'Pa', role: 'input' },
65
+ { symbol: 'M', name: 'Molar mass', unit: 'kg/mol', role: 'input' },
66
+ { symbol: 'T', name: 'Absolute temperature', unit: 'K', role: 'input' },
67
+ {
68
+ symbol: 'Rgas',
69
+ name: 'Universal gas constant',
70
+ unit: 'J/(mol K)',
71
+ role: 'constant',
72
+ value: 8.314462618,
73
+ note: 'CODATA exact value since the 2019 SI redefinition.',
74
+ },
75
+ ],
76
+ expressions: [
77
+ {
78
+ id: 'density',
79
+ resultSymbol: 'rho',
80
+ unit: 'kg/m3',
81
+ expression: '{P} * {M} / ({Rgas} * {T})',
82
+ latex: '\\rho = \\frac{P M}{R T}',
83
+ },
84
+ ],
85
+ piecewise: [],
86
+ lookups: [],
87
+ classifications: [],
88
+ notes: {
89
+ validity:
90
+ 'The ideal gas assumption degrades near saturation and at high pressure. For real gases multiply by the compressibility factor Z.',
91
+ basis:
92
+ 'P is ABSOLUTE pressure. Supplying gauge pressure here is the most common way this returns a wrong density.',
93
+ },
94
+ },
95
+ examples: [
96
+ {
97
+ // Dry air at one standard atmosphere and 15 °C → the familiar 1.225 kg/m3.
98
+ target: 'density',
99
+ inputs: { P: 101325, M: 0.0289647, T: 288.15 },
100
+ expected: 1.225,
101
+ tolerance: 0.001,
102
+ },
103
+ ],
104
+ };
105
+
106
+ const reynolds: SeedFormula = {
107
+ slug: 'reynolds-number',
108
+ title: 'Reynolds number and flow regime',
109
+ tags: [SEED_TAG, 'fluids'],
110
+ spec: {
111
+ id: 'reynolds-number',
112
+ name: 'Reynolds number and flow regime',
113
+ source: { standard: 'White, Fluid Mechanics', edition: '7th', sections: ['6.2'] },
114
+ unitSystem: 'SI',
115
+ variables: [
116
+ { symbol: 'rho', name: 'Density', unit: 'kg/m3', role: 'input' },
117
+ { symbol: 'v', name: 'Bulk velocity', unit: 'm/s', role: 'input' },
118
+ {
119
+ symbol: 'D',
120
+ name: 'Characteristic length (pipe inside diameter)',
121
+ unit: 'm',
122
+ role: 'input',
123
+ },
124
+ { symbol: 'mu', name: 'Dynamic viscosity', unit: 'Pa s', role: 'input' },
125
+ ],
126
+ expressions: [
127
+ {
128
+ id: 'reynolds',
129
+ resultSymbol: 'Re',
130
+ // No declared unit: the result is dimensionless, and saying so with an
131
+ // empty string would be a claim the checker cannot verify.
132
+ expression: '{rho} * {v} * {D} / {mu}',
133
+ latex: '\\mathrm{Re} = \\frac{\\rho v D}{\\mu}',
134
+ },
135
+ ],
136
+ piecewise: [],
137
+ lookups: [],
138
+ classifications: [
139
+ {
140
+ id: 'regime-rating',
141
+ domain: ['laminar', 'transitional', 'turbulent'],
142
+ criteria: {
143
+ laminar:
144
+ 'Re below roughly 2300 in a smooth circular pipe. Viscous forces dominate; the velocity profile is parabolic and the flow is orderly.',
145
+ transitional:
146
+ 'Re roughly 2300 to 4000. Behaviour is intermittent and not reliably either regime; correlations for both are unreliable here.',
147
+ turbulent:
148
+ 'Re above roughly 4000. Inertial forces dominate; the profile is flatter and mixing is vigorous.',
149
+ },
150
+ note: 'A rating is an INPUT — the criteria are here so a choice can be justified, not so it can be inferred.',
151
+ },
152
+ ],
153
+ notes: {
154
+ thresholds:
155
+ 'The 2300 and 4000 thresholds are conventional rather than sharp, and apply to smooth circular pipe. The source gives no criterion for non-circular sections.',
156
+ },
157
+ },
158
+ examples: [
159
+ {
160
+ // Water at ~20 °C, 2 m/s through a 50 mm pipe.
161
+ target: 'reynolds',
162
+ inputs: { rho: 998, v: 2, D: 0.05, mu: 0.001 },
163
+ expected: 99800,
164
+ tolerance: 1,
165
+ },
166
+ ],
167
+ };
168
+
169
+ const darcyWeisbach: SeedFormula = {
170
+ slug: 'darcy-weisbach-head-loss',
171
+ title: 'Darcy–Weisbach head loss',
172
+ tags: [SEED_TAG, 'fluids'],
173
+ spec: {
174
+ id: 'darcy-weisbach-head-loss',
175
+ name: 'Darcy–Weisbach head loss',
176
+ source: { standard: 'White, Fluid Mechanics', edition: '7th', sections: ['6.4'] },
177
+ unitSystem: 'SI',
178
+ variables: [
179
+ { symbol: 'Re', name: 'Reynolds number', role: 'input' },
180
+ { symbol: 'L', name: 'Pipe length', unit: 'm', role: 'input' },
181
+ { symbol: 'D', name: 'Pipe inside diameter', unit: 'm', role: 'input' },
182
+ { symbol: 'v', name: 'Bulk velocity', unit: 'm/s', role: 'input' },
183
+ // Only the critical-velocity target needs these two, which is why the
184
+ // signature is computed per TARGET rather than per formula.
185
+ { symbol: 'rho', name: 'Density', unit: 'kg/m3', role: 'input' },
186
+ { symbol: 'mu', name: 'Dynamic viscosity', unit: 'Pa s', role: 'input' },
187
+ {
188
+ symbol: 'g',
189
+ name: 'Standard gravity',
190
+ unit: 'm/s2',
191
+ role: 'constant',
192
+ value: 9.80665,
193
+ },
194
+ {
195
+ symbol: 'vhead',
196
+ name: 'Velocity head',
197
+ unit: 'm',
198
+ role: 'derived',
199
+ expression: '{v} ^ 2 / (2 * {g})',
200
+ },
201
+ ],
202
+ expressions: [
203
+ {
204
+ id: 'laminar-factor',
205
+ // No resultSymbol: the PIECEWISE owns `f`. Two expressions both
206
+ // claiming it would make the symbol ambiguous and the evaluator would
207
+ // (rightly) refuse to pick one.
208
+ expression: '64 / {Re}',
209
+ note: 'Exact for fully developed laminar flow in a circular pipe.',
210
+ },
211
+ {
212
+ id: 'turbulent-factor',
213
+ expression: '0.316 * {Re} ^ -0.25',
214
+ note: 'Blasius correlation — an empirical fit for smooth pipe, roughly 4000 < Re < 1e5.',
215
+ },
216
+ {
217
+ id: 'head-loss',
218
+ resultSymbol: 'hf',
219
+ unit: 'm',
220
+ expression: '{f} * ({L} / {D}) * {vhead}',
221
+ latex: 'h_f = f \\frac{L}{D} \\frac{v^2}{2g}',
222
+ },
223
+ {
224
+ id: 'critical-velocity',
225
+ resultSymbol: 'vcrit',
226
+ unit: 'm/s',
227
+ // Symbols, not hard-coded fluid properties: the first cut wrote
228
+ // `2300 * 0.001 / (998 * {D})` and the dimension checker correctly
229
+ // rejected it — bare numbers carry no units, so the result came out as
230
+ // 1/length rather than a velocity.
231
+ expression: '2300 * {mu} / ({rho} * {D})',
232
+ // The deliberate teaching example. It is genuinely reconstructed rather
233
+ // than read off a page, and it is here so the warning can be seen.
234
+ unverified:
235
+ 'Rearranged from the Re=2300 transition criterion rather than read from a source. Included to demonstrate what an unverified equation looks like — confirm before relying on it.',
236
+ },
237
+ ],
238
+ piecewise: [
239
+ {
240
+ id: 'friction-factor',
241
+ resultSymbol: 'f',
242
+ cases: [
243
+ { when: '{Re} < 2300', use: 'laminar-factor', label: 'Laminar' },
244
+ { when: '{Re} >= 2300', use: 'turbulent-factor', label: 'Turbulent' },
245
+ ],
246
+ note: 'No `otherwise`: every real Re matches one of the two, and a silent fallback would hide a bad input.',
247
+ },
248
+ ],
249
+ lookups: [],
250
+ classifications: [],
251
+ notes: {
252
+ transition:
253
+ 'Between Re 2300 and 4000 neither branch is reliable — the piecewise still returns a number, and that number should be treated as indicative only.',
254
+ roughness:
255
+ 'The Blasius branch assumes a SMOOTH pipe. For rough pipe the Colebrook–White equation is required; it is implicit in f and is not modelled here.',
256
+ },
257
+ },
258
+ examples: [
259
+ {
260
+ // Laminar: Re = 1000 → f = 0.064; vhead = 2^2/(2*9.80665) = 0.203944 m.
261
+ target: 'head-loss',
262
+ inputs: { Re: 1000, L: 10, D: 0.05, v: 2 },
263
+ expected: 2.61048,
264
+ tolerance: 0.0001,
265
+ },
266
+ {
267
+ // Turbulent: Re = 10000 → f = 0.316 * 10000^-0.25 = 0.0316.
268
+ target: 'head-loss',
269
+ inputs: { Re: 10000, L: 10, D: 0.05, v: 2 },
270
+ expected: 1.288928,
271
+ tolerance: 0.0001,
272
+ },
273
+ {
274
+ // The branch itself, so the piecewise is exercised directly.
275
+ target: 'friction-factor',
276
+ inputs: { Re: 1000 },
277
+ expected: 0.064,
278
+ tolerance: 1e-9,
279
+ },
280
+ ],
281
+ };
282
+
283
+ const orificeFlow: SeedFormula = {
284
+ slug: 'orifice-flow-rate',
285
+ title: 'Orifice discharge rate',
286
+ tags: [SEED_TAG, 'fluids'],
287
+ spec: {
288
+ id: 'orifice-flow-rate',
289
+ name: 'Orifice discharge rate',
290
+ source: {
291
+ standard: 'Munson, Fundamentals of Fluid Mechanics',
292
+ edition: '7th',
293
+ sections: ['3.6'],
294
+ },
295
+ unitSystem: 'SI',
296
+ variables: [
297
+ { symbol: 'd', name: 'Orifice diameter', unit: 'm', role: 'input' },
298
+ { symbol: 'h', name: 'Head above the orifice centreline', unit: 'm', role: 'input' },
299
+ { symbol: 'g', name: 'Standard gravity', unit: 'm/s2', role: 'constant', value: 9.80665 },
300
+ {
301
+ symbol: 'A',
302
+ name: 'Orifice area',
303
+ unit: 'm2',
304
+ role: 'derived',
305
+ expression: 'PI / 4 * {d} ^ 2',
306
+ },
307
+ ],
308
+ expressions: [
309
+ {
310
+ id: 'discharge',
311
+ resultSymbol: 'Q',
312
+ unit: 'm3/s',
313
+ expression: '{Cd} * {A} * SQRT(2 * {g} * {h})',
314
+ latex: 'Q = C_d A \\sqrt{2 g h}',
315
+ },
316
+ ],
317
+ piecewise: [],
318
+ lookups: [
319
+ {
320
+ id: 'discharge-coefficient',
321
+ name: 'Discharge coefficient by orifice edge',
322
+ keys: ['edgeType'],
323
+ result: 'Cd',
324
+ resultSymbol: 'Cd',
325
+ // Declaring the domain is what makes the coverage check meaningful:
326
+ // every declared combination is checked against the rows, so a missing
327
+ // case is reported instead of silently unrepresented.
328
+ domains: { edgeType: ['sharp', 'rounded', 'short-tube'] },
329
+ rows: [
330
+ { edgeType: 'sharp', Cd: 0.61 },
331
+ { edgeType: 'rounded', Cd: 0.98 },
332
+ { edgeType: 'short-tube', Cd: 0.8 },
333
+ ],
334
+ onMiss: 'error',
335
+ },
336
+ ],
337
+ classifications: [
338
+ {
339
+ id: 'edgeType-classification',
340
+ domain: ['sharp', 'rounded', 'short-tube'],
341
+ criteria: {
342
+ sharp:
343
+ 'A thin plate with a square, unrounded edge. The jet contracts downstream (vena contracta), which is why Cd is well below 1.',
344
+ rounded:
345
+ 'The approach is bell-mouthed or well radiused, so the streamlines turn gradually and contraction is nearly eliminated.',
346
+ 'short-tube':
347
+ 'A plain tube a few diameters long attached to the opening; the jet reattaches inside it, recovering part of the contraction loss.',
348
+ },
349
+ },
350
+ ],
351
+ notes: {
352
+ coefficients:
353
+ 'These coefficients are representative textbook values for water at ordinary temperatures, not a calibration. A metered installation should use its own calibration.',
354
+ },
355
+ },
356
+ examples: [
357
+ {
358
+ // 50 mm sharp-edged orifice under 2 m of head.
359
+ target: 'discharge',
360
+ inputs: { d: 0.05, h: 2, edgeType: 'sharp' },
361
+ expected: 0.0075014,
362
+ tolerance: 1e-6,
363
+ },
364
+ {
365
+ // The lookup on its own — the rounded edge nearly doubles the flow.
366
+ target: 'discharge-coefficient',
367
+ inputs: { edgeType: 'rounded' },
368
+ expected: 0.98,
369
+ tolerance: 1e-9,
370
+ },
371
+ ],
372
+ };
373
+
374
+ const pumpPower: SeedFormula = {
375
+ slug: 'pump-hydraulic-power',
376
+ title: 'Pump hydraulic and shaft power',
377
+ tags: [SEED_TAG, 'pumps'],
378
+ spec: {
379
+ id: 'pump-hydraulic-power',
380
+ name: 'Pump hydraulic and shaft power',
381
+ source: { standard: 'White, Fluid Mechanics', edition: '7th', sections: ['11.2'] },
382
+ unitSystem: 'SI',
383
+ variables: [
384
+ { symbol: 'rho', name: 'Fluid density', unit: 'kg/m3', role: 'input' },
385
+ { symbol: 'Q', name: 'Volumetric flow rate', unit: 'm3/s', role: 'input' },
386
+ { symbol: 'H', name: 'Total head developed', unit: 'm', role: 'input' },
387
+ { symbol: 'g', name: 'Standard gravity', unit: 'm/s2', role: 'constant', value: 9.80665 },
388
+ {
389
+ symbol: 'eta',
390
+ name: 'Pump efficiency (0–1)',
391
+ role: 'input',
392
+ // A default, not a constant: the caller may know the real efficiency,
393
+ // and this is a placeholder for when they do not.
394
+ value: 0.7,
395
+ note: 'Typical mid-range centrifugal efficiency. Use the manufacturer curve where available.',
396
+ },
397
+ // Declared as outputs so the dimension checker knows what they ARE. It
398
+ // learns units from `variables` only, so a chained symbol that appears
399
+ // nowhere in that list binds as dimensionless and the shaft-power check
400
+ // silently passed as a plain number. Declaring an output is good practice
401
+ // regardless; here it is load-bearing.
402
+ { symbol: 'Ph', name: 'Hydraulic power', unit: 'W', role: 'output' },
403
+ { symbol: 'Ps', name: 'Shaft power', unit: 'W', role: 'output' },
404
+ ],
405
+ expressions: [
406
+ {
407
+ id: 'hydraulic-power',
408
+ resultSymbol: 'Ph',
409
+ unit: 'W',
410
+ expression: '{rho} * {g} * {Q} * {H}',
411
+ latex: 'P_h = \\rho g Q H',
412
+ },
413
+ {
414
+ id: 'shaft-power',
415
+ resultSymbol: 'Ps',
416
+ unit: 'W',
417
+ // Chains: Ph resolves through the single target that produces it, so a
418
+ // caller asking for shaft power never has to compute the hydraulic
419
+ // power first.
420
+ expression: '{Ph} / {eta}',
421
+ latex: 'P_s = \\frac{P_h}{\\eta}',
422
+ },
423
+ ],
424
+ piecewise: [],
425
+ lookups: [],
426
+ classifications: [],
427
+ notes: {
428
+ efficiency:
429
+ 'Efficiency here is the pump alone. A motor and any drive have their own efficiencies; electrical input power is higher again.',
430
+ head: 'H is the head the pump DEVELOPS, not the static lift — it includes friction and velocity head.',
431
+ },
432
+ },
433
+ examples: [
434
+ {
435
+ // Water, 50 L/s against 20 m of head.
436
+ target: 'hydraulic-power',
437
+ inputs: { rho: 998, Q: 0.05, H: 20 },
438
+ expected: 9787.0367,
439
+ tolerance: 0.001,
440
+ },
441
+ {
442
+ // Same duty through the chain, at the declared default efficiency.
443
+ target: 'shaft-power',
444
+ inputs: { rho: 998, Q: 0.05, H: 20 },
445
+ expected: 13981.481,
446
+ tolerance: 0.001,
447
+ },
448
+ {
449
+ // Overriding the default.
450
+ target: 'shaft-power',
451
+ inputs: { rho: 998, Q: 0.05, H: 20, eta: 0.5 },
452
+ expected: 19574.0734,
453
+ tolerance: 0.001,
454
+ },
455
+ ],
456
+ };
457
+
458
+ export const FORMULA_SEED: readonly SeedFormula[] = [
459
+ idealGas,
460
+ reynolds,
461
+ darcyWeisbach,
462
+ orificeFlow,
463
+ pumpPower,
464
+ ];
465
+
466
+ export const FORMULA_SEED_SLUGS: readonly string[] = FORMULA_SEED.map((f) => f.slug);