pentarosarium commited on
Commit
8a9c71f
1 Parent(s): 8f0749d
Files changed (1) hide show
  1. app.py +294 -57
app.py CHANGED
@@ -32,6 +32,9 @@ from queue import Queue
32
 
33
  from deep_translator import GoogleTranslator
34
  from googletrans import Translator as LegacyTranslator
 
 
 
35
 
36
 
37
  class ProcessControl:
@@ -431,54 +434,272 @@ class ProcessingUI:
431
  def __init__(self):
432
  if 'control' not in st.session_state:
433
  st.session_state.control = ProcessControl()
434
- if 'negative_container' not in st.session_state:
435
- st.session_state.negative_container = st.empty()
436
- if 'events_container' not in st.session_state:
437
- st.session_state.events_container = st.empty()
438
 
439
- # Create control buttons
440
- col1, col2 = st.columns(2)
441
- with col1:
442
- if st.button("⏸️ Пауза/Возобновить" if not st.session_state.control.is_paused() else "▶️ Возобновить", key="pause_button"):
443
- if st.session_state.control.is_paused():
444
- st.session_state.control.resume()
445
- else:
446
- st.session_state.control.pause()
447
-
448
- with col2:
449
- if st.button("⏹️ Стоп и всё", key="stop_button"):
450
- st.session_state.control.stop()
451
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  self.progress_bar = st.progress(0)
453
- self.status = st.empty()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
 
455
  def update_progress(self, current, total):
 
456
  progress = current / total
457
  self.progress_bar.progress(progress)
458
- self.status.text(f"Обрабатываем {current} из {total} сообщений...")
459
-
460
- def show_negative(self, entity, headline, analysis, impact=None):
461
- with st.session_state.negative_container:
462
- st.markdown(f"""
463
- <div style='background-color: #ffebee; padding: 10px; border-radius: 5px; margin: 5px 0;'>
464
- <strong style='color: #d32f2f;'>⚠️ Внимание: негатив!:</strong><br>
465
- <strong>Entity:</strong> {entity}<br>
466
- <strong>News:</strong> {headline}<br>
467
- <strong>Analysis:</strong> {analysis}<br>
468
- {f"<strong>Impact:</strong> {impact}<br>" if impact else ""}
469
- </div>
470
- """, unsafe_allow_html=True)
471
-
472
- def show_event(self, entity, event_type, headline):
473
- with st.session_state.events_container:
474
- st.markdown(f"""
475
- <div style='background-color: #e3f2fd; padding: 10px; border-radius: 5px; margin: 5px 0;'>
476
- <strong style='color: #1976d2;'>🔔 Возможно, речь о важном факте:</strong><br>
477
- <strong>Entity:</strong> {entity}<br>
478
- <strong>Type:</strong> {event_type}<br>
479
- <strong>News:</strong> {headline}
480
- </div>
481
- """, unsafe_allow_html=True)
482
 
483
  class EventDetectionSystem:
484
  def __init__(self):
@@ -656,6 +877,8 @@ class TranslationSystem:
656
 
657
  def process_file(uploaded_file, model_choice, translation_method=None):
658
  df = None
 
 
659
  try:
660
  # Initialize UI and control systems
661
  ui = ProcessingUI()
@@ -699,11 +922,32 @@ def process_file(uploaded_file, model_choice, translation_method=None):
699
  # Check for stop/pause
700
  if st.session_state.control.is_stopped():
701
  st.warning("Обработку остановили")
 
 
 
 
 
 
 
 
 
 
702
  break
703
 
704
  st.session_state.control.wait_if_paused()
705
  if st.session_state.control.is_paused():
706
  st.info("Обработка на паузе. Можно возобновить.")
 
 
 
 
 
 
 
 
 
 
 
707
  continue
708
 
709
  try:
@@ -723,6 +967,9 @@ def process_file(uploaded_file, model_choice, translation_method=None):
723
  df.at[idx, 'Event_Type'] = event_type
724
  df.at[idx, 'Event_Summary'] = event_summary
725
 
 
 
 
726
  # Show events in real-time
727
  if event_type != "Нет":
728
  ui.show_event(
@@ -756,6 +1003,8 @@ def process_file(uploaded_file, model_choice, translation_method=None):
756
  impact
757
  )
758
 
 
 
759
  # Update progress
760
  processed_rows += 1
761
  ui.update_progress(processed_rows, total_rows)
@@ -766,20 +1015,8 @@ def process_file(uploaded_file, model_choice, translation_method=None):
766
 
767
  time.sleep(0.1)
768
 
