npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tricoteuses/occultatrice

v0.1.3

Published

Occultation par pseudonymes des personnes physiques dans les documents juridiques structurés

Readme

Tricoteuses Occultatrice

Cœur d'occultation par pseudonymes des personnes physiques dans les corpus à individus privés (nominations JORF, décisions de jurisprudence, actes RAA).

Reçu un document structuré (texte intégral et entités déjà typées), le module produit :

  • la copie occultée : les noms des personnes sont remplacés par des pseudonymes en convention judiciaire (« M. X », « Mme Y »…), attribués par ordre d'apparition dans le document ; une même personne reçoit le même pseudonyme dans tout le document ;
  • le manifeste de substitutions : positions exactes de chaque substitution, dans le texte original comme dans la copie. Il sert de piste d'audit privée et alimentera le caviardage PDF.

Le texte original n'est jamais modifié : l'occultation est une fonction pure, déterministe et sans dépendance d'exécution.

Usage

import { chercherResidus, occulterDocument } from "@tricoteuses/occultatrice"

const document = {
  texte: "En qualité de secrétaire général : Mme Jeanne MARTIN.",
  personnes: [
    {
      personneId: "jorf:personne:123",
      civilite: "Mme",
      formes: ["Jeanne MARTIN", "MARTIN Jeanne"],
    },
  ],
}

const { texteOcculte, manifeste } = occulterDocument(document)
// texteOcculte === "En qualité de secrétaire général : Mme X."
// manifeste.substitutions[0].debut / .fin : positions dans le texte original
// manifeste.substitutions[0].debutOcculte / .finOcculte : positions dans la copie

// Contrôle qu'aucune chaîne occultée ne subsiste dans la copie :
const residus = chercherResidus(texteOcculte, manifeste, ["Jeanne", "MARTIN"])

Règles de détection et de nommage

  • Entités structurées exhaustives d'abord : les personnes déclarées (signataires, orateurs, nominés…) sont recherchées dans le texte, sans distinction de casse ni d'accents, aux frontières de mots ; la plus longue correspondance à chaque position l'emporte. Une mention NER qui chevauche une occurrence structurée d'une autre personne est écartée ; si elle couvre la même personne (après fusion d'identité), les intervalles sont fusionnés en une substitution unique (« Alain FAURE » détecté par NER étend « FAURE » déclaré en structuré, civilité comprise).- Mentions NER ensuite : les mentions (intervalles déjà détectés) complètent les zones restantes. Les mentions sont regroupées par personneId explicite, ou à défaut par forme normalisée ; un groupe sans identité explicite est fusionné avec une personne structurée lorsque sa forme normalisée est égale — ou sous-suite ordonnée de mots, dans un sens ou dans l'autre — d'exactement une personne structurée (ainsi « FAURE », détecté par NER, reçoit le pseudonyme de « Alain FAURE », déclaré en structuré).
  • Pseudonymes : M. X, Mme Y, M. Z, puis M. A… au-delà, une seconde passe ajoute un numéro (M. X2), garantissant l'unicité. La civilité du pseudonyme vient de l'entité déclarée, ou pour une mention NER du jeton de civilité qui précède le nom (« M. », « Mme », « Madame »…) ou de civiliteParDefaut (« M » par défaut).
  • Identifiants implicites : les personnes détectées uniquement par NER reçoivent un identifiant dérivé (forme:<clé normalisée> ou id:<personneId>), suffixé #2, #3… si cet identifiant est déjà déclaré dans le document — jamais de collision entre personnes.
  • Absorption des civilités : le jeton « M. » / « Mme » / « Madame »… qui précède immédiatement un nom est englobé dans la substitution, pour produire « Mme X » et non « Mme Mme X » (désactivable avec absorberCivilites: false).
  • Déterminisme : deux exécutions sur le même document produisent exactement la même copie et le même manifeste.
  • Tous les indices sont des offsets en unités UTF-16 (indices natifs JavaScript, directement utilisables avec String.prototype.slice).

Doctrine des signataires (ADR §2, révisé le 2026-10-18)

Le cœur occulte ce qu'on lui fournit ; la doctrine de sélection des cibles est une fonction séparée, à appliquer avant occulterDocument :

import { occulterDocument, selectionnerCibles } from "@tricoteuses/occultatrice"

const cibles = selectionnerCibles(texte, personnes, mentions, {
  blocsSignature: [{ debut, fin }], // bornes exactes des blocs de signature
  visas: [{ debut, fin }],
})
const { texteOcculte, manifeste } = occulterDocument({
  texte,
  personnes: cibles.personnes,
  mentions: cibles.mentions,
})

Les signataires des actes officiels ne sont pas des cibles : une personne qui signe et dont aucune occurrence ne sort des blocs de signature, des visas ou d'une formule d'organe chargé de l'exécution sort de l'occultation ; un signataire aussi visé reste masqué, signature comprise. Une personne citée hors signature, visas compris, reste ciblée. Les mentions NER du bloc de signature ne sont gardées que si elles se rattachent à une personne visée. La doctrine vit une seule fois, dans ce module : la chaîne structurée fournit ses zones exactes par segments de projection, la chaîne de caviardage PDF d'Exploratrice consomme le même socle avec ses zones détectées sur le texte brut. Limites partagées par les deux chaînes : les organes coordonnés (« M. X et Mme Y sont chargés de l'exécution ») ne sont pas reconnus — sur-masquage, dans le sens sûr.

