suwankim
Deploy n8n workflow evaluator
3609933
"""
n8n 워크플로우 자동 심사 시스템 - Streamlit 웹 앱
Hugging Face Spaces용
기능:
1. 기본 심사: 기존 심사 기준으로 평가
2. 커스텀 심사: 사용자 정의 심사 기준으로 평가
"""
import os
import streamlit as st
import tempfile
import shutil
import json
from pathlib import Path
from io import BytesIO
import pandas as pd
from main_evaluator import WorkflowEvaluator, CustomWorkflowEvaluator, MultiCriteriaWorkflowEvaluator
from custom_criteria_generator import (
CustomCriteriaGenerator,
DEFAULT_CRITERIA_TEMPLATES,
FIXED_CRITERIA_TEMPLATES,
get_default_template,
get_fixed_template_with_points,
list_default_templates,
list_fixed_templates
)
def validate_file_upload(csv_file, zip_file):
"""파일 업로드 검증"""
errors = []
if csv_file is None:
errors.append("❌ CSV 파일을 업로드해주세요.")
elif not csv_file.name.endswith('.csv'):
errors.append("❌ CSV 파일만 업로드 가능합니다.")
if zip_file is None:
errors.append("❌ ZIP 파일을 업로드해주세요.")
elif not zip_file.name.endswith('.zip'):
errors.append("❌ ZIP 파일만 업로드 가능합니다.")
return errors
def render_default_evaluation_page():
"""기본 심사 페이지 렌더링"""
st.header("📊 기본 심사")
st.markdown("기존 심사 기준(총 90점)으로 워크플로우를 평가합니다.")
# 설명
with st.expander("ℹ️ 사용 방법", expanded=False):
st.markdown("""
### 📝 평가 항목
1. **기술적 완성도** (15점): 워크플로우의 구조, 데이터 처리, 예외 처리
2. **업스테이지 제품 활용도** (15점): API 사용 적합성, 프롬프트 설계, 기능 조합
3. **실용성** (30점): 업무 적용 가능성, 재사용성·확장성, 사용자 편의성
4. **문제 해결 접근법** (30점): 문제 정의의 독창성, 솔루션 참신성
**총점: 90점**
### 📂 파일 요구사항
- **CSV 파일**: 제출된 프로젝트 정보가 포함된 CSV 파일 (.csv)
- **ZIP 파일**: 워크플로우 JSON 파일들이 포함된 압축 파일 (.zip)
### ⚠️ 주의사항
- 각 팀은 정확히 1개의 JSON 워크플로우 파일만 제출해야 합니다.
""")
st.markdown("")
# 파일 업로드
st.subheader("📁 파일 업로드")
col1, col2 = st.columns(2)
with col1:
csv_file = st.file_uploader(
"CSV 파일 업로드",
type=['csv'],
help="프로젝트 제출 정보가 담긴 CSV 파일",
key="default_csv"
)
with col2:
zip_file = st.file_uploader(
"ZIP 파일 업로드",
type=['zip'],
help="워크플로우 JSON 파일들이 포함된 ZIP 파일",
key="default_zip"
)
# 파일 검증
if csv_file or zip_file:
errors = validate_file_upload(csv_file, zip_file)
if errors:
for error in errors:
st.error(error)
return
# 평가 실행 버튼
if st.button("🚀 평가 시작", type="primary", disabled=(csv_file is None or zip_file is None), key="default_eval"):
run_default_evaluation(csv_file, zip_file)
def run_default_evaluation(csv_file, zip_file):
"""기본 심사 실행"""
with tempfile.TemporaryDirectory() as temp_dir:
try:
progress_bar = st.progress(0)
status_text = st.empty()
# 파일 저장
status_text.text("📥 파일 저장 중...")
progress_bar.progress(10)
csv_path = os.path.join(temp_dir, csv_file.name)
zip_path = os.path.join(temp_dir, zip_file.name)
output_dir = os.path.join(temp_dir, "results")
with open(csv_path, "wb") as f:
f.write(csv_file.getbuffer())
with open(zip_path, "wb") as f:
f.write(zip_file.getbuffer())
progress_bar.progress(20)
# 평가 시스템 초기화
status_text.text("🔧 평가 시스템 초기화 중...")
evaluator = WorkflowEvaluator(csv_path, zip_path, output_dir)
progress_bar.progress(30)
# 평가 실행
status_text.text("🔍 워크플로우 평가 진행 중... (시간이 걸릴 수 있습니다)")
with st.spinner("평가 진행 중..."):
df_result = evaluator.evaluate_all()
progress_bar.progress(80)
status_text.text("💾 결과 저장 중...")
output_path = evaluator.save_results()
progress_bar.progress(90)
progress_bar.progress(100)
status_text.text("✅ 평가 완료!")
st.success("🎉 평가가 완료되었습니다!")
# 결과 다운로드
display_results(output_path)
except Exception as e:
st.error(f"❌ 오류 발생: {str(e)}")
import traceback
with st.expander("🔍 상세 오류 정보"):
st.code(traceback.format_exc())
def render_custom_criteria_page():
"""커스텀 심사 기준 설정 페이지"""
st.header("⚙️ 커스텀 심사 기준 설정")
# 안내 문구
st.info("""
ℹ️ **커스텀 심사 구조**
모든 심사 기준의 배점을 자유롭게 설정할 수 있습니다!
1. **기술적 완성도** (템플릿 고정, 배점 자유)
2. **업스테이지 활용도** (템플릿 고정, 배점 자유)
3. **추가 심사 기준** (실용성/문제해결/직접입력, 여러 개 추가 가능)
**총점 제한 없음 - 자유롭게 설정하세요!**
""")
# 탭 구성
tab1, tab2, tab3 = st.tabs(["📝 심사 기준 & 배점 설정", "🤖 프롬프트 생성", "🚀 커스텀 심사 실행"])
with tab1:
render_multi_criteria_editor()
with tab2:
render_multi_prompt_generator()
with tab3:
render_multi_custom_evaluation()
def render_multi_criteria_editor():
"""여러 심사 기준 설정"""
# 세션 상태 초기화
if 'custom_criteria_list' not in st.session_state:
st.session_state['custom_criteria_list'] = []
if 'technical_points' not in st.session_state:
st.session_state['technical_points'] = 15
if 'upstage_points' not in st.session_state:
st.session_state['upstage_points'] = 15
# 1. 고정 템플릿 배점 설정
st.subheader("📌 고정 템플릿 배점 설정")
st.markdown("**템플릿 내용은 고정되어 있으며, 총 배점만 조정할 수 있습니다.**")
col1, col2 = st.columns(2)
with col1:
st.session_state['technical_points'] = st.number_input(
"🔧 기술적 완성도 배점",
min_value=1,
max_value=30,
value=min(st.session_state['technical_points'], 30),
help="워크플로우의 구조, 데이터 처리, 예외 처리 평가"
)
with st.expander("기술적 완성도 템플릿 미리보기"):
st.markdown("""
- 구조 완결성 & 모듈화 (40%)
- 데이터·스키마 일관성 (40%)
- 분기/예외 처리 (20%)
""")
with col2:
st.session_state['upstage_points'] = st.number_input(
"⭐ 업스테이지 활용도 배점",
min_value=1,
max_value=30,
value=min(st.session_state['upstage_points'], 30),
help="업스테이지 API 사용, 프롬프트 설계, 기능 조합 평가"
)
with st.expander("업스테이지 활용도 템플릿 미리보기"):
st.markdown("""
- API 사용 적합성 (33%)
- Prompt/System 설계 (33%)
- 기능 조합·오케스트레이션 (34%)
""")
fixed_total = st.session_state['technical_points'] + st.session_state['upstage_points']
st.markdown("---")
# 2. 추가 심사 기준 설정
st.subheader("➕ 추가 심사 기준 설정")
st.markdown("실용성, 문제해결 등 추가 평가 기준을 설정할 수 있습니다.")
# 추가 기준 추가 버튼
col1, col2 = st.columns([3, 1])
with col1:
template_to_add = st.selectbox(
"템플릿 선택",
["직접 입력"] + list_default_templates()
)
with col2:
if st.button("➕ 기준 추가"):
if template_to_add == "직접 입력":
new_criteria = {
"name": f"커스텀 기준 {len(st.session_state['custom_criteria_list']) + 1}",
"description": "",
"total_points": 30,
"evaluation_target": "project_description",
"sub_criteria": []
}
else:
new_criteria = get_default_template(template_to_add).copy()
st.session_state['custom_criteria_list'].append(new_criteria)
st.rerun()
# 추가된 기준 표시 및 편집
custom_criteria_list = st.session_state['custom_criteria_list']
if not custom_criteria_list:
st.info("➕ 위의 버튼을 눌러 추가 심사 기준을 추가하세요.")
else:
for idx, criteria in enumerate(custom_criteria_list):
with st.expander(f"📋 {criteria.get('name', f'기준 {idx+1}')} ({criteria.get('total_points', 0)}점)", expanded=False):
render_single_criteria_editor(idx, criteria)
# 총점 계산
st.markdown("---")
additional_total = sum(c.get('total_points', 0) for c in custom_criteria_list)
total_points = fixed_total + additional_total
col1, col2, col3 = st.columns(3)
with col1:
st.metric("고정 템플릿", f"{fixed_total}점")
with col2:
st.metric("추가 기준", f"{additional_total}점")
with col3:
st.metric("총점", f"{total_points}점")
def render_single_criteria_editor(idx: int, criteria: dict):
"""단일 심사 기준 편집기 (최적화 버전)"""
col1, col2, col3 = st.columns([2, 1, 1])
with col1:
new_name = st.text_input(
"기준 이름",
value=criteria.get('name', ''),
key=f"crit_name_{idx}",
placeholder="예: 실용성"
)
if new_name != criteria.get('name', ''):
criteria['name'] = new_name
with col2:
points_value = criteria.get('total_points', 30)
if points_value < 1:
points_value = 30
new_points = st.number_input(
"총점",
min_value=1,
max_value=100,
value=min(points_value, 100),
key=f"crit_points_{idx}"
)
if new_points != criteria.get('total_points', 30):
criteria['total_points'] = new_points
with col3:
if st.button("🗑️ 삭제", key=f"del_crit_{idx}"):
st.session_state['custom_criteria_list'].pop(idx)
st.rerun()
new_desc = st.text_area(
"설명",
value=criteria.get('description', ''),
key=f"crit_desc_{idx}",
height=60
)
if new_desc != criteria.get('description', ''):
criteria['description'] = new_desc
new_target = st.radio(
"평가 대상",
["project_description", "workflow"],
format_func=lambda x: "프로젝트 설명서" if x == "project_description" else "워크플로우 JSON",
horizontal=True,
index=0 if criteria.get('evaluation_target', 'project_description') == 'project_description' else 1,
key=f"crit_target_{idx}"
)
if new_target != criteria.get('evaluation_target', 'project_description'):
criteria['evaluation_target'] = new_target
# 세부 기준 - 간소화된 버전
sub_criteria = criteria.get('sub_criteria', [])
col_a, col_b = st.columns([4, 1])
with col_a:
st.markdown(f"**세부 기준:** ({len(sub_criteria)}개)")
with col_b:
if st.button(f"➕ 추가", key=f"add_sub_{idx}"):
sub_criteria.append({
"name": f"세부 기준 {len(sub_criteria) + 1}",
"description": "",
"points": 10,
"details": []
})
criteria['sub_criteria'] = sub_criteria
st.rerun()
# 세부 기준은 간단히 표시
if sub_criteria:
for sub_idx, sub in enumerate(sub_criteria):
col1, col2, col3 = st.columns([3, 1, 1])
with col1:
new_sub_name = st.text_input(
"이름",
value=sub.get('name', ''),
key=f"sub_name_{idx}_{sub_idx}",
label_visibility="collapsed"
)
if new_sub_name != sub.get('name', ''):
sub['name'] = new_sub_name
with col2:
sub_points_value = max(sub.get('points', 10), 1)
new_sub_points = st.number_input(
"점수",
min_value=1,
max_value=100,
value=sub_points_value,
key=f"sub_points_{idx}_{sub_idx}",
label_visibility="collapsed"
)
if new_sub_points != sub.get('points', 10):
sub['points'] = new_sub_points
with col3:
if st.button("❌", key=f"del_sub_{idx}_{sub_idx}"):
sub_criteria.pop(sub_idx)
criteria['sub_criteria'] = sub_criteria
st.rerun()
def render_criteria_editor():
"""심사 기준 편집기 (기존 - 사용 안 함)"""
st.subheader("심사 기준 편집")
# 템플릿 선택
col1, col2 = st.columns([2, 1])
with col1:
template_option = st.selectbox(
"템플릿 선택",
["직접 입력"] + list_default_templates(),
help="기본 템플릿을 선택하거나 직접 입력할 수 있습니다."
)
with col2:
if template_option != "직접 입력":
if st.button("템플릿 불러오기"):
template = get_default_template(template_option)
if template:
st.session_state['custom_criteria'] = template
st.success(f"'{template_option}' 템플릿을 불러왔습니다!")
st.rerun()
st.markdown("---")
# 기존 기준이 있으면 불러오기
if 'custom_criteria' not in st.session_state:
st.session_state['custom_criteria'] = {
"name": "",
"description": "",
"total_points": 30,
"evaluation_target": "project_description",
"sub_criteria": []
}
criteria = st.session_state['custom_criteria']
# total_points가 0 이하이면 기본값으로 설정
if criteria.get('total_points', 0) < 1:
criteria['total_points'] = 30
# total_points가 60 초과이면 60으로 제한
if criteria.get('total_points', 0) > 60:
criteria['total_points'] = 60
# 기본 정보 입력
col1, col2 = st.columns(2)
with col1:
criteria['name'] = st.text_input(
"심사 기준 이름",
value=criteria.get('name', ''),
placeholder="예: 기술적 완성도"
)
with col2:
criteria['total_points'] = st.number_input(
"총점 (커스텀 평가 배점, 고정 30점 제외)",
min_value=1,
max_value=60,
value=min(max(criteria.get('total_points', 30), 1), 60),
help="기술적 완성도(15점) + 업스테이지 활용도(15점) = 30점은 자동 평가됩니다."
)
criteria['description'] = st.text_area(
"심사 기준 설명",
value=criteria.get('description', ''),
placeholder="이 심사 기준이 무엇을 평가하는지 설명해주세요...",
height=100
)
criteria['evaluation_target'] = st.radio(
"평가 대상",
["project_description", "workflow"],
format_func=lambda x: "프로젝트 설명서" if x == "project_description" else "워크플로우 JSON",
horizontal=True,
index=0 if criteria.get('evaluation_target', 'project_description') == 'project_description' else 1,
help="일반적으로 실용성/문제해결은 프로젝트 설명서, 기술적 평가는 워크플로우 JSON을 평가합니다."
)
st.markdown("---")
st.subheader("세부 기준 설정")
# 세부 기준 편집
sub_criteria = criteria.get('sub_criteria', [])
# 세부 기준 추가 버튼
if st.button("➕ 세부 기준 추가"):
sub_criteria.append({
"name": f"세부 기준 {len(sub_criteria) + 1}",
"description": "",
"points": 10,
"details": []
})
criteria['sub_criteria'] = sub_criteria
st.rerun()
# 각 세부 기준 편집
for idx, sub in enumerate(sub_criteria):
with st.expander(f"📌 {sub.get('name', f'세부 기준 {idx+1}')} ({sub.get('points', 0)}점)", expanded=True):
col1, col2, col3 = st.columns([3, 1, 1])
with col1:
sub['name'] = st.text_input(
"기준 이름",
value=sub.get('name', ''),
key=f"sub_name_{idx}"
)
with col2:
# points가 0 이하이면 기본값으로 설정
sub_points_value = sub.get('points', 10)
if sub_points_value < 1:
sub_points_value = 10
sub['points'] = 10
sub['points'] = st.number_input(
"배점",
min_value=1,
max_value=50,
value=sub_points_value,
key=f"sub_points_{idx}"
)
with col3:
if st.button("🗑️ 삭제", key=f"del_sub_{idx}"):
sub_criteria.pop(idx)
criteria['sub_criteria'] = sub_criteria
st.rerun()
sub['description'] = st.text_area(
"설명",
value=sub.get('description', ''),
key=f"sub_desc_{idx}",
height=80
)
# 세부 항목 (details)
st.markdown("**세부 평가 항목:**")
details = sub.get('details', [])
if st.button(f"➕ 세부 항목 추가", key=f"add_detail_{idx}"):
details.append({
"name": f"항목 {len(details) + 1}",
"points": 3,
"description": ""
})
sub['details'] = details
st.rerun()
for d_idx, detail in enumerate(details):
col1, col2, col3, col4 = st.columns([2, 1, 3, 1])
with col1:
detail['name'] = st.text_input(
"항목명",
value=detail.get('name', ''),
key=f"detail_name_{idx}_{d_idx}",
label_visibility="collapsed"
)
with col2:
# detail points가 0 이하이면 기본값으로 설정
detail_points_value = detail.get('points', 3)
if detail_points_value < 1:
detail_points_value = 3
detail['points'] = 3
detail['points'] = st.number_input(
"점수",
min_value=1,
max_value=20,
value=detail_points_value,
key=f"detail_points_{idx}_{d_idx}",
label_visibility="collapsed"
)
with col3:
detail['description'] = st.text_input(
"설명",
value=detail.get('description', ''),
key=f"detail_desc_{idx}_{d_idx}",
label_visibility="collapsed"
)
with col4:
if st.button("❌", key=f"del_detail_{idx}_{d_idx}"):
details.pop(d_idx)
sub['details'] = details
st.rerun()
# 저장
st.markdown("---")
col1, col2 = st.columns(2)
with col1:
if st.button("💾 기준 저장", type="primary"):
st.session_state['custom_criteria'] = criteria
st.success("✅ 심사 기준이 저장되었습니다!")
with col2:
# JSON으로 내보내기
if criteria.get('name'):
criteria_json = json.dumps(criteria, ensure_ascii=False, indent=2)
st.download_button(
label="📥 JSON으로 내보내기",
data=criteria_json,
file_name=f"criteria_{criteria['name']}.json",
mime="application/json"
)
# 점수 합계 검증
total_from_sub = sum(s.get('points', 0) for s in sub_criteria)
if total_from_sub != criteria.get('total_points', 0):
st.warning(f"⚠️ 세부 기준 점수 합계({total_from_sub})가 총점({criteria.get('total_points', 0)})과 일치하지 않습니다.")
def render_multi_prompt_generator():
"""여러 심사 기준에 대한 프롬프트 생성"""
st.subheader("🤖 LLM 평가 프롬프트 생성")
# 필수 데이터 확인
if 'technical_points' not in st.session_state:
st.warning("⚠️ 먼저 '심사 기준 & 배점 설정' 탭에서 기준을 설정해주세요.")
return
# 생성할 프롬프트 준비
prompts_to_generate = []
# 1. 기술적 완성도 (고정 템플릿)
tech_template = get_fixed_template_with_points("기술적_완성도", st.session_state['technical_points'])
prompts_to_generate.append(("기술적 완성도", tech_template))
# 2. 업스테이지 활용도 (고정 템플릿)
upstage_template = get_fixed_template_with_points("업스테이지_활용도", st.session_state['upstage_points'])
prompts_to_generate.append(("업스테이지 활용도", upstage_template))
# 3. 추가 기준들
custom_criteria_list = st.session_state.get('custom_criteria_list', [])
for criteria in custom_criteria_list:
if criteria.get('name'):
prompts_to_generate.append((criteria['name'], criteria))
if not prompts_to_generate:
st.info("설정된 심사 기준이 없습니다.")
return
st.markdown(f"**총 {len(prompts_to_generate)}개의 심사 기준에 대한 프롬프트를 생성합니다.**")
# 프롬프트 생성 방식 선택
generation_method = st.radio(
"프롬프트 생성 방식",
["자동 생성 (규칙 기반)", "LLM 활용 생성 (더 정교함)"],
horizontal=True
)
if st.button("🔮 모든 프롬프트 생성", type="primary"):
with st.spinner("프롬프트 생성 중..."):
try:
generator = CustomCriteriaGenerator()
generated_prompts = {}
progress_bar = st.progress(0)
for idx, (name, template) in enumerate(prompts_to_generate):
st.text(f"생성 중: {name}")
if generation_method == "자동 생성 (규칙 기반)":
prompt = generator.generate_evaluation_prompt(
criteria_name=template['name'],
criteria_description=template['description'],
sub_criteria=template['sub_criteria'],
total_points=template['total_points'],
evaluation_target=template['evaluation_target']
)
else:
prompt = generator.generate_prompt_with_llm(
criteria_name=template['name'],
criteria_description=template['description'],
sub_criteria=template['sub_criteria'],
total_points=template['total_points'],
evaluation_target=template['evaluation_target']
)
generated_prompts[name] = {
"prompt": prompt,
"template": template
}
progress_bar.progress((idx + 1) / len(prompts_to_generate))
st.session_state['generated_prompts'] = generated_prompts
st.success("✅ 모든 프롬프트가 생성되었습니다!")
except Exception as e:
st.error(f"❌ 프롬프트 생성 실패: {str(e)}")
# 생성된 프롬프트 표시
if 'generated_prompts' in st.session_state:
st.markdown("---")
st.subheader("📄 생성된 프롬프트")
generated_prompts = st.session_state['generated_prompts']
for name, data in generated_prompts.items():
with st.expander(f"📋 {name} ({data['template']['total_points']}점)", expanded=False):
edited_prompt = st.text_area(
"프롬프트 (편집 가능)",
value=data['prompt'],
height=300,
key=f"prompt_{name}"
)
# 수정된 프롬프트 저장
data['prompt'] = edited_prompt
st.download_button(
label="📥 다운로드",
data=edited_prompt,
file_name=f"prompt_{name}.txt",
mime="text/plain",
key=f"download_{name}"
)
def render_prompt_generator():
"""프롬프트 생성 페이지 (기존 - 사용 안 함)"""
st.subheader("🤖 LLM 평가 프롬프트 생성")
if 'custom_criteria' not in st.session_state or not st.session_state['custom_criteria'].get('name'):
st.warning("⚠️ 먼저 '기준 설정' 탭에서 심사 기준을 설정해주세요.")
return
criteria = st.session_state['custom_criteria']
st.markdown(f"**현재 설정된 기준:** {criteria['name']} ({criteria['total_points']}점)")
# 프롬프트 생성 방식 선택
generation_method = st.radio(
"프롬프트 생성 방식",
["자동 생성 (규칙 기반)", "LLM 활용 생성 (더 정교함)"],
horizontal=True
)
additional_context = st.text_area(
"추가 컨텍스트 (선택)",
placeholder="평가 시 특별히 고려해야 할 사항이 있다면 입력하세요...",
height=100
)
if st.button("🔮 프롬프트 생성", type="primary"):
with st.spinner("프롬프트 생성 중..."):
try:
generator = CustomCriteriaGenerator()
if generation_method == "자동 생성 (규칙 기반)":
prompt = generator.generate_evaluation_prompt(
criteria_name=criteria['name'],
criteria_description=criteria['description'],
sub_criteria=criteria['sub_criteria'],
total_points=criteria['total_points'],
evaluation_target=criteria['evaluation_target']
)
else:
prompt = generator.generate_prompt_with_llm(
criteria_name=criteria['name'],
criteria_description=criteria['description'],
sub_criteria=criteria['sub_criteria'],
total_points=criteria['total_points'],
evaluation_target=criteria['evaluation_target'],
additional_context=additional_context
)
st.session_state['generated_prompt'] = prompt
st.success("✅ 프롬프트가 생성되었습니다!")
except Exception as e:
st.error(f"❌ 프롬프트 생성 실패: {str(e)}")
# 생성된 프롬프트 표시
if 'generated_prompt' in st.session_state:
st.markdown("---")
st.subheader("📄 생성된 프롬프트")
# 편집 가능한 텍스트 영역
edited_prompt = st.text_area(
"프롬프트 (편집 가능)",
value=st.session_state['generated_prompt'],
height=400
)
col1, col2 = st.columns(2)
with col1:
if st.button("💾 프롬프트 저장"):
st.session_state['generated_prompt'] = edited_prompt
st.success("✅ 프롬프트가 저장되었습니다!")
with col2:
st.download_button(
label="📥 프롬프트 다운로드",
data=edited_prompt,
file_name=f"prompt_{criteria['name']}.txt",
mime="text/plain"
)
def render_multi_custom_evaluation():
"""여러 심사 기준으로 평가 실행"""
st.subheader("🚀 커스텀 심사 실행")
# 필수 조건 확인
if 'generated_prompts' not in st.session_state:
st.warning("⚠️ 먼저 '프롬프트 생성' 탭에서 평가 프롬프트를 생성해주세요.")
return
generated_prompts = st.session_state['generated_prompts']
if not generated_prompts:
st.warning("⚠️ 생성된 프롬프트가 없습니다.")
return
# 심사 기준 요약
st.success(f"""
✅ 심사 준비 완료
**총 {len(generated_prompts)}개의 심사 기준:**
""")
total_points = 0
for name, data in generated_prompts.items():
points = data['template']['total_points']
total_points += points
st.write(f"- {name}: {points}점")
st.write(f"\n**총 {total_points}점**")
st.markdown("---")
# 파일 업로드
st.markdown("### 📁 파일 업로드")
col1, col2 = st.columns(2)
with col1:
csv_file = st.file_uploader(
"CSV 파일 업로드",
type=['csv'],
help="프로젝트 제출 정보가 담긴 CSV 파일",
key="multi_custom_csv"
)
with col2:
zip_file = st.file_uploader(
"ZIP 파일 업로드",
type=['zip'],
help="워크플로우 JSON 파일들이 포함된 ZIP 파일",
key="multi_custom_zip"
)
# 파일 검증
if csv_file or zip_file:
errors = validate_file_upload(csv_file, zip_file)
if errors:
for error in errors:
st.error(error)
return
# 평가 실행
if st.button("🚀 커스텀 심사 시작", type="primary", disabled=(csv_file is None or zip_file is None)):
run_multi_custom_evaluation(csv_file, zip_file, generated_prompts)
def render_custom_evaluation():
"""커스텀 기준으로 심사 실행 (기존 - 사용 안 함)"""
st.subheader("🚀 커스텀 심사 실행")
# 필수 조건 확인
has_criteria = 'custom_criteria' in st.session_state and st.session_state['custom_criteria'].get('name')
has_prompt = 'generated_prompt' in st.session_state
if not has_criteria:
st.warning("⚠️ 먼저 '기준 설정' 탭에서 심사 기준을 설정해주세요.")
return
if not has_prompt:
st.warning("⚠️ 먼저 '프롬프트 생성' 탭에서 평가 프롬프트를 생성해주세요.")
return
criteria = st.session_state['custom_criteria']
prompt = st.session_state['generated_prompt']
total_with_fixed = 30 + criteria['total_points']
st.success(f"""
✅ 심사 준비 완료
- 고정 평가: 기술적 완성도(15점) + 업스테이지 활용도(15점) = **30점**
- 커스텀 평가: **{criteria['name']}** ({criteria['total_points']}점)
- **총 {total_with_fixed}점**
""")
st.markdown("---")
# 파일 업로드
st.markdown("### 📁 파일 업로드")
col1, col2 = st.columns(2)
with col1:
csv_file = st.file_uploader(
"CSV 파일 업로드",
type=['csv'],
help="프로젝트 제출 정보가 담긴 CSV 파일",
key="custom_csv"
)
with col2:
zip_file = st.file_uploader(
"ZIP 파일 업로드",
type=['zip'],
help="워크플로우 JSON 파일들이 포함된 ZIP 파일",
key="custom_zip"
)
# 파일 검증
if csv_file or zip_file:
errors = validate_file_upload(csv_file, zip_file)
if errors:
for error in errors:
st.error(error)
return
# 평가 실행
if st.button("🚀 커스텀 심사 시작", type="primary", disabled=(csv_file is None or zip_file is None)):
run_custom_evaluation(csv_file, zip_file, criteria, prompt)
def run_multi_custom_evaluation(csv_file, zip_file, generated_prompts):
"""여러 심사 기준으로 평가 실행"""
with tempfile.TemporaryDirectory() as temp_dir:
try:
progress_bar = st.progress(0)
status_text = st.empty()
# 파일 저장
status_text.text("📥 파일 저장 중...")
progress_bar.progress(10)
csv_path = os.path.join(temp_dir, csv_file.name)
zip_path = os.path.join(temp_dir, zip_file.name)
output_dir = os.path.join(temp_dir, "results")
with open(csv_path, "wb") as f:
f.write(csv_file.getbuffer())
with open(zip_path, "wb") as f:
f.write(zip_file.getbuffer())
progress_bar.progress(20)
# 평가 기준 리스트 구성
criteria_list = []
for name, data in generated_prompts.items():
criteria_list.append({
"name": name,
"prompt": data['prompt'],
"total_points": data['template']['total_points'],
"evaluation_target": data['template']['evaluation_target']
})
# 멀티 기준 평가 시스템 초기화
status_text.text("🔧 멀티 기준 평가 시스템 초기화 중...")
evaluator = MultiCriteriaWorkflowEvaluator(
csv_path=csv_path,
zip_path=zip_path,
output_dir=output_dir,
criteria_list=criteria_list
)
progress_bar.progress(30)
# 평가 실행
status_text.text("🔍 멀티 기준으로 평가 진행 중... (시간이 걸릴 수 있습니다)")
with st.spinner("멀티 기준 평가 진행 중..."):
df_result = evaluator.evaluate_all()
progress_bar.progress(80)
status_text.text("💾 결과 저장 중...")
output_path = evaluator.save_results()
progress_bar.progress(90)
progress_bar.progress(100)
status_text.text("✅ 멀티 기준 평가 완료!")
st.success("🎉 멀티 기준 평가가 완료되었습니다!")
# 결과 다운로드
display_results(output_path)
except Exception as e:
st.error(f"❌ 오류 발생: {str(e)}")
import traceback
with st.expander("🔍 상세 오류 정보"):
st.code(traceback.format_exc())
def run_custom_evaluation(csv_file, zip_file, criteria, prompt):
"""커스텀 심사 실행 (기존 - 사용 안 함)"""
with tempfile.TemporaryDirectory() as temp_dir:
try:
progress_bar = st.progress(0)
status_text = st.empty()
# 파일 저장
status_text.text("📥 파일 저장 중...")
progress_bar.progress(10)
csv_path = os.path.join(temp_dir, csv_file.name)
zip_path = os.path.join(temp_dir, zip_file.name)
output_dir = os.path.join(temp_dir, "results")
with open(csv_path, "wb") as f:
f.write(csv_file.getbuffer())
with open(zip_path, "wb") as f:
f.write(zip_file.getbuffer())
progress_bar.progress(20)
# 커스텀 평가 시스템 초기화
status_text.text("🔧 커스텀 평가 시스템 초기화 중...")
evaluator = CustomWorkflowEvaluator(
csv_path=csv_path,
zip_path=zip_path,
output_dir=output_dir,
custom_criteria={
"name": criteria['name'],
"prompt": prompt,
"total_points": criteria['total_points'],
"evaluation_target": criteria['evaluation_target']
}
)
progress_bar.progress(30)
# 평가 실행
status_text.text("🔍 커스텀 기준으로 평가 진행 중... (시간이 걸릴 수 있습니다)")
with st.spinner("커스텀 평가 진행 중..."):
df_result = evaluator.evaluate_all()
progress_bar.progress(80)
status_text.text("💾 결과 저장 중...")
output_path = evaluator.save_results()
progress_bar.progress(90)
progress_bar.progress(100)
status_text.text("✅ 커스텀 평가 완료!")
st.success("🎉 커스텀 평가가 완료되었습니다!")
# 결과 다운로드
display_results(output_path)
except Exception as e:
st.error(f"❌ 오류 발생: {str(e)}")
import traceback
with st.expander("🔍 상세 오류 정보"):
st.code(traceback.format_exc())
def display_results(output_path):
"""평가 결과 표시 및 다운로드"""
st.markdown("---")
st.header("📊 평가 결과")
# 결과 파일 읽기
df_result = pd.read_excel(output_path, engine='openpyxl')
# 결과 다운로드
st.subheader("💾 결과 다운로드")
# XLSX 다운로드
output = BytesIO()
with pd.ExcelWriter(output, engine='openpyxl') as writer:
df_result.to_excel(writer, index=False, sheet_name='평가결과')
excel_data = output.getvalue()
st.download_button(
label="📥 XLSX 다운로드",
data=excel_data,
file_name="평가_결과_최종.xlsx",
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
def main():
st.set_page_config(
page_title="n8n 워크플로우 자동 심사",
page_icon="📊",
layout="wide"
)
st.title("🤖 n8n 워크플로우 자동 심사 시스템")
st.markdown("---")
# API Key 확인 (환경변수에서 자동 로드)
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
st.error("❌ OpenAI API Key가 설정되지 않았습니다. 환경변수를 확인하세요.")
return
st.success("✅ OpenAI API Key 로드 완료")
# 사이드바 네비게이션
st.sidebar.title("🧭 네비게이션")
page = st.sidebar.radio(
"페이지 선택",
["📊 기본 심사", "⚙️ 커스텀 심사 기준"],
label_visibility="collapsed"
)
st.sidebar.markdown("---")
st.sidebar.markdown("""
### 📌 사용 가이드
**기본 심사**
- 기존 90점 만점 기준으로 평가
- CSV + ZIP 파일 업로드 후 즉시 평가
**커스텀 심사 기준**
1. 심사 기준 설정
2. LLM 프롬프트 자동 생성
3. 커스텀 기준으로 평가
""")
# 페이지 렌더링
if page == "📊 기본 심사":
render_default_evaluation_page()
else:
render_custom_criteria_page()
# 푸터
st.markdown("---")
st.markdown("""
<div style='text-align: center; color: gray;'>
<p>n8n 워크플로우 자동 심사 시스템 | Powered by GPT-4o & Streamlit</p>
</div>
""", unsafe_allow_html=True)
if __name__ == "__main__":
main()