bizsupply-sdk 0.1.3
SDK for developing plugins for the bizSupply platform (requires a bizSupply account)
Requires Python: >=3.11
Keywords: ai, bizsupply, classification, document-processing, extraction, plugin, sdk

# bizSupply Plugin SDK

The official SDK for developing plugins and benchmarks for the [bizSupply](https://www.bizsupply.ai/) document processing platform.

> **This SDK is designed exclusively for the [bizSupply](https://www.bizsupply.ai/) platform.** It provides base classes, testing utilities, and CLI tools for building plugins that run inside the bizSupply engine. The SDK has no standalone functionality — a bizSupply account is required. [Sign up at bizsupply.ai](https://www.bizsupply.ai/) to get started.

## Quick Start

```bash
# Install the SDK
pip install bizsupply-sdk

# Create a new plugin project
bizsupply init classification --name my-classifier
cd my-classifier

# Create a new benchmark
bizsupply init benchmark --name energy-price

# Validate your code
bizsupply validate src/plugin.py

# Run tests
pytest tests/
```

## Plugin Types

| Type | Purpose | Base Class | Required Method |
|------|---------|------------|-----------------|
| **Source** | Ingest documents from external sources | `SourcePlugin` | `fetch()`, `has_new_data()` |
| **Classification** | Categorize documents with labels | `ClassificationPlugin` | `classify()` |
| **Extraction** | Extract structured data from documents | `ExtractionPlugin` | `extract()` |
| **Benchmark** | Score and compare documents | `BaseBenchmark` | `score()`, `compute()`, `compare()` |

## Example: Classification Plugin

```python
from typing import Any
from bizsupply_sdk import ClassificationPlugin, Document


class MyClassificationPlugin(ClassificationPlugin):
    """Classify documents using LLM analysis."""

    async def classify(
        self,
        document: Document,
        file_data: bytes | None,
        mime_type: str | None,
        available_labels: list[str],
        current_path: list[str],
        configs: dict[str, Any],
    ) -> str | None:
        """Classify a document by selecting from available_labels.

        The engine handles ontology tree traversal - just pick one label.
        Return None if no label matches.
        """
        # Use LLM to classify
        path_str = " > ".join(current_path) if current_path else "Root"
        prompt = f"Path: {path_str}\nOptions: {available_labels}\nSelect the best match."

        response = await self.prompt_llm(
            prompt=prompt,
            file_data=file_data,  # Gemini reads PDFs directly
            mime_type=mime_type,
        )

        # prompt_llm() returns dict | list | None
        category = response.get("category") if isinstance(response, dict) else None
        if category in available_labels:
            return category
        return None
```

## Example: Benchmark

```python
from typing import Any

from bizsupply_sdk import BaseBenchmark, ExtendedDocument, ScoredDocument, MatchCondition, MatchRule


class EnergyPriceBenchmark(BaseBenchmark):
    """Find the best energy contract price from invoice data."""

    name = "energy_price"
    target_labels = ["contract", "energy"]
    metric_unit = "EUR/kWh"

    MATCH_RULES = [
        MatchRule(
            name="contract_invoice_match",
            left_group=["contract", "energy"],
            right_group=["invoice", "energy"],
            conditions=[
                MatchCondition(
                    left_field="client_tax_id",
                    right_field="client_tax_id",
                    match_type="==",
                ),
            ],
        ),
    ]

    def score(self, document: ExtendedDocument) -> float | None:
        prices = [inv.get("price_per_kwh") for inv in document.aggregations]
        prices = [p for p in prices if p is not None]
        return sum(prices) / len(prices) if prices else None

    def compute(self, results: list[ScoredDocument]) -> float:
        return min(r.score for r in results)  # Best (lowest) market price

    def compare(self, document_score: float, benchmark_score: float) -> bool:
        return document_score > benchmark_score  # Paying more than market
```

## Testing Your Plugin

```python
import pytest
from bizsupply_sdk.testing import MockPluginServices, create_test_document


@pytest.fixture
def mock_services():
    return MockPluginServices(
        llm_responses={"default": "Contract"}
    )


async def test_classification(mock_services):
    plugin = MyClassificationPlugin()
    mock_services.configure_plugin(plugin)

    document = create_test_document(content="Contract agreement...")

    result = await plugin.classify(
        document=document,
        file_data=b"test content",
        mime_type="application/pdf",
        available_labels=["Contract", "Invoice", "Report"],
        current_path=[],
        configs={},
    )

    assert result == "Contract"
    assert mock_services.llm_call_count == 1
```

## CLI Commands

| Command | Description |
|---------|-------------|
| `bizsupply init <type>` | Scaffold a new plugin or benchmark (`source`, `classification`, `extraction`, `benchmark`, `ontology`) |
| `bizsupply validate <file>` | Validate plugin or benchmark code structure |
| `bizsupply tutorial` | Interactive development workflow tutorial |

## Documentation

- [Getting Started](https://bizsupply.readme.io/docs/welcome)
- [Create a Classification Plugin](https://bizsupply.readme.io/docs/create-classification-plugin)
- [Create an Extraction Plugin](https://bizsupply.readme.io/docs/create-extraction-plugin)
- [Create a Source Plugin](https://bizsupply.readme.io/docs/create-source-plugin)

## Links

- [bizSupply Platform](https://www.bizsupply.ai/) - Sign up and manage your account
- [Documentation](https://bizsupply.readme.io/docs/welcome) - Full developer documentation

## License

MIT License - see LICENSE file for details.