@cyanheads/congressgov-mcp-server 0.3.18 → 0.3.20
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 +1 -1
- package/CLAUDE.md +1 -1
- package/README.md +5 -3
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts +12 -1
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.js +28 -9
- package/dist/mcp-server/tools/definitions/bill-lookup.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/bill-summaries.tool.d.ts +8 -1
- package/dist/mcp-server/tools/definitions/bill-summaries.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/bill-summaries.tool.js +13 -5
- package/dist/mcp-server/tools/definitions/bill-summaries.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-lookup.tool.d.ts +8 -1
- package/dist/mcp-server/tools/definitions/committee-lookup.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-lookup.tool.js +37 -12
- package/dist/mcp-server/tools/definitions/committee-lookup.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts +8 -1
- package/dist/mcp-server/tools/definitions/committee-reports.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/committee-reports.tool.js +9 -2
- package/dist/mcp-server/tools/definitions/committee-reports.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/crs-reports.tool.d.ts +8 -1
- package/dist/mcp-server/tools/definitions/crs-reports.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/crs-reports.tool.js +3 -2
- package/dist/mcp-server/tools/definitions/crs-reports.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts +8 -1
- package/dist/mcp-server/tools/definitions/daily-record.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/daily-record.tool.js +11 -4
- package/dist/mcp-server/tools/definitions/daily-record.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/enacted-laws.tool.d.ts +8 -1
- package/dist/mcp-server/tools/definitions/enacted-laws.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/enacted-laws.tool.js +10 -3
- package/dist/mcp-server/tools/definitions/enacted-laws.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/member-lookup.tool.d.ts +8 -1
- package/dist/mcp-server/tools/definitions/member-lookup.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/member-lookup.tool.js +15 -3
- package/dist/mcp-server/tools/definitions/member-lookup.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/roll-votes.tool.d.ts +12 -1
- package/dist/mcp-server/tools/definitions/roll-votes.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/roll-votes.tool.js +68 -3
- package/dist/mcp-server/tools/definitions/roll-votes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/senate-nominations.tool.d.ts +8 -1
- package/dist/mcp-server/tools/definitions/senate-nominations.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/senate-nominations.tool.js +26 -7
- package/dist/mcp-server/tools/definitions/senate-nominations.tool.js.map +1 -1
- package/dist/mcp-server/tools/format-helpers.d.ts +3 -3
- package/dist/mcp-server/tools/format-helpers.d.ts.map +1 -1
- package/dist/mcp-server/tools/format-helpers.js +657 -42
- package/dist/mcp-server/tools/format-helpers.js.map +1 -1
- package/dist/mcp-server/tools/tool-helpers.d.ts +34 -0
- package/dist/mcp-server/tools/tool-helpers.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-helpers.js +96 -0
- package/dist/mcp-server/tools/tool-helpers.js.map +1 -1
- package/dist/services/congress-api/congress-api-service.d.ts +1 -1
- package/dist/services/congress-api/congress-api-service.d.ts.map +1 -1
- package/dist/services/congress-api/congress-api-service.js +69 -26
- package/dist/services/congress-api/congress-api-service.js.map +1 -1
- package/dist/services/congress-api/types.d.ts +1 -0
- package/dist/services/congress-api/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/server.json +3 -3
|
@@ -14,16 +14,31 @@
|
|
|
14
14
|
function tb(content) {
|
|
15
15
|
return [{ type: 'text', text: content }];
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Strip HTML to plain text while preserving paragraph and line breaks. Upstream
|
|
19
|
+
* summary fields and other narrative bodies ship as HTML; we want the visible
|
|
20
|
+
* structure (paragraph boundaries) to survive into the rendered Markdown.
|
|
21
|
+
*
|
|
22
|
+
* Inline contexts that need single-line output should pass `{ inline: true }`.
|
|
23
|
+
*/
|
|
24
|
+
function stripHtml(html, { inline = false } = {}) {
|
|
25
|
+
const text = html
|
|
26
|
+
.replace(/<\s*br\s*\/?\s*>/gi, '\n')
|
|
27
|
+
.replace(/<\s*\/p\s*>/gi, '\n\n')
|
|
19
28
|
.replace(/<[^>]*>/g, '')
|
|
20
29
|
.replace(/ /g, ' ')
|
|
21
30
|
.replace(/'/g, "'")
|
|
22
31
|
.replace(/&/g, '&')
|
|
23
32
|
.replace(/</g, '<')
|
|
24
33
|
.replace(/>/g, '>')
|
|
25
|
-
.replace(/"/g, '"')
|
|
26
|
-
|
|
34
|
+
.replace(/"/g, '"');
|
|
35
|
+
if (inline)
|
|
36
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
37
|
+
return text
|
|
38
|
+
.replace(/[ \t]+/g, ' ')
|
|
39
|
+
.replace(/\n[ \t]+/g, '\n')
|
|
40
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
41
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
27
42
|
.trim();
|
|
28
43
|
}
|
|
29
44
|
/**
|
|
@@ -54,7 +69,7 @@ function htmlToMarkdown(html) {
|
|
|
54
69
|
.replace(/\n{3,}/g, '\n\n')
|
|
55
70
|
.trim();
|
|
56
71
|
}
|
|
57
|
-
/** Safe deep access
|
|
72
|
+
/** Safe deep access for compact field display — collapses whitespace to a single line. */
|
|
58
73
|
function s(obj, ...path) {
|
|
59
74
|
let cur = obj;
|
|
60
75
|
for (const key of path) {
|
|
@@ -63,7 +78,7 @@ function s(obj, ...path) {
|
|
|
63
78
|
cur = cur[key];
|
|
64
79
|
}
|
|
65
80
|
if (typeof cur === 'string')
|
|
66
|
-
return stripHtml(cur);
|
|
81
|
+
return stripHtml(cur, { inline: true });
|
|
67
82
|
if (typeof cur === 'number')
|
|
68
83
|
return String(cur);
|
|
69
84
|
return;
|
|
@@ -85,17 +100,38 @@ function pagHeader(result) {
|
|
|
85
100
|
return `**${count} result${count !== 1 ? 's' : ''}**${next != null ? ` | next offset: ${next}` : ''}`;
|
|
86
101
|
}
|
|
87
102
|
/** Render a paginated list with header and per-item rendering. */
|
|
88
|
-
function renderList(result, renderItem) {
|
|
103
|
+
function renderList(result, renderItem, query) {
|
|
89
104
|
const items = (result.data ?? []);
|
|
90
105
|
const header = pagHeader(result);
|
|
106
|
+
const queryLine = query ? `_Search: ${query}_` : undefined;
|
|
91
107
|
if (items.length === 0) {
|
|
92
|
-
|
|
108
|
+
/** Distinguish "0 total" from "page is past the end" — the header alone reads
|
|
109
|
+
* as "N items returned but didn't render" when count > 0 and the page is empty. */
|
|
110
|
+
const p = result.pagination;
|
|
111
|
+
const total = p?.count ?? 0;
|
|
112
|
+
const pageHint = total > 0
|
|
113
|
+
? `_Page is empty — offset is past the end of ${total} total item${total !== 1 ? 's' : ''}._`
|
|
114
|
+
: query
|
|
115
|
+
? '_No matching results. Broaden the filter or check the criteria above._'
|
|
116
|
+
: '_No matching results._';
|
|
117
|
+
return [queryLine, header, '', pageHint].filter(Boolean).join('\n\n');
|
|
93
118
|
}
|
|
94
119
|
const renderer = renderItem ?? renderGenericItem;
|
|
95
120
|
const rendered = items.map((item, i) => typeof item === 'object' && item !== null
|
|
96
121
|
? renderer(item, i)
|
|
97
122
|
: `${i + 1}. ${String(item)}`);
|
|
98
|
-
return [header, '', ...rendered].join('\n\n');
|
|
123
|
+
return [queryLine, header, '', ...rendered].filter(Boolean).join('\n\n');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Render an object's remaining fields after a hand-built header. Skips keys
|
|
127
|
+
* already consumed by the header so they don't repeat in the body.
|
|
128
|
+
*/
|
|
129
|
+
function renderDetailRest(obj, skip) {
|
|
130
|
+
const filtered = {};
|
|
131
|
+
for (const [k, v] of Object.entries(obj))
|
|
132
|
+
if (!skip.has(k))
|
|
133
|
+
filtered[k] = v;
|
|
134
|
+
return renderDetail(filtered);
|
|
99
135
|
}
|
|
100
136
|
/** Render any object as structured markdown. Used for detail views. */
|
|
101
137
|
function renderDetail(obj) {
|
|
@@ -118,13 +154,13 @@ function renderDetail(obj) {
|
|
|
118
154
|
if (val == null || val === '')
|
|
119
155
|
continue;
|
|
120
156
|
if (typeof val === 'string') {
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
157
|
+
const inline = stripHtml(val, { inline: true });
|
|
158
|
+
if (inline.length > 300) {
|
|
123
159
|
lines.push(`**${key}:**`);
|
|
124
|
-
lines.push(
|
|
160
|
+
lines.push(stripHtml(val));
|
|
125
161
|
}
|
|
126
162
|
else {
|
|
127
|
-
lines.push(`**${key}:** ${
|
|
163
|
+
lines.push(`**${key}:** ${inline}`);
|
|
128
164
|
}
|
|
129
165
|
}
|
|
130
166
|
else if (typeof val === 'number' || typeof val === 'boolean') {
|
|
@@ -172,7 +208,7 @@ function renderDetail(obj) {
|
|
|
172
208
|
if (v2 == null || v2 === '')
|
|
173
209
|
continue;
|
|
174
210
|
if (typeof v2 === 'string')
|
|
175
|
-
lines.push(` **${k2}:** ${stripHtml(v2)}`);
|
|
211
|
+
lines.push(` **${k2}:** ${stripHtml(v2, { inline: true })}`);
|
|
176
212
|
else if (typeof v2 === 'number' || typeof v2 === 'boolean')
|
|
177
213
|
lines.push(` **${k2}:** ${v2}`);
|
|
178
214
|
else if (typeof v2 === 'object' && v2)
|
|
@@ -203,13 +239,13 @@ function renderGenericItem(item, index) {
|
|
|
203
239
|
if (HEADING_FIELDS.has(key))
|
|
204
240
|
continue;
|
|
205
241
|
if (typeof val === 'string') {
|
|
206
|
-
const
|
|
207
|
-
if (
|
|
242
|
+
const inline = stripHtml(val, { inline: true });
|
|
243
|
+
if (inline.length > 300) {
|
|
208
244
|
lines.push(`**${key}:**`);
|
|
209
|
-
lines.push(
|
|
245
|
+
lines.push(stripHtml(val));
|
|
210
246
|
}
|
|
211
247
|
else {
|
|
212
|
-
lines.push(`**${key}:** ${
|
|
248
|
+
lines.push(`**${key}:** ${inline}`);
|
|
213
249
|
}
|
|
214
250
|
}
|
|
215
251
|
else if (typeof val === 'number' || typeof val === 'boolean') {
|
|
@@ -262,7 +298,7 @@ function renderInline(obj) {
|
|
|
262
298
|
if (val == null || val === '')
|
|
263
299
|
continue;
|
|
264
300
|
if (typeof val === 'string') {
|
|
265
|
-
const cleaned = stripHtml(val);
|
|
301
|
+
const cleaned = stripHtml(val, { inline: true });
|
|
266
302
|
const preview = cleaned.length > 120 ? `${cleaned.slice(0, 117)}...` : cleaned;
|
|
267
303
|
parts.push(`${key}: ${preview}`);
|
|
268
304
|
}
|
|
@@ -390,7 +426,8 @@ function renderCrsReportItem(item, i) {
|
|
|
390
426
|
]);
|
|
391
427
|
if (meta)
|
|
392
428
|
lines.push(meta);
|
|
393
|
-
|
|
429
|
+
if (summary)
|
|
430
|
+
lines.push(summary);
|
|
394
431
|
if (url)
|
|
395
432
|
lines.push(`**URL:** ${url}`);
|
|
396
433
|
return lines.join('\n');
|
|
@@ -522,14 +559,533 @@ function renderCommitteeReportTextItem(item, i) {
|
|
|
522
559
|
}
|
|
523
560
|
return lines.join('\n');
|
|
524
561
|
}
|
|
562
|
+
/** Member-sponsored amendments — `type`/`title` are null upstream; identify by `amendmentNumber`. */
|
|
563
|
+
function renderAmendmentItem(item, i) {
|
|
564
|
+
const number = s(item, 'amendmentNumber');
|
|
565
|
+
const url = s(item, 'url') ?? '';
|
|
566
|
+
/** URL path carries the chamber prefix (samdt / hamdt) we need for a readable type label. */
|
|
567
|
+
const amdMatch = url.match(/\/amendment\/(\d+)\/(samdt|hamdt|suamdt|huamdt)\//i);
|
|
568
|
+
const typeCode = amdMatch?.[2]?.toLowerCase();
|
|
569
|
+
const chamber = typeCode === 'samdt' || typeCode === 'suamdt'
|
|
570
|
+
? 'Senate Amendment'
|
|
571
|
+
: typeCode === 'hamdt' || typeCode === 'huamdt'
|
|
572
|
+
? 'House Amendment'
|
|
573
|
+
: 'Amendment';
|
|
574
|
+
const heading = number ? `${chamber} ${number}` : 'Amendment';
|
|
575
|
+
const lines = [`### ${i + 1}. ${heading}`];
|
|
576
|
+
const meta = join([
|
|
577
|
+
f('Congress', s(item, 'congress')),
|
|
578
|
+
f('Introduced', s(item, 'introducedDate')),
|
|
579
|
+
]);
|
|
580
|
+
if (meta)
|
|
581
|
+
lines.push(meta);
|
|
582
|
+
const actionDate = s(item, 'latestAction', 'actionDate');
|
|
583
|
+
const actionText = s(item, 'latestAction', 'text');
|
|
584
|
+
if (actionDate || actionText)
|
|
585
|
+
lines.push(`**Latest Action:** ${[actionDate, actionText].filter(Boolean).join(' — ')}`);
|
|
586
|
+
if (url)
|
|
587
|
+
lines.push(`**URL:** ${url}`);
|
|
588
|
+
return lines.join('\n');
|
|
589
|
+
}
|
|
590
|
+
/** Bill text versions — heading from `type` (e.g. "Enrolled Bill"), formats[] as labeled URLs. */
|
|
591
|
+
function renderBillTextItem(item, i) {
|
|
592
|
+
const type = s(item, 'type') ?? 'Bill Text';
|
|
593
|
+
const date = s(item, 'date');
|
|
594
|
+
const lines = [`### ${i + 1}. ${type}`];
|
|
595
|
+
if (date)
|
|
596
|
+
lines.push(`**Date:** ${date}`);
|
|
597
|
+
const formats = item.formats;
|
|
598
|
+
if (Array.isArray(formats)) {
|
|
599
|
+
for (const fmt of formats) {
|
|
600
|
+
const fType = s(fmt, 'type');
|
|
601
|
+
const fUrl = s(fmt, 'url');
|
|
602
|
+
if (fType && fUrl)
|
|
603
|
+
lines.push(`**${fType}:** ${fUrl}`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return lines.join('\n');
|
|
607
|
+
}
|
|
608
|
+
/** Nomination type wrapper: `{isCivilian: true}` / `{isMilitary: true}` → readable label. */
|
|
609
|
+
function nominationTypeLabel(raw) {
|
|
610
|
+
if (!raw || typeof raw !== 'object')
|
|
611
|
+
return;
|
|
612
|
+
const t = raw;
|
|
613
|
+
if (t.isCivilian === true)
|
|
614
|
+
return 'Civilian';
|
|
615
|
+
if (t.isMilitary === true)
|
|
616
|
+
return 'Military';
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
function nominationHeading(item) {
|
|
620
|
+
const citation = s(item, 'citation');
|
|
621
|
+
if (citation)
|
|
622
|
+
return citation;
|
|
623
|
+
const number = s(item, 'number');
|
|
624
|
+
const partNumber = s(item, 'partNumber');
|
|
625
|
+
if (number && partNumber && partNumber !== '00')
|
|
626
|
+
return `PN${number}-${Number(partNumber)}`;
|
|
627
|
+
if (number)
|
|
628
|
+
return `PN${number}`;
|
|
629
|
+
return 'Nomination';
|
|
630
|
+
}
|
|
631
|
+
function renderNominationListItem(item, i) {
|
|
632
|
+
const heading = nominationHeading(item);
|
|
633
|
+
const type = nominationTypeLabel(item.nominationType);
|
|
634
|
+
const lines = [`### ${i + 1}. ${heading}`];
|
|
635
|
+
const description = s(item, 'description');
|
|
636
|
+
if (description)
|
|
637
|
+
lines.push(description);
|
|
638
|
+
const meta = join([
|
|
639
|
+
f('Congress', s(item, 'congress')),
|
|
640
|
+
f('Type', type),
|
|
641
|
+
f('Received', s(item, 'receivedDate')),
|
|
642
|
+
f('Authority Date', s(item, 'authorityDate')),
|
|
643
|
+
f('Updated', s(item, 'updateDate')),
|
|
644
|
+
]);
|
|
645
|
+
if (meta)
|
|
646
|
+
lines.push(meta);
|
|
647
|
+
const actionDate = s(item, 'latestAction', 'actionDate');
|
|
648
|
+
const actionText = s(item, 'latestAction', 'text');
|
|
649
|
+
if (actionDate || actionText)
|
|
650
|
+
lines.push(`**Latest Action:** ${[actionDate, actionText].filter(Boolean).join(' — ')}`);
|
|
651
|
+
const url = s(item, 'url');
|
|
652
|
+
if (url)
|
|
653
|
+
lines.push(`**URL:** ${url}`);
|
|
654
|
+
return lines.join('\n');
|
|
655
|
+
}
|
|
656
|
+
/** Nomination committee items — shape {name, systemCode, chamber, type, activities[], url}. */
|
|
657
|
+
function renderNominationCommitteeItem(item, i) {
|
|
658
|
+
const name = s(item, 'name') ?? s(item, 'systemCode') ?? 'Committee';
|
|
659
|
+
const lines = [`### ${i + 1}. ${name}`];
|
|
660
|
+
const meta = join([
|
|
661
|
+
f('Code', s(item, 'systemCode')),
|
|
662
|
+
f('Chamber', s(item, 'chamber')),
|
|
663
|
+
f('Type', s(item, 'type')),
|
|
664
|
+
]);
|
|
665
|
+
if (meta)
|
|
666
|
+
lines.push(meta);
|
|
667
|
+
const activities = item.activities;
|
|
668
|
+
if (Array.isArray(activities) && activities.length > 0) {
|
|
669
|
+
lines.push('**Activities:**');
|
|
670
|
+
for (const a of activities) {
|
|
671
|
+
const date = s(a, 'date')?.slice(0, 10);
|
|
672
|
+
const aname = s(a, 'name');
|
|
673
|
+
lines.push(`- ${[date, aname].filter(Boolean).join(' — ')}`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
const url = s(item, 'url');
|
|
677
|
+
if (url)
|
|
678
|
+
lines.push(`**URL:** ${url}`);
|
|
679
|
+
return lines.join('\n');
|
|
680
|
+
}
|
|
681
|
+
/** Individual nominee items — shape {firstName, middleName, lastName, ordinal, state, prefix?, suffix?}. */
|
|
682
|
+
function renderNomineeItem(item, i) {
|
|
683
|
+
const prefix = s(item, 'prefix');
|
|
684
|
+
const first = s(item, 'firstName');
|
|
685
|
+
const middle = s(item, 'middleName');
|
|
686
|
+
const last = s(item, 'lastName');
|
|
687
|
+
const suffix = s(item, 'suffix');
|
|
688
|
+
const name = [prefix, first, middle, last, suffix].filter(Boolean).join(' ').trim();
|
|
689
|
+
const heading = name || 'Nominee';
|
|
690
|
+
const lines = [`### ${i + 1}. ${heading}`];
|
|
691
|
+
const meta = join([f('Ordinal', s(item, 'ordinal')), f('State', s(item, 'state'))]);
|
|
692
|
+
if (meta)
|
|
693
|
+
lines.push(meta);
|
|
694
|
+
return lines.join('\n');
|
|
695
|
+
}
|
|
696
|
+
/** Nomination hearing items — shape {chamber, citation, date, jacketNumber, number, partNumber, errata?}. */
|
|
697
|
+
function renderNominationHearingItem(item, i) {
|
|
698
|
+
const citation = s(item, 'citation');
|
|
699
|
+
const number = s(item, 'number');
|
|
700
|
+
const heading = citation ?? (number ? `Hearing ${number}` : 'Hearing');
|
|
701
|
+
const lines = [`### ${i + 1}. ${heading}`];
|
|
702
|
+
const partNumber = s(item, 'partNumber');
|
|
703
|
+
const meta = join([
|
|
704
|
+
f('Chamber', s(item, 'chamber')),
|
|
705
|
+
f('Date', s(item, 'date')?.slice(0, 10)),
|
|
706
|
+
f('Number', number),
|
|
707
|
+
partNumber && partNumber !== '1' && partNumber !== '01' ? f('Part', partNumber) : undefined,
|
|
708
|
+
f('Jacket', s(item, 'jacketNumber')),
|
|
709
|
+
s(item, 'errata') === 'Y' ? '_Errata_' : undefined,
|
|
710
|
+
]);
|
|
711
|
+
if (meta)
|
|
712
|
+
lines.push(meta);
|
|
713
|
+
return lines.join('\n');
|
|
714
|
+
}
|
|
715
|
+
/** Dispatch nomination list rows to the right renderer by shape signal. */
|
|
716
|
+
function pickNominationListRenderer(first) {
|
|
717
|
+
/** Action rows share the bill-action shape (actionDate, text, type, actionCode);
|
|
718
|
+
* bill-specific extensions (committees, sourceSystem) are absent and no-op. */
|
|
719
|
+
if ('actionDate' in first && 'text' in first)
|
|
720
|
+
return renderBillActionItem;
|
|
721
|
+
/** Nominee rows: firstName/lastName, or ordinal + state without citation. */
|
|
722
|
+
if ('firstName' in first || 'lastName' in first)
|
|
723
|
+
return renderNomineeItem;
|
|
724
|
+
/** Committee rows: systemCode + name. */
|
|
725
|
+
if ('systemCode' in first && 'name' in first)
|
|
726
|
+
return renderNominationCommitteeItem;
|
|
727
|
+
/** Hearing rows: jacketNumber is unique to hearings. */
|
|
728
|
+
if ('jacketNumber' in first)
|
|
729
|
+
return renderNominationHearingItem;
|
|
730
|
+
/** Default: nomination list rows (citation/number/partNumber + description/nominationType). */
|
|
731
|
+
return renderNominationListItem;
|
|
732
|
+
}
|
|
733
|
+
function renderNominationDetail(item) {
|
|
734
|
+
const heading = nominationHeading(item);
|
|
735
|
+
const type = nominationTypeLabel(item.nominationType);
|
|
736
|
+
const lines = [`# ${heading}`];
|
|
737
|
+
const description = s(item, 'description');
|
|
738
|
+
if (description)
|
|
739
|
+
lines.push(description);
|
|
740
|
+
const meta = join([
|
|
741
|
+
f('Congress', s(item, 'congress')),
|
|
742
|
+
f('Type', type),
|
|
743
|
+
f('Part Number', s(item, 'partNumber')),
|
|
744
|
+
f('Received', s(item, 'receivedDate')),
|
|
745
|
+
f('Authority Date', s(item, 'authorityDate')),
|
|
746
|
+
f('Updated', s(item, 'updateDate')),
|
|
747
|
+
]);
|
|
748
|
+
if (meta)
|
|
749
|
+
lines.push(meta);
|
|
750
|
+
const actionDate = s(item, 'latestAction', 'actionDate');
|
|
751
|
+
const actionText = s(item, 'latestAction', 'text');
|
|
752
|
+
if (actionDate || actionText)
|
|
753
|
+
lines.push(`**Latest Action:** ${[actionDate, actionText].filter(Boolean).join(' — ')}`);
|
|
754
|
+
const subResources = [];
|
|
755
|
+
for (const key of ['actions', 'committees', 'hearings']) {
|
|
756
|
+
const sub = item[key];
|
|
757
|
+
if (sub && typeof sub.count === 'number' && sub.count > 0)
|
|
758
|
+
subResources.push(`${sub.count} ${key}`);
|
|
759
|
+
}
|
|
760
|
+
if (subResources.length)
|
|
761
|
+
lines.push(`**Available:** ${subResources.join(', ')}`);
|
|
762
|
+
const nominees = item.nominees;
|
|
763
|
+
if (Array.isArray(nominees) && nominees.length > 0) {
|
|
764
|
+
lines.push(`\n**Nominees (${nominees.length}):**`);
|
|
765
|
+
for (const n of nominees.slice(0, 20)) {
|
|
766
|
+
const ord = s(n, 'ordinal');
|
|
767
|
+
const count = s(n, 'nomineeCount');
|
|
768
|
+
const org = s(n, 'organization');
|
|
769
|
+
const title = s(n, 'positionTitle');
|
|
770
|
+
const parts = [
|
|
771
|
+
ord ? `Ord ${ord}` : undefined,
|
|
772
|
+
count ? `${count} nominee(s)` : undefined,
|
|
773
|
+
org,
|
|
774
|
+
title,
|
|
775
|
+
].filter(Boolean);
|
|
776
|
+
lines.push(`- ${parts.join(' — ')}`);
|
|
777
|
+
}
|
|
778
|
+
if (nominees.length > 20)
|
|
779
|
+
lines.push(`- _...${nominees.length - 20} more_`);
|
|
780
|
+
}
|
|
781
|
+
else if (s(item, 'partNumber') === '00') {
|
|
782
|
+
/** Parent nominations (partNumber=00) have no nominees array. Sub-resources
|
|
783
|
+
* also return 0 results — they live on the partitioned children (PN851-1, PN851-2, …). */
|
|
784
|
+
lines.push('\n_This is a parent nomination. Individual nominees and confirmation actions live on partitioned children (e.g. `PN851-1`, `PN851-2`). Use the partitioned form for `actions`, `committees`, `hearings`, or `nominees`._');
|
|
785
|
+
}
|
|
786
|
+
const url = s(item, 'url');
|
|
787
|
+
if (url)
|
|
788
|
+
lines.push(`\n**URL:** ${url}`);
|
|
789
|
+
return lines.join('\n');
|
|
790
|
+
}
|
|
791
|
+
/** Roll call vote detail — question, result, date, party totals. */
|
|
792
|
+
function renderRollVoteDetail(item) {
|
|
793
|
+
const roll = s(item, 'rollCallNumber');
|
|
794
|
+
const congress = s(item, 'congress');
|
|
795
|
+
const session = s(item, 'sessionNumber');
|
|
796
|
+
const result = s(item, 'result');
|
|
797
|
+
const question = s(item, 'voteQuestion');
|
|
798
|
+
const voteType = s(item, 'voteType');
|
|
799
|
+
const startDate = s(item, 'startDate');
|
|
800
|
+
const updated = s(item, 'updateDate');
|
|
801
|
+
const identifier = s(item, 'identifier');
|
|
802
|
+
const sourceUrl = s(item, 'sourceDataURL');
|
|
803
|
+
const headingLeft = roll ? `Roll ${roll}` : 'Roll call';
|
|
804
|
+
const heading = result ? `${headingLeft} — ${result}` : headingLeft;
|
|
805
|
+
const lines = [`# ${heading}`];
|
|
806
|
+
if (question)
|
|
807
|
+
lines.push(`**Question:** ${question}`);
|
|
808
|
+
const meta = join([
|
|
809
|
+
f('Congress', congress),
|
|
810
|
+
f('Session', session),
|
|
811
|
+
f('Type', voteType),
|
|
812
|
+
f('Date', startDate),
|
|
813
|
+
f('Updated', updated),
|
|
814
|
+
identifier && identifier !== roll ? f('ID', identifier) : undefined,
|
|
815
|
+
]);
|
|
816
|
+
if (meta)
|
|
817
|
+
lines.push(meta);
|
|
818
|
+
const totals = item.votePartyTotal;
|
|
819
|
+
if (Array.isArray(totals) && totals.length > 0) {
|
|
820
|
+
lines.push('\n**Party Totals:**');
|
|
821
|
+
for (const t of totals) {
|
|
822
|
+
const party = s(t, 'party', 'name') ?? s(t, 'voteParty') ?? '?';
|
|
823
|
+
const yea = s(t, 'yeaTotal') ?? '0';
|
|
824
|
+
const nay = s(t, 'nayTotal') ?? '0';
|
|
825
|
+
const present = s(t, 'presentTotal') ?? '0';
|
|
826
|
+
const notVoting = s(t, 'notVotingTotal') ?? '0';
|
|
827
|
+
lines.push(`- **${party}:** Yea ${yea}, Nay ${nay}, Present ${present}, Not Voting ${notVoting}`);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
const results = item.results;
|
|
831
|
+
if (Array.isArray(results) && results.length > 0) {
|
|
832
|
+
lines.push(`\n**Member Votes:** ${results.length} on this page`);
|
|
833
|
+
for (const r of results.slice(0, 20)) {
|
|
834
|
+
const name = s(r, 'firstName') && s(r, 'lastName')
|
|
835
|
+
? `${s(r, 'firstName')} ${s(r, 'lastName')}`
|
|
836
|
+
: (s(r, 'bioguideId') ?? '?');
|
|
837
|
+
const vote = s(r, 'voteCast');
|
|
838
|
+
const party = s(r, 'voteParty');
|
|
839
|
+
const state = s(r, 'voteState');
|
|
840
|
+
const parts = [
|
|
841
|
+
name,
|
|
842
|
+
party && state ? `(${party}-${state})` : undefined,
|
|
843
|
+
vote ? `→ ${vote}` : undefined,
|
|
844
|
+
].filter(Boolean);
|
|
845
|
+
lines.push(`- ${parts.join(' ')}`);
|
|
846
|
+
}
|
|
847
|
+
if (results.length > 20)
|
|
848
|
+
lines.push(`- _...${results.length - 20} more_`);
|
|
849
|
+
}
|
|
850
|
+
if (sourceUrl)
|
|
851
|
+
lines.push(`\n**Source Data URL:** ${sourceUrl}`);
|
|
852
|
+
return lines.join('\n');
|
|
853
|
+
}
|
|
854
|
+
/** Bill / law detail — title-first header, then the rest of the structured fields. */
|
|
855
|
+
function renderBillDetail(item) {
|
|
856
|
+
const type = s(item, 'type')?.toUpperCase() ?? '';
|
|
857
|
+
const number = s(item, 'number') ?? '';
|
|
858
|
+
const title = s(item, 'title') ?? 'Untitled';
|
|
859
|
+
const id = type && number ? `${type} ${number}: ` : '';
|
|
860
|
+
const lines = [`# ${id}${title}`];
|
|
861
|
+
const meta = join([
|
|
862
|
+
f('Congress', s(item, 'congress')),
|
|
863
|
+
f('Chamber', s(item, 'originChamber')),
|
|
864
|
+
f('Policy Area', s(item, 'policyArea', 'name')),
|
|
865
|
+
f('Introduced', s(item, 'introducedDate')),
|
|
866
|
+
f('Updated', s(item, 'updateDate')),
|
|
867
|
+
]);
|
|
868
|
+
if (meta)
|
|
869
|
+
lines.push(meta);
|
|
870
|
+
const actionDate = s(item, 'latestAction', 'actionDate');
|
|
871
|
+
const actionText = s(item, 'latestAction', 'text');
|
|
872
|
+
if (actionDate || actionText)
|
|
873
|
+
lines.push(`**Latest Action:** ${[actionDate, actionText].filter(Boolean).join(' — ')}`);
|
|
874
|
+
const laws = item.laws;
|
|
875
|
+
if (Array.isArray(laws) && laws.length > 0) {
|
|
876
|
+
const cites = laws
|
|
877
|
+
.map((law) => {
|
|
878
|
+
const num = s(law, 'number');
|
|
879
|
+
const lawType = s(law, 'type');
|
|
880
|
+
return num && lawType ? `${lawType} ${num}` : (num ?? lawType);
|
|
881
|
+
})
|
|
882
|
+
.filter(Boolean);
|
|
883
|
+
if (cites.length)
|
|
884
|
+
lines.push(`**Law:** ${cites.join(', ')}`);
|
|
885
|
+
}
|
|
886
|
+
const rest = renderDetailRest(item, HEADER_BILL_KEYS);
|
|
887
|
+
if (rest)
|
|
888
|
+
lines.push('', rest);
|
|
889
|
+
return lines.join('\n');
|
|
890
|
+
}
|
|
891
|
+
const HEADER_BILL_KEYS = new Set([
|
|
892
|
+
'type',
|
|
893
|
+
'number',
|
|
894
|
+
'title',
|
|
895
|
+
'congress',
|
|
896
|
+
'originChamber',
|
|
897
|
+
'originChamberCode',
|
|
898
|
+
'policyArea',
|
|
899
|
+
'introducedDate',
|
|
900
|
+
'updateDate',
|
|
901
|
+
'latestAction',
|
|
902
|
+
'laws',
|
|
903
|
+
]);
|
|
904
|
+
/** CRS report detail — title-first header, then the rest of the structured fields. */
|
|
905
|
+
function renderCrsReportDetail(item) {
|
|
906
|
+
const reportNumber = s(item, 'id') ?? s(item, 'reportNumber') ?? s(item, 'number');
|
|
907
|
+
const title = s(item, 'title') ?? 'Title not available';
|
|
908
|
+
const heading = reportNumber ? `${reportNumber}: ${title}` : title;
|
|
909
|
+
const lines = [`# ${heading}`];
|
|
910
|
+
const meta = join([
|
|
911
|
+
f('Type', s(item, 'contentType')),
|
|
912
|
+
f('Status', s(item, 'status')),
|
|
913
|
+
f('Version', s(item, 'currentVersion') ?? s(item, 'version')),
|
|
914
|
+
f('Published', s(item, 'publishDate')),
|
|
915
|
+
f('Updated', s(item, 'updateDate')),
|
|
916
|
+
]);
|
|
917
|
+
if (meta)
|
|
918
|
+
lines.push(meta);
|
|
919
|
+
const authors = item.authors;
|
|
920
|
+
if (Array.isArray(authors) && authors.length > 0) {
|
|
921
|
+
const names = authors
|
|
922
|
+
.map((a) => s(a, 'author') ?? s(a, 'name'))
|
|
923
|
+
.filter(Boolean);
|
|
924
|
+
if (names.length)
|
|
925
|
+
lines.push(`**Authors:** ${names.join(', ')}`);
|
|
926
|
+
}
|
|
927
|
+
const rest = renderDetailRest(item, HEADER_CRS_KEYS);
|
|
928
|
+
if (rest)
|
|
929
|
+
lines.push('', rest);
|
|
930
|
+
return lines.join('\n');
|
|
931
|
+
}
|
|
932
|
+
const HEADER_CRS_KEYS = new Set([
|
|
933
|
+
'id',
|
|
934
|
+
'reportNumber',
|
|
935
|
+
'number',
|
|
936
|
+
'title',
|
|
937
|
+
'contentType',
|
|
938
|
+
'status',
|
|
939
|
+
'currentVersion',
|
|
940
|
+
'version',
|
|
941
|
+
'publishDate',
|
|
942
|
+
'updateDate',
|
|
943
|
+
'authors',
|
|
944
|
+
]);
|
|
945
|
+
/** Member detail. */
|
|
946
|
+
function renderMemberDetail(item) {
|
|
947
|
+
const name = s(item, 'directOrderName') ??
|
|
948
|
+
s(item, 'invertedOrderName') ??
|
|
949
|
+
s(item, 'bioguideId') ??
|
|
950
|
+
'Unknown';
|
|
951
|
+
const lines = [`# ${name}`];
|
|
952
|
+
const meta = join([
|
|
953
|
+
f('ID', s(item, 'bioguideId')),
|
|
954
|
+
f('Party', s(item, 'partyName') ?? s(item, 'currentParty')),
|
|
955
|
+
f('State', s(item, 'state')),
|
|
956
|
+
item.district != null ? f('District', s(item, 'district')) : undefined,
|
|
957
|
+
f('Currently Serving', typeof item.currentMember === 'boolean' ? String(item.currentMember) : undefined),
|
|
958
|
+
f('Birth Year', s(item, 'birthYear')),
|
|
959
|
+
f('Updated', s(item, 'updateDate')),
|
|
960
|
+
]);
|
|
961
|
+
if (meta)
|
|
962
|
+
lines.push(meta);
|
|
963
|
+
const honorific = s(item, 'honorificName');
|
|
964
|
+
if (honorific)
|
|
965
|
+
lines.push(`**Honorific:** ${honorific}`);
|
|
966
|
+
/** terms may be a direct array or nested as `{item: [...]}`. */
|
|
967
|
+
const rawTerms = item.terms;
|
|
968
|
+
const termsArr = Array.isArray(rawTerms)
|
|
969
|
+
? rawTerms
|
|
970
|
+
: rawTerms &&
|
|
971
|
+
typeof rawTerms === 'object' &&
|
|
972
|
+
Array.isArray(rawTerms.item)
|
|
973
|
+
? rawTerms.item
|
|
974
|
+
: undefined;
|
|
975
|
+
if (termsArr && termsArr.length > 0) {
|
|
976
|
+
lines.push(`\n**Terms (${termsArr.length}):**`);
|
|
977
|
+
const recent = termsArr.slice(-5);
|
|
978
|
+
for (const term of recent) {
|
|
979
|
+
const chamber = s(term, 'chamber');
|
|
980
|
+
const start = s(term, 'startYear');
|
|
981
|
+
const end = s(term, 'endYear');
|
|
982
|
+
const party = s(term, 'partyName');
|
|
983
|
+
const stateName = s(term, 'stateName');
|
|
984
|
+
const range = start && end ? `${start}–${end}` : start;
|
|
985
|
+
const parts = [chamber, range, party, stateName].filter(Boolean);
|
|
986
|
+
lines.push(`- ${parts.join(', ')}`);
|
|
987
|
+
}
|
|
988
|
+
if (termsArr.length > 5)
|
|
989
|
+
lines.push(`- _...${termsArr.length - 5} earlier_`);
|
|
990
|
+
}
|
|
991
|
+
const partyHistory = item.partyHistory;
|
|
992
|
+
if (Array.isArray(partyHistory) && partyHistory.length > 0) {
|
|
993
|
+
lines.push(`\n**Party History:**`);
|
|
994
|
+
for (const p of partyHistory) {
|
|
995
|
+
const partyName = s(p, 'partyName');
|
|
996
|
+
const start = s(p, 'startYear');
|
|
997
|
+
const end = s(p, 'endYear');
|
|
998
|
+
const range = start && end ? `${start}–${end}` : start;
|
|
999
|
+
const parts = [partyName, range && `(${range})`].filter(Boolean);
|
|
1000
|
+
lines.push(`- ${parts.join(' ')}`);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
const leadership = item.leadership;
|
|
1004
|
+
if (Array.isArray(leadership) && leadership.length > 0) {
|
|
1005
|
+
lines.push(`\n**Leadership Roles (${leadership.length}):**`);
|
|
1006
|
+
for (const l of leadership.slice(0, 10)) {
|
|
1007
|
+
const type = s(l, 'type');
|
|
1008
|
+
const congress = s(l, 'congress');
|
|
1009
|
+
lines.push(`- ${[type, congress ? `Congress ${congress}` : undefined].filter(Boolean).join(' — ')}`);
|
|
1010
|
+
}
|
|
1011
|
+
if (leadership.length > 10)
|
|
1012
|
+
lines.push(`- _...${leadership.length - 10} more_`);
|
|
1013
|
+
}
|
|
1014
|
+
const subResources = [];
|
|
1015
|
+
for (const key of ['sponsoredLegislation', 'cosponsoredLegislation']) {
|
|
1016
|
+
const sub = item[key];
|
|
1017
|
+
if (sub && typeof sub.count === 'number' && sub.count > 0) {
|
|
1018
|
+
const label = key === 'sponsoredLegislation' ? 'sponsored' : 'cosponsored';
|
|
1019
|
+
subResources.push(`${sub.count} ${label}`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
if (subResources.length)
|
|
1023
|
+
lines.push(`\n**Legislation:** ${subResources.join(', ')}`);
|
|
1024
|
+
const url = s(item, 'url');
|
|
1025
|
+
if (url)
|
|
1026
|
+
lines.push(`\n**URL:** ${url}`);
|
|
1027
|
+
return lines.join('\n');
|
|
1028
|
+
}
|
|
1029
|
+
/** Committee list item — name + key fields. */
|
|
1030
|
+
function renderCommitteeListItem(item, i) {
|
|
1031
|
+
const name = s(item, 'name') ?? s(item, 'systemCode') ?? 'Committee';
|
|
1032
|
+
const lines = [`### ${i + 1}. ${name}`];
|
|
1033
|
+
const meta = join([
|
|
1034
|
+
f('Code', s(item, 'systemCode')),
|
|
1035
|
+
f('Chamber', s(item, 'chamber')),
|
|
1036
|
+
f('Type', s(item, 'committeeTypeCode')),
|
|
1037
|
+
f('Updated', s(item, 'updateDate')),
|
|
1038
|
+
]);
|
|
1039
|
+
if (meta)
|
|
1040
|
+
lines.push(meta);
|
|
1041
|
+
const url = s(item, 'url');
|
|
1042
|
+
if (url)
|
|
1043
|
+
lines.push(`**URL:** ${url}`);
|
|
1044
|
+
return lines.join('\n');
|
|
1045
|
+
}
|
|
1046
|
+
/** Committee report list item — citation-first; upstream omits title and bill ref. */
|
|
1047
|
+
function renderCommitteeReportListItem(item, i) {
|
|
1048
|
+
const citation = s(item, 'citation');
|
|
1049
|
+
const type = s(item, 'type');
|
|
1050
|
+
const number = s(item, 'number');
|
|
1051
|
+
const part = s(item, 'part');
|
|
1052
|
+
const congress = s(item, 'congress');
|
|
1053
|
+
const chamber = s(item, 'chamber');
|
|
1054
|
+
const updated = s(item, 'updateDate');
|
|
1055
|
+
const url = s(item, 'url');
|
|
1056
|
+
const heading = citation ?? (type && number ? `${type} ${congress ?? ''}-${number}` : 'Committee Report');
|
|
1057
|
+
const lines = [`### ${i + 1}. ${heading}`];
|
|
1058
|
+
const meta = join([
|
|
1059
|
+
f('Congress', congress),
|
|
1060
|
+
f('Chamber', chamber),
|
|
1061
|
+
f('Type', type),
|
|
1062
|
+
f('Number', number),
|
|
1063
|
+
part && part !== '1' ? f('Part', part) : undefined,
|
|
1064
|
+
f('Updated', updated),
|
|
1065
|
+
]);
|
|
1066
|
+
if (meta)
|
|
1067
|
+
lines.push(meta);
|
|
1068
|
+
if (url)
|
|
1069
|
+
lines.push(`**URL:** ${url}`);
|
|
1070
|
+
return lines.join('\n');
|
|
1071
|
+
}
|
|
525
1072
|
// ── Per-Tool Format Exports ─────────────────────────────────────────
|
|
526
|
-
|
|
1073
|
+
/** Read `result.query` (a string) — handlers attach this to echo filter criteria. */
|
|
1074
|
+
function queryOf(result) {
|
|
1075
|
+
return typeof result.query === 'string' ? result.query : undefined;
|
|
1076
|
+
}
|
|
1077
|
+
function makeFormatter(detailKeys, itemRenderer, detailRenderer) {
|
|
527
1078
|
return (result) => {
|
|
528
1079
|
if (Array.isArray(result.data))
|
|
529
|
-
return tb(renderList(result, itemRenderer));
|
|
1080
|
+
return tb(renderList(result, itemRenderer, queryOf(result)));
|
|
530
1081
|
for (const key of detailKeys) {
|
|
531
|
-
|
|
532
|
-
|
|
1082
|
+
const detail = result[key];
|
|
1083
|
+
if (detail != null) {
|
|
1084
|
+
const rendered = detailRenderer && typeof detail === 'object' && detail !== null
|
|
1085
|
+
? detailRenderer(detail)
|
|
1086
|
+
: renderDetail(detail);
|
|
1087
|
+
return tb(rendered);
|
|
1088
|
+
}
|
|
533
1089
|
}
|
|
534
1090
|
return tb(renderDetail(result));
|
|
535
1091
|
};
|
|
@@ -540,16 +1096,41 @@ export function formatBills(result) {
|
|
|
540
1096
|
const first = result.data[0];
|
|
541
1097
|
const firstRecord = typeof first === 'object' && first !== null ? first : undefined;
|
|
542
1098
|
const renderer = firstRecord ? pickBillListRenderer(firstRecord) : undefined;
|
|
543
|
-
return tb(renderList(result, renderer));
|
|
1099
|
+
return tb(renderList(result, renderer, queryOf(result)));
|
|
544
1100
|
}
|
|
545
1101
|
if (result.bill != null)
|
|
546
|
-
return tb(
|
|
1102
|
+
return tb(renderBillDetail(result.bill));
|
|
547
1103
|
return tb(renderDetail(result));
|
|
548
1104
|
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Bill sub-resource summary item — known shape (no nested `bill.*`, since the
|
|
1107
|
+
* caller already has the bill). Reuses `htmlToMarkdown` so `<p>` / `<strong>`
|
|
1108
|
+
* survive into the rendered Markdown.
|
|
1109
|
+
*/
|
|
1110
|
+
function renderBillSubresourceSummaryItem(item, i) {
|
|
1111
|
+
const version = s(item, 'actionDesc') ?? s(item, 'versionCode') ?? 'Summary';
|
|
1112
|
+
const actionDate = s(item, 'actionDate');
|
|
1113
|
+
const updated = s(item, 'updateDate');
|
|
1114
|
+
const lines = [`### ${i + 1}. ${version}`];
|
|
1115
|
+
const meta = join([f('Action Date', actionDate), f('Summary Updated', updated)]);
|
|
1116
|
+
if (meta)
|
|
1117
|
+
lines.push(meta);
|
|
1118
|
+
const text = typeof item.text === 'string' ? htmlToMarkdown(item.text) : '';
|
|
1119
|
+
if (text)
|
|
1120
|
+
lines.push('', text);
|
|
1121
|
+
return lines.join('\n');
|
|
1122
|
+
}
|
|
549
1123
|
function pickBillListRenderer(first) {
|
|
550
1124
|
if ('title' in first && 'number' in first)
|
|
551
1125
|
return renderBillItem;
|
|
552
|
-
|
|
1126
|
+
/** Bill text versions: `type` + `formats[]`, no `actionDate`. */
|
|
1127
|
+
if ('type' in first && 'formats' in first)
|
|
1128
|
+
return renderBillTextItem;
|
|
1129
|
+
/** Bill sub-resource summaries: `actionDesc`/`versionCode` + `text`, no `actionCode`/`sourceSystem`. */
|
|
1130
|
+
if ('text' in first && ('actionDesc' in first || 'versionCode' in first)) {
|
|
1131
|
+
return renderBillSubresourceSummaryItem;
|
|
1132
|
+
}
|
|
1133
|
+
/** Actions always ship a `text` body; most also carry actionDate/actionCode/sourceSystem. */
|
|
553
1134
|
if ('text' in first &&
|
|
554
1135
|
('actionDate' in first || 'actionCode' in first || 'sourceSystem' in first))
|
|
555
1136
|
return renderBillActionItem;
|
|
@@ -560,16 +1141,23 @@ export const formatSummaries = makeFormatter([], renderSummaryItem);
|
|
|
560
1141
|
/** Member browse, detail, and sponsored/cosponsored legislation. */
|
|
561
1142
|
export function formatMembers(result) {
|
|
562
1143
|
if (Array.isArray(result.data)) {
|
|
1144
|
+
const hint = queryOf(result);
|
|
563
1145
|
const first = result.data[0];
|
|
564
1146
|
const firstRecord = typeof first === 'object' && first !== null ? first : undefined;
|
|
565
1147
|
if (firstRecord && 'bioguideId' in firstRecord)
|
|
566
|
-
return tb(renderList(result, renderMemberItem));
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
1148
|
+
return tb(renderList(result, renderMemberItem, hint));
|
|
1149
|
+
/** Sponsored/cosponsored may mix bills (type+title) and amendments (amendmentNumber, null type/title).
|
|
1150
|
+
* Dispatch per-row so amendments don't render as 'Untitled'. */
|
|
1151
|
+
if (firstRecord && ('number' in firstRecord || 'amendmentNumber' in firstRecord)) {
|
|
1152
|
+
const dispatch = (item, i) => 'amendmentNumber' in item && item.amendmentNumber != null
|
|
1153
|
+
? renderAmendmentItem(item, i)
|
|
1154
|
+
: renderBillItem(item, i);
|
|
1155
|
+
return tb(renderList(result, dispatch, hint));
|
|
1156
|
+
}
|
|
1157
|
+
return tb(renderList(result, undefined, hint));
|
|
570
1158
|
}
|
|
571
1159
|
if (result.member != null)
|
|
572
|
-
return tb(
|
|
1160
|
+
return tb(renderMemberDetail(result.member));
|
|
573
1161
|
return tb(renderDetail(result));
|
|
574
1162
|
}
|
|
575
1163
|
/** Pull the committee's display name from nested history when the top-level `name` is missing. */
|
|
@@ -584,8 +1172,16 @@ function extractCommitteeName(committee) {
|
|
|
584
1172
|
}
|
|
585
1173
|
/** Committee browse, detail, and sub-resources (bills, reports, nominations). */
|
|
586
1174
|
export function formatCommittees(result) {
|
|
587
|
-
if (Array.isArray(result.data))
|
|
588
|
-
|
|
1175
|
+
if (Array.isArray(result.data)) {
|
|
1176
|
+
const hint = queryOf(result);
|
|
1177
|
+
const first = result.data[0];
|
|
1178
|
+
const firstRecord = typeof first === 'object' && first !== null ? first : undefined;
|
|
1179
|
+
/** Committee list rows have `systemCode` + `name`. Sub-resource rows
|
|
1180
|
+
* (bills/reports/nominations) keep their generic / specialized renderers. */
|
|
1181
|
+
if (firstRecord && 'systemCode' in firstRecord && 'name' in firstRecord)
|
|
1182
|
+
return tb(renderList(result, renderCommitteeListItem, hint));
|
|
1183
|
+
return tb(renderList(result, undefined, hint));
|
|
1184
|
+
}
|
|
589
1185
|
if (result.committee != null) {
|
|
590
1186
|
const committee = result.committee;
|
|
591
1187
|
const name = extractCommitteeName(committee);
|
|
@@ -597,7 +1193,7 @@ export function formatCommittees(result) {
|
|
|
597
1193
|
/** Committee reports — list, detail, and text. */
|
|
598
1194
|
export function formatCommitteeReports(result) {
|
|
599
1195
|
if (Array.isArray(result.data))
|
|
600
|
-
return tb(renderList(result));
|
|
1196
|
+
return tb(renderList(result, renderCommitteeReportListItem, queryOf(result)));
|
|
601
1197
|
if (Array.isArray(result.text)) {
|
|
602
1198
|
const textResult = { data: result.text, pagination: { count: result.text.length } };
|
|
603
1199
|
return tb(renderList(textResult, renderCommitteeReportTextItem));
|
|
@@ -611,9 +1207,9 @@ export function formatCommitteeReports(result) {
|
|
|
611
1207
|
/** CRS policy analysis reports. */
|
|
612
1208
|
export function formatCrsReports(result) {
|
|
613
1209
|
if (Array.isArray(result.data))
|
|
614
|
-
return tb(renderList(result, renderCrsReportItem));
|
|
1210
|
+
return tb(renderList(result, renderCrsReportItem, queryOf(result)));
|
|
615
1211
|
if (result.report != null)
|
|
616
|
-
return tb(
|
|
1212
|
+
return tb(renderCrsReportDetail(result.report));
|
|
617
1213
|
return tb(renderDetail(result));
|
|
618
1214
|
}
|
|
619
1215
|
/** Daily Congressional Record — volumes, issues, articles. */
|
|
@@ -625,14 +1221,33 @@ export function formatDailyRecord(result) {
|
|
|
625
1221
|
const renderer = firstRecord && ('sectionName' in firstRecord || 'title' in firstRecord)
|
|
626
1222
|
? renderDailyArticleItem
|
|
627
1223
|
: renderDailyRecordItem;
|
|
628
|
-
return tb(renderList(result, renderer));
|
|
1224
|
+
return tb(renderList(result, renderer, queryOf(result)));
|
|
629
1225
|
}
|
|
630
1226
|
return tb(renderDetail(result));
|
|
631
1227
|
}
|
|
632
|
-
/** Enacted public and private laws. */
|
|
633
|
-
export const formatLaws = makeFormatter(['law'], renderBillItem);
|
|
1228
|
+
/** Enacted public and private laws. Upstream /law mirrors /bill, so reuse bill formatters. */
|
|
1229
|
+
export const formatLaws = makeFormatter(['law'], renderBillItem, renderBillDetail);
|
|
634
1230
|
/** House roll call votes and member voting positions. */
|
|
635
|
-
export
|
|
1231
|
+
export function formatVotes(result) {
|
|
1232
|
+
if (Array.isArray(result.data))
|
|
1233
|
+
return tb(renderList(result, renderRollVoteItem, queryOf(result)));
|
|
1234
|
+
if (result.vote != null)
|
|
1235
|
+
return tb(renderRollVoteDetail(result.vote));
|
|
1236
|
+
return tb(renderDetail(result));
|
|
1237
|
+
}
|
|
636
1238
|
/** Presidential nominations and Senate confirmation pipeline. */
|
|
637
|
-
export
|
|
1239
|
+
export function formatNominations(result) {
|
|
1240
|
+
if (Array.isArray(result.data)) {
|
|
1241
|
+
const hint = queryOf(result);
|
|
1242
|
+
const first = result.data[0];
|
|
1243
|
+
const firstRecord = typeof first === 'object' && first !== null ? first : undefined;
|
|
1244
|
+
const renderer = firstRecord
|
|
1245
|
+
? pickNominationListRenderer(firstRecord)
|
|
1246
|
+
: renderNominationListItem;
|
|
1247
|
+
return tb(renderList(result, renderer, hint));
|
|
1248
|
+
}
|
|
1249
|
+
if (result.nomination != null)
|
|
1250
|
+
return tb(renderNominationDetail(result.nomination));
|
|
1251
|
+
return tb(renderDetail(result));
|
|
1252
|
+
}
|
|
638
1253
|
//# sourceMappingURL=format-helpers.js.map
|