sungho7373 commited on
Commit
2e8210f
·
verified ·
1 Parent(s): 563e1ba

Upload 11 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 예시 Dockerfile 핵심 내용
2
+ FROM python:3.11
3
+
4
+ # 시스템 패키지 설치 (packages.txt가 있다면 활용)
5
+ RUN apt-get update && apt-get install -y $(cat packages.txt)
6
+
7
+ WORKDIR /code
8
+ COPY . .
9
+
10
+ # 라이브러리 및 브라우저 설치
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+ RUN playwright install --with-deps chromium
13
+
14
+ # 포트 설정 (허깅페이스 기본값)
15
+ EXPOSE 7860
16
+
17
+ # 실행 명령 (myapp 폴더 안의 app.py 실행)
18
+ CMD ["python", "myapp/app.py"]
myapp/Procfile ADDED
File without changes
myapp/app.py ADDED
@@ -0,0 +1,345 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import uvicorn
4
+ from fastapi import FastAPI
5
+ from fastapi.responses import HTMLResponse
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from pydantic import BaseModel
8
+
9
+ # ==========================================
10
+ # Load AI pipeline components
11
+ # ==========================================
12
+ from step0_ingestion import DataIngestionPipeline
13
+ from step1_lexical import LexicalAnalyzer
14
+ from step2_semantic import SemanticAnalyzer
15
+ from step3_rag import FactCheckerRAG
16
+ from step4_xai import XAIScorer
17
+
18
+ app = FastAPI()
19
+
20
+ app.add_middleware(
21
+ CORSMiddleware,
22
+ allow_origins=["*"],
23
+ allow_credentials=True,
24
+ allow_methods=["*"],
25
+ allow_headers=["*"],
26
+ )
27
+
28
+ print("==================================================")
29
+ print(" ⏳ [Hugging Face Space] Loading AI engine models...")
30
+ print("==================================================")
31
+ ingestion = DataIngestionPipeline()
32
+ lexical = LexicalAnalyzer()
33
+ semantic = SemanticAnalyzer()
34
+ rag_checker = FactCheckerRAG()
35
+ xai_scorer = XAIScorer()
36
+ print("\n✅ [Server Ready]\n")
37
+
38
+ class AdRequest(BaseModel):
39
+ product_url: str
40
+
41
+ # 1. Dashboard frontend (HTML + Chart.js visualization)
42
+ @app.get("/", response_class=HTMLResponse)
43
+ async def serve_frontend():
44
+ html_content = """
45
+ <!DOCTYPE html>
46
+ <html lang="en">
47
+ <head>
48
+ <meta charset="UTF-8">
49
+ <title>AI Dark Pattern & Subscription Auditor</title>
50
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
51
+ <style>
52
+ body { font-family: 'Inter', -apple-system, sans-serif; background-color: #0f172a; color: #f1f5f9; padding: 20px; margin: 0; }
53
+ .container { max-width: 1200px; margin: auto; }
54
+ h2 { color: #ffffff; text-align: left; border-bottom: 2px solid #334155; padding-bottom: 10px; }
55
+ .input-section { background: #1e293b; padding: 25px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
56
+ input { width: 100%; padding: 15px; margin: 8px 0; background: #0f172a; border: 1px solid #334155; border-radius: 8px; color: #fff; box-sizing: border-box; font-size: 15px; }
57
+ button { width: 100%; padding: 15px; background: #3b82f6; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: bold; cursor: pointer; transition: 0.3s; }
58
+ button:hover { background: #2563eb; }
59
+ button:disabled { background: #475569; cursor: not-allowed; opacity: 0.7; }
60
+
61
+ .dashboard { display: none; grid-template-columns: 1fr 2fr; gap: 20px; margin-top: 20px; }
62
+ .dashboard-full { display: none; margin-top: 20px; }
63
+ .card { background: #1e293b; padding: 25px; border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.2); }
64
+
65
+ #loading-spinner { display: none; text-align: center; margin: 40px 0; }
66
+ .spinner { display: inline-block; width: 60px; height: 60px; border: 5px solid #334155; border-top: 5px solid #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; }
67
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
68
+ .loading-text { margin-top: 15px; font-size: 18px; font-weight: bold; color: #3b82f6; }
69
+ .loading-subtext { font-size: 14px; color: #94a3b8; font-weight: normal; margin-top: 5px; }
70
+
71
+ .details-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin-top: 15px; }
72
+ .detail-item { background: #0f172a; padding: 20px; border-radius: 8px; line-height: 1.6; font-size: 15px; }
73
+ .detail-item h4 { margin-top: 0; color: #3b82f6; border-bottom: 1px solid #334155; padding-bottom: 8px; }
74
+
75
+ #error-message { display: none; background: #ef4444; color: white; padding: 15px; border-radius: 8px; margin-top: 20px; text-align: center; font-weight: bold; }
76
+ .highlight-red { color: #f87171; font-weight: bold; font-size: 1.1em; }
77
+ .chart-container { position: relative; height: 350px; width: 100%; margin-top: 15px; }
78
+ </style>
79
+ </head>
80
+ <body>
81
+ <div class="container">
82
+ <h2>🚨 AI Dark Pattern & Subscription Auditor</h2>
83
+ <div class="input-section">
84
+ <input type="text" id="product_url" placeholder="Enter service URL to audit (e.g., SaaS landing page or 'Free Trial' offer page)">
85
+ <button id="analyze-btn" onclick="analyzeAd()">Start Forensic Audit</button>
86
+ </div>
87
+
88
+ <div id="loading-spinner">
89
+ <div class="spinner"></div>
90
+ <div class="loading-text">🧠 AI is auditing terms, conditions, and billing disclosures...</div>
91
+ <div class="loading-subtext">Scanning for hidden continuity clauses and deceptive UI patterns.</div>
92
+ </div>
93
+
94
+ <div id="error-message"></div>
95
+
96
+ <div class="dashboard" id="dashboard">
97
+ <div class="card" style="display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center;">
98
+ <h3 style="margin-top: 0; color: #94a3b8;">Subscription Risk Score</h3>
99
+ <h1 id="scoreText" style="font-size: 64px; margin: 10px 0;"></h1>
100
+ <p id="scoreLabel" style="font-size: 18px; font-weight: bold; margin: 0;"></p>
101
+ </div>
102
+ <div class="card">
103
+ <h3 style="margin-top: 0;">🤖 AI Reasoning Trace (XAI)</h3>
104
+ <p style="color: #94a3b8; font-size: 14px; margin-bottom: 15px;">How individual signals contributed to the hidden billing risk assessment.</p>
105
+ <div id="xaiReasoning" style="background: #0f172a; padding: 15px; border-radius: 8px; line-height: 1.6;"></div>
106
+ </div>
107
+ </div>
108
+
109
+ <div class="dashboard-full" id="detailsDashboard">
110
+ <div class="card">
111
+ <h3 style="margin-top: 0;">🔍 Forensic Pipeline Report</h3>
112
+ <div class="details-grid">
113
+ <div class="detail-item">
114
+ <h4>1. Bait & Switch Lexicon</h4>
115
+ <span id="x1Details"></span>
116
+ </div>
117
+ <div class="detail-item">
118
+ <h4>2. Intent Clarity Analysis</h4>
119
+ <span id="x2Details"></span>
120
+ </div>
121
+ <div class="detail-item">
122
+ <h4>3. FTC Compliance (RAG)</h4>
123
+ <span id="x3Details"></span>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+
129
+ <div class="dashboard-full" id="vectorDashboard">
130
+ <div class="card">
131
+ <h3 style="margin-top: 0;">🌌 Semantic Risk Mapping</h3>
132
+ <p style="color: #94a3b8; font-size: 14px;">Extracted sentences mapped against deceptive intent patterns. Red zone indicates high-pressure or obscured billing language.</p>
133
+ <div class="chart-container">
134
+ <canvas id="vectorChart"></canvas>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </div>
139
+
140
+ <script>
141
+ let vectorChartInstance = null;
142
+
143
+ async function analyzeAd() {
144
+ const productUrl = document.getElementById('product_url').value;
145
+ if (!productUrl.trim()) { alert("Please enter a valid URL."); return; }
146
+
147
+ document.getElementById('loading-spinner').style.display = 'block';
148
+ document.getElementById('analyze-btn').disabled = true;
149
+ document.getElementById('analyze-btn').innerText = 'Auditing... ⏳';
150
+ document.getElementById('dashboard').style.display = 'none';
151
+ document.getElementById('detailsDashboard').style.display = 'none';
152
+ document.getElementById('vectorDashboard').style.display = 'none';
153
+ document.getElementById('error-message').style.display = 'none';
154
+
155
+ try {
156
+ const response = await fetch('/api/analyze', {
157
+ method: 'POST',
158
+ headers: { 'Content-Type': 'application/json' },
159
+ body: JSON.stringify({ product_url: productUrl })
160
+ });
161
+ const data = await response.json();
162
+
163
+ if (data.status === "success") {
164
+ document.getElementById('dashboard').style.display = 'grid';
165
+ document.getElementById('detailsDashboard').style.display = 'block';
166
+ document.getElementById('vectorDashboard').style.display = 'block';
167
+
168
+ const rawScore = parseFloat(data.final_score);
169
+ const scoreEl = document.getElementById('scoreText');
170
+ const labelEl = document.getElementById('scoreLabel');
171
+
172
+ scoreEl.innerText = rawScore.toFixed(1);
173
+
174
+ if(rawScore >= 70) {
175
+ scoreEl.style.color = "#ef4444";
176
+ labelEl.innerText = "🚨 CRITICAL RISK: Forced Continuity Likely";
177
+ labelEl.style.color = "#ef4444";
178
+ } else if(rawScore >= 40) {
179
+ scoreEl.style.color = "#f59e0b";
180
+ labelEl.innerText = "⚠️ SUSPICIOUS: Obscured Billing Terms";
181
+ labelEl.style.color = "#f59e0b";
182
+ } else {
183
+ scoreEl.style.color = "#10b981";
184
+ labelEl.innerText = "✅ LOW RISK: Transparent Pricing";
185
+ labelEl.style.color = "#10b981";
186
+ }
187
+
188
+ document.getElementById('xaiReasoning').innerHTML = data.xai_reasoning;
189
+ document.getElementById('x1Details').innerHTML = data.x1_details;
190
+ document.getElementById('x2Details').innerHTML = data.x2_details;
191
+ document.getElementById('x3Details').innerHTML = data.x3_details;
192
+
193
+ renderVectorChart(data.vector_data);
194
+
195
+ } else {
196
+ document.getElementById('error-message').innerText = "❌ Audit failed: " + data.error;
197
+ document.getElementById('error-message').style.display = 'block';
198
+ }
199
+ } catch (error) {
200
+ document.getElementById('error-message').innerText = "❌ Server error: " + error;
201
+ document.getElementById('error-message').style.display = 'block';
202
+ } finally {
203
+ document.getElementById('loading-spinner').style.display = 'none';
204
+ document.getElementById('analyze-btn').disabled = false;
205
+ document.getElementById('analyze-btn').innerText = 'Start Forensic Audit';
206
+ }
207
+ }
208
+
209
+ function renderVectorChart(vectorData) {
210
+ const ctx = document.getElementById('vectorChart').getContext('2d');
211
+ if (vectorChartInstance) { vectorChartInstance.destroy(); }
212
+
213
+ const safePoints = vectorData.filter(d => d.risk === 'low').map(d => ({x: d.x, y: d.y, text: d.text}));
214
+ const warningPoints = vectorData.filter(d => d.risk === 'high').map(d => ({x: d.x, y: d.y, text: d.text}));
215
+ const normPoint = [{x: 0, y: 0, text: "Compliant Baseline (FTC ROSCA)"}];
216
+
217
+ vectorChartInstance = new Chart(ctx, {
218
+ type: 'scatter',
219
+ data: {
220
+ datasets: [
221
+ {
222
+ label: 'Clear/Transparent',
223
+ data: safePoints,
224
+ backgroundColor: 'rgba(16, 185, 129, 0.7)',
225
+ pointRadius: 6
226
+ },
227
+ {
228
+ label: 'Deceptive/Obscured',
229
+ data: warningPoints,
230
+ backgroundColor: 'rgba(239, 68, 68, 0.8)',
231
+ pointRadius: 8
232
+ },
233
+ {
234
+ label: 'Compliance Center',
235
+ data: normPoint,
236
+ backgroundColor: 'white',
237
+ borderColor: 'black',
238
+ borderWidth: 2,
239
+ pointRadius: 10,
240
+ pointStyle: 'rectRot'
241
+ }
242
+ ]
243
+ },
244
+ options: {
245
+ responsive: true,
246
+ maintainAspectRatio: false,
247
+ color: '#ffffff',
248
+ scales: {
249
+ x: { grid: { color: '#334155' }, title: { display: true, text: 'Semantic Clarity', color: '#94a3b8' } },
250
+ y: { grid: { color: '#334155' }, title: { display: true, text: 'Deception Dimension', color: '#94a3b8' } }
251
+ },
252
+ plugins: {
253
+ legend: { labels: { color: 'white' } },
254
+ tooltip: { callbacks: { label: (ctx) => ctx.raw.text } }
255
+ }
256
+ }
257
+ });
258
+ }
259
+ </script>
260
+ </body>
261
+ </html>
262
+ """
263
+ return HTMLResponse(content=html_content)
264
+
265
+ # 2. Analysis API
266
+ @app.post("/api/analyze")
267
+ def api_analyze(req: AdRequest):
268
+ try:
269
+ # Step 0: Web crawling and OCR
270
+ crawled_text = ingestion.run_ocr_from_web(req.product_url) if req.product_url.strip() else ""
271
+ if len(crawled_text) < 10:
272
+ return {"status": "error", "error": "Insufficient text extracted from the provided URL."}
273
+
274
+ # Step 1, 2, 3: Model analysis
275
+ x1_score = lexical.calculate_x1_score(crawled_text)
276
+ x2_score = semantic.calculate_x2_score(crawled_text)
277
+ x3_score, matched_fact = rag_checker.calculate_x3_score(crawled_text)
278
+
279
+ # Step 4: Final scoring and SHAP analysis
280
+ final_score, shap_vals, _ = xai_scorer.calculate_final_score_and_explain(x1_score, x2_score, x3_score)
281
+
282
+ # 1. Lexical trigger details (Subscription Traps)
283
+ detected_words = [word for word in lexical.lexicon.keys() if word.lower() in crawled_text.lower()]
284
+ if detected_words:
285
+ x1_details = f"Detected high-risk triggers: <span class='highlight-red'>'{', '.join(detected_words)}'</span>.<br><br>Lexical risk: <b>{x1_score:.1f} pts</b>"
286
+ else:
287
+ x1_details = "No explicit 'Bait' keywords detected in primary content.<br><br>Lexical risk: <b>0 pts</b>"
288
+
289
+ # 2. Semantic context details (Obscurity)
290
+ x2_details = f"The AI model interpreted the <b>intentionality of the disclosure</b>.<br><br>Deception score: <b style='color:#f59e0b;'>{x2_score:.1f} pts</b>"
291
+ if x2_score > 50:
292
+ x2_details += "<br>👉 Warning: Free offer is prioritized while billing obligations are downplayed."
293
+
294
+ # 3. RAG / FTC compliance details
295
+ x3_details = f"Cross-referenced with FTC Negative Option Rule and ROSCA guidelines.<br><br>Non-compliance score: <b>{x3_score:.1f} pts</b><br><br>💡 <b>Relevant Regulation:</b><br><span style='color:#94a3b8;'>{matched_fact}</span>"
296
+
297
+ # 4. SHAP (XAI) reasoning
298
+ features = ["Bait Keywords", "Intent Obscurity", "Regulatory Violation"]
299
+ xai_reasoning = "<ul>"
300
+ for i, feature_name in enumerate(features):
301
+ impact = shap_vals[i]
302
+ if impact > 0:
303
+ xai_reasoning += f"<li>🔴 <b>{feature_name}:</b> increased risk by <span class='highlight-red'>+{impact:.2f}</span></li>"
304
+ else:
305
+ xai_reasoning += f"<li>🟢 <b>{feature_name}:</b> reduced risk by <span>{impact:.2f}</span></li>"
306
+ xai_reasoning += "</ul><p style='margin-top:10px;'>Combined assessment of deceptive UX and legal non-compliance.</p>"
307
+
308
+ # 5. Vector-space visualization (Simulated for English context)
309
+ lines = [line.strip() for line in crawled_text.split('\n') if len(line.strip()) > 10]
310
+ sample_lines = random.sample(lines, min(len(lines), 15))
311
+
312
+ vector_data = []
313
+ for line in sample_lines:
314
+ is_risky = any(w.lower() in line.lower() for w in detected_words) or (x2_score > 50 and random.random() > 0.5)
315
+
316
+ if is_risky:
317
+ x_coord = random.uniform(1.0, 5.0)
318
+ y_coord = random.uniform(1.0, 5.0)
319
+ risk_level = 'high'
320
+ else:
321
+ x_coord = random.uniform(-5.0, 1.0)
322
+ y_coord = random.uniform(-3.0, 1.5)
323
+ risk_level = 'low'
324
+
325
+ vector_data.append({
326
+ "x": round(x_coord, 2),
327
+ "y": round(y_coord, 2),
328
+ "text": line[:50] + "..." if len(line) > 50 else line,
329
+ "risk": risk_level
330
+ })
331
+
332
+ return {
333
+ "status": "success",
334
+ "final_score": float(round(final_score, 1)),
335
+ "x1_details": x1_details,
336
+ "x2_details": x2_details,
337
+ "x3_details": x3_details,
338
+ "xai_reasoning": xai_reasoning,
339
+ "vector_data": vector_data
340
+ }
341
+ except Exception as e:
342
+ return {"status": "error", "error": str(e)}
343
+
344
+ if __name__ == "__main__":
345
+ uvicorn.run("app:app", host="0.0.0.0", port=7860)
myapp/main_pipeline.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from step0_ingestion import DataIngestionPipeline
2
+ from step1_lexical import LexicalAnalyzer
3
+ from step2_semantic import SemanticAnalyzer
4
+ from step3_rag import FactCheckerRAG
5
+ from step4_xai import XAIScorer
6
+
7
+ def run_full_pipeline():
8
+ print("==================================================")
9
+ print(" 🚀 [End-to-End Pipeline] Launching the misleading-ad detection AI system (crawling/OCR only)")
10
+ print("==================================================\n")
11
+
12
+ # 1. 모델 및 엔진 초기화
13
+ ingestion = DataIngestionPipeline()
14
+ lexical = LexicalAnalyzer()
15
+ semantic = SemanticAnalyzer()
16
+ rag_checker = FactCheckerRAG()
17
+ xai_scorer = XAIScorer()
18
+
19
+ target_product_url = "https://brand.naver.com/pacsafe/products/9365045491"
20
+
21
+ print("\n▶️ [Step 0] Collecting webpage text and image data...")
22
+
23
+ crawled_text = ""
24
+ try:
25
+ crawled_text = ingestion.run_ocr_from_web(target_product_url)
26
+ except Exception as e:
27
+ print(f"⚠️ OCR extraction failed: {e}")
28
+
29
+ if not crawled_text or len(crawled_text.strip()) == 0:
30
+ print("❌ No analyzable text was found. Exiting.")
31
+ return
32
+
33
+ print("\n▶️ [Step 1, 2, 3] Running deep text analysis...")
34
+
35
+ x1_score = lexical.calculate_x1_score(crawled_text)
36
+ x2_score = semantic.calculate_x2_score(crawled_text)
37
+ x3_score, matched_fact = rag_checker.calculate_x3_score(crawled_text)
38
+
39
+ print("\n▶️ [Step 4] Generating final XGBoost scoring and SHAP explanations...")
40
+ final_score, shap_vals, base_value = xai_scorer.calculate_final_score_and_explain(x1_score, x2_score, x3_score)
41
+
42
+ print("\n==================================================")
43
+ print(" 📜 [Final Misleading-Ad Detection Report]")
44
+ print("==================================================")
45
+ print(f" 🔹 [X1] Lexical trigger score : {x1_score:5.1f} pts")
46
+ print(f" 🔹 [X2] Semantic context score : {x2_score:5.1f} pts")
47
+ print(f" 🔹 [X3] Regulatory fact-check score : {x3_score:5.1f} pts")
48
+ print("--------------------------------------------------")
49
+ print(f" 🎯 [S] Final ML risk score : {final_score:5.1f} / 100.0 pts")
50
+ print("==================================================")
51
+
52
+ if final_score > 70:
53
+ print(" 🚨 [Verdict] Very high risk. Strong signs of misleading or exaggerated advertising were detected.")
54
+ elif final_score > 40:
55
+ print(" ⚠️ [Verdict] Caution. Some exaggerated or potentially inaccurate claims were detected.")
56
+ else:
57
+ print(" ✅ [Verdict] Low risk. The content appears relatively safe.")
58
+
59
+ print("\n==================================================")
60
+ print(" 🤖 [XAI] Why the model made this decision (SHAP values)")
61
+ print("==================================================")
62
+ print(" (Positive values increase risk, while negative values support a safer assessment.)\n")
63
+
64
+ features = ["X1 (Lexical)", "X2 (Semantic)", "X3 (Fact Check)"]
65
+ for i, feature_name in enumerate(features):
66
+ impact = shap_vals[i]
67
+ direction = "🔴 Risk increase" if impact > 0 else "🟢 Risk decrease"
68
+ print(f" {direction} contribution: {feature_name:<18} -> impact: {impact:+.2f}")
69
+ print("==================================================")
70
+
71
+ if __name__ == "__main__":
72
+ run_full_pipeline()
myapp/step0_ingestion.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gc
3
+ import requests
4
+ from paddleocr import PaddleOCR
5
+ from playwright.sync_api import sync_playwright
6
+ import logging
7
+
8
+ class DataIngestionPipeline:
9
+ def __init__(self):
10
+ # Set logging to suppress unnecessary info
11
+ logging.getLogger('ppocr').setLevel(logging.ERROR)
12
+ print("✅ DataIngestionPipeline initialized (English/Global Context)")
13
+
14
+ def clear_memory(self):
15
+ gc.collect()
16
+ print("🧹 Memory cleanup complete")
17
+
18
+ def run_ocr_from_web(self, product_url):
19
+ print(f"\n🌐 [Step 0] Auditing Web Content: {product_url}")
20
+
21
+ extracted_html_text = ""
22
+ raw_image_urls = []
23
+
24
+ with sync_playwright() as p:
25
+ print(" -> Launching headless browser...")
26
+ browser = p.chromium.launch(
27
+ headless=True,
28
+ args=["--no-sandbox", "--disable-setuid-sandbox"]
29
+ )
30
+ page = browser.new_page()
31
+
32
+ # Increase timeout for global sites
33
+ page.goto(product_url, wait_until="domcontentloaded", timeout=60000)
34
+ page.wait_for_timeout(3000)
35
+
36
+ # [Core Logic 1] Extracting Visible HTML Text (Crucial for Dark Patterns)
37
+ # Dark patterns are often in the footer or tiny text labels.
38
+ print(" -> 📄 Extracting visible text from HTML (Footer/Terms/Body)...")
39
+ extracted_html_text = page.evaluate("() => document.body.innerText")
40
+
41
+ # [Core Logic 2] Handling English "See More" Buttons
42
+ print(" -> 🎯 Looking for disclosure buttons (Details/Terms/More)...")
43
+ try:
44
+ # Updated selectors for English subscription sites
45
+ selectors = [
46
+ 'button:has-text("Show more")',
47
+ 'button:has-text("See details")',
48
+ 'button:has-text("Terms")',
49
+ 'a:has-text("Read more")',
50
+ 'span:has-text("View more")'
51
+ ]
52
+ for selector in selectors:
53
+ btn = page.locator(selector).first
54
+ if btn.is_visible(timeout=2000):
55
+ btn.click()
56
+ print(f" => Clicked: {selector}")
57
+ page.wait_for_timeout(1000)
58
+ except Exception:
59
+ pass
60
+
61
+ # Scroll to trigger lazy loading for images
62
+ print(" -> Scrolling to load billing disclosures...")
63
+ for _ in range(5):
64
+ page.evaluate("window.scrollBy(0, 1000)")
65
+ page.wait_for_timeout(800)
66
+
67
+ # Collect Image URLs for OCR
68
+ img_elements = page.query_selector_all('img')
69
+ for img in img_elements:
70
+ src = img.get_attribute('data-src') or img.get_attribute('src')
71
+ if src and ('http' in src or src.startswith('//')):
72
+ if src.startswith('//'): src = 'https:' + src
73
+ raw_image_urls.append(src)
74
+
75
+ browser.close()
76
+
77
+ # Image Filtering
78
+ valid_urls = [url for url in raw_image_urls if not any(x in url.lower() for x in ['.gif', 'icon', 'logo', 'svg', 'thumb'])]
79
+
80
+ # [Core Logic 3] OCR for English Text
81
+ all_extracted_text = [extracted_html_text] # Start with HTML text
82
+
83
+ if valid_urls:
84
+ print(f"✅ Found {len(valid_urls)} images. Running English OCR on key visual assets...")
85
+
86
+ # Change language to 'en' for English detection
87
+ ocr = PaddleOCR(
88
+ use_angle_cls=True,
89
+ lang='en', # CRITICAL: Changed to English
90
+ # show_log=False
91
+ )
92
+
93
+ headers = {
94
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
95
+ }
96
+
97
+ processed_count = 0
98
+ # Scan top 3 major images (likely billing banners)
99
+ for i, img_url in enumerate(valid_urls[:5]):
100
+ if processed_count >= 3: break
101
+
102
+ temp_img_path = f"temp_ocr_{i}.jpg"
103
+ try:
104
+ response = requests.get(img_url, headers=headers, timeout=10)
105
+ with open(temp_img_path, 'wb') as f:
106
+ f.write(response.content)
107
+
108
+ if os.path.getsize(temp_img_path) < 40000: # Ignore small icons
109
+ if os.path.exists(temp_img_path): os.remove(temp_img_path)
110
+ continue
111
+
112
+ processed_count += 1
113
+ result = ocr.ocr(temp_img_path, cls=True)
114
+
115
+ if result and result[0]:
116
+ texts = [line[1][0] for line in result[0]]
117
+ print(f" => ✨ Extracted {len(texts)} lines from image {i}.")
118
+ all_extracted_text.extend(texts)
119
+
120
+ except Exception as e:
121
+ print(f"⚠️ Image processing error: {e}")
122
+ finally:
123
+ if os.path.exists(temp_img_path): os.remove(temp_img_path)
124
+
125
+ del ocr
126
+
127
+ self.clear_memory()
128
+
129
+ # Merge all text (HTML Text + OCR Text)
130
+ final_text = "\n".join(all_extracted_text).strip()
131
+ print(f"✅ Web Audit Complete. Total characters extracted: {len(final_text)}")
132
+ return final_text
133
+
myapp/step1_lexical.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+
3
+ class LexicalAnalyzer:
4
+ def __init__(self):
5
+ print("✅ Loading the English Dark Pattern Lexical Analyzer...")
6
+
7
+ # 🚨 Subscription Trap & Dark Pattern Lexicon (English)
8
+ # Weights indicate the level of risk associated with "Bait-and-Switch" tactics.
9
+ self.lexicon = {
10
+ # Bait Triggers
11
+ "free trial": 2.5,
12
+ "0.00": 1.5,
13
+ "for free": 2.0,
14
+ "get started free": 1.5,
15
+ "7 days free": 2.0,
16
+ "30 days free": 2.0,
17
+
18
+ # Hidden Continuity / Trap Triggers
19
+ "auto-renew": 3.0,
20
+ "automatic renewal": 3.0,
21
+ "recurring billing": 3.0,
22
+ "monthly subscription": 2.0,
23
+ "billed annually": 1.5,
24
+ "hidden fee": 3.0,
25
+ "card required": 2.5,
26
+ "credit card required": 2.5,
27
+
28
+ # Forced Urgency / Scarcity (Dark Patterns)
29
+ "limited time offer": 1.0,
30
+ "exclusive deal": 1.0,
31
+ "ends soon": 1.0,
32
+ "cancel anytime": 1.0 # Often used to mask the difficulty of actual cancellation
33
+ }
34
+
35
+ # 🛡️ Negation words (to prevent false positives like "No hidden fees")
36
+ self.negation_words = {"no", "not", "never", "without", "zero", "none"}
37
+
38
+ def split_into_sentences(self, text):
39
+ """Splits English text into sentences using punctuation."""
40
+ sentences = re.split(r'[.!?\n]+', text)
41
+ return [s.strip() for s in sentences if len(s.strip()) > 5]
42
+
43
+ def check_negation_context(self, sentence, target_phrase):
44
+ """
45
+ Checks if a negation word appears shortly before the target phrase.
46
+ Example: "No [hidden fees]" -> Cleared.
47
+ """
48
+ sentence = sentence.lower()
49
+ target_phrase = target_phrase.lower()
50
+
51
+ # Look for words within a 3-word window before the phrase
52
+ words = sentence.split()
53
+ try:
54
+ # Find the starting index of the target phrase in terms of word position
55
+ phrase_start_index = sentence.find(target_phrase)
56
+ prefix_text = sentence[:phrase_start_index].split()
57
+
58
+ # Check the last 3 words before the phrase
59
+ lookback_window = prefix_text[-3:] if len(prefix_text) >= 3 else prefix_text
60
+
61
+ for neg in self.negation_words:
62
+ if neg in lookback_window:
63
+ return True
64
+ return False
65
+ except:
66
+ return False
67
+
68
+ def calculate_x1_score(self, text):
69
+ if not text:
70
+ return 0.0
71
+
72
+ print("\n🔍 [Step 1] Analyzing text for 'Bait-and-Switch' lexical triggers...")
73
+
74
+ text_lower = text.lower()
75
+ sentences = self.split_into_sentences(text)
76
+ total_sentences = len(sentences)
77
+
78
+ if total_sentences == 0:
79
+ return 0.0
80
+
81
+ total_penalty = 0.0
82
+ detected_issues = []
83
+
84
+ # Check for each phrase in the lexicon
85
+ for phrase, weight in self.lexicon.items():
86
+ # Find all occurrences of the phrase
87
+ matches = re.finditer(re.escape(phrase), text_lower)
88
+
89
+ for match in matches:
90
+ # Find which sentence contains this match to check context
91
+ start_pos = match.start()
92
+ # Find the sentence containing this position
93
+ current_sentence = next((s for s in sentences if phrase in s.lower()), "")
94
+
95
+ is_negated = self.check_negation_context(current_sentence, phrase)
96
+
97
+ if is_negated:
98
+ detected_issues.append(f"🛡️ Cleared by negation: '{phrase}' in '{current_sentence[:40]}...'")
99
+ else:
100
+ total_penalty += weight
101
+ detected_issues.append(f"🚨 Flagged: '{phrase}' (weight: +{weight})")
102
+
103
+ # 🧮 X1 Score Scaling (0 ~ 100)
104
+ # We use a density-based scoring approach
105
+ raw_score = (total_penalty / (total_sentences * 0.5)) * 20
106
+ x1_score = min(raw_score, 100.0)
107
+
108
+ # Report logs
109
+ print(f"[Lexical Report] Penalty: {total_penalty:.1f}, Sentences: {total_sentences}")
110
+ print(f"📈 Final Lexical Risk Score: {x1_score:.2f} / 100.0")
111
+
112
+ return x1_score
113
+
114
+ # ==========================================
115
+ # Local Test Logic
116
+ # ==========================================
117
+ if __name__ == "__main__":
118
+ analyzer = LexicalAnalyzer()
119
+
120
+ print("\n" + "="*50)
121
+ print("Test 1: Safe ad with clear disclosures")
122
+ test_1 = "Get our service for $10 a month. No hidden fees. Cancel anytime."
123
+ analyzer.calculate_x1_score(test_1)
124
+
125
+ print("\n" + "="*50)
126
+ print("Test 2: High-risk subscription trap")
127
+ test_2 = "Start your 7 days free trial now! Only $0.00 today. Automatic renewal applies after the trial period. Credit card required for verification."
128
+ analyzer.calculate_x1_score(test_2)
129
+
myapp/step2_semantic.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn.functional as F
3
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
4
+ import gc
5
+
6
+ class SemanticAnalyzer:
7
+ def __init__(self):
8
+ print("🧠 [Step 2] Loading the English Semantic Analyzer (DistilBERT)...")
9
+
10
+ # Support for Apple Silicon (MPS), NVIDIA (CUDA), or CPU
11
+ if torch.backends.mps.is_available():
12
+ self.device = torch.device("mps")
13
+ elif torch.cuda.is_available():
14
+ self.device = torch.device("cuda")
15
+ else:
16
+ self.device = torch.device("cpu")
17
+
18
+ print(f" -> Computation device: {self.device}")
19
+
20
+ # Use DistilBERT: Lightweight, fast, and excellent for English context
21
+ self.model_name = "distilbert-base-uncased"
22
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
23
+
24
+ # output_hidden_states=True to extract CLS vectors for similarity
25
+ self.model = AutoModelForSequenceClassification.from_pretrained(
26
+ self.model_name,
27
+ num_labels=2,
28
+ output_hidden_states=True
29
+ ).to(self.device)
30
+ self.model.eval()
31
+
32
+ # [Vector A Space] Reference examples of English Dark Patterns & Subscription Traps
33
+ # These represent the "DNA" of deceptive billing behavior.
34
+ self.reference_bad_texts = [
35
+ "Start your free trial now, $0.00 today, automatically billed full price after 7 days.",
36
+ "No commitment, cancel anytime, but credit card required for identity verification.",
37
+ "Special $1 offer ends in 10 minutes, recurring monthly subscription starts thereafter.",
38
+ "Membership automatically renews at $49.99 per month unless cancelled via phone call.",
39
+ "Hidden service fees and processing charges applied at the final checkout step."
40
+ ]
41
+ print(" -> Encoding global dark pattern references into vector space...")
42
+ self.reference_embeddings = self._get_embeddings(self.reference_bad_texts)
43
+
44
+ def clear_memory(self):
45
+ """Prevents memory leaks during inference"""
46
+ gc.collect()
47
+ if self.device.type == "mps":
48
+ torch.mps.empty_cache()
49
+ elif self.device.type == "cuda":
50
+ torch.cuda.empty_cache()
51
+
52
+ def _get_embeddings(self, texts):
53
+ """Converts text list into sentence embedding vectors (CLS token)"""
54
+ inputs = self.tokenizer(texts, padding=True, truncation=True, max_length=512, return_tensors="pt").to(self.device)
55
+ with torch.no_grad():
56
+ outputs = self.model(**inputs)
57
+ # Use [CLS] token (index 0) from the last hidden layer
58
+ sentence_embeddings = outputs.hidden_states[-1][:, 0, :]
59
+ return sentence_embeddings
60
+
61
+ def calculate_x2_score(self, text):
62
+ if not text or len(text.strip()) < 10:
63
+ return 0.0
64
+
65
+ print("\n🧠 [Step 2] Analyzing semantic context for deceptive intent...")
66
+
67
+ # Truncate text to 512 tokens (standard for BERT-based models)
68
+ inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=512).to(self.device)
69
+
70
+ with torch.no_grad():
71
+ outputs = self.model(**inputs)
72
+
73
+ # 1. Classification Logit (Raw Probability)
74
+ logits = outputs.logits
75
+ probs = F.softmax(logits, dim=-1)
76
+ prob_deceptive = probs[0][1].item()
77
+
78
+ # 2. Vector Similarity (Comparing vs known Dark Patterns)
79
+ current_embedding = outputs.hidden_states[-1][:, 0, :]
80
+ similarities = F.cosine_similarity(current_embedding, self.reference_embeddings)
81
+ max_sim = torch.max(similarities).item()
82
+
83
+ # Cleanup
84
+ del inputs, outputs, logits, probs, current_embedding, similarities
85
+ self.clear_memory()
86
+
87
+ # Final X2 Score Calculation (Ensemble of Probability + Similarity)
88
+ # Since the base model isn't fine-tuned specifically for dark patterns,
89
+ # Similarity (80%) is a stronger signal than raw classification (20%).
90
+ classification_score = prob_deceptive * 100
91
+ similarity_score = max(max_sim, 0) * 100
92
+
93
+ x2_score = (similarity_score * 0.8) + (classification_score * 0.2)
94
+
95
+ print(f" -> 📊 Intent Detection Prob: {prob_deceptive*100:.1f}%")
96
+ print(f" -> 🔗 Contextual Similarity to Traps: {max_sim*100:.1f}%")
97
+ print(f"📈 Final Semantic Risk Score: {x2_score:.2f}")
98
+
99
+ return x2_score
100
+
101
+ # ==========================================
102
+ # Unit Test
103
+ # ==========================================
104
+ if __name__ == "__main__":
105
+ analyzer = SemanticAnalyzer()
106
+
107
+ # Test: A typical subscription trap phrase
108
+ test_text = "Join now for $0! Just enter your card details for verification. You will be billed $99 after the trial."
109
+ analyzer.calculate_x2_score(test_text)
myapp/step3_rag.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import torch
4
+ import numpy as np
5
+ from openai import OpenAI
6
+ from sentence_transformers import SentenceTransformer, util
7
+ from dotenv import load_dotenv
8
+
9
+ # Load environment variables
10
+ load_dotenv()
11
+
12
+ class FactCheckerRAG:
13
+ def __init__(self):
14
+ print("📚 [Step 3] Loading the Global Compliance RAG (FTC Guidelines)...")
15
+
16
+ # Use a universal English model for better retrieval accuracy
17
+ self.device = "mps" if torch.backends.mps.is_available() else ("cuda" if torch.cuda.is_available() else "cpu")
18
+ self.retriever = SentenceTransformer('all-MiniLM-L6-v2', device=self.device)
19
+
20
+ api_key = os.getenv("OPENAI_API_KEY")
21
+ if not api_key:
22
+ print("⚠️ Warning: OPENAI_API_KEY is not set.")
23
+
24
+ self.client = OpenAI(api_key=api_key)
25
+
26
+ # ⚖️ FTC Regulatory Knowledge Base (Negative Option & Dark Patterns)
27
+ self.fact_db = [
28
+ # 1. Disclosure Requirements
29
+ "Material terms of the negative option offer (billing frequency, amount, cancellation) must be disclosed clearly and conspicuously before obtaining billing information.",
30
+ "The disclosure must be placed in close proximity to the request for consent and must not be hidden in fine print or separate terms and conditions pages.",
31
+
32
+ # 2. Express Informed Consent
33
+ "Marketers must obtain the consumer's express informed consent before charging them for a subscription or recurring service.",
34
+ "Deceptive checkboxes or 'pre-checked' boxes for additional paid services are considered unfair and deceptive acts.",
35
+
36
+ # 3. Simple Cancellation (The 'Click-to-Cancel' Rule)
37
+ "The cancellation mechanism must be at least as simple as the mechanism used to initiate the subscription (e.g., if you join online, you must be able to cancel online).",
38
+ "Failing to provide a simple method to stop recurring charges is a direct violation of the ROSCA (Restore Online Shoppers' Confidence Act).",
39
+
40
+ # 4. Misleading Pricing (Bait & Switch)
41
+ "Promising a 'Free' or '$0' trial without disclosing that a full-price subscription begins automatically after a short period is a deceptive practice.",
42
+ "Requiring credit card information for a 'free' service must be accompanied by a clear explanation of when and why the card will be charged."
43
+ ]
44
+
45
+ # Pre-calculate DB embeddings
46
+ self.db_embeddings = self.retriever.encode(self.fact_db, convert_to_tensor=True)
47
+
48
+ def calculate_x3_score(self, text):
49
+ """Calculate a regulatory non-compliance score based on FTC guidelines via RAG."""
50
+ if not text or len(text.strip()) < 10:
51
+ return 0.0, "Insufficient text provided for compliance audit."
52
+
53
+ try:
54
+ # 1. Retrieval: Find the most relevant FTC guideline
55
+ query_embedding = self.retriever.encode(text, convert_to_tensor=True)
56
+ cosine_scores = util.cos_sim(query_embedding, self.db_embeddings)[0]
57
+ best_idx = torch.argmax(cosine_scores).item()
58
+ retrieved_fact = self.fact_db[best_idx]
59
+
60
+ # 2. Generation: GPT Audit
61
+ prompt = f"""
62
+ You are a senior auditor for the FTC (Federal Trade Commission) specialized in deceptive subscription practices.
63
+ Audit the [Ad/Service Copy] based on the [Relevant Regulation].
64
+
65
+ [Relevant Regulation]: {retrieved_fact}
66
+ [Ad/Service Copy]: {text}
67
+
68
+ Response Format (Strictly follow this):
69
+ Compliance Risk: [Score from 0 to 100, where 100 is a definite violation]
70
+ Specific Violation: [1-2 sentences explaining why it violates or follows the rule]
71
+ Verdict: [CRITICAL / SUSPICIOUS / COMPLIANT]
72
+ """
73
+
74
+ print(" -> 🤖 GPT Auditor is cross-checking with federal regulations...")
75
+ response = self.client.chat.completions.create(
76
+ model="gpt-4o", # Using 4o for better reasoning in English
77
+ messages=[{"role": "user", "content": prompt}],
78
+ temperature=0.0
79
+ )
80
+
81
+ result_text = response.choices[0].message.content
82
+ print(f" [Audit Report]\n{result_text}")
83
+
84
+ # Extract score
85
+ score_match = re.search(r"Compliance Risk:\s*(\d+)", result_text)
86
+ x3_score = float(score_match.group(1)) if score_match else 0.0
87
+
88
+ return x3_score, retrieved_fact
89
+
90
+ except Exception as e:
91
+ print(f"⚠️ Audit Error: {e}")
92
+ return 0.0, "An error occurred during regulatory analysis."
93
+
94
+ if __name__ == "__main__":
95
+ checker = FactCheckerRAG()
96
+ # Test case: Typical subscription trap language
97
+ test_copy = "Get your FREE trial now! $0.00 today. Just enter your credit card to verify your identity."
98
+
99
+ score, fact = checker.calculate_x3_score(test_copy)
100
+ print("-" * 50)
101
+ print(f"Final Non-Compliance Score: {score}")
102
+ print(f"Reference FTC Guideline: {fact}")
myapp/step4_xai.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import xgboost as xgb
3
+ import shap
4
+
5
+ class XAIScorer:
6
+ def __init__(self):
7
+ print("📊 [Step 4] Loading the XAI Scoring Engine (XGBoost + SHAP)...")
8
+
9
+ # 1. Generating synthetic data for PoC training
10
+ # Features: X1 (Lexical Bait), X2 (Semantic Intent), X3 (Regulatory Violation)
11
+ np.random.seed(42)
12
+ X_train = np.random.rand(1000, 3) * 100
13
+
14
+ # Hypothetical labeling logic:
15
+ # In subscription traps, Regulatory non-compliance (X3) and Semantic intent (X2)
16
+ # carry the most weight in determining a 'Deceptive' label.
17
+ # Target: 1 (Deceptive/Trap), 0 (Compliant)
18
+ y_train = ((X_train[:, 0]*0.2 + X_train[:, 1]*0.4 + X_train[:, 2]*0.4) > 50).astype(int)
19
+
20
+ # 2. Define and train the XGBoost Classifier
21
+ # This model acts as the 'decision-making brain' of the auditor.
22
+ self.model = xgb.XGBClassifier(
23
+ n_estimators=50,
24
+ max_depth=3,
25
+ learning_rate=0.1,
26
+ eval_metric='logloss',
27
+ random_state=42
28
+ )
29
+ self.model.fit(X_train, y_train)
30
+
31
+ # 3. Initialize the SHAP TreeExplainer
32
+ # This allows us to see exactly how each feature influenced the final risk score.
33
+ self.explainer = shap.TreeExplainer(self.model)
34
+ print(" -> 🧠 Machine-learning scoring engine is ready (Global Version).")
35
+
36
+ def calculate_final_score_and_explain(self, x1, x2, x3):
37
+ """
38
+ Calculates the probability of being a Dark Pattern and provides an XAI trace.
39
+ x1: Lexical Score, x2: Semantic Score, x3: RAG Compliance Score
40
+ """
41
+ # Convert input scores into a numpy array for the model
42
+ X_input = np.array([[x1, x2, x3]])
43
+
44
+ # =========================================================
45
+ # 1. Final Risk Scoring
46
+ # Predict_proba returns the probability of class 1 (Deceptive)
47
+ # =========================================================
48
+ probabilities = self.model.predict_proba(X_input)
49
+ final_score = probabilities[0][1] * 100 # Scale to 0-100 range
50
+
51
+ # =========================================================
52
+ # 2. XAI (SHAP Value Calculation)
53
+ # SHAP explains the deviation from the base value (average prediction).
54
+ # =========================================================
55
+ shap_values = self.explainer.shap_values(X_input)
56
+
57
+ # Handle different SHAP output formats (list or array)
58
+ if isinstance(shap_values, list):
59
+ # For binary classification, we take the values for the positive class (1)
60
+ shap_vals = shap_values[1][0]
61
+ elif len(shap_values.shape) == 3: # Some versions return (obs, class, features)
62
+ shap_vals = shap_values[0][1]
63
+ else:
64
+ shap_vals = shap_values[0]
65
+
66
+ # Extract the Base Value (The average risk score of the training set)
67
+ if isinstance(self.explainer.expected_value, (list, np.ndarray)):
68
+ base_value = self.explainer.expected_value[1]
69
+ else:
70
+ base_value = self.explainer.expected_value
71
+
72
+ # Note: final_score ≈ (base_value + sum(shap_vals)) * 100
73
+ return final_score, shap_vals, base_value
74
+
75
+ if __name__ == "__main__":
76
+ scorer = XAIScorer()
77
+ # Example: A page with some lexical bait but high semantic deception and regulatory failure
78
+ f_score, s_vals, b_val = scorer.calculate_final_score_and_explain(30.0, 75.0, 85.0)
79
+
80
+ print("\n" + "="*50)
81
+ print(f"Audit Result: {f_score:.2f}% Deception Probability")
82
+ print("-" * 50)
83
+ print(f"Lexical Impact: {s_vals[0]:+.2f}")
84
+ print(f"Semantic Impact: {s_vals[1]:+.2f}")
85
+ print(f"Regulatory Impact: {s_vals[2]:+.2f}")
86
+ print("="*50)
packages.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ ffmpeg
2
+ libgl1
3
+ libglib2.0-0
4
+ libsm6
5
+ libxext6
6
+ libxrender-dev
7
+ libgomp1
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ pydantic
4
+ requests
5
+ playwright
6
+ python-dotenv
7
+ openai
8
+ --extra-index-url https://download.pytorch.org/whl/cpu
9
+ torch
10
+ sentence-transformers
11
+ transformers
12
+ python-mecab-ko
13
+ numpy
14
+ xgboost==1.7.6
15
+ shap
16
+ paddleocr