add manual validation request on the submission form
This commit is contained in:
parent
2260c7cc27
commit
8960f551e6
@ -97,7 +97,8 @@ onMounted(async () => {
|
||||
|
||||
const handleSubmission = async (submissionData: {
|
||||
files: any[],
|
||||
notes?: string
|
||||
notes?: string,
|
||||
manualValidationRequested?: boolean
|
||||
}) => {
|
||||
try {
|
||||
isLoading.value = true
|
||||
@ -107,7 +108,8 @@ const handleSubmission = async (submissionData: {
|
||||
const response = await submissionHelpers.createFromFiles(
|
||||
submissionData.files,
|
||||
puzzles.value,
|
||||
submissionData.notes
|
||||
submissionData.notes,
|
||||
submissionData.manualValidationRequested
|
||||
)
|
||||
|
||||
if (response.error) {
|
||||
|
||||
@ -40,6 +40,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Manual Validation Request -->
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="manualValidationRequested"
|
||||
class="checkbox checkbox-primary"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<span class="label-text font-medium">Request manual validation</span>
|
||||
<div class="label-text-alt text-xs opacity-70 mt-1">
|
||||
Check this if you want an admin to manually review your submission, even if OCR confidence is high.
|
||||
<br>
|
||||
<em>Note: This will be automatically checked if any OCR confidence is below 50%.</em>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="card-actions justify-end">
|
||||
<button
|
||||
@ -57,7 +76,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import FileUpload from './FileUpload.vue'
|
||||
import type { SteamCollectionItem, SubmissionFile } from '@/types'
|
||||
|
||||
@ -67,7 +86,7 @@ interface Props {
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
submit: [submissionData: { files: SubmissionFile[], notes?: string }]
|
||||
submit: [submissionData: { files: SubmissionFile[], notes?: string, manualValidationRequested?: boolean }]
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@ -75,6 +94,7 @@ const emit = defineEmits<Emits>()
|
||||
|
||||
const submissionFiles = ref<SubmissionFile[]>([])
|
||||
const notes = ref('')
|
||||
const manualValidationRequested = ref(false)
|
||||
const isSubmitting = ref(false)
|
||||
|
||||
const notesLength = computed(() => notes.value.length)
|
||||
@ -104,6 +124,23 @@ const responsesByPuzzle = computed(() => {
|
||||
return grouped
|
||||
})
|
||||
|
||||
// Check if any OCR confidence is below 50%
|
||||
const hasLowConfidence = computed(() => {
|
||||
return submissionFiles.value.some(file => {
|
||||
if (!file.ocrData?.confidence) return false
|
||||
return file.ocrData.confidence.cost < 0.5 ||
|
||||
file.ocrData.confidence.cycles < 0.5 ||
|
||||
file.ocrData.confidence.area < 0.5
|
||||
})
|
||||
})
|
||||
|
||||
// Auto-check manual validation when confidence is low
|
||||
watch(hasLowConfidence, (newValue) => {
|
||||
if (newValue && !manualValidationRequested.value) {
|
||||
manualValidationRequested.value = true
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!canSubmit.value) return
|
||||
|
||||
@ -113,12 +150,14 @@ const handleSubmit = async () => {
|
||||
// Emit the files and notes for the parent to handle API submission
|
||||
emit('submit', {
|
||||
files: submissionFiles.value,
|
||||
notes: notes.value.trim() || undefined
|
||||
notes: notes.value.trim() || undefined,
|
||||
manualValidationRequested: manualValidationRequested.value
|
||||
})
|
||||
|
||||
// Reset form
|
||||
submissionFiles.value = []
|
||||
notes.value = ''
|
||||
manualValidationRequested.value = false
|
||||
|
||||
} catch (error) {
|
||||
console.error('Submission error:', error)
|
||||
|
||||
@ -115,6 +115,7 @@ export class ApiService {
|
||||
async createSubmission(
|
||||
submissionData: {
|
||||
notes?: string
|
||||
manual_validation_requested?: boolean
|
||||
responses: Array<{
|
||||
puzzle_id: number
|
||||
puzzle_name: string
|
||||
@ -225,9 +226,10 @@ export const puzzleHelpers = {
|
||||
|
||||
export const submissionHelpers = {
|
||||
async createFromFiles(
|
||||
files: SubmissionFile[],
|
||||
files: SubmissionFile[],
|
||||
puzzles: SteamCollectionItem[],
|
||||
notes?: string
|
||||
notes?: string,
|
||||
manualValidationRequested?: boolean
|
||||
): Promise<ApiResponse<Submission>> {
|
||||
// Group files by detected puzzle
|
||||
const responsesByPuzzle: Record<string, {
|
||||
@ -286,7 +288,11 @@ export const submissionHelpers = {
|
||||
// Extract actual File objects for upload
|
||||
const fileObjects = files.map(f => f.file)
|
||||
|
||||
return apiService.createSubmission({ notes, responses }, fileObjects)
|
||||
return apiService.createSubmission({
|
||||
notes,
|
||||
manual_validation_requested: manualValidationRequested,
|
||||
responses
|
||||
}, fileObjects)
|
||||
},
|
||||
|
||||
async loadSubmissions(limit = 20, offset = 0): Promise<Submission[]> {
|
||||
|
||||
@ -78,6 +78,7 @@ export interface Submission {
|
||||
is_validated?: boolean
|
||||
validated_by?: number | null
|
||||
validated_at?: string | null
|
||||
manual_validation_requested?: boolean
|
||||
total_responses?: number
|
||||
needs_validation?: boolean
|
||||
created_at?: string
|
||||
|
||||
@ -168,10 +168,10 @@ class PuzzleResponseInline(admin.TabularInline):
|
||||
class SubmissionAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"id", "user", "total_responses", "needs_validation",
|
||||
"is_validated", "created_at"
|
||||
"manual_validation_requested", "is_validated", "created_at"
|
||||
]
|
||||
list_filter = [
|
||||
"is_validated", "created_at", "updated_at"
|
||||
"is_validated", "manual_validation_requested", "created_at", "updated_at"
|
||||
]
|
||||
search_fields = ["id", "user__username", "notes"]
|
||||
readonly_fields = ["id", "created_at", "updated_at", "total_responses", "needs_validation"]
|
||||
@ -182,7 +182,7 @@ class SubmissionAdmin(admin.ModelAdmin):
|
||||
"fields": ("id", "user", "notes")
|
||||
}),
|
||||
("Validation", {
|
||||
"fields": ("is_validated", "validated_by", "validated_at")
|
||||
"fields": ("manual_validation_requested", "is_validated", "validated_by", "validated_at")
|
||||
}),
|
||||
("Statistics", {
|
||||
"fields": ("total_responses", "needs_validation"),
|
||||
|
||||
@ -67,10 +67,19 @@ def create_submission(
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# Check if any confidence score is below 50% to auto-request validation
|
||||
auto_request_validation = any(
|
||||
(response_data.ocr_confidence_cost is not None and response_data.ocr_confidence_cost < 0.5) or
|
||||
(response_data.ocr_confidence_cycles is not None and response_data.ocr_confidence_cycles < 0.5) or
|
||||
(response_data.ocr_confidence_area is not None and response_data.ocr_confidence_area < 0.5)
|
||||
for response_data in data.responses
|
||||
)
|
||||
|
||||
# Create the submission
|
||||
submission = Submission.objects.create(
|
||||
user=request.user if request.user.is_authenticated else None,
|
||||
notes=data.notes,
|
||||
manual_validation_requested=data.manual_validation_requested or auto_request_validation,
|
||||
)
|
||||
|
||||
file_index = 0
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-30 11:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('submissions', '0006_remove_puzzleresponse_ocr_confidence_score_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='submission',
|
||||
name='manual_validation_requested',
|
||||
field=models.BooleanField(default=False, help_text='Whether the user specifically requested manual validation'),
|
||||
),
|
||||
]
|
||||
@ -242,6 +242,12 @@ class Submission(models.Model):
|
||||
validated_at = models.DateTimeField(
|
||||
null=True, blank=True, help_text="When this submission was validated"
|
||||
)
|
||||
|
||||
# Manual validation request
|
||||
manual_validation_requested = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Whether the user specifically requested manual validation"
|
||||
)
|
||||
|
||||
# Timestamps
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@ -34,6 +34,7 @@ class SubmissionIn(Schema):
|
||||
"""Schema for creating a submission"""
|
||||
|
||||
notes: Optional[str] = None
|
||||
manual_validation_requested: bool = False
|
||||
responses: List[PuzzleResponseIn]
|
||||
|
||||
|
||||
@ -102,6 +103,7 @@ class SubmissionOut(ModelSchema):
|
||||
"is_validated",
|
||||
"validated_by",
|
||||
"validated_at",
|
||||
"manual_validation_requested",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user