Metadata-Version: 2.4
Name: beanvision-sdk
Version: 1.2.2
Summary: Official Python SDK for the BeanVision coffee data platform.
Author: BeanVision
License-Expression: MIT
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: attrs>=22.2.0
Requires-Dist: httpx<0.29.0,>=0.27
Requires-Dist: pydantic<3.0,>=2.8
Provides-Extra: dev
Requires-Dist: black>=24.0; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: pytest>=8.2; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"

# BeanVision Python SDK

BeanVision is a typed Python SDK for working with coffee data across the full coffee lifecycle.

It provides:

- A generated OpenAPI client
- A stable high-level Python client
- Grouped service interfaces
- Coffee-focused domain objects
- Cross-domain analytics
- Deterministic recommendations
- Type checking, linting, formatting, and automated tests
- Support for Green Coffee, Roast, Extraction, and Cupping data

## Requirements

Python 3.12 or newer.

## Installation

From the `sdk` directory:

```bash
python -m pip install -e .
```

Install development dependencies:

```bash
python -m pip install -e ".[dev]"
```

## Quick start

```python
from beanvision import BeanVisionClient

with BeanVisionClient(
    base_url="http://localhost:8000",
    api_key="your-api-key",
) as client:
    coffee = client.passports.coffee("passport-001")

    print(coffee.summary.title)
    print(coffee.green.moisture)
    print(coffee.roast.agtron)
    print(coffee.extraction.tds)
    print(coffee.cupping.total_score)
```

## Coffee Passport

`CoffeePassport` provides a high-level view across the complete coffee lifecycle:

```python
coffee = client.passports.coffee("passport-001")
```

It exposes:

```python
coffee.green
coffee.roast
coffee.extraction
coffee.cupping
coffee.analytics
coffee.recommendations
```

## Green Coffee

```python
coffee.green.moisture
coffee.green.water_activity
coffee.green.density
coffee.green.color
coffee.green.screen_size
coffee.green.defect_count
coffee.green.process
coffee.green.variety
```

## Roast

```python
coffee.roast.color
coffee.roast.agtron
coffee.roast.batch_size
coffee.roast.charge_temperature
coffee.roast.turning_point
coffee.roast.drying_end
coffee.roast.first_crack
coffee.roast.drop_temperature
coffee.roast.development_time
coffee.roast.development_percent
coffee.roast.total_time
coffee.roast.weight_loss
coffee.roast.machine
coffee.roast.profile
coffee.roast.notes
```

## Extraction

```python
coffee.extraction.dose
coffee.extraction.beverage_weight
coffee.extraction.brew_ratio
coffee.extraction.tds
coffee.extraction.extraction_yield
coffee.extraction.water_temperature
coffee.extraction.brew_time
coffee.extraction.grinder
coffee.extraction.grind_setting
coffee.extraction.recipe
coffee.extraction.notes
```

Extraction target helpers:

```python
coffee.extraction.target_range
coffee.extraction.is_under_extracted
coffee.extraction.is_over_extracted
coffee.extraction.within_target_range
```

## Cupping

```python
coffee.cupping.total_score
coffee.cupping.fragrance
coffee.cupping.flavor
coffee.cupping.aftertaste
coffee.cupping.acidity
coffee.cupping.body
coffee.cupping.balance
coffee.cupping.sweetness
coffee.cupping.clean_cup
coffee.cupping.uniformity
coffee.cupping.overall
coffee.cupping.defects
coffee.cupping.descriptors
coffee.cupping.notes
```

# Analytics

Every `CoffeePassport` exposes:

```python
coffee.analytics
```

## Summary

```python
summary = coffee.analytics.summary

print(summary.green_moisture)
print(summary.roast_agtron)
print(summary.extraction_yield)
print(summary.cupping_total_score)
```

## Completeness

```python
completeness = coffee.analytics.completeness

print(completeness.green)
print(completeness.roast)
print(completeness.extraction)
print(completeness.cupping)
print(completeness.overall)
```

Completeness values are percentages from `0` to `100`.

## Quality flags

```python
flags = coffee.analytics.quality_flags

print(flags.has_missing_data)
print(flags.missing_count)
print(flags.missing_green_density)
print(flags.missing_roast_agtron)
print(flags.missing_extraction_tds)
print(flags.missing_cupping_score)
```

## Timeline summary

```python
timeline = coffee.analytics.timeline_summary

print(timeline.total_observations)
print(timeline.current_observations)
print(timeline.non_current_observations)
print(timeline.first_observation_at)
print(timeline.latest_observation_at)
print(timeline.domains_present)
```

## Benchmarking

Run the default light-roast benchmark:

```python
benchmark = coffee.analytics.benchmark()
```

Each value is classified as:

```text
below
within
above
missing
```

Useful aggregate properties:

```python
benchmark.all_within
benchmark.has_missing
benchmark.out_of_range_count
```

Custom benchmark profiles are supported:

