Initial commit for fresh VoiceInvoiceAI repo

This commit is contained in:
Hardik Sankhla
2025-06-25 23:03:48 +05:30
commit 6fe387d900
13 changed files with 1113 additions and 0 deletions

115
backend/core/utils.py Normal file
View File

@@ -0,0 +1,115 @@
# backend/core/utils.py
import torch
import gc
import re
import json
from datetime import datetime, timedelta
from typing import Dict, Any, Optional
from backend.config import user_db, item_db # These are now mock DBs, kept for autofill logic
from backend.models import InvoiceData, InvoiceItem, ValidationError # ValidationError for type hinting/catching
def clear_gpu_memory():
"""Clears CUDA memory cache and runs garbage collection."""
if torch.cuda.is_available():
torch.cuda.empty_cache()
gc.collect()
def autofill_invoice_data(invoice_data: InvoiceData) -> InvoiceData:
"""
Autofills invoice data based on simulated local databases (user_db, item_db).
Fills in missing client address, item prices, and calculates totals/dates.
Args:
invoice_data (InvoiceData): The invoice data Pydantic model to autofill.
Returns:
InvoiceData: The updated invoice data with autofilled information.
"""
# Create a mutable copy to avoid modifying the original object in place
# if this function is called on an object that might be reused or
# that came directly from a FastAPI request body.
invoice_data_copy = invoice_data.model_copy(deep=True)
if invoice_data_copy.client_name:
normalized_client_name = invoice_data_copy.client_name.lower().strip()
if normalized_client_name in user_db:
client_info = user_db[normalized_client_name]
if not invoice_data_copy.client_address:
invoice_data_copy.client_address = client_info.get("address")
# Only update tax_rate if it's not explicitly set in the incoming data
if invoice_data_copy.tax_rate is None or invoice_data_copy.tax_rate == 0.08: # Default value
invoice_data_copy.tax_rate = client_info.get("default_tax_rate", 0.08)
for item in invoice_data_copy.items:
# If unit_price is missing but description exists, try to autofill
if not item.unit_price and item.description:
normalized_description = item.description.lower().strip()
for db_item, db_info in item_db.items():
if db_item in normalized_description: # Simple substring match
item.unit_price = db_info.get("unit_price")
# Break after finding the first match
break
# Ensure item total is calculated or re-calculated after autofill of unit_price
if item.quantity is not None and item.unit_price is not None:
item.total = round(item.quantity * item.unit_price, 2)
elif item.total is None: # If no quantity/unit_price but total needed
item.total = 0.0 # Default to 0 if not enough info
# Recalculate totals after autofilling item prices and ensure they are not None
invoice_data_copy.subtotal = round(sum(item.total for item in invoice_data_copy.items if item.total is not None), 2)
effective_tax_rate = invoice_data_copy.tax_rate if invoice_data_copy.tax_rate is not None else 0.0
invoice_data_copy.tax_amount = round(invoice_data_copy.subtotal * effective_tax_rate, 2)
invoice_data_copy.grand_total = round(invoice_data_copy.subtotal + invoice_data_copy.tax_amount, 2)
# Handle dates
if not invoice_data_copy.invoice_date:
invoice_data_copy.invoice_date = datetime.now().strftime("%Y-%m-%d")
if not invoice_data_copy.due_date:
try:
invoice_date_dt = datetime.strptime(invoice_data_copy.invoice_date, "%Y-%m-%d")
invoice_data_copy.due_date = (invoice_date_dt + timedelta(days=30)).strftime("%Y-%m-%d")
except (ValueError, TypeError):
# Fallback if invoice_date is invalid or not set
invoice_data_copy.due_date = (datetime.now() + timedelta(days=30)).strftime("%Y-%m-%d")
return invoice_data_copy
def check_model_devices(qwen2_audio_model: Optional[Any], device: str) -> str:
"""
Checks the device placement and memory usage of the Qwen2-Audio model.
"""
status_output = []
status_output.append("--- Checking Model Device Placement ---")
if qwen2_audio_model:
try:
if hasattr(qwen2_audio_model, 'hf_device_map') and qwen2_audio_model.hf_device_map:
status_output.append(f"Qwen2-Audio model device map: {qwen2_audio_model.hf_device_map}")
else:
qwen_device = next(qwen2_audio_model.parameters()).device
status_output.append(f"Qwen2-Audio model (first parameter) is on: {qwen_device}")
status_output.append(f"Qwen2-Audio model data type: {next(qwen2_audio_model.parameters()).dtype}")
if device == "cuda":
status_output.append(f"Current GPU Memory Allocated: {torch.cuda.memory_allocated() / (1024**3):.2f} GB")
status_output.append(f"Current GPU Memory Cached: {torch.cuda.memory_reserved() / (1024**3):.2f} GB")
status_output.append(f"Max GPU Memory Allocated: {torch.cuda.max_memory_allocated() / (1024**3):.2f} GB")
status_output.append(f"Max GPU Memory Cached: {torch.cuda.max_memory_reserved() / (1024**3):.2f} GB")
except Exception as e:
status_output.append(f"Error checking model device: {e}")
else:
status_output.append("Qwen2-Audio model is not loaded.")
status_output.append(f"System detected device: {device}")
if device == "cuda":
status_output.append(f"Number of CUDA devices: {torch.cuda.device_count()}")
if torch.cuda.device_count() > 0:
status_output.append(f"Current CUDA device: {torch.cuda.current_device()} ({torch.cuda.get_device_name(torch.cuda.current_device())})")
status_output.append(f"Total GPU Memory on current device: {torch.cuda.get_device_properties(torch.cuda.current_device()).total_memory / (1024**3):.2f} GB")
else:
status_output.append("No CUDA devices detected. Running on CPU.")
return "\n".join(status_output)