@cyanheads/congressgov-mcp-server 0.4.3 → 0.5.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/AGENTS.md +20 -5
- package/CLAUDE.md +20 -5
- package/README.md +3 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/prompts/definitions/legislative-research.prompt.d.ts +9 -0
- package/dist/mcp-server/prompts/definitions/legislative-research.prompt.d.ts.map +1 -1
- package/dist/mcp-server/prompts/definitions/legislative-research.prompt.js +46 -15
- package/dist/mcp-server/prompts/definitions/legislative-research.prompt.js.map +1 -1
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts +38 -0
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.js +72 -4
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts +37 -0
- package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-reports.tool.js +66 -7
- package/dist/mcp-server/tools/definitions/committee-reports.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts +41 -3
- package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/daily-record.tool.js +61 -8
- package/dist/mcp-server/tools/definitions/daily-record.tool.js.map +1 -1
- package/dist/mcp-server/tools/format-helpers.d.ts +9 -5
- package/dist/mcp-server/tools/format-helpers.d.ts.map +1 -1
- package/dist/mcp-server/tools/format-helpers.js +397 -89
- package/dist/mcp-server/tools/format-helpers.js.map +1 -1
- package/dist/mcp-server/tools/tool-helpers.d.ts +67 -0
- package/dist/mcp-server/tools/tool-helpers.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-helpers.js +85 -0
- package/dist/mcp-server/tools/tool-helpers.js.map +1 -1
- package/dist/services/congress-documents/congress-documents-service.d.ts +85 -0
- package/dist/services/congress-documents/congress-documents-service.d.ts.map +1 -0
- package/dist/services/congress-documents/congress-documents-service.js +289 -0
- package/dist/services/congress-documents/congress-documents-service.js.map +1 -0
- package/dist/services/congress-documents/document-formats.d.ts +29 -0
- package/dist/services/congress-documents/document-formats.d.ts.map +1 -0
- package/dist/services/congress-documents/document-formats.js +51 -0
- package/dist/services/congress-documents/document-formats.js.map +1 -0
- package/dist/services/congress-documents/extract-text-stream.d.ts +58 -0
- package/dist/services/congress-documents/extract-text-stream.d.ts.map +1 -0
- package/dist/services/congress-documents/extract-text-stream.js +689 -0
- package/dist/services/congress-documents/extract-text-stream.js.map +1 -0
- package/dist/services/congress-documents/extract-text.d.ts +27 -0
- package/dist/services/congress-documents/extract-text.d.ts.map +1 -0
- package/dist/services/congress-documents/extract-text.js +87 -0
- package/dist/services/congress-documents/extract-text.js.map +1 -0
- package/dist/services/congress-documents/types.d.ts +45 -0
- package/dist/services/congress-documents/types.d.ts.map +1 -0
- package/dist/services/congress-documents/types.js +6 -0
- package/dist/services/congress-documents/types.js.map +1 -0
- package/package.json +1 -1
- package/server.json +3 -3
|
@@ -6,11 +6,15 @@
|
|
|
6
6
|
* NOT reliably forwarded. These formatters render complete, structured
|
|
7
7
|
* markdown so the LLM can reason about all returned data.
|
|
8
8
|
*
|
|
9
|
-
* **Completeness invariant.** A record reaching
|
|
10
|
-
* whole: every upstream field appears, and no value is
|
|
11
|
-
* hand-built header may curate presentation, but
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* **Completeness invariant.** A record reaching any renderer — detail view or
|
|
10
|
+
* list row — is rendered whole: every upstream field appears, and no value is
|
|
11
|
+
* cut mid-string. A hand-built header may curate presentation, but a detail
|
|
12
|
+
* header ends in `renderDetailRest`, a list row ends in `withRowRest`, and a
|
|
13
|
+
* curated single-line row ends in `withUnnamedFields` — so a field none of them
|
|
14
|
+
* names still reaches the reader. A collection a curated line renders lossily
|
|
15
|
+
* (its latest entry only, or only some of each entry's fields) is deliberately
|
|
16
|
+
* left out of the row's named set, so the fall-through carries it whole. The
|
|
17
|
+
* single deliberate exception is a
|
|
14
18
|
* `{count, url}` sub-resource reference, rendered as "N available": the URL is
|
|
15
19
|
* the upstream REST endpoint, and the tool's own operation is the callable path
|
|
16
20
|
* to those rows. Caps belong to pagination, which has a continuation path
|
|
@@ -218,11 +222,10 @@ function renderDetail(obj) {
|
|
|
218
222
|
lines.push(`**${key}:** ${count} available`);
|
|
219
223
|
continue;
|
|
220
224
|
}
|
|
221
|
-
// latestAction: { actionDate, text }
|
|
222
225
|
if (key === 'latestAction') {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
const line = latestActionLine(record);
|
|
227
|
+
if (line)
|
|
228
|
+
lines.push(line);
|
|
226
229
|
continue;
|
|
227
230
|
}
|
|
228
231
|
// Small objects inline, larger ones nested
|
|
@@ -248,22 +251,28 @@ function renderDetail(obj) {
|
|
|
248
251
|
}
|
|
249
252
|
/** Render any list item with all its fields. */
|
|
250
253
|
function renderGenericItem(item, index) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Only the keys the heading actually consumed are skipped below. A fixed
|
|
256
|
+
* skip-list swallows the alternates: a committee-report row carries both a
|
|
257
|
+
* `citation` and a `type`/`number` pair, and the heading built from the pair
|
|
258
|
+
* would drop `H. Rept. 113-118` — the label Congress.gov itself publishes.
|
|
259
|
+
*/
|
|
260
|
+
const named = new Set();
|
|
261
|
+
let id;
|
|
262
|
+
if (item.type && item.number != null) {
|
|
263
|
+
named.add('type').add('number');
|
|
264
|
+
id = `${String(item.type).toUpperCase()} ${item.number}`;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
id = pick(item, named, 'citation', 'bioguideId', 'systemCode');
|
|
268
|
+
}
|
|
269
|
+
const name = pick(item, named, 'title', 'name', 'fullName', 'directOrderName', 'description', 'question');
|
|
261
270
|
const heading = [id, name].filter(Boolean).join(': ') || 'Item';
|
|
262
271
|
const lines = [`### ${index + 1}. ${heading}`];
|
|
263
272
|
for (const [key, val] of Object.entries(item)) {
|
|
264
273
|
if (val == null || val === '')
|
|
265
274
|
continue;
|
|
266
|
-
if (
|
|
275
|
+
if (named.has(key))
|
|
267
276
|
continue;
|
|
268
277
|
if (typeof val === 'string') {
|
|
269
278
|
const inline = stripHtml(val, { inline: true });
|
|
@@ -279,10 +288,9 @@ function renderGenericItem(item, index) {
|
|
|
279
288
|
lines.push(`**${key}:** ${val}`);
|
|
280
289
|
}
|
|
281
290
|
else if (typeof val === 'object' && !Array.isArray(val) && val !== null) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
lines.push(`**Latest Action:** ${[date, text].filter(Boolean).join(' — ')}`);
|
|
291
|
+
const action = key === 'latestAction' ? latestActionLine(item) : undefined;
|
|
292
|
+
if (action) {
|
|
293
|
+
lines.push(action);
|
|
286
294
|
}
|
|
287
295
|
else {
|
|
288
296
|
lines.push(`**${key}:** ${renderInline(val)}`);
|
|
@@ -303,19 +311,25 @@ function renderGenericItem(item, index) {
|
|
|
303
311
|
}
|
|
304
312
|
return lines.join('\n');
|
|
305
313
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
314
|
+
/** Fields of a `latestAction` object the curated line names — `actionTime` is not one. */
|
|
315
|
+
const LATEST_ACTION_KEYS = new Set(['actionDate', 'text']);
|
|
316
|
+
/**
|
|
317
|
+
* The `**Latest Action:** date — text` line, followed by any sub-field that
|
|
318
|
+
* curation does not name. Every renderer that shows a `latestAction` object
|
|
319
|
+
* shows it the same lossy way, so the fall-through lives here rather than in
|
|
320
|
+
* each caller's key set — upstream carries `actionTime` on bill rows reached
|
|
321
|
+
* through `member_lookup sponsored`, and it reached no `content[]` client.
|
|
322
|
+
* Returns undefined when the row carries no latest action at all.
|
|
323
|
+
*/
|
|
324
|
+
function latestActionLine(item) {
|
|
325
|
+
const action = item.latestAction;
|
|
326
|
+
if (!action || typeof action !== 'object' || Array.isArray(action))
|
|
327
|
+
return;
|
|
328
|
+
const record = action;
|
|
329
|
+
const curated = [s(record, 'actionDate'), s(record, 'text')].filter(Boolean).join(' — ');
|
|
330
|
+
const line = withUnnamedFields(record, curated, LATEST_ACTION_KEYS);
|
|
331
|
+
return line ? `**Latest Action:** ${line.replace(/^ — /, '')}` : undefined;
|
|
332
|
+
}
|
|
319
333
|
/**
|
|
320
334
|
* Compact one-line render of a small object. Compact but never lossy: values are
|
|
321
335
|
* carried in full and nested objects/arrays recurse, so no field or character is
|
|
@@ -370,6 +384,35 @@ function withUnnamedFields(row, curated, named) {
|
|
|
370
384
|
const unnamed = inlineFields(rest);
|
|
371
385
|
return unnamed ? `${curated} — ${unnamed}` : curated;
|
|
372
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* A curated multi-line list row followed by every field of the row the curation
|
|
389
|
+
* never named — the list-row counterpart to a detail header's `renderDetailRest`
|
|
390
|
+
* tail. Without it a hand-built row renders a fixed field set, and anything
|
|
391
|
+
* upstream adds (or the curation never reached, like a subcommittee's `parent`)
|
|
392
|
+
* stays in `structuredContent` and never reaches a `content[]`-only client.
|
|
393
|
+
*/
|
|
394
|
+
function withRowRest(item, lines, named) {
|
|
395
|
+
const rest = renderDetailRest(item, named);
|
|
396
|
+
return rest ? [...lines, '', rest].join('\n') : lines.join('\n');
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* First present value among `keys`, marking only the key actually consumed as
|
|
400
|
+
* named. An alias chain (`updateDate ?? publishDate`) otherwise swallows the
|
|
401
|
+
* alternates: they are distinct upstream facts, so the ones the header did not
|
|
402
|
+
* render have to stay available to the fall-through. Renderers that call this
|
|
403
|
+
* hold their key constant as an array and copy it into a fresh set per row —
|
|
404
|
+
* `pick` mutates the set it is given.
|
|
405
|
+
*/
|
|
406
|
+
function pick(item, named, ...keys) {
|
|
407
|
+
for (const key of keys) {
|
|
408
|
+
const val = s(item, key);
|
|
409
|
+
if (val != null && val !== '') {
|
|
410
|
+
named.add(key);
|
|
411
|
+
return val;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
373
416
|
// ── Domain-Specific Item Renderers ──────────────────────────────────
|
|
374
417
|
/**
|
|
375
418
|
* Public/private law citations from a bill or law record's `laws[]`, e.g.
|
|
@@ -412,17 +455,30 @@ function renderBillItem(item, i) {
|
|
|
412
455
|
});
|
|
413
456
|
lines.push(`**Sponsor:** ${sponsors.join(', ')}`);
|
|
414
457
|
}
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
lines.push(`**Latest Action:** ${[actionDate, actionText].filter(Boolean).join(' — ')}`);
|
|
458
|
+
const latestAction = latestActionLine(item);
|
|
459
|
+
if (latestAction)
|
|
460
|
+
lines.push(latestAction);
|
|
419
461
|
const updated = s(item, 'updateDate');
|
|
420
462
|
if (updated)
|
|
421
463
|
lines.push(`**Updated:** ${updated}`);
|
|
422
464
|
if (url)
|
|
423
465
|
lines.push(`**URL:** ${url}`);
|
|
424
|
-
return lines
|
|
466
|
+
return withRowRest(item, lines, BILL_ROW_KEYS);
|
|
425
467
|
}
|
|
468
|
+
/** `sponsors` is absent: the curated line drops each sponsor's `bioguideId`. */
|
|
469
|
+
const BILL_ROW_KEYS = new Set([
|
|
470
|
+
'type',
|
|
471
|
+
'number',
|
|
472
|
+
'title',
|
|
473
|
+
'congress',
|
|
474
|
+
'originChamber',
|
|
475
|
+
'originChamberCode',
|
|
476
|
+
'policyArea',
|
|
477
|
+
'laws',
|
|
478
|
+
'latestAction',
|
|
479
|
+
'updateDate',
|
|
480
|
+
'url',
|
|
481
|
+
]);
|
|
426
482
|
function renderMemberItem(item, i) {
|
|
427
483
|
const name = s(item, 'name') ?? s(item, 'directOrderName') ?? s(item, 'fullName') ?? 'Unknown Member';
|
|
428
484
|
const url = s(item, 'url');
|
|
@@ -455,15 +511,28 @@ function renderMemberItem(item, i) {
|
|
|
455
511
|
}
|
|
456
512
|
if (url)
|
|
457
513
|
lines.push(`**URL:** ${url}`);
|
|
458
|
-
return lines
|
|
514
|
+
return withRowRest(item, lines, MEMBER_ROW_KEYS);
|
|
459
515
|
}
|
|
516
|
+
/** `terms` is absent: the curated line renders the latest term only. */
|
|
517
|
+
const MEMBER_ROW_KEYS = new Set([
|
|
518
|
+
'bioguideId',
|
|
519
|
+
'name',
|
|
520
|
+
'directOrderName',
|
|
521
|
+
'fullName',
|
|
522
|
+
'partyName',
|
|
523
|
+
'party',
|
|
524
|
+
'state',
|
|
525
|
+
'district',
|
|
526
|
+
'url',
|
|
527
|
+
]);
|
|
460
528
|
function renderSummaryItem(item, i) {
|
|
529
|
+
const named = new Set(SUMMARY_ROW_KEYS);
|
|
461
530
|
const billType = s(item, 'bill', 'type')?.toUpperCase() ?? '';
|
|
462
531
|
const billNum = s(item, 'bill', 'number') ?? '';
|
|
463
532
|
const congress = s(item, 'bill', 'congress') ?? '';
|
|
464
|
-
const version =
|
|
533
|
+
const version = pick(item, named, 'actionDesc', 'versionCode') ?? '';
|
|
465
534
|
const actionDate = s(item, 'actionDate') ?? '';
|
|
466
|
-
const summaryUpdate =
|
|
535
|
+
const summaryUpdate = pick(item, named, 'lastSummaryUpdateDate', 'updateDate') ?? '';
|
|
467
536
|
const rawText = typeof item.text === 'string' ? item.text : '';
|
|
468
537
|
const text = rawText ? htmlToMarkdown(rawText) : '';
|
|
469
538
|
const url = s(item, 'url') ?? s(item, 'bill', 'url');
|
|
@@ -478,19 +547,28 @@ function renderSummaryItem(item, i) {
|
|
|
478
547
|
if (meta)
|
|
479
548
|
lines.push(meta);
|
|
480
549
|
const billTitle = s(item, 'bill', 'title');
|
|
481
|
-
|
|
550
|
+
const billLine = `**Bill Title:** ${billTitle ?? 'Not available'}`;
|
|
551
|
+
const bill = item.bill;
|
|
552
|
+
lines.push(bill && typeof bill === 'object' && !Array.isArray(bill)
|
|
553
|
+
? withUnnamedFields(bill, billLine, BILL_REF_KEYS)
|
|
554
|
+
: billLine);
|
|
482
555
|
// The summary text is the critical data — the whole point of this tool
|
|
483
556
|
lines.push('');
|
|
484
557
|
lines.push(text || '_Summary text not available._');
|
|
485
558
|
if (url)
|
|
486
559
|
lines.push(`\n**URL:** ${url}`);
|
|
487
|
-
return lines
|
|
560
|
+
return withRowRest(item, lines, named);
|
|
488
561
|
}
|
|
562
|
+
/** `actionDesc`/`versionCode` and the two update dates are resolved by `pick`. */
|
|
563
|
+
const SUMMARY_ROW_KEYS = ['actionDate', 'text', 'url', 'bill'];
|
|
564
|
+
/** Fields of a summary row's nested bill reference the heading already renders. */
|
|
565
|
+
const BILL_REF_KEYS = new Set(['congress', 'type', 'number', 'title', 'url']);
|
|
489
566
|
function renderCrsReportItem(item, i) {
|
|
567
|
+
const named = new Set(CRS_REPORT_ROW_KEYS);
|
|
490
568
|
const reportNumber = s(item, 'reportNumber') ?? s(item, 'number') ?? s(item, 'id') ?? 'Report number not available';
|
|
491
569
|
const title = s(item, 'title') ?? 'Title not available';
|
|
492
|
-
const updated =
|
|
493
|
-
const summary =
|
|
570
|
+
const updated = pick(item, named, 'updateDate', 'publishDate', 'date') ?? '';
|
|
571
|
+
const summary = pick(item, named, 'summary', 'abstract') ?? '';
|
|
494
572
|
const contentType = s(item, 'contentType');
|
|
495
573
|
const status = s(item, 'status');
|
|
496
574
|
const version = s(item, 'version');
|
|
@@ -508,8 +586,23 @@ function renderCrsReportItem(item, i) {
|
|
|
508
586
|
lines.push(summary);
|
|
509
587
|
if (url)
|
|
510
588
|
lines.push(`**URL:** ${url}`);
|
|
511
|
-
return lines
|
|
589
|
+
return withRowRest(item, lines, named);
|
|
512
590
|
}
|
|
591
|
+
/**
|
|
592
|
+
* The three identifier aliases are all named: upstream sends one of them, and
|
|
593
|
+
* the heading renders whichever arrived. The date and summary aliases are
|
|
594
|
+
* distinct facts, so `pick` names only the one the header consumed.
|
|
595
|
+
*/
|
|
596
|
+
const CRS_REPORT_ROW_KEYS = [
|
|
597
|
+
'reportNumber',
|
|
598
|
+
'number',
|
|
599
|
+
'id',
|
|
600
|
+
'title',
|
|
601
|
+
'contentType',
|
|
602
|
+
'status',
|
|
603
|
+
'version',
|
|
604
|
+
'url',
|
|
605
|
+
];
|
|
513
606
|
/** Daily Congressional Record articles — flattened from section-wrapped shape. */
|
|
514
607
|
function renderDailyArticleItem(item, i) {
|
|
515
608
|
const title = s(item, 'title') ?? 'Untitled article';
|
|
@@ -530,8 +623,9 @@ function renderDailyArticleItem(item, i) {
|
|
|
530
623
|
lines.push(`**${type}:** ${url}`);
|
|
531
624
|
}
|
|
532
625
|
}
|
|
533
|
-
return lines
|
|
626
|
+
return withRowRest(item, lines, DAILY_ARTICLE_ROW_KEYS);
|
|
534
627
|
}
|
|
628
|
+
const DAILY_ARTICLE_ROW_KEYS = new Set(['title', 'sectionName', 'startPage', 'endPage', 'text']);
|
|
535
629
|
/** Daily Congressional Record volumes and issues. */
|
|
536
630
|
function renderDailyRecordItem(item, i) {
|
|
537
631
|
const volume = s(item, 'volumeNumber');
|
|
@@ -554,8 +648,17 @@ function renderDailyRecordItem(item, i) {
|
|
|
554
648
|
lines.push(meta);
|
|
555
649
|
if (url)
|
|
556
650
|
lines.push(`**URL:** ${url}`);
|
|
557
|
-
return lines
|
|
651
|
+
return withRowRest(item, lines, DAILY_RECORD_ROW_KEYS);
|
|
558
652
|
}
|
|
653
|
+
/** `issueDate` is absent: the heading shows the date, the fall-through the time. */
|
|
654
|
+
const DAILY_RECORD_ROW_KEYS = new Set([
|
|
655
|
+
'volumeNumber',
|
|
656
|
+
'issueNumber',
|
|
657
|
+
'congress',
|
|
658
|
+
'sessionNumber',
|
|
659
|
+
'updateDate',
|
|
660
|
+
'url',
|
|
661
|
+
]);
|
|
559
662
|
/** House roll call votes. */
|
|
560
663
|
function renderRollVoteItem(item, i) {
|
|
561
664
|
const roll = s(item, 'rollCallNumber');
|
|
@@ -592,8 +695,23 @@ function renderRollVoteItem(item, i) {
|
|
|
592
695
|
lines.push(`**URL:** ${url}`);
|
|
593
696
|
if (sourceUrl)
|
|
594
697
|
lines.push(`**Source Data URL:** ${sourceUrl}`);
|
|
595
|
-
return lines
|
|
698
|
+
return withRowRest(item, lines, ROLL_VOTE_ROW_KEYS);
|
|
596
699
|
}
|
|
700
|
+
const ROLL_VOTE_ROW_KEYS = new Set([
|
|
701
|
+
'rollCallNumber',
|
|
702
|
+
'identifier',
|
|
703
|
+
'legislationType',
|
|
704
|
+
'legislationNumber',
|
|
705
|
+
'legislationUrl',
|
|
706
|
+
'result',
|
|
707
|
+
'voteType',
|
|
708
|
+
'startDate',
|
|
709
|
+
'congress',
|
|
710
|
+
'sessionNumber',
|
|
711
|
+
'updateDate',
|
|
712
|
+
'sourceDataURL',
|
|
713
|
+
'url',
|
|
714
|
+
]);
|
|
597
715
|
/** Bill legislative actions. */
|
|
598
716
|
function renderBillActionItem(item, i) {
|
|
599
717
|
const actionDate = s(item, 'actionDate');
|
|
@@ -612,8 +730,10 @@ function renderBillActionItem(item, i) {
|
|
|
612
730
|
if (names.length > 0)
|
|
613
731
|
lines.push(`**Committees:** ${names.join(', ')}`);
|
|
614
732
|
}
|
|
615
|
-
return lines
|
|
733
|
+
return withRowRest(item, lines, BILL_ACTION_ROW_KEYS);
|
|
616
734
|
}
|
|
735
|
+
/** `committees` is absent: the name list drops each committee's `systemCode`. */
|
|
736
|
+
const BILL_ACTION_ROW_KEYS = new Set(['actionDate', 'text', 'type', 'actionCode', 'sourceSystem']);
|
|
617
737
|
/** Committee report text — items wrap a `formats` array of {type, url, isErrata}. */
|
|
618
738
|
function renderCommitteeReportTextItem(item, i) {
|
|
619
739
|
const formats = item.formats;
|
|
@@ -635,8 +755,9 @@ function renderCommitteeReportTextItem(item, i) {
|
|
|
635
755
|
const label = e.isErrata ? `${e.type} (Errata)` : e.type;
|
|
636
756
|
lines.push(`**${label}:** ${e.url}`);
|
|
637
757
|
}
|
|
638
|
-
return lines
|
|
758
|
+
return withRowRest(item, lines, COMMITTEE_REPORT_TEXT_ROW_KEYS);
|
|
639
759
|
}
|
|
760
|
+
const COMMITTEE_REPORT_TEXT_ROW_KEYS = new Set(['formats']);
|
|
640
761
|
/** Member-sponsored amendments — `type`/`title` are null upstream; identify by `amendmentNumber`. */
|
|
641
762
|
function renderAmendmentItem(item, i) {
|
|
642
763
|
const number = s(item, 'amendmentNumber');
|
|
@@ -657,14 +778,20 @@ function renderAmendmentItem(item, i) {
|
|
|
657
778
|
]);
|
|
658
779
|
if (meta)
|
|
659
780
|
lines.push(meta);
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
lines.push(`**Latest Action:** ${[actionDate, actionText].filter(Boolean).join(' — ')}`);
|
|
781
|
+
const latestAction = latestActionLine(item);
|
|
782
|
+
if (latestAction)
|
|
783
|
+
lines.push(latestAction);
|
|
664
784
|
if (url)
|
|
665
785
|
lines.push(`**URL:** ${url}`);
|
|
666
|
-
return lines
|
|
786
|
+
return withRowRest(item, lines, AMENDMENT_ROW_KEYS);
|
|
667
787
|
}
|
|
788
|
+
const AMENDMENT_ROW_KEYS = new Set([
|
|
789
|
+
'amendmentNumber',
|
|
790
|
+
'congress',
|
|
791
|
+
'introducedDate',
|
|
792
|
+
'latestAction',
|
|
793
|
+
'url',
|
|
794
|
+
]);
|
|
668
795
|
/** Bill text versions — heading from `type` (e.g. "Enrolled Bill"), formats[] as labeled URLs. */
|
|
669
796
|
function renderBillTextItem(item, i) {
|
|
670
797
|
const type = s(item, 'type') ?? 'Bill Text';
|
|
@@ -681,8 +808,9 @@ function renderBillTextItem(item, i) {
|
|
|
681
808
|
lines.push(`**${fType}:** ${fUrl}`);
|
|
682
809
|
}
|
|
683
810
|
}
|
|
684
|
-
return lines
|
|
811
|
+
return withRowRest(item, lines, BILL_TEXT_ROW_KEYS);
|
|
685
812
|
}
|
|
813
|
+
const BILL_TEXT_ROW_KEYS = new Set(['type', 'date', 'formats']);
|
|
686
814
|
/** Nomination type wrapper: `{isCivilian: true}` / `{isMilitary: true}` → readable label. */
|
|
687
815
|
function nominationTypeLabel(raw) {
|
|
688
816
|
if (!raw || typeof raw !== 'object')
|
|
@@ -722,15 +850,27 @@ function renderNominationListItem(item, i) {
|
|
|
722
850
|
]);
|
|
723
851
|
if (meta)
|
|
724
852
|
lines.push(meta);
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
lines.push(`**Latest Action:** ${[actionDate, actionText].filter(Boolean).join(' — ')}`);
|
|
853
|
+
const latestAction = latestActionLine(item);
|
|
854
|
+
if (latestAction)
|
|
855
|
+
lines.push(latestAction);
|
|
729
856
|
const url = s(item, 'url');
|
|
730
857
|
if (url)
|
|
731
858
|
lines.push(`**URL:** ${url}`);
|
|
732
|
-
return lines
|
|
859
|
+
return withRowRest(item, lines, NOMINATION_ROW_KEYS);
|
|
733
860
|
}
|
|
861
|
+
const NOMINATION_ROW_KEYS = new Set([
|
|
862
|
+
'citation',
|
|
863
|
+
'number',
|
|
864
|
+
'partNumber',
|
|
865
|
+
'nominationType',
|
|
866
|
+
'description',
|
|
867
|
+
'congress',
|
|
868
|
+
'receivedDate',
|
|
869
|
+
'authorityDate',
|
|
870
|
+
'updateDate',
|
|
871
|
+
'latestAction',
|
|
872
|
+
'url',
|
|
873
|
+
]);
|
|
734
874
|
/** Nomination committee items — shape {name, systemCode, chamber, type, activities[], url}. */
|
|
735
875
|
function renderNominationCommitteeItem(item, i) {
|
|
736
876
|
const name = s(item, 'name') ?? s(item, 'systemCode') ?? 'Committee';
|
|
@@ -754,8 +894,10 @@ function renderNominationCommitteeItem(item, i) {
|
|
|
754
894
|
const url = s(item, 'url');
|
|
755
895
|
if (url)
|
|
756
896
|
lines.push(`**URL:** ${url}`);
|
|
757
|
-
return lines
|
|
897
|
+
return withRowRest(item, lines, NOMINATION_COMMITTEE_ROW_KEYS);
|
|
758
898
|
}
|
|
899
|
+
/** `activities` is absent: the curated lines cut each activity date to 10 chars. */
|
|
900
|
+
const NOMINATION_COMMITTEE_ROW_KEYS = new Set(['name', 'systemCode', 'chamber', 'type', 'url']);
|
|
759
901
|
/** Individual nominee items — shape {firstName, middleName, lastName, ordinal, state, prefix?, suffix?}. */
|
|
760
902
|
function renderNomineeItem(item, i) {
|
|
761
903
|
const prefix = s(item, 'prefix');
|
|
@@ -769,8 +911,17 @@ function renderNomineeItem(item, i) {
|
|
|
769
911
|
const meta = join([f('Ordinal', s(item, 'ordinal')), f('State', s(item, 'state'))]);
|
|
770
912
|
if (meta)
|
|
771
913
|
lines.push(meta);
|
|
772
|
-
return lines
|
|
914
|
+
return withRowRest(item, lines, NOMINEE_ITEM_KEYS);
|
|
773
915
|
}
|
|
916
|
+
const NOMINEE_ITEM_KEYS = new Set([
|
|
917
|
+
'prefix',
|
|
918
|
+
'firstName',
|
|
919
|
+
'middleName',
|
|
920
|
+
'lastName',
|
|
921
|
+
'suffix',
|
|
922
|
+
'ordinal',
|
|
923
|
+
'state',
|
|
924
|
+
]);
|
|
774
925
|
/** Nomination hearing items — shape {chamber, citation, date, jacketNumber, number, partNumber, errata?}. */
|
|
775
926
|
function renderNominationHearingItem(item, i) {
|
|
776
927
|
const citation = s(item, 'citation');
|
|
@@ -780,7 +931,7 @@ function renderNominationHearingItem(item, i) {
|
|
|
780
931
|
const partNumber = s(item, 'partNumber');
|
|
781
932
|
const meta = join([
|
|
782
933
|
f('Chamber', s(item, 'chamber')),
|
|
783
|
-
f('Date', s(item, 'date')
|
|
934
|
+
f('Date', s(item, 'date')),
|
|
784
935
|
f('Number', number),
|
|
785
936
|
partNumber && partNumber !== '1' && partNumber !== '01' ? f('Part', partNumber) : undefined,
|
|
786
937
|
f('Jacket', s(item, 'jacketNumber')),
|
|
@@ -788,8 +939,17 @@ function renderNominationHearingItem(item, i) {
|
|
|
788
939
|
]);
|
|
789
940
|
if (meta)
|
|
790
941
|
lines.push(meta);
|
|
791
|
-
return lines
|
|
942
|
+
return withRowRest(item, lines, NOMINATION_HEARING_ROW_KEYS);
|
|
792
943
|
}
|
|
944
|
+
const NOMINATION_HEARING_ROW_KEYS = new Set([
|
|
945
|
+
'citation',
|
|
946
|
+
'number',
|
|
947
|
+
'chamber',
|
|
948
|
+
'date',
|
|
949
|
+
'partNumber',
|
|
950
|
+
'jacketNumber',
|
|
951
|
+
'errata',
|
|
952
|
+
]);
|
|
793
953
|
/** Dispatch nomination list rows to the right renderer by shape signal. */
|
|
794
954
|
function pickNominationListRenderer(first) {
|
|
795
955
|
/** Action rows share the bill-action shape (actionDate, text, type, actionCode);
|
|
@@ -825,10 +985,9 @@ function renderNominationDetail(item) {
|
|
|
825
985
|
]);
|
|
826
986
|
if (meta)
|
|
827
987
|
lines.push(meta);
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
lines.push(`**Latest Action:** ${[actionDate, actionText].filter(Boolean).join(' — ')}`);
|
|
988
|
+
const latestAction = latestActionLine(item);
|
|
989
|
+
if (latestAction)
|
|
990
|
+
lines.push(latestAction);
|
|
832
991
|
const subResources = [];
|
|
833
992
|
for (const key of ['actions', 'committees', 'hearings']) {
|
|
834
993
|
const sub = item[key];
|
|
@@ -946,9 +1105,10 @@ const HEADER_ROLL_VOTE_KEYS = new Set([
|
|
|
946
1105
|
]);
|
|
947
1106
|
/** One member's position: "Warren Davidson (R-OH) → Nay". */
|
|
948
1107
|
function renderMemberVoteRow(r) {
|
|
1108
|
+
const named = new Set(MEMBER_VOTE_ROW_KEYS);
|
|
949
1109
|
const first = s(r, 'firstName');
|
|
950
1110
|
const last = s(r, 'lastName');
|
|
951
|
-
const name = first && last ? `${first} ${last}` : (last ?? first ??
|
|
1111
|
+
const name = (first && last ? `${first} ${last}` : (last ?? first)) ?? pick(r, named, 'bioguideId') ?? '?';
|
|
952
1112
|
const party = s(r, 'voteParty');
|
|
953
1113
|
const state = s(r, 'voteState');
|
|
954
1114
|
const cast = s(r, 'voteCast');
|
|
@@ -959,8 +1119,11 @@ function renderMemberVoteRow(r) {
|
|
|
959
1119
|
: state
|
|
960
1120
|
? `(${state})`
|
|
961
1121
|
: undefined;
|
|
962
|
-
|
|
1122
|
+
const curated = `- ${[name, loc, cast ? `→ ${cast}` : undefined].filter(Boolean).join(' ')}`;
|
|
1123
|
+
return withUnnamedFields(r, curated, named);
|
|
963
1124
|
}
|
|
1125
|
+
/** `bioguideId` is named only when it stood in for a missing name. */
|
|
1126
|
+
const MEMBER_VOTE_ROW_KEYS = ['firstName', 'lastName', 'voteParty', 'voteState', 'voteCast'];
|
|
964
1127
|
/**
|
|
965
1128
|
* Member voting positions for one roll call — vote-context header, the
|
|
966
1129
|
* `pagination`-derived range footer, then the paginated roster from `data[]`.
|
|
@@ -986,6 +1149,9 @@ function renderVoteMembers(result) {
|
|
|
986
1149
|
const legNum = s(vote, 'legislationNumber');
|
|
987
1150
|
if (legType && legNum)
|
|
988
1151
|
lines.push(`**Legislation:** ${legType} ${legNum}`);
|
|
1152
|
+
const voteRest = renderDetailRest(vote, VOTE_CONTEXT_KEYS);
|
|
1153
|
+
if (voteRest)
|
|
1154
|
+
lines.push('', voteRest);
|
|
989
1155
|
if (rows.length === 0) {
|
|
990
1156
|
lines.push('', total > 0
|
|
991
1157
|
? `_Page is empty — offset is past the end of ${total} member position${total !== 1 ? 's' : ''}._`
|
|
@@ -1003,6 +1169,16 @@ function renderVoteMembers(result) {
|
|
|
1003
1169
|
}
|
|
1004
1170
|
return lines.join('\n');
|
|
1005
1171
|
}
|
|
1172
|
+
/** Fields of the `vote` context sibling the members header already renders. */
|
|
1173
|
+
const VOTE_CONTEXT_KEYS = new Set([
|
|
1174
|
+
'rollCallNumber',
|
|
1175
|
+
'congress',
|
|
1176
|
+
'sessionNumber',
|
|
1177
|
+
'voteQuestion',
|
|
1178
|
+
'result',
|
|
1179
|
+
'legislationType',
|
|
1180
|
+
'legislationNumber',
|
|
1181
|
+
]);
|
|
1006
1182
|
// ── Senate roll call votes (LIS feed) ───────────────────────────────
|
|
1007
1183
|
/**
|
|
1008
1184
|
* Senate vote payloads come from the LIS XML feed, not the Congress.gov JSON API,
|
|
@@ -1032,17 +1208,34 @@ function renderSenateVoteItem(item, i) {
|
|
|
1032
1208
|
const title = s(item, 'title');
|
|
1033
1209
|
if (title)
|
|
1034
1210
|
lines.push(title);
|
|
1035
|
-
return lines
|
|
1211
|
+
return withRowRest(item, lines, SENATE_VOTE_ROW_KEYS);
|
|
1036
1212
|
}
|
|
1213
|
+
/** `chamber` is the dispatch marker, implicit in the Senate heading. */
|
|
1214
|
+
const SENATE_VOTE_ROW_KEYS = new Set([
|
|
1215
|
+
'chamber',
|
|
1216
|
+
'voteNumber',
|
|
1217
|
+
'issue',
|
|
1218
|
+
'result',
|
|
1219
|
+
'question',
|
|
1220
|
+
'measure',
|
|
1221
|
+
'voteDate',
|
|
1222
|
+
'yeas',
|
|
1223
|
+
'nays',
|
|
1224
|
+
'title',
|
|
1225
|
+
]);
|
|
1037
1226
|
/** One Senate member's position — uses the feed's pre-formatted "Baldwin (D-WI)" label. */
|
|
1038
1227
|
function renderSenateMemberRow(r) {
|
|
1039
1228
|
const cast = s(r, 'voteCast');
|
|
1040
1229
|
const full = s(r, 'memberFull');
|
|
1041
|
-
if (full)
|
|
1042
|
-
|
|
1230
|
+
if (full) {
|
|
1231
|
+
/** The feed composes memberFull from the last name, party, and state. */
|
|
1232
|
+
const named = new Set([...SENATE_MEMBER_ROW_KEYS, 'memberFull', 'lastName', 'party', 'state']);
|
|
1233
|
+
return withUnnamedFields(r, `- ${full}${cast ? ` → ${cast}` : ''}`, named);
|
|
1234
|
+
}
|
|
1235
|
+
const named = new Set([...SENATE_MEMBER_ROW_KEYS, 'firstName', 'lastName', 'party', 'state']);
|
|
1043
1236
|
const first = s(r, 'firstName');
|
|
1044
1237
|
const last = s(r, 'lastName');
|
|
1045
|
-
const name = first && last ? `${first} ${last}` : (last ?? first ??
|
|
1238
|
+
const name = (first && last ? `${first} ${last}` : (last ?? first)) ?? pick(r, named, 'lisMemberId') ?? '?';
|
|
1046
1239
|
const party = s(r, 'party');
|
|
1047
1240
|
const state = s(r, 'state');
|
|
1048
1241
|
const loc = party && state
|
|
@@ -1052,8 +1245,10 @@ function renderSenateMemberRow(r) {
|
|
|
1052
1245
|
: state
|
|
1053
1246
|
? `(${state})`
|
|
1054
1247
|
: undefined;
|
|
1055
|
-
|
|
1248
|
+
const curated = `- ${[name, loc, cast ? `→ ${cast}` : undefined].filter(Boolean).join(' ')}`;
|
|
1249
|
+
return withUnnamedFields(r, curated, named);
|
|
1056
1250
|
}
|
|
1251
|
+
const SENATE_MEMBER_ROW_KEYS = ['chamber', 'voteCast'];
|
|
1057
1252
|
/** Senate vote detail — question, result, tally, derived party totals, document/amendment. */
|
|
1058
1253
|
function renderSenateVoteDetail(item) {
|
|
1059
1254
|
const num = s(item, 'voteNumber');
|
|
@@ -1170,6 +1365,9 @@ function renderSenateVoteMembers(result) {
|
|
|
1170
1365
|
], ' — ');
|
|
1171
1366
|
if (context)
|
|
1172
1367
|
lines.push(context);
|
|
1368
|
+
const voteRest = renderDetailRest(vote, SENATE_VOTE_CONTEXT_KEYS);
|
|
1369
|
+
if (voteRest)
|
|
1370
|
+
lines.push('', voteRest);
|
|
1173
1371
|
if (rows.length === 0) {
|
|
1174
1372
|
lines.push('', total > 0
|
|
1175
1373
|
? `_Page is empty — offset is past the end of ${total} member position${total !== 1 ? 's' : ''}._`
|
|
@@ -1185,6 +1383,17 @@ function renderSenateVoteMembers(result) {
|
|
|
1185
1383
|
}
|
|
1186
1384
|
return lines.join('\n');
|
|
1187
1385
|
}
|
|
1386
|
+
/** Fields of the Senate `vote` context sibling the members header already renders. */
|
|
1387
|
+
const SENATE_VOTE_CONTEXT_KEYS = new Set([
|
|
1388
|
+
'chamber',
|
|
1389
|
+
'voteNumber',
|
|
1390
|
+
'congress',
|
|
1391
|
+
'session',
|
|
1392
|
+
'voteQuestionText',
|
|
1393
|
+
'question',
|
|
1394
|
+
'voteResultText',
|
|
1395
|
+
'voteResult',
|
|
1396
|
+
]);
|
|
1188
1397
|
/** Senate payloads carry a `chamber: 'senate'` marker on the envelope, vote, and items. */
|
|
1189
1398
|
function isSenateResult(result) {
|
|
1190
1399
|
if (result.chamber === 'senate')
|
|
@@ -1213,10 +1422,9 @@ function renderBillDetail(item) {
|
|
|
1213
1422
|
]);
|
|
1214
1423
|
if (meta)
|
|
1215
1424
|
lines.push(meta);
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
lines.push(`**Latest Action:** ${[actionDate, actionText].filter(Boolean).join(' — ')}`);
|
|
1425
|
+
const latestAction = latestActionLine(item);
|
|
1426
|
+
if (latestAction)
|
|
1427
|
+
lines.push(latestAction);
|
|
1220
1428
|
const citation = lawCitations(item);
|
|
1221
1429
|
if (citation)
|
|
1222
1430
|
lines.push(`**Law:** ${citation}`);
|
|
@@ -1402,8 +1610,18 @@ function renderCommitteeListItem(item, i) {
|
|
|
1402
1610
|
const url = s(item, 'url');
|
|
1403
1611
|
if (url)
|
|
1404
1612
|
lines.push(`**URL:** ${url}`);
|
|
1405
|
-
return lines
|
|
1613
|
+
return withRowRest(item, lines, COMMITTEE_ROW_KEYS);
|
|
1406
1614
|
}
|
|
1615
|
+
/** `parent` is absent: a subcommittee row's owning committee code has to reach the reader. */
|
|
1616
|
+
const COMMITTEE_ROW_KEYS = new Set([
|
|
1617
|
+
'name',
|
|
1618
|
+
'systemCode',
|
|
1619
|
+
'chamber',
|
|
1620
|
+
'committeeTypeCode',
|
|
1621
|
+
'updateDate',
|
|
1622
|
+
'url',
|
|
1623
|
+
'approximate',
|
|
1624
|
+
]);
|
|
1407
1625
|
/** Committee report list item — citation-first; upstream omits title and bill ref. */
|
|
1408
1626
|
function renderCommitteeReportListItem(item, i) {
|
|
1409
1627
|
const citation = s(item, 'citation');
|
|
@@ -1428,8 +1646,76 @@ function renderCommitteeReportListItem(item, i) {
|
|
|
1428
1646
|
lines.push(meta);
|
|
1429
1647
|
if (url)
|
|
1430
1648
|
lines.push(`**URL:** ${url}`);
|
|
1649
|
+
return withRowRest(item, lines, COMMITTEE_REPORT_ROW_KEYS);
|
|
1650
|
+
}
|
|
1651
|
+
const COMMITTEE_REPORT_ROW_KEYS = new Set([
|
|
1652
|
+
'citation',
|
|
1653
|
+
'type',
|
|
1654
|
+
'number',
|
|
1655
|
+
'part',
|
|
1656
|
+
'congress',
|
|
1657
|
+
'chamber',
|
|
1658
|
+
'updateDate',
|
|
1659
|
+
'url',
|
|
1660
|
+
]);
|
|
1661
|
+
// ── Document Content (the `content` operation) ──────────────────────
|
|
1662
|
+
/**
|
|
1663
|
+
* One character window of a legislative document's text.
|
|
1664
|
+
*
|
|
1665
|
+
* The other renderers in this file reshape upstream JSON records; this one
|
|
1666
|
+
* carries a document body, so the text is emitted verbatim — no entity work, no
|
|
1667
|
+
* whitespace collapsing, no markdown wrapper. GPO's pre-formatted layout (column
|
|
1668
|
+
* alignment, indentation, blank-line structure) *is* the document's structure,
|
|
1669
|
+
* and a code fence would break on the doubled backticks bill text uses for
|
|
1670
|
+
* opening quotation marks.
|
|
1671
|
+
*
|
|
1672
|
+
* The header states the window in both vocabularies: a 1-based human range, and
|
|
1673
|
+
* the 0-based `offset` / `nextOffset` a caller actually feeds back.
|
|
1674
|
+
*/
|
|
1675
|
+
function renderDocumentContent(content) {
|
|
1676
|
+
const total = typeof content.totalCharacters === 'number' ? content.totalCharacters : 0;
|
|
1677
|
+
const offset = typeof content.offset === 'number' ? content.offset : 0;
|
|
1678
|
+
const text = typeof content.text === 'string' ? content.text : '';
|
|
1679
|
+
const nextOffset = typeof content.nextOffset === 'number' ? content.nextOffset : null;
|
|
1680
|
+
const truncated = content.truncated === true;
|
|
1681
|
+
const title = s(content, 'documentTitle') ?? 'Document';
|
|
1682
|
+
const lines = [`# ${title}`];
|
|
1683
|
+
const range = text.length > 0
|
|
1684
|
+
? `**Characters ${(offset + 1).toLocaleString('en-US')}–${(offset + text.length).toLocaleString('en-US')} of ${total.toLocaleString('en-US')}**`
|
|
1685
|
+
: `**0 characters**`;
|
|
1686
|
+
lines.push(join([
|
|
1687
|
+
f('Format', s(content, 'format')),
|
|
1688
|
+
range,
|
|
1689
|
+
`**Truncated:** ${truncated}`,
|
|
1690
|
+
`offset: ${offset}`,
|
|
1691
|
+
nextOffset != null ? `next offset: ${nextOffset}` : '_end of document_',
|
|
1692
|
+
]));
|
|
1693
|
+
const sourceUrl = s(content, 'sourceUrl');
|
|
1694
|
+
if (sourceUrl)
|
|
1695
|
+
lines.push(`**Source:** ${sourceUrl}`);
|
|
1696
|
+
const rest = renderDetailRest(content, DOCUMENT_CONTENT_KEYS);
|
|
1697
|
+
if (rest)
|
|
1698
|
+
lines.push('', rest);
|
|
1699
|
+
lines.push('', text || '_This document is empty._');
|
|
1431
1700
|
return lines.join('\n');
|
|
1432
1701
|
}
|
|
1702
|
+
const DOCUMENT_CONTENT_KEYS = new Set([
|
|
1703
|
+
'documentTitle',
|
|
1704
|
+
'format',
|
|
1705
|
+
'sourceUrl',
|
|
1706
|
+
'text',
|
|
1707
|
+
'totalCharacters',
|
|
1708
|
+
'offset',
|
|
1709
|
+
'truncated',
|
|
1710
|
+
'nextOffset',
|
|
1711
|
+
]);
|
|
1712
|
+
/** The `content` payload, when the result carries one. */
|
|
1713
|
+
function documentContentOf(result) {
|
|
1714
|
+
const content = result.content;
|
|
1715
|
+
return typeof content === 'object' && content !== null && !Array.isArray(content)
|
|
1716
|
+
? content
|
|
1717
|
+
: undefined;
|
|
1718
|
+
}
|
|
1433
1719
|
// ── Per-Tool Format Exports ─────────────────────────────────────────
|
|
1434
1720
|
function makeFormatter(detailKeys, itemRenderer, detailRenderer) {
|
|
1435
1721
|
return (result) => {
|
|
@@ -1449,6 +1735,9 @@ function makeFormatter(detailKeys, itemRenderer, detailRenderer) {
|
|
|
1449
1735
|
}
|
|
1450
1736
|
/** Bill browse, detail, and sub-resources (actions, amendments, cosponsors, etc.). */
|
|
1451
1737
|
export function formatBills(result) {
|
|
1738
|
+
const content = documentContentOf(result);
|
|
1739
|
+
if (content)
|
|
1740
|
+
return tb(renderDocumentContent(content));
|
|
1452
1741
|
if (Array.isArray(result.data)) {
|
|
1453
1742
|
const first = result.data[0];
|
|
1454
1743
|
const firstRecord = typeof first === 'object' && first !== null ? first : undefined;
|
|
@@ -1465,7 +1754,8 @@ export function formatBills(result) {
|
|
|
1465
1754
|
* survive into the rendered Markdown.
|
|
1466
1755
|
*/
|
|
1467
1756
|
function renderBillSubresourceSummaryItem(item, i) {
|
|
1468
|
-
const
|
|
1757
|
+
const named = new Set(BILL_SUMMARY_SUBRESOURCE_ROW_KEYS);
|
|
1758
|
+
const version = pick(item, named, 'actionDesc', 'versionCode') ?? 'Summary';
|
|
1469
1759
|
const actionDate = s(item, 'actionDate');
|
|
1470
1760
|
const updated = s(item, 'updateDate');
|
|
1471
1761
|
const lines = [`### ${i + 1}. ${version}`];
|
|
@@ -1475,8 +1765,9 @@ function renderBillSubresourceSummaryItem(item, i) {
|
|
|
1475
1765
|
const text = typeof item.text === 'string' ? htmlToMarkdown(item.text) : '';
|
|
1476
1766
|
if (text)
|
|
1477
1767
|
lines.push('', text);
|
|
1478
|
-
return lines
|
|
1768
|
+
return withRowRest(item, lines, named);
|
|
1479
1769
|
}
|
|
1770
|
+
const BILL_SUMMARY_SUBRESOURCE_ROW_KEYS = ['actionDate', 'updateDate', 'text'];
|
|
1480
1771
|
function pickBillListRenderer(first) {
|
|
1481
1772
|
if ('title' in first && 'number' in first)
|
|
1482
1773
|
return renderBillItem;
|
|
@@ -1522,8 +1813,19 @@ function renderSearchBillItem(item, i) {
|
|
|
1522
1813
|
const summary = s(item, 'summaryPreview');
|
|
1523
1814
|
if (summary)
|
|
1524
1815
|
lines.push('', summary);
|
|
1525
|
-
return lines
|
|
1816
|
+
return withRowRest(item, lines, SEARCH_BILL_ROW_KEYS);
|
|
1526
1817
|
}
|
|
1818
|
+
const SEARCH_BILL_ROW_KEYS = new Set([
|
|
1819
|
+
'billId',
|
|
1820
|
+
'billType',
|
|
1821
|
+
'billNumber',
|
|
1822
|
+
'congress',
|
|
1823
|
+
'title',
|
|
1824
|
+
'originChamber',
|
|
1825
|
+
'latestActionDate',
|
|
1826
|
+
'latestActionText',
|
|
1827
|
+
'summaryPreview',
|
|
1828
|
+
]);
|
|
1527
1829
|
/** Local bill keyword search over the FTS mirror. */
|
|
1528
1830
|
export const formatSearchBills = makeFormatter([], renderSearchBillItem);
|
|
1529
1831
|
/** Member browse, detail, and sponsored/cosponsored legislation. */
|
|
@@ -1578,6 +1880,9 @@ export function formatCommittees(result) {
|
|
|
1578
1880
|
}
|
|
1579
1881
|
/** Committee reports — list, detail, and text. */
|
|
1580
1882
|
export function formatCommitteeReports(result) {
|
|
1883
|
+
const content = documentContentOf(result);
|
|
1884
|
+
if (content)
|
|
1885
|
+
return tb(renderDocumentContent(content));
|
|
1581
1886
|
if (Array.isArray(result.data))
|
|
1582
1887
|
return tb(renderList(result, renderCommitteeReportListItem));
|
|
1583
1888
|
if (Array.isArray(result.text)) {
|
|
@@ -1600,6 +1905,9 @@ export function formatCrsReports(result) {
|
|
|
1600
1905
|
}
|
|
1601
1906
|
/** Daily Congressional Record. Dispatches between volumes/issues and flattened articles. */
|
|
1602
1907
|
export function formatDailyRecord(result) {
|
|
1908
|
+
const content = documentContentOf(result);
|
|
1909
|
+
if (content)
|
|
1910
|
+
return tb(renderDocumentContent(content));
|
|
1603
1911
|
if (Array.isArray(result.data)) {
|
|
1604
1912
|
const first = result.data[0];
|
|
1605
1913
|
const firstRecord = typeof first === 'object' && first !== null ? first : undefined;
|