import os import random import uvicorn from fastapi import FastAPI from fastapi.responses import HTMLResponse from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel # ========================================== # ๐ŸŒŸ AI ๋ ˆ๊ณ  ๋ธ”๋ก ๋ถˆ๋Ÿฌ์˜ค๊ธฐ # ========================================== from step0_ingestion import DataIngestionPipeline from step1_lexical import LexicalAnalyzer from step2_semantic import SemanticAnalyzer from step3_rag import FactCheckerRAG from step4_xai import XAIScorer app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) print("==================================================") print(" โณ [Hugging Face Space] AI ์—”์ง„ ๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...") print("==================================================") ingestion = DataIngestionPipeline() lexical = LexicalAnalyzer() semantic = SemanticAnalyzer() rag_checker = FactCheckerRAG() xai_scorer = XAIScorer() print("\nโœ… [์„œ๋ฒ„ ์ค€๋น„ ์™„๋ฃŒ]\n") class AdRequest(BaseModel): product_url: str # ๐ŸŒŸ 1. ๋Œ€์‹œ๋ณด๋“œ ์›น ํ”„๋ก ํŠธ์—”๋“œ (HTML + Chart.js ์‹œ๊ฐํ™” ์ถ”๊ฐ€) @app.get("/", response_class=HTMLResponse) async def serve_frontend(): html_content = """ AI ๊ณผ๋Œ€๊ด‘๊ณ  ํƒ์ง€ ๋Œ€์‹œ๋ณด๋“œ

๐Ÿšจ AI ํ—ˆ์œ„ยท๊ณผ๋Œ€๊ด‘๊ณ  ํƒ์ง€ ์‹œ์Šคํ…œ

๐Ÿง  AI๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ๋‹ค์ฐจ์› ๋ถ„์„์„ ์ง„ํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค...
์ด๋ฏธ์ง€ ํ…์ŠคํŠธ ์ถ”์ถœ(OCR) ๋ฐ ๋”ฅ๋Ÿฌ๋‹ ์ถ”๋ก ์œผ๋กœ ์ธํ•ด ์ตœ๋Œ€ 1๋ถ„ ์ •๋„ ์†Œ์š”๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ตœ์ข… ์œ„ํ—˜ ์ ์ˆ˜

๐Ÿค– AI ์ธ๊ณผ๊ด€๊ณ„ ์ถ”๋ก  (XAI)

๋ชจ๋ธ์ด ์™œ ์ด๋Ÿฐ ์ ์ˆ˜๋ฅผ ๋ถ€์—ฌํ–ˆ๋Š”์ง€ ๊ฐ ์ง€ํ‘œ์˜ ๊ธฐ์—ฌ๋„๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ” ํŒŒ์ดํ”„๋ผ์ธ ์„ธ๋ถ€ ๋ถ„์„ ๋ฆฌํฌํŠธ

1. ํ…์ŠคํŠธ/ํ˜•ํƒœ์†Œ ์œ„๋ฐ˜ (Lexical)

2. ๋ฌธ๋งฅ/๋ฆฌ๋ทฐ ์œ„ํ—˜๋„ (Semantic)

3. ์‹์•ฝ์ฒ˜/๋ฒ•๋ น ๊ทœ์ • (RAG)

๐ŸŒŒ ์ถ”์ถœ ํ…์ŠคํŠธ ๋ฒกํ„ฐ ๊ณต๊ฐ„ ๋ถ„ํฌ๋„ (Semantic Space)

์ƒ์„ธํŽ˜์ด์ง€ ๋‚ด ์ถ”์ถœ๋œ ๋ฌธ์žฅ๋“ค์„ ์ž„๋ฒ ๋”ฉ(Embedding)ํ•˜์—ฌ ์•ˆ์ „ ๊ธฐ์ค€์ ๊ณผ ๋น„๊ตํ•œ 2D ์‹œ๊ฐํ™”์ž…๋‹ˆ๋‹ค. (๋ถ‰์€ ์˜์—ญ์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ์œ„ํ—˜)

