@uwmd/core 1.3.0 → 1.6.0
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/dist/browser.d.ts +14 -6
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +8 -4
- package/dist/browser.js.map +1 -1
- package/dist/capital-stack.d.ts +116 -0
- package/dist/capital-stack.d.ts.map +1 -0
- package/dist/capital-stack.js +227 -0
- package/dist/capital-stack.js.map +1 -0
- package/dist/cascade.d.ts +60 -6
- package/dist/cascade.d.ts.map +1 -1
- package/dist/cascade.js +93 -14
- package/dist/cascade.js.map +1 -1
- package/dist/cli.js +269 -11
- package/dist/cli.js.map +1 -1
- package/dist/composition.d.ts +182 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js +536 -0
- package/dist/composition.js.map +1 -0
- package/dist/context.js +23 -23
- package/dist/defaults.d.ts +18 -0
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +64 -0
- package/dist/defaults.js.map +1 -1
- package/dist/index.d.ts +14 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/lite-bridge.d.ts +9 -0
- package/dist/lite-bridge.d.ts.map +1 -1
- package/dist/lite-bridge.js +44 -5
- package/dist/lite-bridge.js.map +1 -1
- package/dist/lite.js +37 -1
- package/dist/lite.js.map +1 -1
- package/dist/market-data.d.ts +134 -0
- package/dist/market-data.d.ts.map +1 -0
- package/dist/market-data.js +292 -0
- package/dist/market-data.js.map +1 -0
- package/dist/packs/index.d.ts +1 -0
- package/dist/packs/index.d.ts.map +1 -1
- package/dist/packs/index.js +3 -0
- package/dist/packs/index.js.map +1 -1
- package/dist/packs/mixed-use.d.ts +3 -0
- package/dist/packs/mixed-use.d.ts.map +1 -0
- package/dist/packs/mixed-use.js +204 -0
- package/dist/packs/mixed-use.js.map +1 -0
- package/dist/protocol.d.ts +57 -8
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +153 -4
- package/dist/protocol.js.map +1 -1
- package/dist/receipts.d.ts +189 -9
- package/dist/receipts.d.ts.map +1 -1
- package/dist/receipts.js +373 -14
- package/dist/receipts.js.map +1 -1
- package/dist/report.js +165 -165
- package/dist/types.d.ts +54 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validator.d.ts.map +1 -1
- package/dist/validator.js +263 -6
- package/dist/validator.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/cascade.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cascade.d.ts","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cascade.d.ts","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"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"}
|
package/dist/cascade.js
CHANGED
|
@@ -1,11 +1,52 @@
|
|
|
1
1
|
// Fallback cascade resolver — Protocol §V.7
|
|
2
2
|
//
|
|
3
|
-
// Walks the
|
|
3
|
+
// Walks the eight cascade steps in order (seven before RFC 0021 §5 added
|
|
4
|
+
// `inherited_assumption` at protocol 1.5.0); first hit wins. The producer that
|
|
4
5
|
// uses this stamps the resulting `_meta.source` with the cascade step that
|
|
5
6
|
// produced the value. See FORMAT_SPEC §3.4 (provisional flag) and §2.6
|
|
6
7
|
// (source tags).
|
|
7
8
|
import { getAssetClassDefaults } from './defaults.js';
|
|
8
9
|
import { deepGet } from './parser.js';
|
|
10
|
+
/**
|
|
11
|
+
* Raised when two equidistant ancestors assert the same field. Diamond
|
|
12
|
+
* inheritance resolves explicitly or not at all — silently picking one would
|
|
13
|
+
* make the answer depend on graph traversal order, which is the class of bug
|
|
14
|
+
* this whole RFC is written to avoid.
|
|
15
|
+
*/
|
|
16
|
+
export class AmbiguousInheritanceError extends Error {
|
|
17
|
+
code = 'COMP-AMBIGUOUS-INHERIT';
|
|
18
|
+
field_path;
|
|
19
|
+
ancestors;
|
|
20
|
+
constructor(field_path, ancestors) {
|
|
21
|
+
super(`[COMP-AMBIGUOUS-INHERIT] Equidistant ancestors ${ancestors
|
|
22
|
+
.map((a) => `'${a}'`)
|
|
23
|
+
.join(' and ')} both assert '${field_path}'. Resolve which is authoritative rather than letting traversal order decide.`);
|
|
24
|
+
this.name = 'AmbiguousInheritanceError';
|
|
25
|
+
this.field_path = field_path;
|
|
26
|
+
this.ancestors = ancestors;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Select the inherited value for `field_path`: the nearest ancestor wins.
|
|
31
|
+
*
|
|
32
|
+
* Exported because a host often needs the winning ancestor's identity to stamp
|
|
33
|
+
* `_meta.inherited_from`, not merely the value.
|
|
34
|
+
*/
|
|
35
|
+
export function selectInheritedAssumption(field_path, inherited) {
|
|
36
|
+
const holders = inherited.filter((a) => Object.hasOwn(a.values, field_path));
|
|
37
|
+
if (holders.length === 0)
|
|
38
|
+
return null;
|
|
39
|
+
let nearest = holders[0];
|
|
40
|
+
for (const candidate of holders.slice(1)) {
|
|
41
|
+
if (candidate.distance < nearest.distance)
|
|
42
|
+
nearest = candidate;
|
|
43
|
+
}
|
|
44
|
+
const tied = holders.filter((a) => a.distance === nearest.distance && a.document_id !== nearest.document_id);
|
|
45
|
+
if (tied.length > 0) {
|
|
46
|
+
throw new AmbiguousInheritanceError(field_path, [nearest, ...tied].map((a) => a.document_id).sort());
|
|
47
|
+
}
|
|
48
|
+
return nearest;
|
|
49
|
+
}
|
|
9
50
|
// ─── Internal helpers ────────────────────────────────────────────────────────
|
|
10
51
|
function getBlock(parsed, sectionId) {
|
|
11
52
|
const entry = parsed.sections[sectionId];
|
|
@@ -81,12 +122,16 @@ function findBySource(parsed, field_path, wanted) {
|
|
|
81
122
|
*
|
|
82
123
|
* Cascade order:
|
|
83
124
|
* 1. user_override (in-file, source-tag match)
|
|
84
|
-
* 2. user_input (in-file, source-tag match)
|
|
85
|
-
* 3.
|
|
86
|
-
* 4.
|
|
87
|
-
* 5.
|
|
88
|
-
* 6.
|
|
89
|
-
* 7.
|
|
125
|
+
* 2. user_input (in-file, source-tag match; also market_data_accepted)
|
|
126
|
+
* 3. inherited_assumption (ctx.inherited — composition DAG only)
|
|
127
|
+
* 4. investor_profile (ctx.profile)
|
|
128
|
+
* 5. market_data (ctx.market)
|
|
129
|
+
* 6. asset_class_default (defaults registry)
|
|
130
|
+
* 7. global_default (ctx.global)
|
|
131
|
+
* 8. system_default (ctx.system)
|
|
132
|
+
*
|
|
133
|
+
* Throws `AmbiguousInheritanceError` when two equidistant ancestors assert the
|
|
134
|
+
* same field — the one case where resolution refuses rather than choosing.
|
|
90
135
|
*
|
|
91
136
|
* Returns `undefined` value with step='system_default' as a sentinel only when
|
|
92
137
|
* every step misses; callers SHOULD treat that as an unresolvable input and
|
|
@@ -98,12 +143,45 @@ export function resolveValue(field_path, parsed, ctx = {}) {
|
|
|
98
143
|
if (override) {
|
|
99
144
|
return { value: override.value, source: 'user_override', step: 'user_override' };
|
|
100
145
|
}
|
|
101
|
-
// Step 2: user_input (
|
|
102
|
-
|
|
146
|
+
// Step 2: user_input ('manual' is a synonym for typed-in values).
|
|
147
|
+
//
|
|
148
|
+
// `market_data_accepted` resolves here too (RFC 0022 §4). It is an in-file
|
|
149
|
+
// value of record, not a fallback: the analyst accepted one observation of
|
|
150
|
+
// one vintage, and a later live lookup MUST NOT silently replace it. Placing
|
|
151
|
+
// it here rather than at step 4 is what makes promotion stop the field being
|
|
152
|
+
// ranked as an unresolved gap.
|
|
153
|
+
//
|
|
154
|
+
// The returned `source` is the tag found, so `market_data_accepted` survives
|
|
155
|
+
// resolution rather than being flattened into `user_input`. Only the *step*
|
|
156
|
+
// is shared — the cascade is normatively ordered and a producer must not
|
|
157
|
+
// reorder it (protocol §IX), so a promoted value is a distinctly tagged value
|
|
158
|
+
// resolved at an existing step rather than a step of its own. Extending the
|
|
159
|
+
// cascade is a protocol change, which is what RFC 0021 §5 did for
|
|
160
|
+
// `inherited_assumption`; RFC 0022 deliberately did not need one.
|
|
161
|
+
const input = findBySource(parsed, field_path, ['user_input', 'manual', 'market_data_accepted']);
|
|
103
162
|
if (input) {
|
|
104
163
|
return { value: input.value, source: input.source, step: 'user_input' };
|
|
105
164
|
}
|
|
106
|
-
// Step 3:
|
|
165
|
+
// Step 3: inherited_assumption (RFC 0021 §5).
|
|
166
|
+
//
|
|
167
|
+
// Below `user_input`, so a value entered on the deal always wins —
|
|
168
|
+
// inheritance supplies defaults, never overrides. Above `investor_profile`,
|
|
169
|
+
// because a named ancestor in this deal's own composition DAG is more
|
|
170
|
+
// specific than an institution-wide preference set.
|
|
171
|
+
if (ctx.inherited && ctx.inherited.length > 0) {
|
|
172
|
+
// Throws on equidistant ancestors rather than picking one. Deliberate: a
|
|
173
|
+
// silent pick would make the answer depend on traversal order.
|
|
174
|
+
const ancestor = selectInheritedAssumption(field_path, ctx.inherited);
|
|
175
|
+
if (ancestor) {
|
|
176
|
+
return {
|
|
177
|
+
value: ancestor.values[field_path],
|
|
178
|
+
source: 'inherited_assumption',
|
|
179
|
+
step: 'inherited_assumption',
|
|
180
|
+
resolved_from: ancestor.document_id,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Step 4: investor_profile
|
|
107
185
|
if (ctx.profile && field_path in ctx.profile.values) {
|
|
108
186
|
return {
|
|
109
187
|
value: ctx.profile.values[field_path],
|
|
@@ -112,7 +190,7 @@ export function resolveValue(field_path, parsed, ctx = {}) {
|
|
|
112
190
|
resolved_from: ctx.profile.source_id,
|
|
113
191
|
};
|
|
114
192
|
}
|
|
115
|
-
// Step
|
|
193
|
+
// Step 5: market_data
|
|
116
194
|
const asset_class = ctx.asset_class ??
|
|
117
195
|
parsed.frontmatter.asset_class ??
|
|
118
196
|
'multifamily';
|
|
@@ -124,6 +202,7 @@ export function resolveValue(field_path, parsed, ctx = {}) {
|
|
|
124
202
|
source: 'market_data',
|
|
125
203
|
step: 'market_data',
|
|
126
204
|
range: hit.range,
|
|
205
|
+
...(hit.source_id ? { resolved_from: hit.source_id } : {}),
|
|
127
206
|
};
|
|
128
207
|
}
|
|
129
208
|
}
|
|
@@ -132,7 +211,7 @@ export function resolveValue(field_path, parsed, ctx = {}) {
|
|
|
132
211
|
if (inFileMarket) {
|
|
133
212
|
return { value: inFileMarket.value, source: 'market_data', step: 'market_data' };
|
|
134
213
|
}
|
|
135
|
-
// Step
|
|
214
|
+
// Step 6: asset_class_default
|
|
136
215
|
const table = getAssetClassDefaults(asset_class);
|
|
137
216
|
const range = table?.fields[field_path];
|
|
138
217
|
if (range) {
|
|
@@ -144,7 +223,7 @@ export function resolveValue(field_path, parsed, ctx = {}) {
|
|
|
144
223
|
resolved_from: `${table?.asset_class}@${table?.version}`,
|
|
145
224
|
};
|
|
146
225
|
}
|
|
147
|
-
// Step
|
|
226
|
+
// Step 7: global_default
|
|
148
227
|
if (ctx.global && field_path in ctx.global.values) {
|
|
149
228
|
return {
|
|
150
229
|
value: ctx.global.values[field_path],
|
|
@@ -152,7 +231,7 @@ export function resolveValue(field_path, parsed, ctx = {}) {
|
|
|
152
231
|
step: 'global_default',
|
|
153
232
|
};
|
|
154
233
|
}
|
|
155
|
-
// Step
|
|
234
|
+
// Step 8: system_default
|
|
156
235
|
if (ctx.system && field_path in ctx.system.values) {
|
|
157
236
|
return {
|
|
158
237
|
value: ctx.system.values[field_path],
|
package/dist/cascade.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cascade.js","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,EAAE;AACF,
|
|
1
|
+
{"version":3,"file":"cascade.js","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// uwmd CLI — command-line interface for .uw.md files
|
|
3
3
|
// Commands: parse, validate, compact, diff, init, summary, render
|
|
4
4
|
// Usage: uwmd <command> <file> [options]
|
|
5
|
-
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
6
|
-
import { resolve, basename } from 'node:path';
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'node:fs';
|
|
6
|
+
import { resolve, basename, dirname } from 'node:path';
|
|
7
7
|
import { parseUWFile } from './parser.js';
|
|
8
8
|
import { validateUWFile } from './validator.js';
|
|
9
9
|
import { compact, diff } from './compactor.js';
|
|
@@ -24,6 +24,8 @@ import { runBancroftAgent } from './agents/bancroft.js';
|
|
|
24
24
|
import { buildContext } from './context-profiles.js';
|
|
25
25
|
import { rankGaps } from './refinement.js';
|
|
26
26
|
import { resolveValue } from './cascade.js';
|
|
27
|
+
import { parseUWPart, resolveComposition, externalizeSection, stringifyUWPart, UWPART_EXTENSION, } from './composition.js';
|
|
28
|
+
import { createDocumentMarketData, parseMarketDataDocument } from './market-data.js';
|
|
27
29
|
import { getAssetClassDefaults } from './defaults.js';
|
|
28
30
|
import { MULTIFAMILY_PACK, getPackForAssetClass } from './packs/index.js';
|
|
29
31
|
import { issueReceipt, verifyReceipt, assertUWReceipt } from './receipts.js';
|
|
@@ -171,7 +173,9 @@ function cmdValidate(file, flags) {
|
|
|
171
173
|
}
|
|
172
174
|
async function cmdVerify(file, flags) {
|
|
173
175
|
const content = readFile(file);
|
|
174
|
-
|
|
176
|
+
// --resolved verifies the assembled record. Without it an externalized
|
|
177
|
+
// section verifies as a directive, which is not the document anyone means.
|
|
178
|
+
const parsed = withResolved(parseUWFile(content), file, flags);
|
|
175
179
|
// No flag set → run all three.
|
|
176
180
|
const onlyValidate = flags['validate'] === true;
|
|
177
181
|
const onlyIntegrity = flags['integrity'] === true;
|
|
@@ -329,7 +333,11 @@ function cmdSummary(file) {
|
|
|
329
333
|
}
|
|
330
334
|
async function cmdExport(file, flags) {
|
|
331
335
|
const includeSuperseded = !(flags['no-superseded'] === true || flags['compact'] === true);
|
|
332
|
-
|
|
336
|
+
// --resolved exports the assembled record. Exporting the directive instead
|
|
337
|
+
// would ship a document whose rent roll is a list of filenames.
|
|
338
|
+
const loaded = flags['resolved']
|
|
339
|
+
? toUWEnvelope(withResolved(parseUWFile(readFile(file)), file, flags))
|
|
340
|
+
: await loadEnvelope(file);
|
|
333
341
|
const envelope = includeSuperseded ? loaded : { ...loaded, superseded: {} };
|
|
334
342
|
const text = stringifyUWEnvelope(await stampEnvelopeDigest(envelope));
|
|
335
343
|
// Default output path: swap the .uw.md suffix for .uw.json (fall back to appending).
|
|
@@ -419,9 +427,15 @@ async function cmdConvert(file, flags) {
|
|
|
419
427
|
const projection = projectUWEnvelopeToLite(envelope);
|
|
420
428
|
encoded = projection.content;
|
|
421
429
|
extension = '.uw.md';
|
|
422
|
-
|
|
430
|
+
// Two independent losses, reported separately. A record whose only loss is
|
|
431
|
+
// an externalized section omits zero *paths*, so folding these together
|
|
432
|
+
// would print "omitted 0 advanced path(s)" over a missing rent roll.
|
|
433
|
+
if (projection.report.omitted_paths.length > 0) {
|
|
423
434
|
console.warn(`Warning: Lite projection omitted ${projection.report.omitted_paths.length} advanced path(s).`);
|
|
424
435
|
}
|
|
436
|
+
if (projection.report.externalized_sections.length > 0) {
|
|
437
|
+
console.warn(`Warning: Lite projection omitted ${projection.report.externalized_sections.length} externalized section(s), unresolved here: ${projection.report.externalized_sections.join(', ')}.`);
|
|
438
|
+
}
|
|
425
439
|
if (typeof flags['projection-report'] === 'string') {
|
|
426
440
|
writeFileSync(resolve(flags['projection-report']), JSON.stringify(projection.report, null, 2), 'utf-8');
|
|
427
441
|
}
|
|
@@ -644,6 +658,65 @@ function cmdReport(file, flags) {
|
|
|
644
658
|
console.log(`Rendered ${label} → ${basename(outPath)} (${result.sectionsRendered.length} sections${result.sectionsSkipped.length ? `, skipped: ${result.sectionsSkipped.join(', ')}` : ''})`);
|
|
645
659
|
console.log('For PDF output, use @uwmd/report (uwmd-report) or print this HTML from a browser.');
|
|
646
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Load `--market-data <file>` into a cascade-ready lookup, or return null when
|
|
663
|
+
* the flag is absent. Exits with a message rather than throwing, so a bad
|
|
664
|
+
* observation set is a usage error rather than a stack trace.
|
|
665
|
+
*/
|
|
666
|
+
function loadMarketData(flags) {
|
|
667
|
+
const path = flags['market-data'];
|
|
668
|
+
if (!path)
|
|
669
|
+
return null;
|
|
670
|
+
try {
|
|
671
|
+
const doc = parseMarketDataDocument(parseUWFile(readFile(path)));
|
|
672
|
+
return {
|
|
673
|
+
lookup: createDocumentMarketData(doc),
|
|
674
|
+
identity: {
|
|
675
|
+
document_id: doc.document_id,
|
|
676
|
+
as_of: doc.as_of,
|
|
677
|
+
provider: doc.provider,
|
|
678
|
+
geo: doc.geo,
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
catch (e) {
|
|
683
|
+
console.error(`Market data: ${e instanceof Error ? e.message : String(e)}`);
|
|
684
|
+
process.exit(1);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function cmdMarketDataValidate(file, flags) {
|
|
688
|
+
const parsed = parseUWFile(readFile(file));
|
|
689
|
+
try {
|
|
690
|
+
const doc = parseMarketDataDocument(parsed);
|
|
691
|
+
if (flags['json']) {
|
|
692
|
+
process.stdout.write(`${JSON.stringify({ valid: true, ...doc }, null, 2)}\n`);
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
console.log(`OK ${doc.document_id}`);
|
|
696
|
+
console.log(` as of ${doc.as_of} — ${doc.provider}`);
|
|
697
|
+
console.log(` ${doc.geo}${doc.asset_class ? ` — ${doc.asset_class}` : ''}`);
|
|
698
|
+
console.log(` ${doc.observations.length} observation(s):`);
|
|
699
|
+
for (const o of doc.observations) {
|
|
700
|
+
console.log(` ${o.field_path} = ${String(o.value)} ${o.unit}${o.confidence ? ` (${o.confidence})` : ''}`);
|
|
701
|
+
console.log(` basis: ${o.basis}`);
|
|
702
|
+
}
|
|
703
|
+
// Restated on every run because it is the thing people forget.
|
|
704
|
+
console.log('\n Attributable, not verified: this says the observations');
|
|
705
|
+
console.log(' are traceable, not that they are accurate or current.');
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
catch (e) {
|
|
709
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
710
|
+
if (flags['json']) {
|
|
711
|
+
process.stdout.write(`${JSON.stringify({ valid: false, error: message }, null, 2)}\n`);
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
console.error(`INVALID ${file}`);
|
|
715
|
+
console.error(` ${message}`);
|
|
716
|
+
}
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
647
720
|
function cmdScope(file, flags) {
|
|
648
721
|
const content = readFile(file);
|
|
649
722
|
const parsed = parseUWFile(content);
|
|
@@ -655,12 +728,19 @@ function cmdScope(file, flags) {
|
|
|
655
728
|
console.error(`No published asset-class default table for '${assetClass}'.`);
|
|
656
729
|
process.exit(1);
|
|
657
730
|
}
|
|
731
|
+
const market = loadMarketData(flags);
|
|
658
732
|
const out = {};
|
|
659
733
|
for (const path of Object.keys(table.fields)) {
|
|
660
|
-
const r = resolveValue(path, parsed
|
|
734
|
+
const r = resolveValue(path, parsed, {
|
|
735
|
+
asset_class: assetClass,
|
|
736
|
+
...(market ? { market: market.lookup } : {}),
|
|
737
|
+
});
|
|
661
738
|
out[path] = {
|
|
662
739
|
value: r.value,
|
|
663
740
|
step: r.step,
|
|
741
|
+
// Surfaced so a promoted value stays distinguishable in the output: it
|
|
742
|
+
// resolves at the `user_input` step but is tagged `market_data_accepted`.
|
|
743
|
+
source: r.source,
|
|
664
744
|
range: r.range,
|
|
665
745
|
resolved_from: r.resolved_from,
|
|
666
746
|
};
|
|
@@ -670,6 +750,7 @@ function cmdScope(file, flags) {
|
|
|
670
750
|
deal_stage_target: 'scope',
|
|
671
751
|
asset_class: assetClass,
|
|
672
752
|
defaults_table: `${assetClass}@${table.version}`,
|
|
753
|
+
...(market ? { market_data: market.identity } : {}),
|
|
673
754
|
resolved: out,
|
|
674
755
|
};
|
|
675
756
|
const text = JSON.stringify(payload, null, 2);
|
|
@@ -692,7 +773,16 @@ function cmdRefine(file, flags) {
|
|
|
692
773
|
parsed.frontmatter.asset_class ??
|
|
693
774
|
'multifamily';
|
|
694
775
|
const pack = getPackForAssetClass(assetClass) ?? MULTIFAMILY_PACK;
|
|
695
|
-
|
|
776
|
+
// With `--market-data`, a field the observation set covers is no longer a
|
|
777
|
+
// gap the cascade cannot fill, so it drops out of the VOI ranking. That is
|
|
778
|
+
// the point: the ranking should tell you what is still worth diligencing.
|
|
779
|
+
const market = loadMarketData(flags);
|
|
780
|
+
const result = rankGaps(parsed, {
|
|
781
|
+
targets,
|
|
782
|
+
top,
|
|
783
|
+
packs: [pack],
|
|
784
|
+
...(market ? { cascadeContext: { asset_class: assetClass, market: market.lookup } } : {}),
|
|
785
|
+
});
|
|
696
786
|
if (flags['json']) {
|
|
697
787
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
698
788
|
return;
|
|
@@ -729,6 +819,137 @@ for (let _i = 0; _i < args.length; _i++) {
|
|
|
729
819
|
} // skip flag + its value
|
|
730
820
|
positional.push(a);
|
|
731
821
|
}
|
|
822
|
+
// ─── Composition (RFC 0021) ──────────────────────────────────────────────────
|
|
823
|
+
/** Load every `.uwpart.md` in a directory, keyed by `part_id`. */
|
|
824
|
+
function loadPartsDir(dir) {
|
|
825
|
+
const parts = new Map();
|
|
826
|
+
if (!existsSync(dir))
|
|
827
|
+
return parts;
|
|
828
|
+
for (const file of readdirSync(dir).filter((f) => f.endsWith(UWPART_EXTENSION)).sort()) {
|
|
829
|
+
const path = resolve(dir, file);
|
|
830
|
+
const part = parseUWPart(parseUWFile(readFileSync(path, 'utf-8')), { filename: file });
|
|
831
|
+
if (parts.has(part.part_id)) {
|
|
832
|
+
console.error(`Duplicate part_id '${part.part_id}' in ${dir}`);
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
parts.set(part.part_id, part);
|
|
836
|
+
}
|
|
837
|
+
return parts;
|
|
838
|
+
}
|
|
839
|
+
/** Where fragments live for a record, unless told otherwise. */
|
|
840
|
+
function defaultPartsDir(file) {
|
|
841
|
+
return resolve(dirname(resolve(file)), 'parts');
|
|
842
|
+
}
|
|
843
|
+
function cmdCompose(file, flags) {
|
|
844
|
+
const section = flags['externalize'];
|
|
845
|
+
if (typeof section !== 'string') {
|
|
846
|
+
console.error('Usage: uwmd compose <file> --externalize <section> [--collection-key <k>] [--collection-path <p>]');
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
849
|
+
const collectionKey = flags['collection-key'] ?? 'unit_id';
|
|
850
|
+
const collectionPath = flags['collection-path'] ?? 'units';
|
|
851
|
+
const parsed = parseUWFile(readFile(file));
|
|
852
|
+
let result;
|
|
853
|
+
try {
|
|
854
|
+
result = externalizeSection(parsed, {
|
|
855
|
+
section,
|
|
856
|
+
collectionKey,
|
|
857
|
+
collectionPath,
|
|
858
|
+
...(flags['part-prefix'] ? { partIdPrefix: flags['part-prefix'] } : {}),
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
catch (e) {
|
|
862
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
863
|
+
process.exit(1);
|
|
864
|
+
}
|
|
865
|
+
const outDir = flags['out-dir'] ? resolve(flags['out-dir']) : defaultPartsDir(file);
|
|
866
|
+
const recordPath = flags['in-place']
|
|
867
|
+
? resolve(file)
|
|
868
|
+
: flags['output']
|
|
869
|
+
? resolve(flags['output'])
|
|
870
|
+
: resolve(replaceUWExtension(file, '.externalized.uwx.md'));
|
|
871
|
+
const recordText = stringifyUWX(toUWEnvelope(result.document));
|
|
872
|
+
if (flags['dry-run']) {
|
|
873
|
+
console.log(`Would write ${result.parts.length} fragment(s) to ${outDir}:`);
|
|
874
|
+
for (const part of result.parts)
|
|
875
|
+
console.log(` ${part.part_id}${UWPART_EXTENSION}`);
|
|
876
|
+
console.log(`Would write the record to ${recordPath}`);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
mkdirSync(outDir, { recursive: true });
|
|
880
|
+
for (const part of result.parts) {
|
|
881
|
+
writeFileSync(resolve(outDir, `${part.part_id}${UWPART_EXTENSION}`), stringifyUWPart(part), 'utf-8');
|
|
882
|
+
}
|
|
883
|
+
writeFileSync(recordPath, recordText, 'utf-8');
|
|
884
|
+
console.log(`Externalized ${section} → ${result.parts.length} fragment(s) in ${basename(outDir)}/`);
|
|
885
|
+
console.log(`Record written to ${basename(recordPath)}`);
|
|
886
|
+
// Externalizing is a packaging decision, not a model change — worth saying,
|
|
887
|
+
// because the file looks dramatically different afterwards.
|
|
888
|
+
console.log('The resolved record has the same semantic digest as the original.');
|
|
889
|
+
}
|
|
890
|
+
function cmdResolve(file, flags) {
|
|
891
|
+
const partsDir = flags['parts'] ? resolve(flags['parts']) : defaultPartsDir(file);
|
|
892
|
+
const parsed = parseUWFile(readFile(file));
|
|
893
|
+
let parts;
|
|
894
|
+
try {
|
|
895
|
+
parts = loadPartsDir(partsDir);
|
|
896
|
+
}
|
|
897
|
+
catch (e) {
|
|
898
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
899
|
+
process.exit(1);
|
|
900
|
+
}
|
|
901
|
+
const resolution = resolveComposition(parsed, { parts });
|
|
902
|
+
if (flags['json']) {
|
|
903
|
+
process.stdout.write(`${JSON.stringify({
|
|
904
|
+
status: resolution.status,
|
|
905
|
+
externalized: resolution.externalized,
|
|
906
|
+
parts_available: parts.size,
|
|
907
|
+
issues: resolution.issues,
|
|
908
|
+
}, null, 2)}\n`);
|
|
909
|
+
}
|
|
910
|
+
else {
|
|
911
|
+
console.log(`${resolution.status === 'resolved' ? 'RESOLVED' : 'UNRESOLVED'} ${basename(file)}`);
|
|
912
|
+
console.log(` ${parts.size} fragment(s) available in ${basename(partsDir)}/`);
|
|
913
|
+
console.log(` externalized section(s): ${resolution.externalized.join(', ') || '(none)'}`);
|
|
914
|
+
for (const issue of resolution.issues) {
|
|
915
|
+
console.log(` [${issue.code}] ${issue.message}`);
|
|
916
|
+
}
|
|
917
|
+
if (resolution.status === 'unresolved') {
|
|
918
|
+
// The rule worth restating: under-resolution is never a smaller answer.
|
|
919
|
+
console.log('\n Sections stay externalized rather than resolving partially —');
|
|
920
|
+
console.log(' a collection missing rows would still total and still validate.');
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (flags['output'] && resolution.status === 'resolved') {
|
|
924
|
+
const outPath = resolve(flags['output']);
|
|
925
|
+
writeFileSync(outPath, stringifyUWX(toUWEnvelope(resolution.document)), 'utf-8');
|
|
926
|
+
if (!flags['json'])
|
|
927
|
+
console.log(` resolved record → ${basename(outPath)}`);
|
|
928
|
+
}
|
|
929
|
+
if (resolution.status !== 'resolved')
|
|
930
|
+
process.exit(1);
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Resolve a record's externalized sections before another command reads it.
|
|
934
|
+
*
|
|
935
|
+
* Returns the record unchanged when `--resolved` was not requested, so callers
|
|
936
|
+
* can apply it unconditionally. Exits when resolution fails: verifying or
|
|
937
|
+
* exporting a partially-resolved record would report on a document that does
|
|
938
|
+
* not exist.
|
|
939
|
+
*/
|
|
940
|
+
function withResolved(parsed, file, flags) {
|
|
941
|
+
if (!flags['resolved'])
|
|
942
|
+
return parsed;
|
|
943
|
+
const partsDir = flags['parts'] ? resolve(flags['parts']) : defaultPartsDir(file);
|
|
944
|
+
const resolution = resolveComposition(parsed, { parts: loadPartsDir(partsDir) });
|
|
945
|
+
if (resolution.status !== 'resolved') {
|
|
946
|
+
console.error(`Cannot resolve ${basename(file)} against ${basename(partsDir)}/:`);
|
|
947
|
+
for (const issue of resolution.issues)
|
|
948
|
+
console.error(` [${issue.code}] ${issue.message}`);
|
|
949
|
+
process.exit(1);
|
|
950
|
+
}
|
|
951
|
+
return resolution.document;
|
|
952
|
+
}
|
|
732
953
|
// Top-level async wrapper so `run --live` can use await
|
|
733
954
|
(async () => {
|
|
734
955
|
switch (command) {
|
|
@@ -748,7 +969,7 @@ for (let _i = 0; _i < args.length; _i++) {
|
|
|
748
969
|
break;
|
|
749
970
|
case 'verify':
|
|
750
971
|
if (!positional[0]) {
|
|
751
|
-
console.error('Usage: uwmd verify <file> [--validate] [--integrity] [--policy] [--json]');
|
|
972
|
+
console.error('Usage: uwmd verify <file> [--validate] [--integrity] [--policy] [--resolved] [--json]');
|
|
752
973
|
process.exit(1);
|
|
753
974
|
}
|
|
754
975
|
await cmdVerify(positional[0], flags);
|
|
@@ -1020,7 +1241,7 @@ for (let _i = 0; _i < args.length; _i++) {
|
|
|
1020
1241
|
break;
|
|
1021
1242
|
case 'export':
|
|
1022
1243
|
if (!positional[0]) {
|
|
1023
|
-
console.error('Usage: uwmd export <file.uw.md> [--output <file.uw.json>] [--no-superseded] [--stdout]');
|
|
1244
|
+
console.error('Usage: uwmd export <file.uw.md> [--output <file.uw.json>] [--no-superseded] [--resolved] [--stdout]');
|
|
1024
1245
|
process.exit(1);
|
|
1025
1246
|
}
|
|
1026
1247
|
await cmdExport(positional[0], flags);
|
|
@@ -1044,18 +1265,47 @@ for (let _i = 0; _i < args.length; _i++) {
|
|
|
1044
1265
|
break;
|
|
1045
1266
|
case 'scope':
|
|
1046
1267
|
if (!positional[0]) {
|
|
1047
|
-
console.error('Usage: uwmd scope <file> [--asset-class multifamily] [--output <file>]');
|
|
1268
|
+
console.error('Usage: uwmd scope <file> [--asset-class multifamily] [--market-data <file>] [--output <file>]');
|
|
1048
1269
|
process.exit(1);
|
|
1049
1270
|
}
|
|
1050
1271
|
cmdScope(positional[0], flags);
|
|
1051
1272
|
break;
|
|
1052
1273
|
case 'refine':
|
|
1053
1274
|
if (!positional[0]) {
|
|
1054
|
-
console.error('Usage: uwmd refine <file> [--targets dscr,debt_yield] [--top 5] [--json]');
|
|
1275
|
+
console.error('Usage: uwmd refine <file> [--targets dscr,debt_yield] [--top 5] [--market-data <file>] [--json]');
|
|
1055
1276
|
process.exit(1);
|
|
1056
1277
|
}
|
|
1057
1278
|
cmdRefine(positional[0], flags);
|
|
1058
1279
|
break;
|
|
1280
|
+
case 'compose':
|
|
1281
|
+
if (!positional[0]) {
|
|
1282
|
+
console.error('Usage: uwmd compose <file> --externalize <section> [--collection-key <k>] [--collection-path <p>] [--out-dir <dir>] [--in-place] [--dry-run]');
|
|
1283
|
+
process.exit(1);
|
|
1284
|
+
}
|
|
1285
|
+
cmdCompose(positional[0], flags);
|
|
1286
|
+
break;
|
|
1287
|
+
case 'resolve':
|
|
1288
|
+
if (!positional[0]) {
|
|
1289
|
+
console.error('Usage: uwmd resolve <file> [--parts <dir>] [--output <file>] [--json]');
|
|
1290
|
+
process.exit(1);
|
|
1291
|
+
}
|
|
1292
|
+
cmdResolve(positional[0], flags);
|
|
1293
|
+
break;
|
|
1294
|
+
case 'market-data': {
|
|
1295
|
+
const sub = positional[0];
|
|
1296
|
+
if (sub === 'validate') {
|
|
1297
|
+
if (!positional[1]) {
|
|
1298
|
+
console.error('Usage: uwmd market-data validate <file> [--json]');
|
|
1299
|
+
process.exit(1);
|
|
1300
|
+
}
|
|
1301
|
+
cmdMarketDataValidate(positional[1], flags);
|
|
1302
|
+
}
|
|
1303
|
+
else {
|
|
1304
|
+
console.error('Usage: uwmd market-data validate <file> [--json]');
|
|
1305
|
+
process.exit(1);
|
|
1306
|
+
}
|
|
1307
|
+
break;
|
|
1308
|
+
}
|
|
1059
1309
|
case 'lease': {
|
|
1060
1310
|
const sub = positional[0];
|
|
1061
1311
|
if (sub === 'validate') {
|
|
@@ -1191,6 +1441,8 @@ Commands:
|
|
|
1191
1441
|
init Generate a blank .uwx.md file
|
|
1192
1442
|
summary <file> Print quick metrics to terminal
|
|
1193
1443
|
export <file> Export a digested UW JSON 1.0 document
|
|
1444
|
+
compose <file> Externalize a section into .uwpart.md fragments (RFC 0021)
|
|
1445
|
+
resolve <file> Resolve externalized sections from a parts directory (RFC 0021)
|
|
1194
1446
|
formats List registered machine representations
|
|
1195
1447
|
convert <file> Convert Lite/UWX/JSON/XML/CSV bundle representations
|
|
1196
1448
|
report <file> Render the lender package / credit memo HTML (§7.1/§7.2)
|
|
@@ -1209,6 +1461,12 @@ Options:
|
|
|
1209
1461
|
--strict Throw on parse errors instead of collecting
|
|
1210
1462
|
--format <f> Render format: json|csv|chat|summary (render, default: summary)
|
|
1211
1463
|
--no-superseded Drop append-only history from the .uw.json export (export)
|
|
1464
|
+
--resolved Resolve externalized sections first (verify, export)
|
|
1465
|
+
--parts <dir> Fragment directory (compose, resolve, --resolved; default: ./parts)
|
|
1466
|
+
--externalize <s> Section to split into fragments (compose)
|
|
1467
|
+
--collection-key <k> Row identity field (compose, default: unit_id)
|
|
1468
|
+
--collection-path <p> Field the rows occupy (compose, default: units)
|
|
1469
|
+
--in-place Overwrite the input record (compose)
|
|
1212
1470
|
--stdout Write export/convert output to stdout
|
|
1213
1471
|
--to <format> Target: lite|uwx|uw-json|uw-xml|uw-csv-bundle (convert)
|
|
1214
1472
|
--projection-report Write the UWX-to-Lite omission report as JSON (convert)
|