Skip to main content

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:

python -m pip install -e .

Install development dependencies:

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

Quick start

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:

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

It exposes:

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

Green Coffee

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

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

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:

coffee.extraction.target_range
coffee.extraction.is_under_extracted
coffee.extraction.is_over_extracted
coffee.extraction.within_target_range

Cupping

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:

coffee.analytics

Summary

summary = coffee.analytics.summary

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

Completeness

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

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

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:

benchmark = coffee.analytics.benchmark()

Each value is classified as:

below
within
above
missing

Useful aggregate properties:

benchmark.all_within
benchmark.has_missing
benchmark.out_of_range_count

Custom benchmark profiles are supported:

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:

coffee.recommendations

Generate deterministic recommendations:

recommendations = coffee.recommendations.generate()

Each recommendation provides:

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

Example:

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:

recommendations = coffee.recommendations.generate(profile)

Services

The SDK groups API operations into service objects:

client.lots
client.observations
client.passports

Lots

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

lots = client.lots.list()

Observations

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

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:

from beanvision.observation_types import ObservationType

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

Error handling

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

Example:

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:

sdk/examples/

Start with:

python examples/01_connect.py

The flagship Analytics and Recommendations example is:

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:

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

Check formatting without modifying files:

black --check beanvision tests examples

Generated client

The generated OpenAPI client lives in:

sdk/generated/

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

Regenerate it from the repository root with:

./scripts/generate_sdk.sh

Continuous integration

GitHub Actions runs:

  • Black
  • Ruff
  • MyPy
  • Pytest

Workflow:

.github/workflows/sdk-quality.yml

Project structure

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:

1.2.0

License

See the repository-level LICENSE file.

Metadata

Release files for beanvision-sdk 1.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for beanvision-sdk 1.2.2
File Size Uploaded Provenance
beanvision_sdk-1.2.2.tar.gz 46.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for beanvision-sdk 1.2.2
File Interpreter ABI Platform
beanvision_sdk-1.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 107.1 kB

Release files / beanvision_sdk-1.2.2.tar.gz

Download URL beanvision_sdk-1.2.2.tar.gz
Size 46.8 kB
Tags Source
SHA-256 checksum
How to use checksums
3c733f77725b6992ab2c1b1d6d97116f0ad1478672118a448322a34a6f1a6ccd
BLAKE2b-256 checksum
How to use checksums
32429980569d226c19665e5dcbf26a0f013302777116a680572e9aecda4b96e1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Release files / beanvision_sdk-1.2.2-py3-none-any.whl

Download URL beanvision_sdk-1.2.2-py3-none-any.whl
Size 60.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ad7a06f85d8291d370143c1a3ea3a1cb9a4bd0b93a5b4d5d0ee6177532c48082
BLAKE2b-256 checksum
How to use checksums
248ef2ff5c8365ff401942f572db46c8fcea377a3a9d16af95bd0e1ff6cd2618
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Security for beanvision-sdk 1.2.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for beanvision_sdk-1.2.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

1.2.2 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page