From 328a6ecd151f18bab16a91c7e5a773179c9217f5 Mon Sep 17 00:00:00 2001 From: Christopher Odoom Date: Fri, 24 Jul 2026 18:54:50 -0400 Subject: [PATCH 1/3] Add all-models index page --- routes/main_routes.py | 11 +++++++++++ templates/model_details.html | 4 ++-- tests/test_main_routes.py | 9 +++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/routes/main_routes.py b/routes/main_routes.py index a3b2efc..e1514b7 100644 --- a/routes/main_routes.py +++ b/routes/main_routes.py @@ -668,6 +668,17 @@ def history(): return redirect(url_for('auth.login', next=url_for('main.history'))) except Exception as e: return render_template('error.html', error=str(e)) +@main.route('/models') +def models_index(): + """Display every available statistical model.""" + model_database = current_app.config.get('MODEL_DATABASE', {}) + return render_template( + 'models_list.html', + models=sorted(model_database.items()), + group_name='All Statistical Models', + ) + + @main.route('/models/') def models_in_group(group_name): """Display models belonging to a specific group.""" diff --git a/templates/model_details.html b/templates/model_details.html index 9bac7b6..77309cc 100644 --- a/templates/model_details.html +++ b/templates/model_details.html @@ -317,7 +317,7 @@
Academic Papers and References
@@ -327,4 +327,4 @@
Academic Papers and References
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/tests/test_main_routes.py b/tests/test_main_routes.py index 9f45860..6826a82 100644 --- a/tests/test_main_routes.py +++ b/tests/test_main_routes.py @@ -26,6 +26,15 @@ def test_double_encoded_model_group_url(self, client): assert b'Invalid model group' not in response.data assert b'Linear Regression' in response.data + def test_models_index(self, client): + """The all-models page lists models from the configured database.""" + response = client.get('/models') + + assert response.status_code == 200 + assert b'All Statistical Models' in response.data + assert b'Linear Regression' in response.data + assert b'Logistic Regression' in response.data + def test_double_encoded_model_detail_urls(self, client, monkeypatch): """Encoded model links work across detail and interpretation routes.""" interpretation_module = ModuleType('utils.interpretation') From 6468b3a9353740aae92859455f0a6f288cbce704 Mon Sep 17 00:00:00 2001 From: Christopher Odoom Date: Fri, 24 Jul 2026 19:00:53 -0400 Subject: [PATCH 2/3] Open full catalog from model links --- templates/base.html | 4 ++-- templates/home.html | 6 +++--- templates/results.html | 2 +- tests/test_main_routes.py | 17 ++++++++++++++++- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/templates/base.html b/templates/base.html index 9ebe782..37bcf6f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -420,7 +420,7 @@
Explore
@@ -269,7 +269,7 @@

Expert Consultation

Educational Resources

Access comprehensive documentation, tutorials, and examples for each statistical model in our database.

- Learn More + Learn More
@@ -317,4 +317,4 @@

Questionnaire Designer

