Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions lazer/cardano/gastro-benchmark/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# GastroBenchmark

`GastroBenchmark` es el microservicio de `Cuqui` para comparar ofertas de proveedores contra referencias internacionales de mercado.

La idea del sistema es simple: cuando un comerciante busca un producto dentro de `Cuqui`, no solo ve qué proveedor lo vende más barato, sino también cuánto está pagando por encima o por debajo del mercado internacional de la materia prima o commodity relacionada. Esa referencia se construye con Pyth sobre Cardano, con una arquitectura preparada para operar tanto off-chain como on-chain.

## Rol dentro de Cuqui
`Cuqui` es la plataforma principal.

Repositorio de `Cuqui`:
https://github.com/pjcdz/cuqui

`Cuqui` se encarga de:
- ingerir listas de precios de proveedores
- parsear PDFs, XLSX, DOCX, imágenes y mensajes
- normalizar productos y ofertas
- construir el catálogo comprador y la experiencia frontend

`GastroBenchmark` se encarga de:
- recibir productos u ofertas ya normalizadas desde `Cuqui`
- mapearlas a benchmarks internacionales de commodities cuando exista cobertura
- consultar precios Pyth
- calcular markup contra mercado internacional
- devolver snapshots, historial en USD y explicaciones listas para UI

En otras palabras: `Cuqui` es la plataforma; `GastroBenchmark` es la capa backend especializada que le agrega contexto de mercado internacional.

Este repositorio debe leerse junto con `Cuqui`, porque `GastroBenchmark` no busca reemplazar la plataforma visual sino actuar como su microservicio de benchmarks y contexto de mercado.

## Por qué Cardano + Pyth
Este proyecto nació para una hackathon y su base conceptual y técnica está en Cardano.

La intención del diseño es que esta capacidad de benchmark internacional viva sobre una arquitectura Cardano + Pyth:
- hoy, como microservicio backend que consulta y expone información útil para `Cuqui`
- mañana, también con validaciones y flujos on-chain más fuertes a medida que la cobertura de commodities en Pyth/Cardano madure

Por eso este repo conserva tanto la base on-chain como la capa off-chain:
- helpers e integración Cardano
- validadores Aiken
- cliente de precios Pyth
- servicio REST para consumo desde `Cuqui`

## Qué resuelve para el comerciante
Supongamos que un comerciante busca harina.

`Cuqui` puede mostrarle:
- qué proveedores la ofrecen
- a qué precio la ofrece cada uno
- cuál es el precio unitario normalizado

`GastroBenchmark` agrega:
- cuál es la referencia internacional relevante
- a qué valor está esa referencia en USD
- si el comerciante está pagando por encima, cerca o por debajo de esa referencia

La conclusión buscada no es “este proveedor es el más barato” solamente, sino:

“estoy pagando X% por encima o por debajo del mercado internacional para este insumo”.

## Limitación real hoy
La cobertura actual de commodities en Pyth/Cardano no incluye todos los productos gastronómicos que `Cuqui` puede parsear.

Eso significa que:
- algunos productos sí tendrán benchmark internacional
- otros solo podrán compararse con un proxy commodity
- muchos todavía no tendrán benchmark disponible

Eso no es un bug ni una inconsistencia del proyecto. Es parte explícita del diseño. El microservicio está preparado para crecer con la cobertura real de Pyth/Cardano sin prometer una precisión que hoy todavía no existe.

## Estado técnico actual
- Base Cardano preservada en `src/contract.ts`, `src/onchain-update.ts`, `src/transaction.ts` y los validadores Aiken
- Capa benchmark off-chain en `src/pyth.ts`, `src/benchmark-catalog.ts` y `src/benchmark-service.ts`
- API REST en `src/server.ts`
- Dashboard CLI para demo en `src/dashboard.ts`

## Flujo entre Cuqui y GastroBenchmark
1. `Cuqui` ingiere documentos de proveedores y extrae productos.
2. `Cuqui` normaliza nombres, unidades, precios y ofertas.
3. `Cuqui` consulta este microservicio con esa información normalizada.
4. `GastroBenchmark` responde con benchmark internacional, historial y comparación.
5. `Cuqui` renderiza ese resultado en el frontend para el comerciante.

## API
```text
GET /health
GET /feeds
GET /benchmarks/latest
GET /benchmarks/history?benchmarkId=3018&points=24
POST /compare/products
POST /compare/offers
```

### Input esperado desde Cuqui
```json
{
"items": [
{
"catalogProductId": "prod_123",
"productName": "Harina de maiz amarilla",
"categoryRoot": "Harinas",
"baseUnit": "kg",
"baseUnitPrice": 0.22,
"currency": "USD",
"supplierName": "Molino Norte"
}
]
}
```

### Respuesta ejemplo
```json
{
"results": [
{
"benchmarkStatus": "matched",
"comparisonStatus": "watch",
"benchmarkKind": "direct_match",
"comparisonUnit": "usd/kg",
"comparisonPriceUsd": 0.177157,
"markupPercent": 24.18,
"explanation": "La comparacion usa un benchmark commodity disponible hoy en Pyth/Cardano y lo proyecta a la unidad normalizada del producto."
}
]
}
```

## Estados relevantes de respuesta
- `matched`: hay benchmark internacional utilizable
- `market_closed_fallback_used`: se usó un punto histórico reciente
- `unit_not_comparable`: existe benchmark, pero no hay comparación confiable con la unidad normalizada actual
- `no_pyth_cardano_coverage`: el producto todavía no tiene cobertura en Pyth/Cardano
- `future_mapping_candidate`: existe idea de mapping, pero todavía no hay precio comparable útil

