catalog-core (0.2.0)

Published 2026-06-21 19:49:02 +00:00 by xavier

Installation

pip install --index-url  catalog-core

About this package

Brique réutilisable offre & catalogue par marque (CDC-CATALOG-CORE-v1) — embarquée par chaque app, données par app, claims gouvernés C3.

catalog-core

Brique logicielle réutilisable — gestion de l'offre commerciale : le catalogue par marque (quels produits sont offerts, à quel prix, où, et avec quels claims) + contrat de consolidation vers le Cockpit.

Spécifiée par CDC-CATALOG-CORE-v1 (Holding MAPFARM). 3ᵉ membre du trio commercialclient-core (à qui on vend) · catalog-core (quoi on vend) · fin-core (l'argent) — reliés par l'ordre (OMS, CDC-3). Chaque application embarque la brique et possède ses propres données (store par app) ; le Cockpit consolide une vue holding par read-model et n'écrit jamais dans les stores des apps.

Ce dépôt = Livrable 0 du CDC : la brique seule (modèle d'offre §5, claims gouvernés C3 §7, garde-fous de publication §6.1, contrat de consolidation §3.3). Le câblage consommateur (Harvest L1, Cockpit L2, multi-marque/structure L3, promotions/multi-canal L4) est hors de ce dépôt.

Principes (non négociables — CDC §8)

# Règle
R1 Tout objet catalogue de tête tagué entite_id + societe_id + marque_id (canon, ADR-041).
R2 Chaque app est propriétaire de son catalogue (séparabilité).
R3 Aucun store catalogue partagé en écriture ; Cockpit = read-model.
R4 Le produit est défini en CDC-4 ; l'offre référence (produit_ref), ne le réinvente pas.
R5 Tout claim publié passe le pare-feu C3 — grounded sinon bloqué (fail-closed).
R6 Claim bloqué persisté (audit).
R7 Prix-liste (catalogue) ≠ revenu réalisé (fin-core) — distincts, liés par l'ordre.
R8 Contrat de consolidation standardisé : toute app sur la brique émet le même format.

Calculs Decimal-first (ROUND_HALF_UP, 2 décimales), HT canonique, EUR.

Claims gouvernés C3 — le cœur de la brique (§7)

Tout claim publié passe le pare-feu épistémique C3 : le grounding compare contre les valeurs affirmées structurées du corpus, jamais la présence textuelle (leçon « arabica » : bloqué même mentionné, car le produit est Fine Robusta). Fail-closed : un claim non fondé est bloqué, jamais publié.

La brique ne dépend pas en dur de corpus : elle expose un port (ClaimGrounder) que l'app branche sur le moteur réel (corpus_core.fusion_c3). Sans grounder injecté, le défaut FailClosedGrounder bloque tout.

from catalog_core import Claim, govern_claim, VerdictC3

class CorpusGrounder:  # implémentation app-côté, adossée au corpus (T1)
    def ground(self, claim): ...  # -> GroundingVerdict(grounded=..., blocked=..., ...)

claim = Claim(id="c1", offre_id="o1", classe="variety", texte="Fine Robusta",
              attribut="variety", valeur="Fine Robusta")
jugé = govern_claim(claim, CorpusGrounder())
assert jugé.verdict_c3 in (VerdictC3.GROUNDED, VerdictC3.BLOCKED, VerdictC3.PENDING)
# `jugé.claim_blocked` est persisté (audit).

Installation (registre PyPI Gitea)

--extra-index-url (et non --index-url) : la brique vient du registre Gitea, ses dépendances (pydantic, psycopg…) restent résolues depuis PyPI.

pip install --extra-index-url https://git.mapfarm.cloud/api/packages/mapfarm/pypi/simple/ catalog-core
# avec l'adaptateur de persistance psycopg :
pip install --extra-index-url https://git.mapfarm.cloud/api/packages/mapfarm/pypi/simple/ "catalog-core[db]"

Nature d'offre — produit ou prestation (0.2.0)

Une offre a une nature :

  • produit (défaut) — un produit CDC-4 rendu disponible à la vente (storefront/PIM) : produit_ref requis (R4), peut porter des claims C3.
  • prestation — un service offert (ex. le catalogue de services de la Factory) : pas de produit_ref, généralement sans claim.

Usage

from decimal import Decimal
from catalog_core import Offre, Prix, CatalogRules, NatureOffre

# Offre produit (storefront) — produit_ref obligatoire (R4)
offre = Offre(id="o1", entite_id="harvest", societe_id="mapfarm", marque_id="mlc",
              produit_ref="prod-magellan")
prix = Prix(id="p1", offre_id="o1", montant=Decimal("12.00"), regime_tva="franchise")

# Offre prestation (service) — pas de produit_ref
service = Offre(id="svc-dev", entite_id="it", societe_id="mapfarm", marque_id="factory",
                nature=NatureOffre.PRESTATION)
tarif = Prix(id="t-dev", offre_id="svc-dev", montant=Decimal("600.00"))  # forfait

rules = CatalogRules()
publiee = rules.publish(offre, claims=[])             # refuse si un claim est bloqué (R5)

Persistance (chaque app, sur son schéma) :

import psycopg
from catalog_core.schema import apply_schema

with psycopg.connect(dsn, autocommit=True) as conn:
    apply_schema(conn, "xavier__harvest__catalog")    # DDL idempotent, store de l'app

Consolidation (read-model émis vers le Cockpit) :

from catalog_core.consolidation import build_report

report = build_report(app="harvest", offres=offres, prix=prix,
                      disponibilites=dispo, claims=claims)   # par marque + taux de claims bloqués

Dev

make install   # pip install -e ".[test]"
make lint      # ruff
make test      # pytest (DDL idempotent skip si pas de Docker)
make build     # sdist + wheel

Requirements

Requires Python: >=3.11
Details
PyPI
2026-06-21 19:49:02 +00:00
545
Holding MAPFARM
Proprietary
32 KiB
Assets (2)
Versions (2) View all
0.2.0 2026-06-21
0.1.0 2026-06-21