Skip to content

Filtres et Recherche

Paul Thiberville edited this page Oct 8, 2026 · 9 revisions

Filtres et recherche

Statut : 🟡 Brouillon · Màj : 2026-10-08 · Tickets : #164 #306

Les filtres sont une mécanique centrale : ils transforment « Je veux faire quelque chose » en « Voilà les parcours réellement adaptés à ma situation ». Ils sont accessibles immédiatement depuis l'accueil (chips) et en version complète (E-03).

Taxonomie

Les libellés ci-dessous sont des valeurs d'affichage. Les clés techniques (en anglais, snake_case) sont la seule chose persistée ; un libellé n'est jamais une clé.

Groupe Clé Valeurs (clé → libellé) Type
Public audiences solo Solo · couple Couple · family Famille · friends Entre amis · dog_friendly Dog friendly · kids_friendly Kids friendly multi-valeurs par parcours
Budget par personne budget free Gratuit · low Jusqu'à 25 € · medium 25 à 70 € · high Plus de 70 € une valeur, dérivée de la somme exacte des coûts par personne (D-032)
Durée duration 1_2h Jusqu'à 2 h 30 · half_day 2 h 30 à 5 h · full_day 5 à 12 h · weekend Plus de 12 h une valeur, dérivée de la durée calculée
Déplacement transport walk À pied · bike Vélo · metro Métro · car Voiture une valeur principale, valeurs secondaires possibles
Ambiance moods culture Culture · nature Nature · shopping Shopping · food Gastronomie · romantic Romantique · unusual Insolite · instagrammable Instagrammable · relax Détente · sport Sportif multi-valeurs
Conditions conditions no_booking Sans réservation · wheelchair Accessible PMR · indoor Intérieur · outdoor Extérieur · sunny Beau temps · rainy Jour de pluie multi-valeurs, certaines dérivées

Budget et durée s'affichent en montants et en heures (D-010) : « Premium » est réservé à l'abonnement et « Journée » n'est plus employé. Chaque valeur a une icône dans les chips (sauf Budget et Durée), listée dans Ecrans › E-03.

