Vineyard Estate
Vineyard estate API: blocks, vines, harvests and tastings as four related Pixeltable tables with yield UDFs
One app.py with 4 tables, 5 computed columns, 8 HTTP routes. Needs pixeltable[serve]>=0.7.14. Updated 2026-10-03.
app.py
"""Vineyard Estate API built with Pixeltable.pxt schema update app.py estatepxt service run app.py estate"""import pixeltable as pxtimport pixeltable.functions as pxtffrom pixeltable.serving import FastAPIRouterfrom udfs import ripeness, score_label, vine_age, yield_band, yield_per_acre# ---- tables ----TableModel = pxt.model_base()class Blocks(TableModel, name='blocks'):block_id = pxt.Column(type=pxt.String, primary_key=True)name: pxt.Stringvarietal: pxt.Stringacres: pxt.Floatorganic: pxt.Boolregion: pxt.Stringirrigation: pxt.String | None # added later; nullable, so the update is in placeclass Vines(TableModel, name='vines'):id = pxt.Column(value=pxtf.uuid.uuid7(), primary_key=True)block_id: pxt.Stringclone: pxt.Stringrootstock: pxt.Stringplanted_year: pxt.Intage = vine_age(planted_year)class Harvests(TableModel, name='harvests'):id = pxt.Column(value=pxtf.uuid.uuid7(), primary_key=True)block_id: pxt.Stringpicked_on: pxt.Stringtons: pxt.Floatacres: pxt.Floatbrix: pxt.Floattpa = yield_per_acre(tons, acres)band = yield_band(tons, acres)ripe = ripeness(brix)class Tastings(TableModel, name='tastings'):id = pxt.Column(value=pxtf.uuid.uuid7(), primary_key=True)block_id: pxt.Stringvintage: pxt.Intscore: pxt.Intnotes: pxt.String | Nonelabel = score_label(score)# ---- queries ----@pxt.querydef open_blocks(min_acres: float):"""Organic blocks of at least `min_acres`, largest first."""return Blocks.where((Blocks.acres >= min_acres) & (Blocks.organic == True)).select( # noqa: E712Blocks.block_id, Blocks.name, Blocks.varietal, Blocks.acres, Blocks.irrigation).order_by(Blocks.acres, asc=False)@pxt.querydef block_harvests(block_id: str):"""Harvest history for one block, oldest first."""return Harvests.where(Harvests.block_id == block_id).select(Harvests.picked_on, Harvests.tons, Harvests.tpa, Harvests.band, Harvests.brix, Harvests.ripe).order_by(Harvests.picked_on)# ---- routes ----estate_api = FastAPIRouter(name='estate_api')estate_api.add_insert_route(Blocks, path='/blocks',inputs=[Blocks.block_id, Blocks.name, Blocks.varietal, Blocks.acres, Blocks.organic,Blocks.region, Blocks.irrigation],outputs=[Blocks.block_id])estate_api.add_insert_route(Vines, path='/vines',inputs=[Vines.block_id, Vines.clone, Vines.rootstock, Vines.planted_year],outputs=[Vines.id, Vines.age])estate_api.add_insert_route(Harvests, path='/harvests',inputs=[Harvests.block_id, Harvests.picked_on, Harvests.tons, Harvests.acres, Harvests.brix],outputs=[Harvests.id, Harvests.tpa, Harvests.band, Harvests.ripe])estate_api.add_insert_route(Tastings, path='/tastings',inputs=[Tastings.block_id, Tastings.vintage, Tastings.score, Tastings.notes],outputs=[Tastings.id, Tastings.label])estate_api.add_update_route(Blocks, path='/blocks/irrigation', inputs=[Blocks.irrigation],outputs=[Blocks.block_id, Blocks.irrigation])estate_api.add_compute_route(Harvests, path='/yield-band', inputs=[Harvests.tons, Harvests.acres],outputs=[Harvests.tpa, Harvests.band])estate_api.add_query_route(path='/blocks/open', query=open_blocks, method='get')estate_api.add_query_route(path='/blocks/harvests', query=block_harvests, method='get')
What app.py declares
Each row links to its line.
- runs on insert
- stored
- index
- route
serves estate_api
- POST/blocksinsert, blockstakes block_id, name, varietal, acres, organic, region, irrigation; returns block_idL78
- POST/vinesinsert, vinestakes block_id, clone, rootstock, planted_year; returns id, ageL82
- POST/harvestsinsert, harveststakes block_id, picked_on, tons, acres, brix; returns id, tpa, band, ripeL85
- POST/tastingsinsert, tastingstakes block_id, vintage, score, notes; returns id, labelL88
- POST/blocks/irrigationupdate, blockstakes irrigation; returns block_id, irrigationL91
- POST/yield-bandcompute, harveststakes tons, acres; returns tpa, bandL93
- GET/blocks/openquery, open_blockstakes min_acresL95
- GET/blocks/harvestsquery, block_harveststakes block_idL96
Run it
Needs uv. The routes then answer on port 8000.
terminal
git clone https://github.com/pierrebrunelle/pixeltable-vineyard-blockscd pixeltable-vineyard-blocksuv syncuv run pxt schema update app.py estateuv run python seed.py estateuv run pxt service run app.py estate --port 8000
Related apps
- Forge Heat CyclesForge heat-cycle registry API: B-tree indexes, chained risk UDFs, and parallel shop-floor writersShares: UDFs
- Climbing Route ToposClimbing-route topo registry API: photo uploads with thumbnails, grayscale copies, grade bands and index-backed crag lookupsShares: UDFs
- Warehouse Pick ListsWarehouse pick-list REST API: units, pick zones, waves and tote labels as incremental computed columns over Json order linesShares: UDFs