@@ -535,6 +535,101 @@ function App() {
535535 }
536536 } , [ safeFilterConfig , safeUploadedData , applyAdvancedFilters , calculateStats , setFilterConfig , setUploadedData , setInsights ] )
537537
538+ // Generate time-series insights
539+ const generateTimeSeriesInsights = useCallback ( ( dateColumns : DataColumn [ ] , numericColumns : DataColumn [ ] ) : Insight [ ] => {
540+ const insights : Insight [ ] = [ ]
541+
542+ // Find time series pairs (date + numeric columns)
543+ dateColumns . forEach ( dateCol => {
544+ if ( ! dateCol . dateValues || dateCol . dateValues . length === 0 ) return
545+
546+ const dateRange = dateCol . stats ?. dateRange
547+ const frequency = dateCol . stats ?. frequency
548+
549+ // Date range insight with format information
550+ insights . push ( {
551+ type : 'temporal' ,
552+ title : `${ dateCol . name } Date Analysis` ,
553+ description : `Data spans ${ dateRange } with ${ frequency } frequency. Detected formats: ${ dateCol . stats ?. formatSummary || 'Various' } ` ,
554+ value : dateRange ,
555+ importance : 'high'
556+ } )
557+
558+ // Frequency pattern insight
559+ if ( frequency !== 'irregular' ) {
560+ insights . push ( {
561+ type : 'temporal' ,
562+ title : 'Data Collection Pattern' ,
563+ description : `Regular ${ frequency } data collection detected, ideal for trend analysis and forecasting` ,
564+ importance : 'medium'
565+ } )
566+ }
567+
568+ // Seasonal analysis for monthly/yearly data
569+ if ( dateCol . dateValues && dateCol . dateValues . filter ( d => d !== null ) . length >= 12 && ( frequency === 'monthly' || frequency === 'yearly' ) ) {
570+ const monthCounts = new Array ( 12 ) . fill ( 0 )
571+ dateCol . dateValues . forEach ( date => {
572+ if ( date ) {
573+ monthCounts [ date . getMonth ( ) ] ++
574+ }
575+ } )
576+
577+ const maxMonth = monthCounts . indexOf ( Math . max ( ...monthCounts ) )
578+ const minMonth = monthCounts . indexOf ( Math . min ( ...monthCounts ) )
579+ const monthNames = [ 'Jan' , 'Feb' , 'Mar' , 'Apr' , 'May' , 'Jun' , 'Jul' , 'Aug' , 'Sep' , 'Oct' , 'Nov' , 'Dec' ]
580+
581+ insights . push ( {
582+ type : 'seasonal' ,
583+ title : 'Seasonal Patterns' ,
584+ description : `Peak activity in ${ monthNames [ maxMonth ] } , lowest in ${ monthNames [ minMonth ] } ` ,
585+ importance : 'medium'
586+ } )
587+ }
588+
589+ // Combine with numeric data for trend analysis
590+ numericColumns . forEach ( numCol => {
591+ if ( dateCol . values . length === numCol . values . length ) {
592+ // Create time series pairs
593+ const pairs : Array < { date : Date , value : number } > = [ ]
594+ for ( let i = 0 ; i < dateCol . dateValues ! . length ; i ++ ) {
595+ const date = dateCol . dateValues ! [ i ]
596+ const value = Number ( numCol . values [ i ] )
597+ if ( date && ! isNaN ( value ) ) {
598+ pairs . push ( { date, value } )
599+ }
600+ }
601+
602+ if ( pairs . length >= 3 ) {
603+ // Sort by date
604+ pairs . sort ( ( a , b ) => a . date . getTime ( ) - b . date . getTime ( ) )
605+
606+ // Calculate trend
607+ const firstHalf = pairs . slice ( 0 , Math . floor ( pairs . length / 2 ) )
608+ const secondHalf = pairs . slice ( Math . floor ( pairs . length / 2 ) )
609+
610+ const firstAvg = firstHalf . reduce ( ( sum , p ) => sum + p . value , 0 ) / firstHalf . length
611+ const secondAvg = secondHalf . reduce ( ( sum , p ) => sum + p . value , 0 ) / secondHalf . length
612+
613+ const trendDirection = secondAvg > firstAvg ? 'increasing' : 'decreasing'
614+ const trendMagnitude = Math . abs ( ( secondAvg - firstAvg ) / firstAvg * 100 )
615+
616+ if ( trendMagnitude > 5 ) {
617+ insights . push ( {
618+ type : 'trend' ,
619+ title : `${ numCol . name } Time Trend` ,
620+ description : `${ numCol . name } shows ${ trendDirection } trend over time with ${ trendMagnitude . toFixed ( 1 ) } % change` ,
621+ value : `${ trendDirection === 'increasing' ? '+' : '-' } ${ trendMagnitude . toFixed ( 1 ) } %` ,
622+ importance : trendMagnitude > 20 ? 'high' : 'medium'
623+ } )
624+ }
625+ }
626+ }
627+ } )
628+ } )
629+
630+ return insights
631+ } , [ ] )
632+
538633 const analyzeData = useCallback ( ( columns : DataColumn [ ] ) : Insight [ ] => {
539634 const newInsights : Insight [ ] = [ ]
540635
@@ -662,101 +757,6 @@ function App() {
662757 return newInsights . slice ( 0 , 8 ) // Limit to 8 insights
663758 } , [ generateTimeSeriesInsights ] )
664759
665- // Generate time-series insights
666- const generateTimeSeriesInsights = useCallback ( ( dateColumns : DataColumn [ ] , numericColumns : DataColumn [ ] ) : Insight [ ] => {
667- const insights : Insight [ ] = [ ]
668-
669- // Find time series pairs (date + numeric columns)
670- dateColumns . forEach ( dateCol => {
671- if ( ! dateCol . dateValues || dateCol . dateValues . length === 0 ) return
672-
673- const dateRange = dateCol . stats ?. dateRange
674- const frequency = dateCol . stats ?. frequency
675-
676- // Date range insight with format information
677- insights . push ( {
678- type : 'temporal' ,
679- title : `${ dateCol . name } Date Analysis` ,
680- description : `Data spans ${ dateRange } with ${ frequency } frequency. Detected formats: ${ dateCol . stats ?. formatSummary || 'Various' } ` ,
681- value : dateRange ,
682- importance : 'high'
683- } )
684-
685- // Frequency pattern insight
686- if ( frequency !== 'irregular' ) {
687- insights . push ( {
688- type : 'temporal' ,
689- title : 'Data Collection Pattern' ,
690- description : `Regular ${ frequency } data collection detected, ideal for trend analysis and forecasting` ,
691- importance : 'medium'
692- } )
693- }
694-
695- // Seasonal analysis for monthly/yearly data
696- if ( dateCol . dateValues && dateCol . dateValues . filter ( d => d !== null ) . length >= 12 && ( frequency === 'monthly' || frequency === 'yearly' ) ) {
697- const monthCounts = new Array ( 12 ) . fill ( 0 )
698- dateCol . dateValues . forEach ( date => {
699- if ( date ) {
700- monthCounts [ date . getMonth ( ) ] ++
701- }
702- } )
703-
704- const maxMonth = monthCounts . indexOf ( Math . max ( ...monthCounts ) )
705- const minMonth = monthCounts . indexOf ( Math . min ( ...monthCounts ) )
706- const monthNames = [ 'Jan' , 'Feb' , 'Mar' , 'Apr' , 'May' , 'Jun' , 'Jul' , 'Aug' , 'Sep' , 'Oct' , 'Nov' , 'Dec' ]
707-
708- insights . push ( {
709- type : 'seasonal' ,
710- title : 'Seasonal Patterns' ,
711- description : `Peak activity in ${ monthNames [ maxMonth ] } , lowest in ${ monthNames [ minMonth ] } ` ,
712- importance : 'medium'
713- } )
714- }
715-
716- // Combine with numeric data for trend analysis
717- numericColumns . forEach ( numCol => {
718- if ( dateCol . values . length === numCol . values . length ) {
719- // Create time series pairs
720- const pairs : Array < { date : Date , value : number } > = [ ]
721- for ( let i = 0 ; i < dateCol . dateValues ! . length ; i ++ ) {
722- const date = dateCol . dateValues ! [ i ]
723- const value = Number ( numCol . values [ i ] )
724- if ( date && ! isNaN ( value ) ) {
725- pairs . push ( { date, value } )
726- }
727- }
728-
729- if ( pairs . length >= 3 ) {
730- // Sort by date
731- pairs . sort ( ( a , b ) => a . date . getTime ( ) - b . date . getTime ( ) )
732-
733- // Calculate trend
734- const firstHalf = pairs . slice ( 0 , Math . floor ( pairs . length / 2 ) )
735- const secondHalf = pairs . slice ( Math . floor ( pairs . length / 2 ) )
736-
737- const firstAvg = firstHalf . reduce ( ( sum , p ) => sum + p . value , 0 ) / firstHalf . length
738- const secondAvg = secondHalf . reduce ( ( sum , p ) => sum + p . value , 0 ) / secondHalf . length
739-
740- const trendDirection = secondAvg > firstAvg ? 'increasing' : 'decreasing'
741- const trendMagnitude = Math . abs ( ( secondAvg - firstAvg ) / firstAvg * 100 )
742-
743- if ( trendMagnitude > 5 ) {
744- insights . push ( {
745- type : 'trend' ,
746- title : `${ numCol . name } Time Trend` ,
747- description : `${ numCol . name } shows ${ trendDirection } trend over time with ${ trendMagnitude . toFixed ( 1 ) } % change` ,
748- value : `${ trendDirection === 'increasing' ? '+' : '-' } ${ trendMagnitude . toFixed ( 1 ) } %` ,
749- importance : trendMagnitude > 20 ? 'high' : 'medium'
750- } )
751- }
752- }
753- }
754- } )
755- } )
756-
757- return insights
758- } , [ ] )
759-
760760 // Clear all filters
761761 const clearAllFilters = useCallback ( ( ) => {
762762 setFilterConfig ( { } )
0 commit comments