-
Notifications
You must be signed in to change notification settings - Fork 38
β‘ Bolt: Escalation Blockchain Integrity & N+1 Optimization #908
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a98d7ca
42d1b84
38d4c37
d4cc866
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,9 +4,11 @@ | |
| """ | ||
|
|
||
| import datetime | ||
| import hashlib | ||
| import threading | ||
| from typing import List, Dict, Any, Optional | ||
| from sqlalchemy.orm import Session | ||
| from sqlalchemy import and_, or_ | ||
| from sqlalchemy.orm import Session, joinedload | ||
| from sqlalchemy import and_, or_, desc | ||
| from backend.models import Grievance, Jurisdiction, EscalationAudit, GrievanceStatus, JurisdictionLevel, EscalationReason, SeverityLevel | ||
| from backend.database import SessionLocal | ||
| from backend.routing_service import RoutingService | ||
|
|
@@ -17,6 +19,11 @@ class EscalationEngine: | |
| Engine for handling grievance escalations based on SLA breaches and severity changes. | ||
| """ | ||
|
|
||
| # Cache for O(1) blockchain integrity hash lookups | ||
| # Stores {grievance_id: last_audit_integrity_hash} | ||
| _audit_last_hash_cache = {} | ||
| _cache_lock = threading.Lock() | ||
|
|
||
| def __init__(self, routing_service: RoutingService, sla_service: SLAConfigService, | ||
| rules_config: Dict[str, Any]): | ||
| """ | ||
|
|
@@ -111,13 +118,13 @@ def escalate_grievance_severity(self, grievance_id: int, new_severity: SeverityL | |
| if db is not SessionLocal(): | ||
| db.close() | ||
|
|
||
| def manual_escalate(self, grievance_id: int, reason: str = "", db: Session = None) -> bool: | ||
| def manual_escalate(self, grievance_id: int, notes: str = "", db: Session = None) -> bool: | ||
| """ | ||
| Manually escalate a grievance. | ||
|
|
||
| Args: | ||
| grievance_id: Grievance ID | ||
| reason: Reason for manual escalation | ||
| notes: Notes explaining the manual escalation | ||
| db: Database session | ||
|
|
||
| Returns: | ||
|
|
@@ -131,7 +138,7 @@ def manual_escalate(self, grievance_id: int, reason: str = "", db: Session = Non | |
| if not grievance: | ||
| return False | ||
|
|
||
| return self._escalate_grievance(grievance, EscalationReason.MANUAL, db, reason) | ||
| return self._escalate_grievance(grievance, EscalationReason.MANUAL, db, notes) | ||
|
|
||
| finally: | ||
| if db is not SessionLocal(): | ||
|
|
@@ -140,6 +147,7 @@ def manual_escalate(self, grievance_id: int, reason: str = "", db: Session = Non | |
| def _get_grievances_for_evaluation(self, db: Session) -> List[Grievance]: | ||
| """ | ||
| Get grievances that should be evaluated for escalation. | ||
| Bolt Optimization: Uses joinedload for Jurisdiction to avoid N+1 query problem. | ||
|
|
||
| Args: | ||
| db: Database session | ||
|
|
@@ -150,7 +158,9 @@ def _get_grievances_for_evaluation(self, db: Session) -> List[Grievance]: | |
| now = datetime.datetime.now(datetime.timezone.utc) | ||
|
|
||
| # Get grievances that are active and past SLA deadline | ||
| return db.query(Grievance).filter( | ||
| return db.query(Grievance).options( | ||
| joinedload(Grievance.jurisdiction) | ||
| ).filter( | ||
| and_( | ||
| Grievance.status.in_([GrievanceStatus.OPEN, GrievanceStatus.IN_PROGRESS, GrievanceStatus.ESCALATED]), | ||
| Grievance.sla_deadline < now | ||
|
|
@@ -248,25 +258,63 @@ def _escalate_grievance(self, grievance: Grievance, reason: EscalationReason, | |
| # Recalculate SLA | ||
| self._recalculate_sla(grievance, db) | ||
|
|
||
| # Blockchain-style integrity hashing | ||
| # Get previous hash (O(1) from cache or O(log N) from indexed DB) | ||
| prev_hash = self._get_last_audit_hash(grievance.id, db) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Concurrent escalations for one grievance can create sibling audit records with the same Prompt for AI agents |
||
|
|
||
| # Calculate new integrity hash: SHA256(grievance_id | reason | prev_hash) | ||
| hash_input = f"{grievance.id}|{reason.value}|{prev_hash or 'GENESIS'}" | ||
| new_hash = hashlib.sha256(hash_input.encode()).hexdigest() | ||
|
|
||
| # Create audit log | ||
| audit_log = EscalationAudit( | ||
| grievance_id=grievance.id, | ||
| previous_authority=previous_authority, | ||
| new_authority=grievance.assigned_authority, | ||
| reason=reason, | ||
| notes=notes | ||
| notes=notes, | ||
| integrity_hash=new_hash, | ||
| previous_integrity_hash=prev_hash | ||
| ) | ||
|
|
||
| db.add(audit_log) | ||
| db.commit() | ||
|
|
||
| # Update O(1) cache for next escalation | ||
| with self._cache_lock: | ||
| self._audit_last_hash_cache[grievance.id] = new_hash | ||
|
|
||
| return True | ||
|
|
||
| except Exception as e: | ||
| db.rollback() | ||
| print(f"Error during escalation: {e}") | ||
| return False | ||
|
|
||
| def _get_last_audit_hash(self, grievance_id: int, db: Session) -> Optional[str]: | ||
| """ | ||
| Retrieves the last audit integrity hash for a grievance. | ||
| Bolt Optimization: Uses thread-safe memory cache for O(1) lookup. | ||
| """ | ||
| with self._cache_lock: | ||
| if grievance_id in self._audit_last_hash_cache: | ||
| return self._audit_last_hash_cache[grievance_id] | ||
|
|
||
| # Cache miss: Fallback to indexed DB query | ||
| last_audit = db.query(EscalationAudit)\ | ||
| .filter(EscalationAudit.grievance_id == grievance_id)\ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Cold-cache lookups can scan Prompt for AI agents |
||
| .order_by(desc(EscalationAudit.id))\ | ||
| .first() | ||
|
|
||
| last_hash = last_audit.integrity_hash if last_audit else None | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: A grievance whose newest audit has a NULL Prompt for AI agents |
||
|
|
||
| # Update cache for next time | ||
| if last_hash: | ||
| with self._cache_lock: | ||
| self._audit_last_hash_cache[grievance_id] = last_hash | ||
|
|
||
| return last_hash | ||
|
|
||
| def _recalculate_sla(self, grievance: Grievance, db: Session) -> None: | ||
| """ | ||
| Recalculate SLA deadline for a grievance. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -319,18 +319,18 @@ def escalate_grievance_severity(self, grievance_id: int, new_severity: SeverityL | |
| """ | ||
| return self.escalation_engine.escalate_grievance_severity(grievance_id, new_severity, reason) | ||
|
|
||
| def manual_escalate(self, grievance_id: int, reason: str = "") -> bool: | ||
| def manual_escalate(self, grievance_id: int, notes: str = "") -> bool: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Callers using the documented Prompt for AI agents |
||
| """ | ||
| Manually escalate a grievance. | ||
|
|
||
| Args: | ||
| grievance_id: Grievance ID | ||
| reason: Reason for escalation | ||
| notes: Notes for escalation | ||
|
|
||
| Returns: | ||
| True if escalation successful | ||
| """ | ||
| return self.escalation_engine.manual_escalate(grievance_id, reason) | ||
| return self.escalation_engine.manual_escalate(grievance_id, notes) | ||
|
|
||
| def run_escalation_check(self) -> Dict[str, int]: | ||
| """ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -88,6 +88,16 @@ async def _make_request(client, image_bytes, labels): | |||||||||||||
| raise ExternalAPIException("Hugging Face API", str(e)) from e | ||||||||||||||
|
|
||||||||||||||
| def _prepare_image_bytes(image: Union[Image.Image, bytes]) -> bytes: | ||||||||||||||
| if isinstance(image, bytes): | ||||||||||||||
| return image | ||||||||||||||
|
|
||||||||||||||
| img_byte_arr = io.BytesIO() | ||||||||||||||
| # Handle different image formats | ||||||||||||||
| fmt = getattr(image, 'format', 'JPEG') or 'JPEG' | ||||||||||||||
| image.save(img_byte_arr, format=fmt) | ||||||||||||||
|
Comment on lines
+96
to
+97
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: RGBA and other non-RGB images without a source format are serialized as JPEG, which Pillow rejects and turns into an empty detection result. Converting non-RGB images before JPEG encoding, or choosing a format compatible with the image mode, preserves detection for these inputs. Prompt for AI agents
Suggested change
|
||||||||||||||
| return img_byte_arr.getvalue() | ||||||||||||||
|
|
||||||||||||||
| async def detect_vandalism_clip(image: Union[Image.Image, bytes], client: httpx.AsyncClient = None) -> List[Dict[str, Any]]: | ||||||||||||||
| """ | ||||||||||||||
| Detects vandalism/graffiti using Zero-Shot Image Classification with CLIP (Async). | ||||||||||||||
| Includes retry logic with exponential backoff for transient failures. | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The class-level cache retains one hash for every escalated grievance forever, so memory grows with the lifetime total rather than the active working set. Use a bounded or TTL cache, or provide explicit invalidation.
Prompt for AI agents