```python
from beanvision.analytics import (
    BenchmarkRange,
    CoffeeBenchmarkProfile,
)

profile = CoffeeBenchmarkProfile(
    green_moisture=BenchmarkRange(9.5, 11.5),
    green_water_activity=BenchmarkRange(0.45, 0.60),
    green_density=BenchmarkRange(680.0, 780.0),
    roast_agtron=BenchmarkRange(75.0, 90.0),
    roast_development_percent=BenchmarkRange(10.0, 16.0),
    roast_weight_loss=BenchmarkRange(10.0, 14.5),
    extraction_tds=BenchmarkRange(1.15, 1.55),
    extraction_yield=BenchmarkRange(18.0, 22.0),
    cupping_total_score=BenchmarkRange(80.0, 100.0),
)

benchmark = coffee.analytics.benchmark(profile)
```

# Recommendations

Every `CoffeePassport` exposes:

```python
coffee.recommendations
```

Generate deterministic recommendations:

```python
recommendations = coffee.recommendations.generate()
```

Each recommendation provides:

```python
recommendation.severity
recommendation.category
recommendation.title
recommendation.explanation
recommendation.suggestion
recommendation.is_actionable
```

Example:

```python
for recommendation in recommendations:
    print(recommendation.severity, recommendation.title)
    print(recommendation.explanation)
    print(recommendation.suggestion)
```

Recommendations are generated from:

- Data-quality flags
- Green Coffee benchmarks
- Roast benchmarks
- Extraction benchmarks
- Cupping benchmarks

A custom benchmark profile can also be supplied:

```python
recommendations = coffee.recommendations.generate(profile)
```

# Services

The SDK groups API operations into service objects:

```python
client.lots
client.observations
client.passports
```

## Lots

```python
lot = client.lots.create(
    human_id="LOT-ETH-001",
    name="Ethiopia Guji",
    origin_country="Ethiopia",
)

lots = client.lots.list()
```

## Observations

```python
observation = client.observations.create(
    subject_id="lot-001",
    observation_type="green.moisture",
    source_type="difluid",
    source_id="omix-plus-001",
    value_numeric=10.5,
    unit="percent",
)

observations = client.observations.list_for_lot("lot-001")
```

## Passports

```python
passports = client.passports.list(
    organization_id="org-001",
    status="draft",
    country="Ethiopia",
    tag="washed",
    limit=50,
    offset=0,
)

summary = client.passports.get("passport-001")
detail = client.passports.detail("passport-001")
timeline = client.passports.timeline(
    "passport-001",
    limit=100,
    offset=0,
)
coffee = client.passports.coffee("passport-001")
```

# Observation types

Use the canonical registry instead of raw strings where practical:

```python
from beanvision.observation_types import ObservationType

ObservationType.GREEN_MOISTURE
ObservationType.ROAST_AGTRON
ObservationType.EXTRACTION_TDS
ObservationType.CUPPING_TOTAL_SCORE
```

# Error handling

```python
from beanvision.exceptions import (
    BeanVisionAPIError,
    BeanVisionAuthenticationError,
    BeanVisionConflictError,
    BeanVisionNotFoundError,
    BeanVisionValidationError,
)
```

Example:

```python
from beanvision.exceptions import BeanVisionNotFoundError

try:
    coffee = client.passports.coffee("missing-passport")
except BeanVisionNotFoundError as exc:
    print(exc)
```

# Examples

A guided learning path is available in:

```text
sdk/examples/
```

Start with:

```bash
python examples/01_connect.py
```

The flagship Analytics and Recommendations example is:

```bash
python examples/11_analytics_and_recommendations.py
```

See `sdk/examples/README.md` for the complete walkthrough.

# Development

Run the quality checks from the `sdk` directory:

```bash
black beanvision tests examples
ruff check beanvision tests examples
mypy beanvision
pytest -q
```

Check formatting without modifying files:

```bash
black --check beanvision tests examples
```

# Generated client

The generated OpenAPI client lives in:

```text
sdk/generated/
```

The public `beanvision` package wraps the generated client with a stable, coffee-focused API.

Regenerate it from the repository root with:

```bash
./scripts/generate_sdk.sh
```

# Continuous integration

GitHub Actions runs:

- Black
- Ruff
- MyPy
- Pytest

Workflow:

```text
.github/workflows/sdk-quality.yml
```

# Project structure

```text
sdk/
├── beanvision/
│   ├── analytics/
│   ├── recommendations/
│   ├── client.py
│   ├── cupping.py
│   ├── extraction.py
│   ├── green.py
│   ├── observation_domain.py
│   ├── observation_types.py
│   ├── passport.py
│   └── roast.py
├── examples/
├── generated/
├── tests/
├── README.md
└── pyproject.toml
```

# Versioning

The SDK follows Semantic Versioning.

Current development version:

```text
1.2.0
```

# License

See the repository-level `LICENSE` file.
