CoolFace
Apppublic

lscsc/Process_dash_pub

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
quality_dashboard2-1.py1764 linesDownload Raw Back to backup
1# -*- coding: utf-8 -*-
2
3import streamlit as st
4import plotly.graph_objects as go
5import pandas as pd
6import numpy as np
7import warnings
8import platform
9import plotly.express as px
10from scipy import stats
11import streamlit.components.v1 as components
12from datetime import datetime, timedelta
13import os
14
15
16# 페이지 설정
17st.set_page_config(
18    page_title="품질 공정정보 대시보드",
19    page_icon="📊",
20    layout="wide",
21    initial_sidebar_state="expanded"
22)
23
24
25# 경고 메시지 무시
26warnings.filterwarnings('ignore')
27
28
29
30def add_sidebar_styles():
31    """사이드바 스타일 추가 함수 - 간결한 디자인"""
32    st.markdown(
33        """
34        <style>
35        /* 간결한 사이드바 기본 스타일 */
36        [data-testid="stSidebar"] {
37            background-color: #f8f9fa;
38            padding: 1rem 0.5rem;
39            color: #333;
40        }
41        
42        /* 사이드바 내용 컨테이너 */
43        [data-testid="stSidebar"] > div:first-child {
44            padding: 1rem 0.5rem;
45        }
46        
47        /* 사이드바 제목 스타일 */
48        [data-testid="stSidebar"] [data-testid="stMarkdown"] h1 {
49            font-size: 1.3rem;
50            color: #2C3E50;
51            font-weight: 500;
52            margin-bottom: 1rem;
53            padding-bottom: 0.5rem;
54            border-bottom: 1px solid #e0e0e0;
55        }
56        
57        /* 사이드바 소제목 스타일 */
58        [data-testid="stSidebar"] [data-testid="stMarkdown"] h3 {
59            font-size: 1rem;
60            color: #2C3E50;
61            margin-top: 1rem;
62            margin-bottom: 0.5rem;
63            font-weight: 500;
64        }
65        
66        /* 구분선 스타일 */
67        [data-testid="stSidebar"] hr {
68            border-color: #e0e0e0;
69            margin: 1rem 0;
70        }
71        
72        /* 라디오 버튼 컨테이너 스타일 */
73        [data-testid="stSidebar"] [data-testid="stRadio"] > div {
74            display: flex;
75            flex-direction: column;
76            gap: 0.25rem;
77        }
78        
79        /* 라디오 버튼 레이블 스타일 */
80        [data-testid="stSidebar"] [data-testid="stRadio"] > div > label {
81            border-radius: 4px;
82            padding: 0.5rem 0.5rem 0.5rem 1.5rem;  /* 왼쪽 패딩 증가 */
83            transition: all 0.2s ease;
84            border-left: 2px solid transparent;
85        }
86        
87        /* 라디오 버튼 호버 효과 */
88        [data-testid="stSidebar"] [data-testid="stRadio"] > div > label:hover {
89            background-color: rgba(0, 0, 0, 0.05);
90            border-left: 2px solid #3498db;
91        }
92        
93        /* 선택된 라디오 버튼 스타일 */
94        [data-testid="stSidebar"] [data-testid="stRadio"] > div > label[aria-checked="true"] {
95            background-color: rgba(52, 152, 219, 0.1);
96            border-left: 2px solid #3498db;
97        }
98        
99        /* 버튼 스타일 */
100        [data-testid="stSidebar"] button {
101            width: 100%;
102            background-color: #f1f3f5;
103            color: #333;
104            border-radius: 4px;
105            padding: 0.5rem;
106            font-weight: 500;
107            border: 1px solid #dee2e6;
108            margin-top: 1rem;
109            transition: all 0.2s ease;
110        }
111        
112        /* 버튼 호버 효과 */
113        [data-testid="stSidebar"] button:hover {
114            background-color: #e9ecef;
115            border-color: #ced4da;
116        }
117        
118        /* 로고 컨테이너 스타일 */
119        .sidebar-logo-container {
120            text-align: center;
121            padding: 0.5rem 0;
122            margin-bottom: 1rem;
123        }
124        
125        /* 로고 이미지 스타일 */
126        .sidebar-logo-container img {
127            max-width: 60px;
128            height: auto;
129        }
130        
131        /* 사이드바 푸터 스타일 */
132        .sidebar-footer {
133            margin-top: 1rem;
134            padding-top: 0.5rem;
135            border-top: 1px solid #e0e0e0;
136            font-size: 0.75rem;
137            color: #6c757d;
138            text-align: center;
139        }
140        </style>
141        """, 
142        unsafe_allow_html=True
143    )
144
145
146
147
148# 페이지 설정 및 전체 스타일 개선
149def add_main_styles():
150    """메인 컨텐츠 스타일 추가 함수 - 간결한 디자인"""
151    st.markdown(
152        """
153        <style>
154        /* 전체 페이지 스타일 */
155        .stApp {
156            background-color: #ffffff;
157        }
158        
159        /* 메인 컨테이너 스타일 */
160        [data-testid="stAppViewContainer"] {
161            background-color: #ffffff;
162            padding: 0.5rem;
163        }
164        
165        /* 메인 컨텐츠 영역 스타일 */
166        .main .block-container {
167            padding: 1.5rem;
168            max-width: 1200px;
169            margin: 0 auto;
170        }
171        
172        /* 타이틀 스타일 */
173        h1 {
174            color: #2C3E50;
175            font-weight: 600;
176            font-size: 1.8rem;
177            margin-bottom: 1rem;
178            padding-bottom: 0.5rem;
179            border-bottom: 1px solid #f0f0f0;
180        }
181        
182        /* 서브 타이틀 스타일 */
183        h2, h3 {
184            color: #2C3E50;
185            margin-top: 1rem;
186            margin-bottom: 0.75rem;
187            font-weight: 500;
188        }
189        
190        h3 {
191            font-size: 1.2rem;
192            padding-left: 1rem;  /* 왼쪽 여백 증가 */
193            margin-left: 0.5rem;  /* 추가 왼쪽 마진 */
194            border-left: 3px solid #3498db;
195        }
196        
197        /* 위젯 간격 조정 */
198        .stSelectbox, .stMultiSelect {
199            margin-bottom: 0.75rem;
200        }
201        
202        /* 메트릭 카드 스타일 */
203        [data-testid="stMetric"] {
204            background-color: #f8f9fa;
205            border-radius: 6px;
206            padding: 0.75rem !important;
207            border-left: 3px solid #3498db;
208        }
209        
210        /* 메트릭 레이블 스타일 */
211        [data-testid="stMetric"] > div:first-child {
212            color: #6c757d;
213        }
214        
215        /* 메트릭 값 스타일 */
216        [data-testid="stMetric"] > div:nth-child(2) {
217            font-size: 1.3rem;
218            font-weight: 600;
219            color: #2C3E50;
220        }
221        
222        /* 데이터프레임 스타일 */
223        .dataframe {
224            border-collapse: collapse !important;
225            width: 100% !important;
226            border-radius: 6px;
227            overflow: hidden;
228        }
229        
230        .dataframe th {
231            background-color: #f1f3f5 !important;
232            color: #495057 !important;
233            font-weight: 500 !important;
234            padding: 0.5rem 0.75rem !important;
235            text-align: left !important;
236        }
237        
238        .dataframe td {
239            padding: 0.5rem 0.75rem !important;
240            border-top: 1px solid #f0f0f0 !important;
241        }
242        
243        .dataframe tr:nth-child(even) {
244            background-color: #f8f9fa !important;
245        }
246        
247        /* 탭 스타일 개선 */
248        .stTabs [data-baseweb="tab-list"] {
249            gap: 0;
250            border-bottom: 1px solid #e0e0e0;
251        }
252        
253        .stTabs [data-baseweb="tab"] {
254            height: 40px;
255            white-space: pre-wrap;
256            background-color: transparent;
257            border-radius: 0;
258            border-bottom: 2px solid transparent;
259            padding: 0 1rem;
260            font-weight: 500;
261            color: #6c757d;
262            transition: all 0.2s ease;
263        }
264        
265        .stTabs [aria-selected="true"] {
266            color: #3498db !important;
267            border-bottom: 2px solid #3498db !important;
268            background-color: transparent !important;
269        }
270        
271        /* 이슈 박스 스타일 */
272        .issue-box {
273            background-color: #f8f9fa;
274            border-radius: 6px;
275            padding: 1rem;
276            margin: 0.75rem 0;
277            border-left: 3px solid #ff7043;
278        }
279        
280        /* 공정 박스 스타일 */
281        .process-box {
282            background-color: #f8f9fa;
283            border-radius: 6px;
284            padding: 1rem;
285            margin: 0.75rem 0;
286            border-left: 3px solid #66bb6a;
287        }
288        
289        /* 불량 박스 스타일 */
290        .defect-box {
291            background-color: #f8f9fa;
292            border-radius: 6px;
293            padding: 1rem;
294            margin: 0.75rem 0;
295            border-left: 3px solid #5c6bc0;
296        }
297        
298        /* 서브헤더 스타일 추가 */
299        .stMarkdown h3 {
300            margin-left: 0.5rem;
301            padding-left: 1rem;
302        }
303        
304        /* 추가 마크다운 내 소제목 스타일 */
305        .stMarkdown h4 {
306            margin-left: 0.75rem;
307        }
308        </style>
309        """, 
310        unsafe_allow_html=True
311    )
312
313
314def check_password():
315    """비밀번호 확인 함수"""
316    if "password_correct" not in st.session_state:
317        st.session_state["password_correct"] = False
318    
319    if not st.session_state["password_correct"]:
320        # 비밀번호 입력 필드를 한 번만 표시
321        with st.container():
322            password = st.text_input(
323                "비밀번호를 입력하세요", 
324                type="password",
325                key="password_input"
326            )
327            
328            if password:
329                if password == "1234":
330                    st.session_state["password_correct"] = True
331                    # 비밀번호가 맞으면 페이지 리프레시
332                    st.rerun()  # experimental_rerun() 대신 rerun() 사용
333                else:
334                    st.error("비밀번호가 올바르지 않습니다.")
335                    return False
336            return False
337    
338    return True
339
340
341
342def initialize_session_state():
343    """세션 상태 초기화 함수"""
344    if 'password_correct' not in st.session_state:
345        st.session_state.password_correct = False
346    
347    if 'selected_product' not in st.session_state:
348        st.session_state.selected_product = None
349    
350    if 'selected_data' not in st.session_state:
351        st.session_state.selected_data = None
352        
353    if 'tab_selection' not in st.session_state:
354        st.session_state.tab_selection = "공정 현황"
355    
356    if 'data_loaded' not in st.session_state:
357        st.session_state.data_loaded = False
358    
359    if 'data_dict' not in st.session_state:
360        st.session_state.data_dict = {}
361
362    def password_entered():
363        """사용자가 입력한 비밀번호가 올바른지 확인합니다."""
364        if st.session_state["password"] == "1234":  # 간단한 비밀번호 설정
365            st.session_state["password_correct"] = True
366            del st.session_state["password"]
367        else:
368            st.session_state["password_correct"] = False
369
370    if "password_correct" not in st.session_state:
371        # 로그인 화면 표시
372        st.markdown(
373            """
374            <div class="login-container">
375                <div class="login-logo">
376                    <img src="https://cdn-icons-png.flaticon.com/512/1995/1995515.png" width="80">
377                </div>
378                <h2 class="login-title">품질관리 시스템 로그인</h2>
379            </div>
380            """, 
381            unsafe_allow_html=True
382        )
383        
384        st.text_input(
385            "비밀번호를 입력하세요", 
386            type="password", 
387            on_change=password_entered, 
388            key="password",
389            placeholder="비밀번호 4자리 (힌트: 1234)"
390        )
391        return False
392    elif not st.session_state["password_correct"]:
393        # 잘못된 비밀번호 입력 시
394        st.markdown(
395            """
396            <div class="login-container">
397                <div class="login-logo">
398                    <img src="https://cdn-icons-png.flaticon.com/512/1995/1995515.png" width="80">
399                </div>
400                <h2 class="login-title">품질관리 시스템 로그인</h2>
401                <p class="login-error">😕 비밀번호가 올바르지 않습니다.</p>
402            </div>
403            """, 
404            unsafe_allow_html=True
405        )
406        
407        st.text_input(
408            "비밀번호를 입력하세요", 
409            type="password", 
410            on_change=password_entered, 
411            key="password",
412            placeholder="비밀번호 4자리 (힌트: 1234)"
413        )
414        return False
415    else:
416        return True
417
418def initialize_session_state():
419    """세션 상태 초기화 함수"""
420    if 'password_correct' not in st.session_state:
421        st.session_state.password_correct = False
422    
423    if 'selected_product' not in st.session_state:
424        st.session_state.selected_product = None
425    
426    if 'selected_data' not in st.session_state:
427        st.session_state.selected_data = None
428        
429    if 'tab_selection' not in st.session_state:
430        st.session_state.tab_selection = "공정 현황"
431    
432    if 'data_loaded' not in st.session_state:
433        st.session_state.data_loaded = False
434    
435    if 'data_dict' not in st.session_state:
436        st.session_state.data_dict = {}
437
438
439def safe_read_excel(filepath, default_data=None):
440    """안전하게 엑셀 파일을 읽는 함수"""
441    try:
442        if os.path.exists(filepath):
443            return pd.read_excel(filepath)
444        else:
445            st.warning(f"파일을 찾을 수 없습니다: {filepath}")
446            return default_data if default_data is not None else pd.DataFrame()
447    except Exception as e:
448        st.warning(f"파일 읽기 오류 ({filepath}): {str(e)}")
449        return default_data if default_data is not None else pd.DataFrame()
450
451def load_data():
452    """데이터 로드 함수"""
453    # 이미 로드된 데이터가 있으면 재사용
454    if st.session_state.data_loaded:
455        return st.session_state.data_dict
456    
457    try:
458        # 데이터 사전 초기화
459        data_dict = {}
460        
461        # 각 데이터 파일 로드 (안전하게)
462        data_dict['process_info'] = safe_read_excel('공정상세정보/장섬유공정정보.xlsx')
463        data_dict['quality_issues'] = safe_read_excel('품질이슈정보/장섬유이슈정보.xlsx')
464        data_dict['process_specs'] = safe_read_excel('제품 및 공정규격/제품 및 공정규격.xlsx')
465        data_dict['process_capability'] = safe_read_excel('월별 공정능력 정보/월별 공정능력 정보.xlsx')
466        data_dict['defect_rates'] = safe_read_excel('불량률 정보/불량률 정보.xlsx')
467        data_dict['customer_info'] = safe_read_excel('거래선 정보/거래선 정보.xlsx')
468        
469        # 날짜 형식 변환 (품질이슈정보의 날짜 컬럼)
470        if not data_dict['quality_issues'].empty and '기준일' in data_dict['quality_issues'].columns:
471            data_dict['quality_issues']['기준일'] = pd.to_datetime(data_dict['quality_issues']['기준일'], errors='coerce')
472        
473        # 데이터 로드 상태 및 데이터 저장
474        st.session_state.data_loaded = True
475        st.session_state.data_dict = data_dict
476        
477        # 데이터 로드 성공 메시지
478        st.success("데이터가 성공적으로 로드되었습니다.")
479        
480        return data_dict
481    except Exception as e:
482        st.error(f"데이터 로드 중 오류 발생: {str(e)}")
483        return {}
484
485def load_process_infographic():
486    """공정 인포그래픽 HTML 로드 함수"""
487    try:
488        filepath = '공정기본정보/유리 장섬유 생산공정 인포그래픽.html'
489        if os.path.exists(filepath):
490            with open(filepath, 'r', encoding='utf-8') as f:
491                html_content = f.read()
492            return html_content
493        else:
494            st.warning(f"인포그래픽 파일을 찾을 수 없습니다: {filepath}")
495            # 대체 HTML 콘텐츠 제공
496            return """
497            <div style="text-align: center; padding: 20px; background-color: #f8f9fa; border-radius: 10px;">
498                <h3>인포그래픽을 불러올 수 없습니다</h3>
499                <p>파일을 찾을 수 없거나 접근할 수 없습니다.</p>
500            </div>
501            """
502    except Exception as e:
503        st.warning(f"인포그래픽 로드 중 오류 발생: {str(e)}")
504        # 오류 발생 시 대체 HTML 콘텐츠 제공
505        return """
506        <div style="text-align: center; padding: 20px; background-color: #f8f9fa; border-radius: 10px;">
507            <h3>인포그래픽 로드 오류</h3>
508            <p>파일을 읽는 중 오류가 발생했습니다.</p>
509        </div>
510        """
511
512def calculate_process_capability(data, ucl, lcl, sigma_level=3):
513    """공정능력지수 계산 함수"""
514    try:
515        if len(data) == 0:
516            return {
517                'Cp': 0,
518                'Cpu': 0,
519                'Cpl': 0,
520                'Cpk': 0,
521                'PPM': 0
522            }
523        
524        mean = data.mean()
525        std = data.std()
526        
527        if std == 0:
528            return {
529                'Cp': float('inf'),
530                'Cpu': float('inf'),
531                'Cpl': float('inf'),
532                'Cpk': float('inf'),
533                'PPM': 0
534            }
535    
536        # 공정능력지수 계산
537        cp = (ucl - lcl) / (6 * std) if std != 0 else float('inf')
538        cpu = (ucl - mean) / (3 * std) if std != 0 else float('inf')
539        cpl = (mean - lcl) / (3 * std) if std != 0 else float('inf')
540        cpk = min(cpu, cpl)
541    
542        # 예상불량률 계산 (ppm 단위)
543        z_upper = (ucl - mean) / std if std != 0 else float('inf')
544        z_lower = (mean - lcl) / std if std != 0 else float('inf')
545        ppm_upper = stats.norm.sf(z_upper) * 1000000
546        ppm_lower = stats.norm.sf(z_lower) * 1000000
547        total_ppm = ppm_upper + ppm_lower
548    
549        return {
550            'Cp': cp,
551            'Cpu': cpu,
552            'Cpl': cpl,
553            'Cpk': cpk,
554            'PPM': total_ppm
555        }
556
557    except Exception as e:
558        st.error(f"공정능력지수 계산 중 오류 발생: {str(e)}")
559        return {
560            'Cp': 0,
561            'Cpu': 0,
562            'Cpl': 0,
563            'Cpk': 0,
564            'PPM': 0
565        }
566
567def display_process_overview(data):
568    """공정 현황 개요 표시 함수"""
569    if not isinstance(data, dict):
570        st.error("유효하지 않은 데이터 형식입니다.")
571        return
572    
573    st.markdown("### 유리 장섬유 생산공정 개요")
574    
575    # 공정 정보 데이터프레임 가져오기 (data 딕셔너리에서)
576    process_info_df = data.get('process_info', pd.DataFrame())
577    
578    # 공정 정보가 비어 있는 경우 처리
579    if process_info_df.empty:
580        st.warning("공정 정보를 불러올 수 없습니다.")
581        return
582    
583    # 공정 흐름도 시각화 (개선된 버전)
584    st.markdown("#### 공정 흐름도")
585    
586    # 공정 단계 추출
587    process_stages = process_info_df['표준단위공정'].unique()
588    
589    # 각 공정 단계별 아이콘 매핑
590    process_icons = {
591        "Batch": "flask",
592        "Forming": "industry",
593        "Sizing": "tint",
594        "Winding": "sync",
595        "Drying": "fire",
596        "Packing": "box"
597    }
598    
599    # 공정 단계별 색상 매핑
600    process_colors = {
601        "Batch": "#4285F4",      # 파란색
602        "Forming": "#EA4335",    # 빨간색
603        "Sizing": "#FBBC05",     # 노란색
604        "Winding": "#34A853",    # 녹색
605        "Drying": "#FF6D01",     # 주황색
606        "Packing": "#46BDC6"     # 청록색
607    }
608    
609    # 기본 색상 및 아이콘 설정
610    default_color = "#6C757D"
611    default_icon = "cog"
612    
613    # HTML로 공정 흐름도 생성
614    flow_html = """
615    <style>
616        .flow-container {
617            display: flex;
618            justify-content: space-between;
619            align-items: center;
620            margin: 30px 0;
621            position: relative;
622            overflow-x: auto;
623            padding: 20px 10px;
624        }
625        
626        .flow-container::before {
627            content: "";
628            position: absolute;
629            top: 50%;
630            left: 70px;
631            right: 70px;
632            height: 3px;
633            background: linear-gradient(to right, #e0e0e0, #2563EB, #e0e0e0);
634            z-index: 1;
635        }
636        
637        .flow-step {
638            display: flex;
639            flex-direction: column;
640            align-items: center;
641            position: relative;
642            z-index: 2;
643            min-width: 120px;
644        }
645        
646        .flow-icon {
647            width: 80px;
648            height: 80px;
649            border-radius: 50%;
650            display: flex;
651            align-items: center;
652            justify-content: center;
653            margin-bottom: 12px;
654            color: white;
655            font-size: 30px;
656            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
657            position: relative;
658            background: white;
659            border: 3px solid;
660            transition: transform 0.3s ease, box-shadow 0.3s ease;
661        }
662        
663        .flow-icon:hover {
664            transform: translateY(-5px);
665            box-shadow: 0 6px 12px rgba(0,0,0,0.15);
666        }
667        
668        .flow-step-title {
669            font-size: 16px;
670            font-weight: 600;
671            text-align: center;
672            color: #333;
673        }
674        
675        .flow-step-desc {
676            font-size: 13px;
677            color: #666;
678            text-align: center;
679            margin-top: 5px;
680            max-width: 120px;
681        }
682        
683        .arrow {
684            position: absolute;
685            top: 50%;
686            transform: translateY(-50%);
687            right: -20px;
688            color: #2563EB;
689            font-size: 24px;
690            z-index: 3;
691        }
692        
693        .last-step .arrow {
694            display: none;
695        }
696        
697        @media (max-width: 768px) {
698            .flow-container {
699                flex-direction: column;
700                align-items: flex-start;
701                overflow-x: visible;
702            }
703            
704            .flow-container::before {
705                top: 70px;
706                bottom: 70px;
707                left: 40px;
708                width: 3px;
709                height: auto;
710                background: linear-gradient(to bottom, #e0e0e0, #2563EB, #e0e0e0);
711            }
712            
713            .flow-step {
714                flex-direction: row;
715                margin-bottom: 30px;
716                width: 100%;
717            }
718            
719            .flow-icon {
720                margin-right: 20px;
721                margin-bottom: 0;
722            }
723            
724            .flow-step-content {
725                text-align: left;
726            }
727            
728            .flow-step-title {
729                text-align: left;
730            }
731            
732            .flow-step-desc {
733                text-align: left;
734                max-width: none;
735            }
736            
737            .arrow {
738                transform: rotate(90deg);
739                right: auto;
740                top: auto;
741                bottom: -25px;
742                left: 40px;
743            }
744        }
745    </style>
746    
747    <div class="flow-container">
748    """
749    
750    # 각 공정 단계별 HTML 생성
751    for i, stage in enumerate(process_stages):
752        # 해당 공정 단계의 세부 공정들 추출
753        sub_processes = process_info_df[process_info_df['표준단위공정'] == stage]['실공정(Step)'].tolist()
754        sub_process_text = ", ".join(sub_processes)
755        
756        # 아이콘 및 색상 결정
757        icon = process_icons.get(stage, default_icon)
758        color = process_colors.get(stage, default_color)
759        
760        # 마지막 단계 여부 확인
761        is_last = (i == len(process_stages) - 1)
762        last_class = " last-step" if is_last else ""
763        
764        # 단계별 HTML 추가
765        flow_html += f"""
766        <div class="flow-step{last_class}">
767            <div class="flow-icon" style="border-color: {color};">
768                <i class="fas fa-{icon}" style="color: {color};"></i>
769            </div>
770            <div class="flow-step-content">
771                <div class="flow-step-title">{stage}</div>
772                <div class="flow-step-desc">{sub_process_text[:30]}{'...' if len(sub_process_text) > 30 else ''}</div>
773            </div>
774            {'' if is_last else '<div class="arrow"><i class="fas fa-chevron-right"></i></div>'}
775        </div>
776        """
777    
778    flow_html += """
779    </div>
780    """
781    
782    # HTML 흐름도 표시
783    st.components.v1.html(flow_html, height=200)
784    
785    # 공정 단계 선택 및 세부 정보 표시 (기존 코드)
786    st.markdown("#### 공정 단계별 상세 정보")
787    
788    # 공정 단계 선택
789    selected_stage = st.selectbox("공정 단계 선택", process_stages)
790    
791    # 선택된 공정 단계의 세부 공정 표시
792    sub_processes = process_info_df[process_info_df['표준단위공정'] == selected_stage]
793    
794    if not sub_processes.empty:
795        # 세부 공정 선택
796        selected_sub = st.selectbox("세부 공정 선택", sub_processes['실공정(Step)'].tolist())
797        
798        # 선택된 세부 공정 정보 표시
799        process_detail = sub_processes[sub_processes['실공정(Step)'] == selected_sub].iloc[0]
800        
801        # 공정 정보 카드 표시
802        col1, col2 = st.columns(2)
803        
804        with col1:
805            st.markdown(f"""
806            <div class="process-box">
807                <h4 style="margin-top: 0;">공정 개요</h4>
808                <p><strong>공정명:</strong> {selected_sub}</p>
809                <p><strong>공정내용:</strong> {process_detail['공정내용']}</p>
810            </div>
811            """, unsafe_allow_html=True)
812        
813        with col2:
814            st.markdown(f"""
815            <div class="process-box">
816                <h4 style="margin-top: 0;">품질 관리 포인트</h4>
817                <p><strong>품질 인자:</strong> {process_detail['품질 인자']}</p>
818            </div>
819            """, unsafe_allow_html=True)
820        
821        # 공정 조건 인자와 개선 포인트 표시
822        col1, col2 = st.columns(2)
823        
824        with col1:
825            st.markdown(f"""
826            <div class="process-box">
827                <h4 style="margin-top: 0;">공정 조건 인자</h4>
828                <p>{process_detail['공정 조건 인자']}</p>
829            </div>
830            """, unsafe_allow_html=True)
831        
832        with col2:
833            st.markdown(f"""
834            <div class="issue-box">
835                <h4 style="margin-top: 0;">개선 Point (문제점)</h4>
836                <p>{process_detail['개선 Point (문제점)']}</p>
837            </div>
838            """, unsafe_allow_html=True)
839    else:
840        st.warning(f"{selected_stage} 공정에 대한 정보를 찾을 수 없습니다.")
841    
842    # 전체 공정 맵 표시
843    st.markdown("#### 전체 공정 맵")
844    
845    # 공정 데이터 테이블로 표시
846    st.dataframe(
847        process_info_df,
848        column_config={
849            "표준단위공정": st.column_config.TextColumn("표준단위공정", width="medium"),
850            "실공정(Step)": st.column_config.TextColumn("실공정(Step)", width="medium"),
851            "공정내용": st.column_config.TextColumn("공정내용", width="large"),
852            "공정 조건 인자": st.column_config.TextColumn("공정 조건 인자", width="large"),
853            "품질 인자": st.column_config.TextColumn("품질 인자", width="large"),
854            "개선 Point (문제점)": st.column_config.TextColumn("개선 Point (문제점)", width="large"),
855        },
856        use_container_width=True,
857        hide_index=True
858    )
859    
860    # 공정별 품질 인자 시각화
861    st.markdown("#### 공정별 품질 인자")
862    
863    # 품질 인자 데이터 준비
864    quality_factors = []
865    for _, row in process_info_df.iterrows():
866        factors = row['품질 인자'].split(',')
867        for factor in factors:
868            quality_factors.append({
869                '공정': row['표준단위공정'] + '-' + row['실공정(Step)'],
870                '품질 인자': factor.strip()
871            })
872    
873    quality_df = pd.DataFrame(quality_factors)
874    
875    # 품질 인자 빈도 계산
876    factor_counts = quality_df['품질 인자'].value_counts().reset_index()
877    factor_counts.columns = ['품질 인자', '빈도']
878    factor_counts = factor_counts.sort_values('빈도', ascending=False).head(10)
879    
880    # 품질 인자 빈도 차트
881    fig = px.bar(
882        factor_counts,
883        x='품질 인자',
884        y='빈도',
885        title='주요 품질 인자 TOP 10',
886        color='빈도',
887        color_continuous_scale='Viridis'
888    )
889    
890    fig.update_layout(
891        xaxis_title='품질 인자',
892        yaxis_title='언급 빈도',
893        height=400
894    )
895    
896    st.plotly_chart(fig, use_container_width=True)
897    
898    # 공정별 개선 포인트 시각화
899    st.markdown("#### 공정별 개선 포인트")
900    
901    # 개선 포인트 데이터 준비
902    improvement_points = []
903    for _, row in process_info_df.iterrows():
904        points = row['개선 Point (문제점)'].split(',')
905        for point in points:
906            improvement_points.append({
907                '공정': row['표준단위공정'] + '-' + row['실공정(Step)'],
908                '개선 포인트': point.strip()
909            })
910    
911    improvement_df = pd.DataFrame(improvement_points)
912    
913    # 개선 포인트 워드 클라우드 (텍스트 기반 시각화)
914    st.markdown("##### 주요 개선 포인트 키워드")
915    
916    # 개선 포인트 텍스트 모음
917    all_points = ' '.join(improvement_df['개선 포인트'].tolist())
918    
919    # 불용어 정의 (의미 없는 조사, 접속사 등)
920    stopwords = ['인한', '로', '으로', '에', '에서', '의', '이', '가', '을', '를', '은', '는', 
921                '와', '과', '이나', '또는', '및', '등', '에서의', '로의', '로써', '로서', '하여', 
922                '하는', '하게', '하다', '한', '된', '되는', '되어', '때문에', '때문', '통한', 
923                '통해', '의한', '의해', '에의한', '에의해', '불균일로', '편차로', '마모로', '저하로', '발생']
924    
925    # 유효 키워드 최소 길이
926    min_keyword_length = 2
927    
928    # 주요 키워드 추출 (개선된 방식)
929    keywords = {}
930    
931    # 텍스트를 단어로 분리
932    words = all_points.replace(',', ' ').replace('.', ' ').replace('(', ' ').replace(')', ' ').split()
933    
934    for word in words:
935        word = word.strip()
936        
937        # 불용어 제거 및 길이 체크
938        if word not in stopwords and len(word) >= min_keyword_length:
939            # 단어 끝에 붙은 '로', '에', '의' 등 제거
940            for suffix in ['로', '에', '의', '을', '를', '은', '는']:
941                if word.endswith(suffix) and len(word) > len(suffix):
942                    word = word[:-len(suffix)]
943            
944            # 단어가 여전히 최소 길이 이상인지 확인
945            if len(word) >= min_keyword_length:
946                if word in keywords:
947                    keywords[word] += 1
948                else:
949                    keywords[word] = 1
950    
951    # 상위 키워드 추출
952    top_keywords = sorted(keywords.items(), key=lambda x: x[1], reverse=True)[:20]
953    top_keywords_df = pd.DataFrame(top_keywords, columns=['키워드', '빈도'])
954    
955    # 키워드 빈도 차트
956    fig = px.bar(
957        top_keywords_df,
958        x='키워드',
959        y='빈도',
960        title='주요 개선 포인트 키워드 TOP 20',
961        color='빈도',
962        color_continuous_scale='Reds'
963    )
964    
965    fig.update_layout(
966        xaxis_title='키워드',
967        yaxis_title='빈도',
968        height=400
969    )
970    
971    st.plotly_chart(fig, use_container_width=True)
972
973
974
975def display_quality_analysis(data):
976    """품질 분석 표시 함수"""
977    st.subheader("품질 분석")
978    
979    # 공정능력 정보 확인
980    process_capability = data.get('process_capability', pd.DataFrame())
981    
982    if process_capability.empty:
983        st.warning("공정능력 정보를 불러올 수 없습니다.")
984        return
985    
986    # 제품 선택
987    if '제품' in process_capability.columns:
988        products = process_capability['제품'].unique()
989        if len(products) > 0:
990            selected_product = st.selectbox("제품 선택", products)
991            
992            # 선택된 제품의 공정능력 데이터
993            product_capability = process_capability[process_capability['제품'] == selected_product]
994            
995            # 검사항목 선택
996            if '검사항목' in product_capability.columns and not product_capability.empty:
997                inspection_items = product_capability['검사항목'].unique()
998                if len(inspection_items) > 0:
999                    selected_item = st.selectbox("검사항목 선택", inspection_items)
1000                    
1001                    # 선택된 검사항목의 데이터
1002                    item_data = product_capability[product_capability['검사항목'] == selected_item]
1003                    
1004                    if not item_data.empty:
1005                        # 월별 공정능력 차트
1006                        st.markdown("### 월별 공정능력 추이")
1007                        
1008                        # 필요한 컬럼이 있는지 확인
1009                        required_columns = ['월', 'Cpk']
1010                        if all(col in item_data.columns for col in required_columns):
1011                            fig = go.Figure()
1012                            
1013                            # Cpk 추이 그래프
1014                            fig.add_trace(go.Scatter(
1015                                x=item_data['월'],
1016                                y=item_data['Cpk'],
1017                                mode='lines+markers',
1018                                name='Cpk',
1019                                line=dict(color='blue', width=2),
1020                                marker=dict(size=8)
1021                            ))
1022                            
1023                            # 기준선 (Cpk=1.33)
1024                            fig.add_trace(go.Scatter(
1025                                x=[item_data['월'].min(), item_data['월'].max()],
1026                                y=[1.33, 1.33],
1027                                mode='lines',
1028                                name='기준 (Cpk=1.33)',
1029                                line=dict(color='red', dash='dash')
1030                            ))
1031                            
1032                            # 그래프 레이아웃 설정
1033                            fig.update_layout(
1034                                title=f"{selected_product} - {selected_item} 공정능력 추이",
1035                                xaxis_title="월",
1036                                yaxis_title="공정능력지수",
1037                                legend=dict(
1038                                    orientation="h",
1039                                    yanchor="bottom",
1040                                    y=1.02,
1041                                    xanchor="right",
1042                                    x=1
1043                                ),
1044                                height=400
1045                            )
1046                            
1047                            st.plotly_chart(fig, use_container_width=True)
1048                        else:
1049                            st.warning("공정능력 추이를 표시하는데 필요한 데이터가 없습니다.")
1050                        
1051                        # 검사값 분포 차트
1052                        st.markdown("### 검사값 분포")
1053                        
1054                        # 검사값, 규격 상한/하한 데이터
1055                        if all(col in item_data.columns for col in ['검사값', '상한', '하한']):
1056                            values = item_data['검사값']
1057                            if not values.empty:
1058                                mean = values.mean()
1059                                std = values.std()
1060                                usl = item_data['상한'].mean()
1061                                lsl = item_data['하한'].mean()
1062                                
1063                                # 히스토그램 생성
1064                                hist_fig = go.Figure()
1065                                
1066                                # 히스토그램 추가
1067                                hist_fig.add_trace(go.Histogram(
1068                                    x=values,
1069                                    name='검사값 분포',
1070                                    opacity=0.7,
1071                                    marker=dict(color='royalblue'),
1072                                    histnorm='probability density'
1073                                ))
1074                                
1075                                # 정규분포 곡선 추가
1076                                x_range = np.linspace(values.min() - 0.5, values.max() + 0.5, 100)
1077                                y_range = stats.norm.pdf(x_range, mean, std)
1078                                
1079                                hist_fig.add_trace(go.Scatter(
1080                                    x=x_range,
1081                                    y=y_range,
1082                                    mode='lines',
1083                                    name='정규분포',
1084                                    line=dict(color='red', width=2)
1085                                ))
1086                                
1087                                # 규격 상한/하한 추가
1088                                hist_fig.add_trace(go.Scatter(
1089                                    x=[usl, usl],
1090                                    y=[0, max(y_range) * 1.2],
1091                                    mode='lines',
1092                                    name='상한 규격',
1093                                    line=dict(color='green', width=2, dash='dash')
1094                                ))
1095                                
1096                                hist_fig.add_trace(go.Scatter(
1097                                    x=[lsl, lsl],
1098                                    y=[0, max(y_range) * 1.2],
1099                                    mode='lines',
1100                                    name='하한 규격',
1101                                    line=dict(color='green', width=2, dash='dash')
1102                                ))
1103                                
1104                                # 평균선 추가
1105                                hist_fig.add_trace(go.Scatter(
1106                                    x=[mean, mean],
1107                                    y=[0, max(y_range) * 1.2],
1108                                    mode='lines',
1109                                    name='평균',
1110                                    line=dict(color='black', width=2)
1111                                ))
1112                                
1113                                # 그래프 레이아웃 설정
1114                                hist_fig.update_layout(
1115                                    title=f"{selected_product} - {selected_item} 검사값 분포",
1116                                    xaxis_title="검사값",
1117                                    yaxis_title="확률 밀도",
1118                                    legend=dict(
1119                                        orientation="h",
1120                                        yanchor="bottom",
1121                                        y=1.02,
1122                                        xanchor="right",
1123                                        x=1
1124                                    ),
1125                                    height=400
1126                                )
1127                                
1128                                st.plotly_chart(hist_fig, use_container_width=True)
1129                                
1130                                # 공정능력 분석 결과
1131                                st.markdown("### 공정능력 분석 결과")
1132                                
1133                                # 공정능력지수 계산
1134                                capability = calculate_process_capability(values, usl, lsl)
1135                                
1136                                col1, col2, col3, col4 = st.columns(4)
1137                                
1138                                with col1:
1139                                    st.metric(label="Cp", value=f"{capability['Cp']:.3f}")
1140                                with col2:
1141                                    st.metric(label="Cpk", value=f"{capability['Cpk']:.3f}")
1142                                with col3:
1143                                    st.metric(label="Cpu", value=f"{capability['Cpu']:.3f}")
1144                                with col4:
1145                                    st.metric(label="Cpl", value=f"{capability['Cpl']:.3f}")
1146                                
1147                                # 예상 불량률
1148                                st.metric(label="예상 불량률 (PPM)", value=f"{capability['PPM']:.2f}")
1149                                
1150                                # 공정능력 평가
1151                                if capability['Cpk'] >= 1.33:
1152                                    st.success("공정능력 평가: 우수 (Cpk ≥ 1.33)")
1153                                elif capability['Cpk'] >= 1.00:
1154                                    st.warning("공정능력 평가: 보통 (1.00 ≤ Cpk < 1.33)")
1155                                else:
1156                                    st.error("공정능력 평가: 미흡 (Cpk < 1.00)")
1157                            else:
1158                                st.warning("검사값 데이터가 없습니다.")
1159                        else:
1160                            st.warning("검사값 분포를 표시하는데 필요한 데이터가 없습니다.")
1161                    else:
1162                        st.warning(f"{selected_item} 검사항목에 대한 데이터가 없습니다.")
1163                else:
1164                    st.warning("검사항목이 없습니다.")
1165            else:
1166                st.warning("공정능력 데이터에 '검사항목' 컬럼이 없거나 데이터가 비어 있습니다.")
1167        else:
1168            st.warning("제품 정보가 없습니다.")
1169    else:
1170        st.warning("공정능력 데이터에 '제품' 컬럼이 없습니다.")
1171
1172def display_defect_analysis(data):
1173    """불량 분석 표시 함수"""
1174    st.subheader("불량 분석")
1175    
1176    defect_rates = data.get('defect_rates', pd.DataFrame())
1177    
1178    if defect_rates.empty:
1179        st.warning("불량률 정보를 불러올 수 없습니다.")
1180        return
1181    
1182    # 유형 선택 (공정Loss, 공정불량 등)
1183    if '불량구분' in defect_rates.columns:
1184        defect_types = defect_rates['불량구분'].unique()
1185        if len(defect_types) > 0:
1186            selected_defect_type = st.selectbox("불량구분 선택", defect_types)
1187            
1188            # 선택된 불량구분의 데이터
1189            type_defects = defect_rates[defect_rates['불량구분'] == selected_defect_type]
1190            
1191            if not type_defects.empty:
1192                # 세부불량 항목별 분석
1193                st.markdown("### 세부불량 항목별 분석")
1194                
1195                # 연도 선택
1196                years = [col for col in type_defects.columns if col.endswith('년') and not col.startswith('5개년')]
1197                if years:
1198                    selected_year = st.selectbox("연도 선택", years, index=len(years)-1)  # 기본값은 가장 최근 연도
1199                    
1200                    # 선택된 연도의 세부불량 항목별 데이터 준비

Showing the first 1,200 of 1764 lines. Download the file for the rest.