change for ocr confidence
This commit is contained in:
@@ -48,14 +48,41 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="text-sm space-y-1">
|
||||
<div>Cost: {{ response.cost || '-' }}</div>
|
||||
<div>Cycles: {{ response.cycles || '-' }}</div>
|
||||
<div>Area: {{ response.area || '-' }}</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span>Cost: {{ response.cost || '-' }}</span>
|
||||
<span
|
||||
v-if="response.ocr_confidence_cost"
|
||||
class="badge badge-xs"
|
||||
:class="getConfidenceBadgeClass(response.ocr_confidence_cost)"
|
||||
>
|
||||
{{ Math.round(response.ocr_confidence_cost * 100) }}%
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span>Cycles: {{ response.cycles || '-' }}</span>
|
||||
<span
|
||||
v-if="response.ocr_confidence_cycles"
|
||||
class="badge badge-xs"
|
||||
:class="getConfidenceBadgeClass(response.ocr_confidence_cycles)"
|
||||
>
|
||||
{{ Math.round(response.ocr_confidence_cycles * 100) }}%
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span>Area: {{ response.area || '-' }}</span>
|
||||
<span
|
||||
v-if="response.ocr_confidence_area"
|
||||
class="badge badge-xs"
|
||||
:class="getConfidenceBadgeClass(response.ocr_confidence_area)"
|
||||
>
|
||||
{{ Math.round(response.ocr_confidence_area * 100) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="badge badge-warning badge-sm">
|
||||
{{ response.ocr_confidence_score ? Math.round(response.ocr_confidence_score * 100) + '%' : 'Low' }}
|
||||
{{ getOverallConfidence(response) }}%
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@@ -273,6 +300,26 @@ onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
// Helper functions for confidence display
|
||||
const getConfidenceBadgeClass = (confidence: number): string => {
|
||||
if (confidence >= 0.8) return 'badge-success'
|
||||
if (confidence >= 0.6) return 'badge-warning'
|
||||
return 'badge-error'
|
||||
}
|
||||
|
||||
const getOverallConfidence = (response: PuzzleResponse): number => {
|
||||
const confidences = [
|
||||
response.ocr_confidence_cost,
|
||||
response.ocr_confidence_cycles,
|
||||
response.ocr_confidence_area
|
||||
].filter(conf => conf !== undefined && conf !== null) as number[]
|
||||
|
||||
if (confidences.length === 0) return 0
|
||||
|
||||
const average = confidences.reduce((sum, conf) => sum + conf, 0) / confidences.length
|
||||
return Math.round(average * 100)
|
||||
}
|
||||
|
||||
// Expose refresh method
|
||||
defineExpose({
|
||||
refresh: loadData
|
||||
|
||||
@@ -83,7 +83,17 @@
|
||||
|
||||
<div v-else-if="file.ocrData" class="mt-1 space-y-1">
|
||||
<div class="text-xs flex items-center justify-between">
|
||||
<span class="font-medium text-success">✓ OCR Complete</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-success">✓ OCR Complete</span>
|
||||
<span
|
||||
v-if="file.ocrData.confidence"
|
||||
class="badge badge-xs"
|
||||
:class="getConfidenceBadgeClass(file.ocrData.confidence.overall)"
|
||||
:title="`Overall confidence: ${Math.round(file.ocrData.confidence.overall * 100)}%`"
|
||||
>
|
||||
{{ Math.round(file.ocrData.confidence.overall * 100) }}%
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
@click="retryOCR(file)"
|
||||
class="btn btn-xs btn-ghost"
|
||||
@@ -95,15 +105,43 @@
|
||||
<div class="text-xs space-y-1 bg-base-200 p-2 rounded">
|
||||
<div v-if="file.ocrData.puzzle">
|
||||
<strong>Puzzle:</strong> {{ file.ocrData.puzzle }}
|
||||
<span
|
||||
v-if="file.ocrData.confidence?.puzzle"
|
||||
class="ml-2 opacity-60"
|
||||
:title="`Puzzle confidence: ${Math.round(file.ocrData.confidence.puzzle * 100)}%`"
|
||||
>
|
||||
({{ Math.round(file.ocrData.confidence.puzzle * 100) }}%)
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="file.ocrData.cost">
|
||||
<strong>Cost:</strong> {{ file.ocrData.cost }}
|
||||
<span
|
||||
v-if="file.ocrData.confidence?.cost"
|
||||
class="ml-2 opacity-60"
|
||||
:title="`Cost confidence: ${Math.round(file.ocrData.confidence.cost * 100)}%`"
|
||||
>
|
||||
({{ Math.round(file.ocrData.confidence.cost * 100) }}%)
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="file.ocrData.cycles">
|
||||
<strong>Cycles:</strong> {{ file.ocrData.cycles }}
|
||||
<span
|
||||
v-if="file.ocrData.confidence?.cycles"
|
||||
class="ml-2 opacity-60"
|
||||
:title="`Cycles confidence: ${Math.round(file.ocrData.confidence.cycles * 100)}%`"
|
||||
>
|
||||
({{ Math.round(file.ocrData.confidence.cycles * 100) }}%)
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="file.ocrData.area">
|
||||
<strong>Area:</strong> {{ file.ocrData.area }}
|
||||
<span
|
||||
v-if="file.ocrData.confidence?.area"
|
||||
class="ml-2 opacity-60"
|
||||
:title="`Area confidence: ${Math.round(file.ocrData.confidence.area * 100)}%`"
|
||||
>
|
||||
({{ Math.round(file.ocrData.confidence.area * 100) }}%)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -306,4 +344,10 @@ const processOCR = async (submissionFile: SubmissionFile) => {
|
||||
const retryOCR = (submissionFile: SubmissionFile) => {
|
||||
processOCR(submissionFile)
|
||||
}
|
||||
|
||||
const getConfidenceBadgeClass = (confidence: number): string => {
|
||||
if (confidence >= 0.8) return 'badge-success'
|
||||
if (confidence >= 0.6) return 'badge-warning'
|
||||
return 'badge-error'
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type {
|
||||
SteamCollectionItem,
|
||||
Submission,
|
||||
PuzzleResponse,
|
||||
import type {
|
||||
SteamCollectionItem,
|
||||
Submission,
|
||||
PuzzleResponse,
|
||||
SubmissionFile,
|
||||
UserInfo
|
||||
} from '../types'
|
||||
@@ -32,7 +32,7 @@ interface SubmissionStats {
|
||||
// API Service Class
|
||||
export class ApiService {
|
||||
private async request<T>(
|
||||
endpoint: string,
|
||||
endpoint: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<ApiResponse<T>> {
|
||||
try {
|
||||
@@ -122,16 +122,18 @@ export class ApiService {
|
||||
cycles?: string
|
||||
area?: string
|
||||
needs_manual_validation?: boolean
|
||||
ocr_confidence_score?: number
|
||||
ocr_confidence_cost?: number
|
||||
ocr_confidence_cycles?: number
|
||||
ocr_confidence_area?: number
|
||||
}>
|
||||
},
|
||||
files: File[]
|
||||
): Promise<ApiResponse<Submission>> {
|
||||
const formData = new FormData()
|
||||
|
||||
|
||||
// Add JSON data
|
||||
formData.append('data', JSON.stringify(submissionData))
|
||||
|
||||
|
||||
// Add files
|
||||
files.forEach((file) => {
|
||||
formData.append('files', file)
|
||||
@@ -203,36 +205,36 @@ export const puzzleHelpers = {
|
||||
|
||||
findPuzzleByName(puzzles: SteamCollectionItem[], name: string): SteamCollectionItem | null {
|
||||
if (!name) return null
|
||||
|
||||
|
||||
// Try exact match first
|
||||
let match = puzzles.find(p =>
|
||||
let match = puzzles.find(p =>
|
||||
p.title.toLowerCase() === name.toLowerCase()
|
||||
)
|
||||
|
||||
|
||||
if (!match) {
|
||||
// Try partial match
|
||||
match = puzzles.find(p =>
|
||||
match = puzzles.find(p =>
|
||||
p.title.toLowerCase().includes(name.toLowerCase()) ||
|
||||
name.toLowerCase().includes(p.title.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return match || null
|
||||
}
|
||||
}
|
||||
|
||||
export const submissionHelpers = {
|
||||
async createFromFiles(
|
||||
files: SubmissionFile[],
|
||||
files: SubmissionFile[],
|
||||
puzzles: SteamCollectionItem[],
|
||||
notes?: string
|
||||
): Promise<ApiResponse<Submission>> {
|
||||
// Group files by detected puzzle
|
||||
const responsesByPuzzle: Record<string, {
|
||||
puzzle: SteamCollectionItem | null,
|
||||
files: SubmissionFile[]
|
||||
const responsesByPuzzle: Record<string, {
|
||||
puzzle: SteamCollectionItem | null,
|
||||
files: SubmissionFile[]
|
||||
}> = {}
|
||||
|
||||
|
||||
files.forEach(file => {
|
||||
if (file.ocrData?.puzzle) {
|
||||
const puzzleName = file.ocrData.puzzle
|
||||
@@ -251,14 +253,14 @@ export const submissionHelpers = {
|
||||
.filter(([_, data]) => data.puzzle) // Only include matched puzzles
|
||||
.map(([puzzleName, data]) => {
|
||||
// Get OCR data from the first file with complete data
|
||||
const fileWithOCR = data.files.find(f =>
|
||||
const fileWithOCR = data.files.find(f =>
|
||||
f.ocrData?.cost || f.ocrData?.cycles || f.ocrData?.area
|
||||
)
|
||||
|
||||
|
||||
// Check if manual validation is needed
|
||||
const needsValidation = !fileWithOCR?.ocrData ||
|
||||
!fileWithOCR.ocrData.cost ||
|
||||
!fileWithOCR.ocrData.cycles ||
|
||||
const needsValidation = !fileWithOCR?.ocrData ||
|
||||
!fileWithOCR.ocrData.cost ||
|
||||
!fileWithOCR.ocrData.cycles ||
|
||||
!fileWithOCR.ocrData.area
|
||||
|
||||
return {
|
||||
@@ -268,7 +270,9 @@ export const submissionHelpers = {
|
||||
cycles: fileWithOCR?.ocrData?.cycles,
|
||||
area: fileWithOCR?.ocrData?.area,
|
||||
needs_manual_validation: needsValidation,
|
||||
ocr_confidence_score: needsValidation ? 0.5 : 0.9 // Rough estimate
|
||||
ocr_confidence_cost: fileWithOCR?.ocrData?.confidence?.cost || 0.0,
|
||||
ocr_confidence_cycles: fileWithOCR?.ocrData?.confidence?.cycles || 0.0,
|
||||
ocr_confidence_area: fileWithOCR?.ocrData?.confidence?.area || 0.0
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -5,6 +5,13 @@ export interface OpusMagnumData {
|
||||
cost: string;
|
||||
cycles: string;
|
||||
area: string;
|
||||
confidence: {
|
||||
puzzle: number;
|
||||
cost: number;
|
||||
cycles: number;
|
||||
area: number;
|
||||
overall: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface OCRRegion {
|
||||
@@ -64,6 +71,7 @@ export class OpusMagnumOCRService {
|
||||
|
||||
// Extract text from each region
|
||||
const results: Partial<OpusMagnumData> = {};
|
||||
const confidenceScores: Record<string, number> = {};
|
||||
|
||||
for (const [key, region] of Object.entries(this.regions)) {
|
||||
const regionCanvas = document.createElement('canvas');
|
||||
@@ -108,8 +116,11 @@ export class OpusMagnumOCRService {
|
||||
}
|
||||
|
||||
// Perform OCR on the region
|
||||
const { data: { text } } = await this.worker!.recognize(regionCanvas);
|
||||
const { data: { text, confidence } } = await this.worker!.recognize(regionCanvas);
|
||||
let cleanText = text.trim();
|
||||
|
||||
// Store the confidence score for this field
|
||||
confidenceScores[key] = confidence / 100; // Tesseract returns 0-100, we want 0-1
|
||||
|
||||
// Post-process based on field type
|
||||
if (key === 'cost') {
|
||||
@@ -134,16 +145,29 @@ export class OpusMagnumOCRService {
|
||||
cleanText = this.findBestPuzzleMatch(cleanText);
|
||||
}
|
||||
|
||||
results[key as keyof OpusMagnumData] = cleanText;
|
||||
(results as any)[key] = cleanText;
|
||||
}
|
||||
|
||||
URL.revokeObjectURL(imageUrl);
|
||||
|
||||
// Calculate overall confidence as the average of all field confidences
|
||||
const confidenceValues = Object.values(confidenceScores);
|
||||
const overallConfidence = confidenceValues.length > 0
|
||||
? confidenceValues.reduce((sum, conf) => sum + conf, 0) / confidenceValues.length
|
||||
: 0;
|
||||
|
||||
resolve({
|
||||
puzzle: results.puzzle || '',
|
||||
cost: results.cost || '',
|
||||
cycles: results.cycles || '',
|
||||
area: results.area || ''
|
||||
area: results.area || '',
|
||||
confidence: {
|
||||
puzzle: confidenceScores.puzzle || 0,
|
||||
cost: confidenceScores.cost || 0,
|
||||
cycles: confidenceScores.cycles || 0,
|
||||
area: confidenceScores.area || 0,
|
||||
overall: overallConfidence
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
URL.revokeObjectURL(imageUrl);
|
||||
|
||||
@@ -29,6 +29,13 @@ export interface OpusMagnumData {
|
||||
cost: string
|
||||
cycles: string
|
||||
area: string
|
||||
confidence: {
|
||||
puzzle: number
|
||||
cost: number
|
||||
cycles: number
|
||||
area: number
|
||||
overall: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SubmissionFile {
|
||||
@@ -49,7 +56,9 @@ export interface PuzzleResponse {
|
||||
cycles?: string
|
||||
area?: string
|
||||
needs_manual_validation?: boolean
|
||||
ocr_confidence_score?: number
|
||||
ocr_confidence_cost?: number
|
||||
ocr_confidence_cycles?: number
|
||||
ocr_confidence_area?: number
|
||||
validated_cost?: string
|
||||
validated_cycles?: string
|
||||
validated_area?: string
|
||||
|
||||
Reference in New Issue
Block a user