انتقل إلى المحتوى الرئيسي

الوحدة 16 — المشروع: محرّك Veille الكامل، بحث وتوصية

حجزت Inès الغرفة الزّجاجيّة الصّغيرة في مقرّ Veille وكتبت على السّبّورة بالقلم الأزرق ثلاثة أفعال: «إيجاد، تحليل، توصية». هذه الوحدة هي العقد الذي يوقّعه Sami معها: تجميع الوحدات الخمس عشرة السّابقة في محرِّك يُجيب عن هذه الأفعال الثّلاثة على المقالات 200 853 من مجموعة News، ومعرفة كيف يعرضه على عميل مستقبَل في خمس دقائق دون مغادرة الحقيبة أبدًا، ودون أن يفتح تبويبًا من خارج المنظومة، ودون تسوية على جودة الأرقام المعروضة.

دفتر شروط في صفحة واحدة

يستهدف المنتج ثلاثة أنماط من العملاء — وكالة اتّصال سياسيّ، ومجلّة عافية، ووكالة سفر. يتّسع للحقيبة ويُعرَض على حاسوب محمول بذاكرة 6 غيغابايت مخصَّصة لـDocker. لا شيء يُثبَّت محلّيًّا؛ كلّ عمليّة تمرّ عبر ./lab.sh، وKibana Dev Tools، وNeo4j Browser، والحاوية veille-python.

ما يجب أن يعمل في النّهاية:

  • Elasticsearch 9.5.3 مع الفهرس news (200 853 وثيقة، shard واحد، صفر replica، mapping الحقيبة).
  • Kibana مع عرض بيانات news واللّوحة Veille — vue d'ensemble (أربع تصورات، تحكّم واحد بالفئة).
  • Neo4j 5.26 Community مع رسم News (200 853 :Article، و41 :Categorie، و23 082 :Auteur) وقيوده الثّلاث للفرادة.
  • سكربت python/veille.py موسَّع يجمع بين Elasticsearch وNeo4j.

ما يجب توثيقه في مجلّد livraison-projet/ (بجانب الحقيبة، يُضغَط في نهاية المشروع):

livraison-projet/
├── README.md # دليل العرض التقديميّ، 5 دقائق بالسّاعة
├── es/
│ ├── mapping-news.json # نسخة معلَّقة من الـmapping
│ └── requetes-personas.md # 15 استعلامًا معلَّقًا (5 لكلّ persona)
├── kibana/
│ └── veille.ndjson # تصدير اللّوحة + عرض البيانات
├── neo4j/
│ └── recommandations.cypher # 5 استعلامات توصية
└── python/
└── veille.py # سكربت موسَّع (تصفية فئة/فترة + شرح)

الشّخصيّات الثّلاث (personas)

تُملي كلّ شخصيّة خمسة استعلامات في المرحلة 2، ومُدخَلًا في لوحة المرحلة 3، وسطرًا في العرض التقديميّ في المرحلة 7.

  • العميل A — وكالة اتّصال سياسيّ. يتابع مقالات POLITICS (32 739 في المجموع) بين 2016 و2017، ويريد التّعرّف على المؤلِّفين الأكثر غزارةً في الحملة الرّئاسيّة وتصدير جدول زمنيّ شهريّ. المؤلِّفون المستهدفون المعروفون: Lee Moran (264 مقالًا في POLITICS)، وEd Mazza، وRon Dicker.
  • العميل B — مجلّة عافية. تغطّي WELLNESS (17 827) وHEALTHY LIVING. تطلب الإكمال التّلقائيّ على العناوين (المكوّن completion من الوحدة 8) والتّسامح مع الأخطاء («yoga» تُوجَد حتّى حين يكتب المستخدم «yaga»). قاعدة الكلمات المفتاحيّة المتوقَّعة: yoga، meditation، mindfulness، sleep، diet.
  • العميل C — وكالة سفر. تتابع TRAVEL (9 887 مقالًا) شهرًا بشهر لضبط حملاتها على الموسميّة. تريد تحديد ذرى الصّيف ونهاية العام بين 2012 و2018.

