mcp-sequential-research 1.0.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.
@@ -0,0 +1,1113 @@
1
+ /**
2
+ * Research Report Compilation
3
+ *
4
+ * Deterministic compiler that transforms research plan + raw results
5
+ * into a structured markdown report with citations.
6
+ */
7
+
8
+ import type {
9
+ CompileInput,
10
+ CompileOutput,
11
+ Citation,
12
+ RawResult,
13
+ ReportSection,
14
+ PlanOutput,
15
+ PriorArtCluster,
16
+ ClaimRiskFlag,
17
+ NoveltyGap,
18
+ } from "./schema.js";
19
+ import {
20
+ generateId,
21
+ isoNow,
22
+ formatSourcesList,
23
+ countWords,
24
+ deduplicateCitations,
25
+ renumberCitations,
26
+ applyCitationMapping,
27
+ formatBulletList,
28
+ formatCallout,
29
+ } from "./format.js";
30
+
31
+ /**
32
+ * Section templates by query type
33
+ */
34
+ const SECTION_TEMPLATES: Record<string, {
35
+ heading: string;
36
+ level: number;
37
+ formatter: (data: Record<string, unknown>, citations: Citation[]) => string;
38
+ }> = {
39
+ definition: {
40
+ heading: "Overview",
41
+ level: 2,
42
+ formatter: (data, citations) => {
43
+ const parts: string[] = [];
44
+
45
+ if (data.definition) {
46
+ parts.push(String(data.definition));
47
+ if (citations.length > 0) {
48
+ parts[parts.length - 1] += ` ${formatInlineCitations(citations)}`;
49
+ }
50
+ }
51
+
52
+ if (Array.isArray(data.characteristics) && data.characteristics.length > 0) {
53
+ parts.push("\n**Key Characteristics:**");
54
+ parts.push(formatBulletList(data.characteristics.map(String)));
55
+ }
56
+
57
+ if (Array.isArray(data.examples) && data.examples.length > 0) {
58
+ parts.push("\n**Examples:**");
59
+ parts.push(formatBulletList(data.examples.map(String)));
60
+ }
61
+
62
+ return parts.join("\n");
63
+ },
64
+ },
65
+ comparison: {
66
+ heading: "Comparison",
67
+ level: 2,
68
+ formatter: (data, citations) => {
69
+ const parts: string[] = [];
70
+
71
+ if (Array.isArray(data.similarities) && data.similarities.length > 0) {
72
+ parts.push("**Similarities:**");
73
+ parts.push(formatBulletList(data.similarities.map(String)));
74
+ }
75
+
76
+ if (Array.isArray(data.differences) && data.differences.length > 0) {
77
+ parts.push("\n**Differences:**");
78
+ parts.push(formatBulletList(data.differences.map(String)));
79
+ }
80
+
81
+ if (data.recommendation) {
82
+ parts.push(`\n**Recommendation:** ${data.recommendation}`);
83
+ }
84
+
85
+ if (citations.length > 0) {
86
+ parts.push(`\n${formatInlineCitations(citations)}`);
87
+ }
88
+
89
+ return parts.join("\n");
90
+ },
91
+ },
92
+ enumeration: {
93
+ heading: "Types and Categories",
94
+ level: 2,
95
+ formatter: (data, citations) => {
96
+ const parts: string[] = [];
97
+
98
+ if (Array.isArray(data.items) && data.items.length > 0) {
99
+ parts.push(formatBulletList(data.items.map(String)));
100
+ }
101
+
102
+ if (data.categories && typeof data.categories === "object") {
103
+ for (const [category, items] of Object.entries(data.categories as Record<string, unknown[]>)) {
104
+ parts.push(`\n**${category}:**`);
105
+ if (Array.isArray(items)) {
106
+ parts.push(formatBulletList(items.map(String)));
107
+ }
108
+ }
109
+ }
110
+
111
+ if (citations.length > 0) {
112
+ parts.push(`\n${formatInlineCitations(citations)}`);
113
+ }
114
+
115
+ return parts.join("\n");
116
+ },
117
+ },
118
+ procedure: {
119
+ heading: "Process",
120
+ level: 2,
121
+ formatter: (data, citations) => {
122
+ const parts: string[] = [];
123
+
124
+ if (Array.isArray(data.prerequisites) && data.prerequisites.length > 0) {
125
+ parts.push("**Prerequisites:**");
126
+ parts.push(formatBulletList(data.prerequisites.map(String)));
127
+ }
128
+
129
+ if (Array.isArray(data.steps) && data.steps.length > 0) {
130
+ parts.push("\n**Steps:**");
131
+ data.steps.forEach((step, i) => {
132
+ parts.push(`${i + 1}. ${step}`);
133
+ });
134
+ }
135
+
136
+ if (Array.isArray(data.warnings) && data.warnings.length > 0) {
137
+ parts.push("\n" + formatCallout(data.warnings.join("\n"), "warning"));
138
+ }
139
+
140
+ if (citations.length > 0) {
141
+ parts.push(`\n${formatInlineCitations(citations)}`);
142
+ }
143
+
144
+ return parts.join("\n");
145
+ },
146
+ },
147
+ causation: {
148
+ heading: "Causes and Factors",
149
+ level: 2,
150
+ formatter: (data, citations) => {
151
+ const parts: string[] = [];
152
+
153
+ if (Array.isArray(data.primary_causes) && data.primary_causes.length > 0) {
154
+ parts.push("**Primary Causes:**");
155
+ parts.push(formatBulletList(data.primary_causes.map(String)));
156
+ }
157
+
158
+ if (Array.isArray(data.contributing_factors) && data.contributing_factors.length > 0) {
159
+ parts.push("\n**Contributing Factors:**");
160
+ parts.push(formatBulletList(data.contributing_factors.map(String)));
161
+ }
162
+
163
+ if (data.evidence_quality) {
164
+ parts.push(`\n*Evidence Quality: ${data.evidence_quality}*`);
165
+ }
166
+
167
+ if (citations.length > 0) {
168
+ parts.push(`\n${formatInlineCitations(citations)}`);
169
+ }
170
+
171
+ return parts.join("\n");
172
+ },
173
+ },
174
+ evidence: {
175
+ heading: "Evidence",
176
+ level: 2,
177
+ formatter: (data, citations) => {
178
+ const parts: string[] = [];
179
+
180
+ if (Array.isArray(data.supporting_evidence) && data.supporting_evidence.length > 0) {
181
+ parts.push("**Supporting Evidence:**");
182
+ parts.push(formatBulletList(data.supporting_evidence.map(String)));
183
+ }
184
+
185
+ if (Array.isArray(data.contrary_evidence) && data.contrary_evidence.length > 0) {
186
+ parts.push("\n**Contrary Evidence:**");
187
+ parts.push(formatBulletList(data.contrary_evidence.map(String)));
188
+ }
189
+
190
+ if (data.consensus) {
191
+ parts.push(`\n**Consensus:** ${data.consensus}`);
192
+ }
193
+
194
+ if (citations.length > 0) {
195
+ parts.push(`\n${formatInlineCitations(citations)}`);
196
+ }
197
+
198
+ return parts.join("\n");
199
+ },
200
+ },
201
+ current_state: {
202
+ heading: "Current State",
203
+ level: 2,
204
+ formatter: (data, citations) => {
205
+ const parts: string[] = [];
206
+
207
+ if (data.current_status) {
208
+ parts.push(String(data.current_status));
209
+ }
210
+
211
+ if (Array.isArray(data.recent_developments) && data.recent_developments.length > 0) {
212
+ parts.push("\n**Recent Developments:**");
213
+ parts.push(formatBulletList(data.recent_developments.map(String)));
214
+ }
215
+
216
+ if (Array.isArray(data.key_players) && data.key_players.length > 0) {
217
+ parts.push("\n**Key Players:**");
218
+ parts.push(formatBulletList(data.key_players.map(String)));
219
+ }
220
+
221
+ if (citations.length > 0) {
222
+ parts.push(`\n${formatInlineCitations(citations)}`);
223
+ }
224
+
225
+ return parts.join("\n");
226
+ },
227
+ },
228
+ historical: {
229
+ heading: "Historical Context",
230
+ level: 2,
231
+ formatter: (data, citations) => {
232
+ const parts: string[] = [];
233
+
234
+ if (Array.isArray(data.timeline) && data.timeline.length > 0) {
235
+ parts.push("**Timeline:**");
236
+ parts.push(formatBulletList(data.timeline.map(String)));
237
+ }
238
+
239
+ if (Array.isArray(data.milestones) && data.milestones.length > 0) {
240
+ parts.push("\n**Key Milestones:**");
241
+ parts.push(formatBulletList(data.milestones.map(String)));
242
+ }
243
+
244
+ if (Array.isArray(data.key_figures) && data.key_figures.length > 0) {
245
+ parts.push("\n**Key Figures:**");
246
+ parts.push(formatBulletList(data.key_figures.map(String)));
247
+ }
248
+
249
+ if (citations.length > 0) {
250
+ parts.push(`\n${formatInlineCitations(citations)}`);
251
+ }
252
+
253
+ return parts.join("\n");
254
+ },
255
+ },
256
+ prediction: {
257
+ heading: "Future Outlook",
258
+ level: 2,
259
+ formatter: (data, citations) => {
260
+ const parts: string[] = [];
261
+
262
+ if (Array.isArray(data.short_term) && data.short_term.length > 0) {
263
+ parts.push("**Short-term Outlook:**");
264
+ parts.push(formatBulletList(data.short_term.map(String)));
265
+ }
266
+
267
+ if (Array.isArray(data.long_term) && data.long_term.length > 0) {
268
+ parts.push("\n**Long-term Outlook:**");
269
+ parts.push(formatBulletList(data.long_term.map(String)));
270
+ }
271
+
272
+ if (Array.isArray(data.uncertainties) && data.uncertainties.length > 0) {
273
+ parts.push("\n**Key Uncertainties:**");
274
+ parts.push(formatBulletList(data.uncertainties.map(String)));
275
+ }
276
+
277
+ if (citations.length > 0) {
278
+ parts.push(`\n${formatInlineCitations(citations)}`);
279
+ }
280
+
281
+ return parts.join("\n");
282
+ },
283
+ },
284
+ opinion: {
285
+ heading: "Expert Perspectives",
286
+ level: 2,
287
+ formatter: (data, citations) => {
288
+ const parts: string[] = [];
289
+
290
+ if (data.majority_view) {
291
+ parts.push(`**Majority View:** ${data.majority_view}`);
292
+ }
293
+
294
+ if (Array.isArray(data.alternative_views) && data.alternative_views.length > 0) {
295
+ parts.push("\n**Alternative Views:**");
296
+ parts.push(formatBulletList(data.alternative_views.map(String)));
297
+ }
298
+
299
+ if (Array.isArray(data.key_debates) && data.key_debates.length > 0) {
300
+ parts.push("\n**Key Debates:**");
301
+ parts.push(formatBulletList(data.key_debates.map(String)));
302
+ }
303
+
304
+ if (citations.length > 0) {
305
+ parts.push(`\n${formatInlineCitations(citations)}`);
306
+ }
307
+
308
+ return parts.join("\n");
309
+ },
310
+ },
311
+
312
+ // Patent-grade section templates
313
+ patent_broad: {
314
+ heading: "Patent Landscape",
315
+ level: 2,
316
+ formatter: (data, citations) => {
317
+ const parts: string[] = [];
318
+
319
+ if (Array.isArray(data.patents) && data.patents.length > 0) {
320
+ parts.push("**Relevant Patents:**");
321
+ parts.push(formatBulletList(data.patents.map(String)));
322
+ }
323
+
324
+ if (Array.isArray(data.key_claims) && data.key_claims.length > 0) {
325
+ parts.push("\n**Key Claim Language:**");
326
+ parts.push(formatBulletList(data.key_claims.map(String)));
327
+ }
328
+
329
+ if (Array.isArray(data.classifications) && data.classifications.length > 0) {
330
+ parts.push("\n**Technology Classifications:** " + data.classifications.join(", "));
331
+ }
332
+
333
+ if (Array.isArray(data.assignees) && data.assignees.length > 0) {
334
+ parts.push("\n**Major Assignees:** " + data.assignees.join(", "));
335
+ }
336
+
337
+ if (citations.length > 0) {
338
+ parts.push(`\n${formatInlineCitations(citations)}`);
339
+ }
340
+
341
+ return parts.join("\n");
342
+ },
343
+ },
344
+ patent_synonyms: {
345
+ heading: "Terminology Analysis",
346
+ level: 2,
347
+ formatter: (data, citations) => {
348
+ const parts: string[] = [];
349
+
350
+ if (Array.isArray(data.synonyms) && data.synonyms.length > 0) {
351
+ parts.push("**Alternative Terms:**");
352
+ parts.push(formatBulletList(data.synonyms.map(String)));
353
+ }
354
+
355
+ if (Array.isArray(data.patent_language) && data.patent_language.length > 0) {
356
+ parts.push("\n**Patent Language:**");
357
+ parts.push(formatBulletList(data.patent_language.map(String)));
358
+ }
359
+
360
+ if (citations.length > 0) {
361
+ parts.push(`\n${formatInlineCitations(citations)}`);
362
+ }
363
+
364
+ return parts.join("\n");
365
+ },
366
+ },
367
+ patent_problem_benefit: {
368
+ heading: "Problem-Solution Analysis",
369
+ level: 2,
370
+ formatter: (data, citations) => {
371
+ const parts: string[] = [];
372
+
373
+ if (Array.isArray(data.problems) && data.problems.length > 0) {
374
+ parts.push("**Problems Addressed:**");
375
+ parts.push(formatBulletList(data.problems.map(String)));
376
+ }
377
+
378
+ if (Array.isArray(data.benefits) && data.benefits.length > 0) {
379
+ parts.push("\n**Claimed Benefits:**");
380
+ parts.push(formatBulletList(data.benefits.map(String)));
381
+ }
382
+
383
+ if (Array.isArray(data.technical_effects) && data.technical_effects.length > 0) {
384
+ parts.push("\n**Technical Effects:**");
385
+ parts.push(formatBulletList(data.technical_effects.map(String)));
386
+ }
387
+
388
+ if (data.prior_art_gap) {
389
+ parts.push(`\n**Gap in Prior Art:** ${data.prior_art_gap}`);
390
+ }
391
+
392
+ if (citations.length > 0) {
393
+ parts.push(`\n${formatInlineCitations(citations)}`);
394
+ }
395
+
396
+ return parts.join("\n");
397
+ },
398
+ },
399
+ patent_competitor: {
400
+ heading: "Competitor Analysis",
401
+ level: 2,
402
+ formatter: (data, citations) => {
403
+ const parts: string[] = [];
404
+
405
+ if (Array.isArray(data.assignees) && data.assignees.length > 0) {
406
+ parts.push("**Key Patent Holders:**");
407
+ parts.push(formatBulletList(data.assignees.map(String)));
408
+ }
409
+
410
+ if (Array.isArray(data.recent_filings) && data.recent_filings.length > 0) {
411
+ parts.push("\n**Recent Filing Activity:**");
412
+ parts.push(formatBulletList(data.recent_filings.map(String)));
413
+ }
414
+
415
+ if (Array.isArray(data.key_patents) && data.key_patents.length > 0) {
416
+ parts.push("\n**Most Cited Patents:**");
417
+ parts.push(formatBulletList(data.key_patents.map(String)));
418
+ }
419
+
420
+ if (citations.length > 0) {
421
+ parts.push(`\n${formatInlineCitations(citations)}`);
422
+ }
423
+
424
+ return parts.join("\n");
425
+ },
426
+ },
427
+ patent_limitation: {
428
+ heading: "Claim Limitations",
429
+ level: 2,
430
+ formatter: (data, citations) => {
431
+ const parts: string[] = [];
432
+
433
+ if (Array.isArray(data.claim_elements) && data.claim_elements.length > 0) {
434
+ parts.push("**Claim Elements:**");
435
+ parts.push(formatBulletList(data.claim_elements.map(String)));
436
+ }
437
+
438
+ if (Array.isArray(data.limitations) && data.limitations.length > 0) {
439
+ parts.push("\n**Technical Limitations:**");
440
+ parts.push(formatBulletList(data.limitations.map(String)));
441
+ }
442
+
443
+ if (data.parameters && typeof data.parameters === "object") {
444
+ parts.push("\n**Parameter Ranges:**");
445
+ for (const [param, value] of Object.entries(data.parameters as Record<string, unknown>)) {
446
+ parts.push(`- ${param}: ${value}`);
447
+ }
448
+ }
449
+
450
+ if (citations.length > 0) {
451
+ parts.push(`\n${formatInlineCitations(citations)}`);
452
+ }
453
+
454
+ return parts.join("\n");
455
+ },
456
+ },
457
+
458
+ // Web query section templates
459
+ web_academic: {
460
+ heading: "Academic Research",
461
+ level: 2,
462
+ formatter: (data, citations) => {
463
+ const parts: string[] = [];
464
+
465
+ if (Array.isArray(data.papers) && data.papers.length > 0) {
466
+ parts.push("**Academic Publications:**");
467
+ parts.push(formatBulletList(data.papers.map(String)));
468
+ }
469
+
470
+ if (Array.isArray(data.authors) && data.authors.length > 0) {
471
+ parts.push("\n**Key Researchers:** " + data.authors.join(", "));
472
+ }
473
+
474
+ if (Array.isArray(data.institutions) && data.institutions.length > 0) {
475
+ parts.push("\n**Research Institutions:** " + data.institutions.join(", "));
476
+ }
477
+
478
+ if (citations.length > 0) {
479
+ parts.push(`\n${formatInlineCitations(citations)}`);
480
+ }
481
+
482
+ return parts.join("\n");
483
+ },
484
+ },
485
+ web_vendor: {
486
+ heading: "Commercial Implementations",
487
+ level: 2,
488
+ formatter: (data, citations) => {
489
+ const parts: string[] = [];
490
+
491
+ if (Array.isArray(data.products) && data.products.length > 0) {
492
+ parts.push("**Commercial Products:**");
493
+ parts.push(formatBulletList(data.products.map(String)));
494
+ }
495
+
496
+ if (Array.isArray(data.vendors) && data.vendors.length > 0) {
497
+ parts.push("\n**Vendors:** " + data.vendors.join(", "));
498
+ }
499
+
500
+ if (data.availability) {
501
+ parts.push(`\n**Market Availability:** ${data.availability}`);
502
+ }
503
+
504
+ if (citations.length > 0) {
505
+ parts.push(`\n${formatInlineCitations(citations)}`);
506
+ }
507
+
508
+ return parts.join("\n");
509
+ },
510
+ },
511
+ web_opensource: {
512
+ heading: "Open Source Implementations",
513
+ level: 2,
514
+ formatter: (data, citations) => {
515
+ const parts: string[] = [];
516
+
517
+ if (Array.isArray(data.repositories) && data.repositories.length > 0) {
518
+ parts.push("**Repositories:**");
519
+ parts.push(formatBulletList(data.repositories.map(String)));
520
+ }
521
+
522
+ if (Array.isArray(data.implementations) && data.implementations.length > 0) {
523
+ parts.push("\n**Implementations:**");
524
+ parts.push(formatBulletList(data.implementations.map(String)));
525
+ }
526
+
527
+ if (Array.isArray(data.licenses) && data.licenses.length > 0) {
528
+ parts.push("\n**Licenses:** " + data.licenses.join(", "));
529
+ }
530
+
531
+ if (citations.length > 0) {
532
+ parts.push(`\n${formatInlineCitations(citations)}`);
533
+ }
534
+
535
+ return parts.join("\n");
536
+ },
537
+ },
538
+ web_conference: {
539
+ heading: "Conference Papers",
540
+ level: 2,
541
+ formatter: (data, citations) => {
542
+ const parts: string[] = [];
543
+
544
+ if (Array.isArray(data.papers) && data.papers.length > 0) {
545
+ parts.push("**Papers:**");
546
+ parts.push(formatBulletList(data.papers.map(String)));
547
+ }
548
+
549
+ if (Array.isArray(data.conferences) && data.conferences.length > 0) {
550
+ parts.push("\n**Conferences:** " + data.conferences.join(", "));
551
+ }
552
+
553
+ if (Array.isArray(data.preprints) && data.preprints.length > 0) {
554
+ parts.push("\n**Preprints:**");
555
+ parts.push(formatBulletList(data.preprints.map(String)));
556
+ }
557
+
558
+ if (citations.length > 0) {
559
+ parts.push(`\n${formatInlineCitations(citations)}`);
560
+ }
561
+
562
+ return parts.join("\n");
563
+ },
564
+ },
565
+ };
566
+
567
+ /**
568
+ * Format inline citation references
569
+ */
570
+ function formatInlineCitations(citations: Citation[]): string {
571
+ if (citations.length === 0) return "";
572
+ return citations.map((c) => `[${c.id}]`).join(", ");
573
+ }
574
+
575
+ /**
576
+ * Build a report section from a query result
577
+ */
578
+ function buildSection(
579
+ plan: PlanOutput,
580
+ result: RawResult
581
+ ): ReportSection | null {
582
+ // Find the query in the plan
583
+ const query = plan.queries.find((q) => q.query_id === result.query_id);
584
+ if (!query) return null;
585
+
586
+ // Skip failed queries
587
+ if (!result.success || !result.data) return null;
588
+
589
+ // Get template for query type
590
+ const template = SECTION_TEMPLATES[query.query_type];
591
+ if (!template) {
592
+ // Fallback for unknown types
593
+ return {
594
+ heading: query.query_text,
595
+ level: 2,
596
+ content: JSON.stringify(result.data, null, 2),
597
+ citations_used: result.sources?.map((s) => s.id) ?? [],
598
+ };
599
+ }
600
+
601
+ const content = template.formatter(
602
+ result.data as Record<string, unknown>,
603
+ result.sources ?? []
604
+ );
605
+
606
+ return {
607
+ heading: template.heading,
608
+ level: template.level,
609
+ content,
610
+ citations_used: result.sources?.map((s) => s.id) ?? [],
611
+ };
612
+ }
613
+
614
+ /**
615
+ * Generate executive summary from sections
616
+ */
617
+ function generateExecutiveSummary(
618
+ topic: string,
619
+ sections: ReportSection[],
620
+ stats: { succeeded: number; failed: number }
621
+ ): string {
622
+ const parts: string[] = [];
623
+
624
+ parts.push(`This report presents research findings on **${topic}**.`);
625
+
626
+ if (stats.succeeded > 0) {
627
+ parts.push(`The analysis covers ${stats.succeeded} research queries across ${sections.length} sections.`);
628
+ }
629
+
630
+ if (stats.failed > 0) {
631
+ parts.push(`Note: ${stats.failed} queries could not be completed.`);
632
+ }
633
+
634
+ // Add brief description of each section
635
+ const sectionNames = sections.map((s) => s.heading).slice(0, 5);
636
+ if (sectionNames.length > 0) {
637
+ parts.push(`Key topics include: ${sectionNames.join(", ")}.`);
638
+ }
639
+
640
+ return parts.join(" ");
641
+ }
642
+
643
+ /**
644
+ * Build the full markdown report
645
+ */
646
+ function buildMarkdownReport(
647
+ title: string,
648
+ summary: string,
649
+ sections: ReportSection[],
650
+ sources: Citation[],
651
+ includeSources: boolean,
652
+ includeMethodology: boolean,
653
+ citationStyle: "inline" | "footnote" | "endnote"
654
+ ): string {
655
+ const parts: string[] = [];
656
+
657
+ // Title
658
+ parts.push(`# ${title}`);
659
+ parts.push("");
660
+
661
+ // Executive Summary
662
+ parts.push("## Executive Summary");
663
+ parts.push("");
664
+ parts.push(summary);
665
+ parts.push("");
666
+
667
+ // Methodology (optional)
668
+ if (includeMethodology) {
669
+ parts.push("## Methodology");
670
+ parts.push("");
671
+ parts.push("This report was compiled using a sequential research approach:");
672
+ parts.push("1. Research queries were generated based on the topic and focus areas");
673
+ parts.push("2. Each query was executed to gather relevant information");
674
+ parts.push("3. Results were compiled and organized into thematic sections");
675
+ parts.push("4. Sources were consolidated and citations were verified");
676
+ parts.push("");
677
+ }
678
+
679
+ // Content sections
680
+ for (const section of sections) {
681
+ parts.push("#".repeat(section.level) + " " + section.heading);
682
+ parts.push("");
683
+ parts.push(section.content);
684
+ parts.push("");
685
+ }
686
+
687
+ // Sources
688
+ if (includeSources && sources.length > 0) {
689
+ parts.push(formatSourcesList(sources, citationStyle));
690
+ }
691
+
692
+ return parts.join("\n");
693
+ }
694
+
695
+ /**
696
+ * Check if this is a patent-focused research plan
697
+ */
698
+ function isPatentFocused(plan: PlanOutput): boolean {
699
+ return plan.queries.some((q) =>
700
+ q.query_type.startsWith("patent_") || q.query_type.startsWith("web_")
701
+ );
702
+ }
703
+
704
+ /**
705
+ * Generate prior art clusters from sources
706
+ */
707
+ function generatePriorArtClusters(
708
+ sources: Citation[],
709
+ _results: RawResult[],
710
+ _plan: PlanOutput
711
+ ): PriorArtCluster[] {
712
+ const clusters: PriorArtCluster[] = [];
713
+
714
+ // Group by source type
715
+ const patentSources = sources.filter((s) => s.source_type === "patent" || s.source_type === "document");
716
+ const webSources = sources.filter((s) => s.source_type === "web");
717
+ const academicSources = sources.filter((s) =>
718
+ s.url?.includes(".edu") || s.url?.includes("arxiv") || s.url?.includes("ieee")
719
+ );
720
+
721
+ if (patentSources.length > 0) {
722
+ clusters.push({
723
+ cluster_id: generateId("cluster"),
724
+ theme: "Patent Prior Art",
725
+ source_ids: patentSources.map((s) => s.id),
726
+ relevance: "high",
727
+ overlap_notes: `${patentSources.length} patent documents identified as potential prior art`,
728
+ });
729
+ }
730
+
731
+ if (academicSources.length > 0) {
732
+ clusters.push({
733
+ cluster_id: generateId("cluster"),
734
+ theme: "Academic Publications",
735
+ source_ids: academicSources.map((s) => s.id),
736
+ relevance: "medium",
737
+ overlap_notes: `${academicSources.length} academic sources may establish prior knowledge`,
738
+ });
739
+ }
740
+
741
+ const commercialSources = webSources.filter((s) =>
742
+ !s.url?.includes(".edu") && !s.url?.includes("arxiv") && !s.url?.includes("github")
743
+ );
744
+ if (commercialSources.length > 0) {
745
+ clusters.push({
746
+ cluster_id: generateId("cluster"),
747
+ theme: "Commercial Prior Art",
748
+ source_ids: commercialSources.map((s) => s.id),
749
+ relevance: "medium",
750
+ overlap_notes: `${commercialSources.length} commercial sources showing market implementation`,
751
+ });
752
+ }
753
+
754
+ const ossSources = webSources.filter((s) =>
755
+ s.url?.includes("github") || s.url?.includes("gitlab")
756
+ );
757
+ if (ossSources.length > 0) {
758
+ clusters.push({
759
+ cluster_id: generateId("cluster"),
760
+ theme: "Open Source Prior Art",
761
+ source_ids: ossSources.map((s) => s.id),
762
+ relevance: "low",
763
+ overlap_notes: `${ossSources.length} open source implementations may affect novelty`,
764
+ });
765
+ }
766
+
767
+ return clusters;
768
+ }
769
+
770
+ /**
771
+ * Generate claim risk flags based on results
772
+ */
773
+ function generateClaimRiskFlags(
774
+ sources: Citation[],
775
+ results: RawResult[],
776
+ plan: PlanOutput
777
+ ): ClaimRiskFlag[] {
778
+ const risks: ClaimRiskFlag[] = [];
779
+
780
+ // Check for potential anticipation
781
+ const patentResults = results.filter((r) => {
782
+ const query = plan.queries.find((q) => q.query_id === r.query_id);
783
+ return query?.query_type.startsWith("patent_");
784
+ });
785
+
786
+ if (patentResults.length > 0) {
787
+ const patentCount = sources.filter((s) =>
788
+ s.source_type === "patent" || s.source_type === "document"
789
+ ).length;
790
+
791
+ if (patentCount > 5) {
792
+ risks.push({
793
+ risk_id: generateId("risk"),
794
+ severity: "high",
795
+ category: "anticipation",
796
+ description: `${patentCount} potentially relevant patents identified. Review each for anticipation of core claims.`,
797
+ blocking_sources: sources
798
+ .filter((s) => s.source_type === "patent" || s.source_type === "document")
799
+ .slice(0, 5)
800
+ .map((s) => s.id),
801
+ mitigation: "Conduct detailed claim-by-claim analysis against top 5 most relevant patents",
802
+ });
803
+ }
804
+ }
805
+
806
+ // Check for obviousness risk
807
+ const problemBenefitResults = results.filter((r) => {
808
+ const query = plan.queries.find((q) => q.query_id === r.query_id);
809
+ return query?.query_type === "patent_problem_benefit";
810
+ });
811
+
812
+ if (problemBenefitResults.length > 0 && sources.length > 3) {
813
+ risks.push({
814
+ risk_id: generateId("risk"),
815
+ severity: "medium",
816
+ category: "obviousness",
817
+ description: "Multiple sources address similar problems. Combination of references may render claims obvious.",
818
+ mitigation: "Identify unexpected results or synergistic effects to overcome obviousness",
819
+ });
820
+ }
821
+
822
+ // Check for academic prior art
823
+ const academicCount = sources.filter((s) =>
824
+ s.url?.includes(".edu") || s.url?.includes("arxiv") || s.url?.includes("ieee")
825
+ ).length;
826
+
827
+ if (academicCount > 3) {
828
+ risks.push({
829
+ risk_id: generateId("risk"),
830
+ severity: "medium",
831
+ category: "anticipation",
832
+ description: `${academicCount} academic publications found. Academic literature often predates patent filings.`,
833
+ blocking_sources: sources
834
+ .filter((s) => s.url?.includes(".edu") || s.url?.includes("arxiv"))
835
+ .slice(0, 3)
836
+ .map((s) => s.id),
837
+ mitigation: "Check publication dates against priority date; identify implementation-specific claims",
838
+ });
839
+ }
840
+
841
+ return risks;
842
+ }
843
+
844
+ /**
845
+ * Generate novelty gap suggestions
846
+ */
847
+ function generateNoveltyGaps(
848
+ sources: Citation[],
849
+ results: RawResult[],
850
+ plan: PlanOutput
851
+ ): NoveltyGap[] {
852
+ const gaps: NoveltyGap[] = [];
853
+
854
+ // Look for limitation results
855
+ const limitationResults = results.filter((r) => {
856
+ const query = plan.queries.find((q) => q.query_id === r.query_id);
857
+ return query?.query_type === "patent_limitation";
858
+ });
859
+
860
+ if (limitationResults.length > 0) {
861
+ for (const result of limitationResults) {
862
+ if (!result.success || !result.data) continue;
863
+
864
+ const data = result.data as Record<string, unknown>;
865
+
866
+ // Suggest parameter-based differentiation
867
+ if (data.parameters && typeof data.parameters === "object") {
868
+ const params = Object.keys(data.parameters as Record<string, unknown>);
869
+ if (params.length > 0) {
870
+ gaps.push({
871
+ gap_id: generateId("gap"),
872
+ limitation_type: "parameter",
873
+ description: `Consider novel parameter ranges for: ${params.join(", ")}`,
874
+ confidence: "medium",
875
+ claim_language_suggestion: `wherein the ${params[0]} is in a range of [X] to [Y]`,
876
+ });
877
+ }
878
+ }
879
+
880
+ // Suggest structural differentiation
881
+ if (Array.isArray(data.claim_elements) && data.claim_elements.length > 0) {
882
+ gaps.push({
883
+ gap_id: generateId("gap"),
884
+ limitation_type: "structural",
885
+ description: "Consider adding structural limitations not found in prior art",
886
+ confidence: "medium",
887
+ claim_language_suggestion: "comprising a [novel component] configured to [novel function]",
888
+ });
889
+ }
890
+ }
891
+ }
892
+
893
+ // Look for problem-benefit gaps
894
+ const problemResults = results.filter((r) => {
895
+ const query = plan.queries.find((q) => q.query_id === r.query_id);
896
+ return query?.query_type === "patent_problem_benefit";
897
+ });
898
+
899
+ if (problemResults.length > 0) {
900
+ for (const result of problemResults) {
901
+ if (!result.success || !result.data) continue;
902
+
903
+ const data = result.data as Record<string, unknown>;
904
+
905
+ if (data.prior_art_gap) {
906
+ gaps.push({
907
+ gap_id: generateId("gap"),
908
+ limitation_type: "functional",
909
+ description: String(data.prior_art_gap),
910
+ confidence: "high",
911
+ claim_language_suggestion: "wherein [component] is configured to [address identified gap]",
912
+ });
913
+ }
914
+ }
915
+ }
916
+
917
+ // Suggest combination novelty if multiple sources
918
+ if (sources.length > 5) {
919
+ gaps.push({
920
+ gap_id: generateId("gap"),
921
+ limitation_type: "combination",
922
+ description: "Consider claiming novel combination of known elements that produces unexpected result",
923
+ confidence: "low",
924
+ claim_language_suggestion: "A system comprising [element A] and [element B] configured to cooperatively [produce unexpected result]",
925
+ });
926
+ }
927
+
928
+ return gaps;
929
+ }
930
+
931
+ /**
932
+ * Build patent analysis section for markdown report
933
+ */
934
+ function buildPatentAnalysisSection(
935
+ clusters: PriorArtCluster[],
936
+ risks: ClaimRiskFlag[],
937
+ gaps: NoveltyGap[]
938
+ ): string {
939
+ const parts: string[] = [];
940
+
941
+ if (clusters.length > 0 || risks.length > 0 || gaps.length > 0) {
942
+ parts.push("## Patent Analysis");
943
+ parts.push("");
944
+ }
945
+
946
+ if (clusters.length > 0) {
947
+ parts.push("### Prior Art Clusters");
948
+ parts.push("");
949
+ for (const cluster of clusters) {
950
+ parts.push(`**${cluster.theme}** (${cluster.relevance} relevance)`);
951
+ parts.push(`- Sources: ${cluster.source_ids.join(", ")}`);
952
+ if (cluster.overlap_notes) {
953
+ parts.push(`- ${cluster.overlap_notes}`);
954
+ }
955
+ parts.push("");
956
+ }
957
+ }
958
+
959
+ if (risks.length > 0) {
960
+ parts.push("### Claim Risk Flags");
961
+ parts.push("");
962
+ for (const risk of risks) {
963
+ const severityIcon = risk.severity === "critical" ? "!!!" :
964
+ risk.severity === "high" ? "!!" : "!";
965
+ parts.push(`**${severityIcon} ${risk.category.toUpperCase()}** (${risk.severity})`);
966
+ parts.push(`- ${risk.description}`);
967
+ if (risk.blocking_sources && risk.blocking_sources.length > 0) {
968
+ parts.push(`- Blocking sources: ${risk.blocking_sources.join(", ")}`);
969
+ }
970
+ if (risk.mitigation) {
971
+ parts.push(`- Mitigation: ${risk.mitigation}`);
972
+ }
973
+ parts.push("");
974
+ }
975
+ }
976
+
977
+ if (gaps.length > 0) {
978
+ parts.push("### Novelty Gap Suggestions");
979
+ parts.push("");
980
+ for (const gap of gaps) {
981
+ parts.push(`**${gap.limitation_type.toUpperCase()}** (${gap.confidence} confidence)`);
982
+ parts.push(`- ${gap.description}`);
983
+ if (gap.claim_language_suggestion) {
984
+ parts.push(`- Suggested claim language: \`${gap.claim_language_suggestion}\``);
985
+ }
986
+ parts.push("");
987
+ }
988
+ }
989
+
990
+ return parts.join("\n");
991
+ }
992
+
993
+ /**
994
+ * Main compile function
995
+ */
996
+ export function compileReport(input: CompileInput): CompileOutput {
997
+ const reportId = generateId("report");
998
+ const warnings: string[] = [];
999
+ const patentFocused = isPatentFocused(input.plan);
1000
+
1001
+ // Collect all sources
1002
+ const allSources: Citation[] = [];
1003
+ for (const result of input.raw_results) {
1004
+ if (result.sources) {
1005
+ allSources.push(...result.sources);
1006
+ }
1007
+ }
1008
+
1009
+ // Deduplicate and optionally renumber
1010
+ const dedupedSources = deduplicateCitations(allSources);
1011
+ const { citations: finalSources, mapping } = renumberCitations(dedupedSources);
1012
+
1013
+ // Build sections
1014
+ const sections: ReportSection[] = [];
1015
+ let queriesSucceeded = 0;
1016
+ let queriesFailed = 0;
1017
+
1018
+ for (const result of input.raw_results) {
1019
+ if (result.success) {
1020
+ queriesSucceeded++;
1021
+ const section = buildSection(input.plan, result);
1022
+ if (section) {
1023
+ // Apply citation renumbering
1024
+ section.content = applyCitationMapping(section.content, mapping);
1025
+ section.citations_used = section.citations_used.map((id) => mapping.get(id) ?? id);
1026
+ sections.push(section);
1027
+ }
1028
+ } else {
1029
+ queriesFailed++;
1030
+ if (result.error) {
1031
+ warnings.push(`Query ${result.query_id} failed: ${result.error}`);
1032
+ }
1033
+ }
1034
+ }
1035
+
1036
+ // Generate patent analysis if patent-focused
1037
+ let priorArtClusters: PriorArtCluster[] | undefined;
1038
+ let claimRiskFlags: ClaimRiskFlag[] | undefined;
1039
+ let noveltyGaps: NoveltyGap[] | undefined;
1040
+ let patentAnalysisSection = "";
1041
+
1042
+ if (patentFocused) {
1043
+ priorArtClusters = generatePriorArtClusters(finalSources, input.raw_results, input.plan);
1044
+ claimRiskFlags = generateClaimRiskFlags(finalSources, input.raw_results, input.plan);
1045
+ noveltyGaps = generateNoveltyGaps(finalSources, input.raw_results, input.plan);
1046
+ patentAnalysisSection = buildPatentAnalysisSection(priorArtClusters, claimRiskFlags, noveltyGaps);
1047
+ }
1048
+
1049
+ // Generate title
1050
+ const title = `Research Report: ${input.plan.topic}`;
1051
+
1052
+ // Generate executive summary
1053
+ const executiveSummary = generateExecutiveSummary(
1054
+ input.plan.topic,
1055
+ sections,
1056
+ { succeeded: queriesSucceeded, failed: queriesFailed }
1057
+ );
1058
+
1059
+ // Build full markdown report
1060
+ let markdownReport = buildMarkdownReport(
1061
+ title,
1062
+ executiveSummary,
1063
+ sections,
1064
+ finalSources,
1065
+ input.include_sources ?? true,
1066
+ input.include_methodology ?? false,
1067
+ input.citation_style ?? "inline"
1068
+ );
1069
+
1070
+ // Append patent analysis section if present
1071
+ if (patentAnalysisSection) {
1072
+ // Insert before sources section
1073
+ const sourcesIndex = markdownReport.lastIndexOf("---\n\n### References");
1074
+ if (sourcesIndex > 0) {
1075
+ markdownReport = markdownReport.slice(0, sourcesIndex) +
1076
+ patentAnalysisSection + "\n" +
1077
+ markdownReport.slice(sourcesIndex);
1078
+ } else {
1079
+ markdownReport += "\n" + patentAnalysisSection;
1080
+ }
1081
+ }
1082
+
1083
+ // Calculate statistics
1084
+ const wordCount = countWords(markdownReport);
1085
+ const patentSourceCount = finalSources.filter((s) =>
1086
+ s.source_type === "patent" || s.source_type === "document"
1087
+ ).length;
1088
+ const webSourceCount = finalSources.filter((s) => s.source_type === "web").length;
1089
+
1090
+ return {
1091
+ report_id: reportId,
1092
+ plan_id: input.plan.plan_id,
1093
+ title,
1094
+ compiled_at: isoNow(),
1095
+ executive_summary: executiveSummary,
1096
+ sections,
1097
+ markdown_report: markdownReport,
1098
+ sources: finalSources,
1099
+ statistics: {
1100
+ queries_executed: input.raw_results.length,
1101
+ queries_succeeded: queriesSucceeded,
1102
+ queries_failed: queriesFailed,
1103
+ total_sources: finalSources.length,
1104
+ word_count: wordCount,
1105
+ patent_sources: patentSourceCount > 0 ? patentSourceCount : undefined,
1106
+ web_sources: webSourceCount > 0 ? webSourceCount : undefined,
1107
+ },
1108
+ prior_art_clusters: priorArtClusters && priorArtClusters.length > 0 ? priorArtClusters : undefined,
1109
+ claim_risk_flags: claimRiskFlags && claimRiskFlags.length > 0 ? claimRiskFlags : undefined,
1110
+ novelty_gaps: noveltyGaps && noveltyGaps.length > 0 ? noveltyGaps : undefined,
1111
+ warnings: warnings.length > 0 ? warnings : undefined,
1112
+ };
1113
+ }