Détection NER auto-hébergée (dégradable)

La détection du texte libre s'appuie sur des services NER auto-hébergés, branchés en environnement de développement :

  • la pseudonymisation-api de la Cour de cassation (Cour-de-cassation/pseudonymisation-api, code sous licence MIT) — point de terminaison POST /ner, contrat Decision de juritools (idLabel, idDecision, sourceId, sourceName, text, categories ; identifiants transmissibles via l'option document) ; étiquettes personnePhysique par défaut (les personnes en fonction publique, comme professionnelMagistratGreffier, ne sont pas occultées) ;
  • Presidio (microsoft/presidio, code sous licence MIT) avec le pipeline spaCy fr_core_news_lg (code spaCy sous MIT, modèle sous licence LGPL-LR).

Les positions renvoyées par ces services Python comptent des points de code : elles sont converties en unités UTF-16 à la réception ; les entités de la pseudonymisation-api sont en outre vérifiées contre le texte envoyé (champ text de chaque entité) — toute entité incohérente est écartée plutôt que mal découpée.

import {
  creerDetecteurPseudonymisationApi,
  occulterDocumentAvecDetection,
} from "@tricoteuses/occultatrice"

const detecteur = creerDetecteurPseudonymisationApi({
  url: process.env.PSEUDONYMISATION_API_URL ?? "http://localhost:8081",
  // jeton: process.env.PSEUDONYMISATION_API_TOKEN, // si l'instance en exige un
})

const { resultat, detection } = await occulterDocumentAvecDetection(
  document,
  detecteur,
)
if (detection.degrade) {
  // Service injoignable ou réponse inutilisable : l'occultation a reposé
  // sur les seules entités structurées (posture best-effort documentée).
  console.warn(detection.detail)
}

Dégradation : par défaut (strict: false), un service injoignable, lent (delaiExpirationMs, 10 s par défaut), en erreur HTTP ou de réponse inutilisable retourne { mentions: [], degrade: true, detail } au lieu de lever — l'occultation repose alors sur les seules entités structurées. Avec strict: true, l'erreur est levée. Aucune clé ni URL sensible n'est codée en dur : la configuration passe par les options (typiquement des variables d'environnement du paquet consommateur).

Posture best-effort

L'occultation ne vaut pas anonymisation au sens juridique : la détection est best-effort, les corrections se font sur signalement par rejeu du pipeline, sans altérer l'original. Voir l'ADR docs/architecture/ARCHITECTURE_DECISION_MASQUAGE_PERSONNES_PHYSIQUES.md et la spec (ticket Forgejo #276).

Commandes

npm run build   # construction (vite, ESM + types)
npm run check   # vérification des types (tsc --noEmit)
npm run lint    # eslint
npm test        # tests (vitest)
npm run format  # prettier