المراحل السّبع

المرحلة 1 — الفهرس news مع mapping مُبرَّر

المطلوب. livraison-projet/es/mapping-news.json منسوخًا من kits/42-elasticsearch-neo4j/elasticsearch/mappings/news.json، إضافةً إلى قسم في README.md يُبرّر كلّ حقل.

معيار النّجاح.

./lab.sh es news/_count
{"count":200853,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0}}
./lab.sh es news/_mapping

يجب أن يُظهر المخرَج headline بنوع text مع المُحلِّل titre_en وحقلَين فرعيَّين (raw بـkeyword، وsuggest بـcompletion)، وcategory بـkeyword، وdate بـdate بصيغة yyyy-MM-dd، وauthors بـtext مع حقل فرعيّ raw.

نقطة الانطلاق. يُغطّي mapping الحقيبة كلّ الاحتياجات فعلًا؛ العمل هو صياغة التّبرير. لكلّ حقل جملة: «category بـkeyword للسّماح بالتّجميع الدّقيق على القيم الـ41 والتّصفية بسياق filter (الوحدة 4)»، «headline.suggest بـcompletion للإكمال التّلقائيّ للشّخصيّة B (الوحدة 8)».

الفخّ. إعادة تعريف headline بلا الحقل الفرعيّ suggest يكسر مرحلة B بكاملها. لا يُضاف completion إلى mapping قائم: يجب _reindex كامل (الوحدة 4). تحقّق قبل البدء من أنّ الحقل الفرعيّ موجود فعلًا.

المرحلة 2 — خمسة استعلامات بحث لكلّ شخصيّة

المطلوب. livraison-projet/es/requetes-personas.md، 15 استعلامًا مصنَّفًا حسب الشّخصيّة، لكلّ منها العدّاد المتوقَّع وجملة سياق مهنيّ.

معيار النّجاح. كلّ الاستعلامات تُلصَق-تُشغَّل في Kibana Dev Tools وتُعيد أرقامًا متّسقة مع الشّخصيّات الأخرى.

نقطة الانطلاق. ثلاثة أمثلة أساسيّة يجب تنويعها.

الشّخصيّة A — بحث ذو صلة بحملة 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>"] } }
}
}

هذا النّمط bool مع must (النّصّ الحرّ، المُدرَج ضمن النّقاط) وfilter (المعايير الثّنائيّة، المخبَّأة) هو الذي يجب أن يعود في كلّ ما تفعله Veille (الوحدة 5). الاستعلامات الخمسة للشّخصيّة A: هذا البحث، وaـdate_histogram شهريّ على POLITICS 2016-2017، وterms على authors.raw مُصفَّى بـPOLITICS، وterm على authors.raw = "Lee Moran" مع تجميع فرعيّ بالفئة، وصيغة bool تستثني Russia بـmust_not.

الشّخصيّة B — إكمال تلقائيّ على headline.suggest:

GET news/_search
{
"suggest": {
"titre_suggest": {
"prefix": "yog",
"completion": {
"field": "headline.suggest",
"size": 5,
"skip_duplicates": true
}
}
}
}

التّسامح مع الأخطاء:

GET news/_search
{
"size": 5,
"query": {
"match": {
"headline": { "query": "meditaton mindfullness", "fuzziness": "AUTO" }
}
}
}

الاستعلامات الخمسة للشّخصيّة B: هذان الاثنان، وmulti_match على "healthy diet" (headline^3 وshort_description)، وterms على category مُصفًّى على WELLNESS وHEALTHY LIVING (النّتيجة: 17 827 + رقمك على HEALTHY LIVING قد يختلف قليلًا)، وtop_hits بالفئة يُعيد العنوان الأحدث (الوحدة 6).

