Spaces:
Configuration error
Configuration error
| """ | |
| BI Storyteller Web Interface | |
| Professional HTTP server with REST API - Standard Library Only | |
| """ | |
| import json | |
| import os | |
| from http.server import HTTPServer, BaseHTTPRequestHandler | |
| from urllib.parse import urlparse, parse_qs | |
| import mimetypes | |
| from main import BIStoryteller | |
| class BIStoryteller_WebHandler(BaseHTTPRequestHandler): | |
| """HTTP request handler for BI Storyteller web interface""" | |
| def __init__(self, *args, **kwargs): | |
| self.bi = BIStoryteller() | |
| super().__init__(*args, **kwargs) | |
| def do_GET(self): | |
| """Handle GET requests""" | |
| if self.path == '/' or self.path == '/index.html': | |
| self.serve_main_page() | |
| elif self.path.startswith('/api/status'): | |
| self.get_status() | |
| else: | |
| self.send_error(404, "Page not found") | |
| def do_POST(self): | |
| """Handle POST requests""" | |
| content_length = int(self.headers.get('Content-Length', 0)) | |
| post_data = self.rfile.read(content_length) | |
| try: | |
| data = json.loads(post_data.decode('utf-8')) | |
| except: | |
| self.send_error(400, "Invalid JSON") | |
| return | |
| if self.path == '/api/set_api_key': | |
| result = self.bi.set_groq_api_key(data.get('api_key', '')) | |
| self._send_json_response(result) | |
| elif self.path == '/api/extract_variables': | |
| result = self.bi.extract_variables(data.get('business_problem', '')) | |
| self._send_json_response(result) | |
| elif self.path == '/api/generate_questionnaire': | |
| result = self.bi.generate_questionnaire( | |
| data.get('variables', []), | |
| data.get('business_problem', '') | |
| ) | |
| self._send_json_response(result) | |
| elif self.path == '/api/generate_data': | |
| result = self.bi.generate_sample_data( | |
| data.get('variables', []), | |
| data.get('sample_size', 1000) | |
| ) | |
| self._send_json_response(result) | |
| elif self.path == '/api/clean_data': | |
| result = self.bi.clean_data(data.get('data', [])) | |
| self._send_json_response(result) | |
| elif self.path == '/api/perform_eda': | |
| result = self.bi.perform_eda(data.get('data', [])) | |
| self._send_json_response(result) | |
| elif self.path == '/api/train_model': | |
| result = self.bi.train_predictive_model( | |
| data.get('data', []), | |
| data.get('algorithm', 'Random Forest') | |
| ) | |
| self._send_json_response(result) | |
| elif self.path == '/api/analyze_trends': | |
| result = self.bi.analyze_trends( | |
| data.get('data', []), | |
| data.get('time_period', 'Monthly') | |
| ) | |
| self._send_json_response(result) | |
| elif self.path == '/api/analyze_sentiment': | |
| result = self.bi.analyze_sentiment(data.get('data', [])) | |
| self._send_json_response(result) | |
| elif self.path == '/api/run_ab_test': | |
| result = self.bi.run_ab_test( | |
| data.get('data', []), | |
| data.get('test_variable', ''), | |
| data.get('success_metric', '') | |
| ) | |
| self._send_json_response(result) | |
| elif self.path == '/api/chat': | |
| result = self.bi.chat_with_data(data.get('question', '')) | |
| self._send_json_response(result) | |
| elif self.path == '/api/export': | |
| result = self.bi.export_results(data.get('filename')) | |
| self._send_json_response(result) | |
| elif self.path == '/api/import': | |
| result = self.bi.import_results(data.get('filename')) | |
| self._send_json_response(result) | |
| else: | |
| self.send_error(404, "API endpoint not found") | |
| def get_status(self): | |
| """Get current analysis status""" | |
| status = { | |
| "modules_completed": [], | |
| "current_step": 1, | |
| "total_steps": 12 | |
| } | |
| if self.bi.variables: | |
| status["modules_completed"].append("Variable Extraction") | |
| status["current_step"] = 2 | |
| if self.bi.questionnaire: | |
| status["modules_completed"].append("Questionnaire Generation") | |
| status["current_step"] = 3 | |
| if self.bi.sample_data: | |
| status["modules_completed"].append("Data Generation") | |
| status["current_step"] = 4 | |
| if self.bi.cleaned_data: | |
| status["modules_completed"].append("Data Cleaning") | |
| status["current_step"] = 5 | |
| if self.bi.eda_results: | |
| status["modules_completed"].append("EDA Analysis") | |
| status["current_step"] = 6 | |
| if self.bi.model_results: | |
| status["modules_completed"].append("Predictive Modeling") | |
| status["current_step"] = 7 | |
| if self.bi.trend_results: | |
| status["modules_completed"].append("Trend Analysis") | |
| status["current_step"] = 8 | |
| if self.bi.sentiment_results: | |
| status["modules_completed"].append("Sentiment Analysis") | |
| status["current_step"] = 9 | |
| if self.bi.ab_test_results: | |
| status["modules_completed"].append("A/B Testing") | |
| status["current_step"] = 10 | |
| if self.bi.chat_history: | |
| status["modules_completed"].append("Chat Interface") | |
| status["current_step"] = 11 | |
| self._send_json_response(status) | |
| def serve_main_page(self): | |
| """Serve the main HTML page""" | |
| html_content = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>BI Storyteller - Marketing Analysis Platform</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| color: #333; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| .header { | |
| text-align: center; | |
| color: white; | |
| margin-bottom: 30px; | |
| } | |
| .header h1 { | |
| font-size: 2.5rem; | |
| margin-bottom: 10px; | |
| font-weight: 700; | |
| } | |
| .header p { | |
| font-size: 1.2rem; | |
| opacity: 0.9; | |
| } | |
| .workflow-container { | |
| background: white; | |
| border-radius: 20px; | |
| padding: 30px; | |
| box-shadow: 0 20px 40px rgba(0,0,0,0.1); | |
| } | |
| .progress-bar { | |
| width: 100%; | |
| height: 8px; | |
| background: #e0e0e0; | |
| border-radius: 4px; | |
| margin-bottom: 30px; | |
| overflow: hidden; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, #667eea, #764ba2); | |
| width: 8.33%; | |
| transition: width 0.3s ease; | |
| } | |
| .module { | |
| border: 2px solid #e0e0e0; | |
| border-radius: 12px; | |
| padding: 20px; | |
| margin-bottom: 20px; | |
| transition: all 0.3s ease; | |
| } | |
| .module.active { | |
| border-color: #667eea; | |
| background: #f8f9ff; | |
| } | |
| .module.completed { | |
| border-color: #4caf50; | |
| background: #f1f8e9; | |
| } | |
| .module h3 { | |
| color: #333; | |
| margin-bottom: 15px; | |
| font-size: 1.3rem; | |
| } | |
| .form-group { | |
| margin-bottom: 15px; | |
| } | |
| .form-group label { | |
| display: block; | |
| margin-bottom: 5px; | |
| font-weight: 600; | |
| color: #555; | |
| } | |
| .form-group input, | |
| .form-group textarea, | |
| .form-group select { | |
| width: 100%; | |
| padding: 12px; | |
| border: 2px solid #e0e0e0; | |
| border-radius: 8px; | |
| font-size: 16px; | |
| transition: border-color 0.3s ease; | |
| } | |
| .form-group input:focus, | |
| .form-group textarea:focus, | |
| .form-group select:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| } | |
| .btn { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border: none; | |
| padding: 12px 24px; | |
| border-radius: 8px; | |
| font-size: 16px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: transform 0.2s ease; | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| } | |
| .btn:disabled { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .results { | |
| background: #f8f9fa; | |
| border-radius: 8px; | |
| padding: 15px; | |
| margin-top: 15px; | |
| border-left: 4px solid #667eea; | |
| } | |
| .results pre { | |
| white-space: pre-wrap; | |
| font-family: 'Monaco', 'Consolas', monospace; | |
| font-size: 14px; | |
| line-height: 1.4; | |
| } | |
| .status { | |
| text-align: center; | |
| padding: 10px; | |
| margin-bottom: 20px; | |
| border-radius: 8px; | |
| font-weight: 600; | |
| } | |
| .status.success { | |
| background: #d4edda; | |
| color: #155724; | |
| border: 1px solid #c3e6cb; | |
| } | |
| .status.error { | |
| background: #f8d7da; | |
| color: #721c24; | |
| border: 1px solid #f5c6cb; | |
| } | |
| .hidden { | |
| display: none; | |
| } | |
| .grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 20px; | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 10px; | |
| } | |
| .header h1 { | |
| font-size: 2rem; | |
| } | |
| .workflow-container { | |
| padding: 20px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>π BI Storyteller</h1> | |
| <p>Marketing Analysis Automation Platform</p> | |
| </div> | |
| <div class="workflow-container"> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="progressFill"></div> | |
| </div> | |
| <div id="statusMessage" class="status hidden"></div> | |
| <!-- Module 1: API Key Setup --> | |
| <div class="module active" id="module1"> | |
| <h3>π Step 1: API Key Setup (Optional)</h3> | |
| <div class="form-group"> | |
| <label for="apiKey">Groq API Key (Leave empty for offline mode):</label> | |
| <input type="password" id="apiKey" placeholder="Enter your Groq API key..."> | |
| </div> | |
| <button class="btn" onclick="setApiKey()">Set API Key & Continue</button> | |
| </div> | |
| <!-- Module 2: Variable Extraction --> | |
| <div class="module" id="module2"> | |
| <h3>π Step 2: Variable Extraction</h3> | |
| <div class="form-group"> | |
| <label for="businessProblem">Describe Your Business Problem:</label> | |
| <textarea id="businessProblem" rows="4" placeholder="e.g., We want to improve customer retention and increase purchase frequency..."></textarea> | |
| </div> | |
| <button class="btn" onclick="extractVariables()">Extract Variables</button> | |
| <div id="variablesResult" class="results hidden"></div> | |
| </div> | |
| <!-- Module 3: Questionnaire Generation --> | |
| <div class="module" id="module3"> | |
| <h3>π Step 3: Generate Questionnaire</h3> | |
| <p>Generate survey questions based on extracted variables.</p> | |
| <button class="btn" onclick="generateQuestionnaire()">Generate Questionnaire</button> | |
| <div id="questionnaireResult" class="results hidden"></div> | |
| </div> | |
| <!-- Module 4: Data Generation --> | |
| <div class="module" id="module4"> | |
| <h3>π’ Step 4: Generate Sample Data</h3> | |
| <div class="form-group"> | |
| <label for="sampleSize">Sample Size:</label> | |
| <select id="sampleSize"> | |
| <option value="100">100 records</option> | |
| <option value="500" selected>500 records</option> | |
| <option value="1000">1,000 records</option> | |
| <option value="5000">5,000 records</option> | |
| </select> | |
| </div> | |
| <button class="btn" onclick="generateData()">Generate Sample Data</button> | |
| <div id="dataResult" class="results hidden"></div> | |
| </div> | |
| <!-- Module 5: Data Cleaning --> | |
| <div class="module" id="module5"> | |
| <h3>π§Ή Step 5: Clean Data</h3> | |
| <p>Remove outliers, handle missing values, and preprocess data.</p> | |
| <button class="btn" onclick="cleanData()">Clean Data</button> | |
| <div id="cleaningResult" class="results hidden"></div> | |
| </div> | |
| <!-- Module 6: EDA --> | |
| <div class="module" id="module6"> | |
| <h3>π Step 6: Exploratory Data Analysis</h3> | |
| <p>Perform statistical analysis and generate insights.</p> | |
| <button class="btn" onclick="performEDA()">Perform EDA</button> | |
| <div id="edaResult" class="results hidden"></div> | |
| </div> | |
| <!-- Module 7: Predictive Analytics --> | |
| <div class="module" id="module7"> | |
| <h3>π€ Step 7: Predictive Analytics</h3> | |
| <div class="form-group"> | |
| <label for="algorithm">Algorithm:</label> | |
| <select id="algorithm"> | |
| <option value="Random Forest">Random Forest</option> | |
| <option value="Logistic Regression">Logistic Regression</option> | |
| <option value="SVM">Support Vector Machine</option> | |
| <option value="Neural Network">Neural Network</option> | |
| </select> | |
| </div> | |
| <button class="btn" onclick="trainModel()">Train Model</button> | |
| <div id="modelResult" class="results hidden"></div> | |
| </div> | |
| <!-- Module 8: Trend Analysis --> | |
| <div class="module" id="module8"> | |
| <h3>π Step 8: Trend Analysis</h3> | |
| <div class="form-group"> | |
| <label for="timePeriod">Time Period:</label> | |
| <select id="timePeriod"> | |
| <option value="Daily">Daily</option> | |
| <option value="Weekly">Weekly</option> | |
| <option value="Monthly" selected>Monthly</option> | |
| </select> | |
| </div> | |
| <button class="btn" onclick="analyzeTrends()">Analyze Trends</button> | |
| <div id="trendResult" class="results hidden"></div> | |
| </div> | |
| <!-- Module 9: Sentiment Analysis --> | |
| <div class="module" id="module9"> | |
| <h3>π Step 9: Sentiment Analysis</h3> | |
| <p>Analyze customer feedback and sentiment patterns.</p> | |
| <button class="btn" onclick="analyzeSentiment()">Analyze Sentiment</button> | |
| <div id="sentimentResult" class="results hidden"></div> | |
| </div> | |
| <!-- Module 10: A/B Testing --> | |
| <div class="module" id="module10"> | |
| <h3>π§ͺ Step 10: A/B Testing</h3> | |
| <div class="grid"> | |
| <div class="form-group"> | |
| <label for="testVariable">Test Variable:</label> | |
| <input type="text" id="testVariable" placeholder="e.g., channel"> | |
| </div> | |
| <div class="form-group"> | |
| <label for="successMetric">Success Metric:</label> | |
| <input type="text" id="successMetric" placeholder="e.g., conversion_rate"> | |
| </div> | |
| </div> | |
| <button class="btn" onclick="runABTest()">Run A/B Test</button> | |
| <div id="abTestResult" class="results hidden"></div> | |
| </div> | |
| <!-- Module 11: Chat Interface --> | |
| <div class="module" id="module11"> | |
| <h3>π¬ Step 11: Chat with Your Data</h3> | |
| <div class="form-group"> | |
| <label for="chatQuestion">Ask a Question:</label> | |
| <input type="text" id="chatQuestion" placeholder="e.g., What are the key factors driving customer satisfaction?"> | |
| </div> | |
| <button class="btn" onclick="chatWithData()">Ask Question</button> | |
| <div id="chatResult" class="results hidden"></div> | |
| </div> | |
| <!-- Module 12: Export --> | |
| <div class="module" id="module12"> | |
| <h3>π€ Step 12: Export Results</h3> | |
| <div class="grid"> | |
| <div> | |
| <button class="btn" onclick="exportResults()">Export Analysis (JSON)</button> | |
| </div> | |
| <div> | |
| <button class="btn" onclick="exportCSV('cleaned')">Export Cleaned Data (CSV)</button> | |
| </div> | |
| </div> | |
| <div id="exportResult" class="results hidden"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| let currentStep = 1; | |
| let analysisData = {}; | |
| function updateProgress() { | |
| const progressFill = document.getElementById('progressFill'); | |
| const percentage = (currentStep / 12) * 100; | |
| progressFill.style.width = percentage + '%'; | |
| } | |
| function showStatus(message, isError = false) { | |
| const statusEl = document.getElementById('statusMessage'); | |
| statusEl.textContent = message; | |
| statusEl.className = `status ${isError ? 'error' : 'success'}`; | |
| statusEl.classList.remove('hidden'); | |
| setTimeout(() => statusEl.classList.add('hidden'), 5000); | |
| } | |
| function activateModule(moduleNum) { | |
| // Deactivate all modules | |
| document.querySelectorAll('.module').forEach(m => { | |
| m.classList.remove('active'); | |
| }); | |
| // Mark completed modules | |
| for (let i = 1; i < moduleNum; i++) { | |
| document.getElementById(`module${i}`).classList.add('completed'); | |
| } | |
| // Activate current module | |
| document.getElementById(`module${moduleNum}`).classList.add('active'); | |
| currentStep = moduleNum; | |
| updateProgress(); | |
| } | |
| async function apiCall(endpoint, data = {}) { | |
| try { | |
| const response = await fetch(`/api/${endpoint}`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(data) | |
| }); | |
| return await response.json(); | |
| } catch (error) { | |
| console.error('API call failed:', error); | |
| return { success: false, error: error.message }; | |
| } | |
| } | |
| async function setApiKey() { | |
| const apiKey = document.getElementById('apiKey').value; | |
| const result = await apiCall('set_api_key', { api_key: apiKey }); | |
| if (result.success) { | |
| showStatus(result.message); | |
| activateModule(2); | |
| } else { | |
| showStatus(result.error || 'Failed to set API key', true); | |
| } | |
| } | |
| async function extractVariables() { | |
| const businessProblem = document.getElementById('businessProblem').value; | |
| if (!businessProblem.trim()) { | |
| showStatus('Please describe your business problem', true); | |
| return; | |
| } | |
| const result = await apiCall('extract_variables', { business_problem: businessProblem }); | |
| if (result.success) { | |
| analysisData.variables = result.variables; | |
| analysisData.businessProblem = businessProblem; | |
| document.getElementById('variablesResult').innerHTML = ` | |
| <pre>β Extracted Variables: | |
| ${result.variables.map(v => `β’ ${v.replace('_', ' ')}`).join('\\n')} | |
| Extraction Method: ${result.extraction_method} | |
| Confidence: ${(result.confidence * 100).toFixed(1)}%</pre> | |
| `; | |
| document.getElementById('variablesResult').classList.remove('hidden'); | |
| showStatus(`Successfully extracted ${result.variables.length} variables`); | |
| activateModule(3); | |
| } else { | |
| showStatus(result.error || 'Failed to extract variables', true); | |
| } | |
| } | |
| async function generateQuestionnaire() { | |
| if (!analysisData.variables) { | |
| showStatus('Please extract variables first', true); | |
| return; | |
| } | |
| const result = await apiCall('generate_questionnaire', { | |
| variables: analysisData.variables, | |
| business_problem: analysisData.businessProblem | |
| }); | |
| if (result.success) { | |
| analysisData.questionnaire = result.questionnaire; | |
| document.getElementById('questionnaireResult').innerHTML = ` | |
| <pre>β Generated Questionnaire: | |
| Total Questions: ${result.total_questions} | |
| Estimated Time: ${result.estimated_time} | |
| Sample Questions: | |
| ${result.questionnaire.slice(0, 3).map((q, i) => `${i+1}. ${q.question}`).join('\\n')}</pre> | |
| `; | |
| document.getElementById('questionnaireResult').classList.remove('hidden'); | |
| showStatus(`Generated ${result.total_questions} survey questions`); | |
| activateModule(4); | |
| } else { | |
| showStatus(result.error || 'Failed to generate questionnaire', true); | |
| } | |
| } | |
| async function generateData() { | |
| if (!analysisData.variables) { | |
| showStatus('Please extract variables first', true); | |
| return; | |
| } | |
| const sampleSize = parseInt(document.getElementById('sampleSize').value); | |
| const result = await apiCall('generate_data', { | |
| variables: analysisData.variables, | |
| sample_size: sampleSize | |
| }); | |
| if (result.success) { | |
| analysisData.sampleData = result.data; | |
| document.getElementById('dataResult').innerHTML = ` | |
| <pre>β Sample Data Generated: | |
| Records: ${result.sample_size} | |
| Variables: ${result.variables.length} | |
| Method: ${result.generation_method} | |
| Sample Record: | |
| ${JSON.stringify(result.data[0], null, 2)}</pre> | |
| `; | |
| document.getElementById('dataResult').classList.remove('hidden'); | |
| showStatus(`Generated ${result.sample_size} sample records`); | |
| activateModule(5); | |
| } else { | |
| showStatus(result.error || 'Failed to generate data', true); | |
| } | |
| } | |
| async function cleanData() { | |
| if (!analysisData.sampleData) { | |
| showStatus('Please generate sample data first', true); | |
| return; | |
| } | |
| const result = await apiCall('clean_data', { data: analysisData.sampleData }); | |
| if (result.success) { | |
| analysisData.cleanedData = result.cleaned_data; | |
| document.getElementById('cleaningResult').innerHTML = ` | |
| <pre>β Data Cleaning Complete: | |
| Original Records: ${result.original_size} | |
| Cleaned Records: ${result.cleaned_size} | |
| Outliers Removed: ${result.removed_outliers} | |
| Data Quality: ${((result.cleaned_size / result.original_size) * 100).toFixed(1)}%</pre> | |
| `; | |
| document.getElementById('cleaningResult').classList.remove('hidden'); | |
| showStatus(`Data cleaned: ${result.cleaned_size} records ready for analysis`); | |
| activateModule(6); | |
| } else { | |
| showStatus(result.error || 'Failed to clean data', true); | |
| } | |
| } | |
| async function performEDA() { | |
| if (!analysisData.cleanedData) { | |
| showStatus('Please clean data first', true); | |
| return; | |
| } | |
| const result = await apiCall('perform_eda', { data: analysisData.cleanedData }); | |
| if (result.success) { | |
| analysisData.edaResults = result.results; | |
| const correlations = Object.entries(result.results.correlations || {}) | |
| .map(([pair, corr]) => `${pair}: ${corr}`) | |
| .slice(0, 5) | |
| .join('\\n'); | |
| document.getElementById('edaResult').innerHTML = ` | |
| <pre>β EDA Analysis Complete: | |
| Variables Analyzed: ${Object.keys(result.results.descriptive_stats || {}).length} | |
| Key Insights: ${result.results.insights.length} | |
| Top Correlations: | |
| ${correlations} | |
| Key Insights: | |
| ${result.results.insights.map(insight => `β’ ${insight}`).join('\\n')}</pre> | |
| `; | |
| document.getElementById('edaResult').classList.remove('hidden'); | |
| showStatus('EDA analysis completed with key insights generated'); | |
| activateModule(7); | |
| } else { | |
| showStatus(result.error || 'Failed to perform EDA', true); | |
| } | |
| } | |
| async function trainModel() { | |
| if (!analysisData.cleanedData) { | |
| showStatus('Please clean data first', true); | |
| return; | |
| } | |
| const algorithm = document.getElementById('algorithm').value; | |
| const result = await apiCall('train_model', { | |
| data: analysisData.cleanedData, | |
| algorithm: algorithm | |
| }); | |
| if (result.success) { | |
| analysisData.modelResults = result.results; | |
| const featureImportance = Object.entries(result.results.feature_importance || {}) | |
| .sort(([,a], [,b]) => b - a) | |
| .slice(0, 5) | |
| .map(([feature, importance]) => `${feature}: ${(importance * 100).toFixed(1)}%`) | |
| .join('\\n'); | |
| document.getElementById('modelResult').innerHTML = ` | |
| <pre>β Predictive Model Trained: | |
| Algorithm: ${result.results.algorithm} | |
| Accuracy: ${(result.results.metrics.accuracy * 100).toFixed(1)}% | |
| Precision: ${(result.results.metrics.precision * 100).toFixed(1)}% | |
| Recall: ${(result.results.metrics.recall * 100).toFixed(1)}% | |
| Top Feature Importance: | |
| ${featureImportance}</pre> | |
| `; | |
| document.getElementById('modelResult').classList.remove('hidden'); | |
| showStatus(`Model trained with ${(result.results.metrics.accuracy * 100).toFixed(1)}% accuracy`); | |
| activateModule(8); | |
| } else { | |
| showStatus(result.error || 'Failed to train model', true); | |
| } | |
| } | |
| async function analyzeTrends() { | |
| if (!analysisData.cleanedData) { | |
| showStatus('Please clean data first', true); | |
| return; | |
| } | |
| const timePeriod = document.getElementById('timePeriod').value; | |
| const result = await apiCall('analyze_trends', { | |
| data: analysisData.cleanedData, | |
| time_period: timePeriod | |
| }); | |
| if (result.success) { | |
| analysisData.trendResults = result.results; | |
| const trends = Object.entries(result.results.trends || {}) | |
| .map(([variable, trend]) => `${variable}: ${trend.direction} (slope: ${trend.slope})`) | |
| .slice(0, 5) | |
| .join('\\n'); | |
| document.getElementById('trendResult').innerHTML = ` | |
| <pre>β Trend Analysis Complete: | |
| Time Period: ${result.results.time_period} | |
| Analysis Periods: ${result.results.analysis_periods} | |
| Key Trends: | |
| ${trends}</pre> | |
| `; | |
| document.getElementById('trendResult').classList.remove('hidden'); | |
| showStatus('Trend analysis completed with forecasts generated'); | |
| activateModule(9); | |
| } else { | |
| showStatus(result.error || 'Failed to analyze trends', true); | |
| } | |
| } | |
| async function analyzeSentiment() { | |
| if (!analysisData.cleanedData) { | |
| showStatus('Please clean data first', true); | |
| return; | |
| } | |
| const result = await apiCall('analyze_sentiment', { data: analysisData.cleanedData }); | |
| if (result.success) { | |
| analysisData.sentimentResults = result.results; | |
| const distribution = Object.entries(result.results.sentiment_distribution || {}) | |
| .map(([sentiment, percentage]) => `${sentiment}: ${percentage}%`) | |
| .join('\\n'); | |
| document.getElementById('sentimentResult').innerHTML = ` | |
| <pre>β Sentiment Analysis Complete: | |
| Total Responses Analyzed: ${result.results.total_analyzed} | |
| Dominant Sentiment: ${result.results.dominant_sentiment} | |
| Sentiment Distribution: | |
| ${distribution} | |
| Analysis Method: ${result.results.analysis_method}</pre> | |
| `; | |
| document.getElementById('sentimentResult').classList.remove('hidden'); | |
| showStatus(`Sentiment analysis: ${result.results.dominant_sentiment} sentiment dominates`); | |
| activateModule(10); | |
| } else { | |
| showStatus(result.error || 'Failed to analyze sentiment', true); | |
| } | |
| } | |
| async function runABTest() { | |
| if (!analysisData.cleanedData) { | |
| showStatus('Please clean data first', true); | |
| return; | |
| } | |
| const testVariable = document.getElementById('testVariable').value; | |
| const successMetric = document.getElementById('successMetric').value; | |
| if (!testVariable || !successMetric) { | |
| showStatus('Please specify both test variable and success metric', true); | |
| return; | |
| } | |
| const result = await apiCall('run_ab_test', { | |
| data: analysisData.cleanedData, | |
| test_variable: testVariable, | |
| success_metric: successMetric | |
| }); | |
| if (result.success) { | |
| analysisData.abTestResults = result.results; | |
| document.getElementById('abTestResult').innerHTML = ` | |
| <pre>β A/B Test Analysis Complete: | |
| Group A: ${result.results.group_a.size} users, ${(result.results.group_a.success_rate * 100).toFixed(1)}% success rate | |
| Group B: ${result.results.group_b.size} users, ${(result.results.group_b.success_rate * 100).toFixed(1)}% success rate | |
| Statistical Test: | |
| Z-Score: ${result.results.statistical_test.z_score} | |
| P-Value: ${result.results.statistical_test.p_value} | |
| Significant: ${result.results.statistical_test.is_significant ? 'Yes' : 'No'} | |
| Conclusion: ${result.results.conclusion.winner} | |
| Lift: ${result.results.conclusion.lift}%</pre> | |
| `; | |
| document.getElementById('abTestResult').classList.remove('hidden'); | |
| showStatus(`A/B test completed: ${result.results.conclusion.winner}`); | |
| activateModule(11); | |
| } else { | |
| showStatus(result.error || 'Failed to run A/B test', true); | |
| } | |
| } | |
| async function chatWithData() { | |
| const question = document.getElementById('chatQuestion').value; | |
| if (!question.trim()) { | |
| showStatus('Please enter a question', true); | |
| return; | |
| } | |
| const result = await apiCall('chat', { question: question }); | |
| if (result.success) { | |
| document.getElementById('chatResult').innerHTML = ` | |
| <pre>β Question: ${question} | |
| π€ Response: ${result.response} | |
| Context Used: ${result.context_used} analysis modules</pre> | |
| `; | |
| document.getElementById('chatResult').classList.remove('hidden'); | |
| document.getElementById('chatQuestion').value = ''; | |
| showStatus('Chat response generated'); | |
| activateModule(12); | |
| } else { | |
| showStatus(result.error || 'Failed to get chat response', true); | |
| } | |
| } | |
| async function exportResults() { | |
| const result = await apiCall('export', {}); | |
| if (result.success) { | |
| document.getElementById('exportResult').innerHTML = ` | |
| <pre>β Analysis Exported Successfully: | |
| Filename: ${result.filename} | |
| Modules Completed: ${result.modules_completed} | |
| File Size: ${(result.file_size / 1024).toFixed(1)} KB | |
| Your complete analysis has been saved and can be shared or imported later.</pre> | |
| `; | |
| document.getElementById('exportResult').classList.remove('hidden'); | |
| showStatus(`Analysis exported to ${result.filename}`); | |
| } else { | |
| showStatus(result.error || 'Failed to export results', true); | |
| } | |
| } | |
| async function exportCSV(dataType) { | |
| const result = await apiCall('export_csv', { data_type: dataType }); | |
| if (result.success) { | |
| showStatus(`${dataType} data exported to ${result.filename}`); | |
| } else { | |
| showStatus(result.error || 'Failed to export CSV', true); | |
| } | |
| } | |
| // Initialize | |
| updateProgress(); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| self.send_response(200) | |
| self.send_header('Content-type', 'text/html') | |
| self.end_headers() | |
| self.wfile.write(html_content.encode()) | |
| def _send_json_response(self, data): | |
| """Send JSON response""" | |
| self.send_response(200) | |
| self.send_header('Content-type', 'application/json') | |
| self.send_header('Access-Control-Allow-Origin', '*') | |
| self.end_headers() | |
| self.wfile.write(json.dumps(data).encode()) | |
| def log_message(self, format, *args): | |
| """Override to reduce log noise""" | |
| pass | |
| def start_web_server(port=8000): | |
| """Start the web server""" | |
| server_address = ('', port) | |
| httpd = HTTPServer(server_address, BIStoryteller_WebHandler) | |
| print(f"π BI Storyteller Web Interface Starting...") | |
| print(f"π Server running at: http://localhost:{port}") | |
| print(f"π§ Standard Library Only - No External Dependencies") | |
| print(f"β‘ Ready for Marketing Analysis Automation!") | |
| print("\n" + "="*50) | |
| print("Press Ctrl+C to stop the server") | |
| print("="*50) | |
| try: | |
| httpd.serve_forever() | |
| except KeyboardInterrupt: | |
| print("\nπ Server stopped by user") | |
| httpd.server_close() | |
| if __name__ == "__main__": | |
| start_web_server() |