mirror of
https://github.com/andwati/student-retention.git
synced 2026-10-18 00:42:53 +00:00
antigravity
This commit is contained in:
@@ -1,6 +1,134 @@
|
||||
"""
|
||||
Main Streamlit application for Student Success Predictor
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
|
||||
from src.components.form_components import (
|
||||
render_preset_selector,
|
||||
render_personal_info_section,
|
||||
render_academic_background_section,
|
||||
render_family_background_section,
|
||||
render_financial_social_section,
|
||||
render_academic_performance_section,
|
||||
render_economic_indicators_section
|
||||
)
|
||||
from src.components.prediction_components import (
|
||||
render_prediction_section,
|
||||
render_model_info_sidebar,
|
||||
render_export_section,
|
||||
render_footer
|
||||
)
|
||||
from src.config.mappings import create_feature_mappings
|
||||
from src.config.theme import ROSE_PINE_THEME, PAGE_CONFIG, APP_TITLE, APP_SUBTITLE
|
||||
from src.utils.auth import require_auth, check_role_permission, render_admin_panel, check_session_timeout
|
||||
from src.utils.models_utils import load_model_and_info
|
||||
|
||||
|
||||
def apply_theme():
|
||||
"""Apply the Rose Pine dark theme"""
|
||||
st.markdown(ROSE_PINE_THEME, unsafe_allow_html=True)
|
||||
|
||||
|
||||
def render_header():
|
||||
"""Render the main application header"""
|
||||
st.markdown(f'<h1 class="main-header">{APP_TITLE}</h1>', unsafe_allow_html=True)
|
||||
st.markdown(f'<p class="subtitle">{APP_SUBTITLE}</p>', unsafe_allow_html=True)
|
||||
|
||||
|
||||
def collect_user_input():
|
||||
"""Collect all user input from the sidebar form"""
|
||||
feature_maps = create_feature_mappings()
|
||||
|
||||
with st.sidebar:
|
||||
# Preset selector
|
||||
preset = render_preset_selector()
|
||||
|
||||
# Personal information
|
||||
personal_info = render_personal_info_section(preset, feature_maps)
|
||||
|
||||
# Academic background
|
||||
academic_bg = render_academic_background_section(preset, feature_maps)
|
||||
|
||||
# Family background
|
||||
family_bg = render_family_background_section(feature_maps)
|
||||
|
||||
# Financial and social factors
|
||||
financial_social = render_financial_social_section()
|
||||
|
||||
# Academic performance
|
||||
academic_performance = render_academic_performance_section(preset)
|
||||
|
||||
# Economic indicators
|
||||
economic_indicators = render_economic_indicators_section()
|
||||
|
||||
# Combine all input data
|
||||
user_input = {
|
||||
**personal_info,
|
||||
**academic_bg,
|
||||
**family_bg,
|
||||
**financial_social,
|
||||
**academic_performance,
|
||||
**economic_indicators
|
||||
}
|
||||
|
||||
return user_input
|
||||
|
||||
|
||||
@require_auth
|
||||
def main():
|
||||
print("Hello from student-retention!")
|
||||
"""Main Streamlit application"""
|
||||
# Check session timeout (30 minutes)
|
||||
check_session_timeout(30)
|
||||
|
||||
# Apply theme
|
||||
apply_theme()
|
||||
|
||||
# Load model and preprocessing info
|
||||
model, preprocessing_info, feature_names = load_model_and_info()
|
||||
|
||||
# Render header with user context
|
||||
render_header()
|
||||
|
||||
# Add admin panel if user is admin
|
||||
if check_role_permission("admin"):
|
||||
with st.sidebar:
|
||||
render_admin_panel()
|
||||
|
||||
# Collect user input
|
||||
user_input = collect_user_input()
|
||||
|
||||
# Main content area
|
||||
col1, col2 = st.columns([2, 1])
|
||||
|
||||
with col1:
|
||||
st.markdown('<h2 class="sub-header">Prediction Results</h2>', unsafe_allow_html=True)
|
||||
|
||||
if check_role_permission("educator"):
|
||||
st.markdown(
|
||||
'<div class="info-box">🎓 <strong>Educator Access:</strong> You have full access to prediction features and student data analysis.</div>',
|
||||
unsafe_allow_html=True)
|
||||
|
||||
# Render prediction section
|
||||
render_prediction_section(model, preprocessing_info, user_input)
|
||||
|
||||
with col2:
|
||||
# Render model info and validation
|
||||
render_model_info_sidebar(user_input)
|
||||
|
||||
# Render export section (role-based access)
|
||||
if check_role_permission("educator"):
|
||||
render_export_section(user_input)
|
||||
else:
|
||||
st.markdown(
|
||||
'<div class="warning-box">📊 <strong>Export Feature:</strong> Available for educators and administrators only.</div>',
|
||||
unsafe_allow_html=True)
|
||||
|
||||
# Render footer
|
||||
render_footer()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
# Configure page before authentication
|
||||
st.set_page_config(**PAGE_CONFIG)
|
||||
main()
|
||||
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
System Overview Diagram
|
||||
High-level view of the entire Student Success Prediction system
|
||||
"""
|
||||
|
||||
from diagrams import Diagram, Cluster, Edge
|
||||
from diagrams.onprem.client import Users
|
||||
from diagrams.generic.storage import Storage
|
||||
from diagrams.programming.language import Python
|
||||
from diagrams.programming.framework import Django
|
||||
from diagrams.onprem.analytics import Tableau
|
||||
from diagrams.onprem.inmemory import Redis
|
||||
from diagrams.aws.ml import SagemakerModel
|
||||
from diagrams.generic.compute import Rack
|
||||
from diagrams.onprem.monitoring import Grafana
|
||||
from diagrams.onprem.network import Nginx
|
||||
|
||||
with Diagram("Student Success Prediction - System Overview",
|
||||
filename="diagrams/00_system_overview",
|
||||
show=False,
|
||||
direction="TB"):
|
||||
|
||||
# System Title and Context
|
||||
title = Storage("Student Success Prediction System\n• 93.84% Accuracy Random Forest Model\n• 36 Features, 3 Outcomes\n• 4,424 Student Dataset")
|
||||
|
||||
# Phase 1: Data Analysis & Model Development
|
||||
with Cluster("Phase 1: Data Analysis & Model Development"):
|
||||
raw_data = Storage("Raw Dataset\ndata.csv")
|
||||
eda_process = Tableau("Exploratory Data Analysis\n• Data quality assessment\n• Statistical analysis\n• Feature insights")
|
||||
ml_pipeline = Python("ML Pipeline\n• Feature engineering\n• Model training\n• Performance validation")
|
||||
model_artifacts = SagemakerModel("Model Artifacts\n• Random Forest (93.84%)\n• Preprocessing info\n• Feature mappings")
|
||||
|
||||
# Phase 2: Application Development
|
||||
with Cluster("Phase 2: Application Development"):
|
||||
modular_architecture = Rack("Modular Architecture\n• Component separation\n• Configuration management\n• Utility services")
|
||||
|
||||
auth_system = Redis("Authentication System\n• Role-based access\n• Session management\n• Security controls")
|
||||
|
||||
ui_components = Django("UI Components\n• Rose Pine theme\n• Interactive forms\n• Responsive design")
|
||||
|
||||
streamlit_app = Django("Streamlit Application\n• Main dashboard\n• User interface\n• API integration")
|
||||
|
||||
# Phase 3: Production Deployment
|
||||
with Cluster("Phase 3: Production Deployment"):
|
||||
deployment_options = Rack("Deployment Options\n• Streamlit Cloud\n• Docker containers\n• Traditional servers")
|
||||
|
||||
monitoring = Grafana("Monitoring & Analytics\n• Performance metrics\n• User analytics\n• System health")
|
||||
|
||||
security = Redis("Security & Compliance\n• Data protection\n• Access control\n• Audit logging")
|
||||
|
||||
# End Users and Outcomes
|
||||
with Cluster("Stakeholders & Users"):
|
||||
students = Users("Students\n• Academic planning\n• Risk assessment\n• Personal insights")
|
||||
educators = Users("Educators\n• Student analytics\n• Intervention planning\n• Performance tracking")
|
||||
administrators = Users("Administrators\n• System management\n• User oversight\n• Strategic planning")
|
||||
|
||||
# Global Internet Access
|
||||
internet = Nginx("Global Access\nSecure HTTPS")
|
||||
|
||||
# System Flow
|
||||
title >> raw_data >> eda_process >> ml_pipeline >> model_artifacts
|
||||
|
||||
model_artifacts >> modular_architecture
|
||||
modular_architecture >> auth_system
|
||||
modular_architecture >> ui_components
|
||||
auth_system >> streamlit_app
|
||||
ui_components >> streamlit_app
|
||||
|
||||
streamlit_app >> deployment_options
|
||||
deployment_options >> monitoring
|
||||
deployment_options >> security
|
||||
|
||||
internet >> streamlit_app
|
||||
streamlit_app >> students
|
||||
streamlit_app >> educators
|
||||
streamlit_app >> administrators
|
||||
|
||||
# Key Metrics and Features
|
||||
with Cluster("Key System Metrics"):
|
||||
accuracy_metric = Storage("Model Performance\n• 93.84% Test Accuracy\n• Balanced class performance\n• Robust generalization")
|
||||
|
||||
feature_count = Storage("Feature Engineering\n• 36 input features\n• 105+ encoded features\n• Comprehensive preprocessing")
|
||||
|
||||
user_experience = Storage("User Experience\n• 3 user roles\n• Intuitive interface\n• Real-time predictions")
|
||||
|
||||
scalability = Storage("Scalability\n• Cloud deployment\n• Modular architecture\n• Production-ready")
|
||||
|
||||
# Connect metrics to main system
|
||||
streamlit_app >> accuracy_metric
|
||||
ml_pipeline >> feature_count
|
||||
ui_components >> user_experience
|
||||
deployment_options >> scalability
|
||||
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Data Flow Architecture Diagram
|
||||
Shows the complete data pipeline from raw data to model predictions
|
||||
"""
|
||||
|
||||
from diagrams import Diagram, Cluster, Edge
|
||||
from diagrams.onprem.database import PostgreSQL
|
||||
from diagrams.onprem.inmemory import Redis
|
||||
from diagrams.programming.language import Python
|
||||
from diagrams.onprem.analytics import Tableau
|
||||
from diagrams.onprem.client import Users
|
||||
from diagrams.onprem.workflow import Airflow
|
||||
from diagrams.generic.storage import Storage
|
||||
from diagrams.generic.compute import Rack
|
||||
from diagrams.aws.ml import SagemakerModel
|
||||
from diagrams.programming.framework import Django
|
||||
from diagrams.onprem.monitoring import Grafana
|
||||
|
||||
with Diagram("Student Success Prediction - Data Flow Architecture",
|
||||
filename="diagrams/01_data_flow_architecture",
|
||||
show=False,
|
||||
direction="TB"):
|
||||
|
||||
# Data Sources
|
||||
with Cluster("Data Sources"):
|
||||
raw_data = Storage("Raw Dataset\n(data.csv)\n4,424 students\n37 features")
|
||||
|
||||
# Data Processing Pipeline
|
||||
with Cluster("Data Processing Pipeline"):
|
||||
eda_notebook = Python("Exploratory Data Analysis\n(predictive_analysis.ipynb)")
|
||||
data_cleaning = Rack("Data Cleaning\n• Remove apostrophes\n• Fix typos\n• Handle duplicates")
|
||||
feature_eng = Rack("Feature Engineering\n• One-hot encoding\n• Label encoding\n• Scaling")
|
||||
|
||||
# Model Development
|
||||
with Cluster("Model Development"):
|
||||
model_training = Python("Model Training\n(train_and_save_model.py)")
|
||||
model_validation = Grafana("Model Validation\n• Cross-validation\n• Performance metrics")
|
||||
model_storage = Storage("Model Artifacts\n• random_forest_model.pkl\n• preprocessing_info.json")
|
||||
|
||||
# Production System
|
||||
with Cluster("Production Dashboard"):
|
||||
auth_system = Redis("Authentication\n• Session management\n• Role-based access")
|
||||
streamlit_app = Django("Streamlit App\n(app.py)")
|
||||
|
||||
with Cluster("Modular Components"):
|
||||
theme_config = Python("Theme Config\n• Rose Pine CSS\n• UI constants")
|
||||
form_components = Python("Form Components\n• Input validation\n• User interface")
|
||||
model_utils = Python("Model Utils\n• Preprocessing\n• Inference")
|
||||
|
||||
# End Users
|
||||
users = Users("End Users\n• Students\n• Educators\n• Administrators")
|
||||
|
||||
# Data Flow
|
||||
raw_data >> eda_notebook >> data_cleaning >> feature_eng
|
||||
feature_eng >> model_training >> model_validation >> model_storage
|
||||
|
||||
model_storage >> model_utils
|
||||
auth_system >> streamlit_app
|
||||
theme_config >> streamlit_app
|
||||
form_components >> streamlit_app
|
||||
model_utils >> streamlit_app
|
||||
|
||||
streamlit_app >> users
|
||||
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
EDA Process Flow Diagram
|
||||
Shows the exploratory data analysis workflow and key insights
|
||||
"""
|
||||
|
||||
from diagrams import Diagram, Cluster, Edge
|
||||
from diagrams.generic.storage import Storage
|
||||
from diagrams.generic.compute import Rack
|
||||
from diagrams.programming.language import Python
|
||||
from diagrams.onprem.analytics import Tableau
|
||||
from diagrams.onprem.monitoring import Grafana
|
||||
|
||||
with Diagram("Exploratory Data Analysis Process Flow",
|
||||
filename="diagrams/02_eda_process_flow",
|
||||
show=False,
|
||||
direction="TB"):
|
||||
|
||||
# Data Input
|
||||
raw_dataset = Storage("Raw Dataset\ndata.csv\n4,424 rows × 37 columns")
|
||||
|
||||
# EDA Steps
|
||||
with Cluster("Data Quality Assessment"):
|
||||
data_info = Rack("Data Info\n• No missing values\n• No duplicates\n• Mixed data types")
|
||||
column_analysis = Rack("Column Analysis\n• 36 predictors\n• 1 target variable\n• Column name cleanup")
|
||||
|
||||
with Cluster("Statistical Analysis"):
|
||||
descriptive_stats = Grafana("Descriptive Statistics\n• Central tendencies\n• Distributions\n• Outlier detection")
|
||||
correlation_analysis = Grafana("Correlation Analysis\n• Feature relationships\n• Multicollinearity check")
|
||||
|
||||
with Cluster("Visualization & Insights"):
|
||||
target_distribution = Tableau("Target Distribution\n• Graduate: 50%\n• Dropout: 32%\n• Enrolled: 17%")
|
||||
feature_plots = Tableau("Feature Analysis\n• Histograms\n• Box plots\n• Categorical counts")
|
||||
pattern_discovery = Tableau("Pattern Discovery\n• Key predictors\n• Risk factors\n• Success indicators")
|
||||
|
||||
# Key Findings
|
||||
with Cluster("Key Findings"):
|
||||
class_imbalance = Rack("Class Imbalance\nIdentified for modeling")
|
||||
high_cardinality = Rack("High Cardinality\nCourse (51 values)\nApplication order (9 values)")
|
||||
data_quality = Rack("Data Quality\nClean dataset\nReady for modeling")
|
||||
|
||||
# Data Flow
|
||||
raw_dataset >> data_info >> descriptive_stats
|
||||
raw_dataset >> column_analysis >> correlation_analysis
|
||||
descriptive_stats >> target_distribution
|
||||
correlation_analysis >> feature_plots
|
||||
target_distribution >> pattern_discovery
|
||||
|
||||
pattern_discovery >> class_imbalance
|
||||
pattern_discovery >> high_cardinality
|
||||
pattern_discovery >> data_quality
|
||||
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Machine Learning Pipeline Architecture
|
||||
Shows the complete ML workflow from preprocessing to model deployment
|
||||
"""
|
||||
|
||||
from diagrams import Diagram, Cluster, Edge
|
||||
from diagrams.generic.storage import Storage
|
||||
from diagrams.generic.compute import Rack
|
||||
from diagrams.programming.language import Python
|
||||
from diagrams.aws.ml import SagemakerModel
|
||||
from diagrams.onprem.monitoring import Grafana
|
||||
from diagrams.generic.database import SQL
|
||||
|
||||
with Diagram("Machine Learning Pipeline Architecture",
|
||||
filename="diagrams/03_ml_pipeline_architecture",
|
||||
show=False,
|
||||
direction="TB"):
|
||||
|
||||
# Input Data
|
||||
clean_data = Storage("Cleaned Dataset\n• 4,424 samples\n• 36 features\n• 3 target classes")
|
||||
|
||||
# Preprocessing Pipeline
|
||||
with Cluster("Data Preprocessing"):
|
||||
feature_encoding = Rack("Feature Encoding\n• Label encoding (target)\n• One-hot encoding (categorical)")
|
||||
data_splitting = Rack("Data Splitting\n• Train: 75%\n• Test: 25%\n• Stratified split")
|
||||
feature_alignment = Rack("Feature Alignment\n• Consistent column order\n• Missing feature handling")
|
||||
|
||||
# Model Training
|
||||
with Cluster("Model Training & Validation"):
|
||||
model_training = Python("Random Forest Training\n• n_estimators=100\n• random_state=42")
|
||||
cross_validation = Grafana("Cross Validation\n• 5-fold CV\n• Performance metrics")
|
||||
hyperparameter_tuning = Grafana("Hyperparameter Tuning\n• Grid search\n• Best parameters")
|
||||
|
||||
# Model Evaluation
|
||||
with Cluster("Model Evaluation"):
|
||||
performance_metrics = Grafana("Performance Metrics\n• Accuracy: 93.84%\n• Precision/Recall\n• F1-Score")
|
||||
confusion_matrix = Grafana("Confusion Matrix\n• Class-wise performance\n• Error analysis")
|
||||
feature_importance = Grafana("Feature Importance\n• Top predictors\n• Impact analysis")
|
||||
|
||||
# Model Persistence
|
||||
with Cluster("Model Artifacts"):
|
||||
model_pickle = Storage("Model Pickle\nrandom_forest_model.pkl")
|
||||
preprocessing_info = SQL("Preprocessing Info\npreprocessing_info.json\n• Feature names\n• Encodings\n• Mappings")
|
||||
|
||||
# Model Validation Results
|
||||
with Cluster("Validation Results"):
|
||||
accuracy_report = Rack("Accuracy Report\n• Training: 100%\n• Testing: 93.84%\n• Generalization: Good")
|
||||
class_performance = Rack("Class Performance\n• Graduate: 95% F1\n• Dropout: 92% F1\n• Enrolled: 88% F1")
|
||||
|
||||
# Data Flow
|
||||
clean_data >> feature_encoding >> data_splitting
|
||||
data_splitting >> model_training >> cross_validation
|
||||
cross_validation >> hyperparameter_tuning >> performance_metrics
|
||||
|
||||
performance_metrics >> confusion_matrix
|
||||
performance_metrics >> feature_importance
|
||||
|
||||
model_training >> model_pickle
|
||||
feature_encoding >> preprocessing_info
|
||||
feature_alignment >> preprocessing_info
|
||||
|
||||
performance_metrics >> accuracy_report
|
||||
confusion_matrix >> class_performance
|
||||
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
Application Architecture Diagram
|
||||
Shows the modular Streamlit application structure and components
|
||||
"""
|
||||
|
||||
from diagrams import Diagram, Cluster, Edge
|
||||
from diagrams.onprem.client import Users
|
||||
from diagrams.onprem.inmemory import Redis
|
||||
from diagrams.programming.framework import Django, React
|
||||
from diagrams.programming.language import Python
|
||||
from diagrams.generic.storage import Storage
|
||||
from diagrams.generic.compute import Rack
|
||||
from diagrams.onprem.network import Nginx
|
||||
|
||||
with Diagram("Streamlit Application Architecture",
|
||||
filename="diagrams/04_application_architecture",
|
||||
show=False,
|
||||
direction="TB"):
|
||||
|
||||
# Users
|
||||
users = Users("End Users\n• Students\n• Educators\n• Administrators")
|
||||
|
||||
# Web Interface
|
||||
streamlit_frontend = Django("Streamlit Frontend\n• Rose Pine Theme\n• Responsive UI\n• Interactive Forms")
|
||||
|
||||
# Authentication Layer
|
||||
with Cluster("Authentication System"):
|
||||
auth_service = Redis("Authentication Service\n• Session management\n• Role-based access\n• Security validation")
|
||||
user_roles = Rack("User Roles\n• Admin (full access)\n• Educator (analytics)\n• User (predictions)")
|
||||
|
||||
# Application Core
|
||||
with Cluster("Application Core (app.py)"):
|
||||
main_app = Python("Main Application\n• Request routing\n• Session handling\n• Error management")
|
||||
auth_decorator = Python("Auth Decorator\n• @require_auth\n• Permission checks\n• Session validation")
|
||||
|
||||
# Configuration Layer
|
||||
with Cluster("Configuration (src/config/)"):
|
||||
theme_config = React("Theme Configuration\n• Rose Pine CSS\n• Color constants\n• UI styling")
|
||||
mappings_config = Storage("Feature Mappings\n• Human-readable labels\n• Preset templates\n• Validation rules")
|
||||
|
||||
# Utility Services
|
||||
with Cluster("Utility Services (src/utils/)"):
|
||||
model_utils = Python("Model Utilities\n• Model loading\n• Preprocessing\n• Prediction logic")
|
||||
auth_utils = Python("Auth Utilities\n• User validation\n• Session management\n• Password hashing")
|
||||
|
||||
# UI Components
|
||||
with Cluster("UI Components (src/components/)"):
|
||||
form_components = React("Form Components\n• Input sections\n• Validation feedback\n• Preset selection")
|
||||
prediction_display = React("Prediction Display\n• Results visualization\n• Confidence scores\n• Recommendations")
|
||||
|
||||
# Model Layer
|
||||
with Cluster("Model Layer"):
|
||||
trained_model = Storage("Trained Model\nrandom_forest_model.pkl\n93.84% accuracy")
|
||||
preprocessing_pipeline = Storage("Preprocessing Pipeline\npreprocessing_info.json\nFeature transformations")
|
||||
|
||||
# Data Flow
|
||||
users >> streamlit_frontend
|
||||
streamlit_frontend >> auth_service >> user_roles
|
||||
auth_service >> main_app >> auth_decorator
|
||||
|
||||
main_app >> theme_config
|
||||
main_app >> mappings_config
|
||||
main_app >> form_components
|
||||
main_app >> prediction_display
|
||||
|
||||
form_components >> model_utils
|
||||
model_utils >> trained_model
|
||||
model_utils >> preprocessing_pipeline
|
||||
model_utils >> prediction_display
|
||||
|
||||
auth_decorator >> auth_utils
|
||||
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
Deployment Architecture Diagram
|
||||
Shows the deployment options and infrastructure for the application
|
||||
"""
|
||||
|
||||
from diagrams import Diagram, Cluster, Edge
|
||||
from diagrams.onprem.client import Users
|
||||
from diagrams.saas.cdn import Cloudflare
|
||||
from diagrams.programming.framework import Django
|
||||
from diagrams.onprem.container import Docker
|
||||
from diagrams.k8s.compute import Pod
|
||||
from diagrams.generic.storage import Storage
|
||||
from diagrams.generic.network import Internet
|
||||
from diagrams.aws.compute import EC2
|
||||
from diagrams.gcp.compute import GKE
|
||||
|
||||
with Diagram("Deployment Architecture Options",
|
||||
filename="diagrams/05_deployment_architecture",
|
||||
show=False,
|
||||
direction="TB"):
|
||||
|
||||
# Users
|
||||
users = Users("Global Users")
|
||||
internet = Internet("Internet")
|
||||
|
||||
# Deployment Option 1: Streamlit Cloud
|
||||
with Cluster("Option 1: Streamlit Cloud (Recommended)"):
|
||||
streamlit_cloud = Django("Streamlit Cloud\n• Automatic deployment\n• GitHub integration\n• SSL/HTTPS\n• Global CDN")
|
||||
github_repo = Storage("GitHub Repository\n• Source code\n• Model artifacts\n• Configuration")
|
||||
|
||||
# Cloud features
|
||||
cloud_features = [
|
||||
Storage("Automatic Scaling"),
|
||||
Storage("Monitoring & Logs"),
|
||||
Storage("Custom Domain Support")
|
||||
]
|
||||
|
||||
# Deployment Option 2: Docker Container
|
||||
with Cluster("Option 2: Docker Deployment"):
|
||||
docker_container = Docker("Docker Container\n• Streamlit app\n• Python runtime\n• Dependencies")
|
||||
container_registry = Storage("Container Registry\n• Docker Hub\n• AWS ECR\n• GCR")
|
||||
|
||||
# Container orchestration options
|
||||
with Cluster("Orchestration Options"):
|
||||
kubernetes = Pod("Kubernetes\n• Pod management\n• Auto-scaling\n• Load balancing")
|
||||
cloud_run = GKE("Cloud Run/Fargate\n• Serverless containers\n• Pay-per-use\n• Auto-scaling")
|
||||
|
||||
# Deployment Option 3: Traditional Server
|
||||
with Cluster("Option 3: Traditional Server"):
|
||||
web_server = EC2("Web Server\n• EC2/Compute Engine\n• Nginx reverse proxy\n• SSL certificate")
|
||||
app_server = Django("Application Server\n• Streamlit process\n• PM2/Supervisor\n• Process management")
|
||||
|
||||
# Shared Resources
|
||||
with Cluster("Shared Resources"):
|
||||
model_storage = Storage("Model Storage\n• Trained models\n• Preprocessing info\n• Version control")
|
||||
monitoring = Storage("Monitoring\n• Application logs\n• Performance metrics\n• Error tracking")
|
||||
backup = Storage("Backup & Recovery\n• Model versioning\n• Configuration backup\n• Disaster recovery")
|
||||
|
||||
# Data Flow
|
||||
users >> internet
|
||||
|
||||
# Streamlit Cloud flow
|
||||
internet >> streamlit_cloud
|
||||
github_repo >> streamlit_cloud
|
||||
streamlit_cloud >> cloud_features[0]
|
||||
streamlit_cloud >> cloud_features[1]
|
||||
streamlit_cloud >> cloud_features[2]
|
||||
|
||||
# Docker flow
|
||||
internet >> docker_container
|
||||
container_registry >> docker_container
|
||||
docker_container >> kubernetes
|
||||
docker_container >> cloud_run
|
||||
|
||||
# Traditional server flow
|
||||
internet >> web_server >> app_server
|
||||
|
||||
# Shared resources connections
|
||||
streamlit_cloud >> model_storage
|
||||
docker_container >> model_storage
|
||||
app_server >> model_storage
|
||||
|
||||
streamlit_cloud >> monitoring
|
||||
kubernetes >> monitoring
|
||||
app_server >> monitoring
|
||||
|
||||
model_storage >> backup
|
||||
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Feature Engineering Pipeline Diagram
|
||||
Shows the detailed feature transformation and encoding process
|
||||
"""
|
||||
|
||||
from diagrams import Diagram, Cluster, Edge
|
||||
from diagrams.generic.storage import Storage
|
||||
from diagrams.generic.compute import Rack
|
||||
from diagrams.programming.language import Python
|
||||
from diagrams.onprem.analytics import Tableau
|
||||
from diagrams.generic.database import SQL
|
||||
|
||||
with Diagram("Feature Engineering Pipeline",
|
||||
filename="diagrams/06_feature_engineering_pipeline",
|
||||
show=False,
|
||||
direction="TB"):
|
||||
|
||||
# Raw Features Input
|
||||
raw_features = Storage("Raw Features (37)\n• 36 predictors\n• 1 target variable\n• Mixed data types")
|
||||
|
||||
# Categorical Features Processing
|
||||
with Cluster("Categorical Feature Processing"):
|
||||
categorical_features = Rack("Categorical Features\n• Marital status\n• Application mode\n• Course\n• Nationality\n• Mother's/Father's qualification\n• Mother's/Father's occupation\n• Displaced\n• Educational special needs\n• Debtor\n• Tuition fees up to date\n• Gender\n• Scholarship holder\n• International")
|
||||
|
||||
label_encoding = Python("Label Encoding\n• Target variable\n• Dropout = 0\n• Enrolled = 1\n• Graduate = 2")
|
||||
|
||||
one_hot_encoding = Python("One-Hot Encoding\n• Categorical predictors\n• Binary columns creation\n• Feature expansion")
|
||||
|
||||
# Numerical Features Processing
|
||||
with Cluster("Numerical Feature Processing"):
|
||||
numerical_features = Rack("Numerical Features\n• Application order\n• Previous qualification grade\n• Admission grade\n• Age at enrollment\n• Curricular units (1st & 2nd sem)\n• Unemployment rate\n• Inflation rate\n• GDP")
|
||||
|
||||
feature_validation = Python("Feature Validation\n• Range checks\n• Outlier detection\n• Data type validation")
|
||||
|
||||
feature_scaling = Python("Feature Scaling\n• Optional for tree models\n• Standardization available\n• Normalization available")
|
||||
|
||||
# Feature Engineering Output
|
||||
with Cluster("Engineered Features"):
|
||||
encoded_features = Storage("Encoded Features (~105)\n• Original numerical (14)\n• One-hot encoded categorical (90+)\n• Label encoded target (1)")
|
||||
|
||||
feature_mapping = SQL("Feature Mapping\n• Original → Encoded names\n• Category mappings\n• Reverse mappings")
|
||||
|
||||
preprocessing_metadata = SQL("Preprocessing Metadata\n• Encoding schemes\n• Feature names\n• Transformation info")
|
||||
|
||||
# Quality Assurance
|
||||
with Cluster("Quality Assurance"):
|
||||
feature_consistency = Rack("Feature Consistency\n• Column order preservation\n• Missing feature handling\n• Shape validation")
|
||||
|
||||
data_validation = Rack("Data Validation\n• No missing values\n• Consistent data types\n• Feature alignment")
|
||||
|
||||
pipeline_testing = Tableau("Pipeline Testing\n• Train/test consistency\n• Transformation verification\n• Edge case handling")
|
||||
|
||||
# Data Flow
|
||||
raw_features >> categorical_features >> one_hot_encoding
|
||||
raw_features >> numerical_features >> feature_validation
|
||||
|
||||
categorical_features >> label_encoding
|
||||
numerical_features >> feature_scaling
|
||||
|
||||
one_hot_encoding >> encoded_features
|
||||
label_encoding >> encoded_features
|
||||
feature_validation >> encoded_features
|
||||
feature_scaling >> encoded_features
|
||||
|
||||
encoded_features >> feature_mapping
|
||||
encoded_features >> preprocessing_metadata
|
||||
|
||||
encoded_features >> feature_consistency
|
||||
feature_consistency >> data_validation
|
||||
data_validation >> pipeline_testing
|
||||
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
User Journey and Interaction Flow Diagram
|
||||
Shows how different user types interact with the application
|
||||
"""
|
||||
|
||||
from diagrams import Diagram, Cluster, Edge
|
||||
from diagrams.onprem.client import Users, User
|
||||
from diagrams.programming.framework import Django
|
||||
from diagrams.onprem.inmemory import Redis
|
||||
from diagrams.generic.compute import Rack
|
||||
from diagrams.onprem.analytics import Tableau
|
||||
from diagrams.generic.storage import Storage
|
||||
|
||||
with Diagram("User Journey and Interaction Flow",
|
||||
filename="diagrams/07_user_journey_flow",
|
||||
show=False,
|
||||
direction="TB"):
|
||||
|
||||
# User Types
|
||||
with Cluster("User Types"):
|
||||
student_user = User("Student\n• Personal predictions\n• Academic planning\n• Risk assessment")
|
||||
educator_user = User("Educator\n• Student analytics\n• Intervention planning\n• Batch predictions")
|
||||
admin_user = User("Administrator\n• System management\n• User oversight\n• Full access")
|
||||
|
||||
# Authentication Flow
|
||||
with Cluster("Authentication Flow"):
|
||||
login_page = Django("Login Page\n• Credential input\n• Role selection\n• Security validation")
|
||||
auth_service = Redis("Authentication Service\n• Session creation\n• Role assignment\n• Access control")
|
||||
session_management = Redis("Session Management\n• Timeout handling\n• Security checks\n• State persistence")
|
||||
|
||||
# Application Interface
|
||||
with Cluster("Application Interface"):
|
||||
main_dashboard = Django("Main Dashboard\n• Role-based UI\n• Feature access\n• Navigation menu")
|
||||
|
||||
# Student workflow
|
||||
with Cluster("Student Workflow"):
|
||||
personal_form = Rack("Personal Information Form\n• Basic demographics\n• Academic history\n• Family background")
|
||||
academic_form = Rack("Academic Performance Form\n• Current grades\n• Curricular units\n• Progress tracking")
|
||||
prediction_results = Tableau("Prediction Results\n• Outcome probability\n• Confidence scores\n• Recommendations")
|
||||
|
||||
# Educator workflow
|
||||
with Cluster("Educator Workflow"):
|
||||
student_management = Rack("Student Management\n• Batch input\n• Template selection\n• Data validation")
|
||||
analytics_dashboard = Tableau("Analytics Dashboard\n• Class performance\n• Risk indicators\n• Intervention alerts")
|
||||
reporting_tools = Tableau("Reporting Tools\n• Performance reports\n• Trend analysis\n• Export capabilities")
|
||||
|
||||
# Admin workflow
|
||||
with Cluster("Admin Workflow"):
|
||||
user_management = Rack("User Management\n• Account creation\n• Role assignment\n• Access control")
|
||||
system_monitoring = Tableau("System Monitoring\n• Usage statistics\n• Performance metrics\n• Error logs")
|
||||
model_management = Storage("Model Management\n• Version control\n• Performance tracking\n• Updates")
|
||||
|
||||
# Prediction Engine
|
||||
with Cluster("Prediction Engine"):
|
||||
input_validation = Rack("Input Validation\n• Data quality checks\n• Range validation\n• Required fields")
|
||||
feature_processing = Rack("Feature Processing\n• Encoding\n• Transformation\n• Alignment")
|
||||
model_inference = Storage("Model Inference\n• Random Forest prediction\n• Confidence calculation\n• Result formatting")
|
||||
|
||||
# User Journey Flows
|
||||
|
||||
# Student journey
|
||||
student_user >> login_page >> auth_service >> main_dashboard
|
||||
main_dashboard >> personal_form >> academic_form
|
||||
academic_form >> input_validation >> feature_processing >> model_inference
|
||||
model_inference >> prediction_results
|
||||
|
||||
# Educator journey
|
||||
educator_user >> login_page >> auth_service >> main_dashboard
|
||||
main_dashboard >> student_management >> analytics_dashboard
|
||||
student_management >> input_validation >> feature_processing >> model_inference
|
||||
model_inference >> reporting_tools
|
||||
|
||||
# Admin journey
|
||||
admin_user >> login_page >> auth_service >> main_dashboard
|
||||
main_dashboard >> user_management >> system_monitoring >> model_management
|
||||
|
||||
# Session management connections
|
||||
auth_service >> session_management
|
||||
session_management >> main_dashboard
|
||||
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
Security and Authentication Architecture Diagram
|
||||
Shows the comprehensive security model and authentication flow
|
||||
"""
|
||||
|
||||
from diagrams import Diagram, Cluster, Edge
|
||||
from diagrams.onprem.client import Users
|
||||
from diagrams.onprem.security import Oauth
|
||||
from diagrams.onprem.inmemory import Redis
|
||||
from diagrams.programming.language import Python
|
||||
from diagrams.generic.compute import Rack
|
||||
from diagrams.generic.storage import Storage
|
||||
from diagrams.onprem.network import Nginx
|
||||
from diagrams.generic.database import SQL
|
||||
|
||||
with Diagram("Security and Authentication Architecture",
|
||||
filename="diagrams/08_security_architecture",
|
||||
show=False,
|
||||
direction="TB"):
|
||||
|
||||
# External Users
|
||||
users = Users("External Users\n• Students\n• Educators\n• Administrators")
|
||||
|
||||
# Security Perimeter
|
||||
with Cluster("Security Perimeter"):
|
||||
https_gateway = Nginx("HTTPS Gateway\n• SSL/TLS encryption\n• Certificate management\n• Secure headers")
|
||||
rate_limiting = Rack("Rate Limiting\n• Request throttling\n• DDoS protection\n• Abuse prevention")
|
||||
|
||||
# Authentication Layer
|
||||
with Cluster("Authentication System"):
|
||||
login_handler = Oauth("Login Handler\n• Credential validation\n• Multi-factor support\n• Brute force protection")
|
||||
|
||||
password_security = Python("Password Security\n• Hashing (bcrypt/scrypt)\n• Salt generation\n• Strength validation")
|
||||
|
||||
session_store = Redis("Session Store\n• Secure session tokens\n• Expiration management\n• Cross-site protection")
|
||||
|
||||
# Authorization Layer
|
||||
with Cluster("Authorization System"):
|
||||
role_manager = Python("Role Manager\n• Role definitions\n• Permission mapping\n• Access control")
|
||||
|
||||
permission_engine = Rack("Permission Engine\n• Route protection\n• Resource access\n• Action authorization")
|
||||
|
||||
with Cluster("User Roles & Permissions"):
|
||||
admin_permissions = Storage("Admin Permissions\n• Full system access\n• User management\n• Model updates\n• System configuration")
|
||||
|
||||
educator_permissions = Storage("Educator Permissions\n• Student analytics\n• Batch predictions\n• Reporting tools\n• Limited admin")
|
||||
|
||||
user_permissions = Storage("User Permissions\n• Personal predictions\n• Profile management\n• Basic analytics\n• Read-only access")
|
||||
|
||||
# Security Middleware
|
||||
with Cluster("Security Middleware"):
|
||||
auth_decorator = Python("Auth Decorator\n• @require_auth\n• Route protection\n• Session validation")
|
||||
|
||||
input_sanitization = Rack("Input Sanitization\n• XSS prevention\n• SQL injection protection\n• Data validation")
|
||||
|
||||
csrf_protection = Rack("CSRF Protection\n• Token validation\n• State verification\n• Request authentication")
|
||||
|
||||
# Application Security
|
||||
with Cluster("Application Security"):
|
||||
secure_storage = Storage("Secure Storage\n• Encrypted data\n• Access logs\n• Audit trails")
|
||||
|
||||
error_handling = Rack("Error Handling\n• Secure error messages\n• Log sanitization\n• Information disclosure prevention")
|
||||
|
||||
monitoring = SQL("Security Monitoring\n• Failed login attempts\n• Suspicious activity\n• Access pattern analysis")
|
||||
|
||||
# Data Protection
|
||||
with Cluster("Data Protection"):
|
||||
data_encryption = Storage("Data Encryption\n• At-rest encryption\n• In-transit encryption\n• Key management")
|
||||
|
||||
privacy_controls = Rack("Privacy Controls\n• Data anonymization\n• PII protection\n• Consent management")
|
||||
|
||||
backup_security = Storage("Backup Security\n• Encrypted backups\n• Access controls\n• Recovery procedures")
|
||||
|
||||
# Security Flow
|
||||
users >> https_gateway >> rate_limiting >> login_handler
|
||||
login_handler >> password_security >> session_store
|
||||
|
||||
session_store >> role_manager >> permission_engine
|
||||
|
||||
role_manager >> admin_permissions
|
||||
role_manager >> educator_permissions
|
||||
role_manager >> user_permissions
|
||||
|
||||
permission_engine >> auth_decorator >> input_sanitization
|
||||
input_sanitization >> csrf_protection
|
||||
|
||||
auth_decorator >> secure_storage
|
||||
csrf_protection >> error_handling
|
||||
error_handling >> monitoring
|
||||
|
||||
secure_storage >> data_encryption
|
||||
data_encryption >> privacy_controls
|
||||
privacy_controls >> backup_security
|
||||
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
Master Script to Generate All Architecture Diagrams
|
||||
Run this script to generate all diagram images at once
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
def generate_diagram(script_name):
|
||||
"""Generate a single diagram by running its Python script"""
|
||||
try:
|
||||
print(f"Generating {script_name}...")
|
||||
result = subprocess.run([sys.executable, script_name],
|
||||
capture_output=True, text=True, cwd="diagrams")
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f"✅ Successfully generated {script_name}")
|
||||
else:
|
||||
print(f"❌ Error generating {script_name}:")
|
||||
print(result.stderr)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Exception while generating {script_name}: {e}")
|
||||
|
||||
def main():
|
||||
"""Generate all architecture diagrams"""
|
||||
|
||||
# Check if diagrams library is installed
|
||||
try:
|
||||
import diagrams
|
||||
print("✅ Diagrams library found")
|
||||
except ImportError:
|
||||
print("❌ Diagrams library not found. Installing...")
|
||||
subprocess.run([sys.executable, "-m", "pip", "install", "-r", "diagrams/requirements.txt"])
|
||||
print("✅ Diagrams library installed")
|
||||
|
||||
# List of diagram scripts to generate
|
||||
diagram_scripts = [
|
||||
"00_system_overview.py",
|
||||
"01_data_flow_architecture.py",
|
||||
"02_eda_process_flow.py",
|
||||
"03_ml_pipeline_architecture.py",
|
||||
"04_application_architecture.py",
|
||||
"05_deployment_architecture.py",
|
||||
"06_feature_engineering_pipeline.py",
|
||||
"07_user_journey_flow.py",
|
||||
"08_security_architecture.py"
|
||||
]
|
||||
|
||||
print("🚀 Starting diagram generation...")
|
||||
print("=" * 50)
|
||||
|
||||
# Create diagrams directory if it doesn't exist
|
||||
Path("diagrams").mkdir(exist_ok=True)
|
||||
|
||||
# Generate each diagram
|
||||
for script in diagram_scripts:
|
||||
if os.path.exists(f"diagrams/{script}"):
|
||||
generate_diagram(script)
|
||||
else:
|
||||
print(f"⚠️ Script not found: {script}")
|
||||
|
||||
print("=" * 50)
|
||||
print("🎉 Diagram generation complete!")
|
||||
print("\nGenerated diagrams:")
|
||||
|
||||
# List generated PNG files
|
||||
diagrams_dir = Path("diagrams")
|
||||
png_files = list(diagrams_dir.glob("*.png"))
|
||||
|
||||
if png_files:
|
||||
for png_file in sorted(png_files):
|
||||
print(f" 📊 {png_file.name}")
|
||||
else:
|
||||
print(" ⚠️ No PNG files found. Check for errors above.")
|
||||
|
||||
print(f"\nTotal diagrams generated: {len(png_files)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -12,4 +12,5 @@ dependencies = [
|
||||
"plotly>=6.3.0",
|
||||
"ruff>=0.13.2",
|
||||
"scikit-learn>=1.7.2",
|
||||
"streamlit>=1.50.0",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
"""
|
||||
Form input components for the Student Success Predictor
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
from typing import Dict, Any, Tuple
|
||||
from ..config.mappings import create_feature_mappings, PRESET_CONFIGURATIONS, ACADEMIC_DEFAULTS
|
||||
|
||||
|
||||
def render_preset_selector() -> str:
|
||||
"""Render the preset selector section"""
|
||||
st.markdown('<div class="sidebar-section">', unsafe_allow_html=True)
|
||||
st.markdown("### 🚀 Quick Start")
|
||||
preset = st.selectbox(
|
||||
"Load Sample Data",
|
||||
["Custom Entry", "Typical Graduate Student", "At-Risk Student", "International Student"],
|
||||
help="Choose a preset to quickly fill common scenarios"
|
||||
)
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
if preset != "Custom Entry":
|
||||
st.markdown(
|
||||
f'<div class="success-box">✅ Loading <strong>{preset}</strong> preset values. You can modify any field below.</div>',
|
||||
unsafe_allow_html=True)
|
||||
|
||||
return preset
|
||||
|
||||
|
||||
def render_personal_info_section(preset: str, feature_maps: Dict) -> Dict[str, Any]:
|
||||
"""Render personal information input section"""
|
||||
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
|
||||
st.markdown("### 👤 Personal Information")
|
||||
st.markdown('<div class="info-box">Basic demographic information about the student</div>', unsafe_allow_html=True)
|
||||
|
||||
defaults = PRESET_CONFIGURATIONS[preset]
|
||||
|
||||
marital_status = st.selectbox(
|
||||
"Marital Status",
|
||||
options=list(feature_maps['marital_status'].keys()),
|
||||
format_func=lambda x: feature_maps['marital_status'][x],
|
||||
index=list(feature_maps['marital_status'].keys()).index(defaults["marital"])
|
||||
)
|
||||
|
||||
gender = st.selectbox(
|
||||
"Gender",
|
||||
[0, 1],
|
||||
format_func=lambda x: "Female" if x == 0 else "Male",
|
||||
index=defaults["gender"]
|
||||
)
|
||||
|
||||
age_at_enrollment = st.number_input(
|
||||
"Age at Enrollment",
|
||||
min_value=17,
|
||||
max_value=70,
|
||||
value=defaults["age"],
|
||||
step=1
|
||||
)
|
||||
|
||||
nationality = st.selectbox(
|
||||
"Nationality",
|
||||
[1, 2, 6, 11, 13, 14, 17, 21, 22, 24, 25, 26, 32, 41, 62, 100, 101, 103, 105, 108, 109],
|
||||
index=0
|
||||
)
|
||||
|
||||
return {
|
||||
'Marital status': marital_status,
|
||||
'Gender': gender,
|
||||
'Age at enrollment': age_at_enrollment,
|
||||
'Nationality': nationality
|
||||
}
|
||||
|
||||
|
||||
def render_academic_background_section(preset: str, feature_maps: Dict) -> Dict[str, Any]:
|
||||
"""Render academic background input section"""
|
||||
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
|
||||
st.markdown("### 🎓 Academic Background")
|
||||
st.markdown('<div class="info-box">Information about the student\'s application and academic history</div>',
|
||||
unsafe_allow_html=True)
|
||||
|
||||
defaults = PRESET_CONFIGURATIONS[preset]
|
||||
|
||||
application_mode = st.selectbox(
|
||||
"Application Mode",
|
||||
options=list(feature_maps['application_mode'].keys()),
|
||||
format_func=lambda x: feature_maps['application_mode'].get(x, f"Mode {x}"),
|
||||
index=list(feature_maps['application_mode'].keys()).index(defaults["app_mode"]) if defaults["app_mode"] in
|
||||
feature_maps[
|
||||
'application_mode'] else 0
|
||||
)
|
||||
|
||||
application_order = st.selectbox(
|
||||
"Application Order (1st choice, 2nd choice, etc.)",
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
index=0
|
||||
)
|
||||
|
||||
course = st.selectbox(
|
||||
"Course",
|
||||
options=list(feature_maps['course'].keys()),
|
||||
format_func=lambda x: feature_maps['course'].get(x, f"Course {x}"),
|
||||
index=list(feature_maps['course'].keys()).index(defaults["course"]) if defaults["course"] in feature_maps[
|
||||
'course'] else 0
|
||||
)
|
||||
|
||||
daytime_evening = st.selectbox(
|
||||
"Attendance",
|
||||
[0, 1],
|
||||
format_func=lambda x: "Evening" if x == 0 else "Daytime"
|
||||
)
|
||||
|
||||
previous_qualification = st.selectbox(
|
||||
"Previous Qualification",
|
||||
options=list(feature_maps['qualification'].keys()),
|
||||
format_func=lambda x: feature_maps['qualification'].get(x, f"Qualification {x}")
|
||||
)
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
previous_qualification_grade = st.number_input(
|
||||
"Previous Qualification Grade",
|
||||
min_value=95,
|
||||
max_value=190,
|
||||
value=defaults["prev_grade"],
|
||||
step=1
|
||||
)
|
||||
with col2:
|
||||
admission_grade = st.number_input(
|
||||
"Admission Grade",
|
||||
min_value=95,
|
||||
max_value=190,
|
||||
value=defaults["adm_grade"],
|
||||
step=1
|
||||
)
|
||||
|
||||
return {
|
||||
'Application mode': application_mode,
|
||||
'Application order': application_order,
|
||||
'Course': course,
|
||||
'Daytime/evening attendance': daytime_evening,
|
||||
'Previous qualification': previous_qualification,
|
||||
'Previous qualification (grade)': previous_qualification_grade,
|
||||
'Admission grade': admission_grade
|
||||
}
|
||||
|
||||
|
||||
def render_family_background_section(feature_maps: Dict) -> Dict[str, Any]:
|
||||
"""Render family background input section"""
|
||||
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
|
||||
st.markdown("### 👨👩👧👦 Family Background")
|
||||
st.markdown('<div class="info-box">Parent education and occupation information</div>', unsafe_allow_html=True)
|
||||
|
||||
mothers_qualification = st.selectbox(
|
||||
"Mother's Qualification",
|
||||
options=list(feature_maps['qualification'].keys()),
|
||||
format_func=lambda x: feature_maps['qualification'].get(x, f"Qualification {x}")
|
||||
)
|
||||
|
||||
fathers_qualification = st.selectbox(
|
||||
"Father's Qualification",
|
||||
options=list(feature_maps['qualification'].keys()),
|
||||
format_func=lambda x: feature_maps['qualification'].get(x, f"Qualification {x}")
|
||||
)
|
||||
|
||||
mothers_occupation = st.selectbox(
|
||||
"Mother's Occupation",
|
||||
options=list(feature_maps['occupation'].keys()),
|
||||
format_func=lambda x: feature_maps['occupation'].get(x, f"Occupation {x}")
|
||||
)
|
||||
|
||||
fathers_occupation = st.selectbox(
|
||||
"Father's Occupation",
|
||||
options=list(feature_maps['occupation'].keys()),
|
||||
format_func=lambda x: feature_maps['occupation'].get(x, f"Occupation {x}")
|
||||
)
|
||||
|
||||
return {
|
||||
'Mothers qualification': mothers_qualification,
|
||||
'Fathers qualification': fathers_qualification,
|
||||
'Mothers occupation': mothers_occupation,
|
||||
'Fathers occupation': fathers_occupation
|
||||
}
|
||||
|
||||
|
||||
def render_financial_social_section() -> Dict[str, Any]:
|
||||
"""Render financial and social factors input section"""
|
||||
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
|
||||
st.markdown("### 💰 Financial & Social")
|
||||
st.markdown('<div class="info-box">Student financial status and special circumstances</div>',
|
||||
unsafe_allow_html=True)
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
displaced = st.radio(
|
||||
"Displaced Student",
|
||||
[0, 1],
|
||||
format_func=lambda x: "No" if x == 0 else "Yes",
|
||||
horizontal=True
|
||||
)
|
||||
educational_special_needs = st.radio(
|
||||
"Educational Special Needs",
|
||||
[0, 1],
|
||||
format_func=lambda x: "No" if x == 0 else "Yes",
|
||||
horizontal=True
|
||||
)
|
||||
debtor = st.radio(
|
||||
"Has Outstanding Debts",
|
||||
[0, 1],
|
||||
format_func=lambda x: "No" if x == 0 else "Yes",
|
||||
horizontal=True
|
||||
)
|
||||
with col2:
|
||||
tuition_fees_up_to_date = st.radio(
|
||||
"Tuition Fees Up to Date",
|
||||
[0, 1],
|
||||
format_func=lambda x: "No" if x == 0 else "Yes",
|
||||
horizontal=True
|
||||
)
|
||||
scholarship_holder = st.radio(
|
||||
"Scholarship Holder",
|
||||
[0, 1],
|
||||
format_func=lambda x: "No" if x == 0 else "Yes",
|
||||
horizontal=True
|
||||
)
|
||||
international = st.radio(
|
||||
"International Student",
|
||||
[0, 1],
|
||||
format_func=lambda x: "No" if x == 0 else "Yes",
|
||||
horizontal=True
|
||||
)
|
||||
|
||||
return {
|
||||
'Displaced': displaced,
|
||||
'Educational special needs': educational_special_needs,
|
||||
'Debtor': debtor,
|
||||
'Tuition fees up to date': tuition_fees_up_to_date,
|
||||
'Scholarship holder': scholarship_holder,
|
||||
'International': international
|
||||
}
|
||||
|
||||
|
||||
def render_academic_performance_section(preset: str) -> Dict[str, Any]:
|
||||
"""Render academic performance input sections"""
|
||||
academic_defaults = ACADEMIC_DEFAULTS[preset]
|
||||
|
||||
# 1st Semester
|
||||
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
|
||||
st.markdown("### 📚 1st Semester Performance")
|
||||
st.markdown(
|
||||
'<div class="warning-box">⚠️ <strong>Important:</strong> Academic performance is a key predictor of student success</div>',
|
||||
unsafe_allow_html=True)
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
curricular_units_1st_sem_enrolled = st.number_input(
|
||||
"Enrolled Units", min_value=0, max_value=26,
|
||||
value=academic_defaults["1st_sem"]["enrolled"], step=1,
|
||||
key="1st_enrolled", help="Number of courses registered for"
|
||||
)
|
||||
curricular_units_1st_sem_approved = st.number_input(
|
||||
"Approved Units", min_value=0, max_value=26,
|
||||
value=academic_defaults["1st_sem"]["approved"], step=1,
|
||||
key="1st_approved", help="Number of courses passed"
|
||||
)
|
||||
curricular_units_1st_sem_credited = st.number_input(
|
||||
"Credited Units", min_value=0, max_value=26,
|
||||
value=academic_defaults["1st_sem"]["credited"], step=1,
|
||||
key="1st_credited", help="Credits from previous education"
|
||||
)
|
||||
with col2:
|
||||
curricular_units_1st_sem_evaluations = st.number_input(
|
||||
"Evaluations", min_value=0, max_value=45,
|
||||
value=academic_defaults["1st_sem"]["evaluations"], step=1,
|
||||
key="1st_evaluations", help="Number of exams taken"
|
||||
)
|
||||
curricular_units_1st_sem_grade = st.number_input(
|
||||
"Average Grade (0-20)", min_value=0, max_value=20,
|
||||
value=academic_defaults["1st_sem"]["grade"], step=1,
|
||||
key="1st_grade", help="Average grade on 0-20 scale"
|
||||
)
|
||||
curricular_units_1st_sem_without_evaluations = st.number_input(
|
||||
"Without Evaluations", min_value=0, max_value=14,
|
||||
value=academic_defaults["1st_sem"]["without"], step=1,
|
||||
key="1st_without", help="Courses registered but not evaluated"
|
||||
)
|
||||
|
||||
# 2nd Semester
|
||||
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
|
||||
st.markdown("### 📖 2nd Semester Performance")
|
||||
st.markdown('<div class="info-box">Second semester data helps improve prediction accuracy</div>',
|
||||
unsafe_allow_html=True)
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
curricular_units_2nd_sem_enrolled = st.number_input(
|
||||
"Enrolled Units", min_value=0, max_value=23,
|
||||
value=academic_defaults["2nd_sem"]["enrolled"], step=1,
|
||||
key="2nd_enrolled", help="Number of courses registered for"
|
||||
)
|
||||
curricular_units_2nd_sem_approved = st.number_input(
|
||||
"Approved Units", min_value=0, max_value=20,
|
||||
value=academic_defaults["2nd_sem"]["approved"], step=1,
|
||||
key="2nd_approved", help="Number of courses passed"
|
||||
)
|
||||
curricular_units_2nd_sem_credited = st.number_input(
|
||||
"Credited Units", min_value=0, max_value=20,
|
||||
value=academic_defaults["2nd_sem"]["credited"], step=1,
|
||||
key="2nd_credited", help="Credits from previous education"
|
||||
)
|
||||
with col2:
|
||||
curricular_units_2nd_sem_evaluations = st.number_input(
|
||||
"Evaluations", min_value=0, max_value=33,
|
||||
value=academic_defaults["2nd_sem"]["evaluations"], step=1,
|
||||
key="2nd_evaluations", help="Number of exams taken"
|
||||
)
|
||||
curricular_units_2nd_sem_grade = st.number_input(
|
||||
"Average Grade (0-20)", min_value=0, max_value=20,
|
||||
value=academic_defaults["2nd_sem"]["grade"], step=1,
|
||||
key="2nd_grade", help="Average grade on 0-20 scale"
|
||||
)
|
||||
curricular_units_2nd_sem_without_evaluations = st.number_input(
|
||||
"Without Evaluations", min_value=0, max_value=12,
|
||||
value=academic_defaults["2nd_sem"]["without"], step=1,
|
||||
key="2nd_without", help="Courses registered but not evaluated"
|
||||
)
|
||||
|
||||
return {
|
||||
'Curricular units 1st sem (credited)': curricular_units_1st_sem_credited,
|
||||
'Curricular units 1st sem (enrolled)': curricular_units_1st_sem_enrolled,
|
||||
'Curricular units 1st sem (evaluations)': curricular_units_1st_sem_evaluations,
|
||||
'Curricular units 1st sem (approved)': curricular_units_1st_sem_approved,
|
||||
'Curricular units 1st sem (grade)': curricular_units_1st_sem_grade,
|
||||
'Curricular units 1st sem (without evaluations)': curricular_units_1st_sem_without_evaluations,
|
||||
'Curricular units 2nd sem (credited)': curricular_units_2nd_sem_credited,
|
||||
'Curricular units 2nd sem (enrolled)': curricular_units_2nd_sem_enrolled,
|
||||
'Curricular units 2nd sem (evaluations)': curricular_units_2nd_sem_evaluations,
|
||||
'Curricular units 2nd sem (approved)': curricular_units_2nd_sem_approved,
|
||||
'Curricular units 2nd sem (grade)': curricular_units_2nd_sem_grade,
|
||||
'Curricular units 2nd sem (without evaluations)': curricular_units_2nd_sem_without_evaluations
|
||||
}
|
||||
|
||||
|
||||
def render_economic_indicators_section() -> Dict[str, Any]:
|
||||
"""Render economic indicators input section"""
|
||||
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
|
||||
st.markdown("### 📊 Economic Indicators")
|
||||
st.markdown(
|
||||
'<div class="info-box">📈 Economic context data - typically set by institution based on current conditions</div>',
|
||||
unsafe_allow_html=True)
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
unemployment_rate = st.number_input(
|
||||
"Unemployment Rate (%)",
|
||||
min_value=7.0, max_value=16.2, value=10.8, step=0.1, format="%.1f"
|
||||
)
|
||||
with col2:
|
||||
inflation_rate = st.number_input(
|
||||
"Inflation Rate (%)",
|
||||
min_value=-0.8, max_value=3.7, value=1.4, step=0.1, format="%.1f"
|
||||
)
|
||||
with col3:
|
||||
gdp = st.number_input(
|
||||
"GDP Growth",
|
||||
min_value=-4.06, max_value=3.51, value=1.74, step=0.1, format="%.2f"
|
||||
)
|
||||
|
||||
return {
|
||||
'Unemployment rate': unemployment_rate,
|
||||
'Inflation rate': inflation_rate,
|
||||
'GDP': gdp
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
"""
|
||||
Prediction and results display components
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import plotly.express as px
|
||||
import plotly.graph_objects as go
|
||||
import time
|
||||
import json
|
||||
from typing import Dict, Any, Tuple, List
|
||||
|
||||
|
||||
def render_prediction_section(model, preprocessing_info, user_input: Dict[str, Any]) -> None:
|
||||
"""Render the prediction section with enhanced animations"""
|
||||
from ..utils.model_utils import preprocess_input, generate_recommendations
|
||||
|
||||
# Prediction button with enhanced styling
|
||||
predict_button = st.button("🔬 Analyze Student Data", type="primary", use_container_width=True)
|
||||
|
||||
if predict_button:
|
||||
# Enhanced loading animation
|
||||
progress_bar = st.progress(0)
|
||||
status_text = st.empty()
|
||||
|
||||
# Simulate processing steps
|
||||
steps = [
|
||||
("Loading student data...", 20),
|
||||
("Preprocessing features...", 40),
|
||||
("Running machine learning model...", 70),
|
||||
("Calculating prediction confidence...", 90),
|
||||
("Finalizing results...", 100)
|
||||
]
|
||||
|
||||
for step, progress in steps:
|
||||
status_text.text(step)
|
||||
progress_bar.progress(progress)
|
||||
time.sleep(0.3)
|
||||
|
||||
# Clear progress indicators
|
||||
progress_bar.empty()
|
||||
status_text.empty()
|
||||
|
||||
# Preprocess input
|
||||
processed_input = preprocess_input(user_input, preprocessing_info)
|
||||
|
||||
# Make prediction
|
||||
prediction = model.predict(processed_input)[0]
|
||||
prediction_proba = model.predict_proba(processed_input)[0]
|
||||
|
||||
# Get prediction label
|
||||
target_reverse_mapping = preprocessing_info['target_reverse_mapping']
|
||||
predicted_outcome = target_reverse_mapping[prediction]
|
||||
|
||||
# Store in session state for export
|
||||
st.session_state['last_prediction'] = {
|
||||
'outcome': predicted_outcome,
|
||||
'probabilities': prediction_proba,
|
||||
'timestamp': time.time()
|
||||
}
|
||||
|
||||
# Enhanced prediction display with confidence indicators
|
||||
confidence_level = max(prediction_proba)
|
||||
confidence_text = "High" if confidence_level > 0.7 else "Medium" if confidence_level > 0.5 else "Low"
|
||||
|
||||
# Display prediction with enhanced styling and confidence
|
||||
if predicted_outcome == "Graduate":
|
||||
st.markdown(f'''<div class="prediction-box graduate-box fade-in">
|
||||
🎓 GRADUATE
|
||||
<br><small style="font-size: 1rem; opacity: 0.9;">Confidence: {confidence_level:.1%} ({confidence_text})</small>
|
||||
</div>''', unsafe_allow_html=True)
|
||||
st.balloons()
|
||||
elif predicted_outcome == "Dropout":
|
||||
st.markdown(f'''<div class="prediction-box dropout-box fade-in">
|
||||
⚠️ AT RISK OF DROPOUT
|
||||
<br><small style="font-size: 1rem; opacity: 0.9;">Confidence: {confidence_level:.1%} ({confidence_text})</small>
|
||||
</div>''', unsafe_allow_html=True)
|
||||
else:
|
||||
st.markdown(f'''<div class="prediction-box enrolled-box fade-in">
|
||||
📚 LIKELY TO STAY ENROLLED
|
||||
<br><small style="font-size: 1rem; opacity: 0.9;">Confidence: {confidence_level:.1%} ({confidence_text})</small>
|
||||
</div>''', unsafe_allow_html=True)
|
||||
|
||||
# Add personalized recommendations
|
||||
render_recommendations(predicted_outcome, user_input)
|
||||
|
||||
# Display probability charts
|
||||
render_probability_charts(prediction_proba)
|
||||
|
||||
|
||||
def render_recommendations(predicted_outcome: str, user_input: Dict[str, Any]) -> None:
|
||||
"""Render personalized recommendations section"""
|
||||
from ..utils.model_utils import generate_recommendations
|
||||
|
||||
st.markdown("### 💡 Personalized Recommendations")
|
||||
|
||||
recommendations = generate_recommendations(predicted_outcome, user_input)
|
||||
|
||||
if predicted_outcome == "Graduate":
|
||||
st.markdown(
|
||||
'<div class="success-box">🎯 <strong>Success Indicators:</strong> This student shows strong potential for graduation. Continue supporting their academic journey.</div>',
|
||||
unsafe_allow_html=True)
|
||||
elif predicted_outcome == "Dropout":
|
||||
if recommendations:
|
||||
rec_text = '<br>'.join(recommendations)
|
||||
st.markdown(f'<div class="warning-box">🎯 <strong>Intervention Strategies:</strong><br>{rec_text}</div>',
|
||||
unsafe_allow_html=True)
|
||||
else:
|
||||
st.markdown(
|
||||
'<div class="info-box">📈 <strong>Monitoring Needed:</strong> This student may benefit from regular check-ins and academic support.</div>',
|
||||
unsafe_allow_html=True)
|
||||
|
||||
|
||||
def render_probability_charts(prediction_proba: List[float]) -> None:
|
||||
"""Render prediction probability charts and metrics"""
|
||||
st.markdown("### 📊 Prediction Confidence")
|
||||
|
||||
# Create probability dataframe
|
||||
import pandas as pd
|
||||
prob_df = pd.DataFrame({
|
||||
'Outcome': ['Dropout', 'Enrolled', 'Graduate'],
|
||||
'Probability': prediction_proba
|
||||
})
|
||||
|
||||
# Create probability chart
|
||||
fig = px.bar(
|
||||
prob_df,
|
||||
x='Outcome',
|
||||
y='Probability',
|
||||
color='Outcome',
|
||||
color_discrete_map={
|
||||
'Graduate': '#9ccfd8', # Rose Pine foam
|
||||
'Dropout': '#eb6f92', # Rose Pine love
|
||||
'Enrolled': '#f6c177' # Rose Pine gold
|
||||
},
|
||||
title="Prediction Probabilities"
|
||||
)
|
||||
fig.update_layout(
|
||||
showlegend=False,
|
||||
plot_bgcolor='rgba(0,0,0,0)',
|
||||
paper_bgcolor='rgba(0,0,0,0)'
|
||||
)
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
# Display detailed probabilities in cards
|
||||
col_a, col_b, col_c = st.columns(3)
|
||||
|
||||
with col_a:
|
||||
st.markdown(f"""
|
||||
<div class="metric-card">
|
||||
<h4>Graduate</h4>
|
||||
<h2>{prediction_proba[2]:.1%}</h2>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with col_b:
|
||||
st.markdown(f"""
|
||||
<div class="metric-card">
|
||||
<h4>Dropout</h4>
|
||||
<h2>{prediction_proba[0]:.1%}</h2>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with col_c:
|
||||
st.markdown(f"""
|
||||
<div class="metric-card">
|
||||
<h4>Enrolled</h4>
|
||||
<h2>{prediction_proba[1]:.1%}</h2>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
|
||||
def render_model_info_sidebar(user_input: Dict[str, Any]) -> None:
|
||||
"""Render model information and validation in sidebar"""
|
||||
from ..utils.model_utils import validate_input
|
||||
from ..config.theme import MODEL_METRICS
|
||||
|
||||
st.markdown('<h2 class="sub-header">📊 Model Insights</h2>', unsafe_allow_html=True)
|
||||
|
||||
# Model information
|
||||
st.markdown('<div class="info-box">', unsafe_allow_html=True)
|
||||
st.markdown(f"""
|
||||
**🤖 Model Details:**
|
||||
- **Algorithm:** Random Forest Classifier
|
||||
- **Features:** 36 student characteristics
|
||||
- **Training Accuracy:** 100%
|
||||
- **Test Accuracy:** {MODEL_METRICS['accuracy']}%
|
||||
- **Classes:** Graduate, Dropout, Enrolled
|
||||
""")
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
# Input validation feedback
|
||||
st.markdown("### 🔍 Input Validation")
|
||||
validation_issues = validate_input(user_input)
|
||||
|
||||
if validation_issues:
|
||||
for issue in validation_issues:
|
||||
st.warning(issue)
|
||||
else:
|
||||
st.success("✅ All inputs look valid!")
|
||||
|
||||
# Model performance visualization
|
||||
st.markdown("### Model Performance")
|
||||
|
||||
metrics = ['Accuracy', 'Precision', 'Recall', 'F1-Score']
|
||||
values = [MODEL_METRICS['accuracy'], MODEL_METRICS['precision'],
|
||||
MODEL_METRICS['recall'], MODEL_METRICS['f1_score']]
|
||||
|
||||
fig_perf = go.Figure(data=go.Scatterpolar(
|
||||
r=values,
|
||||
theta=metrics,
|
||||
fill='toself',
|
||||
line_color='#c4a7e7' # Rose Pine iris
|
||||
))
|
||||
|
||||
fig_perf.update_layout(
|
||||
polar=dict(
|
||||
radialaxis=dict(
|
||||
visible=True,
|
||||
range=[0, 100]
|
||||
)),
|
||||
showlegend=False,
|
||||
title="Model Performance Metrics",
|
||||
height=300,
|
||||
plot_bgcolor='rgba(0,0,0,0)',
|
||||
paper_bgcolor='rgba(0,0,0,0)'
|
||||
)
|
||||
|
||||
st.plotly_chart(fig_perf, use_container_width=True)
|
||||
|
||||
|
||||
def render_export_section(user_input: Dict[str, Any]) -> None:
|
||||
"""Render export functionality"""
|
||||
st.markdown("### 📥 Export Results")
|
||||
|
||||
if st.session_state.get('last_prediction'):
|
||||
export_data = {
|
||||
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'prediction': st.session_state['last_prediction']['outcome'],
|
||||
'confidence': f"{max(st.session_state['last_prediction']['probabilities']):.1%}",
|
||||
'student_data': user_input
|
||||
}
|
||||
|
||||
# Convert to JSON for download
|
||||
json_str = json.dumps(export_data, indent=2, ensure_ascii=False)
|
||||
|
||||
st.download_button(
|
||||
label="📁 Download Prediction Report",
|
||||
data=json_str,
|
||||
file_name=f"student_prediction_{time.strftime('%Y%m%d_%H%M%S')}.json",
|
||||
mime="application/json"
|
||||
)
|
||||
|
||||
|
||||
def render_footer() -> None:
|
||||
"""Render enhanced footer with project information"""
|
||||
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
st.markdown("**📊 Data Source**")
|
||||
st.caption("UCI ML Repository - Student Academic Success")
|
||||
|
||||
with col2:
|
||||
st.markdown("**🔬 Model Info**")
|
||||
st.caption("Random Forest with 93.84% accuracy")
|
||||
|
||||
with col3:
|
||||
st.markdown("**🛠️ Built With**")
|
||||
st.caption("Streamlit • scikit-learn • Plotly")
|
||||
|
||||
st.markdown("---")
|
||||
st.markdown(
|
||||
'<p style="text-align: center; color: #6c757d; font-size: 0.9rem;">🎓 Empowering educational institutions with data-driven insights for student success</p>',
|
||||
unsafe_allow_html=True)
|
||||
@@ -0,0 +1,308 @@
|
||||
"""
|
||||
Authentication module for Student Success Predictor
|
||||
Provides secure login/logout functionality with session management
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, Optional, Tuple
|
||||
from pathlib import Path
|
||||
|
||||
# Path to store user credentials (in production, use a proper database)
|
||||
USERS_FILE = Path("users.json")
|
||||
|
||||
# Default admin user (change password in production!)
|
||||
DEFAULT_USERS = {
|
||||
"admin": {
|
||||
"password_hash": "240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9", # "admin123"
|
||||
"role": "admin",
|
||||
"name": "Administrator"
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
"""Hash a password using SHA-256"""
|
||||
return hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
|
||||
def load_users() -> Dict:
|
||||
"""Load users from file or create default users"""
|
||||
if USERS_FILE.exists():
|
||||
try:
|
||||
with open(USERS_FILE, 'r') as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
# Create default users file
|
||||
save_users(DEFAULT_USERS)
|
||||
return DEFAULT_USERS
|
||||
|
||||
|
||||
def save_users(users: Dict) -> None:
|
||||
"""Save users to file"""
|
||||
with open(USERS_FILE, 'w') as f:
|
||||
json.dump(users, f, indent=2)
|
||||
|
||||
|
||||
def verify_credentials(username: str, password: str) -> Tuple[bool, Optional[Dict]]:
|
||||
"""Verify user credentials"""
|
||||
users = load_users()
|
||||
|
||||
if username in users:
|
||||
stored_hash = users[username]["password_hash"]
|
||||
input_hash = hash_password(password)
|
||||
|
||||
if stored_hash == input_hash:
|
||||
return True, users[username]
|
||||
|
||||
return False, None
|
||||
|
||||
|
||||
def init_session_state():
|
||||
"""Initialize session state for authentication"""
|
||||
if 'authenticated' not in st.session_state:
|
||||
st.session_state.authenticated = False
|
||||
if 'username' not in st.session_state:
|
||||
st.session_state.username = None
|
||||
if 'user_role' not in st.session_state:
|
||||
st.session_state.user_role = None
|
||||
if 'user_name' not in st.session_state:
|
||||
st.session_state.user_name = None
|
||||
|
||||
|
||||
def login_form():
|
||||
"""Render login form with Rose Pine styling"""
|
||||
st.markdown("""
|
||||
<style>
|
||||
.login-container {
|
||||
max-width: 400px;
|
||||
margin: 2rem auto;
|
||||
padding: 2rem;
|
||||
background: linear-gradient(135deg, var(--rp-surface), var(--rp-overlay));
|
||||
border-radius: 16px;
|
||||
border: 2px solid var(--rp-iris);
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
}
|
||||
.login-header {
|
||||
text-align: center;
|
||||
color: var(--rp-text);
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 2rem;
|
||||
background: linear-gradient(135deg, var(--rp-iris), var(--rp-foam));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
.login-info {
|
||||
background: linear-gradient(135deg, var(--rp-overlay), var(--rp-surface));
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--rp-gold);
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--rp-text);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with st.container():
|
||||
st.markdown('<div class="login-container">', unsafe_allow_html=True)
|
||||
|
||||
st.markdown('<h1 class="login-header">🔐 Student Success Predictor</h1>', unsafe_allow_html=True)
|
||||
|
||||
# Demo credentials info
|
||||
st.markdown("""
|
||||
<div class="login-info">
|
||||
<strong>Demo Credentials:</strong><br>
|
||||
👨💼 Admin: <code>admin</code> / <code>admin123</code><br>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with st.form("login_form"):
|
||||
username = st.text_input("Username", placeholder="Enter your username")
|
||||
password = st.text_input("Password", type="password", placeholder="Enter your password")
|
||||
|
||||
col1, col2, col3 = st.columns([1, 2, 1])
|
||||
with col2:
|
||||
login_button = st.form_submit_button("🚀 Login", use_container_width=True)
|
||||
|
||||
if login_button:
|
||||
if username and password:
|
||||
is_valid, user_info = verify_credentials(username, password)
|
||||
|
||||
if is_valid:
|
||||
st.session_state.authenticated = True
|
||||
st.session_state.username = username
|
||||
st.session_state.user_role = user_info["role"]
|
||||
st.session_state.user_name = user_info["name"]
|
||||
st.success(f"Welcome back, {user_info['name']}!")
|
||||
st.rerun()
|
||||
else:
|
||||
st.error("❌ Invalid username or password")
|
||||
else:
|
||||
st.warning("⚠️ Please enter both username and password")
|
||||
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
|
||||
def logout():
|
||||
"""Logout user and clear session"""
|
||||
st.session_state.authenticated = False
|
||||
st.session_state.username = None
|
||||
st.session_state.user_role = None
|
||||
st.session_state.user_name = None
|
||||
st.rerun()
|
||||
|
||||
|
||||
def render_user_info():
|
||||
"""Render user information in sidebar"""
|
||||
if st.session_state.authenticated:
|
||||
with st.sidebar:
|
||||
st.markdown("---")
|
||||
st.markdown("### 👤 User Information")
|
||||
|
||||
# User info card
|
||||
st.markdown(f"""
|
||||
<div style="
|
||||
background: linear-gradient(135deg, var(--rp-surface), var(--rp-overlay));
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid var(--rp-iris);
|
||||
margin-bottom: 1rem;
|
||||
">
|
||||
<strong>👋 {st.session_state.user_name}</strong><br>
|
||||
<small>Role: {st.session_state.user_role.title()}</small><br>
|
||||
<small>User: @{st.session_state.username}</small>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Logout button
|
||||
if st.button("🚪 Logout", type="secondary", use_container_width=True):
|
||||
logout()
|
||||
|
||||
|
||||
def require_auth(func):
|
||||
"""Decorator to require authentication for functions"""
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
init_session_state()
|
||||
|
||||
if not st.session_state.authenticated:
|
||||
login_form()
|
||||
return None
|
||||
|
||||
# Add user info to sidebar
|
||||
render_user_info()
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def check_role_permission(required_role: str = None) -> bool:
|
||||
"""Check if current user has required role"""
|
||||
if not st.session_state.authenticated:
|
||||
return False
|
||||
|
||||
if required_role is None:
|
||||
return True
|
||||
|
||||
user_role = st.session_state.user_role
|
||||
|
||||
# Define role hierarchy (admin > educator > user)
|
||||
role_hierarchy = {
|
||||
"admin": 3,
|
||||
"educator": 2,
|
||||
"user": 1
|
||||
}
|
||||
|
||||
current_level = role_hierarchy.get(user_role, 0)
|
||||
required_level = role_hierarchy.get(required_role, 0)
|
||||
|
||||
return current_level >= required_level
|
||||
|
||||
|
||||
def add_user(username: str, password: str, name: str, role: str = "user") -> bool:
|
||||
"""Add a new user (admin only)"""
|
||||
if not check_role_permission("admin"):
|
||||
return False
|
||||
|
||||
users = load_users()
|
||||
|
||||
if username in users:
|
||||
return False # User already exists
|
||||
|
||||
users[username] = {
|
||||
"password_hash": hash_password(password),
|
||||
"role": role,
|
||||
"name": name
|
||||
}
|
||||
|
||||
save_users(users)
|
||||
return True
|
||||
|
||||
|
||||
def render_admin_panel():
|
||||
"""Render admin panel for user management"""
|
||||
if not check_role_permission("admin"):
|
||||
st.error("🚫 Access denied. Admin privileges required.")
|
||||
return
|
||||
|
||||
st.markdown("### 👨💼 Admin Panel")
|
||||
|
||||
with st.expander("👥 User Management"):
|
||||
st.markdown("#### Add New User")
|
||||
|
||||
with st.form("add_user_form"):
|
||||
new_username = st.text_input("Username")
|
||||
new_password = st.text_input("Password", type="password")
|
||||
new_name = st.text_input("Full Name")
|
||||
new_role = st.selectbox("Role", ["user", "educator", "admin"])
|
||||
|
||||
if st.form_submit_button("➕ Add User"):
|
||||
if all([new_username, new_password, new_name]):
|
||||
if add_user(new_username, new_password, new_name, new_role):
|
||||
st.success(f"✅ User '{new_username}' added successfully!")
|
||||
else:
|
||||
st.error("❌ User already exists or operation failed")
|
||||
else:
|
||||
st.warning("⚠️ Please fill all fields")
|
||||
|
||||
# Display existing users
|
||||
st.markdown("#### Existing Users")
|
||||
users = load_users()
|
||||
|
||||
for username, info in users.items():
|
||||
col1, col2, col3 = st.columns([2, 1, 1])
|
||||
with col1:
|
||||
st.text(f"👤 {info['name']} (@{username})")
|
||||
with col2:
|
||||
st.text(f"🏷️ {info['role']}")
|
||||
with col3:
|
||||
if username != st.session_state.username: # Can't delete self
|
||||
if st.button("🗑️", key=f"del_{username}", help="Delete user"):
|
||||
del users[username]
|
||||
save_users(users)
|
||||
st.rerun()
|
||||
|
||||
|
||||
# Session timeout functionality
|
||||
def check_session_timeout(timeout_minutes: int = 30):
|
||||
"""Check if session has timed out"""
|
||||
import time
|
||||
|
||||
if 'last_activity' not in st.session_state:
|
||||
st.session_state.last_activity = time.time()
|
||||
|
||||
current_time = time.time()
|
||||
time_diff = (current_time - st.session_state.last_activity) / 60 # Convert to minutes
|
||||
|
||||
if time_diff > timeout_minutes and st.session_state.authenticated:
|
||||
st.warning(f"⏰ Session expired after {timeout_minutes} minutes of inactivity")
|
||||
logout()
|
||||
else:
|
||||
st.session_state.last_activity = current_time
|
||||
@@ -0,0 +1,112 @@
|
||||
"""
|
||||
Model loading and data preprocessing utilities
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import pandas as pd
|
||||
import pickle
|
||||
from typing import Dict, Any, List
|
||||
|
||||
|
||||
@st.cache_data
|
||||
def load_model_and_info():
|
||||
"""Load the trained model and preprocessing information"""
|
||||
try:
|
||||
with open('random_forest_model.pkl', 'rb') as f:
|
||||
model = pickle.load(f)
|
||||
|
||||
with open('preprocessing_info.pkl', 'rb') as f:
|
||||
preprocessing_info = pickle.load(f)
|
||||
|
||||
with open('feature_names.pkl', 'rb') as f:
|
||||
feature_names = pickle.load(f)
|
||||
|
||||
return model, preprocessing_info, feature_names
|
||||
except FileNotFoundError:
|
||||
st.error("❌ Model files not found! Please run 'train_and_save_model.py' first.")
|
||||
st.stop()
|
||||
|
||||
|
||||
def preprocess_input(user_input: Dict[str, Any], preprocessing_info: Dict[str, Any]) -> pd.DataFrame:
|
||||
"""Preprocess user input to match the training data format"""
|
||||
|
||||
# Create a DataFrame with the user input
|
||||
input_df = pd.DataFrame([user_input])
|
||||
|
||||
# Get categorical columns that need one-hot encoding
|
||||
categorical_columns = [
|
||||
'Marital status', 'Application mode', 'Course', 'Daytime/evening attendance',
|
||||
'Previous qualification', 'Nationality', 'Mothers qualification',
|
||||
'Fathers qualification', 'Mothers occupation', 'Fathers occupation',
|
||||
'Gender', 'Displaced', 'Educational special needs', 'Debtor',
|
||||
'Tuition fees up to date', 'Scholarship holder', 'International',
|
||||
'Application order'
|
||||
]
|
||||
|
||||
# Perform one-hot encoding for categorical columns
|
||||
for column in categorical_columns:
|
||||
if column in input_df.columns:
|
||||
# Get dummies with prefix
|
||||
dummies = pd.get_dummies(input_df[column], prefix=column)
|
||||
# Concatenate with original data
|
||||
input_df = pd.concat([input_df, dummies], axis=1)
|
||||
# Drop original column
|
||||
input_df.drop(columns=[column], inplace=True)
|
||||
|
||||
# Ensure all training features are present
|
||||
feature_names = preprocessing_info['feature_names']
|
||||
|
||||
# Add missing columns with 0 values
|
||||
for feature in feature_names:
|
||||
if feature not in input_df.columns:
|
||||
input_df[feature] = 0
|
||||
|
||||
# Select only the features used in training and in the same order
|
||||
input_df = input_df[feature_names]
|
||||
|
||||
return input_df
|
||||
|
||||
|
||||
def validate_input(user_input: Dict[str, Any]) -> List[str]:
|
||||
"""Validate user input and return list of issues"""
|
||||
validation_issues = []
|
||||
|
||||
# Check for unrealistic combinations
|
||||
if user_input['Age at enrollment'] < 17:
|
||||
validation_issues.append("⚠️ Age seems too low for enrollment")
|
||||
|
||||
if user_input['Curricular units 1st sem (approved)'] > user_input['Curricular units 1st sem (enrolled)']:
|
||||
validation_issues.append("⚠️ Approved units exceed enrolled units (1st sem)")
|
||||
|
||||
if user_input['Curricular units 2nd sem (approved)'] > user_input['Curricular units 2nd sem (enrolled)']:
|
||||
validation_issues.append("⚠️ Approved units exceed enrolled units (2nd sem)")
|
||||
|
||||
return validation_issues
|
||||
|
||||
|
||||
def generate_recommendations(predicted_outcome: str, user_input: Dict[str, Any]) -> List[str]:
|
||||
"""Generate personalized recommendations based on prediction and input"""
|
||||
recommendations = []
|
||||
|
||||
if predicted_outcome == "Dropout":
|
||||
if user_input['Curricular units 1st sem (approved)'] == 0:
|
||||
recommendations.append("📚 **Academic Support:** Provide tutoring for struggling courses")
|
||||
|
||||
if user_input['Scholarship holder'] == 0:
|
||||
recommendations.append("💰 **Financial Aid:** Consider scholarship opportunities")
|
||||
|
||||
if user_input['Age at enrollment'] > 25:
|
||||
recommendations.append("🕐 **Flexible Scheduling:** Offer evening or part-time options")
|
||||
|
||||
if user_input['Debtor'] == 1:
|
||||
recommendations.append("💳 **Financial Counseling:** Address outstanding debts")
|
||||
|
||||
elif predicted_outcome == "Graduate":
|
||||
recommendations.append("🎯 **Success Indicators:** This student shows strong potential for graduation")
|
||||
recommendations.append("🔄 **Continue Support:** Maintain current academic support systems")
|
||||
|
||||
else: # Enrolled
|
||||
recommendations.append("📈 **Regular Monitoring:** Schedule periodic academic check-ins")
|
||||
recommendations.append("🎯 **Goal Setting:** Help establish clear academic objectives")
|
||||
|
||||
return recommendations
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"admin": {
|
||||
"password_hash": "240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9",
|
||||
"role": "admin",
|
||||
"name": "Administrator"
|
||||
},
|
||||
"educator": {
|
||||
"password_hash": "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92",
|
||||
"role": "educator",
|
||||
"name": "Educator"
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,58 @@ version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "altair"
|
||||
version = "5.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jinja2" },
|
||||
{ name = "jsonschema" },
|
||||
{ name = "narwhals" },
|
||||
{ name = "packaging" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/16/b1/f2969c7bdb8ad8bbdda031687defdce2c19afba2aa2c8e1d2a17f78376d8/altair-5.5.0.tar.gz", hash = "sha256:d960ebe6178c56de3855a68c47b516be38640b73fb3b5111c2a9ca90546dd73d", size = 705305, upload-time = "2024-11-23T23:39:58.542Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/f3/0b6ced594e51cc95d8c1fc1640d3623770d01e4969d29c0bd09945fafefa/altair-5.5.0-py3-none-any.whl", hash = "sha256:91a310b926508d560fe0148d02a194f38b824122641ef528113d029fcd129f8c", size = 731200, upload-time = "2024-11-23T23:39:56.4Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "25.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "6.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/61/e4fad8155db4a04bfb4734c7c8ff0882f078f24294d42798b3568eb63bff/cachetools-6.2.0.tar.gz", hash = "sha256:38b328c0889450f05f5e120f56ab68c8abaf424e1275522b138ffc93253f7e32", size = 30988, upload-time = "2025-08-25T18:57:30.924Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/56/3124f61d37a7a4e7cc96afc5492c78ba0cb551151e530b54669ddd1436ef/cachetools-6.2.0-py3-none-any.whl", hash = "sha256:1c76a8960c0041fcc21097e357f882197c79da0dbff766e7317890a65d7d8ba6", size = 11276, upload-time = "2025-08-25T18:57:29.684Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.8.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfgv"
|
||||
version = "3.4.0"
|
||||
@@ -11,6 +63,58 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "contourpy"
|
||||
version = "1.3.3"
|
||||
@@ -140,6 +244,30 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/a4/247d3e54eb5ed59e94e09866cfc4f9567e274fbf310ba390711851f63b3b/fonttools-4.60.0-py3-none-any.whl", hash = "sha256:496d26e4d14dcccdd6ada2e937e4d174d3138e3d73f5c9b6ec6eb2fd1dab4f66", size = 1142186, upload-time = "2025-09-17T11:33:59.287Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitdb"
|
||||
version = "4.0.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "smmap" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitpython"
|
||||
version = "3.1.45"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "gitdb" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphviz"
|
||||
version = "0.20.3"
|
||||
@@ -158,6 +286,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/ae/2ad30f4652712c82f1c23423d79136fbce338932ad166d70c1efb86a5998/identify-2.6.14-py2.py3-none-any.whl", hash = "sha256:11a073da82212c6646b1f39bb20d4483bfb9543bd5566fec60053c4bb309bf2e", size = 99172, upload-time = "2025-09-06T19:30:51.759Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.6"
|
||||
@@ -179,6 +316,33 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "4.25.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "jsonschema-specifications" },
|
||||
{ name = "referencing" },
|
||||
{ name = "rpds-py" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema-specifications"
|
||||
version = "2025.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "referencing" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kiwisolver"
|
||||
version = "1.4.9"
|
||||
@@ -536,6 +700,55 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "6.32.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", size = 440635, upload-time = "2025-09-11T21:38:42.935Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/98/645183ea03ab3995d29086b8bf4f7562ebd3d10c9a4b14ee3f20d47cfe50/protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085", size = 424411, upload-time = "2025-09-11T21:38:27.427Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/f3/6f58f841f6ebafe076cebeae33fc336e900619d34b1c93e4b5c97a81fdfa/protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1", size = 435738, upload-time = "2025-09-11T21:38:30.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", size = 426454, upload-time = "2025-09-11T21:38:34.076Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", size = 322874, upload-time = "2025-09-11T21:38:35.509Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", size = 322013, upload-time = "2025-09-11T21:38:37.017Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyarrow"
|
||||
version = "21.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ef/c2/ea068b8f00905c06329a3dfcd40d0fcc2b7d0f2e355bdb25b65e0a0e4cd4/pyarrow-21.0.0.tar.gz", hash = "sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc", size = 1133487, upload-time = "2025-07-18T00:57:31.761Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/16/ca/c7eaa8e62db8fb37ce942b1ea0c6d7abfe3786ca193957afa25e71b81b66/pyarrow-21.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a", size = 31154306, upload-time = "2025-07-18T00:56:04.42Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/e8/e87d9e3b2489302b3a1aea709aaca4b781c5252fcb812a17ab6275a9a484/pyarrow-21.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe", size = 32680622, upload-time = "2025-07-18T00:56:07.505Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/52/79095d73a742aa0aba370c7942b1b655f598069489ab387fe47261a849e1/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd", size = 41104094, upload-time = "2025-07-18T00:56:10.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/4b/7782438b551dbb0468892a276b8c789b8bbdb25ea5c5eb27faadd753e037/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61", size = 42825576, upload-time = "2025-07-18T00:56:15.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/62/0f29de6e0a1e33518dec92c65be0351d32d7ca351e51ec5f4f837a9aab91/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d", size = 43368342, upload-time = "2025-07-18T00:56:19.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/c7/0fa1f3f29cf75f339768cc698c8ad4ddd2481c1742e9741459911c9ac477/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99", size = 45131218, upload-time = "2025-07-18T00:56:23.347Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/63/581f2076465e67b23bc5a37d4a2abff8362d389d29d8105832e82c9c811c/pyarrow-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636", size = 26087551, upload-time = "2025-07-18T00:56:26.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/ab/357d0d9648bb8241ee7348e564f2479d206ebe6e1c47ac5027c2e31ecd39/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da", size = 31290064, upload-time = "2025-07-18T00:56:30.214Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/8a/5685d62a990e4cac2043fc76b4661bf38d06efed55cf45a334b455bd2759/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7", size = 32727837, upload-time = "2025-07-18T00:56:33.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/de/c0828ee09525c2bafefd3e736a248ebe764d07d0fd762d4f0929dbc516c9/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6", size = 41014158, upload-time = "2025-07-18T00:56:37.528Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/26/a2865c420c50b7a3748320b614f3484bfcde8347b2639b2b903b21ce6a72/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8", size = 42667885, upload-time = "2025-07-18T00:56:41.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/f9/4ee798dc902533159250fb4321267730bc0a107d8c6889e07c3add4fe3a5/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503", size = 43276625, upload-time = "2025-07-18T00:56:48.002Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/da/e02544d6997037a4b0d22d8e5f66bc9315c3671371a8b18c79ade1cefe14/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79", size = 44951890, upload-time = "2025-07-18T00:56:52.568Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/4e/519c1bc1876625fe6b71e9a28287c43ec2f20f73c658b9ae1d485c0c206e/pyarrow-21.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10", size = 26371006, upload-time = "2025-07-18T00:56:56.379Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydeck"
|
||||
version = "0.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jinja2" },
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/ca/40e14e196864a0f61a92abb14d09b3d3da98f94ccb03b49cf51688140dab/pydeck-0.9.1.tar.gz", hash = "sha256:f74475ae637951d63f2ee58326757f8d4f9cd9f2a457cf42950715003e2cb605", size = 3832240, upload-time = "2024-05-10T15:36:21.153Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/4c/b888e6cf58bd9db9c93f40d1c6be8283ff49d88919231afe93a6bcf61626/pydeck-0.9.1-py2.py3-none-any.whl", hash = "sha256:b3f75ba0d273fc917094fa61224f3f6076ca8752b93d46faf3bcfd9f9d59b038", size = 6900403, upload-time = "2024-05-10T15:36:17.36Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.2.5"
|
||||
@@ -602,6 +815,100 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.36.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "rpds-py" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rpds-py"
|
||||
version = "0.27.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.13.2"
|
||||
@@ -717,6 +1024,44 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smmap"
|
||||
version = "5.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "streamlit"
|
||||
version = "1.50.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "altair" },
|
||||
{ name = "blinker" },
|
||||
{ name = "cachetools" },
|
||||
{ name = "click" },
|
||||
{ name = "gitpython" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pandas" },
|
||||
{ name = "pillow" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "pyarrow" },
|
||||
{ name = "pydeck" },
|
||||
{ name = "requests" },
|
||||
{ name = "tenacity" },
|
||||
{ name = "toml" },
|
||||
{ name = "tornado" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "watchdog", marker = "sys_platform != 'darwin'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/f6/f7d3a0146577c1918439d3163707040f7111a7d2e7e2c73fa7adeb169c06/streamlit-1.50.0.tar.gz", hash = "sha256:87221d568aac585274a05ef18a378b03df332b93e08103fffcf3cd84d852af46", size = 9664808, upload-time = "2025-09-23T19:24:00.31Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/38/991bbf9fa3ed3d9c8e69265fc449bdaade8131c7f0f750dbd388c3c477dc/streamlit-1.50.0-py3-none-any.whl", hash = "sha256:9403b8f94c0a89f80cf679c2fcc803d9a6951e0fba542e7611995de3f67b4bb3", size = 10068477, upload-time = "2025-09-23T19:23:57.245Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "student-retention"
|
||||
version = "0.1.0"
|
||||
@@ -729,6 +1074,7 @@ dependencies = [
|
||||
{ name = "plotly" },
|
||||
{ name = "ruff" },
|
||||
{ name = "scikit-learn" },
|
||||
{ name = "streamlit" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
@@ -740,6 +1086,16 @@ requires-dist = [
|
||||
{ name = "plotly", specifier = ">=6.3.0" },
|
||||
{ name = "ruff", specifier = ">=0.13.2" },
|
||||
{ name = "scikit-learn", specifier = ">=1.7.2" },
|
||||
{ name = "streamlit", specifier = ">=1.50.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tenacity"
|
||||
version = "9.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -751,6 +1107,43 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tornado"
|
||||
version = "6.5.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
@@ -760,6 +1153,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.34.0"
|
||||
@@ -773,3 +1175,21 @@ sdist = { url = "https://files.pythonhosted.org/packages/1c/14/37fcdba2808a6c615
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/06/04c8e804f813cf972e3262f3f8584c232de64f0cde9f703b46cf53a45090/virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", size = 5983279, upload-time = "2025-08-13T14:24:05.111Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "6.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user