الشّخصيّة C — الموسميّة الشّهريّة:

GET news/_search
{
"size": 0,
"query": { "term": { "category": "TRAVEL" } },
"aggs": {
"par_mois": {
"date_histogram": {
"field": "date",
"calendar_interval": "month",
"format": "yyyy-MM"
}
}
}
}

يجمع كوربوس TRAVEL 9 887 وثيقة موزَّعة من 2012-01-28 إلى 2018-05-26؛ يجب أن يُنتج الرّسم البيانيّ نحو 77 دلوًا شهريًّا، مع ذروة صيفيّة مرئيّة كلّ عام (قد يختلف رقمك قليلًا من شهر إلى آخر). الاستعلامات الأربعة الأخرى للشّخصيّة C: terms على مؤلِّفي TRAVEL، وbool مع should على summer أو vacation، وتجميع فرعيّ category × date_histogram بـcalendar_interval: year لمقارنة TRAVEL بـSTYLE & BEAUTY (9 649)، ونسخة fixed_interval: 90d للتّفكير بالفصول.

الفخّ. استعمال term بدل match على headline يُعيد صفر نتيجة بلا خطأ (الحقل محلَّل، الوحدة 5، جرّب 3). نسيان size: 0 على التّجميعات يُعيد 10 وثائق دون فائدة.

المرحلة 3 — لوحة Kibana بأربع تصوّرات

المطلوب. livraison-projet/kibana/veille.ndjson تُحصَّل عبر التّصدير «مع العناصر المرتبطة».

معيار النّجاح. على مقعد جديد، استيراد الـ.ndjson يُعيد تركيب عرض البيانات news، والتّصوّرات الأربع، واللّوحة Veille — vue d'ensemble. يعرض المقياس 200 853، ودلو الأشرطة الأوّل يعرض POLITICS 32 739، والمنحنى يغطّي 2012-2018.

نقطة الانطلاق. تُفصّل الوحدة 7 النّقرات. تذكيرات مفتاحيّة: اضبط النّطاق الزّمنيّ في absolu 2012-01-01 → 2018-06-01 قبل كلّ شيء؛ أنشئ كلّ تصوّر في المكتبة قبل إضافتها إلى اللّوحة؛ أضف تحكّم Options list على category للسّماح للعميل بالتّصفية دون مغادرة اللّوحة.

الفخّ. نسيان تفعيل «تضمين العناصر المرتبطة» عند التّصدير لا ينقل عرض البيانات news، ويفشل الاستيراد على المقعد المجاور برسالة غير واضحة («unable to find data view»).

حفظ النّطاق الزّمنيّ

فعّل «الحفظ مع الفاصل الزّمنيّ» عند حفظ اللّوحة. وإلّا، تبدأ كلّ فتحة من «~15 دقيقة أخيرة» ويرى العميل لوحة فارغة.

المرحلة 4 — رسم News محمَّلًا ومتحقَّقًا منه

المطلوب. الرّسم الكامل في Neo4j 5.26 Community، مع قيوده الثّلاث للفرادة وفهرس التّاريخ.

معيار النّجاح. يكفي أمران بعد ./lab.sh up:

./lab.sh import-news
./lab.sh cypher 11-charger-news.cypher

ثمّ، في Neo4j Browser أو ./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": <قد يختلف رقمك قليلًا> }
SHOW CONSTRAINTS;

يجب أن يُدرج article_id وcategorie_nom وauteur_nom بـUNIQUENESS، إضافةً إلى الفهرس article_date.

نقطة الانطلاق. السّكربت 11-charger-news.cypher من الحقيبة عديم التّأثير التّكراريّ بفضل MERGE. إعادة تشغيله لا تُضاعف أيّ عقدة (الوحدة 11).

