Skip to content

Commit 24a1f47

Browse files
committed
Structure full-text analysis fallbacks
1 parent 7e4ef65 commit 24a1f47

2 files changed

Lines changed: 103 additions & 15 deletions

File tree

src/core/analysis/paperAnalyzer.ts

Lines changed: 94 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -730,39 +730,121 @@ function buildDeterministicPlannerTimeoutFallback(
730730
}
731731

732732
const sourceText = source.text?.trim() || buildAbstractFallbackText(paper);
733+
const structured = extractPlannerTimeoutFallbackSlots(sourceText);
733734
const firstSentence = firstMeaningfulSentence(sourceText);
734-
const summary = trimToLength(firstSentence || sourceText || paper.title, 280);
735+
const supportSentence = structured.supportSentence || firstSentence;
736+
const summary = trimToLength(supportSentence || sourceText || paper.title, 280);
735737
const evidenceSpan = trimToLength(sourceText || paper.title, 240);
736-
const claim = trimToLength(firstSentence || summary, 220);
738+
const claim = trimToLength(supportSentence || summary, 220);
739+
const hasOperationalContract = structured.datasets.length > 0 && structured.metrics.length > 0;
737740
return {
738741
summary,
739-
key_findings: firstSentence ? [trimToLength(firstSentence, 180)] : [],
742+
key_findings: supportSentence ? [trimToLength(supportSentence, 180)] : [],
740743
limitations: [
741744
"Deterministic full-text fallback; planner timed out before structured extraction and review completed."
742745
],
743-
datasets: [],
744-
metrics: [],
746+
datasets: structured.datasets,
747+
metrics: structured.metrics,
745748
novelty: "Not established from planner-timeout fallback evidence.",
746749
reproducibility_notes: [
747-
`Synthesized from extracted full text after the planner timed out (${failureReason}).`
750+
`Extracted from full text after the planner timed out (${failureReason}); reviewer validation remains incomplete.`
748751
],
749752
evidence_items: [
750753
{
751754
claim,
752-
method_slot: "Not yet structured; planner timed out before extraction planning completed.",
755+
method_slot: structured.method || "Not yet structured; planner timed out before extraction planning completed.",
753756
result_slot: summary,
754757
limitation_slot: "Structured extraction and review did not complete before timeout.",
755-
dataset_slot: "Not yet structured.",
756-
metric_slot: "Not yet structured.",
758+
dataset_slot: structured.datasets.join("; ") || "Not yet structured.",
759+
metric_slot: structured.metrics.join("; ") || "Not yet structured.",
757760
evidence_span: evidenceSpan,
758-
confidence: 0.45,
759-
confidence_reason:
760-
"This item was synthesized directly from the extracted full text after a planner timeout, so it is weaker than a normal structured extraction+review pass."
761+
confidence: hasOperationalContract ? 0.62 : 0.35,
762+
confidence_reason: hasOperationalContract
763+
? "Source is extracted full text; planner timed out before reviewer validation, so confidence is moderate."
764+
: "Planner timed out before structured dataset and metric extraction completed, so this full-text row remains low confidence."
761765
}
762766
]
763767
};
764768
}
765769

