CoolFace
Apppublic

lscsc/Process_dash_pub

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

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