Files
component-system/design_docs/qr_storage_system.md
2025-09-14 15:20:25 +02:00

10 KiB

QR Code Storage Location System Design

Overview

Implement a hierarchical storage location system with QR code generation and scanning capabilities to enable quick component location entry and filtering.

Database Schema

1. Storage Locations Table

CREATE TABLE storage_locations (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  description TEXT,
  qr_code VARCHAR(100) UNIQUE NOT NULL,
  parent_id INTEGER REFERENCES storage_locations(id),
  level INTEGER NOT NULL DEFAULT 0,
  path TEXT NOT NULL, -- Materialized path: "shelf1/drawer2/box3"
  is_active BOOLEAN DEFAULT TRUE,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_storage_locations_parent_id ON storage_locations(parent_id);
CREATE INDEX idx_storage_locations_qr_code ON storage_locations(qr_code);
CREATE INDEX idx_storage_locations_path ON storage_locations USING gin(path gin_trgm_ops);
CREATE UNIQUE INDEX idx_storage_locations_name_parent ON storage_locations(name, parent_id);

2. Modified Components Table

-- Migration to add storage_location_id to components
ALTER TABLE components 
ADD COLUMN storage_location_id INTEGER REFERENCES storage_locations(id),
ADD COLUMN legacy_position VARCHAR(255); -- Keep old position data for migration

-- Move existing position data to legacy_position
UPDATE components SET legacy_position = position;

QR Code Format Design

Hierarchical QR Code Strategy

To avoid confusion with multiple QR codes in the same image, use a hierarchical encoding strategy:

Format: SL:{level}:{unique_id}:{parent_path_hash}
Examples:
- Shelf: "SL:1:ABC123:ROOT"
- Drawer: "SL:2:DEF456:ABC123"  
- Box: "SL:3:GHI789:DEF456"

QR Code Components:

  • SL: Storage Location prefix
  • Level: Hierarchy level (1=shelf, 2=drawer, 3=box, etc.)
  • Unique ID: Short alphanumeric code (6-8 chars)
  • Parent Hash: Reference to parent location

Multi-QR Code Detection Strategy

1. Spatial Filtering

When multiple QR codes detected:
1. Calculate distance between codes
2. If distance < threshold:
   - Prefer higher hierarchy level (lower number)
   - Present disambiguation UI
3. If distance > threshold:
   - Allow user to tap/select desired code

2. Context-Aware Selection

Selection Priority:
1. Exact level match (if user scanning for specific level)
2. Deepest level in hierarchy (most specific location)
3. Recently used locations (user preference learning)
4. Manual disambiguation prompt

3. Visual Feedback

Camera Overlay:
- Draw bounding boxes around each detected QR code
- Color-code by hierarchy level
- Show location path preview on hover/tap
- Highlight "best match" with different color

Implementation Components

1. Elixir Modules

Storage Location Schema

defmodule ComponentsElixir.Inventory.StorageLocation do
  use Ecto.Schema
  import Ecto.Changeset
  
  schema "storage_locations" do
    field :name, :string
    field :description, :string
    field :qr_code, :string
    field :level, :integer, default: 0
    field :path, :string
    field :is_active, :boolean, default: true
    
    belongs_to :parent, __MODULE__
    has_many :children, __MODULE__, foreign_key: :parent_id
    has_many :components, Component
    
    timestamps()
  end
end

QR Code Generation

defmodule ComponentsElixir.QRCode do
  def generate_storage_qr(location) do
    qr_data = "SL:#{location.level}:#{location.qr_code}:#{parent_hash(location)}"
    
    # Use :qr_code library to generate QR image
    :qr_code.encode(qr_data)
    |> :qr_code.png()
  end
  
  def parse_storage_qr(qr_string) do
    case String.split(qr_string, ":") do
      ["SL", level, code, parent] ->
        {:ok, %{level: level, code: code, parent: parent}}
      _ ->
        {:error, :invalid_format}
    end
  end
end

2. Phoenix LiveView Components

QR Scanner Component

defmodule ComponentsElixirWeb.QRScannerLive do
  use ComponentsElixirWeb, :live_view
  
  def mount(_params, _session, socket) do
    socket = 
      socket
      |> assign(:scanning, false)
      |> assign(:detected_codes, [])
      |> assign(:selected_location, nil)
      |> allow_upload(:qr_scan, 
          accept: ~w(.jpg .jpeg .png), 
          max_entries: 1,
          auto_upload: true)
    
    {:ok, socket}
  end
  
  def handle_event("start_scan", _, socket) do
    {:noreply, assign(socket, :scanning, true)}
  end
  
  def handle_event("qr_detected", %{"codes" => codes}, socket) do
    parsed_codes = Enum.map(codes, &parse_and_resolve_location/1)
    
    socket = 
      socket
      |> assign(:detected_codes, parsed_codes)
      |> maybe_auto_select_location(parsed_codes)
    
    {:noreply, socket}
  end
  
  defp maybe_auto_select_location(socket, [single_code]) do
    assign(socket, :selected_location, single_code)
  end
  
  defp maybe_auto_select_location(socket, multiple_codes) do
    # Show disambiguation UI
    assign(socket, :selected_location, nil)
  end
end

3. JavaScript QR Detection

Camera Integration

// assets/js/qr_scanner.js
import jsQR from "jsqr";

export const QRScanner = {
  mounted() {
    this.video = this.el.querySelector('video');
    this.canvas = this.el.querySelector('canvas');
    this.context = this.canvas.getContext('2d');
    
    this.startCamera();
    this.scanLoop();
  },
  
  async startCamera() {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        video: { 
          facingMode: 'environment', // Use back camera
          width: { ideal: 1280 },
          height: { ideal: 720 }
        }
      });
      this.video.srcObject = stream;
    } catch (err) {
      console.error('Camera access denied:', err);
    }
  },
  
  scanLoop() {
    if (this.video.readyState === this.video.HAVE_ENOUGH_DATA) {
      this.canvas.width = this.video.videoWidth;
      this.canvas.height = this.video.videoHeight;
      
      this.context.drawImage(this.video, 0, 0);
      const imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
      
      // Detect multiple QR codes
      const codes = this.detectMultipleQRCodes(imageData);
      
      if (codes.length > 0) {
        this.pushEvent("qr_detected", { codes: codes });
      }
    }
    
    requestAnimationFrame(() => this.scanLoop());
  },
  
  detectMultipleQRCodes(imageData) {
    // Implementation for detecting multiple QR codes
    // This is a simplified version - you'd need a more robust library
    const detected = [];
    
    // Scan in grid pattern to find multiple codes
    const gridSize = 4;
    const width = imageData.width / gridSize;
    const height = imageData.height / gridSize;
    
    for (let x = 0; x < gridSize; x++) {
      for (let y = 0; y < gridSize; y++) {
        const subImageData = this.getSubImageData(
          imageData, 
          x * width, 
          y * height, 
          width, 
          height
        );
        
        const code = jsQR(subImageData.data, subImageData.width, subImageData.height);
        if (code && this.isStorageLocationQR(code.data)) {
          detected.push({
            data: code.data,
            location: { x: x * width, y: y * height },
            corners: code.location
          });
        }
      }
    }
    
    return this.filterDuplicates(detected);
  },
  
  isStorageLocationQR(data) {
    return data.startsWith('SL:');
  }
};

