Skip to main content

Product Traceability System - Developer Guide

⚠️ CRITICAL ARCHITECTURE NOTICE​

Hash-Only Mode (Default since 2024)​

The Product Trace API now uses Hash-Only Mode by default, which provides deterministic, millisecond-level matching based on barcode and OCR text hashes:

Hash-Only Mode Pipeline (Default)
├── 🔴 Stage 1: Barcode Exact Match (<10ms)
│ └── Match success → Direct confirm, terminate early
├── 🟡 Stage 2: Text Hash Match (<10ms)
│ └── Text hash 100% match → Direct confirm
└── ❌ No match → Return verified=false

To enable legacy visual similarity matching, set environment variable SCAN_HASH_ONLY_MODE=false.

Legacy Mode (Visual Similarity)​

Deprecation Notice

Legacy mode with DINOv2 visual features is deprecated and should only be used when:

  • Products have no barcode or OCR text
  • Visual similarity matching is specifically required

DINOv2 global features CANNOT be used for final product verification alone.

Test Results (Synthetic Products):​

MetricValueProduction Requirement
False Positive Rate100%< 0.1%
Same Product Similarity0.877> 0.9
Different Product Similarity0.811< 0.5
Classification Margin0.066> 0.3

Root Cause:​

DINOv2 extracts global semantic features - products with similar colors/patterns produce highly overlapping feature vectors. The 0.066 margin is insufficient for reliable discrimination.

Legacy Architecture:​

Product Verification Pipeline (Legacy Mode)
├── 🔴 Stage 1: Barcode Exact Match (0.1ms, 99.99% accuracy)
│ └── Match success → Direct confirm, terminate early
├── 🟡 Stage 2: DINOv2 Coarse Filtering (50ms, 85% recall)
│ └── Similarity < 0.3 → Quick reject
│ └── Similarity ≥ 0.85 → High confidence candidate
│ └── 0.3 ~ 0.85 → Proceed to fine verification
├── 🟢 Stage 3: OCR Text Semantic Match (2-6s, 95% accuracy)
│ └── Text fingerprint 100% match → Direct confirm
│ └── Keyword subset match ≥ 0.8 → High confidence
│ └── TF-IDF vector similarity fusion
└── ⚖️ Final Decision: Weighted fusion + manual review recommendation

Development Guide

This guide provides detailed information about the architecture, design patterns, and implementation details of the Alaikis BI API platform, following the openspec development specification.

System Architecture​

Overview​

The Alaikis BI API is built on a microservices-inspired architecture using FastAPI with the following core components:

┌─────────────────────────────────────────────────────────┐
│ FastAPI Application │
├─────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Auth │ │ Routes │ │ Middleware │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Packing │ │ Product │ │ Address │ │
│ │ Service │ │ Trace Svc │ │ Parser │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Classif- │ │ Async │ │
│ │ ication │ │ Analysis │ │
│ │ Service │ │ Pool │ │
│ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Database │ │ Redis │ │ Model │ │
│ │ (PostgreSQL)│ │ Cache │ │ Registry │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘

Tech Stack​

ComponentTechnologyVersion
Web FrameworkFastAPI0.100+
DatabasePostgreSQL14+
ORMSQLAlchemy2.0+
CacheRedis7.0+
ML FrameworkPyTorch2.0+
OptimizationOR-Tools9.0+
Address Parsinglibpostal1.1+
AuthCasdoor OAuth2 + JWT-
Test Frameworkpytest7.0+

Core Services Architecture​

Packing Service - 3-Layer Hybrid Architecture​

The packing optimization service uses a unique three-layer architecture for optimal performance and correctness:

┌─────────────────────────────────────────────────────────┐
│ Layer 3: OR-Tools Mathematical Solver │
│ - Formal mathematical guarantees │
│ - Constraint Programming │
│ - Integer Linear Programming │
└─────────────────────────────────────────────────────────┘
↑
┌─────────────────────────────────────────────────────────┐
│ Layer 2: AI Model Prediction │
│ - SKU-pallet compatibility scoring │
│ - Placement sequence optimization │
│ - Pattern recognition from historical data │
└─────────────────────────────────────────────────────────┘
↑
┌─────────────────────────────────────────────────────────┐
│ Layer 1: Rule Engine │
│ - Pre-assigned pallet handling │
│ - Unavailable pallet filtering │
│ - Dangerous goods isolation │
│ - Stacking constraints │
└─────────────────────────────────────────────────────────┘

Implementation Details​

# api/service/intelligent_packing_service.py
class IntelligentPackingService:
def __init__(self, use_ai: bool = True):
self.use_ai = use_ai
self.rule_engine = RuleEngine()
self.ai_model = AIMatchingModel() if use_ai else None
self.or_solver = ORToolsSolver()