الفخّ. إطلاق ./lab.sh cypher 11-charger-news.cypher دون تنفيذ ./lab.sh import-news سلفًا يُنتج «Couldn't load the external resource: file:///news.csv»: لم يُكتَب ملفّ CSV بعد في neo4j/import/ (الوحدة 15).

المرحلة 5 — خمسة استعلامات Cypher للتّوصية

المطلوب. livraison-projet/neo4j/recommandations.cypher.

معيار النّجاح. كلّ استعلام يعمل في أقلّ من ثانية على الرّسم الكامل. يجب أن يُظهر PROFILE خطّةً تبدأ بـNodeUniqueIndexSeek، لا أبدًا بـAllNodesScan (الوحدة 12).

نقطة الانطلاق. خمسة أنماط للكتابة.

الاستعلام 1 — مقالات أخرى حديثة لمؤلِّف نفسه، انطلاقًا من مقال بداية:

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;

الاستعلام 2 — مؤلِّفون قريبون بالفئات المشتركة، نمط بقفزتَين:

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;

الاستعلام 3 — أقصر مسار بين مؤلِّفَين (الحدود ضروريّة):

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;

الاستعلام 4 — أعلى فئات مؤلِّف:

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;

لـLee Moran، النّتيجة المتوقَّعة (انظر الموجز): COMEDY 779، وWEIRD NEWS 391، وENTERTAINMENT 387، وPOLITICS 264، وSPORTS 101.

الاستعلام 5 — توصية متقاطعة انطلاقًا من فئة وتاريخ:

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;

الفخّ. نسيان au.nom <> 'Reuters' يُغرق النّتائج: توقّع الوكالة 4 954 مقالًا وحدها، فتُشبع كلّ توصية (الوحدة 13).

المرحلة 6 — السّكربت python/veille.py موسَّعًا

المطلوب. livraison-projet/python/veille.py مع ثلاث امتدادات مقارنةً بنسخة الحقيبة:

  1. تصفية بالفئة والفترة (وسائط سطر الأمر).
  2. أعلى المؤلِّفين للاستعلام (تجميع Elasticsearch في المرور نفسه).
  3. شرح التّوصية: كلّ مقال موصًى به يذكر المؤلِّف المشترك الذي عثر عليه عن طريقه.

معيار النّجاح. الأمر الكامل يعمل في أقلّ من خمس ثوانٍ على الحقيبة المشغَّلة حديثًا:

./lab.sh python veille.py "climate change" POLITICS 2016-01-01 2017-12-31

المخرَج المنتظَر (قد تتفاوت الدّرجات والتّوصيات قليلًا حسب 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)
...

نقطة الانطلاق. انطلق من الدّالّتَين rechercher() وrecommander() من الوحدة 13، ووسِّع 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

الدّالّة recommander() تستقبل الآن RETURN reco.titre AS titre, au.nom AS via, c.nom AS categorie, reco.date AS date والعرض يتبع.

الفخّ. ترميز الوسائط صلبًا يكسر العرض التقديميّ. مُرّ عبر sys.argv (أو argparse إن أردت راحة) وأعدّ قيمًا افتراضيّة للاختبارات السّريعة.

المرحلة 7 — العرض التقديميّ في خمس دقائق

المطلوب. livraison-projet/README.md يستعيد السّيناريو دقيقةً بدقيقة (قسم «العرض التقديميّ» أدناه).

معيار النّجاح. أعِد تشغيل العرض التقديميّ أمام مؤقّت، وصِل إلى 4 دقائق 45 ± 15 ثانية. لا بحث عن علامة تبويب في المباشر.

نقطة الانطلاق. افتح خمس دقائق قبل السّاعة H أربع علامات تبويب، بالتّرتيب: Kibana Dev Tools مع استعلامات محمَّلة سلفًا، ولوحة Kibana Veille — vue d'ensemble، وNeo4j Browser متّصلًا على bolt://localhost:7687، وطرفيّة موضوعة داخل الحقيبة (cd kits/42-elasticsearch-neo4j).

