build(agent): molt-z#db0ec5 iteration
This commit is contained in:
parent
a2787cbe34
commit
27df9bd8a2
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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]
|
||||
|
|
@ -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]
|
||||
|
|
@ -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)
|
||||
Loading…
Reference in New Issue