Build Your Own WineScan
This recipe walks through creating a fully private WineScan vertical from scratch: registering the vertical, defining a custom grading schema (Parker 100-point + bottle condition), adding catalog items, and running an inspection.
What you will build
A private wine inspection product with a custom Parker-style 100-point flavor schema and a bottle condition schema, populated with private Bordeaux catalog items, ready to grade bottles via the inspection API.
1. Create the vertical
import { KliqClient } from '@kliq-ai/sdk';
const kliq = new KliqClient({ apiKey: process.env.KLIQ_API_KEY! });
const vertical = await kliq.verticals.create('t_abc123', {
slug: 'acme-wine-scan',
name: 'WineScan',
description: 'Authentication, condition grading and provenance for fine wine.',
brandColor: '#722F37',
});
console.log(vertical.id); // "vert_wine001"
2. Define grading schemas
Parker 100-point flavor schema
const parkerSchema = await kliq.gradingSchemas.create('t_abc123', {
verticalSlug: 'acme-wine-scan',
slug: 'parker-100-point',
name: 'Parker 100-Point Scale',
standard: 'Robert Parker / Wine Advocate',
overallFormula: { method: 'weighted_average', weightField: 'weight' },
attributes: [
{
key: 'color',
name: 'Color & Appearance',
type: 'scale',
config: { min: 0, max: 5, step: 1 },
weight: 0.05,
required: true,
},
{
key: 'aroma',
name: 'Aroma & Bouquet',
type: 'scale',
config: { min: 0, max: 15, step: 1 },
weight: 0.15,
required: true,
},
{
key: 'flavor',
name: 'Flavor & Finish',
type: 'scale',
config: { min: 0, max: 20, step: 1 },
weight: 0.20,
required: true,
},
{
key: 'overall_quality',
name: 'Overall Quality',
type: 'scale',
config: { min: 0, max: 10, step: 1 },
weight: 0.10,
required: true,
},
],
});
Bottle condition schema
const bottleSchema = await kliq.gradingSchemas.create('t_abc123', {
verticalSlug: 'acme-wine-scan',
slug: 'wine-bottle-condition',
name: 'Wine Bottle Condition',
standard: 'Auction House',
overallFormula: { method: 'weighted_average', weightField: 'weight' },
attributes: [
{
key: 'fill_level',
name: 'Fill Level',
type: 'enum',
config: { options: ['IN', 'BN', 'VTS', 'TS', 'HS', 'MS', 'LS'] },
weight: 0.4,
required: true,
},
{
key: 'label_condition',
name: 'Label Condition',
type: 'scale',
config: { min: 1, max: 5, step: 1 },
weight: 0.2,
required: true,
},
{
key: 'capsule',
name: 'Capsule',
type: 'enum',
config: { options: ['intact', 'corroded', 'cut', 'seepage', 'missing'] },
weight: 0.2,
required: true,
},
{
key: 'cork',
name: 'Cork',
type: 'enum',
config: { options: ['sound', 'raised', 'sunken', 'damp', 'crumbly'] },
weight: 0.2,
required: true,
},
],
});
console.log('Schemas created:', parkerSchema.slug, bottleSchema.slug);
3. Add catalog items
const tenant = kliq.forTenant('t_abc123');
// Add Penfolds Grange as a private catalog item under the public wine type
// (or create a private type first if the item doesn't fit the public catalog)
const grange = await kliq.catalog.items.create('t_abc123', {
typeSlug: 'new-world-icons', // from the public collector-scan catalog
slug: 'penfolds-grange-2016',
name: 'Penfolds Grange 2016',
description: 'South Australian Shiraz-dominant flagship. Rated 99 points (Parker).',
attributes: {
producer: 'Penfolds',
vintage: 2016,
variety: 'Shiraz',
region: 'South Australia',
format: '750ml',
},
});
const latour = await kliq.catalog.items.create('t_abc123', {
typeSlug: 'bordeaux-first-growths',
slug: 'chateau-latour-2005',
name: 'Château Latour 2005',
description: 'Grand Vin, Pauillac. Legendary vintage, 100-point scores from multiple critics.',
attributes: {
appellation: 'Pauillac',
vintage: 2005,
classification: '1er Grand Cru Classé 1855',
},
});
console.log('Catalog items added:', grange.slug, latour.slug);
4. Run a bottle inspection
// Create the inspection for a physical bottle
const inspection = await tenant.inspections.create({
catalogItemSlug: 'chateau-latour-2005',
gradingSchemaSlug: bottleSchema.slug, // assess physical condition first
notes: 'OWC. Stored in temperature-controlled cellar since release.',
metadata: { cellarRef: 'CELLAR-A-42', owner: 'collector_pierre' },
});
// Upload bottle photos (label, capsule, fill level, cork if opened)
const { uploadUrl, imageUrl } = await tenant.observations.getUploadUrl({
filename: 'latour-2005-label.jpg',
contentType: 'image/jpeg',
});
await fetch(uploadUrl, { method: 'PUT', body: bottleLabelImage, headers: { 'Content-Type': 'image/jpeg' } });
await tenant.observations.create({
locationId: 'loc_cellar_a',
imageUrl,
metadata: { inspectionId: inspection.id, angle: 'label' },
});
// Trigger AI grading
await tenant.inspections.grade(inspection.id);
5. Review the results
// Poll until graded
let result = await tenant.inspections.get(inspection.id);
while (result.status === 'grading') {
await new Promise(r => setTimeout(r, 2000));
result = await tenant.inspections.get(inspection.id);
}
console.log('AI condition grades:', result.aiGrades);
// {
// fill_level: 'BN', // Base of Neck — excellent
// label_condition: 5, // Pristine
// capsule: 'intact',
// cork: 'sound',
// }
// Finalize with human confirmation
const final = await tenant.inspections.submitHumanGrades(inspection.id, {
grades: result.aiGrades, // all confirmed
notes: 'Perfect condition. Original wooden case present.',
});
console.log('Status:', final.status); // "completed"
Next steps
- Grading Schemas — Explore attribute types and formulas
- Catalog Registry — Use the public wine catalog
- Inspection Flow — Full lifecycle reference