205 lines
6.6 KiB
Python
205 lines
6.6 KiB
Python
"""Import batch processor service."""
|
|
import asyncio
|
|
from typing import List, Dict, Any, Optional
|
|
from datetime import datetime
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, update, insert, delete
|
|
from sqlalchemy.sql import func
|
|
|
|
from app.models.card_import_batch import CardImportBatch
|
|
from app.models.user_card_import_record import UserCardImportRecord
|
|
from app.models.user_data import UserCardCollection
|
|
from app.models.models import MtgonlineCard
|
|
from app.services.fuzzy_card_matcher import FuzzyCardMatcher
|
|
|
|
|
|
class ImportBatchProcessor:
|
|
"""Process card import batches."""
|
|
|
|
@staticmethod
|
|
async def create_batch(
|
|
db: AsyncSession,
|
|
user_id: int,
|
|
filename: str,
|
|
file_type: str,
|
|
file_size: int,
|
|
card_names: List[str]
|
|
) -> CardImportBatch:
|
|
"""Create a new import batch."""
|
|
batch = CardImportBatch(
|
|
user_id=user_id,
|
|
filename=filename,
|
|
file_type=file_type,
|
|
file_size=file_size,
|
|
status="pending",
|
|
total_cards=len(card_names),
|
|
)
|
|
db.add(batch)
|
|
await db.flush()
|
|
return batch
|
|
|
|
@staticmethod
|
|
async def process_batch(
|
|
db: AsyncSession,
|
|
batch: CardImportBatch,
|
|
card_names: List[str],
|
|
threshold: float = FuzzyCardMatcher.MANUAL_REVIEW_THRESHOLD
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Process an import batch.
|
|
|
|
Args:
|
|
db: Database session
|
|
batch: Import batch to process
|
|
card_names: List of card names from the file
|
|
threshold: Minimum similarity threshold for matching
|
|
|
|
Returns:
|
|
Dictionary with processing results
|
|
"""
|
|
# Update status to processing
|
|
batch.status = "processing"
|
|
await db.flush()
|
|
|
|
try:
|
|
# Fetch all cards from database
|
|
stmt = select(MtgonlineCard)
|
|
result = await db.execute(stmt)
|
|
db_cards = result.scalars().all()
|
|
|
|
# Build candidate list
|
|
candidate_names = [card.name for card in db_cards if card.name]
|
|
|
|
# Perform batch matching
|
|
match_results = FuzzyCardMatcher.batch_match_with_database(
|
|
card_names=card_names,
|
|
db_session=db,
|
|
mtgonline_card_model=MtgonlineCard,
|
|
threshold=threshold
|
|
)
|
|
|
|
# Count matches
|
|
matched_count = sum(1 for _, _, matched_name, _, _ in match_results if matched_name)
|
|
unmatched_count = sum(1 for _, _, matched_name, _, _ in match_results if not matched_name)
|
|
|
|
# Update batch
|
|
batch.matched_cards = matched_count
|
|
batch.unmatched_cards = unmatched_count
|
|
batch.match_results = match_results
|
|
batch.status = "completed"
|
|
batch.updated_at = func.now()
|
|
await db.flush()
|
|
|
|
return {
|
|
"batch_id": batch.id,
|
|
"status": "completed",
|
|
"total_cards": len(card_names),
|
|
"matched_cards": matched_count,
|
|
"unmatched_cards": unmatched_count,
|
|
"match_results": match_results,
|
|
}
|
|
|
|
except Exception as e:
|
|
batch.status = "failed"
|
|
batch.error_message = str(e)
|
|
batch.updated_at = func.now()
|
|
await db.flush()
|
|
|
|
return {
|
|
"batch_id": batch.id,
|
|
"status": "failed",
|
|
"error": str(e),
|
|
}
|
|
|
|
@staticmethod
|
|
async def get_batch_status(db: AsyncSession, batch_id: int) -> Optional[CardImportBatch]:
|
|
"""Get the status of an import batch."""
|
|
stmt = select(CardImportBatch).where(CardImportBatch.id == batch_id)
|
|
result = await db.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
@staticmethod
|
|
async def get_batch_results(db: AsyncSession, batch_id: int) -> Optional[Dict[str, Any]]:
|
|
"""Get the match results for an import batch."""
|
|
batch = await ImportBatchProcessor.get_batch_status(db, batch_id)
|
|
if not batch:
|
|
return None
|
|
return batch.match_results
|
|
|
|
@staticmethod
|
|
async def confirm_batch(db: AsyncSession, batch_id: int, user_id: int) -> UserCardImportRecord:
|
|
"""
|
|
Confirm an import batch.
|
|
|
|
Args:
|
|
db: Database session
|
|
batch_id: ID of the batch to confirm
|
|
user_id: ID of the user confirming
|
|
|
|
Returns:
|
|
UserCardImportRecord for the confirmed import
|
|
"""
|
|
batch = await ImportBatchProcessor.get_batch_status(db, batch_id)
|
|
if not batch:
|
|
raise ValueError(f"Import batch {batch_id} not found")
|
|
|
|
if batch.status != "completed":
|
|
raise ValueError(f"Import batch {batch_id} is not completed (status: {batch.status})")
|
|
|
|
# Check if already confirmed
|
|
stmt = select(UserCardImportRecord).where(
|
|
UserCardImportRecord.user_id == user_id,
|
|
UserCardImportRecord.batch_id == batch_id,
|
|
)
|
|
result = await db.execute(stmt)
|
|
existing = result.scalar_one_or_none()
|
|
|
|
if existing:
|
|
return existing
|
|
|
|
# Create confirmation record
|
|
record = UserCardImportRecord(
|
|
user_id=user_id,
|
|
batch_id=batch_id,
|
|
is_confirmed=True,
|
|
)
|
|
db.add(record)
|
|
await db.flush()
|
|
|
|
return record
|
|
|
|
@staticmethod
|
|
async def get_user_imports(db: AsyncSession, user_id: int) -> List[CardImportBatch]:
|
|
"""Get all import batches for a user."""
|
|
stmt = select(CardImportBatch).where(CardImportBatch.user_id == user_id).order_by(CardImportBatch.created_at.desc())
|
|
result = await db.execute(stmt)
|
|
return result.scalars().all()
|
|
|
|
@staticmethod
|
|
async def delete_batch(db: AsyncSession, batch_id: int, user_id: int) -> bool:
|
|
"""
|
|
Delete an import batch.
|
|
|
|
Args:
|
|
db: Database session
|
|
batch_id: ID of the batch to delete
|
|
user_id: ID of the user deleting
|
|
|
|
Returns:
|
|
True if deleted successfully, False if not found
|
|
"""
|
|
batch = await ImportBatchProcessor.get_batch_status(db, batch_id)
|
|
if not batch or batch.user_id != user_id:
|
|
return False
|
|
|
|
# Delete confirmation records
|
|
stmt = delete(UserCardImportRecord).where(UserCardImportRecord.batch_id == batch_id)
|
|
await db.execute(stmt)
|
|
|
|
# Delete batch
|
|
stmt = delete(CardImportBatch).where(CardImportBatch.id == batch_id)
|
|
await db.execute(stmt)
|
|
await db.flush()
|
|
|
|
return True
|