def pack(self, skus, pallets, **kwargs):
# 1. Apply rules first
processed = self.rule_engine.process(skus, pallets, **kwargs)

# 2. AI prediction if enabled
if self.use_ai:
scored_pallets = self.ai_model.predict_compatibility(
processed['skus'],
processed['pallets']
)
else:
scored_pallets = processed['pallets']

# 3. OR-Tools mathematical optimization
result = self.or_solver.solve(
skus=processed['skus'],
pallets=scored_pallets,
time_limit_ms=kwargs.get('time_limit_ms', 30000)
)

return result

Product Trace Service - Multi-Modal Pipeline​

The product trace service uses a multi-modal feature extraction and verification pipeline:

┌─────────────────────┐
│ Input Image(s) │
└──────────┬──────────┘
│
┌──────────────────┼──────────────────┐
↓ ↓ ↓
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Barcode Detector│ │ OCR Extractor │ │ Feature Extractor│
│ (ZBar + OpenCV) │ │ (Tesseract) │ │ (ResNet + CLIP) │
└────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘
│ │ │
└────────────────────┼────────────────────┘
↓
┌─────────────────────┐
│ Feature Fusion │
│ - Vector averaging │
│ - Confidence weight│
└──────────┬──────────┘
↓
┌─────────────────────┐
│ Database Storage │
│ - pgvector for ANN │
│ - JSON for raw data│
└─────────────────────┘

Three-Stage Verification Pipeline with Early Termination​

Following the openspec specification, the verification pipeline implements early termination at each stage for performance optimization:

# Verification Flow
def verify_product(query_image, sku, identification_code):
"""Three-stage verification pipeline with early termination

Stage 1: Fast barcode matching (< 10ms) - RETURN immediately if found
Stage 2: Global feature quick match (< 100ms) - RETURN early if confident
Stage 3: Fine-grained verification (2-6 seconds) - only if needed
"""

# Stage 1: Fast barcode matching - EARLY TERMINATION
barcodes = barcode_service.detect(query_image)
for barcode in barcodes:
if matches_criteria(barcode, identification_code):
return VERIFIED, 1.0, "barcode_match" # ← EARLY RETURN

# Stage 2: Global feature quick match - EARLY TERMINATION POINTS
query_features = feature_service.extract_global(query_image)
candidates = vector_db.search(query_features, top_k=50)

# Early termination: No candidates found
if len(candidates) == 0:
return NOT_VERIFIED, 0.0, "no_global_candidates" # ← EARLY RETURN

# Early termination: Exactly one candidate
if len(candidates) == 1:
return VERIFIED, candidates[0].similarity, "single_candidate" # ← EARLY RETURN

# Early termination: Single high-confidence match above threshold
best_match = find_best_candidate(candidates, sku, identification_code)
if best_match and best_match.similarity > fast_accept_threshold:
return VERIFIED, best_match.similarity, "fast_feature_match" # ← EARLY RETURN

# Stage 3: Fine-grained verification - only reached if necessary
fine_similarity = fine_grained_service.compare(query_image, best_match.images)
ocr_match = ocr_service.compare_text(query_image, best_match.ocr_data)

final_score = weighted_combination(fine_similarity, ocr_match)

if final_score > final_threshold:
return VERIFIED, final_score, "fine_grained_match"
else:
return NOT_VERIFIED, final_score, "no_match"

DINOv2 Lightweight Feature Extraction Architecture​

Overview​

The product trace service now supports an end-to-end DINOv2 feature extraction pipeline that replaces the traditional dual-channel (global + fine-grained) architecture with a single unified vision transformer model.

Architecture Comparison​

AspectTraditional ArchitectureDINOv2 Architecture
Feature Dimension2304D (2048D global + 256D local)384D unified
Inference Speed~250ms/image~50ms/image (5x faster)
Peak Memory~1.2GB~500MB (60% saving)
Model Count2+ (ResNet + SuperPoint)1 (ViT-S/14)
Accuracy Trade-offHighest accuracySlightly lower (-1-2%) but sufficient for most use cases
Batch ProcessingLimitedNative support (matrix multiplication)

DINOv2 Feature Pipeline​

┌─────────────────────┐
│ Input Image(s) │
└──────────┬──────────┘
│
↓
┌─────────────────────┐
│ Image Preprocessor │
│ - Resize (224x224) │
│ - Normalization │
│ - Batch support │
└──────────┬──────────┘
↓
┌─────────────────────┐
│ DINOv2 ViT-S/14 │
│ - Self-supervised │
│ - End-to-end │
└──────────┬──────────┘
↓
┌─────────────────────┐
│ L2 Normalization │
│ - Unit vector │
│ - Cosine = dot │
└──────────┬──────────┘
↓
┌─────────────────────┐
│ 384D Feature Vector│
└─────────────────────┘

Implementation Details​

