KLIQ|Developers

Shelf Monitoring (ShelfSignal)

This recipe walks through a complete ShelfSignal integration: from setting up your tenant to processing shelf detection results and building dashboard data.

What you will build

An automated shelf monitoring pipeline that detects products, tracks facings, identifies out-of-stock items, and aggregates data by store, SKU, and region.

1. Set up your tenant

import { KliqClient } from '@kliq-ai/sdk';

const kliq = new KliqClient({
apiKey: process.env.KLIQ_API_KEY!,
});

const tenant = kliq.forTenant('your-tenant-id');

2. Import your SKU catalog

Upload your product catalog so the CV model can identify specific SKUs:

// Import from CSV
await tenant.catalog.import({
format: 'csv',
url: 'https://your-bucket.com/sku-catalog.csv',
mapping: {
  name: 'product_name',
  sku: 'sku_code',
  brand: 'brand_name',
  category: 'category',
  barcode: 'ean',
},
});

// Or add items programmatically
await tenant.catalog.create({
name: 'Coca-Cola 330ml',
sku: 'CC-330',
brand: 'Coca-Cola',
category: 'Beverages',
barcode: '5449000000996',
referenceImages: ['https://cdn.example.com/cocacola-330.jpg'],
});

3. Create store locations

const stores = [
{ name: 'Store #42 - Brussels Central', address: '123 Retail Blvd', latitude: 50.8503, longitude: 4.3517, metadata: { region: 'Brussels', chain: 'Delhaize' } },
{ name: 'Store #43 - Antwerp Meir', address: '456 Meir Straat', latitude: 51.2194, longitude: 4.4025, metadata: { region: 'Antwerp', chain: 'Carrefour' } },
];

const locations = await Promise.all(
stores.map(store => tenant.locations.create(store))
);

console.log(`Created ${locations.length} store locations`);

4. Submit shelf photos

const observation = await tenant.observations.create({
locationId: locations[0].id,
imageUrl: 'https://your-bucket.com/shelf-beverages.jpg',
latitude: 50.8503,
longitude: 4.3517,
metadata: {
  aisle: 'beverages',
  captured_by: 'rep_jane',
  planogram_id: 'plan_bev_01',
},
});

5. Run shelf detection

const job = await tenant.cv.start({
observationId: observation.id,
model: 'shelf-detection-v2',
});

const result = await tenant.cv.waitForCompletion(job.id);
console.log(`Found ${result.result.detections.length} products`);

6. Process detection results

const { detections, summary } = result.result;

// Product-level analysis
for (const detection of detections) {
console.log(`${detection.objectName}: ${detection.attributes.facings} facings, confidence ${detection.confidence}`);
}

// Identify out-of-stock items
const outOfStock = summary.outOfStock;
if (outOfStock.length > 0) {
console.warn('Out-of-stock items:', outOfStock);
// Trigger alert...
}

// Compliance scoring
console.log(`Compliance score: ${(summary.complianceScore * 100).toFixed(0)}%`);

7. Aggregate dashboard data

// Aggregate by store
const storeData = new Map<string, { totalSKUs: number; avgCompliance: number }>();

for await (const obs of tenant.observations.listAll({ status: 'completed' })) {
const job = await tenant.cv.list({ observationId: obs.id, limit: 1 });
if (job.data[0]?.result) {
  const summary = job.data[0].result.summary;
  const existing = storeData.get(obs.locationId) || { totalSKUs: 0, avgCompliance: 0 };
  storeData.set(obs.locationId, {
    totalSKUs: existing.totalSKUs + summary.uniqueSKUs,
    avgCompliance: (existing.avgCompliance + summary.complianceScore) / 2,
  });
}
}

8. Set up real-time alerts

// Create webhook for real-time notifications
await tenant.webhooks.create({
url: 'https://your-server.com/api/shelf-alerts',
events: ['cv.job.completed'],
secret: 'whsec_your_secret',
});

// In your webhook handler:
// - Check for out-of-stock items
// - Send Slack/email alerts for low compliance scores
// - Update dashboard in real-time

Next steps