@@ -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 (?: e v a l u a t e d | t r a i n e d | t e s t e d | f i n e [ - ] ? t u n e d ) \s + (?: o n | u s i n g | w i t h ) \s + ( [ ^ . ; \n ] { 3 , 120 } ) / giu,
783+ / \b (?: d a t a s e t | d a t a s e t s | b e n c h m a r k | b e n c h m a r k s | t a s k | t a s k s ) \s * (?: i n c l u d e d | i n c l u d e | w e r e | w a s | : | = ) \s + ( [ ^ . ; \n ] { 3 , 120 } ) / giu
784+ ] ) . map ( cleanDatasetHint )
785+ ] ) . filter ( Boolean ) . slice ( 0 , 4 ) ;
786+ const metrics = uniqueStrings ( [
787+ ...extractHints ( sourceText , [
788+ / \b (?: m e t r i c | m e t r i c s | m e a s u r e d b y | r e p o r t e d | r e p o r t s | r e p o r t i n g | w i t h ) \s + ( [ ^ . ; \n ] { 0 , 120 } \b (?: a c c u r a c y | f 1 | b l e u | r o u g e | p e r p l e x i t y | p a s s @ \d + | r u n t i m e | l a t e n c y | m e m o r y | t h r o u g h p u t ) \b [ ^ . ; \n ] { 0 , 80 } ) / giu,
789+ / \b (?: a c c u r a c y | f 1 | b l e u | r o u g e | p e r p l e x i t y | p a s s @ \d + | r u n t i m e | l a t e n c y | m e m o r y | t h r o u g h p u t ) \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 / ^ (?: a r x i v : | d o i : | c o p y r i g h t | p r e p r i n t | j o u r n a l | p r o c e e d i n g s | a b s t r a c t \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 (?: d a t a s e t | b e n c h m a r k | t a s k | c o r p u s | e v a l u a t e d o n | t r a i n e d o n | t e s t e d o n | f i n e [ - ] ? t u n e d o n ) \b / iu. test ( sentence ) ;
811+ }
812+
813+ function hasMetricCue ( sentence : string ) : boolean {
814+ return / \b (?: m e t r i c | a c c u r a c y | f 1 | b l e u | r o u g e | p e r p l e x i t y | p a s s @ \d + | r u n t i m e | l a t e n c y | m e m o r y | t h r o u g h p u t ) \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 (?: w i t h | u s i n g | m e a s u r e d b y | r e p o r t i n g | r e p o r t s ? ) \b .* $ / iu, "" )
830+ . replace ( / \b (?: a c c u r a c y | f 1 | b l e u | r o u g e | p e r p l e x i t y | p a s s @ \d + | r u n t i m e | l a t e n c y | m e m o r y | t h r o u g h p u t ) \b .* $ / iu, "" )
831+ . trim ( ) ;
832+ }
833+
834+ function cleanMetricHint ( value : string ) : string {
835+ const cleaned = cleanHint ( value ) ;
836+ const metricMatch = / \b (?: a c c u r a c y | f 1 | b l e u | r o u g e | p e r p l e x i t y | p a s s @ \d + | r u n t i m e | l a t e n c y | m e m o r y | t h r o u g h p u t ) \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+
766848function summarizeAbstractForTimeoutFallback ( abstract : string , title : string ) : string {
767849 const sentence = firstMeaningfulSentence ( abstract ) ;
768850 if ( sentence ) {
0 commit comments