# api/service/dinov2_feature_service.py
class DINOv2FeatureService:
"""DINOv2-based end-to-end feature extraction service

Replaces traditional global + fine-grained dual-channel architecture
with single unified ViT model for 5x faster inference.
"""

def __init__(self, model_name: str = "dinov2_vits14",
use_onnx: bool = True, device: str = "auto"):
self.model_name = model_name
self.use_onnx = use_onnx
self.device = self._get_device(device)
self.model = None
self.transform = None
self._model_lock = threading.Lock()
self._model_loaded = False

def extract_features(self, image: Image.Image,
normalize: bool = True) -> np.ndarray:
"""Extract DINOv2 feature vector from single image

Args:
image: PIL Image (any size)
normalize: Whether to L2 normalize the output vector

Returns:
384D numpy array of features
"""
self._ensure_model_loaded()

# Preprocess
img_tensor = self.transform(image).unsqueeze(0).to(self.device)

# Inference
with torch.no_grad():
features = self.model(img_tensor)

# Convert to numpy
features_np = features.cpu().numpy().squeeze()

# Normalize for cosine similarity
if normalize:
norm = np.linalg.norm(features_np)
if norm > 0:
features_np = features_np / norm

return features_np

def extract_batch_features(self, images: List[Image.Image],
normalize: bool = True) -> np.ndarray:
"""Extract features from batch of images (N x 384D matrix)

Optimized for database scanning operations - 10x faster than loop processing.
"""
self._ensure_model_loaded()

batch_tensors = torch.stack([
self.transform(img) for img in images
]).to(self.device)

with torch.no_grad():
features = self.model(batch_tensors)

features_np = features.cpu().numpy()

if normalize:
norms = np.linalg.norm(features_np, axis=1, keepdims=True)
features_np = features_np / np.maximum(norms, 1e-8)

return features_np

def compute_similarity(self, query_vector: np.ndarray,
product_vectors: np.ndarray) -> np.ndarray:
"""Compute cosine similarity using matrix multiplication

For normalized vectors, cosine similarity equals dot product.
Query: (384,) vector
Products: (N, 384) matrix
Result: (N,) similarity scores
"""
# Matrix multiplication for batch similarity - O(N) operation
similarities = product_vectors @ query_vector
return similarities

ONNX Deployment & Quantization​

For production deployment, export the DINOv2 model to ONNX format with INT8 quantization:

# scripts/export_dinov2_onnx.py
def export_to_onnx(model_name: str = "dinov2_vits14",
output_path: str = "models/dinov2_vits14.onnx",
quantize: bool = True):
"""Export DINOv2 model to ONNX with optional INT8 quantization

Args:
quantize: Apply INT8 dynamic range quantization for CPU optimization
- Reduces model size ~75%
- Improves inference speed ~2-3x
- Minimal accuracy loss (< 0.5%)
"""
model = torch.hub.load('facebookresearch/dinov2', model_name)
model.eval()

dummy_input = torch.randn(1, 3, 224, 224)

torch.onnx.export(
model,
dummy_input,
output_path,
export_params=True,
opset_version=14,
do_constant_folding=True,
input_names=['input'],
output_names=['output'],
dynamic_axes={
'input': {0: 'batch_size'},
'output': {0: 'batch_size'}
}
)

if quantize:
quantized_path = output_path.replace('.onnx', '_int8.onnx')
onnx_model = onnx.load(output_path)
quantized_model = quantize_dynamic(
onnx_model,
weight_type=QuantType.QInt8
)
onnx.save(quantized_model, quantized_path)

DINOv2 Integrated Verification Pipeline​

The main VerificationService now uses DINOv2 as its primary feature extraction backend, fully integrated with the cascaded filtering pipeline and all 9 early termination checkpoints.

Initialization & Architecture​

# api/service/verification_service.py
class VerificationService:
"""Unified verification service using DINOv2 feature extraction

Replaces traditional global + fine-grained dual-channel architecture
with single DINOv2 ViT model for 5x faster inference while maintaining
full cascaded filtering and early termination support.

Architecture:
- Barcode detection (ZBar + OpenCV) - EARLY TERMINATION
- DINOv2 feature extraction (ViT-S/14) - 384D unified vector
- OCR text extraction (Tesseract)
- Weighted fusion (DINOv2 weight: 0.8, OCR weight: 0.2)
"""

def __init__(self,
dinov2_feature_service,
ocr_service,
barcode_service,
db_session):
self.dinov2_feature_service = dinov2_feature_service
self.ocr_service = ocr_service
self.barcode_service = barcode_service
self.db = db_session
self.logger = logging.getLogger(__name__)

DINOv2 Scan Pipeline with Early Termination​

