moteur de recherche : trigram+substring, navbar, page resultats
This commit is contained in:
@@ -1,91 +1,47 @@
|
||||
*Démêlons le vrai du faux sur une affirmation qui circule de plus en plus.*
|
||||
*Démêlons le vrai du faux sur une affirmation qui revient régulièrement dans les débats autour de la fiscalité des véhicules à batterie.*
|
||||
|
||||
Depuis quelques mois, à mesure que se multiplient les débats sur la fiscalité des véhicules électriques, on voit fleurir une affirmation étonnante : *« Le compteur Linky a été conçu pour reconnaître la connexion d’une voiture à batterie. »*
|
||||
Cette phrase, impressionnante mais trompeuse, laisse entendre que le compteur intelligent d’Enedis serait capable d’identifier précisément la recharge d’un véhicule électrique.
|
||||
**C’est faux.**
|
||||
Depuis plusieurs mois, à mesure que s'intensifient les discussions sur une éventuelle taxe kilométrique visant les voitures électriques, une affirmation refait surface avec insistance sur les réseaux sociaux et dans certains articles : *« Le compteur Linky a été conçu pour reconnaître la connexion d'une voiture à batterie. »* La formule est efficace, presque inquiétante, et elle nourrit l'idée d'un État qui aurait anticipé depuis longtemps la surveillance des automobilistes électriques via leur compteur domestique.
|
||||
|
||||
Dans cet article, on vous explique de manière simple pourquoi cette affirmation ne tient pas la route.
|
||||
Le problème, c'est que cette affirmation est tout simplement fausse. Ou plus exactement : elle confond grossièrement ce que Linky mesure réellement et ce qu'on lui prête comme capacités. Pour comprendre pourquoi, il faut revenir aux fondamentaux de ce qu'est un compteur électrique, même "intelligent".
|
||||
|
||||
---
|
||||
## Ce que Linky mesure réellement
|
||||
|
||||
## **Ce que sait faire le compteur Linky**
|
||||
Un compteur Linky, c'est avant tout un instrument de mesure. Il enregistre la consommation électrique globale du logement, en temps quasi réel, avec une précision bien supérieure à celle des anciens compteurs électromécaniques. Concrètement, il relève la puissance instantanée appelée par l'ensemble de l'installation, l'intensité du courant qui circule sur les phases, ainsi que quelques paramètres plus techniques comme les harmoniques — des perturbations du signal qui renseignent sur la qualité du courant.
|
||||
|
||||
Le compteur Linky mesure :
|
||||
Tout cela est agrégé. Linky voit *un total*, pas une ventilation appareil par appareil. Quand votre four à 3 kW se met en route, le compteur enregistre une montée de 3 kW. Quand une wallbox commence à charger une voiture à 3,7 kW, il enregistre une montée de 3,7 kW. Du point de vue de Linky, ces deux événements sont parfaitement indiscernables. Il n'a aucun moyen de savoir si l'électricité part vers une plaque de cuisson, un chauffe-eau, un radiateur ou une Tesla branchée au garage.
|
||||
|
||||
* la consommation électrique totale instantanée du logement,
|
||||
* l’intensité du courant,
|
||||
* la puissance appelée,
|
||||
* et certains paramètres techniques comme les harmoniques.
|
||||
C'est une limitation fondamentale, pas un oubli de conception : un compteur de tableau électrique se situe en amont de tout, sur l'arrivée générale. Il voit ce qui entre dans la maison, point final.
|
||||
|
||||
Autrement dit, il “voit” qu’un appareil consomme de l’électricité et combien il en consomme.
|
||||
## Ce que Linky ne sait pas faire — et ne saura jamais faire en l'état
|
||||
|
||||
**Mais il ne sait pas quel appareil consomme.**
|
||||
Pour le Linky, un four de 3 kW, une borne de recharge de 3,7 kW, ou un chauffe-eau de 3 kW… c’est la même chose : une charge de quelques kilowatts sur le réseau.
|
||||
Contrairement à ce que certains articles laissent entendre, Linky n'a aucune capacité à identifier la nature des appareils qui se branchent. Il ne reconnaît pas une voiture électrique, ne lit pas les protocoles de communication entre une borne et un véhicule (Type 2, CCS, CHAdeMO), ne dialogue ni avec le chargeur embarqué ni avec le BMS — le système de gestion de batterie qui pilote la charge côté voiture. Aucune de ces fonctions ne figure dans ses spécifications techniques, qui sont publiques et consultables.
|
||||
|
||||
---
|
||||
Linky n'est ni une prise connectée capable de profiler ce qui s'y branche, ni un analyseur de charge avancé, ni un dispositif de reconnaissance d'appareils par signature. C'est un compteur de facturation, conçu pour relever votre consommation à distance et permettre à votre fournisseur d'affiner les offres tarifaires (heures creuses dynamiques, par exemple). Tout le reste relève du fantasme ou de la confusion.
|
||||
|
||||
## **Ce que Linky ne sait PAS faire**
|
||||
## D'où vient cette idée alors ?
|
||||
|
||||
Contrairement à ce que certains articles laissent croire, Linky **ne sait pas :**
|
||||
La rumeur n'est pas née de nulle part. Elle s'enracine dans deux éléments réels, mais largement mal interprétés.
|
||||
|
||||
* reconnaître une voiture électrique,
|
||||
* identifier un type d’appareil,
|
||||
* lire les protocoles de recharge (type 2, CCS, etc.),
|
||||
* détecter une batterie ou un BMS,
|
||||
* extraire des données spécifiques à un véhicule.
|
||||
**Le premier, c'est l'existence de la TIC, la « télé-information client ».** Il s'agit d'une interface physique présente sur le compteur Linky, qui diffuse en continu certaines données : puissance souscrite, puissance instantanée appelée, index de consommation, période tarifaire en cours. Cette interface est *sortante* : elle envoie des informations vers l'extérieur, vers des appareils domestiques compatibles, mais elle ne reçoit rien en retour.
|
||||
|
||||
Le Linky n’est ni une prise connectée, ni un analyseur de charge avancé, ni un dispositif de reconnaissance d’appareils.
|
||||
Certaines wallbox modernes sont capables de se brancher sur cette TIC pour lire en direct la puissance déjà consommée dans le logement. Elles ajustent alors automatiquement la puissance de charge de la voiture pour ne pas faire disjoncter l'installation : si quelqu'un allume le four pendant que la voiture charge, la borne réduit son appel de courant. C'est une fonction très utile, mais elle fonctionne dans un seul sens. **La wallbox lit Linky. Linky ne lit pas la wallbox, et encore moins la voiture.** Beaucoup de gens, en entendant parler de wallbox "communiquant avec Linky", imaginent un dialogue bidirectionnel qui n'existe pas.
|
||||
|
||||
**Aucune fonction de ce genre n’existe dans ses spécifications.**
|
||||
**Le second élément, c'est l'arrivée du débat sur une taxe kilométrique.** Avec la baisse des recettes de TICPE liée à l'électrification du parc automobile, plusieurs think tanks et rapports parlementaires ont effectivement évoqué l'idée de taxer les kilomètres parcourus en VE, et certains ont mentionné Linky parmi les outils techniques *envisageables*. De cette spéculation prospective, une partie du public a tiré la conclusion que le compteur était déjà équipé pour le faire. Or il y a un gouffre entre « on pourrait peut-être un jour utiliser Linky comme brique d'un dispositif fiscal » et « Linky a été conçu pour ça ». Le premier est une hypothèse politique discutable ; le second est un raccourci qui ne correspond à aucune réalité technique.
|
||||
|
||||
---
|
||||
## Et techniquement, ce serait possible un jour ?
|
||||
|
||||
## **D’où vient la confusion ?**
|
||||
C'est la question intéressante, et la réponse mérite plus de nuance qu'un simple oui ou non.
|
||||
|
||||
Elle vient principalement de deux points :
|
||||
Il existe effectivement un champ de recherche actif, baptisé **NILM** pour *Non-Intrusive Load Monitoring*. L'idée : analyser la courbe de consommation globale d'un logement pour en déduire, par traitement du signal et apprentissage automatique, quels appareils s'y trouvent et quand ils fonctionnent. Chaque appareil aurait, en théorie, une "signature électrique" reconnaissable — un profil d'appel de courant au démarrage, un comportement en régime, etc.
|
||||
|
||||
### 1. Les bornes compatibles “TIC Linky”
|
||||
En pratique, l'exercice est très difficile, et il l'est particulièrement pour la recharge d'un véhicule électrique. Une borne en charge se comporte comme une charge quasi constante de plusieurs kilowatts pendant plusieurs heures. C'est une signature… qui ressemble énormément à celle d'un chauffe-eau, d'un convecteur, d'un sèche-linge en cycle long ou d'un radiateur à inertie. Sans cadence de fonctionnement caractéristique, sans pics distinctifs, sans cycles courts, il n'y a rien de très spécifique à exploiter. Identifier de manière fiable qu'on a affaire à une voiture *et pas* à un autre appareil de puissance similaire reste un problème ouvert dans la littérature scientifique.
|
||||
|
||||
Certaines wallbox sont capables de lire la **télé-information client (TIC)** du compteur Linky.
|
||||
Elles savent alors adapter la puissance de charge selon la puissance disponible dans le logement.
|
||||
Mais surtout, et c'est le point essentiel : **Linky n'embarque aucun de ces algorithmes**. Il transmet des données de comptage agrégées à Enedis, qui les utilise pour la facturation et la gestion du réseau. Enedis n'a ni la mission, ni le droit légal, ni l'infrastructure pour analyser appareil par appareil les usages domestiques de ses clients. Le cadre réglementaire français, notamment via la CNIL, encadre strictement ce qui peut être fait des données de consommation, et toute exploitation plus fine — même la courbe de charge à pas fin — nécessite le consentement explicite de l'abonné.
|
||||
|
||||
Cela donne l’impression que la borne, et donc la voiture, “communiquent avec Linky”.
|
||||
## Ce qu'il faut retenir
|
||||
|
||||
En réalité :
|
||||
Le compteur Linky mesure votre consommation globale, c'est vrai. Il permet à certaines bornes de recharge de moduler intelligemment leur puissance via la TIC, c'est vrai aussi. Mais il ne reconnaît pas, n'identifie pas et ne distingue pas une voiture électrique des autres appareils du logement. Cette capacité n'existe ni dans son matériel, ni dans son logiciel, ni dans les données qu'il transmet à Enedis.
|
||||
|
||||
* la borne lit Linky,
|
||||
* mais Linky **ne lit pas** la voiture.
|
||||
L'affirmation selon laquelle « Linky a été conçu pour reconnaître la connexion d'une voiture à batterie » mélange donc trois choses très différentes : des capacités réelles mais limitées (mesure de puissance, interface TIC sortante), des usages techniques existants côté wallbox, et des hypothèses politiques sur de futurs dispositifs fiscaux. De cette confusion naît une rumeur frappante, mais infondée.
|
||||
|
||||
### 2. Les discussions sur une éventuelle taxe au kilomètre
|
||||
|
||||
Certains articles spéculent sur l’idée d’utiliser Linky pour mesurer la recharge des voitures électriques.
|
||||
Cela alimente un imaginaire où le compteur “reconnaîtrait” automatiquement un véhicule.
|
||||
|
||||
Mais pour l’instant, il ne s’agit que d’hypothèses, souvent mal comprises ou mal expliquées.
|
||||
|
||||
---
|
||||
|
||||
## **Peut-on techniquement reconnaître une voiture à partir de sa consommation ?**
|
||||
|
||||
En théorie, il existe des algorithmes capables d’identifier certains appareils à partir de leur **signature électrique** (c’est le domaine du “NILM”, Non-Intrusive Load Monitoring).
|
||||
|
||||
Mais :
|
||||
|
||||
* le Linky n’embarque **aucun** de ces algorithmes,
|
||||
* Enedis n’a **aucun droit** ni **aucune capacité technique** pour analyser votre usage appareil par appareil,
|
||||
* et les signatures des bornes de recharge ne sont **pas uniques** : elles ressemblent à n’importe quelle charge résistive ou quasi constante.
|
||||
|
||||
Donc **même avec des outils avancés, on ne pourrait pas identifier un VE de manière fiable à partir des données disponibles.**
|
||||
|
||||
---
|
||||
|
||||
## **Conclusion**
|
||||
|
||||
Le compteur Linky :
|
||||
|
||||
* mesure votre consommation globale - vrai
|
||||
* permet à des bornes de moduler la recharge - vrai
|
||||
* reconnaît ou identifie les voitures électriques - faux
|
||||
|
||||
L’affirmation « Linky a été conçu pour reconnaître la connexion d’une voiture à batterie » est donc **fausse**.
|
||||
Elle résulte d’une confusion entre des capacités réelles (mesure de puissance, interface TIC) et des hypothèses fantaisistes.
|
||||
Le débat sur la fiscalité des véhicules électriques est légitime et important. Il mérite mieux que des affirmations qui n'ont pas de base technique.
|
||||
@@ -1,13 +1,25 @@
|
||||
{
|
||||
"uuid": "70b5f213-db76-4072-afb6-f876fe67aaf8",
|
||||
"slug": "non-le-compteur-linky-ne-reconnait-pas-les-voitures-electriques",
|
||||
"title": "Non, le compteur Linky ne reconnaît pas les voitures électriques",
|
||||
"title": "Non, le compteur Linky n'est pas conçu pour repérer votre voiture électrique",
|
||||
"author": "cedric@abonnel.fr",
|
||||
"published": true,
|
||||
"published_at": "2025-12-06 06:36:25",
|
||||
"published_at": "2025-12-06 06:36",
|
||||
"created_at": "2025-12-06 06:36:25",
|
||||
"updated_at": "2025-12-06 06:36:25",
|
||||
"revisions": [],
|
||||
"cover": "cover.jpg",
|
||||
"updated_at": "2026-05-12 01:27:37",
|
||||
"revisions": [
|
||||
{
|
||||
"n": 1,
|
||||
"date": "2026-05-12 01:27:37",
|
||||
"comment": "",
|
||||
"title": "Non, le compteur Linky n'est pas conçu pour repérer votre voiture électrique"
|
||||
}
|
||||
],
|
||||
"cover": "",
|
||||
"files_meta": [],
|
||||
"external_links": [],
|
||||
"seo_title": "",
|
||||
"seo_description": "",
|
||||
"og_image": "",
|
||||
"category": "actualité"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
*Démêlons le vrai du faux sur une affirmation qui revient régulièrement dans les débats autour de la fiscalité des véhicules à batterie.*
|
||||
|
||||
Depuis plusieurs mois, à mesure que s'intensifient les discussions sur une éventuelle taxe kilométrique visant les voitures électriques, une affirmation refait surface avec insistance sur les réseaux sociaux et dans certains articles : *« Le compteur Linky a été conçu pour reconnaître la connexion d'une voiture à batterie. »* La formule est efficace, presque inquiétante, et elle nourrit l'idée d'un État qui aurait anticipé depuis longtemps la surveillance des automobilistes électriques via leur compteur domestique.
|
||||
|
||||
Le problème, c'est que cette affirmation est tout simplement fausse. Ou plus exactement : elle confond grossièrement ce que Linky mesure réellement et ce qu'on lui prête comme capacités. Pour comprendre pourquoi, il faut revenir aux fondamentaux de ce qu'est un compteur électrique, même "intelligent".
|
||||
|
||||
## Ce que Linky mesure réellement
|
||||
|
||||
Un compteur Linky, c'est avant tout un instrument de mesure. Il enregistre la consommation électrique globale du logement, en temps quasi réel, avec une précision bien supérieure à celle des anciens compteurs électromécaniques. Concrètement, il relève la puissance instantanée appelée par l'ensemble de l'installation, l'intensité du courant qui circule sur les phases, ainsi que quelques paramètres plus techniques comme les harmoniques — des perturbations du signal qui renseignent sur la qualité du courant.
|
||||
|
||||
Tout cela est agrégé. Linky voit *un total*, pas une ventilation appareil par appareil. Quand votre four à 3 kW se met en route, le compteur enregistre une montée de 3 kW. Quand une wallbox commence à charger une voiture à 3,7 kW, il enregistre une montée de 3,7 kW. Du point de vue de Linky, ces deux événements sont parfaitement indiscernables. Il n'a aucun moyen de savoir si l'électricité part vers une plaque de cuisson, un chauffe-eau, un radiateur ou une Tesla branchée au garage.
|
||||
|
||||
C'est une limitation fondamentale, pas un oubli de conception : un compteur de tableau électrique se situe en amont de tout, sur l'arrivée générale. Il voit ce qui entre dans la maison, point final.
|
||||
|
||||
## Ce que Linky ne sait pas faire — et ne saura jamais faire en l'état
|
||||
|
||||
Contrairement à ce que certains articles laissent entendre, Linky n'a aucune capacité à identifier la nature des appareils qui se branchent. Il ne reconnaît pas une voiture électrique, ne lit pas les protocoles de communication entre une borne et un véhicule (Type 2, CCS, CHAdeMO), ne dialogue ni avec le chargeur embarqué ni avec le BMS — le système de gestion de batterie qui pilote la charge côté voiture. Aucune de ces fonctions ne figure dans ses spécifications techniques, qui sont publiques et consultables.
|
||||
|
||||
Linky n'est ni une prise connectée capable de profiler ce qui s'y branche, ni un analyseur de charge avancé, ni un dispositif de reconnaissance d'appareils par signature. C'est un compteur de facturation, conçu pour relever votre consommation à distance et permettre à votre fournisseur d'affiner les offres tarifaires (heures creuses dynamiques, par exemple). Tout le reste relève du fantasme ou de la confusion.
|
||||
|
||||
## D'où vient cette idée alors ?
|
||||
|
||||
La rumeur n'est pas née de nulle part. Elle s'enracine dans deux éléments réels, mais largement mal interprétés.
|
||||
|
||||
**Le premier, c'est l'existence de la TIC, la « télé-information client ».** Il s'agit d'une interface physique présente sur le compteur Linky, qui diffuse en continu certaines données : puissance souscrite, puissance instantanée appelée, index de consommation, période tarifaire en cours. Cette interface est *sortante* : elle envoie des informations vers l'extérieur, vers des appareils domestiques compatibles, mais elle ne reçoit rien en retour.
|
||||
|
||||
Certaines wallbox modernes sont capables de se brancher sur cette TIC pour lire en direct la puissance déjà consommée dans le logement. Elles ajustent alors automatiquement la puissance de charge de la voiture pour ne pas faire disjoncter l'installation : si quelqu'un allume le four pendant que la voiture charge, la borne réduit son appel de courant. C'est une fonction très utile, mais elle fonctionne dans un seul sens. **La wallbox lit Linky. Linky ne lit pas la wallbox, et encore moins la voiture.** Beaucoup de gens, en entendant parler de wallbox "communiquant avec Linky", imaginent un dialogue bidirectionnel qui n'existe pas.
|
||||
|
||||
**Le second élément, c'est l'arrivée du débat sur une taxe kilométrique.** Avec la baisse des recettes de TICPE liée à l'électrification du parc automobile, plusieurs think tanks et rapports parlementaires ont effectivement évoqué l'idée de taxer les kilomètres parcourus en VE, et certains ont mentionné Linky parmi les outils techniques *envisageables*. De cette spéculation prospective, une partie du public a tiré la conclusion que le compteur était déjà équipé pour le faire. Or il y a un gouffre entre « on pourrait peut-être un jour utiliser Linky comme brique d'un dispositif fiscal » et « Linky a été conçu pour ça ». Le premier est une hypothèse politique discutable ; le second est un raccourci qui ne correspond à aucune réalité technique.
|
||||
|
||||
## Et techniquement, ce serait possible un jour ?
|
||||
|
||||
C'est la question intéressante, et la réponse mérite plus de nuance qu'un simple oui ou non.
|
||||
|
||||
Il existe effectivement un champ de recherche actif, baptisé **NILM** pour *Non-Intrusive Load Monitoring*. L'idée : analyser la courbe de consommation globale d'un logement pour en déduire, par traitement du signal et apprentissage automatique, quels appareils s'y trouvent et quand ils fonctionnent. Chaque appareil aurait, en théorie, une "signature électrique" reconnaissable — un profil d'appel de courant au démarrage, un comportement en régime, etc.
|
||||
|
||||
En pratique, l'exercice est très difficile, et il l'est particulièrement pour la recharge d'un véhicule électrique. Une borne en charge se comporte comme une charge quasi constante de plusieurs kilowatts pendant plusieurs heures. C'est une signature… qui ressemble énormément à celle d'un chauffe-eau, d'un convecteur, d'un sèche-linge en cycle long ou d'un radiateur à inertie. Sans cadence de fonctionnement caractéristique, sans pics distinctifs, sans cycles courts, il n'y a rien de très spécifique à exploiter. Identifier de manière fiable qu'on a affaire à une voiture *et pas* à un autre appareil de puissance similaire reste un problème ouvert dans la littérature scientifique.
|
||||
|
||||
Mais surtout, et c'est le point essentiel : **Linky n'embarque aucun de ces algorithmes**. Il transmet des données de comptage agrégées à Enedis, qui les utilise pour la facturation et la gestion du réseau. Enedis n'a ni la mission, ni le droit légal, ni l'infrastructure pour analyser appareil par appareil les usages domestiques de ses clients. Le cadre réglementaire français, notamment via la CNIL, encadre strictement ce qui peut être fait des données de consommation, et toute exploitation plus fine — même la courbe de charge à pas fin — nécessite le consentement explicite de l'abonné.
|
||||
|
||||
## Ce qu'il faut retenir
|
||||
|
||||
Le compteur Linky mesure votre consommation globale, c'est vrai. Il permet à certaines bornes de recharge de moduler intelligemment leur puissance via la TIC, c'est vrai aussi. Mais il ne reconnaît pas, n'identifie pas et ne distingue pas une voiture électrique des autres appareils du logement. Cette capacité n'existe ni dans son matériel, ni dans son logiciel, ni dans les données qu'il transmet à Enedis.
|
||||
|
||||
L'affirmation selon laquelle « Linky a été conçu pour reconnaître la connexion d'une voiture à batterie » mélange donc trois choses très différentes : des capacités réelles mais limitées (mesure de puissance, interface TIC sortante), des usages techniques existants côté wallbox, et des hypothèses politiques sur de futurs dispositifs fiscaux. De cette confusion naît une rumeur frappante, mais infondée.
|
||||
|
||||
Le débat sur la fiscalité des véhicules électriques est légitime et important. Il mérite mieux que des affirmations qui n'ont pas de base technique.
|
||||
@@ -1068,3 +1068,85 @@ footer {
|
||||
.star-rating label:hover ~ label {
|
||||
color: #f5a623;
|
||||
}
|
||||
|
||||
/* ─── Barre de recherche (navbar) ────────── */
|
||||
.search-form {
|
||||
margin: 0 .5rem;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 160px;
|
||||
transition: width .2s;
|
||||
background: var(--vl-surface);
|
||||
border-color: var(--vl-border);
|
||||
color: var(--vl-text);
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
width: 220px;
|
||||
box-shadow: 0 0 0 2px rgba(var(--vl-accent-rgb, 59,130,246), .25);
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.search-form { margin: .5rem 0; }
|
||||
.search-input, .search-input:focus { width: 100%; }
|
||||
}
|
||||
|
||||
/* ─── Page de recherche ───────────────────── */
|
||||
.search-page { max-width: 780px; margin: 0 auto; }
|
||||
|
||||
.search-bar .input-group { max-width: 600px; }
|
||||
|
||||
.search-results { display: flex; flex-direction: column; gap: 2rem; }
|
||||
|
||||
.search-result {
|
||||
border-bottom: 1px solid var(--vl-border);
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.search-result-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .75rem;
|
||||
margin-bottom: .25rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.search-result-cat {
|
||||
background: var(--vl-surface);
|
||||
border: 1px solid var(--vl-border);
|
||||
border-radius: 20px;
|
||||
padding: 1px 10px;
|
||||
color: var(--vl-muted);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.search-result-cat:hover { color: var(--vl-accent); }
|
||||
|
||||
.search-result-date { color: var(--vl-muted); }
|
||||
|
||||
.search-result-title {
|
||||
font-size: 1.15rem;
|
||||
margin: 0 0 .4rem;
|
||||
}
|
||||
|
||||
.search-result-title a {
|
||||
color: var(--vl-text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.search-result-title a:hover { color: var(--vl-accent); }
|
||||
|
||||
.search-result-snippet {
|
||||
font-size: .9rem;
|
||||
color: var(--vl-muted);
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.search-result-snippet mark {
|
||||
background: rgba(250, 204, 21, .35);
|
||||
color: inherit;
|
||||
border-radius: 2px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
@@ -1502,6 +1502,27 @@ switch ($action) {
|
||||
include BASE_PATH . '/templates/profile.php';
|
||||
break;
|
||||
|
||||
case 'search':
|
||||
require_once BASE_PATH . '/src/SearchEngine.php';
|
||||
$searchQuery = trim($_GET['q'] ?? '');
|
||||
$searchResults = [];
|
||||
if ($searchQuery !== '') {
|
||||
$privateCats = $articles->getPrivateCategories();
|
||||
$searchPool = array_values(array_filter($articles->getAll(true), static function (array $a) use ($privateCats): bool {
|
||||
$cat = trim($a['category'] ?? '');
|
||||
if ($cat !== '' && in_array($cat, $privateCats, true) && !isLoggedIn()) {
|
||||
return false;
|
||||
}
|
||||
if (strtotime((string)($a['published_at'] ?? '')) > time() && !isLoggedIn()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
$searchResults = (new SearchEngine())->search($searchQuery, $searchPool);
|
||||
}
|
||||
include BASE_PATH . '/templates/search.php';
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
default:
|
||||
$privateCats = $articles->getPrivateCategories();
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Moteur de recherche plein-texte en mémoire.
|
||||
*
|
||||
* Algorithme : scoring multi-champ avec correspondance exacte, sous-chaîne et
|
||||
* similarité trigramme. Logique AND : tous les tokens de la requête doivent
|
||||
* matcher quelque part pour qu'un article soit retourné.
|
||||
*
|
||||
* Score par token :
|
||||
* 1.0 → mot identique (ex. "Linky" = "Linky")
|
||||
* 0.75 → sous-chaîne (ex. "voiture" ⊂ "voitures")
|
||||
* 0–0.5 → similarité trigramme (ex. "linki" ≈ "linky")
|
||||
*
|
||||
* Poids par champ : titre × 6, catégorie × 3, contenu × 1.
|
||||
*/
|
||||
class SearchEngine
|
||||
{
|
||||
private const TITLE_WEIGHT = 6.0;
|
||||
private const CAT_WEIGHT = 3.0;
|
||||
private const CONTENT_WEIGHT = 1.0;
|
||||
private const FUZZY_FLOOR = 0.55; // seuil min. de similarité trigramme
|
||||
private const SNIPPET_LEN = 220;
|
||||
|
||||
/**
|
||||
* @param array<array> $articles Liste brute d'articles (depuis ArticleManager)
|
||||
* @return array<array{article: array, score: float, snippet: string}>
|
||||
*/
|
||||
public function search(string $query, array $articles): array
|
||||
{
|
||||
$tokens = $this->tokenize($query);
|
||||
if (empty($tokens)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$results = [];
|
||||
foreach ($articles as $article) {
|
||||
$plain = $this->stripMarkdown($article['content'] ?? '');
|
||||
$tWords = $this->tokenize($article['title'] ?? '');
|
||||
$cWords = $this->tokenize($article['category'] ?? '');
|
||||
$pWords = $this->tokenize($plain);
|
||||
|
||||
$score = $this->scoreArticle($tokens, $tWords, $cWords, $pWords);
|
||||
if ($score > 0.0) {
|
||||
$results[] = [
|
||||
'article' => $article,
|
||||
'score' => $score,
|
||||
'snippet' => $this->buildSnippet($plain, $tokens),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
usort($results, fn ($a, $b) => $b['score'] <=> $a['score']);
|
||||
return $results;
|
||||
}
|
||||
|
||||
// ─── Scoring ─────────────────────────────────────────────────────────────
|
||||
|
||||
private function scoreArticle(array $tokens, array $tWords, array $cWords, array $pWords): float
|
||||
{
|
||||
$total = 0.0;
|
||||
foreach ($tokens as $token) {
|
||||
$ts = $this->tokenScore($token, $tWords) * self::TITLE_WEIGHT
|
||||
+ $this->tokenScore($token, $cWords) * self::CAT_WEIGHT
|
||||
+ $this->tokenScore($token, $pWords) * self::CONTENT_WEIGHT;
|
||||
|
||||
if ($ts <= 0.0) {
|
||||
return 0.0; // AND strict : token introuvable → article exclu
|
||||
}
|
||||
$total += $ts;
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne un score 0–1 mesurant à quel point $token correspond
|
||||
* au meilleur mot de la liste $words.
|
||||
*/
|
||||
private function tokenScore(string $token, array $words): float
|
||||
{
|
||||
$best = 0.0;
|
||||
$tLen = mb_strlen($token);
|
||||
foreach ($words as $w) {
|
||||
if ($w === $token) {
|
||||
return 1.0; // exact
|
||||
}
|
||||
if ($tLen >= 3 && (str_contains($w, $token) || str_contains($token, $w))) {
|
||||
$best = max($best, 0.75); // sous-chaîne (pluriels, conjugaisons)
|
||||
}
|
||||
if ($tLen >= 4) {
|
||||
$sim = $this->trigramSimilarity($token, $w);
|
||||
if ($sim >= self::FUZZY_FLOOR) {
|
||||
$best = max($best, $sim * 0.55); // fuzzy (fautes de frappe)
|
||||
}
|
||||
}
|
||||
}
|
||||
return $best;
|
||||
}
|
||||
|
||||
// ─── Trigramme ───────────────────────────────────────────────────────────
|
||||
|
||||
private function trigramSimilarity(string $a, string $b): float
|
||||
{
|
||||
$tA = $this->trigrams($a);
|
||||
$tB = $this->trigrams($b);
|
||||
if (empty($tA) || empty($tB)) {
|
||||
return 0.0;
|
||||
}
|
||||
$common = count(array_intersect($tA, $tB));
|
||||
return $common / max(count($tA), count($tB));
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
private function trigrams(string $s): array
|
||||
{
|
||||
$out = [];
|
||||
$len = mb_strlen($s);
|
||||
for ($i = 0; $i + 2 < $len; $i++) {
|
||||
$out[] = mb_substr($s, $i, 3);
|
||||
}
|
||||
return array_unique($out);
|
||||
}
|
||||
|
||||
// ─── Snippet avec surbrillance ────────────────────────────────────────────
|
||||
|
||||
private function buildSnippet(string $text, array $tokens): string
|
||||
{
|
||||
$norm = $this->normalize($text);
|
||||
$pos = 0;
|
||||
foreach ($tokens as $token) {
|
||||
$p = mb_strpos($norm, $token);
|
||||
if ($p !== false) {
|
||||
$pos = max(0, $p - 60);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$raw = mb_substr($text, $pos, self::SNIPPET_LEN);
|
||||
if ($pos > 0) {
|
||||
$raw = '…' . ltrim($raw);
|
||||
}
|
||||
if ($pos + self::SNIPPET_LEN < mb_strlen($text)) {
|
||||
$raw .= '…';
|
||||
}
|
||||
|
||||
$escaped = htmlspecialchars($raw, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
|
||||
// Surbrillance : on cherche les tokens dans le texte HTML-échappé
|
||||
foreach ($tokens as $token) {
|
||||
$escaped = (string) preg_replace(
|
||||
'/(' . preg_quote(htmlspecialchars($token, ENT_QUOTES, 'UTF-8'), '/') . ')/iu',
|
||||
'<mark>$1</mark>',
|
||||
$escaped
|
||||
);
|
||||
}
|
||||
|
||||
return $escaped;
|
||||
}
|
||||
|
||||
// ─── Helpers texte ────────────────────────────────────────────────────────
|
||||
|
||||
/** Découpe en mots normalisés (min. 2 caractères). */
|
||||
private function tokenize(string $text): array
|
||||
{
|
||||
$norm = $this->normalize($text);
|
||||
$words = preg_split('/\W+/u', $norm, -1, PREG_SPLIT_NO_EMPTY) ?: [];
|
||||
return array_values(array_filter($words, fn ($w) => mb_strlen($w) >= 2));
|
||||
}
|
||||
|
||||
/** Minuscule + translittération des accents français. */
|
||||
private function normalize(string $text): string
|
||||
{
|
||||
$text = mb_strtolower($text, 'UTF-8');
|
||||
return strtr($text, [
|
||||
'à' => 'a', 'â' => 'a', 'ä' => 'a',
|
||||
'é' => 'e', 'è' => 'e', 'ê' => 'e', 'ë' => 'e',
|
||||
'î' => 'i', 'ï' => 'i',
|
||||
'ô' => 'o', 'ö' => 'o',
|
||||
'ù' => 'u', 'û' => 'u', 'ü' => 'u',
|
||||
'ç' => 'c', 'æ' => 'ae', 'œ' => 'oe', 'ñ' => 'n',
|
||||
]);
|
||||
}
|
||||
|
||||
/** Retire la syntaxe Markdown pour extraire le texte brut. */
|
||||
private function stripMarkdown(string $md): string
|
||||
{
|
||||
$t = preg_replace('/!\[[^\]]*\]\([^)]+\)/', '', $md) ?? $md; // images
|
||||
$t = preg_replace('/\[([^\]]+)\]\([^)]+\)/', '$1', $t) ?? $t; // liens
|
||||
$t = preg_replace('/```[\s\S]*?```/', '', $t) ?? $t; // blocs code
|
||||
$t = preg_replace('/`[^`]+`/', '', $t) ?? $t; // code inline
|
||||
$t = preg_replace('/^#{1,6}\s*/m', '', $t) ?? $t; // titres
|
||||
$t = preg_replace('/[*_~]{1,3}([^*_~]+)[*_~]{1,3}/', '$1', $t) ?? $t; // gras/italique
|
||||
$t = preg_replace('/^\s*[-*+|>]\s*/m', '', $t) ?? $t; // listes, citations, tableaux
|
||||
$t = preg_replace('/\n{2,}/', ' ', $t) ?? $t;
|
||||
return trim($t);
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,15 @@
|
||||
else: ?>
|
||||
<ul class="navbar-nav me-auto"></ul>
|
||||
<?php endif; ?>
|
||||
<form class="search-form d-flex" action="/" method="GET" role="search">
|
||||
<input type="hidden" name="action" value="search">
|
||||
<input class="form-control form-control-sm search-input"
|
||||
type="search" name="q"
|
||||
value="<?= htmlspecialchars($_GET['q'] ?? '') ?>"
|
||||
placeholder="Rechercher…"
|
||||
aria-label="Rechercher">
|
||||
</form>
|
||||
|
||||
<ul class="navbar-nav">
|
||||
<?php if (function_exists('isLoggedIn') && isLoggedIn()): ?>
|
||||
<li class="nav-item"><a class="nav-link" href="/?action=admin">Admin</a></li>
|
||||
|
||||
@@ -264,7 +264,7 @@ $mainClass = 'container-fluid';
|
||||
// Auto-description depuis le contenu si le champ SEO est vide
|
||||
$seoDescription = $article['seo_description'] ?? '';
|
||||
if ($seoDescription === '') {
|
||||
$plain = strip_tags((new Parsedown())->text($article['content']));
|
||||
$plain = strip_tags($Parsedown->text($article['content']));
|
||||
$plain = preg_replace('/\s+/', ' ', $plain);
|
||||
$seoDescription = mb_strimwidth(trim((string)$plain), 0, 155, '…');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php ob_start(); ?>
|
||||
|
||||
<div class="search-page">
|
||||
|
||||
<form class="search-bar mb-5" action="/" method="GET" role="search">
|
||||
<input type="hidden" name="action" value="search">
|
||||
<div class="input-group">
|
||||
<input type="search" class="form-control form-control-lg" name="q"
|
||||
value="<?= htmlspecialchars($searchQuery) ?>"
|
||||
placeholder="Rechercher…" autofocus autocomplete="off"
|
||||
aria-label="Rechercher">
|
||||
<button class="btn btn-primary" type="submit">Rechercher</button>
|
||||
</div>
|
||||
<div class="form-text mt-1">
|
||||
Plusieurs mots = recherche ET · Fautes de frappe tolérées · Accents ignorés
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if ($searchQuery !== ''): ?>
|
||||
|
||||
<?php if (empty($searchResults)): ?>
|
||||
<p class="text-muted">Aucun résultat pour <strong><?= htmlspecialchars($searchQuery) ?></strong>.</p>
|
||||
<?php else: ?>
|
||||
<p class="text-muted mb-4">
|
||||
<?= count($searchResults) ?> résultat<?= count($searchResults) > 1 ? 's' : '' ?>
|
||||
pour <strong><?= htmlspecialchars($searchQuery) ?></strong>
|
||||
</p>
|
||||
|
||||
<div class="search-results">
|
||||
<?php foreach ($searchResults as $r):
|
||||
$a = $r['article'];
|
||||
$postUrl = '/post/' . rawurlencode($a['slug'] ?? '');
|
||||
$cat = trim($a['category'] ?? '');
|
||||
$date = $a['published_at'] ? date('d/m/Y', strtotime((string)$a['published_at'])) : '';
|
||||
?>
|
||||
<article class="search-result">
|
||||
<div class="search-result-meta">
|
||||
<?php if ($cat !== ''): ?>
|
||||
<a class="search-result-cat" href="/?cat=<?= rawurlencode($cat) ?>"><?= htmlspecialchars($cat) ?></a>
|
||||
<?php endif; ?>
|
||||
<?php if ($date !== ''): ?>
|
||||
<time class="search-result-date"><?= $date ?></time>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<h2 class="search-result-title">
|
||||
<a href="<?= htmlspecialchars($postUrl) ?>"><?= htmlspecialchars($a['title'] ?? '') ?></a>
|
||||
</h2>
|
||||
<?php if ($r['snippet'] !== ''): ?>
|
||||
<p class="search-result-snippet"><?= $r['snippet'] ?></p>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
$title = $searchQuery !== '' ? 'Recherche : ' . $searchQuery : 'Recherche';
|
||||
$metaRobots = 'noindex, follow';
|
||||
$canonical = rtrim(APP_URL, '/') . '/';
|
||||
include __DIR__ . '/layout.php';
|
||||
Reference in New Issue
Block a user