build(agent): molt-b#d1f4fd iteration
This commit is contained in:
parent
8faf33804c
commit
a2787cbe34
|
|
@ -0,0 +1,21 @@
|
||||||
|
node_modules/
|
||||||
|
.npmrc
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
__tests__/
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.cache/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
tmp/
|
||||||
|
.tmp/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
*.egg-info/
|
||||||
|
.pytest_cache/
|
||||||
|
READY_TO_PUBLISH
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# OpenBench SWARM Agent Guidelines
|
||||||
|
|
||||||
|
- Architecture: MVP focusing on a privacy-preserving KPI share/aggregation pipeline with offline-first storage.
|
||||||
|
- Tech Stack: Python 3.8+, standard library, no heavy dependencies for MVP; packaging via setuptools; tests with pytest.
|
||||||
|
- Testing: `pytest` for unit tests; `python3 -m build` to verify packaging metadata and directory structure.
|
||||||
|
- Running tests: `bash test.sh` in the repo root.
|
||||||
|
- Contribution Rules: one feature per patch; keep changes minimal; avoid touching unrelated areas.
|
||||||
|
- Data Model: KPIRecord with revenue, COGS, inventory_turns, lead_time, CAC, LTV; anonymous sharing via anon_id.
|
||||||
|
- Privacy: aggregate with optional Laplace noise (simple, deterministic in tests when anonymize=False).
|
||||||
|
- How to Extend: add new adapters, contracts, or playbooks under respective namespaces; ensure tests cover new behavior.
|
||||||
20
README.md
20
README.md
|
|
@ -1,3 +1,19 @@
|
||||||
# openbench-privacy-preserving-benchmarkin
|
# OpenBench: Privacy-Preserving Benchmarking (MVP)
|
||||||
|
|
||||||
A modular, open-source platform that enables small businesses to anonymously share and compare KPIs across industries and regions, without exposing raw data. It uses policy-driven data exchanges with data contracts and secure aggregation, offering of
|
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.
|
||||||
|
|
||||||
|
How to run
|
||||||
|
- Install dependencies and run tests: `bash test.sh`
|
||||||
|
- The MVP stores KPI records locally in a JSONL file under the package data directory.
|
||||||
|
|
||||||
|
Project layout (high-level)
|
||||||
|
- openbench_privacy_preserving_benchmarkin/core.py: Core data model and analytics primitives.
|
||||||
|
- __init__.py: Re-exports core primitives for simple imports.
|
||||||
|
- test.sh: Quick test runner that also builds the distribution.
|
||||||
|
- AGENTS.md: Swarm agent guidelines describing architecture and testing commands.
|
||||||
|
- pyproject.toml/setup.py: Packaging metadata to satisfy python packaging checks.
|
||||||
|
|
||||||
|
This is a deliberately minimal MVP intended to demonstrate architecture and testing practices.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
"""OpenBench Privacy-Preserving Benchmarking (MVP)
|
||||||
|
|
||||||
|
This package provides a compact, offline-first core for capturing KPI data,
|
||||||
|
performing privacy-preserving aggregations, and computing simple derived metrics.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .core import KPIRecord, LocalStore, SecureAggregator, GrowthCalculator
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"KPIRecord",
|
||||||
|
"LocalStore",
|
||||||
|
"SecureAggregator",
|
||||||
|
"GrowthCalculator",
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
from dataclasses import dataclass, asdict, field
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Optional
|
||||||
|
import random
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
STORE_DIR = os.path.join(os.path.dirname(__file__), "data")
|
||||||
|
STORE_PATH = os.path.join(STORE_DIR, "kpi_records.jsonl")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class KPIRecord:
|
||||||
|
revenue: float
|
||||||
|
cogs: float
|
||||||
|
inventory_turns: float
|
||||||
|
lead_time: float
|
||||||
|
cac: float
|
||||||
|
ltv: float
|
||||||
|
region: str = "global"
|
||||||
|
industry: str = "unknown"
|
||||||
|
anon_id: str = field(default_factory=lambda: uuid.uuid4().hex)
|
||||||
|
timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())
|
||||||
|
|
||||||
|
|
||||||
|
class LocalStore:
|
||||||
|
"""Simple offline-first store for KPI records (JSONL)."""
|
||||||
|
|
||||||
|
def __init__(self, path: Optional[str] = None) -> None:
|
||||||
|
self.path = path or STORE_PATH
|
||||||
|
os.makedirs(os.path.dirname(self.path), exist_ok=True)
|
||||||
|
|
||||||
|
def add_kpi(self, record: KPIRecord) -> None:
|
||||||
|
with open(self.path, "a", encoding="utf-8") as f:
|
||||||
|
f.write(json.dumps(asdict(record)) + "\n")
|
||||||
|
|
||||||
|
def get_all(self) -> List[KPIRecord]:
|
||||||
|
records: List[KPIRecord] = []
|
||||||
|
if not os.path.exists(self.path):
|
||||||
|
return records
|
||||||
|
with open(self.path, "r", encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
data = json.loads(line)
|
||||||
|
# Guard against missing fields in legacy lines
|
||||||
|
records.append(KPIRecord(**data))
|
||||||
|
return records
|
||||||
|
|
||||||
|
|
||||||
|
class SecureAggregator:
|
||||||
|
"""Privacy-aware aggregate over KPI records.
|
||||||
|
|
||||||
|
By default, returns the plain mean. When anonymize=True, adds Laplace noise
|
||||||
|
to the result to preserve differential privacy characteristics. Noise is
|
||||||
|
deterministic for unit tests by allowing a fixed seed on the RNG via Python's
|
||||||
|
random module; in production, a robust rng should be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _laplace_sample(b: float) -> float:
|
||||||
|
# Laplace sampling via the difference of two exponentials: ~ Laplace(0, b)
|
||||||
|
u1 = random.random()
|
||||||
|
u2 = random.random() if u1 == 0 else random.random()
|
||||||
|
# ensure positive values and stable logs
|
||||||
|
return -b * (math.log(u1 + 1e-12) - math.log(u2 + 1e-12))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def aggregate(records: List[KPIRecord], metric: str, anonymize: bool = False, epsilon: float = 1.0) -> float:
|
||||||
|
if not records:
|
||||||
|
return 0.0
|
||||||
|
if not hasattr(KPIRecord, '__annotations__') or metric not in KPIRecord.__annotations__:
|
||||||
|
raise ValueError(f"Unknown metric '{metric}' for KPIRecord.")
|
||||||
|
values = [getattr(r, metric) for r in records]
|
||||||
|
mean = sum(values) / len(values) if values else 0.0
|
||||||
|
if anonymize:
|
||||||
|
b = 1.0 / max(epsilon, 1e-9)
|
||||||
|
noise = SecureAggregator._laplace_sample(b)
|
||||||
|
mean += noise
|
||||||
|
return mean
|
||||||
|
|
||||||
|
|
||||||
|
class GrowthCalculator:
|
||||||
|
"""Derived-growth helpers for KPI records."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def roi(record: KPIRecord) -> float:
|
||||||
|
# Simple return on investment proxy: (LTV - CAC) / CAC
|
||||||
|
if record.cac == 0:
|
||||||
|
return float('inf')
|
||||||
|
return (record.ltv - record.cac) / record.cac
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def growth_index(record: KPIRecord) -> float:
|
||||||
|
# A lightweight composite growth index using revenue and LTV
|
||||||
|
return (record.revenue + record.ltv) / max(1.0, record.cogs)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["KPIRecord", "LocalStore", "SecureAggregator", "GrowthCalculator"]
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "openbench-privacy-preserving-benchmarkin"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Privacy-preserving benchmarking for SMEs with secure aggregation and data contracts."
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
license = { text = "MIT" }
|
||||||
|
authors = [ { name = "OpenCode SWARM", email = "dev@example.com" } ]
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="openbench-privacy-preserving-benchmarkin",
|
||||||
|
version="0.1.0",
|
||||||
|
description="Privacy-preserving benchmarking for SMEs with secure aggregation and data contracts.",
|
||||||
|
packages=find_packages(exclude=("tests",)),
|
||||||
|
include_package_data=True,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "[TEST] Installing package (editable) for test environment..."
|
||||||
|
python3 -m pip install -e .
|
||||||
|
|
||||||
|
echo "[TEST] Running pytest..."
|
||||||
|
pytest -q
|
||||||
|
|
||||||
|
echo "[TEST] Building package..."
|
||||||
|
python3 -m build
|
||||||
|
|
||||||
|
echo "All tests passed and packaging completed."
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from openbench_privacy_preserving_benchmarkin import KPIRecord, LocalStore, SecureAggregator, GrowthCalculator
|
||||||
|
|
||||||
|
|
||||||
|
def test_store_and_aggregate_simple():
|
||||||
|
path = "tests_data/kpi_records.jsonl"
|
||||||
|
if os.path.exists(path):
|
||||||
|
os.remove(path)
|
||||||
|
store = LocalStore(path=path)
|
||||||
|
r1 = KPIRecord(revenue=100.0, cogs=60.0, inventory_turns=3.0, lead_time=5.0, cac=20.0, ltv=120.0, region="NA", industry="Retail", anon_id="anon1", timestamp="2020-01-01T00:00:00Z")
|
||||||
|
r2 = KPIRecord(revenue=200.0, cogs=110.0, inventory_turns=4.0, lead_time=6.0, cac=30.0, ltv=240.0, region="NA", industry="Retail", anon_id="anon2", timestamp="2020-01-02T00:00:00Z")
|
||||||
|
store.add_kpi(r1)
|
||||||
|
store.add_kpi(r2)
|
||||||
|
all_recs = store.get_all()
|
||||||
|
assert len(all_recs) == 2
|
||||||
|
mean_rev = SecureAggregator.aggregate(all_recs, "revenue", anonymize=False)
|
||||||
|
assert mean_rev == (100.0 + 200.0) / 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_roi_calc():
|
||||||
|
r = KPIRecord(revenue=100.0, cogs=50.0, inventory_turns=2.0, lead_time=3.0, cac=10.0, ltv=100.0)
|
||||||
|
roI = GrowthCalculator.roi(r)
|
||||||
|
assert roI == (100.0 - 10.0) / 10.0
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
{"revenue": 100.0, "cogs": 60.0, "inventory_turns": 3.0, "lead_time": 5.0, "cac": 20.0, "ltv": 120.0, "region": "NA", "industry": "Retail", "anon_id": "anon1", "timestamp": "2020-01-01T00:00:00Z"}
|
||||||
|
{"revenue": 200.0, "cogs": 110.0, "inventory_turns": 4.0, "lead_time": 6.0, "cac": 30.0, "ltv": 240.0, "region": "NA", "industry": "Retail", "anon_id": "anon2", "timestamp": "2020-01-02T00:00:00Z"}
|
||||||
Loading…
Reference in New Issue