Seuils de dérivation (modifiables en configuration serveur, pas en dur dans l'app) :

  • Budget par personne : free = 0 € ; low ≤ 25 € ; medium ≤ 70 € ; high > 70 € (clé high et non premium, pour ne pas confondre ce palier avec les parcours Premium ; décision du 2026-09-23).
  • Durée : 1_2h ≤ 2 h 30 ; half_day ≤ 5 h ; full_day ≤ 12 h ; weekend au-delà.
  • no_booking : aucune étape avec booking_required = true.
  • indoor / outdoor : dérivés de la proportion d'étapes intérieures ; rainy implique majorité intérieure ou abritée, sunny majorité extérieure. Le créateur peut ajuster.

Logique de combinaison

  • Entre groupes : ET. couple ET rainy ET half_day ET medium ET culture.
  • Dans un groupe multi-valeurs (Public, Ambiance, Conditions) : OU. culture OU food retourne les parcours ayant l'une ou l'autre.
  • Budget et Durée : sélection d'une ou plusieurs valeurs, OU entre elles.
  • Le compteur de résultats est recalculé à chaque changement (requête légère count), avec un debounce de 300 ms. Sans réponse après 5 s, en erreur ou hors connexion, le bouton du panneau affiche « Voir les parcours » sans nombre et reste utilisable ; hors connexion, les filtres s'appliquent aux résultats en cache (Ecrans › E-03, états, D-011).
  • Un filtre appliqué persiste tant que l'app est ouverte ; il est visible dans les chips et sur le badge du bouton filtres.

Chips rapides de l'accueil

Une seule rangée défilante de chips de 36 px sous la recherche (le bouton filtres est intégré à la pilule de recherche, avec le compteur de filtres actifs) : Gratuit, puis les publics (Couple, Solo, Famille, Entre amis), puis Extérieur (icône banc), puis les ambiances. Quand des filtres sont appliqués depuis E-03, les filtres actifs passent en tête de la rangée ; un tap sur une chip active la retire. La durée se règle dans le panneau de filtres (E-03). La météo n'est plus une chip : elle s'affiche dans la ligne de position de la barre du haut et pilote une section de la sheet (voir « Section météo » ci-dessous, D-004, D-010). L'ordre peut être réorganisé plus tard selon l'usage mesuré (voir Analytics).

Section météo

Pas de bandeau ni de filtre appliqué automatiquement (D-004, D-010). La météo du jour à Paris (prévision à l'heure de consultation, ou pour l'après-midi si le matin est passé) s'affiche dans la ligne de position de la barre du haut (icône et température) et pilote une section de la sheet de l'accueil, toujours placée en deuxième, après « À proximité » :

  • Pluie ou forte probabilité de pluie : « À l'abri de la pluie · Intérieur · pluie cet après-midi », parcours rainy (majorité intérieure ou abritée).
  • Beau temps : « Parfait sous ce soleil · Extérieur · 22 °C », parcours sunny.
  • Sinon, pas de section météo.

La section ne touche ni aux filtres ni au compteur ; son « Voir tout » ouvre la liste de ses parcours (E-04). Source : Open-Meteo (gratuit, sans clé), appelé côté API et mis en cache 30 minutes.

Recherche par zone

  • Les résultats correspondent toujours à la zone affichée (bounding box de la carte), pas à un rayon autour de l'utilisateur.
  • Au chargement : zone autour de la position, ou Paris centre. Zoom initial couvrant environ 3 km.
  • Déplacement ou zoom manuel : aucun rechargement automatique ; apparition du bouton « Rechercher dans cette zone ». La liste existante reste affichée jusqu'à la nouvelle recherche.
  • Zone trop large : au-delà de search.cluster_area_km2 (40 km² par défaut, environ 6 × 6 km ; clé de settings, relue chaque minute, réglable sans déploiement), la recherche ne liste plus les parcours et répond en clusters ; la bottom sheet propose « Zoomez pour voir les parcours ». La zone initiale (environ 3 km) reste sous le seuil ; une vue de tout Paris le dépasse.
  • Clusters (#25, D-066) : grille de 8 × 8 cellules posée sur la zone affichée ; chaque parcours compte dans la cellule de son point de départ ; le cluster est placé à la moyenne des départs qu'il regroupe et porte leur nombre (200 clusters au plus, les plus gros d'abord). Mêmes filtres que la liste, donc les nombres s'additionnent au compteur. Un parcours dont seule l'enveloppe entre dans la zone compte dans la cellule de son départ, hors de la zone. Pourquoi la grille : aucun référentiel de quartiers n'existe, et une grille calculée sur la zone est déterministe, donc la réponse reste cachable (ETag stable). Écarté au MVP : l'agrégat par quartier, qui demanderait d'importer et de tenir des polygones de quartiers.
  • Marqueurs de la carte (#26, D-066) : la carte affiche les parcours de la première page de la recherche, 50 au plus, les mieux classés dans l'ordre recommandé ; les pages suivantes ne se lisent que dans « Voir tout » et n'ajoutent pas de marqueurs ; le sous-titre de la section donne le total de la zone. Plafond voulu au MVP : sous le seuil des clusters, la zone d'environ 3 km dépasse rarement 50 parcours avec le catalogue du lancement ; chaque marqueur porte une photo, dont le coût en mémoire est mesuré (200 marqueurs : de 753 Mo à 1,09 Go en build de développement) ; au-delà de 50, la carte d'une zone listée devient illisible. Si la densité du catalogue fait souvent dépasser 50 parcours dans une zone listée, on baisse d'abord search.cluster_area_km2. Écartée : une réponse dédiée à la carte (épingles légères sans card), à reconsidérer seulement si baisser le seuil ne suffit pas. Plafond confirmé par Ilan le 2026-09-28 (D-071, #221).
  • Un parcours est rattaché à une zone par le point de départ de sa première étape et par son enveloppe (bounding box des étapes). Il apparaît si son enveloppe intersecte la zone affichée.

Recherche textuelle

  • Priorité aux zones : quartier, arrondissement, station, ville (Mapbox Geocoding, pays FR, langue fr, types limités aux zones et aux stations). Widoo cherche des parcours dans une zone : ni adresse ni point d'intérêt, aucune épingle de lieu sur la carte (D-010).
  • Chaque zone proposée affiche son nombre de parcours.
  • Zones (/geocode) et parcours sont deux requêtes parallèles, affichées indépendamment : si l'une échoue, l'autre groupe s'affiche quand même (D-011). Délai de 5 s avant l'état d'erreur. Hors connexion, aucune requête : seules les recherches récentes et les parcours consultés, stockés sur le téléphone, restent disponibles.
  • Secondairement, les parcours dont le titre ou les tags correspondent (recherche ILIKE puis pg_trgm si besoin de tolérance).
  • La sélection d'une zone recentre la carte sur la zone (environ 3 km) et lance la recherche dans la zone ; la zone s'affiche dans la pilule de recherche et entre dans les recherches récentes (stockées sur le téléphone, 10 au plus).
  • Recherches récentes, ce que garde le téléphone (D-081, #306) : pour chaque zone choisie, son nom et son emprise seulement, ce qu'il faut pour l'afficher et recadrer la carte ; jamais la réponse brute de Mapbox (ni liste de suggestions, ni contexte, ni identifiant Mapbox). Rouvrir une zone récente ne rappelle pas /geocode : la carte se recadre sur l'emprise gardée et l'accueil affiche les parcours de la zone, depuis l'API Widoo (/routes/search) ou, hors connexion, depuis le cache des parcours déjà chargés sur le téléphone. « Résultats en cache » désigne toujours ces parcours de Widoo, jamais un résultat de géocodage.
  • Géocodage sans cache serveur (D-081, décision du 2026-10-08, prise par Paul Thiberville pour Ilan, #306) : au MVP, /geocode utilise les résultats temporaires de Mapbox, que ses conditions interdisent de conserver (« Temporary results are not allowed to be cached ») : l'API ne garde aucune réponse de géocodage, ni en mémoire ni en base, et répond Cache-Control: no-store. La charge reste bornée par le debounce de 300 ms de l'app et la limitation à 30 requêtes par minute et par IP sur /geocode. Raison : éviter une dépense prématurée, les résultats permanents exigeant une carte bancaire et une facturation à part chez Mapbox. Écarté pour l'instant : le géocodage permanent (MAPBOX_GEOCODING_PERMANENT=true), qui réactiverait le cache de 24 h déjà prêt dans le code. Question ouverte, à réévaluer avec le budget (volume de recherches mesuré, coût Mapbox du permanent) ; la recherche des stations (Search Box) ne fournit de toute façon que du temporaire.

Recommandation « autour de moi »

Le tri par défaut, dans les carrousels de la bottom sheet (E-01) comme dans les listes « Voir tout » (E-04), est un score de recommandation calculé côté API. Au MVP il est simple, déterministe et explicable ; aucun apprentissage tant qu'il n'y a pas de données d'usage. Il pourra être remplacé par un modèle personnalisé quand il y aura assez d'usage mesuré.

Le score s'applique aux parcours qui passent déjà les filtres (les filtres sont des exigences, pas des préférences). Pour chaque parcours, somme pondérée de composantes normalisées entre 0 et 1 :

Composante Poids Calcul
Proximité 0,30 1 à 0 entre 0 et 5 km du point de référence ; distance au point de départ du parcours. Point de référence : la position si la requête porte near, sinon le centre de la zone. L'app n'envoie jamais la position (Securite-et-RGPD) : elle mesure donc au centre de la zone affichée, d'abord centrée sur l'utilisateur
Qualité 0,25 note moyenne bayésienne (moyenne des parcours notés comme a priori, 3 sur 5 tant qu'aucun ne l'est ; 5 avis pour la neutraliser), ramenée de 1–5 à 0–1 ; 0,5 si aucun avis
Fiabilité 0,20 0,5 + 0,5 × part des lieux verified : 0,5 au lancement, quand aucun lieu n'est encore vérifié, 1 si tous le sont ; 0 dès qu'un lieu est flagged (la pénalité forte)
Fraîcheur 0,10 1 jusqu'à 30 jours après la publication ou le dernier recalcul (la plus récente des deux dates), puis décroissance linéaire jusqu'à 0 à 180 jours ; 0,5 sans aucune date
Contexte 0,10 moyenne des signaux connus, 0,5 sans aucun : moment de la journée (1 si le parcours convient au moment, 0 sinon ; voir « Moments » ci-dessous) ; météo du jour contre les conditions du parcours, neutre jusqu'à la section météo (#31)
Officiel 0,05 parcours « Par Widoo »

Après le tri, une passe de diversité : deux parcours consécutifs ne partagent pas la même ambiance principale et le même quartier ; le second est repoussé de trois positions maximum.

Les poids vivent dans settings, modifiables sans déploiement. L'API renvoie pour chaque card la raison principale (« À 600 m », « Très bien noté », « Vérifié récemment ») pour l'afficher discrètement, ce qui rend le classement compréhensible.

Interprétations retenues à l'implémentation (#63, D-066), confirmées par Ilan le 2026-09-28 (D-071, #221) :

  • Moments : déduits de la catégorie de la première étape, faute de moment saisi par le créateur (écarté : un champ de plus dans l'éditeur). Café, musée, galerie, monument, boutique, parc, balade, activité : le jour (matin, midi, après-midi) ; boulangerie : matin et midi ; restaurant : midi et soirée ; bar : soirée et nuit ; spectacle : soirée ; point de vue : le jour et la soirée ; other : aucun moment, donc neutre. Tranches à l'heure locale de la ville : matin 6–11 h, midi 11–14 h, après-midi 14–18 h, soirée 18–23 h, nuit sinon. Le curseur garde l'instant de la première page : le moment, donc l'ordre, ne change pas pendant le défilement.
  • Calcul : qualité, fiabilité, fraîcheur, officiel et moments sont recalculés chaque nuit dans routes.recommendation (POST /internal/recommendation, API › Interne) ; proximité et contexte se calculent à la requête. Tous les parcours qui passent les filtres sont classés, puis la page est lue par position.
  • Diversité : l'ambiance principale est la première ambiance du parcours, le quartier celui de sa première étape ; une valeur inconnue ne fait jamais doublon.
  • Poids : clé recommendation.weights de settings, objet partiel { proximity, quality, reliability, freshness, context, official }, chaque poids entre 0 et 10, relu chaque minute ; un poids absent prend la valeur du tableau ; une clé inconnue, un poids hors bornes ou des poids tous nuls font revenir au tableau entier.
  • Raison (reason) : parmi les composantes qui méritent d'être dites, celle qui pèse le plus dans le score (poids × valeur) : near_you avec distanceM (« À 600 m », seulement si la requête porte near), top_rated (au moins un avis et une note bayésienne d'au moins 4 sur 5, « Très bien noté »), verified (tous les lieux vérifiés, « Vérifié récemment »), fresh (moins de 30 jours), good_timing (le parcours convient au moment de la journée), official (« Par Widoo »). null quand aucune ne se dégage : la card n'affiche alors pas de raison. Comme l'app n'envoie pas near, elle ne reçoit jamais near_you : la distance reste le tag de la card, calculée sur le téléphone. Affichage (D-071, #222, choix d'Ilan) : seules les raisons qui apportent une information se lisent sur la card, à droite du budget : good_timing « Moment idéal » (« Perfect timing ») et fresh « Récent » (« Recent ») ; top_rated, verified et official ne s'affichent pas, la note, la pastille Vérifié et la ligne « Par Widoo » le disant déjà (« Nouveau » reste la pastille des parcours sans avis). Détail : Ecrans › E-01. near reste dans le contrat de /routes/search pour d'autres clients (site, admin) : exigé par sort=distance, seule source de near_you ; l'app ne l'envoie jamais (D-071).

Autres tris proposés à l'utilisateur : distance, durée croissante, note. Le tri choisi est mémorisé pour la session.

Clone this wiki locally