Update app.py
Browse files
app.py
CHANGED
|
@@ -532,6 +532,8 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 532 |
doc = SimpleDocTemplate(buffer, pagesize=A4, rightMargin=50, leftMargin=50, topMargin=50, bottomMargin=50)
|
| 533 |
elements = []
|
| 534 |
styles = getSampleStyleSheet()
|
|
|
|
|
|
|
| 535 |
title_style = ParagraphStyle(
|
| 536 |
'CustomTitle',
|
| 537 |
parent=styles['Heading1'],
|
|
@@ -540,6 +542,7 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 540 |
alignment=1,
|
| 541 |
textColor=colors.darkblue
|
| 542 |
)
|
|
|
|
| 543 |
subtitle_style = ParagraphStyle(
|
| 544 |
'CustomSubtitle',
|
| 545 |
parent=styles['Heading2'],
|
|
@@ -547,18 +550,22 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 547 |
spaceAfter=20,
|
| 548 |
textColor=colors.darkblue
|
| 549 |
)
|
| 550 |
-
|
| 551 |
-
|
|
|
|
| 552 |
parent=styles['Normal'],
|
| 553 |
fontSize=11,
|
| 554 |
spaceAfter=12,
|
| 555 |
leftIndent=20,
|
| 556 |
textColor=colors.darkgreen
|
| 557 |
)
|
|
|
|
|
|
|
| 558 |
elements.append(Spacer(1, 100))
|
| 559 |
-
elements.append(Paragraph("Production Monitor
|
| 560 |
elements.append(Paragraph("Comprehensive Production Analysis Report", styles['Heading3']))
|
| 561 |
elements.append(Spacer(1, 50))
|
|
|
|
| 562 |
report_info = f"""
|
| 563 |
<para alignment="center">
|
| 564 |
<b>Nilsen Service & Consulting AS</b><br/>
|
|
@@ -570,10 +577,14 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 570 |
"""
|
| 571 |
elements.append(Paragraph(report_info, styles['Normal']))
|
| 572 |
elements.append(PageBreak())
|
|
|
|
|
|
|
| 573 |
elements.append(Paragraph("Executive Summary", subtitle_style))
|
|
|
|
| 574 |
total_production = stats['_total_']['total']
|
| 575 |
work_days = stats['_total_']['work_days']
|
| 576 |
daily_avg = stats['_total_']['daily_avg']
|
|
|
|
| 577 |
exec_summary = f"""
|
| 578 |
<para>
|
| 579 |
This report analyzes production data spanning <b>{work_days} working days</b>.
|
|
@@ -589,6 +600,8 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 589 |
"""
|
| 590 |
elements.append(Paragraph(exec_summary, styles['Normal']))
|
| 591 |
elements.append(Spacer(1, 20))
|
|
|
|
|
|
|
| 592 |
elements.append(Paragraph("Production Summary", styles['Heading3']))
|
| 593 |
summary_data = [['Material Type', 'Total (kg)', 'Share (%)', 'Daily Avg (kg)']]
|
| 594 |
for material, info in stats.items():
|
|
@@ -599,6 +612,7 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 599 |
f"{info['percentage']:.1f}%",
|
| 600 |
f"{info['daily_avg']:,.0f}"
|
| 601 |
])
|
|
|
|
| 602 |
summary_table = Table(summary_data, colWidths=[2*inch, 1.5*inch, 1*inch, 1.5*inch])
|
| 603 |
summary_table.setStyle(TableStyle([
|
| 604 |
('BACKGROUND', (0, 0), (-1, 0), colors.darkblue),
|
|
@@ -610,11 +624,15 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 610 |
]))
|
| 611 |
elements.append(summary_table)
|
| 612 |
elements.append(PageBreak())
|
|
|
|
|
|
|
| 613 |
elements.append(Paragraph("Production Analysis Charts", subtitle_style))
|
|
|
|
| 614 |
try:
|
| 615 |
charts = create_pdf_charts(df, stats)
|
| 616 |
except:
|
| 617 |
charts = {}
|
|
|
|
| 618 |
charts_added = False
|
| 619 |
chart_insights = {
|
| 620 |
'pie': "Material distribution shows production allocation across different materials. Balanced distribution indicates diversified production capabilities.",
|
|
@@ -622,6 +640,7 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 622 |
'bar': "Material comparison highlights performance differences and production capacities. Top performers indicate optimization opportunities.",
|
| 623 |
'shift': "Shift analysis reveals operational efficiency differences between day and night operations. Balance indicates effective resource utilization."
|
| 624 |
}
|
|
|
|
| 625 |
for chart_type, chart_title in [
|
| 626 |
('pie', "Production Distribution"),
|
| 627 |
('trend', "Production Trend"),
|
|
@@ -634,11 +653,12 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 634 |
elements.append(Paragraph(chart_title, styles['Heading3']))
|
| 635 |
elements.append(Image(chart_path, width=6*inch, height=3*inch))
|
| 636 |
insight_text = f"<i>Analysis: {chart_insights.get(chart_type, 'Chart analysis not available.')}</i>"
|
| 637 |
-
elements.append(Paragraph(insight_text,
|
| 638 |
elements.append(Spacer(1, 20))
|
| 639 |
charts_added = True
|
| 640 |
except Exception as e:
|
| 641 |
pass
|
|
|
|
| 642 |
if not charts_added:
|
| 643 |
elements.append(Paragraph("Charts Generation Failed", styles['Heading3']))
|
| 644 |
elements.append(Paragraph("Production Data Summary:", styles['Normal']))
|
|
@@ -647,8 +667,12 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 647 |
summary_text = f"• {material.replace('_', ' ').title()}: {info['total']:,.0f} kg ({info['percentage']:.1f}%)"
|
| 648 |
elements.append(Paragraph(summary_text, styles['Normal']))
|
| 649 |
elements.append(Spacer(1, 20))
|
|
|
|
| 650 |
elements.append(PageBreak())
|
|
|
|
|
|
|
| 651 |
elements.append(Paragraph("Quality Control Analysis", subtitle_style))
|
|
|
|
| 652 |
quality_data = [['Material', 'Outliers', 'Normal Range (kg)', 'Status']]
|
| 653 |
for material, info in outliers.items():
|
| 654 |
if info['count'] == 0:
|
|
@@ -657,12 +681,14 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 657 |
status = "MONITOR"
|
| 658 |
else:
|
| 659 |
status = "ATTENTION"
|
|
|
|
| 660 |
quality_data.append([
|
| 661 |
material.replace('_', ' ').title(),
|
| 662 |
str(info['count']),
|
| 663 |
info['range'],
|
| 664 |
status
|
| 665 |
])
|
|
|
|
| 666 |
quality_table = Table(quality_data, colWidths=[2*inch, 1*inch, 2*inch, 1.5*inch])
|
| 667 |
quality_table.setStyle(TableStyle([
|
| 668 |
('BACKGROUND', (0, 0), (-1, 0), colors.darkred),
|
|
@@ -673,15 +699,18 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 673 |
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey])
|
| 674 |
]))
|
| 675 |
elements.append(quality_table)
|
|
|
|
|
|
|
| 676 |
if model:
|
| 677 |
elements.append(PageBreak())
|
| 678 |
-
elements.append(Paragraph("
|
| 679 |
try:
|
| 680 |
-
|
| 681 |
except:
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
|
|
|
| 685 |
if paragraph.strip():
|
| 686 |
formatted_text = paragraph.replace('**', '<b>', 1).replace('**', '</b>', 1) \
|
| 687 |
.replace('•', ' •') \
|
|
@@ -690,8 +719,10 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 690 |
elements.append(Spacer(1, 8))
|
| 691 |
else:
|
| 692 |
elements.append(PageBreak())
|
| 693 |
-
elements.append(Paragraph("
|
| 694 |
-
elements.append(Paragraph("
|
|
|
|
|
|
|
| 695 |
elements.append(Spacer(1, 30))
|
| 696 |
footer_text = f"""
|
| 697 |
<para alignment="center">
|
|
@@ -701,6 +732,7 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
|
|
| 701 |
</para>
|
| 702 |
"""
|
| 703 |
elements.append(Paragraph(footer_text, styles['Normal']))
|
|
|
|
| 704 |
doc.build(elements)
|
| 705 |
buffer.seek(0)
|
| 706 |
return buffer
|
|
@@ -719,33 +751,39 @@ def create_csv_export(df, stats):
|
|
| 719 |
])
|
| 720 |
return summary_df
|
| 721 |
|
|
|
|
| 722 |
def add_export_section(df, stats, outliers, model):
|
| 723 |
st.markdown('<div class="section-header">📄 Export Reports</div>', unsafe_allow_html=True)
|
|
|
|
| 724 |
if 'export_ready' not in st.session_state:
|
| 725 |
st.session_state.export_ready = False
|
| 726 |
if 'pdf_buffer' not in st.session_state:
|
| 727 |
st.session_state.pdf_buffer = None
|
| 728 |
if 'csv_data' not in st.session_state:
|
| 729 |
st.session_state.csv_data = None
|
|
|
|
| 730 |
col1, col2, col3 = st.columns(3)
|
|
|
|
| 731 |
with col1:
|
| 732 |
-
if st.button("Generate PDF Report
|
| 733 |
try:
|
| 734 |
-
with st.spinner("Generating PDF
|
| 735 |
st.session_state.pdf_buffer = create_enhanced_pdf_report(df, stats, outliers, model)
|
| 736 |
st.session_state.export_ready = True
|
| 737 |
-
st.success("✅ PDF report
|
| 738 |
except Exception as e:
|
| 739 |
st.error(f"❌ PDF generation failed: {str(e)}")
|
| 740 |
st.session_state.export_ready = False
|
|
|
|
| 741 |
if st.session_state.export_ready and st.session_state.pdf_buffer:
|
| 742 |
st.download_button(
|
| 743 |
label="💾 Download PDF Report",
|
| 744 |
data=st.session_state.pdf_buffer,
|
| 745 |
-
file_name=f"
|
| 746 |
mime="application/pdf",
|
| 747 |
key="download_pdf_btn"
|
| 748 |
)
|
|
|
|
| 749 |
with col2:
|
| 750 |
if st.button("Generate CSV Summary", key="generate_csv_btn", type="primary"):
|
| 751 |
try:
|
|
@@ -753,6 +791,7 @@ def add_export_section(df, stats, outliers, model):
|
|
| 753 |
st.success("✅ CSV summary generated successfully!")
|
| 754 |
except Exception as e:
|
| 755 |
st.error(f"❌ CSV generation failed: {str(e)}")
|
|
|
|
| 756 |
if st.session_state.csv_data is not None:
|
| 757 |
csv_string = st.session_state.csv_data.to_csv(index=False)
|
| 758 |
st.download_button(
|
|
@@ -762,6 +801,7 @@ def add_export_section(df, stats, outliers, model):
|
|
| 762 |
mime="text/csv",
|
| 763 |
key="download_csv_btn"
|
| 764 |
)
|
|
|
|
| 765 |
with col3:
|
| 766 |
csv_string = df.to_csv(index=False)
|
| 767 |
st.download_button(
|
|
|
|
| 532 |
doc = SimpleDocTemplate(buffer, pagesize=A4, rightMargin=50, leftMargin=50, topMargin=50, bottomMargin=50)
|
| 533 |
elements = []
|
| 534 |
styles = getSampleStyleSheet()
|
| 535 |
+
|
| 536 |
+
# Custom styles
|
| 537 |
title_style = ParagraphStyle(
|
| 538 |
'CustomTitle',
|
| 539 |
parent=styles['Heading1'],
|
|
|
|
| 542 |
alignment=1,
|
| 543 |
textColor=colors.darkblue
|
| 544 |
)
|
| 545 |
+
|
| 546 |
subtitle_style = ParagraphStyle(
|
| 547 |
'CustomSubtitle',
|
| 548 |
parent=styles['Heading2'],
|
|
|
|
| 550 |
spaceAfter=20,
|
| 551 |
textColor=colors.darkblue
|
| 552 |
)
|
| 553 |
+
|
| 554 |
+
analysis_style = ParagraphStyle(
|
| 555 |
+
'AnalysisStyle',
|
| 556 |
parent=styles['Normal'],
|
| 557 |
fontSize=11,
|
| 558 |
spaceAfter=12,
|
| 559 |
leftIndent=20,
|
| 560 |
textColor=colors.darkgreen
|
| 561 |
)
|
| 562 |
+
|
| 563 |
+
# Title page
|
| 564 |
elements.append(Spacer(1, 100))
|
| 565 |
+
elements.append(Paragraph("Production Monitor Dashboard", title_style))
|
| 566 |
elements.append(Paragraph("Comprehensive Production Analysis Report", styles['Heading3']))
|
| 567 |
elements.append(Spacer(1, 50))
|
| 568 |
+
|
| 569 |
report_info = f"""
|
| 570 |
<para alignment="center">
|
| 571 |
<b>Nilsen Service & Consulting AS</b><br/>
|
|
|
|
| 577 |
"""
|
| 578 |
elements.append(Paragraph(report_info, styles['Normal']))
|
| 579 |
elements.append(PageBreak())
|
| 580 |
+
|
| 581 |
+
# Executive Summary
|
| 582 |
elements.append(Paragraph("Executive Summary", subtitle_style))
|
| 583 |
+
|
| 584 |
total_production = stats['_total_']['total']
|
| 585 |
work_days = stats['_total_']['work_days']
|
| 586 |
daily_avg = stats['_total_']['daily_avg']
|
| 587 |
+
|
| 588 |
exec_summary = f"""
|
| 589 |
<para>
|
| 590 |
This report analyzes production data spanning <b>{work_days} working days</b>.
|
|
|
|
| 600 |
"""
|
| 601 |
elements.append(Paragraph(exec_summary, styles['Normal']))
|
| 602 |
elements.append(Spacer(1, 20))
|
| 603 |
+
|
| 604 |
+
# Production Summary Table
|
| 605 |
elements.append(Paragraph("Production Summary", styles['Heading3']))
|
| 606 |
summary_data = [['Material Type', 'Total (kg)', 'Share (%)', 'Daily Avg (kg)']]
|
| 607 |
for material, info in stats.items():
|
|
|
|
| 612 |
f"{info['percentage']:.1f}%",
|
| 613 |
f"{info['daily_avg']:,.0f}"
|
| 614 |
])
|
| 615 |
+
|
| 616 |
summary_table = Table(summary_data, colWidths=[2*inch, 1.5*inch, 1*inch, 1.5*inch])
|
| 617 |
summary_table.setStyle(TableStyle([
|
| 618 |
('BACKGROUND', (0, 0), (-1, 0), colors.darkblue),
|
|
|
|
| 624 |
]))
|
| 625 |
elements.append(summary_table)
|
| 626 |
elements.append(PageBreak())
|
| 627 |
+
|
| 628 |
+
# Charts Section
|
| 629 |
elements.append(Paragraph("Production Analysis Charts", subtitle_style))
|
| 630 |
+
|
| 631 |
try:
|
| 632 |
charts = create_pdf_charts(df, stats)
|
| 633 |
except:
|
| 634 |
charts = {}
|
| 635 |
+
|
| 636 |
charts_added = False
|
| 637 |
chart_insights = {
|
| 638 |
'pie': "Material distribution shows production allocation across different materials. Balanced distribution indicates diversified production capabilities.",
|
|
|
|
| 640 |
'bar': "Material comparison highlights performance differences and production capacities. Top performers indicate optimization opportunities.",
|
| 641 |
'shift': "Shift analysis reveals operational efficiency differences between day and night operations. Balance indicates effective resource utilization."
|
| 642 |
}
|
| 643 |
+
|
| 644 |
for chart_type, chart_title in [
|
| 645 |
('pie', "Production Distribution"),
|
| 646 |
('trend', "Production Trend"),
|
|
|
|
| 653 |
elements.append(Paragraph(chart_title, styles['Heading3']))
|
| 654 |
elements.append(Image(chart_path, width=6*inch, height=3*inch))
|
| 655 |
insight_text = f"<i>Analysis: {chart_insights.get(chart_type, 'Chart analysis not available.')}</i>"
|
| 656 |
+
elements.append(Paragraph(insight_text, analysis_style))
|
| 657 |
elements.append(Spacer(1, 20))
|
| 658 |
charts_added = True
|
| 659 |
except Exception as e:
|
| 660 |
pass
|
| 661 |
+
|
| 662 |
if not charts_added:
|
| 663 |
elements.append(Paragraph("Charts Generation Failed", styles['Heading3']))
|
| 664 |
elements.append(Paragraph("Production Data Summary:", styles['Normal']))
|
|
|
|
| 667 |
summary_text = f"• {material.replace('_', ' ').title()}: {info['total']:,.0f} kg ({info['percentage']:.1f}%)"
|
| 668 |
elements.append(Paragraph(summary_text, styles['Normal']))
|
| 669 |
elements.append(Spacer(1, 20))
|
| 670 |
+
|
| 671 |
elements.append(PageBreak())
|
| 672 |
+
|
| 673 |
+
# Quality Control Analysis
|
| 674 |
elements.append(Paragraph("Quality Control Analysis", subtitle_style))
|
| 675 |
+
|
| 676 |
quality_data = [['Material', 'Outliers', 'Normal Range (kg)', 'Status']]
|
| 677 |
for material, info in outliers.items():
|
| 678 |
if info['count'] == 0:
|
|
|
|
| 681 |
status = "MONITOR"
|
| 682 |
else:
|
| 683 |
status = "ATTENTION"
|
| 684 |
+
|
| 685 |
quality_data.append([
|
| 686 |
material.replace('_', ' ').title(),
|
| 687 |
str(info['count']),
|
| 688 |
info['range'],
|
| 689 |
status
|
| 690 |
])
|
| 691 |
+
|
| 692 |
quality_table = Table(quality_data, colWidths=[2*inch, 1*inch, 2*inch, 1.5*inch])
|
| 693 |
quality_table.setStyle(TableStyle([
|
| 694 |
('BACKGROUND', (0, 0), (-1, 0), colors.darkred),
|
|
|
|
| 699 |
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey])
|
| 700 |
]))
|
| 701 |
elements.append(quality_table)
|
| 702 |
+
|
| 703 |
+
# Intelligent Analysis (if model available)
|
| 704 |
if model:
|
| 705 |
elements.append(PageBreak())
|
| 706 |
+
elements.append(Paragraph("Intelligent Analysis", subtitle_style))
|
| 707 |
try:
|
| 708 |
+
analysis = generate_ai_summary(model, df, stats, outliers)
|
| 709 |
except:
|
| 710 |
+
analysis = "Intelligent analysis temporarily unavailable."
|
| 711 |
+
|
| 712 |
+
analysis_paragraphs = analysis.split('\n\n')
|
| 713 |
+
for paragraph in analysis_paragraphs:
|
| 714 |
if paragraph.strip():
|
| 715 |
formatted_text = paragraph.replace('**', '<b>', 1).replace('**', '</b>', 1) \
|
| 716 |
.replace('•', ' •') \
|
|
|
|
| 719 |
elements.append(Spacer(1, 8))
|
| 720 |
else:
|
| 721 |
elements.append(PageBreak())
|
| 722 |
+
elements.append(Paragraph("Advanced Analysis", subtitle_style))
|
| 723 |
+
elements.append(Paragraph("Advanced analysis features unavailable - Google API key not configured. Please set the GOOGLE_API_KEY environment variable or configure it in Streamlit secrets to enable intelligent insights.", styles['Normal']))
|
| 724 |
+
|
| 725 |
+
# Footer
|
| 726 |
elements.append(Spacer(1, 30))
|
| 727 |
footer_text = f"""
|
| 728 |
<para alignment="center">
|
|
|
|
| 732 |
</para>
|
| 733 |
"""
|
| 734 |
elements.append(Paragraph(footer_text, styles['Normal']))
|
| 735 |
+
|
| 736 |
doc.build(elements)
|
| 737 |
buffer.seek(0)
|
| 738 |
return buffer
|
|
|
|
| 751 |
])
|
| 752 |
return summary_df
|
| 753 |
|
| 754 |
+
|
| 755 |
def add_export_section(df, stats, outliers, model):
|
| 756 |
st.markdown('<div class="section-header">📄 Export Reports</div>', unsafe_allow_html=True)
|
| 757 |
+
|
| 758 |
if 'export_ready' not in st.session_state:
|
| 759 |
st.session_state.export_ready = False
|
| 760 |
if 'pdf_buffer' not in st.session_state:
|
| 761 |
st.session_state.pdf_buffer = None
|
| 762 |
if 'csv_data' not in st.session_state:
|
| 763 |
st.session_state.csv_data = None
|
| 764 |
+
|
| 765 |
col1, col2, col3 = st.columns(3)
|
| 766 |
+
|
| 767 |
with col1:
|
| 768 |
+
if st.button("Generate PDF Report", key="generate_pdf_btn", type="primary"):
|
| 769 |
try:
|
| 770 |
+
with st.spinner("Generating comprehensive PDF report..."):
|
| 771 |
st.session_state.pdf_buffer = create_enhanced_pdf_report(df, stats, outliers, model)
|
| 772 |
st.session_state.export_ready = True
|
| 773 |
+
st.success("✅ PDF report generated successfully!")
|
| 774 |
except Exception as e:
|
| 775 |
st.error(f"❌ PDF generation failed: {str(e)}")
|
| 776 |
st.session_state.export_ready = False
|
| 777 |
+
|
| 778 |
if st.session_state.export_ready and st.session_state.pdf_buffer:
|
| 779 |
st.download_button(
|
| 780 |
label="💾 Download PDF Report",
|
| 781 |
data=st.session_state.pdf_buffer,
|
| 782 |
+
file_name=f"production_report_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf",
|
| 783 |
mime="application/pdf",
|
| 784 |
key="download_pdf_btn"
|
| 785 |
)
|
| 786 |
+
|
| 787 |
with col2:
|
| 788 |
if st.button("Generate CSV Summary", key="generate_csv_btn", type="primary"):
|
| 789 |
try:
|
|
|
|
| 791 |
st.success("✅ CSV summary generated successfully!")
|
| 792 |
except Exception as e:
|
| 793 |
st.error(f"❌ CSV generation failed: {str(e)}")
|
| 794 |
+
|
| 795 |
if st.session_state.csv_data is not None:
|
| 796 |
csv_string = st.session_state.csv_data.to_csv(index=False)
|
| 797 |
st.download_button(
|
|
|
|
| 801 |
mime="text/csv",
|
| 802 |
key="download_csv_btn"
|
| 803 |
)
|
| 804 |
+
|
| 805 |
with col3:
|
| 806 |
csv_string = df.to_csv(index=False)
|
| 807 |
st.download_button(
|