diff --git a/backend/resolution_proof_service.py b/backend/resolution_proof_service.py index 1bbd1db9..199d9920 100644 --- a/backend/resolution_proof_service.py +++ b/backend/resolution_proof_service.py @@ -464,13 +464,13 @@ def verify_evidence(grievance_id: int, db: Session) -> Dict[str, Any]: Returns: Verification result dictionary """ - # Optimized: Use .count() and .first() to avoid loading all historical evidence - # records into memory, reducing O(N) database transfer and memory overhead. - evidence_count = db.query(ResolutionEvidence).filter( + # Optimized: Evaluate .first() prior to .count() to enable early exit + # when no evidence exists, reducing database round-trips. + evidence = db.query(ResolutionEvidence).filter( ResolutionEvidence.grievance_id == grievance_id - ).count() + ).order_by(ResolutionEvidence.id.desc()).first() - if evidence_count == 0: + if not evidence: return { "grievance_id": grievance_id, "is_verified": False, @@ -483,10 +483,10 @@ def verify_evidence(grievance_id: int, db: Session) -> Dict[str, Any]: "message": "No resolution evidence found for this grievance" } - # Use the most recent evidence - evidence = db.query(ResolutionEvidence).filter( + # Total count is still needed for the response + evidence_count = db.query(ResolutionEvidence).filter( ResolutionEvidence.grievance_id == grievance_id - ).order_by(ResolutionEvidence.id.desc()).first() + ).count() # Re-verify the server signature bundle_str = json.dumps(evidence.metadata_bundle, sort_keys=True) diff --git a/backend/routers/voice.py b/backend/routers/voice.py index 5ec6e385..f2079a20 100644 --- a/backend/routers/voice.py +++ b/backend/routers/voice.py @@ -11,10 +11,12 @@ import logging import os import uuid +import hashlib from datetime import datetime, timezone from backend.database import get_db from backend.models import Issue +from backend.cache import blockchain_last_hash_cache from backend.schemas import ( VoiceTranscriptionResponse, TextTranslationRequest, @@ -254,6 +256,21 @@ async def submit_voice_issue( # Store relative path for portability relative_audio_path = os.path.join("data", "audio_recordings", audio_filename) + # Blockchain feature: calculate integrity hash for the report + # Performance Boost: Use thread-safe cache to eliminate DB query for last hash + prev_hash = blockchain_last_hash_cache.get("last_hash") + if prev_hash is None: + # Cache miss: Fetch only the last hash from DB + prev_issue = await run_in_threadpool( + lambda: db.query(Issue.integrity_hash).order_by(Issue.id.desc()).first() + ) + prev_hash = prev_issue[0] if prev_issue and prev_issue[0] else "" + blockchain_last_hash_cache.set(data=prev_hash, key="last_hash") + + # Simple but effective SHA-256 chaining + hash_content = f"{final_description}|{issue_category.value}|{prev_hash}" + integrity_hash = hashlib.sha256(hash_content.encode()).hexdigest() + # Create issue in database reference_id = generate_reference_id() @@ -273,12 +290,17 @@ async def submit_voice_issue( original_text=original_text, transcription_confidence=voice_result.get('confidence', 0.0), manual_correction_applied=manual_correction_applied, - audio_file_path=relative_audio_path # Store relative path + audio_file_path=relative_audio_path, # Store relative path + integrity_hash=integrity_hash, + previous_integrity_hash=prev_hash ) db.add(new_issue) db.commit() db.refresh(new_issue) + + # Update cache for next report AFTER successful DB commit + blockchain_last_hash_cache.set(data=integrity_hash, key="last_hash") logger.info(f"Voice issue created: ID={new_issue.id}, Language={voice_result.get('source_language')}, Confidence={voice_result.get('confidence')}")