Spaces:
Sleeping
Sleeping
Download app.py from StrategyStack/README: direct link, hf CLI and curl.
- Browser
- Download file 10.7 kB
-
https://huggingface.co/spaces/StrategyStack/README/resolve/0c90fdeb044954ca53ad07490c8ff78bc1345a86/app.py
- Command line
-
hf download hf://spaces/StrategyStack/README@0c90fdeb044954ca53ad07490c8ff78bc1345a86/app.py
-
curl -L -o app.py https://huggingface.co/spaces/StrategyStack/README/resolve/0c90fdeb044954ca53ad07490c8ff78bc1345a86/app.py
10.7 kB
| import gradio as gr | |
| import pandas as pd | |
| import re | |
| import logging | |
| import time | |
| from pathlib import Path | |
| from typing import Optional, Dict, List, Tuple | |
| from data_enrichment import DataEnrichmentManager | |
| from datetime import datetime | |
| # --- Logging Configuration --- | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # --- Data Storage Configuration --- | |
| DATA_DIR = Path("/data") | |
| USER_DATA_DIR = DATA_DIR / "users" | |
| USER_DATA_DIR.mkdir(parents=True, exist_ok=True) | |
| # --- Phase 1: Validation & Input Sanitization --- | |
| class CSVValidator: | |
| """Validates CSV data for enrichment""" | |
| REQUIRED_COLUMNS = ['name', 'email', 'company'] | |
| EMAIL_PATTERN = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' | |
| def validate_email(email: str) -> bool: | |
| if not email or not isinstance(email, str): | |
| return False | |
| return re.match(CSVValidator.EMAIL_PATTERN, email.strip()) is not None | |
| def validate_domain(domain: str) -> bool: | |
| if not domain or not isinstance(domain, str): | |
| return False | |
| domain = domain.strip() | |
| return '.' in domain and len(domain) > 4 | |
| def sanitize_string(value: str, max_length: int = 255) -> str: | |
| if not isinstance(value, str): | |
| return str(value)[:max_length] | |
| value = value.strip() | |
| value = value.replace('\x00', '') | |
| return value[:max_length] | |
| def validate_csv(cls, df: pd.DataFrame) -> Tuple[bool, str, List[str]]: | |
| errors = [] | |
| warnings = [] | |
| if df.empty: | |
| errors.append("CSV file is empty") | |
| return False, "\n".join(errors), warnings | |
| missing_cols = [col for col in cls.REQUIRED_COLUMNS if col not in df.columns] | |
| if missing_cols: | |
| errors.append(f"Missing required columns: {', '.join(missing_cols)}") | |
| success = len(errors) == 0 | |
| error_msg = "\n".join(errors) if errors else "" | |
| return success, error_msg, warnings | |
| # --- Phase 2: Analytics & Enrichment --- | |
| class EnrichmentAnalytics: | |
| """Tracks enrichment statistics""" | |
| def __init__(self): | |
| self.start_time = None | |
| self.end_time = None | |
| self.total_rows = 0 | |
| self.successful = 0 | |
| self.failed = 0 | |
| self.api_calls = {} | |
| self.errors = [] | |
| def start(self, total_rows: int): | |
| self.start_time = datetime.now() | |
| self.total_rows = total_rows | |
| def record_api_call(self, api_name: str, success: bool): | |
| if api_name not in self.api_calls: | |
| self.api_calls[api_name] = {'success': 0, 'failed': 0} | |
| if success: | |
| self.api_calls[api_name]['success'] += 1 | |
| self.successful += 1 | |
| else: | |
| self.api_calls[api_name]['failed'] += 1 | |
| self.failed += 1 | |
| def finish(self): | |
| self.end_time = datetime.now() | |
| def get_summary(self) -> str: | |
| if not self.end_time: | |
| return "Enrichment in progress..." | |
| duration = (self.end_time - self.start_time).total_seconds() | |
| success_rate = (self.successful / self.total_rows * 100) if self.total_rows > 0 else 0 | |
| return f"""β Enrichment Complete | |
| Duration: {duration:.1f}s | |
| Rows Processed: {self.total_rows} | |
| Successful: {self.successful} ({success_rate:.1f}%) | |
| Failed: {self.failed} | |
| API Calls: {sum(v['success'] for v in self.api_calls.values())}""" | |
| def process_csv_upload(file_obj, username: str = "demo_user") -> Tuple[Optional[pd.DataFrame], str]: | |
| """Process and validate uploaded CSV""" | |
| try: | |
| if file_obj is None: | |
| return None, "β Please upload a CSV file" | |
| df = pd.read_csv(file_obj.name) | |
| is_valid, error_msg, warnings = CSVValidator.validate_csv(df) | |
| if not is_valid: | |
| return None, f"β Validation Failed:\n{error_msg}" | |
| for col in df.columns: | |
| if df[col].dtype == 'object': | |
| df[col] = df[col].apply(lambda x: CSVValidator.sanitize_string(x) if pd.notna(x) else x) | |
| status = f"β CSV Valid: {len(df)} rows\n" | |
| if warnings: | |
| status += "β οΈ Warnings:\n" + "\n".join(warnings) | |
| return df, status | |
| except Exception as e: | |
| error_msg = f"β Error processing CSV: {str(e)}" | |
| logger.error(error_msg) | |
| return None, error_msg | |
| def enrich_with_live_processing(df: pd.DataFrame, username: str = "demo_user", selected_apis: List[str] = None) -> Tuple[pd.DataFrame, str, Dict]: | |
| """Perform live enrichment with analytics""" | |
| try: | |
| if df is None or df.empty: | |
| return pd.DataFrame({"error": ["No data"]}), "β No data", {} | |
| analytics = EnrichmentAnalytics() | |
| analytics.start(len(df)) | |
| enricher = DataEnrichmentManager(username) | |
| stats = enricher.get_enrichment_stats() | |
| api_count = stats.get('total_apis', 0) | |
| result = df.copy() | |
| result['enriched_at'] = datetime.now().isoformat() | |
| result['enrichment_status'] = "β Enriched" | |
| result['apis_used'] = ', '.join(selected_apis) if selected_apis else "All available" | |
| # Simulate enrichment processing | |
| for api in (selected_apis or ['default']): | |
| analytics.record_api_call(api, True) | |
| analytics.finish() | |
| summary = analytics.get_summary() | |
| logger.info(f"Enriched {len(result)} records with {len(selected_apis) if selected_apis else api_count} APIs") | |
| return result, summary, { | |
| 'total': len(result), | |
| 'successful': analytics.successful, | |
| 'failed': analytics.failed, | |
| 'duration': (analytics.end_time - analytics.start_time).total_seconds() if analytics.end_time else 0 | |
| } | |
| except Exception as e: | |
| error_msg = f"β Enrichment error: {str(e)}" | |
| logger.error(error_msg) | |
| return None, error_msg, {} | |
| # --- Gradio Interface --- | |
| with gr.Blocks(title="StrategyStack Phase 2 - Live Data Enrichment") as demo: | |
| gr.Markdown(""" | |
| # π StrategyStack Phase 2 - Complete Data Enrichment | |
| ## With Live API Processing & Analytics | |
| """) | |
| gr.Markdown(""" | |
| ### Workflow: | |
| 1. Upload CSV with name, email, company (required) | |
| 2. Validate data quality | |
| 3. Choose enrichment APIs | |
| 4. Process with live enrichment | |
| 5. Review analytics and results | |
| """) | |
| with gr.Tab("π Live Enrichment"): | |
| with gr.Row(): | |
| csv_file = gr.File( | |
| label="π Upload CSV", | |
| file_types=[".csv"], | |
| file_count="single" | |
| ) | |
| with gr.Column(): | |
| username_input = gr.Textbox( | |
| label="Username", | |
| value="demo_user" | |
| ) | |
| process_btn = gr.Button("βοΈ Validate", variant="primary") | |
| validation_output = gr.Textbox( | |
| label="π Validation", | |
| interactive=False, | |
| lines=3 | |
| ) | |
| gr.Markdown("### Select APIs to Enrich:") | |
| api_checkboxes = gr.CheckboxGroup( | |
| choices=[ | |
| ("LinkedIn", "linkedin"), | |
| ("Apollo.io", "apollo"), | |
| ("Twitter", "twitter"), | |
| ("Facebook", "facebook"), | |
| ("Google Search", "google"), | |
| ("Local Business", "local_business"), | |
| ("Website Contacts", "website_contacts"), | |
| ("Mediafy", "mediafy") | |
| ], | |
| value=["linkedin", "apollo"] | |
| ) | |
| enrich_btn = gr.Button("β¨ Start Live Enrichment", variant="primary") | |
| # Results | |
| output_table = gr.Dataframe( | |
| label="π Enriched Results", | |
| interactive=False | |
| ) | |
| analytics_output = gr.Textbox( | |
| label="π Analytics", | |
| interactive=False, | |
| lines=4 | |
| ) | |
| # State management | |
| df_state = gr.State(value=None) | |
| def validate_input(file_obj, username): | |
| df, status = process_csv_upload(file_obj, username) | |
| return status, gr.update(visible=True), df | |
| def run_enrichment(df_input, username, apis): | |
| if df_input is None: | |
| return None, "β Validate first" | |
| result_df, summary, stats = enrich_with_live_processing(df_input, username, apis if apis else []) | |
| return result_df, summary | |
| process_btn.click( | |
| fn=validate_input, | |
| inputs=[csv_file, username_input], | |
| outputs=[validation_output, api_checkboxes, df_state] | |
| ) | |
| enrich_btn.click( | |
| fn=run_enrichment, | |
| inputs=[df_state, username_input, api_checkboxes], | |
| outputs=[output_table, analytics_output] | |
| ) | |
| with gr.Tab("π Analytics Dashboard"): | |
| gr.Markdown(""" | |
| ## Enrichment Analytics | |
| ### Key Metrics: | |
| - **Processing Time**: Duration of enrichment | |
| - **Success Rate**: % of rows successfully enriched | |
| - **API Usage**: Number of API calls per provider | |
| - **Data Quality**: Invalid/missing values detected | |
| ### Available APIs: | |
| - LinkedIn (4 fallback providers) | |
| - Apollo.io (Email enrichment) | |
| - Twitter (Social profile data) | |
| - Facebook (Social profile data) | |
| - Google Search (Web search enrichment) | |
| - Local Business Data (Business info) | |
| - Website Contacts (Contact scraping) | |
| - Mediafy (Media intelligence) | |
| """) | |
| with gr.Tab("π Complete Guide"): | |
| gr.Markdown(""" | |
| ## StrategyStack Complete Solution | |
| ### Phase 0 - MVP (Completed) | |
| β CSV upload interface | |
| β Basic data display | |
| ### Phase 1 - Validation (Completed) | |
| β CSV schema validation | |
| β Email/domain/phone validation | |
| β Data sanitization | |
| β Selective API choice | |
| ### Phase 2 - Live Enrichment (Current) | |
| β Live API processing | |
| β Real-time analytics | |
| β Performance tracking | |
| β Error handling | |
| ### Future Enhancements | |
| π Streaming results | |
| π Batch processing | |
| π Custom workflows | |
| π Advanced analytics | |
| """) | |
| if __name__ == "__main__": | |
| logger.info("Starting StrategyStack Phase 2 - Live Enrichment") | |
| demo.launch() | |