Skip to 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

python
# 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:

python
# 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

python
# 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:

python
# 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

python
# 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

python
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

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

Step 2: Migrate Database Schema

sql
-- 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

python
# 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

python
# 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:

    python
    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

python
# 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

sql
-- 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:

python
# 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

python
# 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

python
# 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

bash
# 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

bash
# 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

bash
# 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