diff --git a/api/products/migrations/0108_add_verification_model_to_codebatch.py b/api/products/migrations/0108_add_verification_model_to_codebatch.py
new file mode 100644
index 0000000..370ac34
--- /dev/null
+++ b/api/products/migrations/0108_add_verification_model_to_codebatch.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.23 on 2025-11-25 22:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0107_chatsession_product'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='codebatch',
+ name='verification_model',
+ field=models.TextField(blank=True, null=True, verbose_name='验证模型名称(可选,留空使用默认模型)'),
+ ),
+ ]
diff --git a/api/products/migrations/0109_add_verification_model_to_scandata.py b/api/products/migrations/0109_add_verification_model_to_scandata.py
new file mode 100644
index 0000000..5772881
--- /dev/null
+++ b/api/products/migrations/0109_add_verification_model_to_scandata.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.23 on 2025-11-25 22:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0108_add_verification_model_to_codebatch'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='scandata',
+ name='verification_model',
+ field=models.TextField(blank=True, null=True, verbose_name='验证模型名称'),
+ ),
+ ]
diff --git a/api/products/migrations/0110_add_verification_model.py b/api/products/migrations/0110_add_verification_model.py
new file mode 100644
index 0000000..4c42882
--- /dev/null
+++ b/api/products/migrations/0110_add_verification_model.py
@@ -0,0 +1,27 @@
+# Generated by Django 4.2.23 on 2025-11-25 22:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0109_add_verification_model_to_scandata'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='VerificationModel',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(db_index=True, max_length=255, unique=True, verbose_name='模型名称')),
+ ('description', models.TextField(blank=True, null=True, verbose_name='描述')),
+ ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
+ ('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
+ ],
+ options={
+ 'verbose_name': '验证模型',
+ 'verbose_name_plural': '验证模型',
+ },
+ ),
+ ]
diff --git a/api/products/models.py b/api/products/models.py
index a132336..6363c8b 100644
--- a/api/products/models.py
+++ b/api/products/models.py
@@ -33,6 +33,21 @@ class GlobalConfig(models.Model):
return json.loads(self.value)
return None
+class VerificationModel(models.Model):
+ name = models.CharField(max_length=255, db_index=True, unique=True,
+ verbose_name="模型名称")
+ description = models.TextField(null=True, blank=True,
+ verbose_name="描述")
+ created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
+ updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
+
+ def __str__(self):
+ return self.name
+
+ class Meta:
+ verbose_name = "验证模型"
+ verbose_name_plural = "验证模型"
+
class Tenant(models.Model):
username = models.CharField(max_length=128,
unique=True, db_index=True,
@@ -179,6 +194,8 @@ class CodeBatch(models.Model):
verbose_name="小程序入口路径(可选)")
mini_prog_entry_env_version = models.CharField(max_length=128, null=True, blank=True, default='release',
verbose_name="小程序入口环境版本(可选), 默认release。如为trial则进入体验版")
+ verification_model = models.TextField(null=True, blank=True,
+ verbose_name="验证模型名称(可选,留空使用默认模型)")
def gen_code(self, n):
a = 10 ** (self.num_digits - 1)
@@ -270,6 +287,7 @@ class ScanData(models.Model):
phone_model = models.TextField(null=True, verbose_name="手机型号")
client_log = models.TextField(null=True, blank=True, verbose_name="采集日志")
labels = models.TextField(default="", blank=True, verbose_name="标签")
+ verification_model = models.TextField(null=True, blank=True, verbose_name="验证模型名称")
class SystemLog(models.Model):
datetime = models.DateTimeField(auto_now_add=True, db_index=True)
diff --git a/api/products/views.py b/api/products/views.py
index 291d7ec..2929b59 100644
--- a/api/products/views.py
+++ b/api/products/views.py
@@ -186,6 +186,16 @@ class GlobalConfigResource(BaseResource):
default_query_field = ['name']
auth_role = 'admin'
+class VerificationModelResource(BaseResource):
+ class Meta:
+ queryset = VerificationModel.objects.all().order_by('-pk')
+ resource_name = 'verification-model'
+ authorization = BaseAuthorization()
+ serializer = BaseSerializer()
+
+ default_query_field = ['name', 'description']
+ auth_role = 'admin'
+
class ArticleResource(BaseResource):
class Meta:
queryset = Article.objects.all().order_by("-pk")
@@ -405,7 +415,7 @@ class ScanDataResource(BaseResource):
'image',
'location', 'message',
'code', 'phone_model',
- 'labels'
+ 'labels', 'verification_model'
]
def dehydrate(self, bundle):
@@ -665,19 +675,28 @@ def get_code_from_url(url):
class QrVerifyView(BaseView):
name = 'qr-verify'
- def do_v5_qr_verify(self, imgs, messages):
+ def do_v5_qr_verify(self, imgs, messages, model_name=None):
files = {}
for i, img in enumerate(imgs):
files[f'frame_{i}_{img[1]}'] = img[0]
+
+ data = {}
+ if model_name:
+ data['model'] = model_name
+
resp = requests.post(
"https://themblem.com/api/v5/qr_verify",
files=files,
+ data=data,
)
rd = resp.json()
messages.append(f"v5 qr_verify response: {rd}")
ok = rd.get("result", {}).get("predicted_class", 0) == 1
if not ok:
raise Exception("v5 qr verify failed")
+
+ # Return the model name that was used (for tracking)
+ return model_name
def post(self, request):
image_name = ''
@@ -720,9 +739,12 @@ class QrVerifyView(BaseView):
t = threading.Thread(target=oss_put, args=(image_name, img_data))
t.run()
if sc.batch.feature_comparison_threshold > 0.01:
- self.do_v5_qr_verify(imgs, messages)
+ model_name = sc.batch.verification_model if sc.batch.verification_model else None
+ used_model = self.do_v5_qr_verify(imgs, messages, model_name=model_name)
+ sd.verification_model = used_model if used_model else None
else:
messages.append("skip v5 qr verify")
+ sd.verification_model = None
sd.succeeded = True
article_id = None
if product.article:
diff --git a/doc/batch-model-config-analysis.md b/doc/batch-model-config-analysis.md
new file mode 100644
index 0000000..ba85777
--- /dev/null
+++ b/doc/batch-model-config-analysis.md
@@ -0,0 +1,262 @@
+# Analysis: Admin Configuration for Model Selection per Batch
+
+## Executive Summary
+
+This document analyzes how to add a feature that allows admins to configure which AI model to use for each batch during QR verification. Currently, the system uses a default model for all batches when `feature_comparison_threshold > 0.01`.
+
+## Current State
+
+### 1. Batch Model (`CodeBatch`)
+- Location: `api/products/models.py:162`
+- Key field: `feature_comparison_threshold` (FloatField, default=0.0)
+ - When `> 0.01`, triggers v5 QR verification
+ - When `<= 0.01`, skips v5 QR verification
+
+### 2. QR Verification Flow
+- Location: `api/products/views.py:665-680`
+- Method: `QrVerifyView.do_v5_qr_verify()`
+- Current behavior:
+ - Makes POST request to `https://themblem.com/api/v5/qr_verify`
+ - Sends image files only
+ - **Does NOT specify a model parameter**
+ - Server uses default model: `best_model_ep82_pos98.94_neg96.13_20250720_222102.pt`
+
+### 3. V5 QR Verify Server
+- Location: `emblem5/ai/server.py:83-114`
+- Endpoint: `/api/v5/qr_verify`
+- Model selection:
+ - Accepts `model` parameter from form data: `fd.get('model', default_model)`
+ - Default model defined in `emblem5/ai/common.py:42`
+ - Models are downloaded from OSS bucket `emblem-models` if not cached locally
+
+### 4. Admin UI
+- Location: `web/src/views/code-batch.vue`
+- Current editable fields include `feature_comparison_threshold`
+- Uses `GenericManager` component for CRUD operations
+
+## Requirements
+
+1. **Database**: Add a field to `CodeBatch` to store the model identifier
+2. **API**: Pass the model parameter to v5 QR verify endpoint when specified
+3. **UI**: Allow admins to configure the model per batch
+4. **Backward Compatibility**: Existing batches should continue using default model if not configured
+
+## Design Approach
+
+### Option 1: Simple Model Name Field (Recommended)
+- Add `verification_model` field to `CodeBatch` (CharField, nullable)
+- Store model filename (e.g., `best_model_ep82_pos98.94_neg96.13_20250720_222102.pt`)
+- Pass to v5 endpoint when `feature_comparison_threshold > 0.01` and model is set
+- **Pros**: Simple, minimal changes, flexible
+- **Cons**: No validation of model existence, manual entry required
+
+### Option 2: Model Reference with Validation
+- Add `verification_model` field + create `VerificationModel` table
+- Store available models in database
+- Validate model exists before allowing selection
+- **Pros**: Better validation, can track model metadata
+- **Cons**: More complex, requires model management UI
+
+### Option 3: Use GlobalConfig for Model List
+- Store available models in `GlobalConfig`
+- Add `verification_model` field to `CodeBatch`
+- Validate against GlobalConfig list
+- **Pros**: Reuses existing config system, simple validation
+- **Cons**: Still requires manual model management
+
+**Recommendation**: Option 1 for minimal changes, with future enhancement to Option 3 if needed.
+
+## Implementation Plan
+
+### Phase 1: Database Changes
+
+1. **Create Migration**
+ - Add `verification_model` field to `CodeBatch` model
+ - Type: `CharField(max_length=255, null=True, blank=True)`
+ - Verbose name: "验证模型名称(可选,留空使用默认模型)"
+ - Default: `None` (uses server default)
+
+2. **Update Model**
+ ```python
+ # api/products/models.py
+ class CodeBatch(models.Model):
+ # ... existing fields ...
+ verification_model = models.CharField(
+ max_length=255,
+ null=True,
+ blank=True,
+ verbose_name="验证模型名称(可选,留空使用默认模型)"
+ )
+ ```
+
+### Phase 2: API Changes
+
+1. **Update `do_v5_qr_verify()` method**
+ - Location: `api/products/views.py:668`
+ - Accept `model_name` parameter
+ - Pass `model` in form data if provided
+ - Fallback to default (current behavior) if not provided
+
+2. **Update `QrVerifyView.post()` method**
+ - Location: `api/products/views.py:722`
+ - Get model from `sc.batch.verification_model`
+ - Pass to `do_v5_qr_verify()` when calling
+
+3. **Code Changes**
+ ```python
+ def do_v5_qr_verify(self, imgs, messages, model_name=None):
+ files = {}
+ for i, img in enumerate(imgs):
+ files[f'frame_{i}_{img[1]}'] = img[0]
+
+ data = {}
+ if model_name:
+ data['model'] = model_name
+
+ resp = requests.post(
+ "https://themblem.com/api/v5/qr_verify",
+ files=files,
+ data=data, # Add model parameter if specified
+ )
+ # ... rest of method ...
+
+ # In post() method:
+ if sc.batch.feature_comparison_threshold > 0.01:
+ model_name = sc.batch.verification_model if hasattr(sc.batch, 'verification_model') else None
+ self.do_v5_qr_verify(imgs, messages, model_name=model_name)
+ ```
+
+### Phase 3: UI Changes
+
+1. **Update Vue Component**
+ - Location: `web/src/views/code-batch.vue`
+ - Add `verification_model` to `editable_fields` array
+ - Add to `visible_fields` array (optional, for display)
+
+2. **Optional: Model Selection Helper**
+ - Could add a dropdown/autocomplete if model list is available
+ - For now, text input is sufficient (admins know model names)
+
+### Phase 4: Testing
+
+1. **Unit Tests**
+ - Test `do_v5_qr_verify()` with and without model parameter
+ - Test backward compatibility (null model uses default)
+
+2. **Integration Tests**
+ - Test QR verification with custom model
+ - Test QR verification without model (default behavior)
+ - Test with `feature_comparison_threshold = 0` (should skip)
+
+3. **Manual Testing**
+ - Create batch with custom model
+ - Verify model is passed to v5 endpoint
+ - Verify default model used when field is empty
+
+## Database Schema Change
+
+```python
+# Migration file: api/products/migrations/XXXX_add_verification_model_to_codebatch.py
+
+from django.db import migrations, models
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('products', 'XXXX_previous_migration'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='codebatch',
+ name='verification_model',
+ field=models.CharField(
+ max_length=255,
+ blank=True,
+ null=True,
+ verbose_name='验证模型名称(可选,留空使用默认模型)'
+ ),
+ ),
+ ]
+```
+
+## API Changes Summary
+
+### Modified Endpoints
+- **None** (internal method change only)
+
+### New Behavior
+- `QrVerifyView.do_v5_qr_verify()` now accepts optional `model_name` parameter
+- When `CodeBatch.verification_model` is set, it's passed to v5 endpoint
+- When `CodeBatch.verification_model` is null/empty, server uses default model
+
+## UI Changes Summary
+
+### Modified Views
+- `web/src/views/code-batch.vue`
+ - Add `verification_model` to editable fields
+ - Optional: Add to visible fields for display
+
+### User Experience
+- Admin can edit batch and set `verification_model` field
+- Field is optional - leaving empty uses default model
+- Text input allows entering model filename directly
+
+## Backward Compatibility
+
+✅ **Fully Compatible**
+- Existing batches have `verification_model = None`
+- When `None`, v5 endpoint uses default model (current behavior)
+- No breaking changes to existing functionality
+
+## Future Enhancements
+
+1. **Model Management UI**
+ - List available models from OSS bucket
+ - Show model metadata (accuracy, date, etc.)
+ - Auto-complete model selection
+
+2. **Model Validation**
+ - Check if model exists before saving
+ - Show warning if model not found
+
+3. **Model Versioning**
+ - Track model versions per batch
+ - Allow rollback to previous models
+
+4. **A/B Testing**
+ - Support multiple models per batch
+ - Compare model performance
+
+## Files to Modify
+
+1. `api/products/models.py` - Add `verification_model` field
+2. `api/products/views.py` - Update `do_v5_qr_verify()` and `QrVerifyView.post()`
+3. `web/src/views/code-batch.vue` - Add field to editable fields
+4. Create migration file in `api/products/migrations/`
+
+## Estimated Effort
+
+- **Database Migration**: 15 minutes
+- **Backend Changes**: 30 minutes
+- **Frontend Changes**: 15 minutes
+- **Testing**: 1 hour
+- **Total**: ~2 hours
+
+## Risks and Considerations
+
+1. **Model Availability**: No validation that model exists in OSS bucket
+ - **Mitigation**: Server will return error if model not found, which is acceptable
+
+2. **Model Naming**: Admins must know exact model filename
+ - **Mitigation**: Document model names, or add helper UI later
+
+3. **Default Model Changes**: If default model changes in server, batches without explicit model will use new default
+ - **Mitigation**: This is expected behavior, but could be documented
+
+4. **Performance**: No performance impact expected
+ - Model download is cached on server side
+
+## Conclusion
+
+This feature can be implemented with minimal changes following Option 1 (Simple Model Name Field). The implementation is backward compatible and allows admins to configure models per batch while maintaining default behavior for existing batches.
+
diff --git a/web/src/_nav.js b/web/src/_nav.js
index efd45f8..034fc34 100644
--- a/web/src/_nav.js
+++ b/web/src/_nav.js
@@ -45,6 +45,13 @@ export default [
icon: 'list',
role: 'admin',
},
+ {
+ component: 'CNavItem',
+ name: '验证模型',
+ to: '/verification-model',
+ icon: 'microchip',
+ role: 'admin',
+ },
{
component: 'CNavItem',
name: '序列码管理',
diff --git a/web/src/router/index.js b/web/src/router/index.js
index 2b7c26d..be92e37 100644
--- a/web/src/router/index.js
+++ b/web/src/router/index.js
@@ -93,6 +93,12 @@ const routes = [
component: () =>
import(/* webpackChunkName: "code-batch" */ '@/views/code-batch.vue'),
},
+ {
+ path: '/verification-model',
+ name: 'VerificationModel',
+ component: () =>
+ import(/* webpackChunkName: "verification-model" */ '@/views/verification-model.vue'),
+ },
{
path: '/code-batch-export/:id',
props: true,
diff --git a/web/src/views/code-batch.vue b/web/src/views/code-batch.vue
index 2d89a56..74a99ba 100644
--- a/web/src/views/code-batch.vue
+++ b/web/src/views/code-batch.vue
@@ -74,6 +74,7 @@ export default {
'description',
'is_active',
'feature_comparison_threshold',
+ 'verification_model',
'scan_redirect_url',
'enable_auto_torch',
'camera_sensitivity',
@@ -92,6 +93,8 @@ export default {
'code_prefix',
'is_active',
'code__count',
+ 'feature_comparison_threshold',
+ 'verification_model',
'scan_redirect_url',
'enable_auto_torch',
'mini_prog_entry_path',
diff --git a/web/src/views/verification-model.vue b/web/src/views/verification-model.vue
new file mode 100644
index 0000000..edb99b9
--- /dev/null
+++ b/web/src/views/verification-model.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+