{% endblock %} -{% block container_end %}{% endblock %} \ No newline at end of file +{% block container_end %}{% endblock %} diff --git a/templates/results.html b/templates/results.html index 4ef889d..2a0441f 100644 --- a/templates/results.html +++ b/templates/results.html @@ -116,7 +116,7 @@
{{ model_name }}
diff --git a/tests/test_main_routes.py b/tests/test_main_routes.py index 6826a82..cd534bf 100644 --- a/tests/test_main_routes.py +++ b/tests/test_main_routes.py @@ -33,7 +33,22 @@ def test_models_index(self, client): assert response.status_code == 200 assert b'All Statistical Models' in response.data assert b'Linear Regression' in response.data - assert b'Logistic Regression' in response.data + assert b'ARIMA' in response.data + assert b'Random Forest' in response.data + + def test_generic_model_links_open_full_catalog( + self, + client, + sample_analysis_data, + ): + """Generic model links do not restrict visitors to regressions.""" + home_response = client.get('/') + results_response = client.post('/results', data=sample_analysis_data) + + assert home_response.status_code == 200 + assert results_response.status_code == 200 + assert home_response.data.count(b'href="/models"') >= 3 + assert b'href="/models"' in results_response.data def test_double_encoded_model_detail_urls(self, client, monkeypatch): """Encoded model links work across detail and interpretation routes.""" From 40d358de5e47002e2b20e4e315e5e86b9fce2e05 Mon Sep 17 00:00:00 2001 From: Christopher Odoom Date: Fri, 24 Jul 2026 19:15:34 -0400 Subject: [PATCH 3/3] Redesign statistical model selection experience --- public/static/css/app.css | 318 +++++++++++++++++ public/static/css/chatbot.css | 86 ++--- routes/main_routes.py | 36 +- templates/analysis_form.html | 83 ++--- templates/base.html | 640 +++++++++------------------------- templates/error.html | 132 ++----- templates/history.html | 4 +- templates/home.html | 513 +++++++++++---------------- templates/model_details.html | 62 ++-- templates/models_list.html | 189 +++++++--- templates/results.html | 226 ++++++------ templates/search_results.html | 86 +++-- tests/test_main_routes.py | 2 +- 13 files changed, 1120 insertions(+), 1257 deletions(-) create mode 100644 public/static/css/app.css diff --git a/public/static/css/app.css b/public/static/css/app.css new file mode 100644 index 0000000..63debd4 --- /dev/null +++ b/public/static/css/app.css @@ -0,0 +1,318 @@ +:root { + --navy-950: #0b1f33; + --navy-900: #102a43; + --navy-800: #183b56; + --navy-700: #274c6b; + --teal-700: #0f766e; + --teal-600: #0d9488; + --teal-100: #ccfbf1; + --teal-50: #f0fdfa; + --gold-500: #d8a42b; + --ink: #172b3a; + --muted: #607486; + --line: #dce5eb; + --surface: #ffffff; + --canvas: #f5f8fa; + --warm: #faf7f1; + --shadow-sm: 0 8px 24px rgba(16, 42, 67, 0.07); + --shadow-md: 0 18px 50px rgba(16, 42, 67, 0.12); + --radius-sm: 0.65rem; + --radius: 1rem; + --radius-lg: 1.5rem; +} + +* { box-sizing: border-box; } + +html { scroll-behavior: smooth; } + +body { + min-height: 100vh; + display: flex; + flex-direction: column; + margin: 0; + color: var(--ink); + background: var(--canvas); + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} + +a { color: var(--teal-700); } +a:hover { color: var(--navy-800); } + +h1, h2, h3, h4, h5, h6 { + color: var(--navy-950); + font-weight: 700; + letter-spacing: -0.025em; +} + +.skip-link { + position: fixed; + top: -5rem; + left: 1rem; + z-index: 2000; + padding: .65rem 1rem; + color: white; + background: var(--navy-950); + border-radius: .5rem; +} +.skip-link:focus { top: 1rem; } + +.app-navbar { + min-height: 76px; + background: rgba(16, 42, 67, .98); + box-shadow: 0 1px 0 rgba(255,255,255,.08); +} + +.app-brand { + display: inline-flex; + align-items: center; + gap: .7rem; + color: white !important; + line-height: 1.05; +} +.app-brand strong, +.app-brand small { display: block; } +.app-brand strong { font-size: .98rem; letter-spacing: -.01em; } +.app-brand small { margin-top: .2rem; color: #b9d4e5; font-size: .76rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; } + +.brand-mark { + display: inline-grid; + width: 2.35rem; + height: 2.35rem; + flex: 0 0 2.35rem; + place-items: center; + color: var(--navy-950); + background: var(--teal-100); + border-radius: .75rem; + font-size: 1.2rem; +} + +.app-navbar .nav-link { + padding: .55rem .75rem !important; + color: #d7e5ee !important; + border-radius: .55rem; + font-size: .94rem; + font-weight: 600; +} +.app-navbar .nav-link:hover, +.app-navbar .nav-link.active { color: white !important; background: rgba(255,255,255,.09); } +.app-navbar .navbar-toggler { border-color: rgba(255,255,255,.22); } +.app-navbar .navbar-toggler-icon { filter: brightness(0) invert(1); } + +.app-navbar .dropdown-menu { + padding: .6rem; + border: 1px solid var(--line); + border-radius: .8rem; + box-shadow: var(--shadow-md); +} +.app-navbar .dropdown-item { + padding: .55rem .7rem; + border-radius: .45rem; + color: var(--ink); + font-size: .9rem; +} +.app-navbar .dropdown-item:hover { color: var(--navy-950); background: var(--teal-50); } +.model-menu { width: min(340px, 90vw); max-height: 70vh; overflow-y: auto; } +.model-menu-all { color: var(--teal-700) !important; font-weight: 700; } + +.app-search { width: min(240px, 100%); } +.app-search > i { + position: absolute; + top: 50%; + left: .8rem; + z-index: 2; + color: #7991a3; + transform: translateY(-50%); +} +.app-search input { + width: 100%; + height: 40px; + padding: .5rem .8rem .5rem 2.3rem; + color: var(--ink); + background: white; + border: 1px solid rgba(255,255,255,.14); + border-radius: 999px; + outline: none; +} +.app-search input:focus { box-shadow: 0 0 0 3px rgba(45, 212, 191, .25); } + +.app-nav-cta { padding: .48rem .8rem; color: var(--navy-950) !important; font-weight: 700; } +.avatar-circle { + display: inline-grid; + width: 36px; + height: 36px; + place-items: center; + color: var(--navy-950); + background: var(--teal-100); + border: 2px solid rgba(255,255,255,.45); + border-radius: 50%; + font-weight: 800; +} + +.app-main { flex: 1; } +.content { min-height: 60vh; padding-top: 2rem; padding-bottom: 3rem; } +.flash-wrap { padding-top: 1rem; } +.flash-wrap:empty { display: none; } + +.card { + border: 1px solid var(--line); + border-radius: var(--radius); + box-shadow: var(--shadow-sm); +} +.card-header { + padding: 1rem 1.25rem; + color: var(--navy-950); + background: var(--surface); + border-bottom: 1px solid var(--line); + border-radius: var(--radius) var(--radius) 0 0 !important; +} +.card-header.bg-primary, +.card-header.bg-success, +.bg-primary { + color: white !important; + background: var(--navy-900) !important; +} +.bg-success { background: var(--teal-700) !important; } +.bg-info { color: var(--navy-950) !important; background: var(--teal-100) !important; } +.text-primary { color: var(--teal-700) !important; } + +.btn { + border-radius: .65rem; + font-weight: 700; +} +.btn-primary, +.btn-purple { + color: white; + background: var(--teal-700); + border-color: var(--teal-700); +} +.btn-primary:hover, +.btn-purple:hover { + color: white; + background: #0b625c; + border-color: #0b625c; +} +.btn-outline-primary { + color: var(--teal-700); + border-color: var(--teal-700); +} +.btn-outline-primary:hover { color: white; background: var(--teal-700); border-color: var(--teal-700); } +.btn-info { color: var(--navy-950); background: var(--teal-100); border-color: #99e7dc; } +.btn-info:hover { color: var(--navy-950); background: #a8efe5; border-color: #74d9cb; } +.btn-warning { color: var(--navy-950); background: #f7df9a; border-color: #e8c75f; } + +.form-control, +.form-select { + border-color: #cbd8e0; + border-radius: .65rem; +} +.form-control:focus, +.form-select:focus { + border-color: var(--teal-600); + box-shadow: 0 0 0 .22rem rgba(13,148,136,.14); +} + +.badge { padding: .4em .65em; border-radius: 999px; font-weight: 650; } +.alert-info { color: #23485b; background: #eaf7f8; border-color: #bfe5e7; } + +.page-eyebrow { + margin-bottom: .7rem; + color: var(--teal-700); + font-size: .78rem; + font-weight: 800; + letter-spacing: .12em; + text-transform: uppercase; +} +.page-lead { max-width: 680px; color: var(--muted); font-size: 1.1rem; } + +.model-library-card { + display: flex; + height: 100%; + flex-direction: column; + padding: 1.25rem; + color: inherit; + background: white; + border: 1px solid var(--line); + border-radius: 1rem; + text-decoration: none; + transition: .2s ease; +} +.model-library-card:hover { + color: inherit; + border-color: #8bd8cf; + box-shadow: var(--shadow-md); + transform: translateY(-3px); +} +.model-card-top { display: flex; justify-content: space-between; gap: 1rem; } +.model-library-card h3 { margin-bottom: .7rem; font-size: 1.08rem; } +.model-library-card .arrow { color: var(--teal-700); } +.model-library-card p { color: var(--muted); font-size: .9rem; } +.model-meta { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: auto; } +.model-meta span { padding: .25rem .5rem; color: var(--navy-700); background: var(--canvas); border-radius: 999px; font-size: .72rem; font-weight: 700; } + +.app-footer { + padding: 4rem 0 1.5rem; + color: #c6d8e4; + background: var(--navy-950); +} +.footer-brand { + display: inline-flex; + align-items: center; + gap: .7rem; + color: white; + text-decoration: none; + font-weight: 750; +} +.footer-copy { max-width: 440px; margin-top: 1rem; color: #9fb5c5; } +.app-footer h2 { margin-bottom: 1rem; color: white; font-size: .8rem; letter-spacing: .12em; text-transform: uppercase; } +.app-footer a:not(.footer-brand) { display: block; margin: .45rem 0; color: #c6d8e4; text-decoration: none; } +.app-footer a:hover { color: white; } +.footer-note { padding: 1rem; color: #cceee9; background: rgba(13,148,136,.12); border: 1px solid rgba(94,234,212,.18); border-radius: .8rem; } +.footer-bottom { + display: flex; + justify-content: space-between; + gap: 1rem; + margin-top: 2.75rem; + padding-top: 1.25rem; + color: #829dad; + border-top: 1px solid rgba(255,255,255,.1); + font-size: .85rem; +} + +#scrollToTopBtn { + position: fixed; + right: 1.25rem; + bottom: 1.25rem; + z-index: 1010; + display: grid; + width: 44px; + height: 44px; + place-items: center; + color: white; + background: var(--navy-800); + border: 0; + border-radius: 50%; + box-shadow: var(--shadow-md); + opacity: 0; + pointer-events: none; + transform: translateY(.5rem); + transition: .2s ease; +} +#scrollToTopBtn.show { opacity: 1; pointer-events: auto; transform: none; } + +@media (max-width: 991.98px) { + .app-navbar { padding: .7rem 0; } + .app-navbar .navbar-collapse { padding-top: .8rem; } + .app-search { width: 100%; } + .app-nav-cta { display: inline-block; margin-top: .4rem; } +} + +@media (max-width: 767.98px) { + .content { padding-top: 1.25rem; } + .footer-bottom { flex-direction: column; } +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; } +} diff --git a/public/static/css/chatbot.css b/public/static/css/chatbot.css index d5c8656..d90d752 100644 --- a/public/static/css/chatbot.css +++ b/public/static/css/chatbot.css @@ -3,29 +3,31 @@ */ /* Chat icon */ -.chat-icon-container { - position: fixed; - bottom: 100px; - right: 30px; +.chat-icon-container { + position: fixed; + bottom: 82px; + right: 20px; z-index: 1000; } -#chat-icon { - width: 60px; - height: 60px; - background-color: #3498db; +#chat-icon { + width: 52px; + height: 52px; + padding: 0; + background-color: #0f766e; + border: 0; border-radius: 50%; display: flex; justify-content: center; align-items: center; cursor: pointer; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + box-shadow: 0 10px 30px rgba(16, 42, 67, 0.22); transition: transform 0.3s, background-color 0.3s; } -#chat-icon:hover { - transform: scale(1.05); - background-color: #2980b9; +#chat-icon:hover { + transform: translateY(-2px); + background-color: #0b625c; } #chat-icon i { @@ -34,15 +36,16 @@ } /* Chat window */ -#chat-window { +#chat-window { position: fixed; - bottom: 170px; - right: 30px; - width: 350px; - height: 500px; + bottom: 145px; + right: 20px; + width: min(380px, calc(100vw - 32px)); + height: min(540px, calc(100vh - 180px)); background-color: white; - border-radius: 12px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); + border: 1px solid #dce5eb; + border-radius: 16px; + box-shadow: 0 18px 50px rgba(16, 42, 67, 0.18); display: flex; flex-direction: column; overflow: hidden; @@ -61,8 +64,8 @@ } /* Chat header */ -.chat-header { - background: linear-gradient(to right, #3498db, #2c3e50); +.chat-header { + background: linear-gradient(125deg, #102a43, #124b55); color: white; padding: 15px; display: flex; @@ -70,7 +73,8 @@ align-items: center; } -.chat-header h3 { +.chat-header h3 { + color: white; margin: 0; font-size: 18px; font-weight: 600; @@ -117,15 +121,15 @@ word-wrap: break-word; } -.user-message .message-bubble { - background-color: #3498db; +.user-message .message-bubble { + background-color: #0f766e; color: white; border-top-right-radius: 4px; } -.bot-message .message-bubble { - background-color: #f0f2f5; - color: #333; +.bot-message .message-bubble { + background-color: #edf3f6; + color: #172b3a; border-top-left-radius: 4px; } @@ -178,12 +182,12 @@ outline: none; } -#user-message:focus { - border-color: #3498db; +#user-message:focus { + border-color: #0d9488; } -#send-message { - background-color: #3498db; +#send-message { + background-color: #0f766e; color: white; border: none; width: 40px; @@ -197,21 +201,19 @@ transition: background-color 0.3s; } -#send-message:hover { - background-color: #2980b9; +#send-message:hover { + background-color: #0b625c; } /* Responsive styles */ @media (max-width: 768px) { - #chat-window { - width: 85%; - height: 60%; - right: 20px; - bottom: 160px; + #chat-window { + right: 16px; + bottom: 140px; } - .chat-icon-container { - right: 20px; - bottom: 90px; + .chat-icon-container { + right: 16px; + bottom: 78px; } -} \ No newline at end of file +} diff --git a/routes/main_routes.py b/routes/main_routes.py index e1514b7..b07960d 100644 --- a/routes/main_routes.py +++ b/routes/main_routes.py @@ -3,7 +3,6 @@ from models import db, User, Analysis, get_model_details from datetime import datetime import json -import random from collections import OrderedDict from urllib.parse import unquote main = Blueprint('main', __name__) @@ -304,11 +303,6 @@ def get_model_recommendation(analysis_goal, dependent_variable, independent_vari if model_name == 'Linear Regression': score -= 0.25 current_app.logger.debug(f" - Linear Regression penalty: -0.25 → {score}") - # Add a small random component to break ties between similar models - # Increase the random component to improve diversity in recommendations - random_component = random.uniform(0, 0.2) - score += random_component - current_app.logger.debug(f" + Random component: +{random_component:.4f} → {score:.4f}") # Give a strong bonus to clustering models when the goal is clustering if (analysis_goal == 'explore' or analysis_goal == 'cluster') and model_name in clustering_models: score += 10.0 # Very significant boost to ensure clustering models win for cluster analysis @@ -326,7 +320,10 @@ def get_model_recommendation(analysis_goal, dependent_variable, independent_vari # First, identify the best matching model if model_scores: # Sort models by score - sorted_models = sorted(model_scores.items(), key=lambda x: x[1], reverse=True) + sorted_models = sorted( + model_scores.items(), + key=lambda item: (-item[1], item[0]), + ) # Log top model scores for debugging current_app.logger.debug(f"TOP MODEL SCORES:") for model, score in sorted_models[:5]: # Show top 5 models @@ -672,10 +669,32 @@ def history(): def models_index(): """Display every available statistical model.""" model_database = current_app.config.get('MODEL_DATABASE', {}) + grouped_models = [] + included_models = set() + for group_name, model_names in MODEL_GROUPS.items(): + group_models = [ + (name, model_database[name]) + for name in model_names + if name in model_database and name not in included_models + ] + if group_models: + grouped_models.append((group_name, sorted(group_models))) + included_models.update(name for name, _details in group_models) + + uncategorized_models = sorted( + (name, details) + for name, details in model_database.items() + if name not in included_models + ) + if uncategorized_models: + grouped_models.append(('Additional Methods', uncategorized_models)) + return render_template( 'models_list.html', models=sorted(model_database.items()), group_name='All Statistical Models', + grouped_models=grouped_models, + is_all_models=True, ) @@ -702,7 +721,8 @@ def models_in_group(group_name): return render_template( 'models_list.html', models=sorted_models, - group_name=group_name # Pass group name for the title + group_name=group_name, + is_all_models=False, ) @main.route('/model/') def model_details(model_name): diff --git a/templates/analysis_form.html b/templates/analysis_form.html index 536fb1e..cd1c7ea 100644 --- a/templates/analysis_form.html +++ b/templates/analysis_form.html @@ -12,30 +12,30 @@ box-shadow: none; } - /* Reset body background */ - body { - background-color: transparent !important; + body { + background-color: #f5f8fa !important; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; } - .form-section { + .form-section { position: relative; width: 100%; padding: 0; - background-color: #e8f4f8; + background-color: #f5f8fa; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='800' height='600' viewBox='0 0 800 600' preserveAspectRatio='none'%3E%3Cdefs%3E%3ClinearGradient id='bg-gradient' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23e8f4f8' /%3E%3Cstop offset='100%25' stop-color='%23d0e8f0' /%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='100%25' height='100%25' fill='url(%23bg-gradient)' /%3E%3C!-- Grid lines --%3E%3Cpath d='M0 50 L800 50 M0 100 L800 100 M0 150 L800 150 M0 200 L800 200 M0 250 L800 250 M0 300 L800 300 M0 350 L800 350 M0 400 L800 400 M0 450 L800 450 M0 500 L800 500 M0 550 L800 550 M50 0 L50 600 M100 0 L100 600 M150 0 L150 600 M200 0 L200 600 M250 0 L250 600 M300 0 L300 600 M350 0 L350 600 M400 0 L400 600 M450 0 L450 600 M500 0 L500 600 M550 0 L550 600 M600 0 L600 600 M650 0 L650 600 M700 0 L700 600 M750 0 L750 600' stroke='%23cae0ea' stroke-width='0.5' stroke-opacity='0.5' /%3E%3C!-- Bar chart --%3E%3Crect x='150' y='280' width='15' height='70' fill='%23267b9c' opacity='0.7' /%3E%3Crect x='170' y='250' width='15' height='100' fill='%23267b9c' opacity='0.7' /%3E%3Crect x='190' y='220' width='15' height='130' fill='%23267b9c' opacity='0.7' /%3E%3Crect x='210' y='260' width='15' height='90' fill='%23267b9c' opacity='0.7' /%3E%3Crect x='230' y='240' width='15' height='110' fill='%23267b9c' opacity='0.7' /%3E%3Crect x='250' y='270' width='15' height='80' fill='%23267b9c' opacity='0.7' /%3E%3C!-- Line chart --%3E%3Cpath d='M50 400 L100 380 L150 390 L200 350 L250 320 L300 330 L350 310 L400 300' fill='none' stroke='%23267b9c' stroke-width='2' /%3E%3Ccircle cx='50' cy='400' r='3' fill='%23267b9c' /%3E%3Ccircle cx='100' cy='380' r='3' fill='%23267b9c' /%3E%3Ccircle cx='150' cy='390' r='3' fill='%23267b9c' /%3E%3Ccircle cx='200' cy='350' r='3' fill='%23267b9c' /%3E%3Ccircle cx='250' cy='320' r='3' fill='%23267b9c' /%3E%3Ccircle cx='300' cy='330' r='3' fill='%23267b9c' /%3E%3Ccircle cx='350' cy='310' r='3' fill='%23267b9c' /%3E%3Ccircle cx='400' cy='300' r='3' fill='%23267b9c' /%3E%3C!-- Pie chart --%3E%3Cpath d='M600 180 L600 100 A80 80 0 0 1 674 142 Z' fill='%23267b9c' opacity='0.7' /%3E%3Cpath d='M600 180 L674 142 A80 80 0 0 1 652 242 Z' fill='%234a98b5' opacity='0.7' /%3E%3Cpath d='M600 180 L652 242 A80 80 0 0 1 548 242 Z' fill='%2370b3c9' opacity='0.7' /%3E%3Cpath d='M600 180 L548 242 A80 80 0 0 1 526 142 Z' fill='%2396cedd' opacity='0.7' /%3E%3Cpath d='M600 180 L526 142 A80 80 0 0 1 600 100 Z' fill='%23bee9f2' opacity='0.7' /%3E%3C!-- Hexagons with icons --%3E%3Cpath d='M100 120 L125 105 L150 120 L150 150 L125 165 L100 150 Z' fill='%23267b9c' fill-opacity='0.2' stroke='%23267b9c' stroke-width='1.5' /%3E%3Ctext x='125' y='142' text-anchor='middle' font-family='Arial' font-size='16' fill='%23267b9c'%3E%E2%86%91%3C/text%3E%3Cpath d='M620 420 L645 405 L670 420 L670 450 L645 465 L620 450 Z' fill='%23267b9c' fill-opacity='0.2' stroke='%23267b9c' stroke-width='1.5' /%3E%3Ctext x='645' y='442' text-anchor='middle' font-family='Arial' font-size='16' fill='%23267b9c'%3E%E2%9C%93%3C/text%3E%3Cpath d='M700 150 L725 135 L750 150 L750 180 L725 195 L700 180 Z' fill='%23267b9c' fill-opacity='0.2' stroke='%23267b9c' stroke-width='1.5' /%3E%3Ctext x='725' y='172' text-anchor='middle' font-family='Arial' font-size='16' fill='%23267b9c'%3E%E2%86%92%3C/text%3E%3C!-- Connecting lines --%3E%3Cpath d='M150 135 L200 135 M260 350 L300 350 M670 435 L700 435 M400 300 L450 300 L450 380' stroke='%23267b9c' stroke-width='1' stroke-dasharray='5,5' stroke-opacity='0.7' /%3E%3C!-- Data points scatter --%3E%3Ccircle cx='480' cy='150' r='3' fill='%23267b9c' /%3E%3Ccircle cx='500' cy='170' r='3' fill='%23267b9c' /%3E%3Ccircle cx='520' cy='140' r='3' fill='%23267b9c' /%3E%3Ccircle cx='490' cy='190' r='3' fill='%23267b9c' /%3E%3Ccircle cx='510' cy='160' r='3' fill='%23267b9c' /%3E%3Ccircle cx='470' cy='180' r='3' fill='%23267b9c' /%3E%3C!-- Statistical curve --%3E%3Cpath d='M450 440 C500 340, 550 340, 600 440' fill='none' stroke='%23267b9c' stroke-width='2' stroke-opacity='0.7' /%3E%3C/svg%3E"); background-size: cover; background-position: center; background-repeat: no-repeat; - min-height: 100vh; + min-height: calc(100vh - 76px); } - .form-content { - background-color: #fff; - border-radius: 12px; - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); + .form-content { + background-color: #fff; + border: 1px solid #dce5eb; + border-radius: 20px; + box-shadow: 0 18px 50px rgba(16, 42, 67, 0.10); padding: 40px; margin: 40px auto; position: relative; @@ -44,26 +44,26 @@ } .form-title { - color: #4F46E5; - font-size: 2.5rem; + color: #0b1f33; + font-size: clamp(2.2rem, 5vw, 3.5rem); font-weight: 700; text-align: center; margin-bottom: 1rem; } .form-subtitle { - color: #6B7280; + color: #607486; text-align: center; font-size: 1.1rem; margin-bottom: 2.5rem; } - .form-group { - background: #F9FAFB; + .form-group { + background: #f8fafb; padding: 25px; border-radius: 8px; margin-bottom: 25px; - border: 1px solid #E5E7EB; + border: 1px solid #dce5eb; } .form-label { @@ -88,8 +88,8 @@ } .form-control:focus, .form-select:focus { - border-color: #6366F1; - box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2); + border-color: #0d9488; + box-shadow: 0 0 0 3px rgba(13, 148, 136, 0.14); outline: none; } @@ -112,9 +112,9 @@ padding: 6px 0; } - .btn-primary { - background-color: #4F46E5; - border-color: #4F46E5; + .btn-primary { + background-color: #0f766e; + border-color: #0f766e; color: white; padding: 12px 24px; font-size: 1rem; @@ -122,9 +122,9 @@ border-radius: 6px; } - .btn-primary:hover { - background-color: #4338CA; - border-color: #4338CA; + .btn-primary:hover { + background-color: #0b625c; + border-color: #0b625c; } .optional-badge { @@ -149,22 +149,23 @@
-

Statistical Model Finder

-

Answer a few simple questions about your research, and we'll help you find the most appropriate statistical model. Don't worry if you're not sure about some answers - we'll guide you through each step!

+
Guided model selection
+

Describe your research design

+

Answer a few questions about your goal and data. The recommendation will include a rationale and compatible alternatives—not just a model name.

- - - - + + +
Click to auto-fill the form with inputs that would recommend different models @@ -196,8 +197,7 @@

Statistical Model Finder

- - +
Think about what you want to achieve with your analysis. @@ -615,5 +615,6 @@

Statistical Model Finder

independentVarsHelpCluster.classList.add('d-none'); } } - -{% endblock %} \ No newline at end of file + +{% endblock %} +{% block container_end %}{% endblock %} diff --git a/templates/base.html b/templates/base.html index 37bcf6f..dd03606 100644 --- a/templates/base.html +++ b/templates/base.html @@ -3,558 +3,230 @@ + {% block title %}Statistical Model Suggester{% endblock %} - - - - - + + - - - - + - - - - - - {% block extra_css %}{% endblock %} - - {% block container_start %} -
- {% endblock %} - - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} -
+
+
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} {% for category, message in messages %} -
{{ message }}
+ {% endfor %} -
- {% endif %} - {% endwith %} - - {% block content %}{% endblock %} - {% block container_end %} -
- {% endblock %} + {% endif %} + {% endwith %} +
-
-
-
-
- StatSnapp - Your intelligent guide to statistical modeling + {% block container_start %}
{% endblock %} + {% block content %}{% endblock %} + {% block container_end %}
{% endblock %} + + + -
-
+
+
- -
+
-