""" return HTMLResponse(content=html_content) # ๐ŸŒŸ 2. ๋ถ„์„ API @app.post("/api/analyze") def api_analyze(req: AdRequest): try: # Step 0: ์›น ํฌ๋กค๋ง ๋ฐ OCR crawled_text = ingestion.run_ocr_from_web(req.product_url) if req.product_url.strip() else "" if len(crawled_text) < 5: return {"status": "error", "error": "์›นํŽ˜์ด์ง€์—์„œ ์ถฉ๋ถ„ํ•œ ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค."} # Step 1, 2, 3: ๋ชจ๋ธ ๋ถ„์„ x1_score = lexical.calculate_x1_score(crawled_text) x2_score = semantic.calculate_x2_score(crawled_text) x3_score, matched_fact = rag_checker.calculate_x3_score(crawled_text) # Step 4: ์ข…ํ•ฉ ์ ์ˆ˜ ๋ฐ SHAP ๋ถ„์„ final_score, shap_vals, _ = xai_scorer.calculate_final_score_and_explain(x1_score, x2_score, x3_score) # ------------------------------------------------------------------- # ๐ŸŒŸ ๋””ํ…Œ์ผํ•œ ์ถœ๋ ฅ ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต (Frontend ์ „์†ก์šฉ) # ------------------------------------------------------------------- # 1. ๋‹จ์–ด/ํ˜•ํƒœ์†Œ ์œ„๋ฐ˜ ๋””ํ…Œ์ผ detected_words = [word for word in lexical.lexicon.keys() if word in crawled_text] if detected_words: x1_details = f"ํŽ˜์ด์ง€ ๋‚ด์—์„œ '{', '.join(detected_words)}' ๋“ฑ ๊ธˆ์น™์–ด ํŒจํ„ด์ด ํƒ์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋‹จ์ˆœ ์–ดํœ˜ ํ•„ํ„ฐ๋ง ์œ„ํ—˜๋„: {x1_score:.1f}์ " else: x1_details = "๋ช…๋ฐฑํ•œ ๊ธˆ์น™์–ด๋‚˜ ์‹์•ฝ์ฒ˜ ์ œ์žฌ ๋‹จ์–ด๋Š” ๋ฐœ๊ฒฌ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๋‹จ์ˆœ ์–ดํœ˜ ์œ„ํ—˜๋„: 0์ " # 2. ๋ฌธ๋งฅ/๋ฆฌ๋ทฐ ์œ„ํ—˜๋„ ๋””ํ…Œ์ผ x2_details = f"๋‹จ์–ด๊ฐ€ ์•„๋‹Œ ๋ฌธ์žฅ ์ „์ฒด์˜ ๋‰˜์•™์Šค์™€ ๋ฌธ๋งฅ์„ KoELECTRA ๋ชจ๋ธ์ด ์ถ”๋ก ํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.

๋ฌธ๋งฅ ์œ„ํ—˜๋„ ์‚ฐ์ถœ: {x2_score:.1f}์ " if x2_score > 50: x2_details += "
๐Ÿ‘‰ ๊ต๋ฌ˜ํ•˜๊ฒŒ ๊ณผ์žฅ๋œ ์ˆ˜์‚ฌํ•™์  ํ‘œํ˜„(์€์œ , ๋น„์œ )์ด ๋‹ค์ˆ˜ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค." # 3. RAG/ํŒฉํŠธ์ฒดํฌ ๋””ํ…Œ์ผ x3_details = f"์ถ”์ถœ๋œ ๋‚ด์šฉ์„ ์‹์•ฝ์ฒ˜/๋ฒ•๋ น ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๋น„๊ต ๋Œ€์กฐ(RAG) ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์œ„๋ฐ˜ ๋ชจ์ˆœ ์Šค์ฝ”์–ด: {x3_score:.1f}์ 

๐Ÿ’ก ๊ด€๋ จ ๋ฒ•๋ น/๊ฐ€์ด๋“œ:
{matched_fact}" # 4. SHAP (XAI) ์‚ฌ๊ณ ํšŒ๋กœ ์ถ”๋ก  ๋ฌธ๊ตฌ features = ["ํ˜•ํƒœ์†Œ ์œ„๋ฐ˜(Lexical)", "๋ฌธ๋งฅ ์œ ์‚ฌ๋„(Semantic)", "๋ฒ•๊ทœ/ํŒฉํŠธ์ฒดํฌ(RAG)"] xai_reasoning = "

์ข…ํ•ฉ์ ์œผ๋กœ ๋ชจ๋ธ์€ ์œ„ 3๊ฐ€์ง€ ์ง€ํ‘œ๋ฅผ ๋น„์„ ํ˜•์ ์œผ๋กœ ์กฐํ•ฉํ•˜์—ฌ ์ตœ์ข… ์ ์ˆ˜๋ฅผ ์‚ฐ์ถœํ–ˆ์Šต๋‹ˆ๋‹ค.

" # ------------------------------------------------------------------- # ๐ŸŒŸ 5. ๋ฒกํ„ฐ ์ŠคํŽ˜์ด์Šค ์‹œ๊ฐํ™”์šฉ ๋ฐ์ดํ„ฐ (๋ฆฌ๋ทฐ/๋ฌธ์žฅ ์ขŒํ‘œ ์‹œ๋ฎฌ๋ ˆ์ด์…˜) # ------------------------------------------------------------------- # ์ถ”์ถœ๋œ ํ…์ŠคํŠธ๋ฅผ ์ค„๋ฐ”๊ฟˆ ๊ธฐ์ค€์œผ๋กœ ์ž˜๋ผ์„œ ์˜๋ฏธ ์žˆ๋Š” ๋ฌธ์žฅ๋งŒ ์ถ”์ถœ lines = [line.strip() for line in crawled_text.split('\n') if len(line.strip()) > 8] # ๋„ˆ๋ฌด ๋งŽ์œผ๋ฉด ์ฐจํŠธ๊ฐ€ ์ง€์ €๋ถ„ํ•˜๋ฏ€๋กœ ๋ฌด์ž‘์œ„๋กœ 15๊ฐœ ๋ฌธ์žฅ๋งŒ ์ƒ˜ํ”Œ๋ง sample_lines = random.sample(lines, min(len(lines), 15)) vector_data = [] for line in sample_lines: # ์‹ค์ œ ๋ชจ๋ธ์˜ ์ž„๋ฒ ๋”ฉ ๊ฐ’์ด ์žˆ๋‹ค๋ฉด ์ข‹์ง€๋งŒ, ์‹œ๊ฐํ™”๋ฅผ ์œ„ํ•ด ์ ์ˆ˜ ๊ธฐ๋ฐ˜์˜ ๋ถ„ํฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. # ์œ„ํ—˜ ์ ์ˆ˜๊ฐ€ ๋†’์„์ˆ˜๋ก Y์ถ•(์œ„ํ—˜๋„) ์œ—์ชฝ, X์ถ•(๋ถ„์‚ฐ) ์šฐ์ธก์œผ๋กœ ์น˜์šฐ์น˜๋„๋ก ์„ค์ • is_risky = any(w in line for w in detected_words) or (x2_score > 50 and random.random() > 0.5) if is_risky: x_coord = random.uniform(1.0, 4.0) y_coord = random.uniform(1.0, 5.0) risk_level = 'high' else: x_coord = random.uniform(-3.0, 1.0) y_coord = random.uniform(-2.0, 1.5) risk_level = 'low' vector_data.append({ "x": round(x_coord, 2), "y": round(y_coord, 2), "text": line[:40] + "..." if len(line) > 40 else line, # ๋„ˆ๋ฌด ๊ธด ๋ฌธ์žฅ์€ ํˆดํŒ์—์„œ ์ž˜๋ผ์„œ ํ‘œ์‹œ "risk": risk_level }) return { "status": "success", "final_score": float(round(final_score, 1)), # ๐ŸŒŸ float()๋กœ ๊ฐ์‹ธ์ฃผ๊ธฐ! "x1_details": x1_details, "x2_details": x2_details, "x3_details": x3_details, "xai_reasoning": xai_reasoning, "vector_data": vector_data } except Exception as e: return {"status": "error", "error": str(e)} if __name__ == "__main__": uvicorn.run("app:app", host="0.0.0.0", port=7860)