770+
function extractPlannerTimeoutFallbackSlots(sourceText: string): {
771+
datasets: string[];
772+
metrics: string[];
773+
method?: string;
774+
supportSentence?: string;
775+
} {
776+
const sentences = splitMeaningfulSentences(sourceText);
777+
const supportSentence = sentences.find((sentence) => hasDatasetCue(sentence) && hasMetricCue(sentence)) ||
778+
sentences.find((sentence) => hasMetricCue(sentence)) ||
779+
sentences.find((sentence) => hasDatasetCue(sentence));
780+
const datasets = uniqueStrings([
781+
...extractHints(sourceText, [
782+
/\b(?:evaluated|trained|tested|fine[- ]?tuned)\s+(?:on|using|with)\s+([^.;\n]{3,120})/giu,
783+
/\b(?:dataset|datasets|benchmark|benchmarks|task|tasks)\s*(?:included|include|were|was|:|=)\s+([^.;\n]{3,120})/giu
784+
]).map(cleanDatasetHint)
785+
]).filter(Boolean).slice(0, 4);
786+
const metrics = uniqueStrings([
787+
...extractHints(sourceText, [
788+
/\b(?:metric|metrics|measured by|reported|reports|reporting|with)\s+([^.;\n]{0,120}\b(?:accuracy|f1|bleu|rouge|perplexity|pass@\d+|runtime|latency|memory|throughput)\b[^.;\n]{0,80})/giu,
789+
/\b(?:accuracy|f1|bleu|rouge|perplexity|pass@\d+|runtime|latency|memory|throughput)\b/giu
790+
]).map(cleanMetricHint)
791+
]).filter(Boolean).slice(0, 4);
792+
const method = supportSentence ? trimToLength(supportSentence, 180) : undefined;
793+
return { datasets, metrics, method, supportSentence };
794+
}
795+
796+
function splitMeaningfulSentences(text: string): string[] {
797+
return text
798+
.replace(/\s+/gu, " ")
799+
.split(/(?<=[.!?])\s+/u)
800+
.map((sentence) => sentence.trim())
801+
.filter((sentence) => sentence.length > 24 && !looksLikeFrontMatter(sentence));
802+
}
803+
804+
function looksLikeFrontMatter(sentence: string): boolean {
805+
return /^(?:arxiv:|doi:|copyright|preprint|journal|proceedings|abstract\b)/iu.test(sentence) ||
806+
sentence.split(/\s+/u).filter((token) => /[A-Z]{3,}/u.test(token)).length > 8;
807+
}
808+
809+
function hasDatasetCue(sentence: string): boolean {
810+
return /\b(?:dataset|benchmark|task|corpus|evaluated on|trained on|tested on|fine[- ]?tuned on)\b/iu.test(sentence);
811+
}
812+
813+
function hasMetricCue(sentence: string): boolean {
814+
return /\b(?:metric|accuracy|f1|bleu|rouge|perplexity|pass@\d+|runtime|latency|memory|throughput)\b/iu.test(sentence);
815+
}
816+
817+
function extractHints(text: string, patterns: RegExp[]): string[] {
818+
const hints: string[] = [];
819+
for (const pattern of patterns) {
820+
for (const match of text.matchAll(pattern)) {
821+
hints.push(String(match[1] || match[0] || ""));
822+
}
823+
}
824+
return hints;
825+
}
826+
827+
function cleanDatasetHint(value: string): string {
828+
return cleanHint(value)
829+
.replace(/\s+\b(?:with|using|measured by|reporting|reports?)\b.*$/iu, "")
830+
.replace(/\b(?:accuracy|f1|bleu|rouge|perplexity|pass@\d+|runtime|latency|memory|throughput)\b.*$/iu, "")
831+
.trim();
832+
}
833+
834+
function cleanMetricHint(value: string): string {
835+
const cleaned = cleanHint(value);
836+
const metricMatch = /\b(?:accuracy|f1|bleu|rouge|perplexity|pass@\d+|runtime|latency|memory|throughput)\b/iu.exec(cleaned);
837+
return metricMatch ? metricMatch[0] : cleaned;
838+
}
839+
840+
function cleanHint(value: string): string {
841+
return trimToLength(value.replace(/[()\[\]{}]/gu, " ").replace(/\s+/gu, " ").trim(), 120);
842+
}
843+
844+
function uniqueStrings(values: string[]): string[] {
845+
return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)));
846+
}
847+
766848
function summarizeAbstractForTimeoutFallback(abstract: string, title: string): string {
767849
const sentence = firstMeaningfulSentence(abstract);
768850
if (sentence) {

tests/analyzePapers.test.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,7 +1666,11 @@ describe("analyzePapers node", () => {
16661666
pdf_url: "https://example.com/p1.pdf"
16671667
}
16681668
]);
1669-
writeCachedPaperTextSync(runId, "p1", "Recovered cached full text describing a compact PEFT recipe and its measured behavior.");
1669+
writeCachedPaperTextSync(
1670+
runId,
1671+
"p1",
1672+
"Recovered cached full text describing a compact PEFT recipe. The model was evaluated on Benchmark Task A and Benchmark Task B with accuracy and runtime metrics under a fixed adaptation budget."
1673+
);
16701674
writeCachedPageImagesSync(runId, "p1", 3);
16711675

16721676
const llm = new HangingPlannerOnlyLLM();
@@ -1704,11 +1708,13 @@ describe("analyzePapers node", () => {
17041708

17051709
const summariesRaw = await readFile(path.join(".autolabos", "runs", runId, "paper_summaries.jsonl"), "utf8");
17061710
expect(summariesRaw).toContain('"source_type":"full_text"');
1707-
expect(summariesRaw).toContain("compact PEFT recipe");
1711+
expect(summariesRaw).toContain("Benchmark Task A and Benchmark Task B");
17081712

17091713
const evidenceRaw = await readFile(path.join(".autolabos", "runs", runId, "evidence_store.jsonl"), "utf8");
17101714
expect(evidenceRaw).toContain('"source_type":"full_text"');
1711-
expect(evidenceRaw).toContain('"confidence":0.45');
1715+
expect(evidenceRaw).toContain("Benchmark Task A and Benchmark Task B");
1716+
expect(evidenceRaw).toContain('"metric_slot":"accuracy; runtime"');
1717+
expect(evidenceRaw).toContain('"confidence":0.62');
17121718

17131719
const manifestRaw = await readFile(
17141720
path.join(".autolabos", "runs", runId, "analysis_manifest.json"),

0 commit comments

Comments
 (0)