AI Assistant

- +

Statistical assistant

+
- - + +
- - - - - - {% block scripts %}{% endblock %} - - - - - - - - - - - + + - - - + {% block scripts %}{% endblock %} diff --git a/templates/error.html b/templates/error.html index f04581d..e6f9b6f 100644 --- a/templates/error.html +++ b/templates/error.html @@ -1,108 +1,24 @@ - - - - - - Error - - - - - - - -
-
- - Return to Home - - - -
- - -
-
-
- -
-
-
-
-
-

Error

-
-
-
-
An error occurred:
-

{{ error }}

-
- -
-
-
-
-
- - - - - \ No newline at end of file +{% extends "base.html" %} + +{% block title %}Something went wrong — Statistical Model Suggester{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} + +{% endblock %} diff --git a/templates/history.html b/templates/history.html index 3a7f43b..d56de07 100644 --- a/templates/history.html +++ b/templates/history.html @@ -56,11 +56,11 @@
{{ entry.research_question }}
{% endif %}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/home.html b/templates/home.html index 1dd8176..9fbf10d 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,320 +1,197 @@ -{% extends "base.html" %} - -{% block title %}Statistical Model Suggester{% endblock %} - -{% block container_start %}{% endblock %} - -{% block extra_css %} - -{% endblock %} - -{% block content %} - -
-
-
-
-
-
-

