Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,9 @@ Production-ready machine learning pipeline for personality classification using

## Dashboard Preview

<div align="center">
<video width="80%" controls>
<source src="docs/images/personality_classification_app.mp4" type="video/mp4">
Your browser does not support the video tag.
<a href="docs/images/personality_classification_app.mp4">Download the video</a>.
</video>
<br>
<em>Watch a live demo of the Personality Classification Dashboard in action</em>
</div>
![Dashboard Demo](docs/images/personality_classification_app.mp4)

*Watch a live demo of the Personality Classification Dashboard in action*

## Quick Start

Expand Down
109 changes: 73 additions & 36 deletions dash_app/dashboard/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import logging
from dataclasses import dataclass
from datetime import datetime

from dash import html
Expand All @@ -13,6 +14,64 @@
)


# Default values for prediction inputs
class PredictionDefaults:
"""Default values for personality prediction features."""

TIME_ALONE = 2.0
SOCIAL_EVENTS = 4.0
GOING_OUTSIDE = 3.0
FRIENDS_SIZE = 8.0
POST_FREQUENCY = 3.0


@dataclass
class PredictionInputs:
"""Data class for prediction input parameters."""

time_alone: float | None = None
social_events: float | None = None
going_outside: float | None = None
friends_size: float | None = None
post_freq: float | None = None
stage_fear: str | None = None
drained_social: str | None = None

def to_feature_dict(self) -> dict[str, int | float]:
"""Convert inputs to feature dictionary for model prediction."""
return {
"Time_spent_Alone": self.time_alone
if self.time_alone is not None
else PredictionDefaults.TIME_ALONE,
"Social_event_attendance": self.social_events
if self.social_events is not None
else PredictionDefaults.SOCIAL_EVENTS,
"Going_outside": self.going_outside
if self.going_outside is not None
else PredictionDefaults.GOING_OUTSIDE,
"Friends_circle_size": self.friends_size
if self.friends_size is not None
else PredictionDefaults.FRIENDS_SIZE,
"Post_frequency": self.post_freq
if self.post_freq is not None
else PredictionDefaults.POST_FREQUENCY,
# One-hot encode Stage_fear
"Stage_fear_No": 1 if self.stage_fear == "No" else 0,
"Stage_fear_Unknown": 1 if self.stage_fear == "Unknown" else 0,
"Stage_fear_Yes": 1 if self.stage_fear == "Yes" else 0,
# One-hot encode Drained_after_socializing
"Drained_after_socializing_No": 1 if self.drained_social == "No" else 0,
"Drained_after_socializing_Unknown": 1
if self.drained_social == "Unknown"
else 0,
"Drained_after_socializing_Yes": 1 if self.drained_social == "Yes" else 0,
# Set external match features to Unknown (default)
"match_p_Extrovert": 0,
"match_p_Introvert": 0,
"match_p_Unknown": 1,
}


def register_callbacks(app, model_loader, prediction_history: list) -> None:
"""Register all callbacks for the Dash application.

Expand All @@ -37,47 +96,25 @@ def register_callbacks(app, model_loader, prediction_history: list) -> None:
State("drained-after-socializing", "value"),
prevent_initial_call=True,
)
def make_prediction(
n_clicks,
time_alone,
social_events,
going_outside,
friends_size,
post_freq,
stage_fear,
drained_social,
):
def make_prediction(n_clicks, *input_values):
"""Handle prediction requests."""
if not n_clicks:
return ""

try:
# Build the feature dictionary with proper encoding
data = {
"Time_spent_Alone": time_alone if time_alone is not None else 2.0,
"Social_event_attendance": social_events
if social_events is not None
else 4.0,
"Going_outside": going_outside if going_outside is not None else 3.0,
"Friends_circle_size": friends_size
if friends_size is not None
else 8.0,
"Post_frequency": post_freq if post_freq is not None else 3.0,
# One-hot encode Stage_fear
"Stage_fear_No": 1 if stage_fear == "No" else 0,
"Stage_fear_Unknown": 1 if stage_fear == "Unknown" else 0,
"Stage_fear_Yes": 1 if stage_fear == "Yes" else 0,
# One-hot encode Drained_after_socializing
"Drained_after_socializing_No": 1 if drained_social == "No" else 0,
"Drained_after_socializing_Unknown": 1
if drained_social == "Unknown"
else 0,
"Drained_after_socializing_Yes": 1 if drained_social == "Yes" else 0,
# Set external match features to Unknown (default)
"match_p_Extrovert": 0,
"match_p_Introvert": 0,
"match_p_Unknown": 1,
}
# Create prediction inputs from callback arguments
inputs = PredictionInputs(
time_alone=input_values[0],
social_events=input_values[1],
going_outside=input_values[2],
friends_size=input_values[3],
post_freq=input_values[4],
stage_fear=input_values[5],
drained_social=input_values[6],
)

# Convert to feature dictionary
data = inputs.to_feature_dict()

# Make prediction
result = model_loader.predict(data)
Expand Down
145 changes: 84 additions & 61 deletions dash_app/dashboard/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

import dash_bootstrap_components as dbc
import plotly.graph_objects as go
from dash import dcc, html


@dataclass
class SliderConfig:
"""Configuration for enhanced sliders."""

slider_id: str
label: str
min_val: int
max_val: int
default: int
intro_text: str
extro_text: str
css_class: str


def create_layout(model_name: str, model_metadata: dict[str, Any]) -> html.Div:
"""Create the main layout for the Dash application.

