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 @@ + + + + + +