#!.venv/bin/python3 from datetime import datetime import numpy as np import pandas as pd import plotly.express as px import plotly.graph_objects as go import streamlit as st from core.config import get_config from core.utils import get_data_file_path from core.utils.data_loader import DataLoader from core.utils.region_mapper import RegionMapper from processing.monitoring.pipeline_monitor import PipelineMonitor from research.experiment import ExperimentConfig from research.experiment.experiment_builder import ExperimentBuilder from research.experiment.experiment_runner import ExperimentRunner from research.experiment.experiment_tracker import ExperimentTracker from research.experiment.feature_extractor import FeatureType from research.model_registry import list_available_models from web.dashboard import Dashboard from web.data_overview import DataOverview from web.data_processing import DataProcessing # Page configuration st.set_page_config( page_title="DRC Names NLP Pipeline", page_icon="🇨🇩", layout="wide", initial_sidebar_state="expanded", ) @st.cache_data def load_config(): """Load application configuration""" return get_config() @st.cache_data def load_dataset(file_path: str) -> pd.DataFrame: """Load dataset with caching""" try: return pd.read_csv(file_path) except Exception as e: st.error(f"Error loading dataset: {e}") return pd.DataFrame() class StreamlitApp: """Main Streamlit application class""" def __init__(self): self.config = load_config() self.data_loader = DataLoader(self.config) self.experiment_tracker = ExperimentTracker(self.config) self.experiment_runner = ExperimentRunner(self.config) self.pipeline_monitor = PipelineMonitor() # Initialize web components self.dashboard = Dashboard(self.config, self.experiment_tracker, self.experiment_runner) self.data_overview = DataOverview(self.config) self.data_processing = DataProcessing(self.config, self.pipeline_monitor) # Initialize session state if "current_experiment" not in st.session_state: st.session_state.current_experiment = None if "experiment_results" not in st.session_state: st.session_state.experiment_results = {} def run(self): st.title("🇨🇩 DRC NERS Pipeline") st.markdown("A comprehensive tool for Congolese name analysis and gender prediction") # Sidebar navigation page = st.sidebar.selectbox( "Navigation", [ "Dashboard", "Dataset Overview", "Data Processing", "Experiments", "Results & Analysis", "Predictions", "Configuration", ], ) # Route to appropriate page if page == "Dashboard": self.dashboard.index() elif page == "Dataset Overview": self.data_overview.index() elif page == "Data Processing": self.data_processing.index() elif page == "Experiments": self.show_experiments() elif page == "Results & Analysis": self.show_results_analysis() elif page == "Predictions": self.show_predictions() elif page == "Configuration": self.show_configuration() def show_experiments(self): """Show experiment management interface""" st.header("Experiment Management") tab1, tab2, tab3 = st.tabs(["New Experiment", "Experiment List", "Batch Experiments"]) with tab1: self.show_experiment_creation() with tab2: self.show_experiment_list() with tab3: self.show_batch_experiments() def show_experiment_creation(self): """Show interface for creating new experiments""" st.subheader("Create New Experiment") with st.form("new_experiment"): col1, col2 = st.columns(2) with col1: exp_name = st.text_input("Experiment Name", placeholder="e.g., native_name_gender_prediction") description = st.text_area("Description", placeholder="Brief description of the experiment") model_type = st.selectbox("Model Type", list_available_models()) # Feature selection feature_options = [f.value for f in FeatureType] selected_features = st.multiselect("Features to Use", feature_options, default=["full_name"]) with col2: # Model parameters st.write("**Model Parameters**") if model_type == "logistic_regression": ngram_min = st.number_input("N-gram Min", 1, 5, 2) ngram_max = st.number_input("N-gram Max", 2, 8, 5) max_features = st.number_input("Max Features", 1000, 50000, 10000) elif model_type == "random_forest": n_estimators = st.number_input("Number of Trees", 10, 500, 100) max_depth = st.number_input("Max Depth", 1, 20, 10) # Training parameters st.write("**Training Parameters**") test_size = st.slider("Test Set Size", 0.1, 0.5, 0.2) cv_folds = st.number_input("Cross-Validation Folds", 3, 10, 5) tags = st.text_input("Tags (comma-separated)", placeholder="e.g., baseline, feature_study") # Advanced options with st.expander("Advanced Options"): # Data filters st.write("**Data Filters**") filter_province = st.selectbox( "Filter by Province (optional)", ["None"] + RegionMapper().get_provinces(), ) min_words = st.number_input("Minimum Word Count", 0, 10, 0) max_words = st.number_input("Maximum Word Count (0 = no limit)", 0, 20, 0) submitted = st.form_submit_button("Create and Run Experiment", type="primary") if submitted: if not exp_name: st.error("Please provide an experiment name") return if not selected_features: st.error("Please select at least one feature") return # Build experiment configuration try: # Prepare model parameters model_params = {} if model_type == "logistic_regression": model_params = { "ngram_range": [ngram_min, ngram_max], "max_features": max_features, } elif model_type == "random_forest": model_params = { "n_estimators": n_estimators, "max_depth": max_depth if max_depth > 0 else None, } # Prepare data filters train_filter = {} if filter_province != "None": train_filter["province"] = filter_province if min_words > 0: train_filter["words"] = {"min": min_words} if max_words > 0: if "words" in train_filter: train_filter["words"]["max"] = max_words else: train_filter["words"] = {"max": max_words} # Create experiment config features = [FeatureType(f) for f in selected_features] tag_list = [tag.strip() for tag in tags.split(",") if tag.strip()] config = ExperimentConfig( name=exp_name, description=description, tags=tag_list, model_type=model_type, model_params=model_params, features=features, train_data_filter=train_filter if train_filter else None, test_size=test_size, cross_validation_folds=cv_folds, ) # Run experiment with st.spinner("Running experiment..."): experiment_id = self.experiment_runner.run_experiment(config) st.success(f"Experiment completed successfully!") st.info(f"Experiment ID: `{experiment_id}`") # Show results experiment = self.experiment_tracker.get_experiment(experiment_id) if experiment and experiment.test_metrics: st.write("**Results:**") for metric, value in experiment.test_metrics.items(): st.metric(metric.title(), f"{value:.4f}") except Exception as e: st.error(f"Error running experiment: {e}") def show_experiment_list(self): """Show list of all experiments with filtering""" st.subheader("All Experiments") # Filters col1, col2, col3 = st.columns(3) with col1: status_filter = st.selectbox( "Filter by Status", ["All", "completed", "running", "failed", "pending"] ) with col2: model_filter = st.selectbox("Filter by Model", ["All"] + list_available_models()) with col3: tag_filter = st.text_input("Filter by Tags (comma-separated)") # Get experiments experiments = self.experiment_tracker.list_experiments() # Apply filters if status_filter != "All": from research.experiment import ExperimentStatus experiments = [e for e in experiments if e.status == ExperimentStatus(status_filter)] if model_filter != "All": experiments = [e for e in experiments if e.config.model_type == model_filter] if tag_filter: tags = [tag.strip() for tag in tag_filter.split(",")] experiments = [e for e in experiments if any(tag in e.config.tags for tag in tags)] if not experiments: st.info("No experiments found matching the filters.") return # Display experiments for i, exp in enumerate(experiments): with st.expander( f"{exp.config.name} - {exp.status.value} - {exp.start_time.strftime('%Y-%m-%d %H:%M')}" ): col1, col2, col3 = st.columns(3) with col1: st.write(f"**Model:** {exp.config.model_type}") st.write(f"**Features:** {', '.join([f.value for f in exp.config.features])}") st.write(f"**Tags:** {', '.join(exp.config.tags)}") with col2: if exp.test_metrics: for metric, value in exp.test_metrics.items(): st.metric(metric.title(), f"{value:.4f}") with col3: st.write(f"**Train Size:** {exp.train_size:,}") st.write(f"**Test Size:** {exp.test_size:,}") if st.button(f"View Details", key=f"details_{i}"): st.session_state.selected_experiment = exp.experiment_id st.rerun() if exp.config.description: st.write(f"**Description:** {exp.config.description}") def show_batch_experiments(self): """Show interface for running batch experiments""" st.subheader("Batch Experiments") st.write("Run multiple experiments with different parameter combinations.") # Parameter sweep configuration with st.form("batch_experiments"): st.write("**Parameter Sweep Configuration**") col1, col2 = st.columns(2) with col1: base_name = st.text_input("Base Experiment Name", "parameter_sweep") model_types = st.multiselect( "Model Types", list_available_models(), default=["logistic_regression"] ) # N-gram ranges for logistic regression st.write("**Logistic Regression Parameters**") ngram_ranges = st.text_area( "N-gram Ranges (one per line, format: min,max)", "2,4\n2,5\n3,6" ) with col2: feature_combinations = st.multiselect( "Feature Combinations", [f.value for f in FeatureType], default=["full_name", "native_name", "surname"], ) test_sizes = st.text_input("Test Sizes (comma-separated)", "0.15,0.2,0.25") tags = st.text_input("Common Tags", "parameter_sweep,batch") if st.form_submit_button("🚀 Run Batch Experiments"): self.run_batch_experiments( base_name, model_types, ngram_ranges, feature_combinations, test_sizes, tags ) def show_results_analysis(self): """Show experiment results and analysis""" st.header("Results & Analysis") tab1, tab2, tab3 = st.tabs(["Experiment Comparison", "Performance Analysis", "Model Analysis"]) with tab1: self.show_experiment_comparison() with tab2: self.show_performance_analysis() with tab3: self.show_model_analysis() def show_experiment_comparison(self): """Show experiment comparison interface""" st.subheader("Compare Experiments") experiments = self.experiment_tracker.list_experiments() completed_experiments = [e for e in experiments if e.status.value == "completed"] if not completed_experiments: st.warning("No completed experiments found.") return # Experiment selection exp_options = { f"{exp.config.name} ({exp.experiment_id[:8]})": exp.experiment_id for exp in completed_experiments } selected_exp_names = st.multiselect( "Select Experiments to Compare", list(exp_options.keys()), default=list(exp_options.keys())[: min(5, len(exp_options))], ) if not selected_exp_names: st.info("Please select experiments to compare.") return selected_exp_ids = [exp_options[name] for name in selected_exp_names] # Generate comparison comparison_df = self.experiment_runner.compare_experiments(selected_exp_ids) if comparison_df.empty: st.error("No data available for comparison.") return # Display comparison table st.write("**Experiment Comparison Table**") # Select columns to display metric_columns = [ col for col in comparison_df.columns if col.startswith("test_") or col.startswith("cv_") ] display_columns = ["name", "model_type", "features"] + metric_columns available_columns = [col for col in display_columns if col in comparison_df.columns] st.dataframe(comparison_df[available_columns], use_container_width=True) # Visualization st.write("**Performance Comparison**") if "test_accuracy" in comparison_df.columns: fig = px.bar( comparison_df, x="name", y="test_accuracy", color="model_type", title="Test Accuracy Comparison", ) fig.update_layout(xaxis_tickangle=-45) st.plotly_chart(fig, use_container_width=True) # Metric comparison across multiple metrics if len(metric_columns) > 1: metric_to_plot = st.selectbox("Select Metric for Detailed Comparison", metric_columns) if metric_to_plot in comparison_df.columns: fig = px.bar( comparison_df, x="name", y=metric_to_plot, color="model_type", title=f"{metric_to_plot.replace('_', ' ').title()} Comparison", ) fig.update_layout(xaxis_tickangle=-45) st.plotly_chart(fig, use_container_width=True) def show_performance_analysis(self): """Show performance analysis across experiments""" st.subheader("Performance Analysis") experiments = self.experiment_tracker.list_experiments() completed_experiments = [ e for e in experiments if e.status.value == "completed" and e.test_metrics ] if not completed_experiments: st.warning("No completed experiments with metrics found.") return # Prepare data for analysis analysis_data = [] for exp in completed_experiments: row = { "experiment_id": exp.experiment_id, "name": exp.config.name, "model_type": exp.config.model_type, "feature_count": len(exp.config.features), "features": ", ".join([f.value for f in exp.config.features]), "train_size": exp.train_size, "test_size": exp.test_size, **exp.test_metrics, } analysis_data.append(row) analysis_df = pd.DataFrame(analysis_data) # Performance trends col1, col2 = st.columns(2) with col1: # Accuracy vs Training Size if "accuracy" in analysis_df.columns and "train_size" in analysis_df.columns: fig = px.scatter( analysis_df, x="train_size", y="accuracy", color="model_type", hover_data=["name"], title="Accuracy vs Training Size", ) st.plotly_chart(fig, use_container_width=True) with col2: # Feature Count vs Performance if "accuracy" in analysis_df.columns and "feature_count" in analysis_df.columns: fig = px.scatter( analysis_df, x="feature_count", y="accuracy", color="model_type", hover_data=["name"], title="Accuracy vs Number of Features", ) st.plotly_chart(fig, use_container_width=True) # Model type comparison if "accuracy" in analysis_df.columns: model_performance = ( analysis_df.groupby("model_type")["accuracy"] .agg(["mean", "std", "count"]) .reset_index() ) fig = go.Figure() fig.add_trace( go.Bar( x=model_performance["model_type"], y=model_performance["mean"], error_y=dict(type="data", array=model_performance["std"]), name="Average Accuracy", ) ) fig.update_layout(title="Average Accuracy by Model Type", yaxis_title="Accuracy") st.plotly_chart(fig, use_container_width=True) # Best experiments summary st.subheader("Top Performing Experiments") if "accuracy" in analysis_df.columns: top_experiments = analysis_df.nlargest(5, "accuracy")[ ["name", "model_type", "features", "accuracy", "precision", "recall", "f1"] ] st.dataframe(top_experiments, use_container_width=True) def show_model_analysis(self): """Show detailed model analysis""" st.subheader("Model Analysis") experiments = self.experiment_tracker.list_experiments() completed_experiments = [e for e in experiments if e.status.value == "completed"] if not completed_experiments: st.warning("No completed experiments found.") return # Select experiment for detailed analysis exp_options = { f"{exp.config.name} ({exp.experiment_id[:8]})": exp for exp in completed_experiments } selected_exp_name = st.selectbox( "Select Experiment for Detailed Analysis", list(exp_options.keys()) ) if not selected_exp_name: return selected_exp = exp_options[selected_exp_name] # Experiment details col1, col2 = st.columns(2) with col1: st.write("**Experiment Configuration**") st.json( { "name": selected_exp.config.name, "model_type": selected_exp.config.model_type, "features": [f.value for f in selected_exp.config.features], "model_params": selected_exp.config.model_params, } ) with col2: st.write("**Performance Metrics**") if selected_exp.test_metrics: for metric, value in selected_exp.test_metrics.items(): st.metric(metric.title(), f"{value:.4f}") # Confusion matrix if selected_exp.confusion_matrix: st.write("**Confusion Matrix**") cm = np.array(selected_exp.confusion_matrix) fig = px.imshow(cm, text_auto=True, aspect="auto", title="Confusion Matrix") st.plotly_chart(fig, use_container_width=True) # Feature importance if selected_exp.feature_importance: st.write("**Feature Importance**") importance_data = sorted( selected_exp.feature_importance.items(), key=lambda x: x[1], reverse=True )[:20] features, importances = zip(*importance_data) fig = px.bar( x=list(importances), y=list(features), orientation="h", title="Top 20 Feature Importances", ) fig.update_layout(height=600) st.plotly_chart(fig, use_container_width=True) # Prediction examples if selected_exp.prediction_examples: st.write("**Prediction Examples**") examples_df = pd.DataFrame(selected_exp.prediction_examples) # Separate correct and incorrect predictions correct_examples = examples_df[examples_df["correct"] == True] incorrect_examples = examples_df[examples_df["correct"] == False] col1, col2 = st.columns(2) with col1: st.write("**Correct Predictions**") if not correct_examples.empty: st.dataframe( correct_examples[["name", "true_label", "predicted_label"]], use_container_width=True, ) with col2: st.write("**Incorrect Predictions**") if not incorrect_examples.empty: st.dataframe( incorrect_examples[["name", "true_label", "predicted_label"]], use_container_width=True, ) def show_predictions(self): """Show prediction interface""" st.header("Make Predictions") # Load available models experiments = self.experiment_tracker.list_experiments() completed_experiments = [ e for e in experiments if e.status.value == "completed" and e.model_path ] if not completed_experiments: st.warning("No trained models available. Please run some experiments first.") return # Model selection model_options = { f"{exp.config.name} (Acc: {exp.test_metrics.get('accuracy', 0):.3f})": exp for exp in completed_experiments if exp.test_metrics } selected_model_name = st.selectbox("Select Model", list(model_options.keys())) if not selected_model_name: return selected_experiment = model_options[selected_model_name] # Prediction modes prediction_mode = st.radio( "Prediction Mode", ["Single Name", "Batch Upload", "Dataset Prediction"] ) if prediction_mode == "Single Name": self.show_single_prediction(selected_experiment) elif prediction_mode == "Batch Upload": self.show_batch_prediction(selected_experiment) elif prediction_mode == "Dataset Prediction": self.show_dataset_prediction(selected_experiment) def show_single_prediction(self, experiment): """Show single name prediction interface""" st.subheader("Single Name Prediction") name_input = st.text_input("Enter a name:", placeholder="e.g., Jean Baptiste Mukendi") if name_input and st.button("Predict Gender"): try: # Load the model model = self.experiment_runner.load_experiment_model(experiment.experiment_id) if model is None: st.error("Failed to load model") return # Create a DataFrame with the input input_df = pd.DataFrame( { "name": [name_input], "words": [len(name_input.split())], "length": [len(name_input.replace(" ", ""))], "province": ["unknown"], # Default values "identified_name": [None], "identified_surname": [None], "probable_native": [None], "probable_surname": [None], } ) # Make prediction prediction = model.predict(input_df)[0] # Get prediction probability if available try: probabilities = model.predict_proba(input_df)[0] confidence = max(probabilities) except: confidence = None # Display results col1, col2 = st.columns(2) with col1: gender_label = "Female" if prediction == "f" else "Male" st.success(f"**Predicted Gender:** {gender_label}") with col2: if confidence: st.metric("Confidence", f"{confidence:.2%}") # Additional info st.info(f"Model used: {experiment.batch_config.name}") st.info( f"Features used: {', '.join([f.value for f in experiment.batch_config.features])}" ) except Exception as e: st.error(f"Error making prediction: {e}") def show_batch_prediction(self, experiment): """Show batch prediction interface""" st.subheader("Batch Prediction") uploaded_file = st.file_uploader("Upload CSV file with names", type="csv") if uploaded_file is not None: try: df = pd.read_csv(uploaded_file) st.write("**Uploaded Data Preview:**") st.dataframe(df.head(), use_container_width=True) # Column selection if "name" not in df.columns: name_column = st.selectbox("Select the name column:", df.columns) df = df.rename(columns={name_column: "name"}) if st.button("Run Batch Prediction"): with st.spinner("Making predictions..."): # Load model model = self.experiment_runner.load_experiment_model( experiment.experiment_id ) if model is None: st.error("Failed to load model") return # Prepare data (add missing columns with defaults) required_columns = [ "words", "length", "province", "identified_name", "identified_surname", "probable_native", "probable_surname", ] for col in required_columns: if col not in df.columns: if col == "words": df[col] = df["name"].str.split().str.len() elif col == "length": df[col] = df["name"].str.replace(" ", "").str.len() else: df[col] = None # Make predictions predictions = model.predict(df) df["predicted_gender"] = predictions df["gender_label"] = df["predicted_gender"].map( {"f": "Female", "m": "Male"} ) # Try to get probabilities try: probabilities = model.predict_proba(df) df["confidence"] = np.max(probabilities, axis=1) except: df["confidence"] = None st.success("Predictions completed!") # Show results result_columns = ["name", "gender_label", "predicted_gender"] if "confidence" in df.columns: result_columns.append("confidence") st.dataframe(df[result_columns], use_container_width=True) # Download results csv = df.to_csv(index=False) st.download_button( label="Download Predictions", data=csv, file_name=f"predictions_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", mime="text/csv", ) # Summary statistics st.subheader("Prediction Summary") gender_counts = df["gender_label"].value_counts() col1, col2, col3 = st.columns(3) with col1: st.metric("Total Predictions", len(df)) with col2: st.metric("Female", gender_counts.get("Female", 0)) with col3: st.metric("Male", gender_counts.get("Male", 0)) # Gender distribution chart fig = px.pie( values=gender_counts.values, names=gender_counts.index, title="Predicted Gender Distribution", ) st.plotly_chart(fig, use_container_width=True) except Exception as e: st.error(f"Error processing file: {e}") def show_dataset_prediction(self, experiment): """Show dataset prediction interface""" st.subheader("Dataset Prediction") st.write("Apply the model to existing datasets") # Dataset selection dataset_options = { "Featured Dataset": self.config.data.output_files["featured"], "Evaluation Dataset": self.config.data.output_files["evaluation"], } selected_dataset = st.selectbox("Select Dataset", list(dataset_options.keys())) file_path = get_data_file_path(dataset_options[selected_dataset], self.config) if not file_path.exists(): st.warning(f"Dataset not found: {file_path}") return # Load and show dataset info df = load_dataset(str(file_path)) st.write(f"Dataset contains {len(df):,} records") # Prediction options col1, col2 = st.columns(2) with col1: sample_size = st.number_input( "Sample size (0 = all data)", 0, len(df), min(1000, len(df)) ) with col2: if "sex" in df.columns: compare_with_actual = st.checkbox("Compare with actual labels", value=True) else: compare_with_actual = False if st.button("Run Dataset Prediction"): with st.spinner("Running predictions..."): # Sample data if requested if sample_size > 0: df_sample = df.sample(n=sample_size, random_state=42) else: df_sample = df # Load model and make predictions model = self.experiment_runner.load_experiment_model(experiment.experiment_id) if model is None: st.error("Failed to load model") return predictions = model.predict(df_sample) df_sample["predicted_gender"] = predictions # Show results if compare_with_actual and "sex" in df_sample.columns: # Calculate accuracy accuracy = (df_sample["sex"] == df_sample["predicted_gender"]).mean() st.metric("Accuracy on Selected Data", f"{accuracy:.4f}") # Confusion matrix from sklearn.metrics import confusion_matrix cm = confusion_matrix(df_sample["sex"], df_sample["predicted_gender"]) fig = px.imshow(cm, text_auto=True, aspect="auto", title="Confusion Matrix") st.plotly_chart(fig, use_container_width=True) # Sample of correct and incorrect predictions correct_mask = df_sample["sex"] == df_sample["predicted_gender"] col1, col2 = st.columns(2) with col1: st.write("**Sample Correct Predictions**") correct_sample = df_sample[correct_mask][ ["name", "sex", "predicted_gender"] ].head(10) st.dataframe(correct_sample, use_container_width=True) with col2: st.write("**Sample Incorrect Predictions**") incorrect_sample = df_sample[~correct_mask][ ["name", "sex", "predicted_gender"] ].head(10) st.dataframe(incorrect_sample, use_container_width=True) else: # Just show predictions st.write("**Sample Predictions**") sample_results = df_sample[["name", "predicted_gender"]].head(20) st.dataframe(sample_results, use_container_width=True) # Gender distribution gender_counts = df_sample["predicted_gender"].value_counts() fig = px.pie( values=gender_counts.values, names=gender_counts.index, title="Predicted Gender Distribution", ) st.plotly_chart(fig, use_container_width=True) def show_configuration(self): st.header("Current Configuration") st.json(self.config.model_dump()) def run_baseline_experiments(self): """Run baseline experiments""" with st.spinner("Running baseline experiments..."): try: builder = ExperimentBuilder() experiments = builder.create_baseline_experiments() experiment_ids = self.experiment_runner.run_experiment_batch(experiments) st.success(f"Completed {len(experiment_ids)} baseline experiments") # Show quick comparison if experiment_ids: comparison = self.experiment_runner.compare_experiments(experiment_ids) st.write("**Results Summary:**") st.dataframe( comparison[["name", "model_type", "test_accuracy"]], use_container_width=True, ) except Exception as e: st.error(f"Error running baseline experiments: {e}") def run_ablation_study(self): """Run feature ablation study""" with st.spinner("Running ablation study..."): try: builder = ExperimentBuilder() experiments = builder.create_feature_ablation_study() experiment_ids = self.experiment_runner.run_experiment_batch(experiments) st.success(f"Completed {len(experiment_ids)} ablation experiments") except Exception as e: st.error(f"Error running ablation study: {e}") def run_component_study(self): """Run name component study""" with st.spinner("Running component study..."): try: builder = ExperimentBuilder() experiments = builder.create_name_component_study() experiment_ids = self.experiment_runner.run_experiment_batch(experiments) st.success(f"Completed {len(experiment_ids)} component experiments") except Exception as e: st.error(f"Error running component study: {e}") def run_province_study(self): """Run province-specific study""" with st.spinner("Running province study..."): try: builder = ExperimentBuilder() experiments = builder.create_province_specific_study() experiment_ids = self.experiment_runner.run_experiment_batch(experiments) st.success(f"Completed {len(experiment_ids)} province experiments") except Exception as e: st.error(f"Error running province study: {e}") def clean_checkpoints(self): """Clean pipeline checkpoints""" for step in ["data_cleaning", "feature_extraction", "llm_annotation", "data_splitting"]: self.pipeline_monitor.clean_step_checkpoints(step, keep_last=1) st.success("Checkpoints cleaned!") def run_batch_experiments( self, base_name, model_types, ngram_ranges, feature_combinations, test_sizes, tags ): """Run batch experiments with parameter combinations""" with st.spinner("Running batch experiments..."): try: experiments = [] # Parse parameters ngram_list = [] for line in ngram_ranges.strip().split("\n"): if "," in line: min_val, max_val = map(int, line.split(",")) ngram_list.append([min_val, max_val]) test_size_list = [float(x.strip()) for x in test_sizes.split(",")] tag_list = [tag.strip() for tag in tags.split(",") if tag.strip()] # Generate experiment combinations exp_count = 0 for model_type in model_types: for feature_combo in feature_combinations: for test_size in test_size_list: if model_type == "logistic_regression": for ngram_range in ngram_list: exp_name = f"{base_name}_{model_type}_{feature_combo}_{ngram_range[0]}_{ngram_range[1]}_{test_size}" config = ExperimentConfig( name=exp_name, description=f"Batch experiment: {model_type} with {feature_combo}", model_type=model_type, features=[FeatureType(feature_combo)], model_params={"ngram_range": ngram_range}, test_size=test_size, tags=tag_list, ) experiments.append(config) exp_count += 1 else: exp_name = f"{base_name}_{model_type}_{feature_combo}_{test_size}" config = ExperimentConfig( name=exp_name, description=f"Batch experiment: {model_type} with {feature_combo}", model_type=model_type, features=[FeatureType(feature_combo)], test_size=test_size, tags=tag_list, ) experiments.append(config) exp_count += 1 # Run experiments experiment_ids = self.experiment_runner.run_experiment_batch(experiments) st.success(f"Completed {len(experiment_ids)} batch experiments") # Show summary if experiment_ids: comparison = self.experiment_runner.compare_experiments(experiment_ids) st.write("**Batch Results Summary:**") st.dataframe( comparison[["name", "model_type", "test_accuracy"]], use_container_width=True, ) except Exception as e: st.error(f"Error running batch experiments: {e}") def main(): """Main application entry point""" app = StreamlitApp() app.run() if __name__ == "__main__": main()