User Experience Flow

1. Adding Components with QR Scan

1. User clicks "Add Component" 
2. Position field shows camera icon
3. Click camera → QR scanner opens
4. Scan storage location QR code
5. If multiple codes detected:
   - Show overlay with detected locations
   - User taps to select specific location
6. Location path auto-filled: "Shelf A → Drawer 2 → Box 5"
7. Component saved with storage_location_id

2. Filtering by Storage Location

1. Component list shows location filter dropdown
2. Filter options show hierarchical tree:
   ├── Shelf A
   │   ├── Drawer 1
   │   │   ├── Box 1
   │   │   └── Box 2
   │   └── Drawer 2
   └── Shelf B
3. Select any level to filter components
4. Breadcrumb shows: "Shelf A → Drawer 2" (23 components)

3. Location Management

1. New "Storage Locations" section in admin
2. Add/edit locations with auto QR generation
3. Print QR labels with location hierarchy
4. Bulk QR code generation for initial setup

Handling Multiple QR Codes in Same Image

Strategy 1: Spatial Separation

  • Calculate euclidean distance between QR code centers
  • If distance < 100px → show disambiguation
  • If distance > 100px → allow selection by tap

Strategy 2: Hierarchy Preference

  • Always prefer deepest level (most specific)
  • If same level → show all options
  • Color-code by hierarchy level in UI

Strategy 3: Machine Learning (Future)

  • Learn user selection patterns
  • Predict most likely intended QR code
  • Still allow manual override

Migration Strategy

Phase 1: Add Storage Locations

  1. Create migration for storage_locations table
  2. Add storage_location_id to components
  3. Create admin interface for location management

Phase 2: QR Code Generation

  1. Add QR code generation to location creation
  2. Implement QR code printing/export functionality
  3. Generate codes for existing locations

Phase 3: QR Code Scanning

  1. Add camera permissions and JavaScript QR scanner
  2. Implement single QR code detection first
  3. Add multi-QR detection and disambiguation

Phase 4: Advanced Features

  1. Location-based filtering and search
  2. Bulk operations by location
  3. Location analytics and optimization

Technical Dependencies

Elixir Dependencies

# mix.exs
{:qr_code, "~> 3.1"},              # QR code generation
{:image, "~> 0.37"},               # Image processing
{:ex_image_info, "~> 0.2.4"}      # Image metadata

JavaScript Dependencies

// package.json
{
  "jsqr": "^1.4.0",
  "qr-scanner": "^1.4.2"
}

Database Indexes for Performance

-- Fast location lookups
CREATE INDEX idx_components_storage_location_id ON components(storage_location_id);

-- Hierarchical queries
CREATE INDEX idx_storage_locations_path_gin ON storage_locations USING gin(path gin_trgm_ops);

-- QR code uniqueness and fast lookup
CREATE UNIQUE INDEX idx_storage_locations_qr_code ON storage_locations(qr_code);

This design provides a robust foundation for QR code-based storage management while handling the complexity of multiple codes in the same image through spatial analysis and user interaction patterns.