def scan_top_k(self, query_image: Image.Image,
top_k: int = 10,
similarity_threshold: float = 0.7,
enable_early_termination: bool = True) -> dict:
"""Scan using DINOv2 with all 9 early termination checkpoints

Follows openspec performance optimization standards.
"""
start_time = time.time()
stages_completed = []

# ========== STAGE 1: BARCODE DETECTION ==========
# Checkpoint 1: Non-background barcode exact match
barcodes = self.barcode_service.detect(query_image)
stages_completed.append("barcode_detection")

for barcode in barcodes:
matches = self._find_products_by_barcode(barcode.data)
if len(matches) >= 1 and enable_early_termination:
return {
"matches": matches[:top_k],
"total_count": len(matches),
"exit_reason": "barcode_exact",
"processing_time_ms": (time.time() - start_time) * 1000,
"early_termination": True,
"stages_completed": stages_completed
}

# Checkpoint 2: Background barcode mode matched >= top_k
if len(barcodes) >= top_k and enable_early_termination:
background_matches = self._find_background_barcode_matches(barcodes)
if len(background_matches) >= top_k:
return {
"matches": background_matches[:top_k],
"total_count": len(background_matches),
"exit_reason": "background_barcode",
"processing_time_ms": (time.time() - start_time) * 1000,
"early_termination": True,
"stages_completed": stages_completed
}

# Checkpoint 3: Exactly one barcode match remaining
barcode_matches = self._find_all_barcode_matches(barcodes)
if len(barcode_matches) == 1 and enable_early_termination:
return {
"matches": barcode_matches,
"total_count": 1,
"exit_reason": "barcode_single_candidate",
"processing_time_ms": (time.time() - start_time) * 1000,
"early_termination": True,
"stages_completed": stages_completed
}

# ========== STAGE 2: DINOv2 GLOBAL FEATURE FILTERING ==========
stages_completed.append("dinov2_feature_extraction")

# Extract DINOv2 feature vector - single forward pass (~50ms)
query_dinov2 = self.dinov2_feature_service.extract_features(query_image)

# Load all product DINOv2 vectors from database
all_products = self._load_all_product_features()

if not all_products:
return {
"matches": [],
"total_count": 0,
"exit_reason": "no_products",
"processing_time_ms": (time.time() - start_time) * 1000,
"early_termination": True,
"stages_completed": stages_completed
}

# BATCH SIMILARITY: Matrix multiplication optimization
# O(N) operation - 10x faster than loop-based calculation
product_matrix = np.vstack([p["dinov2_vector"] for p in all_products])
similarities = product_matrix @ query_dinov2

# Filter by similarity threshold
filtered_by_dinov2 = []
for i, product in enumerate(all_products):
sim = float(similarities[i])
if sim >= similarity_threshold:
filtered_by_dinov2.append({
"product": product,
"dinov2_similarity": sim
})

stages_completed.append("dinov2_filtering")

# Checkpoint 4: No candidates after DINOv2 filtering
if len(filtered_by_dinov2) == 0 and enable_early_termination:
return {
"matches": [],
"total_count": 0,
"exit_reason": "no_candidates",
"processing_time_ms": (time.time() - start_time) * 1000,
"early_termination": True,
"stages_completed": stages_completed
}

# Checkpoint 5: Exactly one candidate after DINOv2 filtering
if len(filtered_by_dinov2) == 1 and enable_early_termination:
return {
"matches": [{
"sku": filtered_by_dinov2[0]["product"]["sku"],
"similarity_score": filtered_by_dinov2[0]["dinov2_similarity"],
"match_method": "dinov2_single_candidate"
}],
"total_count": 1,
"exit_reason": "global_single_candidate",
"processing_time_ms": (time.time() - start_time) * 1000,
"early_termination": True,
"stages_completed": stages_completed
}

# Checkpoint 6: Filtered candidates count <= top_k
if len(filtered_by_dinov2) <= top_k and enable_early_termination:
matches = [{
"sku": item["product"]["sku"],
"similarity_score": item["dinov2_similarity"],
"match_method": "dinov2_filtered"
} for item in filtered_by_dinov2]

return {
"matches": matches,
"total_count": len(matches),
"exit_reason": "candidates_within_k",
"processing_time_ms": (time.time() - start_time) * 1000,
"early_termination": True,
"stages_completed": stages_completed
}

# ========== STAGE 3: OCR TEXT FINGERPRINT MATCHING ==========
stages_completed.append("ocr_extraction")

query_ocr_text = self.ocr_service.extract_text(query_image)
query_text_hash = self._compute_text_fingerprint(query_ocr_text)

# Checkpoint 7: Exact OCR text hash match
text_hash_matches = []
for item in filtered_by_dinov2:
product_text_hash = item["product"].get("text_hash")
if product_text_hash and product_text_hash == query_text_hash:
text_hash_matches.append(item)