Expand Down Expand Up @@ -207,24 +222,28 @@ def create_input_panel() -> dbc.Card:
className="section-title mb-4",
),
create_enhanced_slider(
"time-spent-alone",
"Time Spent Alone (hours/day)",
0,
24,
8,
"Less alone time",
"More alone time",
"slider-social",
SliderConfig(
slider_id="time-spent-alone",
label="Time Spent Alone (hours/day)",
min_val=0,
max_val=24,
default=8,
intro_text="Less alone time",
extro_text="More alone time",
css_class="slider-social",
)
),
create_enhanced_slider(
"social-event-attendance",
"Social Event Attendance (events/month)",
0,
20,
4,
"Fewer events",
"More events",
"slider-social",
SliderConfig(
slider_id="social-event-attendance",
label="Social Event Attendance (events/month)",
min_val=0,
max_val=20,
default=4,
intro_text="Fewer events",
extro_text="More events",
css_class="slider-social",
)
),
# Lifestyle Section
html.H5(
Expand All @@ -238,24 +257,28 @@ def create_input_panel() -> dbc.Card:
className="section-title mt-5 mb-4",
),
create_enhanced_slider(
"going-outside",
"Going Outside Frequency (times/week)",
0,
15,
5,
"Stay indoors",
"Go out frequently",
"slider-lifestyle",
SliderConfig(
slider_id="going-outside",
label="Going Outside Frequency (times/week)",
min_val=0,
max_val=15,
default=5,
intro_text="Stay indoors",
extro_text="Go out frequently",
css_class="slider-lifestyle",
)
),
create_enhanced_slider(
"friends-circle-size",
"Friends Circle Size",
0,
50,
12,
"Small circle",
"Large network",
"slider-lifestyle",
SliderConfig(
slider_id="friends-circle-size",
label="Friends Circle Size",
min_val=0,
max_val=50,
default=12,
intro_text="Small circle",
extro_text="Large network",
css_class="slider-lifestyle",
)
),
# Digital Behavior Section
html.H5(
Expand All @@ -269,14 +292,16 @@ def create_input_panel() -> dbc.Card:
className="section-title mt-5 mb-4",
),
create_enhanced_slider(
"post-frequency",
"Social Media Posts (per week)",
0,
20,
3,
"Rarely post",
"Frequently post",
"slider-digital",
SliderConfig(
slider_id="post-frequency",
label="Social Media Posts (per week)",
min_val=0,
max_val=20,
default=3,
intro_text="Rarely post",
extro_text="Frequently post",
css_class="slider-digital",
)
),
# Psychological Assessment Section
html.H5(
Expand Down Expand Up @@ -353,38 +378,36 @@ def create_input_panel() -> dbc.Card:
)


def create_enhanced_slider(
slider_id: str,
label: str,
min_val: int,
max_val: int,
default: int,
intro_text: str,
extro_text: str,
css_class: str,
) -> html.Div:
"""Create an enhanced slider with personality hints."""
def create_enhanced_slider(config: SliderConfig) -> html.Div:
"""Create an enhanced slider with personality hints.

Args:
config: Slider configuration containing all parameters

Returns:
HTML div containing the slider component
"""
return html.Div(
[
html.Label(label, className="slider-label fw-bold"),
html.Label(config.label, className="slider-label fw-bold"),
dcc.Slider(
id=slider_id,
min=min_val,
max=max_val,
id=config.slider_id,
min=config.min_val,
max=config.max_val,
step=1,
value=default,
value=config.default,
marks={
min_val: {
"label": intro_text,
config.min_val: {
"label": config.intro_text,
"style": {"color": "#3498db", "fontSize": "0.8rem"},
},
max_val: {
"label": extro_text,
config.max_val: {
"label": config.extro_text,
"style": {"color": "#e74c3c", "fontSize": "0.8rem"},
},
},
tooltip={"placement": "bottom", "always_visible": True},
className=f"personality-slider {css_class}",
className=f"personality-slider {config.css_class}",
),
],
className="slider-container mb-3",
Expand Down
4 changes: 1 addition & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Documentation Index

# Documentation Index

Welcome! This documentation covers all aspects of the Six-Stack Personality Classification Pipeline.

## Main Guides
Expand Down Expand Up @@ -42,7 +40,7 @@ docker build -t personality-classifier .
docker run -p 8080:8080 personality-classifier
```

## Resources
## 📚 Resources

- Code: `src/main_modular.py`, `examples/`
- Config templates: [Configuration Guide](configuration.md)
Expand Down
Loading