From 27df9bd8a22bf09da88ae35f4d6fc0816cdb8b44 Mon Sep 17 00:00:00 2001 From: agent-db0ec53c058f1326 Date: Wed, 15 Apr 2026 21:29:22 +0200 Subject: [PATCH] build(agent): molt-z#db0ec5 iteration --- README.md | 4 +- .../__init__.py | 14 +++++ .../adapters/base.py | 16 ++++++ .../adapters/erp.py | 29 +++++++++++ .../adapters/pos.py | 44 ++++++++++++++++ .../contracts.py | 52 +++++++++++++++++++ 6 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 openbench_privacy_preserving_benchmarkin/adapters/base.py create mode 100644 openbench_privacy_preserving_benchmarkin/adapters/erp.py create mode 100644 openbench_privacy_preserving_benchmarkin/adapters/pos.py create mode 100644 openbench_privacy_preserving_benchmarkin/contracts.py diff --git a/README.md b/README.md index de48741..ea951fa 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # OpenBench: Privacy-Preserving Benchmarking (MVP) -This repository contains a minimal, working MVP of the OpenBench platform focused on: +- This repository contains a minimal, working MVP of the OpenBench platform focused on: - An offline-first KPI data model (Revenue, COGS, inventory turns, lead time, CAC, LTV). - A simple, privacy-preserving aggregation primitive (Laplace-noise-enabled) for anonymized benchmarking. - A lightweight Python packaging setup compatible with pytest-based tests and python -m build packaging checks. +- A minimal data-contract system (DataContract) and an in-memory contract registry for versioned payload schemas. +- A pluggable adapter framework with sample POSAdapter and ERPAdapter to bootstrap data ingestion. How to run - Install dependencies and run tests: `bash test.sh` diff --git a/openbench_privacy_preserving_benchmarkin/__init__.py b/openbench_privacy_preserving_benchmarkin/__init__.py index f7a5449..49b6acf 100644 --- a/openbench_privacy_preserving_benchmarkin/__init__.py +++ b/openbench_privacy_preserving_benchmarkin/__init__.py @@ -5,6 +5,20 @@ performing privacy-preserving aggregations, and computing simple derived metrics """ from .core import KPIRecord, LocalStore, SecureAggregator, GrowthCalculator +from .contracts import DataContract, ContractRegistry +from .adapters.pos import POSAdapter +from .adapters.erp import ERPAdapter + +__all__ = [ + "KPIRecord", + "LocalStore", + "SecureAggregator", + "GrowthCalculator", + "DataContract", + "ContractRegistry", + "POSAdapter", + "ERPAdapter", +] __all__ = [ "KPIRecord", diff --git a/openbench_privacy_preserving_benchmarkin/adapters/base.py b/openbench_privacy_preserving_benchmarkin/adapters/base.py new file mode 100644 index 0000000..c81c816 --- /dev/null +++ b/openbench_privacy_preserving_benchmarkin/adapters/base.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import List +from openbench_privacy_preserving_benchmarkin.core import KPIRecord +from openbench_privacy_preserving_benchmarkin.contracts import DataContract + + +class Adapter: + """Base adapter interface for data sources.""" + + def __init__(self, name: str, contract: DataContract): + self.name = name + self.contract = contract + + def collect(self) -> List[KPIRecord]: # pragma: no cover - abstract in base + raise NotImplementedError diff --git a/openbench_privacy_preserving_benchmarkin/adapters/erp.py b/openbench_privacy_preserving_benchmarkin/adapters/erp.py new file mode 100644 index 0000000..c3f9978 --- /dev/null +++ b/openbench_privacy_preserving_benchmarkin/adapters/erp.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from typing import List + +from .base import Adapter +from openbench_privacy_preserving_benchmarkin.core import KPIRecord +from openbench_privacy_preserving_benchmarkin.contracts import DataContract + + +class ERPAdapter(Adapter): + """A toy ERP adapter that yields KPIRecords.""" + + def __init__(self, name: str, contract: DataContract): + super().__init__(name, contract) + + def collect(self) -> List[KPIRecord]: + k1 = KPIRecord( + revenue=800.0, + cogs=500.0, + inventory_turns=1.8, + lead_time=4.0, + cac=40.0, + ltv=900.0, + region="default", + industry="Manufacturing", + anon_id=f"{self.name}-erp1", + timestamp="2020-01-03T00:00:00Z", + ) + return [k1] diff --git a/openbench_privacy_preserving_benchmarkin/adapters/pos.py b/openbench_privacy_preserving_benchmarkin/adapters/pos.py new file mode 100644 index 0000000..654ae6f --- /dev/null +++ b/openbench_privacy_preserving_benchmarkin/adapters/pos.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from typing import List + +from .base import Adapter +from openbench_privacy_preserving_benchmarkin.core import KPIRecord +from openbench_privacy_preserving_benchmarkin.contracts import DataContract + + +class POSAdapter(Adapter): + """A toy POS (Point-of-Sale) adapter that yields KPIRecords.""" + + def __init__(self, name: str, contract: DataContract, seed: int = 42): + super().__init__(name, contract) + self._seed = seed + + def collect(self) -> List[KPIRecord]: + # For MVP: generate two deterministic KPI records as samples + # In a real implementation this would pull from a POS feed. + k1 = KPIRecord( + revenue=1000.0, + cogs=600.0, + inventory_turns=2.5, + lead_time=2.0, + cac=50.0, + ltv=1200.0, + region="default", + industry="Retail", + anon_id=f"{self.name}-sample1", + timestamp="2020-01-01T00:00:00Z", + ) + k2 = KPIRecord( + revenue=1500.0, + cogs=900.0, + inventory_turns=3.0, + lead_time=3.0, + cac=60.0, + ltv=1700.0, + region="default", + industry="Retail", + anon_id=f"{self.name}-sample2", + timestamp="2020-01-02T00:00:00Z", + ) + return [k1, k2] diff --git a/openbench_privacy_preserving_benchmarkin/contracts.py b/openbench_privacy_preserving_benchmarkin/contracts.py new file mode 100644 index 0000000..23fb6f4 --- /dev/null +++ b/openbench_privacy_preserving_benchmarkin/contracts.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import uuid +from dataclasses import dataclass, asdict +from typing import Dict, Any + +from .core import KPIRecord + + +@dataclass +class DataContract: + """A minimal, versioned data contract for KPI payloads. + + This is intentionally lightweight for MVP purposes. Each contract + defines a unique id, a version, a JSON schema-like payload description, + and privacy controls flags used by aggregators. + """ + + contract_id: str + version: int + schema: Dict[str, Any] + privacy_flags: Dict[str, Any] + description: str = "" + + def to_dict(self) -> Dict[str, Any]: + return asdict(self) + + @staticmethod + def make_new(schema: Dict[str, Any], privacy_flags: Dict[str, Any], description: str = "") -> DataContract: + return DataContract( + contract_id=uuid.uuid4().hex, + version=1, + schema=schema, + privacy_flags=privacy_flags, + description=description, + ) + + +class ContractRegistry: + """In-memory registry for data contracts.""" + + def __init__(self) -> None: + self._contracts: Dict[str, DataContract] = {} + + def register(self, contract: DataContract) -> None: + self._contracts[contract.contract_id] = contract + + def get(self, contract_id: str) -> DataContract | None: + return self._contracts.get(contract_id) + + def all(self) -> Dict[str, DataContract]: + return dict(self._contracts)