if len(text_hash_matches) >= 1 and enable_early_termination:
matches = [{
"sku": item["product"]["sku"],
"similarity_score": 1.0,
"match_method": "text_hash_exact"
} for item in text_hash_matches[:top_k]]

return {
"matches": matches,
"total_count": len(matches),
"exit_reason": "text_hash_exact",
"processing_time_ms": (time.time() - start_time) * 1000,
"early_termination": True,
"stages_completed": stages_completed
}

# ========== STAGE 4: HIGH-CONFIDENCE VISUAL MATCH ==========
stages_completed.append("high_confidence_check")

# Checkpoint 8: Single high-confidence visual match (>= 0.9)
high_conf_matches = [
item for item in filtered_by_dinov2
if item["dinov2_similarity"] >= 0.9
]

if len(high_conf_matches) == 1 and enable_early_termination:
return {
"matches": [{
"sku": high_conf_matches[0]["product"]["sku"],
"similarity_score": high_conf_matches[0]["dinov2_similarity"],
"match_method": "visual_high_confidence"
}],
"total_count": 1,
"exit_reason": "visual_high_confidence",
"processing_time_ms": (time.time() - start_time) * 1000,
"early_termination": True,
"stages_completed": stages_completed
}

# ========== STAGE 5: FULL VERIFICATION LOOP ==========
stages_completed.append("full_verification")

# Compute final similarity with OCR fusion
final_results = []
for item in filtered_by_dinov2:
product = item["product"]

# Weighted fusion: DINOv2 (0.8) + OCR (0.2)
product_ocr_data = product.get("ocr_data", "")
ocr_similarity = self.ocr_service.compare_text(
query_ocr_text, product_ocr_data
)

final_similarity = (
item["dinov2_similarity"] * 0.8 +
ocr_similarity * 0.2
)

final_results.append({
"sku": product["sku"],
"similarity_score": final_similarity,
"match_method": "dinov2_ocr_fusion",
"dinov2_similarity": item["dinov2_similarity"],
"ocr_similarity": ocr_similarity
})

# Sort and take top K
final_results.sort(key=lambda x: x["similarity_score"], reverse=True)
top_results = final_results[:top_k]

# Checkpoint 9: Loop early break - collected enough matches
early_break = len(top_results) >= top_k and enable_early_termination

return {
"matches": top_results,
"total_count": len(final_results),
"exit_reason": "loop_early_break" if early_break else "full_pipeline",
"processing_time_ms": (time.time() - start_time) * 1000,
"early_termination": early_break,
"stages_completed": stages_completed
}

Migration Guide from Traditional Features​

Step 1: Export DINOv2 ONNX Model​

python scripts/export_dinov2_onnx.py --model dinov2_vits14 --quantize

Step 2: Migrate Database Schema​

-- Add DINOv2 vector column
ALTER TABLE product_features
ADD COLUMN dinov2_vector vector(384);

-- Create vector index
CREATE INDEX product_features_dinov2_vector_idx
ON product_features
USING ivfflat (dinov2_vector vector_cosine_ops)
WITH (lists = 100);

Step 3: Backfill Existing Features​

# scripts/backfill_dinov2_features.py
def backfill_features():
"""Backfill DINOv2 features for existing product images"""
service = DINOv2FeatureService(use_onnx=True)

products = db.query(ProductFeature).filter(
ProductFeature.dinov2_vector.is_(None)
).all()

for product in products:
image = download_image(product.image_url)
features = service.extract_features(image)
product.dinov2_vector = features
db.commit()

Step 4: Switch Verification Pipeline​

# In verification service initialization
if os.getenv('USE_DINOV2', 'false').lower() == 'true':
dinov2_service = DINOv2FeatureService()
verification_service = DINOv2VerificationService(
dinov2_service, db, ocr_service, barcode_service
)
else:
# Fallback to traditional pipeline
feature_service = FeatureExtractionService()
verification_service = VerificationService(
feature_service, fine_grained_service, ocr_service, barcode_service
)

Performance Optimization Techniques​

  1. Matrix Multiplication Batch Similarity:

    • Traditional loop: O(N * D) time complexity
    • Matrix multiplication: O(N * D) with highly optimized BLAS
    • Practical speedup: 10x faster for large N
  2. ONNX Runtime Optimization:

    • CPU: MKL-DNN / OpenVINO backend
    • GPU: CUDA / TensorRT backend
    • Quantization: INT8 for CPU, FP16 for GPU
  3. Lazy Loading with Double-Checked Locking:

    def _ensure_model_loaded(self):
    if not self._model_loaded:
    with self._model_lock:
    if not self._model_loaded:
    self._load_model()
    self._model_loaded = True
  4. Vector Normalization Pre-computation:

    • Normalize at feature extraction time, not at comparison
    • Cosine similarity = dot product for normalized vectors
    • Saves sqrt operations during verification