الفخّ. إطلاق ./lab.sh up أو ./lab.sh import-news مباشرةً يُضيّع دقيقتَين إلى ثلاث. يُطلَق هذان الأمران في الكواليس، قبل وصول العميل.

مرحلة إضافيّة «جرّب» — الجدول الزّمنيّ «مناخ» متعدّد المصادر

سلّم تقريرًا وحيدًا livraison-projet/rapport-climat.md يُثبت أنّ الأدوات الأربع تصمد معًا على موضوع واحد: عدّاد (match headline: "climate change" ← 2 834)، وجدول زمنيّ سنويّ (date_histogram calendar_interval: year، سبعة دلاء 2012-2018)، والمؤلِّفون الثّلاثة الأنشط على الموضوع (terms مُصفًّى)، ولقطة للّوحة المُصفّاة عبر KQL على headline: "climate change"، وخمسة مقالات موصًى بها من دالّة recommander() في سكربت بايثون.

الحلّ

سكربت بايثون صغير يُركّب اللبنات الأربع:

"""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']})")

يُحفَظ باسم python/rapport_climat.py، ويُنفَّذ بـ./lab.sh python rapport_climat.py. يستعيد الملفّ livraison-projet/rapport-climat.md المخرَج ويُضيف إليه لقطة اللّوحة المُصفَّاة، الملتقطة من زرّ مشاركة → نسخ صورة في Kibana.

شبكة تقييم على 100 نقطة

المعيارالنّقاطكيف نتحقّق
المرحلة 1 — Mapping مُبرَّر10./lab.sh es news/_mapping يُظهر headline بـtext + حقلَين فرعيَّين raw وsuggest، وcategory بـkeyword، وdate بـdate. يُفسِّر README كلّ اختيار بجملة.
المرحلة 2 — 15 استعلامًا للشّخصيّات20يحوي requetes-personas.md 15 كتلة json قابلة للتّنفيذ؛ كلّ استعلام يذكر العدّاد المتوقَّع والدّور المهنيّ المرتبط.
المرحلة 3 — لوحة Kibana15استيراد veille.ndjson على مقعد جديد يُعيد تركيب عرض البيانات، والتّصوّرات الأربع، والتّحكّم بـcategory؛ المقياس = 200 853، أعلى دلو = POLITICS 32 739.
المرحلة 4 — رسم News متحقَّق منه10CALL apoc.meta.stats() يُعيد 200 853 Article، و41 Categorie، و23 082 Auteur؛ SHOW CONSTRAINTS يُدرج القيود الثّلاث للفرادة وفهرس التّاريخ.
المرحلة 5 — 5 استعلامات Cypher للتّوصية15كلّ استعلام يعمل في < 1 ثانية (PROFILE بلا AllNodesScan)؛ الأنماط الخمسة تُغطّي المؤلِّف المشترك، والفئات المشتركة، وأقصر مسار، وأعلى فئات، والتّوصية المتقاطعة.
المرحلة 6 — سكربت بايثون موسَّع15./lab.sh python veille.py "climate change" POLITICS 2016-01-01 2017-12-31 يُعيد المقالات + أعلى المؤلِّفين + التّوصيات في < 5 ثوانٍ، مع الإشارة via <auteur>.
المرحلة 7 — العرض التقديميّ في 5 دقائق10مُعاد التّشغيل في 4 دقائق 45 ± 15 ثانية، 4 علامات تبويب مُعدَّة، لا بحث عن علامة تبويب أو أمر في المباشر.
جودة التّشخيص (إضافيّة)5يُدرج README.md الأعطال الثّلاثة الأرجح ملاحظتها أثناء التّجميع ويُشير إلى الوحدة 15 لكلّ منها.
المجموع100

سلّم إرشاديّ: 90 وما فوق، جاهز للعرض عند عميل حقيقيّ. 75 إلى 89، جاهز داخليًّا لكنّ محورًا يبقى للتّرسيخ — في الغالب المرحلة 3 (النّطاق الزّمنيّ) أو المرحلة 5 (استعلام يمسح بدل seek). تحت 75، أعِد الأولويّة إلى المرحلة 2 (الاستعلامات) والمرحلة 7 (العرض التقديميّ)، اللّتَين تحملان جوهر القيمة المُدرَكة.

متغيّرات

ثلاث مسارات لتكييف المشروع دون إعادة صنعه.

  • متغيّر OpenSearch (مقارنة محرِّكات). ./lab.sh opensearch-up يُطلق OpenSearch 3.8.0 على http://localhost:9201 بلا مصادقة. أعِد استيراد المجموعة إلى هذا الفهرس (كيّف سكربت بايثون ليُشير إلى 9201) وأعِد تشغيل 15 استعلام الشّخصيّات. لاحظ أيّها يعمل كما هو (Query DSL الأساسيّ: match وbool وrange)، وأيّها يُعيد ترتيبًا مختلفًا قليلًا (تباعدت صيغة التّرتيب منذ 2021)، وأيّها لا يوجد في جانب OpenSearch (ES|QL). النّتيجة تُؤطّر اختيار المحرِّك لـVeille (الوحدة 9). ./lab.sh opensearch-down يوقّف بنظافة.
  • متغيّر مجموعة أخرى. يصل عميل من Veille بملفّ CSV خاصّ به (تدفّق RSS داخليّ، أو 50 000 عنوان، أو تصدير من CMS). كيّف الـmapping: احتفظ بالحقول الخمسة headline وshort_description وcategory وdate وauthors؛ أعِد التّسمية إن اختلفت الأعمدة. يوفّر importer/import_news.py نمط _bulk. إعادة تشغيل الشّخصيّات A وB وC على هذه المجموعة يُثبت أنّ استعلامات Veille تبقى متينة خارج مجموعة HuffPost.
  • متغيّر رسم مُثرًى بالكلمات المفتاحيّة. وسِّع نموذج Neo4j لالتقاط الكلمات المفتاحيّة من 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;

يُحرِّر هذا الرّسم المُثرى استعلام توصية سادسًا: «مقالات تتقاسم ثلاث كلمات مفتاحيّة على الأقلّ مع مقال البداية». تدخل شبكة التّقييم كاستعلام إضافيّ، بشرط إضافة قيد MotCle.nom IS UNIQUE قبل التّحميل.

أخطاء شائعة

سبعة أخطاء يُراكمها مشروع نهاية دورة بشكل شبه منهجيّ، مع الإحالة إلى الوحدة 15 للتّحليل المفصَّل.

  • hits.total.value: 0 على term من headline. الحقل text مع stemmer؛ term يبحث عن القيمة الخام (الوحدة 5، جرّب 3). انتقل إلى match، أو استهدف headline.raw إن أردت الدّقّة.
  • Couldn't load the external resource: file:///news.csv. لم يُطلَق المستورِد، أو حُذِف الملفّ بـ./lab.sh reset. أعِد ./lab.sh import-news قبل ./lab.sh cypher 11-charger-news.cypher (الوحدة 15، الحالة رقم 9).
  • can only be executed in an implicit transaction على CALL { } IN TRANSACTIONS. في Neo4j Browser، سبّق بـ:auto. من ./lab.sh cypher <fichier>، لا حاجة لأيّ بادئة — يُنفِّذ cypher-shell في الحقيبة ضمنيًّا سلفًا (الوحدة 15، الحالة رقم 10).
  • security_exception 401 بعد إعادة تعيين غير مكتملة. كلمة مرور Elasticsearch تعيش في المجلَّد؛ تعديل ELASTIC_PASSWORD في .env بلا المرور بـ./lab.sh reset لا يُفعِّلها. reset حقيقيّ ثمّ up يُعيدان توافق المعرِّفات (الوحدة 15، الحالة رقم 6).
  • لوحة Kibana فارغة بعد الاستيراد. بقي النّطاق الزّمنيّ على «~15 دقيقة أخيرة». انتقل إلى مطلق 2012-01-01 → 2018-06-01 واحفظ النّطاق مع اللّوحة (الوحدة 7، الفخّ رقم 1؛ الوحدة 15 لصيغة «بيانات غير مرئيّة»).
  • Fielddata is disabled on text fields by default على تجميع terms من headline. جمِّع دائمًا على الحقل الفرعيّ keyword (headline.raw، authors.raw)؛ لا تُعِد تفعيل fielddata أبدًا، فيُفجِّر الـheap (الوحدة 6، Si ça ne marche pas).
  • توصية Cypher بطيئة. PROFILE الاستعلام؛ إن بدأت الخطّة بـAllNodesScan، فقيد الفرادة ناقص أو المُعامِل لا يقابل أيّ خاصيّة مُفهرَسة. أعِد تحميل 11-charger-news.cypher، ثمّ تحقّق بـSHOW CONSTRAINTS من وجود القيود الثّلاث فعلًا (الوحدة 15، الحالة رقم 11).

عرض تقديميّ في خمس دقائق مكتوب

يُعاد تشغيله أمام عميل مستقبَل. أربع علامات تبويب مُعدَّة مسبقًا، بالتّرتيب: طرفيّة cd داخل الحقيبة، وKibana Dev Tools مع استعلامات محمَّلة سلفًا، ولوحة Kibana Veille — vue d'ensemble، وNeo4j Browser متّصلًا.

  • 0:00 → 0:45 — الفهرس والمجموعة. النّافذة: طرفيّة. اكتب ./lab.sh status ثمّ ./lab.sh es news/_count. الجملة: «نُفهرس 200 853 مقالًا صحفيًّا في ثلاثين ثانية على جهاز بذاكرة 6 غيغابايت. هذه هي الحالة: ثلاث حاويات healthy، وفهرس بـ200 853 وثيقة.»
  • 0:45 → 1:45 — البحث بالصّلة. النّافذة: Kibana Dev Tools. أعِد تشغيل استعلام bool للشّخصيّة A (climate change + POLITICS + 2016-2017 + highlight). الجملة: «تدفع درجة BM25 إلى الصّدارة العناوين التي تظهر فيها الكلمات في العنوان بدل الملخّص؛ يبرز highlight ما تطابق. الاستعلام في عشرة أسطر JSON وهو قابل للتّخبئة.»
  • 1:45 → 2:45 — لوحة القيادة. النّافذة: لوحة Kibana. انقر على POLITICS في تحكّم Options list: تُصفَّى اللّوحات الأربع في آن معًا. الجملة: «يُغذّي الفهرس نفسه شريط البحث والتّحليل. تُسلِّم Léa هذه اللّوحة للعملاء: ينقرون، يقارنون، يُصدِّرون بـCSV.»
  • 2:45 → 3:45 — الرّسم البيانيّ. النّافذة: Neo4j Browser. الصق الاستعلام «مؤلِّفون قريبون من Lee Moran بفئات مشتركة». بدّل العرض إلى Graphe. الجملة: «يعثر Elasticsearch على مقال، ويصعد Neo4j إلى المؤلِّف، ثمّ ينزل نحو مقالات أخرى كتبها المؤلِّفون المجاورون. ثلاثة أسطر من Cypher في مكان أربع jointures عوديّة في SQL.»
  • 3:45 → 4:45 — التّجميع بايثون. النّافذة: طرفيّة. أطلق ./lab.sh python veille.py "climate change" POLITICS 2016-01-01 2017-12-31. الجملة: «تستهدف واجهة Veille هذا السّكربت. خمس نتائج ذات صلة، وخمس توصيات، كلّ ذلك تحت خمس ثوانٍ، مع الإشارة via <auteur> التي تُفسِّر لماذا أُوصي بكلّ مقال.»
  • 4:45 → 5:00 — الخاتمة. النّافذة: اللّوحة، بلا تصفية. الجملة: «يقيم المحرِّك في مجلّد واحد، يُنشَر في ثلاثة أوامر، ويُفكَّك في عشر ثوانٍ بـ./lab.sh reset. نُثبِّته عند عميل مستقبَل في نصف ساعة.»
الإيقاع والتّنفّس

لا تتكلّم بينما يعمل الأمر: استفد من ثوان الاستعلام Cypher الثّلاث لتترك الرّسم يُرسم نفسه. الصّمت يُعطي وزنًا للنّتيجة.

النقاط الأساسيّة

  • يجمع المشروع سبع مخرجات موثَّقة في livraison-projet/؛ كلّ منها يُتحقَّق منه بأمر.
  • ثلاث شخصيّات تُحدّد الاستعلامات المتوقَّعة: POLITICS زمنيًّا، وWELLNESS بإكمال تلقائيّ، وTRAVEL موسميًّا.
  • الـmapping المُبرَّر هو الأساس: كلّ ما يتبعه يعتمد على headline بـtext مع raw + suggest، وcategory بـkeyword، وdate بـdate.
  • تُثمّن شبكة الـ100 نقطة العرضَ التقديميّ (10 نقاط) بقدر استعلامات Cypher (15 نقطة)؛ القيمة المُدرَكة عند العميل تمرّ عبر الإخراج.
  • متغيّر OpenSearch يُعيد الوحدة 9 عمليًّا ويحسم اختيار المحرِّك لـVeille.
  • سكربت python/veille.py موسَّعًا يُثبت أنّ المحرِّكَين يقترنان في ثلاثين سطرًا، دون أن يدوس أحدهما الآخر.
  • العرض التقديميّ في خمس دقائق يُكرَّر: مؤقّت باليد، وأربع علامات تبويب مُعدَّة، وجملة لكلّ نافذة.
  • شبكة التّقييم على 100 نقطة تُرسِّخ التّقدّم: بدل جملة «أظنّ أنّني جاهز» الغامضة، تُنتج جوابًا موضوعيًّا على شكل رقم يُوقَّع أسفله من العميل الدّاخليّ قبل عرض المشروع خارجيًّا لأيّ مترقّب من عملاء Veille.
  • كلّ فخّ من الأفخاخ السّبعة الشّائعة يعود إلى الوحدة 15 بحالة محدَّدة، فيبقى التّشخيص أثناء التّجميع طريقًا مرسومًا واضحًا مقروءًا لا استكشافًا عشوائيًّا يُضيع الوقت والطّاقة.

استكشاف الأخطاء

  • اللّوحة فارغة ← بقي النّطاق الزّمنيّ نسبيًّا؛ انتقل إلى مطلق 2012-01-01 → 2018-06-01 وأعِد الحفظ مع اللّوحة (الوحدة 7، الوحدة 15 لصيغة أخرى).
  • استعلام توصية Cypher بطيءPROFILE يكشف AllNodesScan: أعِد تحميل 11-charger-news.cypher وتحقّق بـSHOW CONSTRAINTS (الوحدة 15، الحالة رقم 11).
  • ./lab.sh python veille.py يُعيد ImportError ← يُنفَّذ السّكربت على المضيف بدل الحاوية؛ مُرّ دائمًا عبر ./lab.sh python …، لا python veille.py مباشرةً (الوحدة 13).
  • يتوقّف العرض التقديميّ عند ./lab.sh import-news مباشرةً ← أطلق الاستيراد خمس دقائق قبل السّاعة H؛ لا تُعِد الاستيراد أبدًا أثناء العرض، فهو يستغرق 28 ثانية تُسقط طاقة القاعة (الوحدة 15 للتّشخيص الكامل).

للاستزادة