File size: 5,086 Bytes
24f95f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
from typing import List
import asyncio
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.models import ScamEvent, Entity, EventEntity
from app.schemas.response import AnalyzeResponse
from app.db.session import AsyncSessionLocal
from sqlalchemy import select, update
import uuid
from datetime import datetime

from app.services.scam_graph import scam_graph
from app.services.simulation_engine import simulation_engine

class MemoryService:
    async def save_event(self, response: AnalyzeResponse, metadata: dict, embedding: list = None):
        async with AsyncSessionLocal() as session:
            # 1. Save Event to Postgres
            event = ScamEvent(
                id=uuid.UUID(response.id),
                text=response.text,
                source=response.source,
                risk_score=response.risk_score,
                decision=response.decision,
                event_metadata={
                    "reasons": response.reasons,
                    "intent": response.intent.dict(),
                    "entities": response.entities.dict(),
                    "live_intel": {
                        "evidence": response.evidence,
                        "claimed_brand": response.claimed_brand,
                        "official_verify": response.official_verify.dict() if response.official_verify else None,
                        "next_steps": response.next_steps,
                        "verdict_synthesis": response.verdict_synthesis,
                        "breadcrumbs": response.breadcrumbs,
                        "similarity": response.similarity
                    },
                    **metadata
                },
                embedding=embedding
            )
            session.add(event)
            
            # 2. Add to Scam Journey Graph (Depth)
            graph_entities = {
                "phones": response.entities.phones,
                "upi_ids": response.entities.upi_ids,
                "links": response.entities.domains
            }
            # Normalize intent signals for graph
            signals = {
                "urgency": int(response.intent.urgency * 10),
                "impersonation": int(response.intent.impersonation * 10),
                "payment": int(response.intent.payment * 10)
            }
            scam_graph.add_event(response.source, graph_entities, signals)
            
            # 3. Optimization: Autonomous Security Drill
            if response.risk_score > 85:
                drill_scenario = f"The user has received this suspicious intake: '{response.text[:200]}...'. Simulation: What is the most likely social engineering escalation the scammer will try next?"
                # Trigger background simulation
                asyncio.create_task(simulation_engine.run_simulation(drill_scenario, {"source": response.source, "risk": response.risk_score}))

            # 4. Process Entities for relational mapping
            for phone in response.entities.phones:
                await self._upsert_entity(session, "phone", phone, event.id)
            for upi in response.entities.upi_ids:
                await self._upsert_entity(session, "upi", upi, event.id)
            for domain in response.entities.domains:
                await self._upsert_entity(session, "domain", domain, event.id)
                
            await session.commit()

    async def _upsert_entity(self, session: AsyncSession, e_type: str, value: str, event_id: uuid.UUID):
        stmt = select(Entity).where(Entity.type == e_type, Entity.value == value)
        result = await session.execute(stmt)
        entity = result.scalar_one_or_none()
        
        if not entity:
            entity = Entity(type=e_type, value=value)
            session.add(entity)
            await session.flush()
        else:
            entity.last_seen = datetime.utcnow()
            
        # Link to event
        link = EventEntity(event_id=event_id, entity_id=entity.id)
        session.add(link)

    async def check_overlap(self, phones: List[str], upis: List[str], domains: List[str]) -> List[dict]:
        """Detect if these entities have appeared in previous reports."""
        async with AsyncSessionLocal() as session:
            overlaps = []
            all_vals = [("phone", p) for p in phones] + [("upi", u) for u in upis] + [("domain", d) for d in domains]
            
            for e_type, val in all_vals:
                stmt = select(Entity).where(Entity.type == e_type, Entity.value == val)
                result = await session.execute(stmt)
                entity = result.scalar_one_or_none()
                if entity:
                    overlaps.append({
                        "source": "Janus Memory Graph",
                        "signal": "infrastructure_overlap",
                        "value": val,
                        "severity": "high",
                        "explanation": f"This '{val}' has been encountered in previous reports. Recidivism detected."
                    })
            return overlaps

memory_service = MemoryService()