Cascaded Filtering & Early Termination Architecture​

Overview​

Following the openspec "spec-driven development" philosophy, the product trace scan API implements a cascaded filtering pipeline with 9 early termination checkpoints to maximize performance while maintaining accuracy.

Pipeline Stages​

┌─────────────────────────┐
│ Input Query Image │
└────────────┬────────────┘
│
┌───────────────────────┼───────────────────────┐
↓ ↓
┌────────────────────┐ ┌────────────────────┐
│ Barcode Matching │←─── STAGE 1 │ 0 or 1 match? │
│ (< 10ms) │ │ → RETURN NOW │
└────────┬───────────┘ └────────────────────┘
│
├───────────────────────────────────────────────────────┐
↓ ↓
┌────────────────────┐ ┌────────────────────┐
│ Global Feature │←─── STAGE 2 │ 0 candidates? │
│ Filtering │ │ → RETURN NOW │
│ (< 100ms) │ └────────────────────┘
└────────┬───────────┘
│
├───────────────────────────────────────────────────────┐
↓ ↓
┌────────────────────┐ ┌────────────────────┐
│ 1 Candidate Left? │←─── CHECKPOINT 3 │ ≤ top_k? │
│ → RETURN NOW │ │ → RETURN NOW │
└────────────────────┘ └────────────────────┘
│
├───────────────────────────────────────────────────────┐
↓ ↓
┌────────────────────┐ ┌────────────────────┐
│ OCR Text Hash │←─── STAGE 3 │ ≥1 hash match? │
│ Matching │ │ → RETURN NOW │
└────────┬───────────┘ └────────────────────┘
│
├───────────────────────────────────────────────────────┐
↓ ↓
┌────────────────────┐ ┌────────────────────┐
│ High-Confidence │←─── STAGE 4 │ 1 high-confidence? │
│ Visual Matching │ │ → RETURN NOW │
└────────┬───────────┘ └────────────────────┘
│
↓
┌────────────────────┐
│ Fine-Grained Loop │←─── STAGE 5
│ with Early Break │ → Break when matches ≥ top_k
└────────┬───────────┘
│
↓
┌────────────────────┐
│ Final Results │
│ (Only reached if │
│ no early exit) │
└────────────────────┘

Early Termination Checkpoints​

Following openspec's performance-first design principle, the verification service implements 9 distinct early termination checkpoints to minimize unnecessary computation:

CheckpointLocationConditionPerformance Impact
1Barcode stageNon-background barcode matching found ≥ 1 productFastest path (< 10ms), highest priority
2Background barcode stageBackground barcode matching ≥ top_k productsFast return for ambiguous barcode scenarios
3Background barcode stageExactly 1 barcode match remainingSingle candidate after barcode detection
4Global filter stage0 candidates remaining after filteringSaves all downstream processing
5Global filter stageExactly 1 candidate remainingSaves OCR + fine-grained stages entirely
6Global filter stageCandidates ≤ top_k after filteringDirect return without heavy computation
7OCR hash stage≥1 matching text hash foundSkips fine-grained visual comparison stage
8High-confidence stageExactly 1 match with similarity ≥ 0.9Fast verification for unambiguous visual matches
9Fine-grained loopMatches collected ≥ top_kBreaks loop early, saves unnecessary iterations

Async Analysis Pool Architecture​

Overview​

The Async Analysis Pool manages background processing for feature extraction with multiple executor pools and timeout protection:

┌─────────────────────────────────────────────────────────┐
│ AsyncAnalysisPool │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Barcode │ │ OCR │ │ Feature │ │
│ │ Executor │ │ Executor │ │ Executor │ │
│ │ (8 workers) │ │ (4 workers) │ │ (2 workers) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ ↓ │
│ ┌───────────────────────────┐ │
│ │ Task Queue │ │
│ │ (asyncio.Queue) │ │
│ └─────────────┬─────────────┘ │
│ ↓ │
│ ┌───────────────────────────┐ │
│ │ Status Tracker │ │
│ │ (progress, sub-tasks) │ │
│ └───────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────┐ │
│ │ Timeout Monitor │ │
│ │ (300s max processing) │ │
│ └───────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

Implementation Details​

# api/service/async_analysis_pool.py
class AsyncAnalysisPool:
def __init__(self, feature_service, fine_grained_service,
ocr_service, barcode_service):
self.feature_service = feature_service
self.fine_grained_service = fine_grained_service
self.ocr_service = ocr_service
self.barcode_service = barcode_service

# Executor pools
self.barcode_executor = ThreadPoolExecutor(max_workers=8)
self.ocr_executor = ThreadPoolExecutor(max_workers=4)
self.feature_executor = ThreadPoolExecutor(max_workers=2)

