New cockpit

This commit is contained in:
2026-05-07 20:47:49 +02:00
parent 474f18404f
commit 0bca327b80
4 changed files with 656 additions and 60 deletions

93
app.py
View File

@@ -46,6 +46,46 @@ _DOMAIN_LABELS = {
'other': ('Autre', '📌'),
}
_CONDITION_LABELS = {
'hypertension': 'Hypertension',
'diabetes_risk': 'Risque diabète',
'cardiovascular_risk': 'Risque cardio-vasculaire',
'anxiety': 'Anxiété',
'digestive_issues': 'Troubles digestifs',
'back_pain': 'Douleurs dorsales',
'osteoporosis_risk': 'Risque ostéoporose',
'metabolic_syndrome': 'Syndrome métabolique',
'thyroid': 'Thyroïde',
'sleep_apnea': 'Apnée du sommeil',
}
_GOAL_LABELS = {
'weight_loss': 'Perte de poids',
'muscle_gain': 'Prise de muscle',
'longevity': 'Longévité',
'energy': 'Énergie',
'better_sleep': 'Améliorer le sommeil',
'stress_reduction': 'Réduction du stress',
'metabolic_health': 'Santé métabolique',
'cardiovascular_health': 'Santé cardio-vasculaire',
'cognitive_health': 'Santé cognitive',
}
_DIET_LABELS = {
'omnivore': 'Omnivore',
'flexitarian': 'Flexitarien',
'vegetarian': 'Végétarien',
'vegan': 'Végétalien',
'paleo': 'Paléo',
'keto': 'Kéto',
'other': 'Autre',
}
_ACTIVITY_LABELS = {
'sedentary': 'Sédentaire',
'light': 'Légère activité',
'moderate': 'Activité modérée',
'intense': 'Activité intense',
'athlete': 'Athlète',
}
# ── Models ─────────────────────────────────────────────────────────────────────
@@ -796,9 +836,37 @@ def admin_review_update(rid):
@login_required
@admin_required
def admin_library():
from pipeline import _CONDITIONS, _HEALTH_GOALS, _DIET_TYPES, _ACTIVITY_LEVELS
recs = PendingRecommendation.query.filter_by(status='approved') \
.order_by(PendingRecommendation.domain, PendingRecommendation.priority.desc()).all()
return render_template('admin/library.html', recs=recs, domain_labels=_DOMAIN_LABELS)
tree = {}
ev_total = {'A': 0, 'B': 0, 'C': 0}
for rec in recs:
d = rec.domain
ev = rec.evidence_level if rec.evidence_level in ('A', 'B', 'C') else 'C'
ev_total[ev] += 1
if d not in tree:
tree[d] = {'recs': [], 'tags': {}, 'ev': {'A': 0, 'B': 0, 'C': 0}}
tree[d]['recs'].append(rec)
tree[d]['ev'][ev] += 1
for tag in (rec.tags or []):
tag = tag.strip().lower()
if not tag:
continue
if tag not in tree[d]['tags']:
tree[d]['tags'][tag] = {'recs': [], 'ev': {'A': 0, 'B': 0, 'C': 0}}
tree[d]['tags'][tag]['recs'].append(rec)
tree[d]['tags'][tag]['ev'][ev] += 1
return render_template('admin/library.html',
recs=recs, tree=tree,
domain_labels=_DOMAIN_LABELS, ev_total=ev_total,
conditions=_CONDITIONS, health_goals=_HEALTH_GOALS,
diet_types=_DIET_TYPES, activity_levels=_ACTIVITY_LEVELS,
condition_labels=_CONDITION_LABELS, goal_labels=_GOAL_LABELS,
diet_labels=_DIET_LABELS, activity_labels=_ACTIVITY_LABELS)
@app.route('/admin/library/<int:rid>/delete', methods=['POST'])
@@ -814,6 +882,29 @@ def admin_library_delete(rid):
return redirect(url_for('admin_library'))
@app.route('/admin/library/<int:rid>/update', methods=['POST'])
@login_required
@admin_required
def admin_library_update(rid):
rec = db.get_or_404(PendingRecommendation, rid)
f = request.form
rec.target_sex = f.get('target_sex', 'all')
rec.target_age_min = int(f['target_age_min']) if f.get('target_age_min') else None
rec.target_age_max = int(f['target_age_max']) if f.get('target_age_max') else None
rec.not_for_under_18 = 'not_for_under_18' in f
rec.not_for_pregnant = 'not_for_pregnant' in f
rec.required_conditions = f.getlist('required_conditions')
rec.excluded_conditions = f.getlist('excluded_conditions')
rec.required_health_goals = f.getlist('required_health_goals')
rec.required_diet_types = f.getlist('required_diet_types')
rec.required_activity_levels = f.getlist('required_activity_levels')
rec.priority = int(f.get('priority', rec.priority))
rec.admin_notes = f.get('admin_notes', '').strip()
db.session.commit()
flash(f'Ciblage de « {rec.title} » mis à jour.', 'success')
return redirect(url_for('admin_library'))
@app.route('/admin/config', methods=['GET', 'POST'])
@login_required
@admin_required