From 7c40a2e4652f956af43f6b6936a9f337e55628f7 Mon Sep 17 00:00:00 2001 From: RohanExploit <178623867+RohanExploit@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:23:17 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[implement=20blockchain?= =?UTF-8?q?=20integrity=20and=20optimize=20I/O=20for=20voice=20issues]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 4 ++++ backend/routers/voice.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 58ed3de2..191736f9 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -73,3 +73,7 @@ ## 2026-04-17 - ORM Counting vs func.count().scalar() **Learning:** Using `db.query(Model).filter(...).count()` can be slower and have more ORM overhead than `db.query(func.count(Model.id)).filter(...).scalar() or 0` or doing an early `.first()` exit. **Action:** When counting records or verifying existence, prefer early `.first()` exits combined with `func.count().scalar()` for performance in high-traffic APIs. + +## 2026-04-20 - Async File I/O in Voice Submission +**Learning:** Saving audio recordings (up to 10MB) synchronously in a FastAPI async endpoint blocks the main event loop, significantly increasing tail latency for all concurrent users during high-traffic periods. +**Action:** Wrap blocking synchronous File I/O operations like `f.write()` in `run_in_threadpool` to offload them to a separate thread, keeping the event loop responsive for other requests. diff --git a/backend/routers/voice.py b/backend/routers/voice.py index 5ec6e385..cd816567 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, @@ -248,12 +250,32 @@ async def submit_voice_issue( audio_filename = f"{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex}{file_extension}" audio_file_path = os.path.join(AUDIO_STORAGE_DIR, audio_filename) - with open(audio_file_path, 'wb') as f: - f.write(audio_content) + # Performance optimization: Wrap blocking synchronous File I/O in threadpool + def _save_audio_file(): + with open(audio_file_path, 'wb') as f: + f.write(audio_content) + + await run_in_threadpool(_save_audio_file) # 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 + # Format must match backend/routers/issues.py for a consistent chain + 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() @@ -267,6 +289,8 @@ async def submit_voice_issue( location=location, source='voice', status='open', + integrity_hash=integrity_hash, + previous_integrity_hash=prev_hash, # Voice-specific fields submission_type='voice', original_language=voice_result.get('source_language'), @@ -280,6 +304,9 @@ async def submit_voice_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')}") return IssueCreateResponse( From 34fffe11615c96dd3a2c71fbf12255d90af7d834 Mon Sep 17 00:00:00 2001 From: Rohan Gaikwad Date: Tue, 21 Apr 2026 11:14:25 +0530 Subject: [PATCH 2/2] Update backend/routers/voice.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backend/routers/voice.py | 44 +++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/backend/routers/voice.py b/backend/routers/voice.py index cd816567..a908f614 100644 --- a/backend/routers/voice.py +++ b/backend/routers/voice.py @@ -255,25 +255,37 @@ def _save_audio_file(): with open(audio_file_path, 'wb') as f: f.write(audio_content) + def _delete_audio_file_best_effort(): + try: + if os.path.exists(audio_file_path): + os.remove(audio_file_path) + except Exception as cleanup_error: + logger.warning("Failed to delete orphaned audio file %s: %s", audio_file_path, cleanup_error) + await run_in_threadpool(_save_audio_file) - # 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") + try: + # 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 - # Format must match backend/routers/issues.py for a consistent chain - hash_content = f"{final_description}|{issue_category.value}|{prev_hash}" + # Simple but effective SHA-256 chaining + # Format must match backend/routers/issues.py for a consistent chain + hash_content = f"{final_description}|{issue_category.value}|{prev_hash}" + except Exception: + db.rollback() + await run_in_threadpool(_delete_audio_file_best_effort) + raise integrity_hash = hashlib.sha256(hash_content.encode()).hexdigest() # Create issue in database