# Concurrency limiting - follows openspec degradation strategy
self.max_concurrent_no_barcode = int(os.getenv(
'MAX_CONCURRENT_NO_BARCODE', '3'
))
self.current_no_barcode_count = 0
self._concurrency_lock = asyncio.Lock()

# Task tracking
self._task_status = {} # record_id -> ProcessingStatus
self._task_futures = {} # record_id -> Future
self._lock = asyncio.Lock()
self._timeout_monitor_task = None

async def submit_task(self, record_id, images):
"""Submit a new analysis task with concurrency control"""
async with self._lock:
self._task_status[record_id] = ProcessingStatus(
status="pending",
progress=0,
message="Task queued"
)

# Apply degradation strategy for no-barcode scenarios
if await self._should_degrade():
raise ServiceUnavailable(
"High concurrency detected, please try again later"
)

# Submit to thread pool
future = self._loop.run_in_executor(
None,
self._process_task,
record_id,
images
)
self._task_futures[record_id] = future

def _process_task(self, record_id, images):
"""Process in thread pool - parallel feature extraction"""
loop = asyncio.new_event_loop()

# Run all extractions in parallel
results = loop.run_until_complete(asyncio.gather(
self._extract_barcodes(images),
self._extract_ocr(images),
self._extract_features(images),
self._extract_fine_grained(images)
))

barcode_result, ocr_result, feature_result, fine_result = results

# Fuse features
fused = self._fuse_features(*results)

# Update database
self._save_results(record_id, fused)

# Update status
self._update_status(record_id, "completed", progress=100)

async def get_status(self, record_id):
"""Get current processing status"""
async with self._lock:
return self._task_status.get(record_id)

Database Schema​

Core Tables​

