CoolFace
Apppublic

lscsc/Process_dash_pub

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
quality_dashboard2-2.py1885 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    process_specs_df = data.get('process_specs', pd.DataFrame())
580    
581    # 공정 정보가 비어 있는 경우 처리
582    if process_info_df.empty:
583        st.warning("공정 정보를 불러올 수 없습니다.")
584        return
585    
586    # 공정 흐름도 시각화 (개선된 버전)
587    st.markdown("#### 공정 흐름도")
588    
589    # 공정 단계 추출
590    process_stages = process_info_df['표준단위공정'].unique()
591    
592    # 각 공정 단계별 아이콘 매핑
593    process_icons = {
594        "Batch": "flask",
595        "Forming": "industry",
596        "Sizing": "tint",
597        "Winding": "sync",
598        "Drying": "fire",
599        "Packing": "box"
600    }
601    
602    # 공정 단계별 색상 매핑
603    process_colors = {
604        "Batch": "#4285F4",      # 파란색
605        "Forming": "#EA4335",    # 빨간색
606        "Sizing": "#FBBC05",     # 노란색
607        "Winding": "#34A853",    # 녹색
608        "Drying": "#FF6D01",     # 주황색
609        "Packing": "#46BDC6"     # 청록색
610    }
611    
612    # 기본 색상 및 아이콘 설정
613    default_color = "#6C757D"
614    default_icon = "cog"
615    
616    # HTML로 공정 흐름도 생성
617    flow_html = """
618    <style>
619        .flow-container {
620            display: flex;
621            justify-content: space-between;
622            align-items: center;
623            margin: 30px 0;
624            position: relative;
625            overflow-x: auto;
626            padding: 20px 10px;
627        }
628        
629        .flow-container::before {
630            content: "";
631            position: absolute;
632            top: 50%;
633            left: 70px;
634            right: 70px;
635            height: 3px;
636            background: linear-gradient(to right, #e0e0e0, #2563EB, #e0e0e0);
637            z-index: 1;
638        }
639        
640        .flow-step {
641            display: flex;
642            flex-direction: column;
643            align-items: center;
644            position: relative;
645            z-index: 2;
646            min-width: 120px;
647        }
648        
649        .flow-icon {
650            width: 80px;
651            height: 80px;
652            border-radius: 50%;
653            display: flex;
654            align-items: center;
655            justify-content: center;
656            margin-bottom: 12px;
657            color: white;
658            font-size: 30px;
659            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
660            position: relative;
661            background: white;
662            border: 3px solid;
663            transition: transform 0.3s ease, box-shadow 0.3s ease;
664        }
665        
666        .flow-icon:hover {
667            transform: translateY(-5px);
668            box-shadow: 0 6px 12px rgba(0,0,0,0.15);
669        }
670        
671        .flow-step-title {
672            font-size: 16px;
673            font-weight: 600;
674            text-align: center;
675            color: #333;
676        }
677        
678        .flow-step-desc {
679            font-size: 13px;
680            color: #666;
681            text-align: center;
682            margin-top: 5px;
683            max-width: 120px;
684        }
685        
686        .arrow {
687            position: absolute;
688            top: 50%;
689            transform: translateY(-50%);
690            right: -20px;
691            color: #2563EB;
692            font-size: 24px;
693            z-index: 3;
694        }
695        
696        .last-step .arrow {
697            display: none;
698        }
699        
700        @media (max-width: 768px) {
701            .flow-container {
702                flex-direction: column;
703                align-items: flex-start;
704                overflow-x: visible;
705            }
706            
707            .flow-container::before {
708                top: 70px;
709                bottom: 70px;
710                left: 40px;
711                width: 3px;
712                height: auto;
713                background: linear-gradient(to bottom, #e0e0e0, #2563EB, #e0e0e0);
714            }
715            
716            .flow-step {
717                flex-direction: row;
718                margin-bottom: 30px;
719                width: 100%;
720            }
721            
722            .flow-icon {
723                margin-right: 20px;
724                margin-bottom: 0;
725            }
726            
727            .flow-step-content {
728                text-align: left;
729            }
730            
731            .flow-step-title {
732                text-align: left;
733            }
734            
735            .flow-step-desc {
736                text-align: left;
737                max-width: none;
738            }
739            
740            .arrow {
741                transform: rotate(90deg);
742                right: auto;
743                top: auto;
744                bottom: -25px;
745                left: 40px;
746            }
747        }
748    </style>
749    
750    <div class="flow-container">
751    """
752    
753    # 각 공정 단계별 HTML 생성
754    for i, stage in enumerate(process_stages):
755        # 해당 공정 단계의 세부 공정들 추출
756        sub_processes = process_info_df[process_info_df['표준단위공정'] == stage]['실공정(Step)'].tolist()
757        sub_process_text = ", ".join(sub_processes)
758        
759        # 아이콘 및 색상 결정
760        icon = process_icons.get(stage, default_icon)
761        color = process_colors.get(stage, default_color)
762        
763        # 마지막 단계 여부 확인
764        is_last = (i == len(process_stages) - 1)
765        last_class = " last-step" if is_last else ""
766        
767        # 단계별 HTML 추가
768        flow_html += f"""
769        <div class="flow-step{last_class}">
770            <div class="flow-icon" style="border-color: {color};">
771                <i class="fas fa-{icon}" style="color: {color};"></i>
772            </div>
773            <div class="flow-step-content">
774                <div class="flow-step-title">{stage}</div>
775                <div class="flow-step-desc">{sub_process_text[:30]}{'...' if len(sub_process_text) > 30 else ''}</div>
776            </div>
777            {'' if is_last else '<div class="arrow"><i class="fas fa-chevron-right"></i></div>'}
778        </div>
779        """
780    
781    flow_html += """
782    </div>
783    """
784    
785    # HTML 흐름도 표시
786    st.components.v1.html(flow_html, height=200)
787    
788    # 공정 단계 선택 및 세부 정보 표시 (기존 코드)
789    st.markdown("#### 공정 단계별 상세 정보")
790    
791    # 공정 단계 선택
792    selected_stage = st.selectbox("공정 단계 선택", process_stages)
793    
794    # 선택된 공정 단계의 세부 공정 표시
795    sub_processes = process_info_df[process_info_df['표준단위공정'] == selected_stage]
796    
797    if not sub_processes.empty:
798        # 세부 공정 선택
799        selected_sub = st.selectbox("세부 공정 선택", sub_processes['실공정(Step)'].tolist())
800        
801        # 선택된 세부 공정 정보 표시
802        process_detail = sub_processes[sub_processes['실공정(Step)'] == selected_sub].iloc[0]
803        
804        # 공정 정보 카드 표시
805        col1, col2 = st.columns(2)
806        
807        with col1:
808            st.markdown(f"""
809            <div class="process-box">
810                <h4 style="margin-top: 0;">공정 개요</h4>
811                <p><strong>공정명:</strong> {selected_sub}</p>
812                <p><strong>공정내용:</strong> {process_detail['공정내용']}</p>
813            </div>
814            """, unsafe_allow_html=True)
815        
816        with col2:
817            st.markdown(f"""
818            <div class="process-box">
819                <h4 style="margin-top: 0;">품질 관리 포인트</h4>
820                <p><strong>품질 인자:</strong> {process_detail['품질 인자']}</p>
821            </div>
822            """, unsafe_allow_html=True)
823        
824        # 공정 조건 인자와 개선 포인트 표시
825        col1, col2 = st.columns(2)
826        
827        with col1:
828            st.markdown(f"""
829            <div class="process-box">
830                <h4 style="margin-top: 0;">공정 조건 인자</h4>
831                <p>{process_detail['공정 조건 인자']}</p>
832            </div>
833            """, unsafe_allow_html=True)
834        
835        with col2:
836            st.markdown(f"""
837            <div class="issue-box">
838                <h4 style="margin-top: 0;">개선 Point (문제점)</h4>
839                <p>{process_detail['개선 Point (문제점)']}</p>
840            </div>
841            """, unsafe_allow_html=True)
842        
843        # 제품 및 공정규격 정보 표시 (새로 추가된 부분)
844        if not process_specs_df.empty:
845            st.markdown("#### 공정 규격 정보")
846            
847            # 공정명으로 규격 정보 필터링
848            # 공정명을 기준으로 매핑 (표준단위공정과 실공정(Step)을 공정명과 매핑)
849            process_name_mapping = {
850                "Batch": "Batch(배합)",
851                "Forming": "섬유화",
852                "Sizing": "SIZE",
853                "Winding": "Winding",
854                "Drying": "Drying",
855                "Packing": "포장",
856                "인수검사": "인수검사",
857                "용융": "용융",
858                "Chopping": "Chopping",
859                "Pelletizer": "Pelletizer",
860                "Water Spray": "Water Spray"
861            }
862            
863            # 매핑된 공정명으로 필터링
864            mapped_process_name = process_name_mapping.get(selected_stage, selected_sub)
865            filtered_specs = process_specs_df[process_specs_df['공정명'].str.contains(mapped_process_name, na=False)]
866            
867            # 세부 공정명으로 추가 필터링 시도
868            if filtered_specs.empty:
869                filtered_specs = process_specs_df[process_specs_df['공정명'].str.contains(selected_sub, na=False)]
870            
871            if not filtered_specs.empty:
872                # 탭 생성: 규격 정보와 차트
873                spec_tabs = st.tabs(["규격 정보", "규격 시각화"])
874                
875                with spec_tabs[0]:
876                    # 규격 정보 표시
877                    st.dataframe(
878                        filtered_specs,
879                        column_config={
880                            "공정명": st.column_config.TextColumn("공정명", width="medium"),
881                            "작업설명": st.column_config.TextColumn("작업설명", width="large"),
882                            "설비명": st.column_config.TextColumn("설비명", width="medium"),
883                            "구분": st.column_config.TextColumn("구분", width="small"),
884                            "항목": st.column_config.TextColumn("항목", width="medium"),
885                            "CTP/CTQ": st.column_config.TextColumn("CTP/CTQ", width="small"),
886                            "규격": st.column_config.TextColumn("규격", width="large"),
887                            "평가(측정) 방법": st.column_config.TextColumn("평가(측정) 방법", width="medium"),
888                            "주기": st.column_config.TextColumn("주기", width="small"),
889                            "관리방법": st.column_config.TextColumn("관리방법", width="medium"),
890                            "대응계획": st.column_config.TextColumn("대응계획", width="large")
891                        },
892                        use_container_width=True,
893                        hide_index=True
894                    )
895                
896                with spec_tabs[1]:
897                    # CTP/CTQ 항목 강조 표시
898                    ctp_items = filtered_specs[filtered_specs['CTP/CTQ'].notna()]
899                    
900                    if not ctp_items.empty:
901                        st.markdown("##### 주요 CTP/CTQ 항목")
902                        
903                        # 주요 항목 카드 형태로 표시
904                        cols = st.columns(min(3, len(ctp_items)))
905                        
906                        for i, (_, item) in enumerate(ctp_items.iterrows()):
907                            col_idx = i % 3
908                            with cols[col_idx]:
909                                st.markdown(f"""
910                                <div style="background-color: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid #4285F4; margin-bottom: 10px;">
911                                    <h5 style="margin-top: 0; color: #2C3E50;">{item['항목']}</h5>
912                                    <p><strong>규격:</strong> {item['규격']}</p>
913                                    <p><strong>측정 방법:</strong> {item['평가(측정) 방법']}</p>
914                                    <p><strong>주기:</strong> {item['주기']}</p>
915                                </div>
916                                """, unsafe_allow_html=True)
917                    
918                    # 규격 항목 시각화
919                    if '항목' in filtered_specs.columns:
920                        # 항목별 개수 계산
921                        item_counts = filtered_specs['항목'].value_counts().reset_index()
922                        item_counts.columns = ['항목', '개수']
923                        
924                        # 항목별 차트
925                        fig = px.bar(
926                            item_counts.head(10),
927                            x='항목',
928                            y='개수',
929                            title=f"{mapped_process_name} 주요 관리 항목 TOP 10",
930                            color='개수',
931                            color_continuous_scale='Blues'
932                        )
933                        
934                        fig.update_layout(
935                            xaxis_title='관리 항목',
936                            yaxis_title='개수',
937                            height=400
938                        )
939                        
940                        st.plotly_chart(fig, use_container_width=True)
941                        
942                        # 측정 방법 분포
943                        if '평가(측정) 방법' in filtered_specs.columns:
944                            method_counts = filtered_specs['평가(측정) 방법'].value_counts().reset_index()
945                            method_counts.columns = ['측정 방법', '개수']
946                            
947                            fig_pie = px.pie(
948                                method_counts,
949                                values='개수',
950                                names='측정 방법',
951                                title=f"{mapped_process_name} 측정 방법 분포",
952                                hole=0.4
953                            )
954                            
955                            fig_pie.update_traces(textposition='inside', textinfo='percent+label')
956                            st.plotly_chart(fig_pie, use_container_width=True)
957            else:
958                st.info(f"{selected_stage} 공정에 대한 규격 정보를 찾을 수 없습니다.")
959        else:
960            st.warning("제품 및 공정규격 정보를 불러올 수 없습니다.")
961    else:
962        st.warning(f"{selected_stage} 공정에 대한 정보를 찾을 수 없습니다.")
963    
964    # 전체 공정 맵 표시
965    st.markdown("#### 전체 공정 맵")
966    
967    # 공정 데이터 테이블로 표시
968    st.dataframe(
969        process_info_df,
970        column_config={
971            "표준단위공정": st.column_config.TextColumn("표준단위공정", width="medium"),
972            "실공정(Step)": st.column_config.TextColumn("실공정(Step)", width="medium"),
973            "공정내용": st.column_config.TextColumn("공정내용", width="large"),
974            "공정 조건 인자": st.column_config.TextColumn("공정 조건 인자", width="large"),
975            "품질 인자": st.column_config.TextColumn("품질 인자", width="large"),
976            "개선 Point (문제점)": st.column_config.TextColumn("개선 Point (문제점)", width="large"),
977        },
978        use_container_width=True,
979        hide_index=True
980    )
981    
982    # 공정별 품질 인자 시각화
983    st.markdown("#### 공정별 품질 인자")
984    
985    # 품질 인자 데이터 준비
986    quality_factors = []
987    for _, row in process_info_df.iterrows():
988        factors = row['품질 인자'].split(',')
989        for factor in factors:
990            quality_factors.append({
991                '공정': row['표준단위공정'] + '-' + row['실공정(Step)'],
992                '품질 인자': factor.strip()
993            })
994    
995    quality_df = pd.DataFrame(quality_factors)
996    
997    # 품질 인자 빈도 계산
998    factor_counts = quality_df['품질 인자'].value_counts().reset_index()
999    factor_counts.columns = ['품질 인자', '빈도']
1000    factor_counts = factor_counts.sort_values('빈도', ascending=False).head(10)
1001    
1002    # 품질 인자 빈도 차트
1003    fig = px.bar(
1004        factor_counts,
1005        x='품질 인자',
1006        y='빈도',
1007        title='주요 품질 인자 TOP 10',
1008        color='빈도',
1009        color_continuous_scale='Viridis'
1010    )
1011    
1012    fig.update_layout(
1013        xaxis_title='품질 인자',
1014        yaxis_title='언급 빈도',
1015        height=400
1016    )
1017    
1018    st.plotly_chart(fig, use_container_width=True)
1019    
1020    # 공정별 개선 포인트 시각화
1021    st.markdown("#### 공정별 개선 포인트")
1022    
1023    # 개선 포인트 데이터 준비
1024    improvement_points = []
1025    for _, row in process_info_df.iterrows():
1026        points = row['개선 Point (문제점)'].split(',')
1027        for point in points:
1028            improvement_points.append({
1029                '공정': row['표준단위공정'] + '-' + row['실공정(Step)'],
1030                '개선 포인트': point.strip()
1031            })
1032    
1033    improvement_df = pd.DataFrame(improvement_points)
1034    
1035    # 개선 포인트 워드 클라우드 (텍스트 기반 시각화)
1036    st.markdown("##### 주요 개선 포인트 키워드")
1037    
1038    # 개선 포인트 텍스트 모음
1039    all_points = ' '.join(improvement_df['개선 포인트'].tolist())
1040    
1041    # 불용어 정의 (의미 없는 조사, 접속사 등)
1042    stopwords = ['인한', '로', '으로', '에', '에서', '의', '이', '가', '을', '를', '은', '는', 
1043                '와', '과', '이나', '또는', '및', '등', '에서의', '로의', '로써', '로서', '하여', 
1044                '하는', '하게', '하다', '한', '된', '되는', '되어', '때문에', '때문', '통한', 
1045                '통해', '의한', '의해', '에의한', '에의해', '불균일로', '편차로', '마모로', '저하로', '발생']
1046    
1047    # 유효 키워드 최소 길이
1048    min_keyword_length = 2
1049    
1050    # 주요 키워드 추출 (개선된 방식)
1051    keywords = {}
1052    
1053    # 텍스트를 단어로 분리
1054    words = all_points.replace(',', ' ').replace('.', ' ').replace('(', ' ').replace(')', ' ').split()
1055    
1056    for word in words:
1057        word = word.strip()
1058        
1059        # 불용어 제거 및 길이 체크
1060        if word not in stopwords and len(word) >= min_keyword_length:
1061            # 단어 끝에 붙은 '로', '에', '의' 등 제거
1062            for suffix in ['로', '에', '의', '을', '를', '은', '는']:
1063                if word.endswith(suffix) and len(word) > len(suffix):
1064                    word = word[:-len(suffix)]
1065            
1066            # 단어가 여전히 최소 길이 이상인지 확인
1067            if len(word) >= min_keyword_length:
1068                if word in keywords:
1069                    keywords[word] += 1
1070                else:
1071                    keywords[word] = 1
1072    
1073    # 상위 키워드 추출
1074    top_keywords = sorted(keywords.items(), key=lambda x: x[1], reverse=True)[:20]
1075    top_keywords_df = pd.DataFrame(top_keywords, columns=['키워드', '빈도'])
1076    
1077    # 키워드 빈도 차트
1078    fig = px.bar(
1079        top_keywords_df,
1080        x='키워드',
1081        y='빈도',
1082        title='주요 개선 포인트 키워드 TOP 20',
1083        color='빈도',
1084        color_continuous_scale='Reds'
1085    )
1086    
1087    fig.update_layout(
1088        xaxis_title='키워드',
1089        yaxis_title='빈도',
1090        height=400
1091    )
1092    
1093    st.plotly_chart(fig, use_container_width=True)
1094
1095
1096def display_quality_analysis(data):
1097    """품질 분석 표시 함수"""
1098    st.subheader("품질 분석")
1099    
1100    # 공정능력 정보 확인
1101    process_capability = data.get('process_capability', pd.DataFrame())
1102    
1103    if process_capability.empty:
1104        st.warning("공정능력 정보를 불러올 수 없습니다.")
1105        return
1106    
1107    # 제품 선택
1108    if '제품' in process_capability.columns:
1109        products = process_capability['제품'].unique()
1110        if len(products) > 0:
1111            selected_product = st.selectbox("제품 선택", products)
1112            
1113            # 선택된 제품의 공정능력 데이터
1114            product_capability = process_capability[process_capability['제품'] == selected_product]
1115            
1116            # 검사항목 선택
1117            if '검사항목' in product_capability.columns and not product_capability.empty:
1118                inspection_items = product_capability['검사항목'].unique()
1119                if len(inspection_items) > 0:
1120                    selected_item = st.selectbox("검사항목 선택", inspection_items)
1121                    
1122                    # 선택된 검사항목의 데이터
1123                    item_data = product_capability[product_capability['검사항목'] == selected_item]
1124                    
1125                    if not item_data.empty:
1126                        # 월별 공정능력 차트
1127                        st.markdown("### 월별 공정능력 추이")
1128                        
1129                        # 필요한 컬럼이 있는지 확인
1130                        required_columns = ['월', 'Cpk']
1131                        if all(col in item_data.columns for col in required_columns):
1132                            fig = go.Figure()
1133                            
1134                            # Cpk 추이 그래프
1135                            fig.add_trace(go.Scatter(
1136                                x=item_data['월'],
1137                                y=item_data['Cpk'],
1138                                mode='lines+markers',
1139                                name='Cpk',
1140                                line=dict(color='blue', width=2),
1141                                marker=dict(size=8)
1142                            ))
1143                            
1144                            # 기준선 (Cpk=1.33)
1145                            fig.add_trace(go.Scatter(
1146                                x=[item_data['월'].min(), item_data['월'].max()],
1147                                y=[1.33, 1.33],
1148                                mode='lines',
1149                                name='기준 (Cpk=1.33)',
1150                                line=dict(color='red', dash='dash')
1151                            ))
1152                            
1153                            # 그래프 레이아웃 설정
1154                            fig.update_layout(
1155                                title=f"{selected_product} - {selected_item} 공정능력 추이",
1156                                xaxis_title="월",
1157                                yaxis_title="공정능력지수",
1158                                legend=dict(
1159                                    orientation="h",
1160                                    yanchor="bottom",
1161                                    y=1.02,
1162                                    xanchor="right",
1163                                    x=1
1164                                ),
1165                                height=400
1166                            )
1167                            
1168                            st.plotly_chart(fig, use_container_width=True)
1169                        else:
1170                            st.warning("공정능력 추이를 표시하는데 필요한 데이터가 없습니다.")
1171                        
1172                        # 검사값 분포 차트
1173                        st.markdown("### 검사값 분포")
1174                        
1175                        # 검사값, 규격 상한/하한 데이터
1176                        if all(col in item_data.columns for col in ['검사값', '상한', '하한']):
1177                            values = item_data['검사값']
1178                            if not values.empty:
1179                                mean = values.mean()
1180                                std = values.std()
1181                                usl = item_data['상한'].mean()
1182                                lsl = item_data['하한'].mean()
1183                                
1184                                # 히스토그램 생성
1185                                hist_fig = go.Figure()
1186                                
1187                                # 히스토그램 추가
1188                                hist_fig.add_trace(go.Histogram(
1189                                    x=values,
1190                                    name='검사값 분포',
1191                                    opacity=0.7,
1192                                    marker=dict(color='royalblue'),
1193                                    histnorm='probability density'
1194                                ))
1195                                
1196                                # 정규분포 곡선 추가
1197                                x_range = np.linspace(values.min() - 0.5, values.max() + 0.5, 100)
1198                                y_range = stats.norm.pdf(x_range, mean, std)
1199                                
1200                                hist_fig.add_trace(go.Scatter(

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