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-timeNext steps
- Building Inspection recipe — BuildingScan integration
- Webhooks guide — Webhook setup and verification
- API Reference — Full endpoint documentation