Statistical Model Suggester

-

Providing expert statistical guidance and model recommendations for your research needs

-
- - Start Your Analysis - +{% extends "base.html" %} + +{% block title %}Statistical Model Suggester — Choose a model with confidence{% endblock %} +{% block page_context %}Choose an appropriate statistical model based on research goal, outcome type, predictors, sample size, distribution, missingness, and data structure.{% endblock %} +{% block container_start %}{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+
+
+
Evidence-aware model selection
+

Choose the right statistical model—with reasons you can defend.

+

Translate your research question and data structure into a focused recommendation, practical alternatives, assumptions, diagnostics, and implementation guidance.

+ -
-
-
-
- - - - -
-
-

Our Services

-
-
-
- -

Model Selection

-

Get personalized recommendations for the most appropriate statistical models based on your data characteristics and research goals.

- Get Started -
-
-
-
- -

Expert Consultation

-

Connect with qualified statisticians for personalized guidance and validation of your analysis approach.

- Find Experts -
-
-
-
- -

Educational Resources

-

Access comprehensive documentation, tutorials, and examples for each statistical model in our database.

- Learn More -
-
-
-
- -

Questionnaire Designer

-

Create professional research questionnaires tailored to your specific needs with our AI-powered questionnaire generator.

- Create Now -
-
-
-
- -
-
-
-
-
- -
-
{{ stats.models_count }}
-
Statistical Models
-
-
-
-
-
- -
-
{{ stats.access_hours }}
-
Hours of Model Access
-
-
-
-
-
- -
-
{{ stats.verification_rate }}
-
Expert Verified Rate
-
-
-
-
-
-{% endblock %} - + Browse the model library + +
+
+ No statistical jargon required + Assumptions made visible + Python and R examples +
+
+ +
+
+ + +
+
+
+
{{ stats.models_count }}documented models
+
{{ stats.access_hours }}self-guided access
+
5+implementation ecosystems
+
+
+
+ +
+
+
+
+
A clearer analytical workflow
+

Support for the decisions around the model—not just its name.

+
+

Move from an initial question to a method you can explain, implement, check, and communicate to collaborators.

+
+
+
+
+
+

Model selection

+

Answer guided questions about your goal and data to narrow the candidate set.

+ Find a model +
+
+
+
+
+

Model library

+

Compare assumptions, use cases, variable types, code, and interpretation guidance.

+ Explore models +
+
+
+
+
+

Questionnaire design

+

Build research instruments that align measures with the intended analysis.

+ Design a questionnaire +
+
+
+
+
+

Expert review

+

Bring in a statistician when design, assumptions, or interpretation need deeper judgment.

+ Find an expert +
+
+
+
+
+ +
+
+
+
+ +

Transparent criteria

Recommendations connect directly to the characteristics you provide.

+
+
+ +

Alternatives included

See other compatible approaches instead of a false single-answer result.

+
+
+ +

Validation first

Use assumptions and diagnostics before drawing substantive conclusions.

+
+
+
+
Start with your research question
+

Get a focused recommendation in a few guided steps.

+

You do not need to know the model name in advance. Describe what you want to learn and what your data look like.

+ Start model selection +
+
+
+{% endblock %} + {% block container_end %}{% endblock %} diff --git a/templates/model_details.html b/templates/model_details.html index 77309cc..2ec4b5a 100644 --- a/templates/model_details.html +++ b/templates/model_details.html @@ -4,41 +4,23 @@ {% block extra_css %} {% endblock %} {% block content %} -
+
+
+ Model library + + {{ model_name }} +
-
+
-
-

{{ model_name }}

+
+
Statistical model reference
+

{{ model_name }}

+

Review when to use this method, its data requirements, implementation patterns, and interpretation guidance.

-
+
Description
@@ -325,6 +316,5 @@
Academic Papers and References
- {% endblock %} diff --git a/templates/models_list.html b/templates/models_list.html index 3ad3e62..cca855e 100644 --- a/templates/models_list.html +++ b/templates/models_list.html @@ -1,46 +1,143 @@ -{% extends "base.html" %} - -{% block title %}Statistical Models{% endblock %} - -{% block content %} -
-
-
-
-
-

{{ group_name | replace('_', ' ') | title }}

-

Click on a model to view detailed information

-
- -
-
-
-
-{% endblock %} \ No newline at end of file +{% extends "base.html" %} + +{% block title %}{{ group_name }} — Statistical Model Library{% endblock %} +{% block page_context %}Browse {{ group_name }} with descriptions, analytical goals, and supported outcome types.{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block container_start %}{% endblock %} +{% block content %} +
+
+
Statistical model library
+

{{ group_name }}

+

+ {% if is_all_models %} + Explore methods by family, compare their intended uses, and open any model for assumptions, examples, and interpretation guidance. + {% else %} + Compare methods in this family and review the data requirements, assumptions, implementation examples, and interpretation guidance for each. + {% endif %} +

+
+
+ +
+
+
+ +
{{ models|length }} models shown
+
+ {% if is_all_models %} + + {% endif %} +
+
+ +
+ {% if is_all_models %} + {% for family_name, family_models in grouped_models %} +
+
+

{{ family_name }}

+ {{ family_models|length }} methods +
+ +
+ {% endfor %} + {% else %} +
+ +
+ {% endif %} +
+ + No models match that filter. Try a broader term such as “predict,” “binary,” or “time series.” +
+
+{% endblock %} +{% block container_end %}{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/results.html b/templates/results.html index 2a0441f..95870eb 100644 --- a/templates/results.html +++ b/templates/results.html @@ -1,126 +1,100 @@ -{% extends "base.html" %} - -{% block title %}Model Recommendation Results{% endblock %} - -{% block content %} -
-
-
-
-
-

Model Recommendation

-
-
- -
-
Research Question
-

{{ research_question }}

-
- - -
-
Recommended Model: {{ recommended_model }}
-
- {{ explanation | safe }} -
-
- - View Model Details - - - {% if recommended_model in MODEL_DATABASE and MODEL_DATABASE[recommended_model] is defined and MODEL_DATABASE[recommended_model].synthetic_data is defined %} - - See Synthetic Data Example - - {% endif %} - - - Interpretation Guide - -
-
- - -
-
-
Alternative Models
-
-
-
- {% if alternative_models %} - {% for model_name in alternative_models %} - {% if model_name in MODEL_DATABASE %} -
-
-
- Recommended Alternative -
-
-
{{ model_name }}
-

{{ MODEL_DATABASE[model_name].description }}

-
- - View Details - - - {% if model_name in MODEL_DATABASE and MODEL_DATABASE[model_name] is defined and MODEL_DATABASE[model_name].synthetic_data is defined %} - - Sample Code - - {% endif %} -
-
-
-
- {% endif %} - {% endfor %} - {% else %} -
-
- No specific alternative models were found for your analysis requirements. The recommended model is the best fit. -
-
- {% endif %} -
- - {% if alternative_models|length < 2 %} -
-
Other Models to Consider
-
- {% set count = namespace(value=0) %} - {% for model_name, model_info in MODEL_DATABASE.items() %} - {% if model_name != recommended_model and - model_name not in alternative_models and - (analysis_goal in model_info.analysis_goals) and - (dependent_variable_type in model_info.dependent_variable) and - count.value < 2 %} -
-
-
-
{{ model_name }}
-

{{ model_info.description }}

- - View Details - -
-
-
- {% set count.value = count.value + 1 %} - {% endif %} - {% endfor %} -
- {% endif %} -
-
- - -
-
-
-
-
-{% endblock %} +{% extends "base.html" %} + +{% block title %}Your model recommendation — Statistical Model Suggester{% endblock %} +{% block page_context %}Recommendation result: {{ recommended_model }} for the research question {{ research_question }}. Review the rationale, alternatives, assumptions, and model documentation.{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+
Leading recommendation
+

{{ recommended_model }}

+

Your question: {{ research_question }}

+
+ +
+
+
Why it fits
+

Recommendation rationale

+
{{ explanation | safe }}
+
+ + +
+ +
+
Compare before deciding
+

Compatible alternatives

+

These methods may also fit parts of your design. Compare their assumptions, interpretability, and validation requirements.

+ +
+ {% if alternative_models %} + {% for model_name in alternative_models %} + {% if model_name in MODEL_DATABASE %} + +

{{ model_name }}

+

{{ MODEL_DATABASE[model_name].description }}

+ Review this model +
+ {% endif %} + {% endfor %} + {% else %} +
No close alternatives matched all the information provided. Review the leading model carefully and consult a statistician for high-stakes work.
+ {% endif %} +
+
+ + +
+{% endblock %} diff --git a/templates/search_results.html b/templates/search_results.html index 6cae98f..8870982 100644 --- a/templates/search_results.html +++ b/templates/search_results.html @@ -1,56 +1,52 @@ {% extends "base.html" %} -{% block title %}Search Results{% endblock %} +{% block title %}Search results for “{{ query }}”{% endblock %} {% block content %} -
-
-
-
-
-

Search Results for "{{ query }}"

-
+
+
Search
+

Results for “{{ query }}”

+

Models and application pages matching your search.

+ + {% if page_results %} +
- {% if page_results %} -
-
Pages
- {% for page in page_results %} - {{ page.name }} - {% endfor %} +

Application pages

+
+ {% for page in page_results %} + {{ page.name }} + {% endfor %}
- {% endif %} - {% if results %} -
+ {% endif %} + + {% if results %} +
+

Statistical models

+
- {% endfor %}
- {% else %} -

No matching pages or models found for "{{ query }}".

- {% endif %} - + {% endfor %} +
+ + {% elif not page_results %} +
+
+ +

No matches found

+

Try a model family, analytical goal, or outcome type.

+ Browse all models
-
-
-
+ + {% endif %}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/tests/test_main_routes.py b/tests/test_main_routes.py index cd534bf..4dc2d33 100644 --- a/tests/test_main_routes.py +++ b/tests/test_main_routes.py @@ -131,7 +131,7 @@ def test_model_recommendation_consistency(self, client): response2 = client.post('/results', data=test_data) assert response1.status_code == 200 assert response2.status_code == 200 - # Responses should be consistent (same model recommended) + assert response1.data == response2.data def test_different_analysis_goals(self, client): """Test different analysis goals produce appropriate models.""" base_data = {