## Desarrollo
```bash
npm install
npm run serve
```

Dashboard:
```bash
npm run dashboard
```

Verificación:
```bash
npm test
npm run check
```

## Variables de entorno
- `PYTH_API_KEY`: requerida para snapshots e historial vía Pyth Lazer
- `PORT`: puerto HTTP del microservicio, por defecto `8080`
- `BLOCKFROST_KEY`: integración Cardano cuando se use la capa Lucid
- `WALLET_SEED`: integración Cardano para flujos on-chain

## Roadmap
- ampliar el catálogo de mappings producto -> commodity
- guardar historial persistente en USD por feed
- reforzar integración on-chain con Cardano
- consumir más commodities a medida que Pyth/Cardano se acerque a la cobertura del mercado web2
- mostrar estas comparaciones directamente dentro de `Cuqui` para comerciantes finales
15 changes: 15 additions & 0 deletions lazer/cardano/gastro-benchmark/onchain/gastro_benchmark/aiken.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This file was generated by Aiken
# You typically do not need to edit this file

[[requirements]]
name = "aiken-lang/stdlib"
version = "v3.0.0"
source = "github"

[[packages]]
name = "aiken-lang/stdlib"
version = "v3.0.0"
requirements = []
source = "github"

[etags]
18 changes: 18 additions & 0 deletions lazer/cardano/gastro-benchmark/onchain/gastro_benchmark/aiken.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name = "cuqui/gastro_benchmark"
version = "1.0.0"
compiler = "v1.1.21"
plutus = "v3"
license = "Apache-2.0"
description = "Fair price procurement platform for Argentine restaurants using Pyth Network"

[repository]
user = "cuqui"
project = "gastro_benchmark"
platform = "github"

[[dependencies]]
name = "aiken-lang/stdlib"
version = "v3.0.0"
source = "github"

[config]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use cardano/transaction.{Transaction}

// Max markup: 30%
const max_markup_numerator: Int = 1300
const max_markup_denominator: Int = 1000

type PurchaseOrderDatum {
supplier_id: ByteArray,
supplier_price: Int,
quantity: Int,
buyer_pkh: ByteArray,
}

type PurchaseOrderRedeemer {
pyth_price: Int,
}

validator gastro_benchmark {
spend(_datum: Option<Data>, _redeemer: Data, _utxo: OutputReference, _self: Transaction) {
// TODO: Implement Pyth price validation logic
True
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
test wheat_price_fair_pass() {
// Pyth dice $5.00/kg, proveedor cobra $6.00/kg (20% markup -> PASS)
True
}

test wheat_price_abusive_fail() {
// Pyth dice $5.00/kg, proveedor cobra $8.00/kg (60% markup -> FAIL)
False
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Continuous Integration

on:
push:
branches: ["main"]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: aiken-lang/setup-aiken@v1
with:
version: v1.1.21
- run: aiken fmt --check
- run: aiken check -D
- run: aiken build
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Aiken compilation artifacts
artifacts/
# Aiken's project working directory
build/
# Aiken's default documentation export
docs/
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# test_project

Write validators in the `validators` folder, and supporting functions in the `lib` folder using `.ak` as a file extension.

```aiken
validator my_first_validator {
spend(_datum: Option<Data>, _redeemer: Data, _output_reference: Data, _context: Data) {
True
}
}
```

## Building

```sh
aiken build
```

## Configuring

**aiken.toml**
```toml
[config.default]
network_id = 41
```

Or, alternatively, write conditional environment modules under `env`.

## Testing

You can write tests in any module using the `test` keyword. For example:

```aiken
use config
test foo() {
config.network_id + 1 == 42
}
```

To run all tests, simply do:

```sh
aiken check
```

To run only tests matching the string `foo`, do:

```sh
aiken check -m foo
```

## Documentation

If you're writing a library, you might want to generate an HTML documentation for it.

Use:

```sh
aiken docs
```

## Resources

Find more on the [Aiken's user manual](https://aiken-lang.org).
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name = "cuqui/test_project"
version = "0.0.0"
compiler = "v1.1.21"
plutus = "v3"
license = "Apache-2.0"
description = "Aiken contracts for project 'cuqui/test_project'"

[repository]
user = "cuqui"
project = "test_project"
platform = "github"

[[dependencies]]
name = "aiken-lang/stdlib"
version = "v3.0.0"
source = "github"

[config]
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Gastro Benchmark - Purchase Order Validator

use aiken/list
use aiken/bytearray

// Max markup: 30%
const max_markup_numerator: Int = 1300
const max_markup_denominator: Int = 1000

type PurchaseOrderDatum {
supplier_id: ByteArray,
supplier_price: Int,
quantity: Int,
buyer_pkh: ByteArray,
}

type PurchaseOrderRedeemer {
pyth_price: Int,
}

validator {
fn validate_purchase_order(
datum: PurchaseOrderDatum,
redeemer: PurchaseOrderRedeemer,
ctx: ScriptContext,
) -> Bool {
// Maximo permitido = pyth_price x 1.30
let max_allowed_price =
redeemer.pyth_price * max_markup_numerator / max_markup_denominator

// Validar precio
let price_valid = datum.supplier_price <= max_allowed_price

// Validar firma del comprador
let buyer_signed =
list.has(ctx.transaction.extra_signatories, datum.buyer_pkh)

price_valid && buyer_signed
}
}
Loading