Files
opencare/templates/profile.html
laurentbarontini efd775706b Initial production-ready release of OpenCare v1
- Flask/SQLAlchemy app with profile, dashboard, recommendations
- AI crawl pipeline (GPT-4o) with admin review workflow
- 14 curated health sources (RSS + index crawling)
- Production config: env vars, Gunicorn, systemd, nginx
- deploy/ scripts for VPS setup and updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 21:38:03 +02:00

439 lines
22 KiB
HTML

{% extends "base.html" %}
{% block title %}Mon profil de santé — OpenCare{% endblock %}
{% block content %}
<div class="container py-4">
<div class="row mb-4">
<div class="col">
<h1 class="fw-bold h3 mb-1"><i class="fas fa-user-edit text-primary me-2"></i>Mon profil de santé</h1>
<p class="text-muted">Plus votre profil est complet, plus les recommandations seront précises.</p>
</div>
</div>
<form method="POST" id="profileForm">
<!-- Tab Navigation -->
<ul class="nav nav-pills profile-tabs mb-4 flex-wrap gap-1" id="profileTabs" role="tablist">
{% for tab_id, icon, label in [
('identite', 'fas fa-id-card', 'Identité'),
('origine', 'fas fa-globe', 'Origine & Biologie'),
('sante', 'fas fa-notes-medical', 'Antécédents'),
('lifestyle', 'fas fa-sun', 'Mode de vie'),
('alimentation', 'fas fa-utensils', 'Alimentation'),
('activite', 'fas fa-running', 'Activité physique'),
('objectifs', 'fas fa-bullseye', 'Objectifs'),
] %}
<li class="nav-item" role="presentation">
<button class="nav-link {% if loop.first %}active{% endif %} profile-tab-btn"
id="tab-{{ tab_id }}" data-bs-toggle="tab"
data-bs-target="#pane-{{ tab_id }}" type="button">
<i class="{{ icon }} me-1"></i>{{ label }}
</button>
</li>
{% endfor %}
</ul>
<div class="tab-content profile-tab-content">
<!-- ① IDENTITÉ -->
<div class="tab-pane fade show active" id="pane-identite">
<div class="profile-section card border-0 shadow-sm">
<div class="card-body p-4">
<h5 class="section-title"><i class="fas fa-id-card me-2 text-primary"></i>Informations personnelles</h5>
<div class="row g-3 mt-1">
<div class="col-md-6">
<label class="form-label fw-semibold">Prénom</label>
<input type="text" name="first_name" class="form-control"
value="{{ profile.first_name or '' }}" placeholder="Votre prénom">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">Nom</label>
<input type="text" name="last_name" class="form-control"
value="{{ profile.last_name or '' }}" placeholder="Votre nom">
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Année de naissance</label>
<input type="number" name="birth_year" class="form-control"
min="1920" max="2015"
value="{{ profile.birth_year or '' }}" placeholder="Ex: 1985">
{% if profile.age %}
<div class="form-text text-success"><i class="fas fa-check me-1"></i>{{ profile.age }} ans</div>
{% endif %}
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Sexe biologique</label>
<select name="sex" class="form-select">
<option value="">— Sélectionner —</option>
{% for val, lbl in [('male','Homme'),('female','Femme'),('other','Autre / Non binaire')] %}
<option value="{{ val }}" {% if profile.sex == val %}selected{% endif %}>{{ lbl }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Taille (cm)</label>
<input type="number" name="height_cm" class="form-control"
min="100" max="250" step="0.5"
value="{{ profile.height_cm or '' }}" placeholder="Ex: 175"
id="heightInput">
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Poids (kg)</label>
<input type="number" name="weight_kg" class="form-control"
min="30" max="300" step="0.1"
value="{{ profile.weight_kg or '' }}" placeholder="Ex: 70"
id="weightInput">
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">IMC calculé</label>
<div class="form-control bg-light fw-semibold" id="bmiDisplay">
{% if profile.bmi %}
<span class="text-{{ profile.bmi_category[1] }}">{{ profile.bmi }} — {{ profile.bmi_category[0] }}</span>
{% else %}
<span class="text-muted">— (remplissez taille & poids)</span>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ② ORIGINE & BIOLOGIE -->
<div class="tab-pane fade" id="pane-origine">
<div class="profile-section card border-0 shadow-sm">
<div class="card-body p-4">
<h5 class="section-title"><i class="fas fa-globe me-2 text-primary"></i>Origine & Biologie</h5>
<p class="text-muted small mb-4">Ces données permettent d'adapter les recommandations aux prédispositions génétiques et culturelles associées aux origines.</p>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-semibold">Origine ethnique</label>
<select name="ethnicity" class="form-select">
<option value="">— Sélectionner —</option>
{% for val, lbl in [
('european', 'Européen / Caucasien'),
('african', 'Africain / Afro-descendant'),
('asian_east', 'Asiatique de l\'Est (Chine, Japon, Corée)'),
('asian_south', 'Asiatique du Sud (Inde, Pakistan)'),
('latin', 'Latino-américain'),
('middle_eastern', 'Moyen-Oriental / Maghrébin'),
('mixed', 'Métis / Origines multiples'),
('other', 'Autre'),
] %}
<option value="{{ val }}" {% if profile.ethnicity == val %}selected{% endif %}>{{ lbl }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">Pays d'origine</label>
<input type="text" name="country_origin" class="form-control"
value="{{ profile.country_origin or '' }}" placeholder="Ex: France, Sénégal, Maroc...">
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Groupe sanguin</label>
<select name="blood_type" class="form-select">
<option value="">— Inconnu —</option>
{% for bt in ['A+','A-','B+','B-','AB+','AB-','O+','O-'] %}
<option value="{{ bt }}" {% if profile.blood_type == bt %}selected{% endif %}>{{ bt }}</option>
{% endfor %}
</select>
</div>
<div class="col-12">
<div class="info-box mt-2">
<i class="fas fa-info-circle text-primary me-2"></i>
<strong>Pourquoi ces données ?</strong> Certaines populations présentent des prédispositions documentées : intolérance au lactose plus fréquente en Asie de l'Est (~90%), risque de diabète T2 plus élevé en Asie du Sud à IMC plus bas, etc.
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ③ ANTÉCÉDENTS -->
<div class="tab-pane fade" id="pane-sante">
<div class="profile-section card border-0 shadow-sm">
<div class="card-body p-4">
<h5 class="section-title"><i class="fas fa-notes-medical me-2 text-primary"></i>Antécédents de santé</h5>
<p class="text-muted small mb-4">Cochez les conditions qui vous concernent ou qui ont des antécédents familiaux directs.</p>
<div class="row g-2 mb-4">
{% for val, lbl in [
('hypertension', '🫀 Hypertension artérielle'),
('diabetes_risk', '🍬 Risque / prédiabète T2'),
('cardiovascular_risk', '❤️ Risque cardiovasculaire'),
('anxiety', '🧠 Anxiété / dépression'),
('digestive_issues', '🫃 Troubles digestifs / SII'),
('back_pain', '🦵 Douleurs dorsales / lombalgies'),
('osteoporosis_risk', '🦴 Risque d\'ostéoporose'),
('metabolic_syndrome', '⚖️ Syndrome métabolique'),
('thyroid_issues', '🦋 Troubles thyroïdiens'),
('sleep_apnea', '😮 Apnée du sommeil'),
] %}
<div class="col-md-6">
<div class="form-check condition-check">
<input class="form-check-input" type="checkbox" name="conditions"
value="{{ val }}" id="cond_{{ val }}"
{% if val in (profile.conditions or []) %}checked{% endif %}>
<label class="form-check-label" for="cond_{{ val }}">{{ lbl }}</label>
</div>
</div>
{% endfor %}
</div>
<div>
<label class="form-label fw-semibold">Allergies / médicaments (optionnel)</label>
<textarea name="allergies_text" class="form-control" rows="3"
placeholder="Ex: allergie aux arachides, prise de metformine, antihypertenseurs...">{{ profile._allergies_text or '' }}</textarea>
<div class="form-text">Ces informations restent locales et ne sont pas transmises.</div>
</div>
</div>
</div>
</div>
<!-- ④ MODE DE VIE -->
<div class="tab-pane fade" id="pane-lifestyle">
<div class="profile-section card border-0 shadow-sm">
<div class="card-body p-4">
<h5 class="section-title"><i class="fas fa-sun me-2 text-primary"></i>Mode de vie</h5>
<div class="row g-4 mt-1">
<div class="col-md-6">
<label class="form-label fw-semibold">
Durée de sommeil moyenne
<span class="badge bg-light text-dark ms-2" id="sleepVal">
{{ '%.1f'|format(profile.sleep_hours) if profile.sleep_hours else '7.0' }}h
</span>
</label>
<input type="range" name="sleep_hours" class="form-range" min="3" max="12" step="0.5"
value="{{ profile.sleep_hours or 7 }}"
oninput="document.getElementById('sleepVal').textContent = this.value + 'h'">
<div class="d-flex justify-content-between text-muted small">
<span>3h</span><span>7.5h (optimal)</span><span>12h</span>
</div>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">
Niveau de stress
<span class="badge bg-light text-dark ms-2" id="stressVal">
{{ profile.stress_level or 5 }}/10
</span>
</label>
<input type="range" name="stress_level" class="form-range stress-range" min="1" max="10"
value="{{ profile.stress_level or 5 }}"
oninput="document.getElementById('stressVal').textContent = this.value + '/10'">
<div class="d-flex justify-content-between text-muted small">
<span>😌 Faible</span><span>😰 Élevé</span>
</div>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">Tabac</label>
<select name="smoking" class="form-select">
<option value="">— Sélectionner —</option>
{% for val, lbl in [('never','Non-fumeur'),('former','Ex-fumeur'),('occasional','Fumeur occasionnel'),('current','Fumeur régulier')] %}
<option value="{{ val }}" {% if profile.smoking == val %}selected{% endif %}>{{ lbl }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">Consommation d'alcool</label>
<select name="alcohol_freq" class="form-select">
<option value="">— Sélectionner —</option>
{% for val, lbl in [('never','Jamais'),('occasional','Occasionnel (<1x/sem)'),('moderate','Modéré (1-3x/sem)'),('frequent','Fréquent (>3x/sem)')] %}
<option value="{{ val }}" {% if profile.alcohol_freq == val %}selected{% endif %}>{{ lbl }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</div>
<!-- ⑤ ALIMENTATION -->
<div class="tab-pane fade" id="pane-alimentation">
<div class="profile-section card border-0 shadow-sm">
<div class="card-body p-4">
<h5 class="section-title"><i class="fas fa-utensils me-2 text-primary"></i>Alimentation</h5>
<div class="row g-4 mt-1">
<div class="col-md-6">
<label class="form-label fw-semibold">Type de régime alimentaire</label>
<select name="diet_type" class="form-select">
<option value="">— Sélectionner —</option>
{% for val, lbl in [
('omnivore', '🥩 Omnivore'),
('flexitarian', '🥗 Flexitarien'),
('vegetarian', '🌿 Végétarien'),
('vegan', '🌱 Végétalien / Vegan'),
('keto', '🥑 Cétogène / Low-carb'),
('paleo', '🍖 Paléo'),
('mediterranean', '🫒 Méditerranéen'),
] %}
<option value="{{ val }}" {% if profile.diet_type == val %}selected{% endif %}>{{ lbl }}</option>
{% endfor %}
</select>
</div>
<div class="col-12">
<label class="form-label fw-semibold">Intolérances / exclusions alimentaires</label>
<div class="row g-2 mt-1">
{% for val, lbl in [
('gluten', '🌾 Gluten'),
('lactose', '🥛 Lactose'),
('fructose', '🍎 Fructose'),
('nuts', '🥜 Fruits à coque'),
('shellfish', '🦐 Crustacés'),
('eggs', '🥚 Œufs'),
('soy', '🌿 Soja'),
('nightshades', '🍅 Solanacées'),
] %}
<div class="col-md-3 col-6">
<div class="form-check intol-check">
<input class="form-check-input" type="checkbox" name="food_intolerances"
value="{{ val }}" id="intol_{{ val }}"
{% if val in (profile.food_intolerances or []) %}checked{% endif %}>
<label class="form-check-label" for="intol_{{ val }}">{{ lbl }}</label>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ⑥ ACTIVITÉ PHYSIQUE -->
<div class="tab-pane fade" id="pane-activite">
<div class="profile-section card border-0 shadow-sm">
<div class="card-body p-4">
<h5 class="section-title"><i class="fas fa-running me-2 text-primary"></i>Activité physique</h5>
<div class="row g-4 mt-1">
<div class="col-12">
<label class="form-label fw-semibold">Niveau d'activité global</label>
<div class="row g-2 mt-1">
{% for val, icon, lbl, desc in [
('sedentary', '🪑', 'Sédentaire', 'Peu ou pas d\'exercice, travail de bureau'),
('light', '🚶', 'Léger', '1-3 fois/semaine, activité légère'),
('moderate', '🚴', 'Modéré', '3-5 fois/semaine, effort modéré'),
('intense', '🏋️', 'Intense', '5-6 fois/semaine, effort soutenu'),
('athlete', '🏆', 'Athlète', 'Entraînement quotidien ou compétition'),
] %}
<div class="col-md-4 col-6">
<label class="activity-level-card {% if profile.activity_level == val %}selected{% endif %}">
<input type="radio" name="activity_level" value="{{ val }}"
{% if profile.activity_level == val %}checked{% endif %}>
<div class="act-icon">{{ icon }}</div>
<div class="act-label fw-semibold">{{ lbl }}</div>
<div class="act-desc text-muted small">{{ desc }}</div>
</label>
</div>
{% endfor %}
</div>
</div>
<div class="col-12">
<label class="form-label fw-semibold">Types d'activités pratiquées</label>
<div class="row g-2 mt-1">
{% for val, lbl in [
('running', '🏃 Course à pied'),
('cycling', '🚴 Vélo'),
('swimming', '🏊 Natation'),
('gym', '🏋️ Musculation/Salle'),
('yoga', '🧘 Yoga / Pilates'),
('team_sports', '⚽ Sports collectifs'),
('martial_arts', '🥋 Arts martiaux'),
('hiking', '🥾 Randonnée'),
('crossfit', '🔥 CrossFit / Functional'),
] %}
<div class="col-md-4 col-6">
<div class="form-check activity-check">
<input class="form-check-input" type="checkbox" name="activity_types"
value="{{ val }}" id="act_{{ val }}"
{% if val in (profile.activity_types or []) %}checked{% endif %}>
<label class="form-check-label" for="act_{{ val }}">{{ lbl }}</label>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ⑦ OBJECTIFS -->
<div class="tab-pane fade" id="pane-objectifs">
<div class="profile-section card border-0 shadow-sm">
<div class="card-body p-4">
<h5 class="section-title"><i class="fas fa-bullseye me-2 text-primary"></i>Objectifs de santé</h5>
<p class="text-muted small mb-4">Sélectionnez vos objectifs principaux. Les recommandations seront filtrées et priorisées en conséquence.</p>
<div class="row g-3">
{% for val, icon, lbl, desc in [
('weight_loss', '⚖️', 'Perte de poids', 'Réduire la masse grasse de façon durable'),
('muscle_gain', '💪', 'Prise de muscle', 'Augmenter la masse musculaire'),
('longevity', '🌿', 'Longévité', 'Vieillir en bonne santé'),
('energy', '⚡', 'Plus d\'énergie', 'Réduire la fatigue chronique'),
('sleep_quality', '😴', 'Sommeil', 'Améliorer la qualité et durée'),
('stress_reduction', '🧘', 'Réduire le stress', 'Anxiété, bien-être mental'),
('metabolic_health', '🔬', 'Santé métabolique', 'Glycémie, insuline, lipides'),
('cardiovascular_health', '❤️', 'Santé cardio', 'Réduire les facteurs de risque'),
('cognitive_performance', '🧠', 'Performance cognitive', 'Mémoire, concentration, focus'),
] %}
<div class="col-md-4">
<label class="goal-card {% if val in (profile.health_goals or []) %}selected{% endif %}">
<input type="checkbox" name="health_goals" value="{{ val }}"
{% if val in (profile.health_goals or []) %}checked{% endif %}>
<div class="goal-icon">{{ icon }}</div>
<div class="goal-title fw-semibold">{{ lbl }}</div>
<div class="goal-desc text-muted small">{{ desc }}</div>
</label>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div><!-- end tab-content -->
<div class="d-flex justify-content-between align-items-center mt-4">
<a href="{{ url_for('dashboard') }}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-2"></i>Retour au tableau de bord
</a>
<button type="submit" class="btn btn-primary btn-lg px-5 fw-semibold">
<i class="fas fa-save me-2"></i>Sauvegarder mon profil
</button>
</div>
</form>
</div>
{% endblock %}
{% block scripts %}
<script>
// BMI auto-calculation
function calcBMI() {
const h = parseFloat(document.getElementById('heightInput').value);
const w = parseFloat(document.getElementById('weightInput').value);
const el = document.getElementById('bmiDisplay');
if (h > 0 && w > 0) {
const bmi = (w / Math.pow(h/100, 2)).toFixed(1);
let cat, cls;
if (bmi < 18.5) { cat = 'Insuffisance pondérale'; cls = 'text-info'; }
else if (bmi < 25) { cat = 'Poids normal'; cls = 'text-success'; }
else if (bmi < 30) { cat = 'Surpoids'; cls = 'text-warning'; }
else { cat = 'Obésité'; cls = 'text-danger'; }
el.innerHTML = `<span class="${cls}">${bmi}${cat}</span>`;
}
}
document.getElementById('heightInput').addEventListener('input', calcBMI);
document.getElementById('weightInput').addEventListener('input', calcBMI);
// Activity level card selection
document.querySelectorAll('.activity-level-card input').forEach(inp => {
inp.addEventListener('change', () => {
document.querySelectorAll('.activity-level-card').forEach(c => c.classList.remove('selected'));
if (inp.checked) inp.closest('.activity-level-card').classList.add('selected');
});
});
// Goal card selection
document.querySelectorAll('.goal-card input').forEach(inp => {
inp.addEventListener('change', () => {
inp.closest('.goal-card').classList.toggle('selected', inp.checked);
});
});
</script>
{% endblock %}