769
- # Handle stopped processing
770
- if st.session_state.control.is_stopped() and len(df) > 0:
771
- st.warning("Обработку остановили. Показываю частичные результаты.")
772
- output = create_output_file(df, uploaded_file, llm)
773
- if output is not None:
774
- st.download_button(
775
- label="📊 Скачать частичный результат",
776
- data=output,
777
- file_name="partial_analysis.xlsx",
778
- mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
779
- key="partial_download"
780
- )
781
-
782
- return df
783
 
784
  except Exception as e:
785
  st.error(f"Ошибка в обработке файла: {str(e)}")
@@ -1165,7 +1402,7 @@ def main():
1165
  st.set_page_config(layout="wide")
1166
 
1167
  with st.sidebar:
1168
- st.title("::: AI-анализ мониторинга новостей (v.3.67):::")
1169
  st.subheader("по материалам СКАН-ИНТЕРФАКС")
1170
 
1171
  model_choice = st.radio(
 
32
 
33
  from deep_translator import GoogleTranslator
34
  from googletrans import Translator as LegacyTranslator
35
+ import plotly.graph_objects as go
36
+ from datetime import datetime
37
+ import plotly.express as px
38
 
39
 
40
  class ProcessControl:
 
434
  def __init__(self):
435
  if 'control' not in st.session_state:
436
  st.session_state.control = ProcessControl()
 
 
 
 
437
 
438
+ # Initialize processing stats in session state if not exists
439
+ if 'processing_stats' not in st.session_state:
440
+ st.session_state.processing_stats = {
441
+ 'start_time': time.time(),
442
+ 'entities': {},
443
+ 'events_timeline': [],
444
+ 'negative_alerts': [],
445
+ 'processing_speed': []
446
+ }
447
+
448
+ # Create main layout
449
+ self.setup_layout()
450
+
451
+ def setup_layout(self):
452
+ """Setup the main UI layout with tabs and sections"""
453
+ # Control Panel
454
+ with st.container():
455
+ col1, col2, col3 = st.columns([2,2,1])
456
+ with col1:
457
+ if st.button(
458
+ "⏸️ Пауза" if not st.session_state.control.is_paused() else "▶️ Продолжить",
459
+ use_container_width=True
460
+ ):
461
+ if st.session_state.control.is_paused():
462
+ st.session_state.control.resume()
463
+ else:
464
+ st.session_state.control.pause()
465
+ with col2:
466
+ if st.button("⏹️ Остановить", use_container_width=True):
467
+ st.session_state.control.stop()
468
+ with col3:
469
+ self.timer_display = st.empty()
470
+
471
+ # Progress Bar with custom styling
472
+ st.markdown("""
473
+ <style>
474
+ .stProgress > div > div > div > div {
475
+ background-image: linear-gradient(to right, #FF6B6B, #4ECDC4);
476
+ }
477
+ </style>""",
478
+ unsafe_allow_html=True
479
+ )
480
  self.progress_bar = st.progress(0)
481
+
482
+ # Create tabs for different views
483
+ tab1, tab2, tab3, tab4 = st.tabs([
484
+ "📊 Основные метрики",
485
+ "🏢 По организациям",
486
+ "⚠️ Важные события",
487
+ "📈 Аналитика"
488
+ ])
489
+
490
+ with tab1:
491
+ self.setup_main_metrics_tab()
492
+
493
+ with tab2:
494
+ self.setup_entity_tab()
495
+
496
+ with tab3:
497
+ self.setup_events_tab()
498
+
499
+ with tab4:
500
+ self.setup_analytics_tab()
501
+
502
+ def setup_main_metrics_tab(self):
503
+ """Setup the main metrics display"""
504
+ # Create metrics containers
505
+ metrics_cols = st.columns(4)
506
+ self.total_processed = metrics_cols[0].empty()
507
+ self.negative_count = metrics_cols[1].empty()
508
+ self.events_count = metrics_cols[2].empty()
509
+ self.speed_metric = metrics_cols[3].empty()
510
+
511
+ # Recent items container with custom styling
512
+ st.markdown("""
513
+ <style>
514
+ .recent-item {
515
+ padding: 10px;
516
+ margin: 5px 0;
517
+ border-radius: 5px;
518
+ background-color: #f0f2f6;
519
+ }
520
+ .negative-item {
521
+ border-left: 4px solid #FF6B6B;
522
+ }
523
+ .positive-item {
524
+ border-left: 4px solid #4ECDC4;
525
+ }
526
+ .event-item {
527
+ border-left: 4px solid #FFE66D;
528
+ }
529
+ </style>
530
+ """, unsafe_allow_html=True)
531
+
532
+ self.recent_items_container = st.empty()
533
+
534
+ def setup_entity_tab(self):
535
+ """Setup the entity-wise analysis display"""
536
+ # Entity filter
537
+ self.entity_filter = st.multiselect(
538
+ "Фильтр по организациям:",
539
+ options=[], # Will be populated as entities are processed
540
+ default=None
541
+ )
542
+
543
+ # Entity metrics
544
+ self.entity_cols = st.columns([2,1,1,1])
545
+ self.entity_chart = st.empty()
546
+ self.entity_table = st.empty()
547
+
548
+ def setup_events_tab(self):
549
+ """Setup the events timeline display"""
550
+ # Event type filter
551
+ self.event_filter = st.multiselect(
552
+ "Тип события:",
553
+ options=["Отчетность", "РЦБ", "Суд"],
554
+ default=None
555
+ )
556
+
557
+ # Timeline container
558
+ self.timeline_container = st.container()
559
+
560
+ def setup_analytics_tab(self):
561
+ """Setup the analytics display"""
562
+ # Processing speed chart
563
+ self.speed_chart = st.empty()
564
+
565
+ # Sentiment distribution pie chart
566
+ self.sentiment_chart = st.empty()
567
+
568
+ # Entity correlation matrix
569
+ self.correlation_chart = st.empty()
570
+
571
+ def update_stats(self, row, sentiment, event_type, processing_speed):
572
+ """Update all statistics and displays"""
573
+ # Update session state stats
574
+ stats = st.session_state.processing_stats
575
+ entity = row['Объект']
576
+
577
+ # Update entity stats
578
+ if entity not in stats['entities']:
579
+ stats['entities'][entity] = {
580
+ 'total': 0,
581
+ 'negative': 0,
582
+ 'events': 0,
583
+ 'timeline': []
584
+ }
585
+
586
+ stats['entities'][entity]['total'] += 1
587
+ if sentiment == 'Negative':
588
+ stats['entities'][entity]['negative'] += 1
589
+ if event_type != 'Нет':
590
+ stats['entities'][entity]['events'] += 1
591
+
592
+ # Update processing speed
593
+ stats['processing_speed'].append(processing_speed)
594
+
595
+ # Update UI components
596
+ self._update_main_metrics(row, sentiment, event_type, processing_speed)
597
+ self._update_entity_view()
598
+ self._update_events_view(row, event_type)
599
+ self._update_analytics()
600
+
601
+ def _update_main_metrics(self, row, sentiment, event_type, speed):
602
+ """Update main metrics tab"""
603
+ total = sum(e['total'] for e in st.session_state.processing_stats['entities'].values())
604
+ total_negative = sum(e['negative'] for e in st.session_state.processing_stats['entities'].values())
605
+ total_events = sum(e['events'] for e in st.session_state.processing_stats['entities'].values())
606
+
607
+ # Update metrics
608
+ self.total_processed.metric("Обработано", total)
609
+ self.negative_count.metric("Негативных", total_negative)
610
+ self.events_count.metric("Событий", total_events)
611
+ self.speed_metric.metric("Скорость", f"{speed:.1f} сообщ/сек")
612
+
613
+ # Update recent items
614
+ self._update_recent_items(row, sentiment, event_type)
615
+
616
+ def _update_recent_items(self, row, sentiment, event_type):
617
+ """Update recent items display with custom styling"""
618
+ items_html = "<div style='max-height: 300px; overflow-y: auto;'>"
619
+
620
+ # Add new item to the beginning of the list
621
+ item_class = 'recent-item '
622
+ if sentiment == 'Negative':
623
+ item_class += 'negative-item'
624
+ elif sentiment == 'Positive':
625
+ item_class += 'positive-item'
626
+ if event_type != 'Нет':
627
+ item_class += ' event-item'
628
+
629
+ items_html += f"""
630
+ <div class='{item_class}'>
631
+ <strong>{row['Объект']}</strong><br>
632
+ {row['Заголовок']}<br>
633
+ <small>
634
+ Тональность: {sentiment}
635
+ {f" | Событие: {event_type}" if event_type != 'Нет' else ""}
636
+ </small>
637
+ </div>
638
+ """
639
+
640
+ items_html += "</div>"
641
+ self.recent_items_container.markdown(items_html, unsafe_allow_html=True)
642
+
643
+ def _update_entity_view(self):
644
+ """Update entity tab visualizations"""
645
+ stats = st.session_state.processing_stats['entities']
646
+ if not stats:
647
+ return
648
+
649
+ # Update entity filter options
650
+ current_options = set(self.entity_filter)
651
+ all_entities = set(stats.keys())
652
+ if current_options != all_entities:
653
+ self.entity_filter = list(all_entities)
654
+
655
+ # Create entity comparison chart using Plotly
656
+ df_entities = pd.DataFrame.from_dict(stats, orient='index')
657
+ fig = go.Figure(data=[
658
+ go.Bar(name='Total', x=df_entities.index, y=df_entities['total']),
659
+ go.Bar(name='Negative', x=df_entities.index, y=df_entities['negative']),
660
+ go.Bar(name='Events', x=df_entities.index, y=df_entities['events'])
661
+ ])
662
+ fig.update_layout(barmode='group', title='Entity Statistics')
663
+ self.entity_chart.plotly_chart(fig, use_container_width=True)
664
+
665
+ def _update_events_view(self, row, event_type):
666
+ """Update events timeline"""
667
+ if event_type != 'Нет':
668
+ event_html = f"""
669
+ <div class='timeline-item'>
670
+ <div class='timeline-marker'></div>
671
+ <div class='timeline-content'>
672
+ <h3>{event_type}</h3>
673
+ <p><strong>{row['Объект']}</strong></p>
674
+ <p>{row['Заголовок']}</p>
675
+ <small>{datetime.now().strftime('%H:%M:%S')}</small>
676
+ </div>
677
+ </div>
678
+ """
679
+ with self.timeline_container:
680
+ st.markdown(event_html, unsafe_allow_html=True)
681
+
682
+ def _update_analytics(self):
683
+ """Update analytics tab visualizations"""
684
+ stats = st.session_state.processing_stats
685
+
686
+ # Processing speed chart
687
+ speeds = stats['processing_speed'][-50:] # Last 50 measurements
688
+ fig_speed = go.Figure(data=go.Scatter(y=speeds, mode='lines'))
689
+ fig_speed.update_layout(title='Processing Speed Over Time')
690
+ self.speed_chart.plotly_chart(fig_speed, use_container_width=True)
691
+
692
+ # Add other analytics visualizations as needed
693
 
694
  def update_progress(self, current, total):
695
+ """Update progress bar and elapsed time"""
696
  progress = current / total
697
  self.progress_bar.progress(progress)
698
+
699
+ # Update elapsed time
700
+ elapsed = time.time() - st.session_state.processing_stats['start_time']
701
+ self.timer_display.markdown(f"⏱️ {format_elapsed_time(elapsed)}")
702
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
 
704
  class EventDetectionSystem:
705
  def __init__(self):
 
877
 
878
  def process_file(uploaded_file, model_choice, translation_method=None):
879
  df = None
880
+ processed_rows_df = pd.DataFrame()
881
+
882
  try:
883
  # Initialize UI and control systems
884
  ui = ProcessingUI()
 
922
  # Check for stop/pause
923
  if st.session_state.control.is_stopped():
924
  st.warning("Обработку остановили")
925
+ if not processed_rows_df.empty: # Only offer download if we have processed rows
926
+ output = create_output_file(processed_rows_df, uploaded_file, llm)
927
+ if output is not None:
928
+ st.download_button(
929
+ label=f"📊 Скачать результат ({processed_rows} из {total_rows} строк)",
930
+ data=output,
931
+ file_name="partial_analysis.xlsx",
932
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
933
+ key="partial_download"
934
+ )
935
  break
936
 
937
  st.session_state.control.wait_if_paused()
938
  if st.session_state.control.is_paused():
939
  st.info("Обработка на паузе. Можно возобновить.")
940
+ if not processed_rows_df.empty: # Only offer download if we have processed rows
941
+ output = create_output_file(processed_rows_df, uploaded_file, llm)
942
+ if output is not None:
943
+ st.download_button(
944
+ label=f"📊 Скачать результат ({processed_rows} из {total_rows} строк)",
945
+ data=output,
946
+ file_name="partial_analysis.xlsx",
947
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
948
+ key="partial_download"
949
+ )
950
+ break
951
  continue
952
 
953
  try:
 
967
  df.at[idx, 'Event_Type'] = event_type
968
  df.at[idx, 'Event_Summary'] = event_summary
969
 
970
+ # Update live statistics
971
+ ui.update_stats(row, sentiment, event_type)
972
+
973
  # Show events in real-time
974
  if event_type != "Нет":
975
  ui.show_event(
 
1003
  impact
1004
  )
1005
 
1006
+ processed_rows_df = pd.concat([processed_rows_df, df.iloc[[idx]]], ignore_index=True)
1007
+
1008
  # Update progress
1009
  processed_rows += 1
1010
  ui.update_progress(processed_rows, total_rows)
 
1015
 
1016
  time.sleep(0.1)
1017
 
1018
+
1019
+ return processed_rows_df if st.session_state.control.is_stopped() else df
 
 
 
 
 
 
 
 
 
 
 
 
1020
 
1021
  except Exception as e:
1022
  st.error(f"Ошибка в обработке файла: {str(e)}")
 
1402
  st.set_page_config(layout="wide")
1403
 
1404
  with st.sidebar:
1405
+ st.title("::: AI-анализ мониторинга новостей (v.3.68!):::")
1406
  st.subheader("по материалам СКАН-ИНТЕРФАКС")
1407
 
1408
  model_choice = st.radio(