sungho7373's picture
Upload folder using huggingface_hub
6270029 verified
Raw History Blame Contribute Delete
20.3 kB
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 = """
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>AI ๊ณผ๋Œ€๊ด‘๊ณ  ํƒ์ง€ ๋Œ€์‹œ๋ณด๋“œ</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: 'Pretendard', -apple-system, sans-serif; background-color: #121212; color: #ffffff; padding: 20px; margin: 0; }
.container { max-width: 1200px; margin: auto; }
h2 { color: #ffffff; text-align: left; border-bottom: 2px solid #333; padding-bottom: 10px; }
.input-section { background: #1e1e1e; padding: 20px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
input { width: 100%; padding: 15px; margin: 8px 0; background: #2c2c2e; border: 1px solid #444; border-radius: 8px; color: #fff; box-sizing: border-box; font-size: 15px; }
button { width: 100%; padding: 15px; background: #4caf50; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: bold; cursor: pointer; transition: 0.3s; }
button:disabled { background: #555; cursor: not-allowed; opacity: 0.7; }
.dashboard { display: none; grid-template-columns: 1fr 2fr; gap: 20px; margin-top: 20px; }
.dashboard-full { display: none; margin-top: 20px; }
.card { background: #1e1e1e; padding: 25px; border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.2); }
#loading-spinner { display: none; text-align: center; margin: 40px 0; }
.spinner { display: inline-block; width: 60px; height: 60px; border: 5px solid #333; border-top: 5px solid #4caf50; border-radius: 50%; animation: spin 1s linear infinite; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.loading-text { margin-top: 15px; font-size: 18px; font-weight: bold; color: #4caf50; }
.loading-subtext { font-size: 14px; color: #888; font-weight: normal; margin-top: 5px; }
.details-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin-top: 15px; }
.detail-item { background: #2c2c2e; padding: 20px; border-radius: 8px; line-height: 1.6; font-size: 15px; }
.detail-item h4 { margin-top: 0; color: #4caf50; border-bottom: 1px solid #444; padding-bottom: 8px; }
#error-message { display: none; background: #ff5252; color: white; padding: 15px; border-radius: 8px; margin-top: 20px; text-align: center; font-weight: bold; }
.highlight-red { color: #ff5252; font-weight: bold; font-size: 1.1em; }
.chart-container { position: relative; height: 350px; width: 100%; margin-top: 15px; }
</style>
</head>
<body>
<div class="container">
<h2>๐Ÿšจ AI ํ—ˆ์œ„ยท๊ณผ๋Œ€๊ด‘๊ณ  ํƒ์ง€ ์‹œ์Šคํ…œ</h2>
<div class="input-section">
<input type="text" id="product_url" placeholder="๋ถ„์„ํ•  ์ƒํ’ˆ ์ƒ์„ธํŽ˜์ด์ง€ ๋งํฌ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š” (์˜ˆ: ์Šค๋งˆํŠธ์Šคํ† ์–ด, ์ž์‚ฌ๋ชฐ ๋งํฌ)">
<button id="analyze-btn" onclick="analyzeAd()">ํฌ๋กค๋ง ๋ฐ AI ๋‹ค์ค‘ ๋ถ„์„ ์‹œ์ž‘</button>
</div>
<div id="loading-spinner">
<div class="spinner"></div>
<div class="loading-text">๐Ÿง  AI๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ๋‹ค์ฐจ์› ๋ถ„์„์„ ์ง„ํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค...</div>
<div class="loading-subtext">์ด๋ฏธ์ง€ ํ…์ŠคํŠธ ์ถ”์ถœ(OCR) ๋ฐ ๋”ฅ๋Ÿฌ๋‹ ์ถ”๋ก ์œผ๋กœ ์ธํ•ด ์ตœ๋Œ€ 1๋ถ„ ์ •๋„ ์†Œ์š”๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.</div>
</div>
<div id="error-message"></div>
<div class="dashboard" id="dashboard">
<div class="card" style="display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center;">
<h3 style="margin-top: 0; color: #aaa;">์ตœ์ข… ์œ„ํ—˜ ์ ์ˆ˜</h3>
<h1 id="scoreText" style="font-size: 64px; margin: 10px 0;"></h1>
<p id="scoreLabel" style="font-size: 18px; font-weight: bold; margin: 0;"></p>
</div>
<div class="card">
<h3 style="margin-top: 0;">๐Ÿค– AI ์ธ๊ณผ๊ด€๊ณ„ ์ถ”๋ก  (XAI)</h3>
<p style="color: #aaa; font-size: 14px; margin-bottom: 15px;">๋ชจ๋ธ์ด ์™œ ์ด๋Ÿฐ ์ ์ˆ˜๋ฅผ ๋ถ€์—ฌํ–ˆ๋Š”์ง€ ๊ฐ ์ง€ํ‘œ์˜ ๊ธฐ์—ฌ๋„๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.</p>
<div id="xaiReasoning" style="background: #2c2c2e; padding: 15px; border-radius: 8px; line-height: 1.6;"></div>
</div>
</div>
<div class="dashboard-full" id="detailsDashboard">
<div class="card">
<h3 style="margin-top: 0;">๐Ÿ” ํŒŒ์ดํ”„๋ผ์ธ ์„ธ๋ถ€ ๋ถ„์„ ๋ฆฌํฌํŠธ</h3>
<div class="details-grid">
<div class="detail-item">
<h4>1. ํ…์ŠคํŠธ/ํ˜•ํƒœ์†Œ ์œ„๋ฐ˜ (Lexical)</h4>
<span id="x1Details"></span>
</div>
<div class="detail-item">
<h4>2. ๋ฌธ๋งฅ/๋ฆฌ๋ทฐ ์œ„ํ—˜๋„ (Semantic)</h4>
<span id="x2Details"></span>
</div>
<div class="detail-item">
<h4>3. ์‹์•ฝ์ฒ˜/๋ฒ•๋ น ๊ทœ์ • (RAG)</h4>
<span id="x3Details"></span>
</div>
</div>
</div>
</div>
<div class="dashboard-full" id="vectorDashboard">
<div class="card">
<h3 style="margin-top: 0;">๐ŸŒŒ ์ถ”์ถœ ํ…์ŠคํŠธ ๋ฒกํ„ฐ ๊ณต๊ฐ„ ๋ถ„ํฌ๋„ (Semantic Space)</h3>
<p style="color: #aaa; font-size: 14px;">์ƒ์„ธํŽ˜์ด์ง€ ๋‚ด ์ถ”์ถœ๋œ ๋ฌธ์žฅ๋“ค์„ ์ž„๋ฒ ๋”ฉ(Embedding)ํ•˜์—ฌ ์•ˆ์ „ ๊ธฐ์ค€์ ๊ณผ ๋น„๊ตํ•œ 2D ์‹œ๊ฐํ™”์ž…๋‹ˆ๋‹ค. (๋ถ‰์€ ์˜์—ญ์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ์œ„ํ—˜)</p>
<div class="chart-container">
<canvas id="vectorChart"></canvas>
</div>
</div>
</div>
</div>
<script>
let vectorChartInstance = null;
async function analyzeAd() {
const productUrl = document.getElementById('product_url').value;
if (!productUrl.trim()) { alert("์›นํŽ˜์ด์ง€ ๋งํฌ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."); return; }
// UI ๋ฆฌ์…‹ ๋ฐ ๋กœ๋”ฉ
document.getElementById('loading-spinner').style.display = 'block';
document.getElementById('analyze-btn').disabled = true;
document.getElementById('analyze-btn').innerText = 'AI ๋ถ„์„ ์ง„ํ–‰ ์ค‘... โณ';
document.getElementById('dashboard').style.display = 'none';
document.getElementById('detailsDashboard').style.display = 'none';
document.getElementById('vectorDashboard').style.display = 'none';
document.getElementById('error-message').style.display = 'none';
try {
const response = await fetch('/api/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ product_url: productUrl })
});
const data = await response.json();
if (data.status === "success") {
document.getElementById('dashboard').style.display = 'grid';
document.getElementById('detailsDashboard').style.display = 'block';
document.getElementById('vectorDashboard').style.display = 'block';
// ๐ŸŒŸ 1. ์ ์ˆ˜ ๋ Œ๋”๋ง (์†Œ์ˆ˜์  1์ž๋ฆฌ ํ”ฝ์Šค & ์ƒ‰์ƒ ๋กœ์ง)
const rawScore = parseFloat(data.final_score);
const scoreEl = document.getElementById('scoreText');
const labelEl = document.getElementById('scoreLabel');
scoreEl.innerText = rawScore.toFixed(1) + "์ ";
if(rawScore >= 70) {
scoreEl.style.color = "#ff5252";
labelEl.innerText = "๐Ÿšจ ์‹ฌ๊ฐํ•œ ๊ณผ๋Œ€๊ด‘๊ณ  ์˜์‹ฌ";
labelEl.style.color = "#ff5252";
} else if(rawScore >= 40) {
scoreEl.style.color = "#ffb142";
labelEl.innerText = "โš ๏ธ ์ฃผ์˜ ์š”๋ง (์ผ๋ถ€ ๊ณผ์žฅ ํฌํ•จ)";
labelEl.style.color = "#ffb142";
} else {
scoreEl.style.color = "#4caf50";
labelEl.innerText = "โœ… ์ •์ƒ ์ปจํ…์ธ ";
labelEl.style.color = "#4caf50";
}
// ๋””ํ…Œ์ผ ๋ Œ๋”๋ง
document.getElementById('xaiReasoning').innerHTML = data.xai_reasoning;
document.getElementById('x1Details').innerHTML = data.x1_details;
document.getElementById('x2Details').innerHTML = data.x2_details;
document.getElementById('x3Details').innerHTML = data.x3_details;
// ๐ŸŒŸ 2. ์ฐจํŠธ ๋ Œ๋”๋ง (๋ฒกํ„ฐ ๊ณต๊ฐ„)
renderVectorChart(data.vector_data);
} else {
document.getElementById('error-message').innerText = "โŒ ๋ถ„์„ ์‹คํŒจ: " + data.error;
document.getElementById('error-message').style.display = 'block';
}
} catch (error) {
console.error('Fetch ์—๋Ÿฌ:', error);
document.getElementById('error-message').innerText = "โŒ ์„œ๋ฒ„ ํ†ต์‹  ์—๋Ÿฌ: " + error;
document.getElementById('error-message').style.display = 'block';
} finally {
document.getElementById('loading-spinner').style.display = 'none';
document.getElementById('analyze-btn').disabled = false;
document.getElementById('analyze-btn').innerText = 'ํฌ๋กค๋ง ๋ฐ AI ๋‹ค์ค‘ ๋ถ„์„ ์‹œ์ž‘';
}
}
function renderVectorChart(vectorData) {
const ctx = document.getElementById('vectorChart').getContext('2d');
if (vectorChartInstance) { vectorChartInstance.destroy(); } // ๊ธฐ์กด ์ฐจํŠธ ์ดˆ๊ธฐํ™”
// ๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌ
const safePoints = vectorData.filter(d => d.risk === 'low').map(d => ({x: d.x, y: d.y, text: d.text}));
const warningPoints = vectorData.filter(d => d.risk === 'high').map(d => ({x: d.x, y: d.y, text: d.text}));
// ๊ธฐ์ค€์ (Norm) ์„ค์ •
const normPoint = [{x: 0, y: 0, text: "์•ˆ์ „ ๊ธฐ์ค€์  (์‹์•ฝ์ฒ˜ ๊ฐ€์ด๋“œ)"}];
vectorChartInstance = new Chart(ctx, {
type: 'scatter',
data: {
datasets: [
{
label: '์•ˆ์ „ ๋ฌธ์žฅ๊ตฐ (Safe)',
data: safePoints,
backgroundColor: 'rgba(76, 175, 80, 0.7)',
pointRadius: 6,
pointHoverRadius: 9
},
{
label: '์œ„ํ—˜/๊ณผ์žฅ ์˜์‹ฌ ๋ฌธ์žฅ๊ตฐ (High Risk)',
data: warningPoints,
backgroundColor: 'rgba(255, 82, 82, 0.8)',
pointRadius: 8,
pointHoverRadius: 11
},
{
label: '์•ˆ์ „ ๊ธฐ์ค€(Center)',
data: normPoint,
backgroundColor: 'white',
borderColor: 'black',
borderWidth: 2,
pointRadius: 10,
pointStyle: 'rectRot'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
color: '#ffffff',
scales: {
x: { grid: { color: '#333' }, title: { display: true, text: '์˜๋ฏธ๋ก ์  ๋ณ€๋™์„ฑ (Semantic Variance)', color: '#aaa' } },
y: { grid: { color: '#333' }, title: { display: true, text: '์œ„ํ—˜๋„ ์Šค์ฝ”์–ด (Risk Dimension)', color: '#aaa' } }
},
plugins: {
legend: { labels: { color: 'white' } },
tooltip: {
callbacks: {
label: function(context) {
return context.raw.text; // ํˆดํŒ์— ์‹ค์ œ ๋ฌธ์žฅ ํ‘œ์‹œ
}
}
}
}
}
});
}
</script>
</body>
</html>
"""
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"ํŽ˜์ด์ง€ ๋‚ด์—์„œ <span class='highlight-red'>'{', '.join(detected_words)}'</span> ๋“ฑ ๊ธˆ์น™์–ด ํŒจํ„ด์ด ํƒ์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.<br><br>๋‹จ์ˆœ ์–ดํœ˜ ํ•„ํ„ฐ๋ง ์œ„ํ—˜๋„: <b>{x1_score:.1f}์ </b>"
else:
x1_details = "๋ช…๋ฐฑํ•œ ๊ธˆ์น™์–ด๋‚˜ ์‹์•ฝ์ฒ˜ ์ œ์žฌ ๋‹จ์–ด๋Š” ๋ฐœ๊ฒฌ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.<br><br>๋‹จ์ˆœ ์–ดํœ˜ ์œ„ํ—˜๋„: <b>0์ </b>"
# 2. ๋ฌธ๋งฅ/๋ฆฌ๋ทฐ ์œ„ํ—˜๋„ ๋””ํ…Œ์ผ
x2_details = f"๋‹จ์–ด๊ฐ€ ์•„๋‹Œ <b>๋ฌธ์žฅ ์ „์ฒด์˜ ๋‰˜์•™์Šค์™€ ๋ฌธ๋งฅ</b>์„ KoELECTRA ๋ชจ๋ธ์ด ์ถ”๋ก ํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.<br><br>๋ฌธ๋งฅ ์œ„ํ—˜๋„ ์‚ฐ์ถœ: <b style='color:#ffb142;'>{x2_score:.1f}์ </b>"
if x2_score > 50:
x2_details += "<br>๐Ÿ‘‰ ๊ต๋ฌ˜ํ•˜๊ฒŒ ๊ณผ์žฅ๋œ ์ˆ˜์‚ฌํ•™์  ํ‘œํ˜„(์€์œ , ๋น„์œ )์ด ๋‹ค์ˆ˜ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค."
# 3. RAG/ํŒฉํŠธ์ฒดํฌ ๋””ํ…Œ์ผ
x3_details = f"์ถ”์ถœ๋œ ๋‚ด์šฉ์„ ์‹์•ฝ์ฒ˜/๋ฒ•๋ น ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๋น„๊ต ๋Œ€์กฐ(RAG) ํ•˜์˜€์Šต๋‹ˆ๋‹ค.<br><br>์œ„๋ฐ˜ ๋ชจ์ˆœ ์Šค์ฝ”์–ด: <b>{x3_score:.1f}์ </b><br><br>๐Ÿ’ก <b>๊ด€๋ จ ๋ฒ•๋ น/๊ฐ€์ด๋“œ:</b><br><span style='color:#aaa;'>{matched_fact}</span>"
# 4. SHAP (XAI) ์‚ฌ๊ณ ํšŒ๋กœ ์ถ”๋ก  ๋ฌธ๊ตฌ
features = ["ํ˜•ํƒœ์†Œ ์œ„๋ฐ˜(Lexical)", "๋ฌธ๋งฅ ์œ ์‚ฌ๋„(Semantic)", "๋ฒ•๊ทœ/ํŒฉํŠธ์ฒดํฌ(RAG)"]
xai_reasoning = "<ul>"
for i, feature_name in enumerate(features):
impact = shap_vals[i]
if impact > 0:
xai_reasoning += f"<li>๐Ÿ”ด <b>{feature_name}:</b> ์œ„ํ—˜๋„๋ฅผ <span class='highlight-red'>+{impact:.2f}</span> ๋งŒํผ ์ฆ๊ฐ€์‹œ์ผฐ์Šต๋‹ˆ๋‹ค.</li>"
else:
xai_reasoning += f"<li>๐ŸŸข <b>{feature_name}:</b> ์•ˆ์ „ ๊ทผ๊ฑฐ๋กœ ์ž‘์šฉํ•˜์—ฌ ์ ์ˆ˜๋ฅผ <span>{impact:.2f}</span> ๋งŒํผ ๋‚ฎ์ท„์Šต๋‹ˆ๋‹ค.</li>"
xai_reasoning += "</ul><p style='margin-top:10px;'>์ข…ํ•ฉ์ ์œผ๋กœ ๋ชจ๋ธ์€ ์œ„ 3๊ฐ€์ง€ ์ง€ํ‘œ๋ฅผ ๋น„์„ ํ˜•์ ์œผ๋กœ ์กฐํ•ฉํ•˜์—ฌ ์ตœ์ข… ์ ์ˆ˜๋ฅผ ์‚ฐ์ถœํ–ˆ์Šต๋‹ˆ๋‹ค.</p>"
# -------------------------------------------------------------------
# ๐ŸŒŸ 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)