Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import re | |
| from typing import List, Tuple, Dict | |
| from subjectless_predicates_122725_v2 import DOUBT_PREDICATES, SUPPORT_PREDICATES, analyze_objectivity | |
| from korean_sentence_splitter import KoreanSentenceSplitter | |
| def highlight_objectivity(text: str) -> str: | |
| if not text: | |
| return "" | |
| splitter = KoreanSentenceSplitter() | |
| sentences = splitter.split(text) | |
| highlighted_sentences = [] | |
| for sent in sentences: | |
| matches = [] | |
| # Find doubt matches | |
| for cat, pattern in DOUBT_PREDICATES.items(): | |
| for match in pattern.finditer(sent): | |
| matches.append((match.start(), match.end(), "doubt", cat)) | |
| # Find support matches | |
| for cat, pattern in SUPPORT_PREDICATES.items(): | |
| for match in pattern.finditer(sent): | |
| matches.append((match.start(), match.end(), "support", cat)) | |
| # Sort matches by start position, then by length (descending) to handle potential overlaps | |
| matches.sort(key=lambda x: (x[0], -(x[1] - x[0]))) | |
| # Filter overlapping matches (keep the longest or first) | |
| filtered_matches = [] | |
| if matches: | |
| last_end = -1 | |
| for start, end, mtype, cat in matches: | |
| if start >= last_end: | |
| filtered_matches.append((start, end, mtype, cat)) | |
| last_end = end | |
| # Build highlighted sentence | |
| last_idx = 0 | |
| h_sent = "" | |
| for start, end, mtype, cat in filtered_matches: | |
| # Add text before match | |
| h_sent += sent[last_idx:start] | |
| # Add highlighted match | |
| match_text = sent[start:end] | |
| if mtype == "doubt": | |
| color = "#ffcccc" # Light red | |
| border = "#ff0000" | |
| h_sent += f'<span style="background-color: {color}; border-bottom: 2px solid {border};" title="{cat}">{match_text}</span>' | |
| else: | |
| color = "#ccffcc" # Light green | |
| border = "#00aa00" | |
| h_sent += f'<span style="background-color: {color}; border-bottom: 2px solid {border};" title="{cat}">{match_text}</span>' | |
| last_idx = end | |
| h_sent += sent[last_idx:] | |
| highlighted_sentences.append(h_sent) | |
| return " ".join(highlighted_sentences) | |
| def clear_input(): | |
| st.session_state.main_text_input = "" | |
| def main(): | |
| st.set_page_config(page_title="뉴스 객관성 술어 분석기", layout="wide") | |
| st.title("📰 뉴스 객관성 분석기: 술어 성격 탐지 및 추출") | |
| st.markdown(""" | |
| 뉴스 객관성 평가를 위해 술어의 성격을 탐지하고 추출하는 기능을 수행합니다. | |
| - <span style="background-color: #ffcccc; border-bottom: 2px solid #ff0000;">빨간색 표시</span>: **객관성 의심 (Objectivity Doubt)** - 발언의 주체가 불분명하여 기자의 주관이 개입되었을 가능성이 높은 표현 | |
| - <span style="background-color: #ccffcc; border-bottom: 2px solid #00aa00;">녹색 표시</span>: **객관성 지지 (Objectivity Support)** - 사실 확인이나 구체적인 출처/데이터를 바탕으로 한 객관적 표현 | |
| """, unsafe_allow_html=True) | |
| # 세션 상태 초기화 | |
| if 'main_text_input' not in st.session_state: | |
| st.session_state.main_text_input = """오늘 주가가 크게 오를 것으로 전망된다. 투자자들 사이에서는 이번 상승세가 당분간 이어질 것이라는 분석이 지배적이다. 반면 일각에서는 거품이라는 지적도 나온다. 실제로 통계청 집계에 따르면 지난달 수출은 역대 최고치를 기록했다. 정부 관계자는 경제 지표가 개선되고 있음이 확인됐다고 밝혔다.""" | |
| col1, col2 = st.columns([1, 1]) | |
| with col1: | |
| st.subheader("입력 텍스트") | |
| # key를 통해 세션 상태와 직접 연결 | |
| input_text = st.text_area("분석할 기사 내용을 입력하세요.", height=400, | |
| placeholder="여기에 기사 내용을 붙여넣으세요...", | |
| key="main_text_input") | |
| btn_col1, btn_col2, _ = st.columns([1, 1, 3]) | |
| analyze_clicked = btn_col1.button("분석", type="primary") | |
| # 초기화 버튼에 콜백 함수 적용 | |
| btn_col2.button("초기화", on_click=clear_input) | |
| # 분석 버튼 클릭 시 결과 표시 | |
| if analyze_clicked and input_text: | |
| with col2: | |
| st.subheader("분석 결과") | |
| with st.spinner("분석 중..."): | |
| highlighted_html = highlight_objectivity(input_text) | |
| st.markdown(f'<div style="line-height: 1.8; font-size: 1.1em; border: 1px solid #ddd; padding: 20px; border-radius: 5px; background-color: white;">{highlighted_html}</div>', unsafe_allow_html=True) | |
| # 통계 요약 | |
| stats = analyze_objectivity(input_text) | |
| st.write("---") | |
| s_col1, s_col2, s_col3 = st.columns(3) | |
| s_col1.metric("의심 문장", stats["doubt_count"]) | |
| s_col2.metric("지지 문장", stats["support_count"]) | |
| if stats["objectivity_ratio"] is not None: | |
| st.progress(stats["objectivity_ratio"]) | |
| st.write(f"**객관성 지표:** {stats['objectivity_ratio']:.2%}") | |
| with st.expander("검출된 술어 상세 목록"): | |
| tab1, tab2 = st.tabs(["의심 술어 (DOUBT)", "지지 술어 (SUPPORT)"]) | |
| with tab1: | |
| if stats["doubt_predicates"]: | |
| st.write(", ".join(set(stats["doubt_predicates"]))) | |
| else: | |
| st.write("발견되지 않음") | |
| with tab2: | |
| if stats["support_predicates"]: | |
| st.write(", ".join(set(stats["support_predicates"]))) | |
| else: | |
| st.write("발견되지 않음") | |
| st.write("---") | |
| st.subheader("💡 설명서: 술어 분류 체계") | |
| st.markdown("`subjectless_predicates_122725_v2.py` 참조") | |
| desc_col1, desc_col2 = st.columns(2) | |
| with desc_col1: | |
| st.markdown("### 🔴 객관성 의심 술어 (DOUBT): 무주체 피동형") | |
| st.markdown(""" | |
| | 대분류 | 설명 | 주요 예시 | | |
| | :--- | :--- | :--- | | |
| | **분석/해석형** | 사건의 의미를 주관적으로 풀이 | 분석된다, 해석된다, ~라는 분석이다 | | |
| | **전망/예측형** | 불확실한 미래를 단정적으로 추측 | 전망된다, 예상된다, 점쳐진다 | | |
| | **관측/추정형** | 뚜렷한 근거 없이 미루어 짐작 | 관측된다, 추정된다, 추측된다 | | |
| | **전언/보도형** | 출처를 흐리며 말을 전달 | 알려졌다, 전해졌다, ~라는 소식이다 | | |
| | **평가/판단형** | 가치 판단이 개입된 서술 | 평가된다, 여겨진다, ~라는 판단이다 | | |
| | **비판/지적형** | 특정 입장에서 부정적으로 언급 | 비판받는다, 지적된다, 논란이 일고 있다 | | |
| | **제기/거론형** | 화제를 수면 위로 올리는 서술 | 제기된다, 거론된다, 언급된다 | | |
| | **우려/의혹형** | 부정적 가능성을 강조 | 우려가 나온다, 의혹이 제기됐다 | | |
| | **가능성형** | 여지를 두는 표현 | 가능성이 크다, 배제할 수 없다 | | |
| | **분위기형** | 주변 상황을 추상적으로 묘사 | 분위기다, 목소리가 높다, 기류가 감지된다 | | |
| | **주장/입장형** | 특정인의 말을 무주체로 전달 | 주장된다, ~라는 입장이다 | | |
| | **시각/견해형** | 관점을 제시 | 시각이다, 견해가 지배적이다 | | |
| | **격찬/혹평형** | 극단적인 감정적 평가 | 찬사가 쏟아졌다, 혹평을 받았다 | | |
| | **관용표현형** | 객관성을 흐리는 상투적 표현 | ~인 셈이다, ~로 보인다, 아닌가 싶다 | | |
| | **완화표현형** | 단정을 피하려는 회피성 표현 | ~듯 보인다, ~일 것 같다 | | |
| """) | |
| with desc_col2: | |
| st.markdown("### 🟢 객관성 지지 술어 (SUPPORT)") | |
| st.markdown(""" | |
| | 대분류 | 설명 | 주요 예시 | | |
| | :--- | :--- | :--- | | |
| | **확인/검증형** | 사실 관계가 분명히 밝혀짐 | 확인됐다, 밝혀졌다, 드러났다, 입증됐다 | | |
| | **발견/탐지형** | 구체적인 실체를 찾아냄 | 발견됐다, 적발됐다, 파악됐다 | | |
| | **기록/집계형** | 수치나 데이터에 기반한 서술 | 기록됐다, 집계됐다, 나타났다, 조사됐다 | | |
| """) | |
| if __name__ == "__main__": | |
| main() | |