Module 16 — Projet : le moteur Veille complet, recherche et recommandation
Inès a réservé la petite salle vitrée du QG de Veille et écrit trois verbes au feutre bleu sur le tableau : « trouver, analyser, recommander ». Ce module est le contrat que Sami signe avec elle : assembler les quinze modules précédents en un moteur qui répond à ces trois verbes sur les 200 853 articles du corpus News, et savoir le démontrer à un prospect en cinq minutes sans jamais quitter le kit.
Cahier des charges en une page
Le produit vise trois profils de clients — agence de communication politique, magazine bien-être, agence de voyage. Il tient dans le kit et se démontre sur un poste portable à 6 Go de RAM alloués à Docker. Rien à installer localement ; toute manipulation passe par ./lab.sh, Kibana Dev Tools, Neo4j Browser et le conteneur veille-python.
Ce qui doit tourner à la fin :
- Elasticsearch 9.5.3 avec l'index
news(200 853 documents, un shard, zéro réplique, mapping du kit). - Kibana avec la vue de données
newset le tableau de bordVeille — vue d'ensemble(quatre visualisations, un contrôle par catégorie). - Neo4j 5.26 Community avec le graphe News (200 853
:Article, 41:Categorie, 23 082:Auteur) et ses trois contraintes d'unicité. - Un script
python/veille.pyétendu qui combine Elasticsearch et Neo4j.
Ce qui doit être versionné dans un dossier livraison-projet/ (à côté du kit, à zipper en fin de projet) :
livraison-projet/
├── README.md # mode d'emploi de la démo, 5 min chrono
├── es/
│ ├── mapping-news.json # copie annotée du mapping
│ └── requetes-personas.md # 15 requêtes commentées (5 par persona)
├── kibana/
│ └── veille.ndjson # export du tableau de bord + vue de données
├── neo4j/
│ └── recommandations.cypher # 5 requêtes de recommandation
└── python/
└── veille.py # script étendu (filtre catégorie/période + explication)
Les trois personas
Chaque persona dicte cinq requêtes de l'étape 2, une entrée du dashboard de l'étape 3 et une ligne de la démo de l'étape 7.
- Client A — Agence de communication politique. Suit les articles
POLITICS(32 739 au total) entre 2016 et 2017, veut identifier les auteurs les plus prolifiques sur la campagne présidentielle et exporter une chronologie mensuelle. Auteurs cibles connus : Lee Moran (264 articles en POLITICS), Ed Mazza, Ron Dicker. - Client B — Magazine bien-être. Couvre
WELLNESS(17 827) etHEALTHY LIVING. Exige l'autocomplétion sur les titres (composantcompletiondu module 8) et la tolérance aux fautes (« yoga » retrouvé même quand l'utilisateur tape « yaga »). Base de mots-clés attendue : yoga, meditation, mindfulness, sleep, diet. - Client C — Agence de voyage. Suit
TRAVEL(9 887 articles) mois par mois pour caler ses campagnes sur la saisonnalité. Veut repérer les pics d'été et de fin d'année entre 2012 et 2018.
Les sept étapes
Étape 1 — L'index news avec un mapping justifié
Livrable. livraison-projet/es/mapping-news.json copié depuis kits/42-elasticsearch-neo4j/elasticsearch/mappings/news.json, plus une section du README.md qui justifie chaque champ.
Critère de réussite.
./lab.sh es news/_count
{"count":200853,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0}}
./lab.sh es news/_mapping
La sortie doit montrer headline en text avec l'analyseur titre_en et deux sous-champs (raw en keyword, suggest en completion), category en keyword, date en date au format yyyy-MM-dd, authors en text avec un sous-champ raw.
Piste de départ. Le mapping du kit couvre déjà tous les besoins ; le travail consiste à rédiger la justification. Pour chaque champ, une phrase : « category est keyword pour permettre l'agrégation exacte sur les 41 valeurs et le filtre par contexte filtre (module 4) », « headline.suggest est un completion pour l'autocomplétion du persona B (module 8) ».
Piège. Redéfinir headline sans le sous-champ suggest casse toute l'étape B. Un completion ne s'ajoute pas à un mapping existant : il faut un _reindex complet (module 4). Vérifier avant de démarrer que le sous-champ est bien présent.
Étape 2 — Cinq requêtes de recherche par persona
Livrable. livraison-projet/es/requetes-personas.md, 15 requêtes classées par persona, chacune avec le compteur attendu et une phrase de contexte métier.
Critère de réussite. Toutes les requêtes se collent-jouent dans Kibana Dev Tools et renvoient des chiffres cohérents avec les autres personas.
Piste de départ. Trois exemples fondateurs à décliner.
Persona A — recherche pertinente sur la campagne 2016-2017 :
GET news/_search
{
"size": 5,
"query": {
"bool": {
"must": [ { "match": { "headline": "trump election" } } ],
"filter": [
{ "term": { "category": "POLITICS" } },
{ "range": { "date": { "gte": "2016-01-01", "lte": "2017-12-31" } } }
]
}
},
"_source": ["headline", "date", "authors"],
"highlight": {
"fields": { "headline": { "pre_tags": ["<mark>"], "post_tags": ["</mark>"] } }
}
}
Ce patron bool avec must (le texte libre, scoré) et filter (les critères binaires, cachés) est celui qui doit revenir dans tout ce que fait Veille (module 5). Les cinq requêtes du persona A : cette recherche, une date_histogram mensuelle sur POLITICS 2016-2017, un terms sur authors.raw filtré POLITICS, un term sur authors.raw = "Lee Moran" avec sous-agrégation par catégorie, et une variante bool qui exclut Russia avec must_not.
Persona B — autocomplétion sur headline.suggest :
GET news/_search
{
"suggest": {
"titre_suggest": {
"prefix": "yog",
"completion": {
"field": "headline.suggest",
"size": 5,
"skip_duplicates": true
}
}
}
}
Tolérance aux fautes :
GET news/_search
{
"size": 5,
"query": {
"match": {
"headline": { "query": "meditaton mindfullness", "fuzziness": "AUTO" }
}
}
}
Les cinq requêtes du persona B : ces deux-là, un multi_match sur "healthy diet" (headline^3 et short_description), un terms sur category filtré sur WELLNESS et HEALTHY LIVING (résultat : 17 827 + votre chiffre HEALTHY LIVING peut différer légèrement), un top_hits par catégorie qui renvoie le titre le plus récent (module 6).
Persona C — saisonnalité mensuelle :
GET news/_search
{
"size": 0,
"query": { "term": { "category": "TRAVEL" } },
"aggs": {
"par_mois": {
"date_histogram": {
"field": "date",
"calendar_interval": "month",
"format": "yyyy-MM"
}
}
}
}
Le corpus TRAVEL totalise 9 887 documents répartis du 2012-01-28 au 2018-05-26 ; l'histogramme doit produire environ 77 buckets mensuels, avec un pic estival visible chaque année (votre chiffre peut différer légèrement d'un mois à l'autre). Les quatre autres requêtes du persona C : un terms sur les auteurs TRAVEL, un bool avec should sur summer ou vacation, une sous-agrégation category × date_histogram en calendar_interval: year pour comparer TRAVEL à STYLE & BEAUTY (9 649), une variante fixed_interval: 90d pour raisonner en trimestres.
Piège. Utiliser term au lieu de match sur headline renvoie zéro résultat sans erreur (le champ est analysé, module 5, À vous 3). Oublier size: 0 sur les agrégations rapatrie 10 documents pour rien.
Étape 3 — Le tableau de bord Kibana à quatre visualisations
Livrable. livraison-projet/kibana/veille.ndjson obtenu par l'export « avec objets associés ».
Critère de réussite. Sur un poste vierge, importer le .ndjson reconstitue la vue de données news, les quatre visualisations et le dashboard Veille — vue d'ensemble. La métrique affiche 200 853, le premier bucket des barres affiche POLITICS 32 739, la courbe couvre 2012-2018.
Piste de départ. Le module 7 détaille les clics. Rappels clés : régler la plage temporelle en absolu 2012-01-01 → 2018-06-01 avant tout ; créer chaque visualisation dans la bibliothèque avant de les ajouter au dashboard ; ajouter un contrôle Options list sur category pour permettre au client de filtrer sans quitter le tableau.
Piège. Oublier de cocher « Inclure les objets associés » à l'export ne transporte pas la vue de données news, et l'import échoue sur le poste voisin avec un message peu clair (« unable to find data view »).
Cochez « Enregistrer avec l'intervalle de temps » quand vous enregistrez le dashboard. Sinon, chaque ouverture repart sur « ~15 dernières minutes » et le client voit un tableau vide.
Étape 4 — Le graphe News chargé et vérifié
Livrable. Le graphe complet dans Neo4j 5.26 Community, avec ses trois contraintes d'unicité et son index de date.
Critère de réussite. Deux commandes suffisent après un ./lab.sh up :
./lab.sh import-news
./lab.sh cypher 11-charger-news.cypher
Puis, dans Neo4j Browser ou ./lab.sh cypher-shell :
CALL apoc.meta.stats() YIELD labels, relTypesCount
RETURN labels, relTypesCount;
labels: { "Article": 200853, "Categorie": 41, "Auteur": 23082 }
relTypesCount: { "PUBLIE_DANS": 200853, "ECRIT_PAR": <votre chiffre peut différer légèrement> }
SHOW CONSTRAINTS;
Doit lister article_id, categorie_nom, auteur_nom en UNIQUENESS, plus l'index article_date.
Piste de départ. Le script 11-charger-news.cypher du kit est idempotent grâce aux MERGE. Le relancer ne double aucun nœud (module 11).
Piège. Lancer ./lab.sh cypher 11-charger-news.cypher sans avoir fait ./lab.sh import-news produit « Couldn't load the external resource: file:///news.csv » : le CSV n'a pas encore été écrit dans neo4j/import/ (module 15).
Étape 5 — Cinq requêtes Cypher de recommandation
Livrable. livraison-projet/neo4j/recommandations.cypher.
Critère de réussite. Chaque requête tourne en moins d'une seconde sur le graphe complet. PROFILE doit montrer un plan qui démarre par un NodeUniqueIndexSeek, jamais un AllNodesScan (module 12).
Piste de départ. Cinq patrons à écrire.
Requête 1 — Autres articles récents d'un même auteur, à partir d'un article de départ :
MATCH (:Article {id: $id})-[:ECRIT_PAR]->(au:Auteur)<-[:ECRIT_PAR]-(reco:Article)
WHERE reco.id <> $id AND au.nom <> 'Reuters'
RETURN reco.titre AS titre, au.nom AS via, reco.date AS date
ORDER BY reco.date DESC LIMIT 5;
Requête 2 — Auteurs proches par catégories communes, motif à deux sauts :
MATCH (moi:Auteur {nom: $nom})<-[:ECRIT_PAR]-(:Article)-[:PUBLIE_DANS]->(c:Categorie)
<-[:PUBLIE_DANS]-(:Article)-[:ECRIT_PAR]->(voisin:Auteur)
WHERE voisin <> moi AND voisin.nom <> 'Reuters'
WITH voisin, count(DISTINCT c) AS communes
RETURN voisin.nom, communes ORDER BY communes DESC LIMIT 10;
Requête 3 — Chemin le plus court entre deux auteurs (bornes indispensables) :
MATCH chemin = shortestPath(
(a:Auteur {nom: $de})-[*..6]-(b:Auteur {nom: $vers})
)
RETURN [n IN nodes(chemin) | coalesce(n.nom, n.titre)] AS etapes,
length(chemin) AS profondeur;
Requête 4 — Top catégories d'un auteur :
MATCH (:Auteur {nom: $nom})<-[:ECRIT_PAR]-(:Article)-[:PUBLIE_DANS]->(c:Categorie)
RETURN c.nom AS categorie, count(*) AS articles
ORDER BY articles DESC LIMIT 5;
Pour Lee Moran, résultat attendu (voir brief) : COMEDY 779, WEIRD NEWS 391, ENTERTAINMENT 387, POLITICS 264, SPORTS 101.
Requête 5 — Recommandation croisée à partir d'une catégorie et d'une date :
MATCH (c:Categorie {nom: $cat})<-[:PUBLIE_DANS]-(a:Article)-[:ECRIT_PAR]->(au:Auteur)
WITH au, count(a) AS n WHERE n >= 3 AND au.nom <> 'Reuters'
MATCH (au)<-[:ECRIT_PAR]-(reco:Article)
WHERE reco.date >= date($depuis)
RETURN reco.titre AS titre, au.nom AS via, reco.date AS date
ORDER BY reco.date DESC LIMIT 10;
Piège. Oublier au.nom <> 'Reuters' inonde les résultats : l'agence signe 4 954 articles à elle seule, elle sature toute recommandation (module 13).
Étape 6 — Le script python/veille.py étendu
Livrable. livraison-projet/python/veille.py avec trois extensions par rapport à la version du kit :
- Filtre par catégorie et période (arguments de ligne de commande).
- Top auteurs pour la requête (agrégation Elasticsearch dans la même passe).
- Explication de la recommandation : chaque article recommandé mentionne l'auteur commun via lequel il a été trouvé.
Critère de réussite. La commande complète tourne en moins de cinq secondes sur le kit fraîchement démarré :
./lab.sh python veille.py "climate change" POLITICS 2016-01-01 2017-12-31
Sortie attendue (les scores et les recommandations peuvent varier légèrement selon BM25) :
Elasticsearch 9.5.3 — cluster « veille »
3 articles pour « climate change » (POLITICS, 2016-01-01 → 2017-12-31) :
[15.44] 2017-06-01 Trump Just Pulled Out Of The Paris Agreement — Kate Sheppard (POLITICS)
...
Top auteurs sur la sélection :
Kate Sheppard, Karen Feridun, Reuters
5 recommandations (auteur commun indiqué) :
2018-04-19 The E.P.A. Rolls Back A Regulation On Coal Ash — via Kate Sheppard (ENVIRONMENT)
...
Piste de départ. Repartir des fonctions rechercher() et recommander() du module 13 et étendre rechercher() :
def rechercher(es, texte, categorie=None, du=None, au=None, taille=5):
filtres = []
if categorie:
filtres.append({"term": {"category": categorie}})
if du or au:
borne = {}
if du: borne["gte"] = du
if au: borne["lte"] = au
filtres.append({"range": {"date": borne}})
rep = es.search(
index="news",
size=taille,
query={
"bool": {
"must": [{"multi_match": {"query": texte,
"fields": ["headline^3", "short_description"]}}],
"filter": filtres,
}
},
aggs={"auteurs": {"terms": {"field": "authors.raw", "size": 3}}},
source=["headline", "authors", "category", "date"],
)
top = [b["key"] for b in rep["aggregations"]["auteurs"]["buckets"]]
articles = [{"id": int(h["_id"]), "score": round(h["_score"], 2), **h["_source"]}
for h in rep["hits"]["hits"]]
return articles, top
La fonction recommander() reçoit maintenant RETURN reco.titre AS titre, au.nom AS via, c.nom AS categorie, reco.date AS date et l'affichage suit.
Piège. Coder les arguments en dur casse la démo. Passer par sys.argv (ou argparse si l'on veut du confort) et prévoir des valeurs par défaut pour les tests rapides.
Étape 7 — La démonstration en cinq minutes
Livrable. livraison-projet/README.md qui reprend le script minute par minute (section « Démonstration » plus bas).
Critère de réussite. Rejouer la démo devant un chronomètre, arriver à 4 min 45 ± 15 s. Aucune recherche d'onglet en direct.
Piste de départ. Ouvrir cinq minutes avant l'heure H quatre onglets, dans l'ordre : Kibana Dev Tools avec les requêtes préchargées, Kibana Dashboard Veille — vue d'ensemble, Neo4j Browser connecté sur bolt://localhost:7687, un terminal placé dans le kit (cd kits/42-elasticsearch-neo4j).
Piège. Lancer ./lab.sh up ou ./lab.sh import-news en direct fait perdre deux à trois minutes. Ces deux commandes se lancent en coulisse, avant l'arrivée du client.
Étape bonus « À vous » — La chronologie « climat » multi-sources
Livrer un rapport unique livraison-projet/rapport-climat.md qui prouve que les quatre outils tiennent ensemble sur un même sujet : un compteur (match headline: "climate change" → 2 834), une chronologie annuelle (date_histogram calendar_interval: year, sept buckets 2012-2018), les trois auteurs les plus actifs sur le sujet (terms filtré), une capture du dashboard filtré via KQL sur headline: "climate change", et cinq articles recommandés par la fonction recommander() du script Python.
Solution
Un mini-script Python qui compose les quatre briques :
"""Rapport climat — Elasticsearch pour trouver, Neo4j pour recommander."""
from veille import rechercher, recommander
from elasticsearch import Elasticsearch
from neo4j import GraphDatabase
import os
es = Elasticsearch(os.environ["ES_URL"],
basic_auth=(os.environ["ES_USER"], os.environ["ES_PASSWORD"]))
pilote = GraphDatabase.driver(os.environ["NEO4J_URI"],
auth=(os.environ["NEO4J_USER"], os.environ["NEO4J_PASSWORD"]))
articles, top = rechercher(es, "climate change", taille=5)
with pilote.session() as session:
top_auteurs = session.run(
"MATCH (au:Auteur)<-[:ECRIT_PAR]-(a:Article) "
"WHERE a.titre CONTAINS 'limate' "
"RETURN au.nom AS nom, count(a) AS n "
"ORDER BY n DESC LIMIT 3"
).data()
recos = recommander(session, [a["id"] for a in articles])
pilote.close()
print("# Rapport climat\n")
print("- Compteur `match headline: \"climate change\"` : 2 834 articles.")
print("- Chronologie annuelle : 7 buckets (2012 → 2018).")
print("- Top auteurs sur le sujet climat :")
for r in top_auteurs:
print(f" - {r['nom']} : {r['n']} articles")
print("- Recommandations :")
for r in recos:
print(f" - {r['date']} {r['titre']} — via {r['via']} ({r['categorie']})")
Enregistré sous python/rapport_climat.py, il s'exécute avec ./lab.sh python rapport_climat.py. Le fichier livraison-projet/rapport-climat.md reprend la sortie et y ajoute la capture du dashboard filtré, prise depuis le bouton Partager → Copier une image de Kibana.
Grille d'évaluation sur 100 points
| Critère | Points | Comment vérifier |
|---|---|---|
| Étape 1 — Mapping justifié | 10 | ./lab.sh es news/_mapping montre headline en text + sous-champs raw et suggest, category en keyword, date en date. Le README explique chaque choix en une phrase. |
| Étape 2 — 15 requêtes des personas | 20 | requetes-personas.md contient 15 blocs json exécutables ; chaque requête indique le compteur attendu et le rôle métier associé. |
| Étape 3 — Tableau de bord Kibana | 15 | Import de veille.ndjson sur un poste vierge recompose la vue de données, les 4 visualisations et le contrôle par category ; métrique = 200 853, top bucket = POLITICS 32 739. |
| Étape 4 — Graphe News vérifié | 10 | CALL apoc.meta.stats() renvoie 200 853 Article, 41 Categorie, 23 082 Auteur ; SHOW CONSTRAINTS liste les trois contraintes d'unicité et l'index de date. |
| Étape 5 — 5 requêtes Cypher de recommandation | 15 | Chaque requête tourne en < 1 s (PROFILE sans AllNodesScan) ; les 5 patrons couvrent auteur commun, catégories communes, plus court chemin, top catégories, recommandation croisée. |
| Étape 6 — Script Python étendu | 15 | ./lab.sh python veille.py "climate change" POLITICS 2016-01-01 2017-12-31 renvoie articles + top auteurs + recommandations en < 5 s, avec la mention via <auteur>. |
| Étape 7 — Démonstration en 5 minutes | 10 | Rejouée en 4 min 45 ± 15 s, 4 onglets préparés, aucune recherche d'onglet ou de commande en direct. |
| Qualité du diagnostic (bonus) | 5 | Le README.md liste les 3 pannes les plus probables observées pendant l'assemblage et pointe vers le module 15 pour chacune. |
| Total | 100 |
Barème indicatif : 90 et plus, prêt à démontrer chez un vrai client. 75 à 89, prêt en interne mais un axe reste à consolider — le plus souvent l'étape 3 (la plage temporelle) ou l'étape 5 (une requête qui scanne au lieu de seek). Sous 75, reprendre en priorité l'étape 2 (les requêtes) et l'étape 7 (la démo), qui portent l'essentiel de la valeur perçue.
Variantes
Trois pistes pour adapter le projet sans le refaire.
- Variante OpenSearch (comparaison moteur).
./lab.sh opensearch-updémarre OpenSearch 3.8.0 surhttp://localhost:9201sans authentification. Réimportez le corpus vers cet index (adapter un script Python pour pointer sur 9201) et rejouez les 15 requêtes des personas. Notez lesquelles fonctionnent à l'identique (Query DSL de base :match,bool,range), lesquelles renvoient un classement légèrement différent (la formule de scoring a divergé depuis 2021), lesquelles n'existent pas côté OpenSearch (ES|QL). Le résultat cadre le choix moteur pour Veille (module 9)../lab.sh opensearch-downarrête proprement. - Variante autre corpus. Un client de Veille arrive avec son propre CSV (un flux RSS interne, 50 000 titres, ou un export d'un CMS). Adaptez le mapping : garder les cinq champs
headline,short_description,category,date,authors; renommer si les colonnes diffèrent.importer/import_news.pyfournit le patron_bulk. Rejouer les personas A, B, C sur ce corpus prouve que les requêtes de Veille restent robustes en dehors du jeu HuffPost. - Variante graphe enrichi avec mots-clés. Étendre le modèle Neo4j pour capturer les mots-clés issus de
short_description:
LOAD CSV WITH HEADERS FROM 'file:///news.csv' AS ligne
CALL {
WITH ligne
MATCH (a:Article {id: toInteger(ligne.id)})
UNWIND [mot IN split(toLower(coalesce(ligne.short_description, '')), ' ')
WHERE size(mot) > 4
AND NOT mot IN ['about', 'their', 'which', 'there', 'other', 'would']] AS mot
MERGE (m:MotCle {nom: mot})
MERGE (a)-[:MENTIONNE]->(m)
} IN TRANSACTIONS OF 2000 ROWS;
Ce graphe enrichi débloque une sixième requête de recommandation : « articles qui partagent au moins trois mots-clés avec l'article de départ ». Elle rentre dans la grille comme requête bonus, à condition d'ajouter une contrainte MotCle.nom IS UNIQUE avant le chargement.
Erreurs fréquentes
Sept erreurs qu'un projet de fin de cours accumule presque systématiquement, avec le renvoi vers le module 15 pour l'analyse détaillée.
hits.total.value: 0sur untermduheadline. Le champ esttextavec un stemmer ;termcherche la valeur brute (module 5, À vous 3). Passer àmatch, ou ciblerheadline.rawsi l'on veut de l'exact.Couldn't load the external resource: file:///news.csv. L'importateur n'a pas été lancé, ou le fichier a été supprimé par un./lab.sh reset. Rejouer./lab.sh import-newsavant./lab.sh cypher 11-charger-news.cypher(module 15, cas n° 9).can only be executed in an implicit transactionsurCALL { } IN TRANSACTIONS. Dans Neo4j Browser, préfixer par:auto. Depuis./lab.sh cypher <fichier>, aucun préfixe n'est nécessaire — lecypher-shelldu kit exécute déjà en implicite (module 15, cas n° 10).security_exception 401après un reset incomplet. Le mot de passe Elasticsearch vit dans le volume ; modifierELASTIC_PASSWORDdans.envsans passer par./lab.sh resetne prend pas effet. Un vrairesetpuisupremet les identifiants en cohérence (module 15, cas n° 6).- Le dashboard Kibana est vide après import. La plage temporelle est restée sur « ~15 dernières minutes ». Basculer en absolu 2012-01-01 → 2018-06-01 et sauvegarder la plage avec le dashboard (module 7, piège numéro 1 ; module 15 pour la variante « données invisibles »).
Fielddata is disabled on text fields by defaultsur une agrégationtermsduheadline. Agréger toujours sur le sous-champkeyword(headline.raw,authors.raw) ; ne jamais réactiverfielddata, qui fait exploser la heap (module 6,Si ça ne marche pas).- La recommandation Cypher rame.
PROFILEla requête ; si le plan démarre parAllNodesScan, la contrainte d'unicité manque ou le paramètre ne correspond à aucune propriété indexée. Recharger11-charger-news.cypher, puis vérifier avecSHOW CONSTRAINTSque les trois contraintes sont bien là (module 15, cas n° 11).
Démonstration en cinq minutes scriptée
À rejouer devant un prospect. Quatre onglets préparés en amont, dans l'ordre : terminal cd dans le kit, Kibana Dev Tools avec les requêtes préchargées, Kibana Dashboard Veille — vue d'ensemble, Neo4j Browser connecté.
- 0:00 → 0:45 — L'index et le corpus. Fenêtre : terminal. Taper
./lab.sh statuspuis./lab.sh es news/_count. Phrase : « Nous indexons 200 853 articles de presse en trente secondes sur une machine à 6 Go de RAM. Voici l'état : trois conteneurshealthy, un index à 200 853 documents. » - 0:45 → 1:45 — La recherche par pertinence. Fenêtre : Kibana Dev Tools. Rejouer la requête
booldu persona A (climate change + POLITICS + 2016-2017 + highlight). Phrase : « Le score BM25 pousse en tête les titres où les mots apparaissent dans le titre plutôt que dans le résumé ; lehighlightsurligne ce qui a matché. La requête tient en dix lignes de JSON et elle est cacheable. » - 1:45 → 2:45 — Le tableau de bord. Fenêtre : Kibana Dashboard. Cliquer sur
POLITICSdans le contrôle Options list : les quatre panneaux se filtrent simultanément. Phrase : « Le même index alimente la barre de recherche et l'analyse. Léa livre ce dashboard aux clients : ils cliquent, ils comparent, ils exportent en CSV. » - 2:45 → 3:45 — Le graphe. Fenêtre : Neo4j Browser. Coller la requête « auteurs proches de Lee Moran par catégories communes ». Basculer l'affichage sur
Graphe. Phrase : « Elasticsearch trouve un article, Neo4j remonte à l'auteur, redescend vers d'autres articles écrits par les auteurs voisins. Trois lignes de Cypher là où il faudrait quatre jointures récursives en SQL. » - 3:45 → 4:45 — L'assemblage Python. Fenêtre : terminal. Lancer
./lab.sh python veille.py "climate change" POLITICS 2016-01-01 2017-12-31. Phrase : « L'API de Veille tape sur ce script. Cinq résultats pertinents, cinq recommandations, tout ça sous cinq secondes, avec la mentionvia <auteur>qui explique pourquoi chaque article est recommandé. » - 4:45 → 5:00 — La conclusion. Fenêtre : dashboard, filtre effacé. Phrase : « Le moteur tient dans un dossier, se déploie en trois commandes, se démonte en dix secondes avec
./lab.sh reset. On l'installe chez un prospect en une demi-heure. »
Ne pas parler pendant que la commande tourne : profiter des trois secondes de la requête Cypher pour laisser le graphe se dessiner. Le silence donne du poids au résultat.
Points à retenir
- Le projet assemble sept livrables versionnés dans
livraison-projet/; chacun se vérifie par une commande. - Trois personas fixent les requêtes attendues : POLITICS chronologique, WELLNESS avec autocomplétion, TRAVEL saisonnier.
- Le mapping justifié est le socle : tout ce qui suit dépend de
headlineentextavecraw+suggest,categoryenkeyword,dateendate. - La grille sur 100 points valorise autant la démo (10 points) que les requêtes Cypher (15 points) ; la valeur perçue par un client passe par la mise en scène.
- La variante OpenSearch reprend le module 9 en pratique et tranche le choix moteur pour Veille.
- Le script
python/veille.pyétendu prouve que les deux moteurs se combinent en trente lignes, sans se marcher dessus. - La démo en cinq minutes se répète : chronomètre en main, quatre onglets préparés, une phrase par fenêtre.
Si ça ne marche pas
- Le dashboard est vide → la plage temporelle est restée en relative ; passer en absolu
2012-01-01 → 2018-06-01et resauvegarder avec le dashboard (module 7, module 15 pour la variante). - Une requête de recommandation Cypher rame →
PROFILErévèle unAllNodesScan: recharger11-charger-news.cypheret vérifier avecSHOW CONSTRAINTS(module 15, cas n° 11). ./lab.sh python veille.pyrenvoieImportError→ le script est exécuté sur l'hôte au lieu du conteneur ; toujours passer par./lab.sh python …, jamaispython veille.pydirectement (module 13).- La démo bloque sur
./lab.sh import-newsen direct → lancer l'import cinq minutes avant l'heure H ; ne jamais réimporter pendant la démo, cela dure 28 secondes qui font retomber l'énergie de la pièce (module 15 pour le diagnostic complet).