-- Users table
CREATE TABLE users (
id SERIAL PRIMARY KEY,
casdoor_id VARCHAR(255) UNIQUE,
username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
full_name VARCHAR(255),
password_hash VARCHAR(255),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- API Tokens
CREATE TABLE tokens (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
name VARCHAR(255),
token VARCHAR(255) UNIQUE NOT NULL,
scopes JSONB,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP
);

-- Product Features (with vector support)
CREATE TABLE product_features (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
sku VARCHAR(255) NOT NULL,
identification_code VARCHAR(255) NOT NULL,
image_url VARCHAR(2048),
image_hash VARCHAR(64),
product_name VARCHAR(255),
description TEXT,
category VARCHAR(100),

-- Feature vectors
global_vector vector(512),
fine_grained_vector vector(1024),

-- Extracted data
barcode_data JSONB,
ocr_data JSONB,
text_hash VARCHAR(64),

-- Processing flags
has_barcode BOOLEAN DEFAULT FALSE,
has_ocr BOOLEAN DEFAULT FALSE,
has_fusion BOOLEAN DEFAULT FALSE,

created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

UNIQUE(user_id, sku, identification_code)
);

-- Verification History
CREATE TABLE verification_history (
id SERIAL PRIMARY KEY,
product_feature_id INTEGER REFERENCES product_features(id),
query_image_url VARCHAR(2048),
similarity_score FLOAT,
threshold FLOAT,
is_verified BOOLEAN,
verification_method VARCHAR(50), -- barcode, global, fine_grained
match_details JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Vector index for ANN search
CREATE INDEX product_features_global_vector_idx
ON product_features
USING ivfflat (global_vector vector_cosine_ops)
WITH (lists = 100);

Database Migrations​

The system uses automatic migration for missing fields:

# api/core/entity/auto_migration.py
def auto_migrate_missing_fields(engine, base):
"""Automatically detect and add missing columns"""
inspector = inspect(engine)

for table_name, table in base.metadata.tables.items():
existing_columns = {
col['name'] for col in inspector.get_columns(table_name)
}

for column in table.columns:
if column.name not in existing_columns:
# Add missing column
with engine.connect() as conn:
conn.execute(text(f"""
ALTER TABLE {table_name}
ADD COLUMN {column.name} {column.type.compile(engine.dialect)}
"""))
conn.commit()

# Also create missing indexes
for table_name, table in base.metadata.tables.items():
existing_indexes = {
idx['name'] for idx in inspector.get_indexes(table_name)
}

for index in table.indexes:
if index.name not in existing_indexes:
index.create(engine)

Authentication System​

Dual Authentication Methods​

# api/utils/auth.py
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
) -> User:
"""
Unified authentication supporting:
1. JWT Bearer tokens (from Casdoor)
2. API Key tokens (from tokens table)
"""
token = credentials.credentials

# Try JWT first
try:
casdoor_id = verify_token(token)
if casdoor_id:
user = db.query(User).filter(
User.casdoor_id == casdoor_id
).first()
if user and user.is_active:
return user
except JWTError:
pass

# Try API Key
api_token = db.query(Token).filter(
Token.token == token,
Token.is_active == True
).first()

if api_token:
user = db.query(User).get(api_token.user_id)
if user and user.is_active:
return user

raise HTTPException(
status_code=401,
detail="Invalid authentication credentials"
)

JWT Token Flow​

# api/utils/jwt.py
def create_access_token(data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(
to_encode,
SECRET_KEY,
algorithm=ALGORITHM
)
return encoded_jwt

def verify_token(token: str) -> Optional[str]:
try:
payload = jwt.decode(
token,
SECRET_KEY,
algorithms=[ALGORITHM]
)
casdoor_id: str = payload.get("sub")
if casdoor_id is None:
return None
return casdoor_id
except JWTError:
return None

openspec Test Organization Specification​

Following the Superpowers microservices architecture principles, the project adheres to a strict test organization convention.

Directory Structure​

tests/
├── unit/ # Unit tests for individual components
│ ├── test_packing_algorithm.py
│ ├── test_feature_extraction.py
│ ├── test_barcode_service.py
│ └── test_ocr_service.py
├── integration/ # Integration tests for API endpoints and workflows
│ ├── test_comprehensive_validation.py
│ ├── test_product_trace_flow.py
│ └── test_cascaded_filtering.py
├── api/ # API-specific tests using TestClient or requests
│ ├── test_api_availability.py
│ ├── test_api_response_structure.py
│ └── test_endpoints.py
├── e2e/ # End-to-end tests for complete user flows
│ └── test_user_workflow.py
├── data/ # Test data fixtures and JSON files
│ ├── test_skus.json
│ ├── test_images/
│ └── user_test_request.json
├── reports/ # Generated test reports and benchmark results
│ ├── packing_api_comprehensive_analysis.md
│ └── intelligent_packing_test_report.md
├── static/ # Static test resources (logs, images, etc.)
│ └── logs/
└── archive/ # Deprecated legacy test files for reference
└── old_test_files/

scripts/
├── testing/ # Debug scripts, stress tests, and development utilities
│ ├── stress_test.py
│ ├── debug_simple.py
│ └── test_with_testjson.py
└── deployment/ # Deployment validation and health check scripts
└── deploy_check.py

File Naming Conventions​

File TypePatternExample
Formal test filestest_*.pytest_packing_calculate.py
Development/debug scriptsdebug_*.pydebug_feature_extraction.py
Performance/stress testsstress_*.pystress_scan_api.py

File Classification Rules​

Formal Tests (tests/):

  • Packing algorithm tests → tests/unit/
  • API integration tests → tests/integration/
  • Product trace tests → tests/integration/
  • Feature extraction tests → tests/unit/
  • Database tests → tests/integration/

Debug Utilities (scripts/testing/):

  • All debug_*.py files
  • Simple verification scripts
  • Model weight inspection tools

Legacy Archive (tests/archive/):

  • Deprecated test files
  • Obsolete debug scripts

Test Execution Commands​

# Unit tests only
pytest tests/unit/ -v

# Integration tests with timeout
pytest tests/integration/ -v --timeout=60

# API-specific tests
pytest tests/api/ -v

# All tests with short tracebacks
pytest tests/ -v --tb=short

# Generate test report
pytest tests/ --json=tests/reports/test_results.json

Superpowers Test Conventions​

  1. Hermetic Tests: All tests must be self-contained and independent
  2. FastAPI Integration: Use TestClient for FastAPI integration tests
  3. Performance Benchmarks: Include performance benchmarks for critical paths
  4. Machine-Readable Reports: Generate JSON reports for CI/CD pipelines
  5. Test Data Management: Maintain test data fixtures in tests/data/
  6. Archive Policy: Archive deprecated tests instead of deleting
  7. Separation of Concerns: Keep debug utilities separate from formal test suites

Development Workflow​

Local Development Setup​

# 1. Clone the repository
git clone <repository-url>
cd api/backend

# 2. Create virtual environment
python -m venv .venv
source .venv/bin/activate # Linux/Mac
.venv\Scripts\activate # Windows

# 3. Install dependencies
pip install -r requirements.txt

# 4. Set up environment variables
cp .env.example .env
# Edit .env with your configuration

# 5. Start PostgreSQL and Redis
docker-compose up -d postgres redis

# 6. Run the application
python main.py

Code Quality Standards​

# Run type checking
mypy api/

# Run linting
flake8 api/ --max-line-length=120

# Format code
black api/

# Run tests before commit
pytest tests/unit/ -v

CI/CD Pipeline​

The project follows a standard CI/CD workflow:

  1. Push: Trigger CI pipeline
  2. Lint: Code quality checks
  3. Test: Run unit and integration tests
  4. Build: Docker image creation
  5. Deploy: Staging → Production

Feature Branch Workflow​

main (production)
↑
develop (integration)
↑
feature/xxx (feature branches)
↓
Submit PR → Code Review → Merge to develop