Spaces:
Sleeping
Sleeping
barathvasan-dev commited on
Commit Β·
279c530
1
Parent(s): 25c874b
Refactor: Consolidate AI Investigation into single unified agent tab with RAG capabilities
Browse files- Replace 4 separate investigation tabs (Vehicle, Area, Midnight, Most Suspicious) with 1 unified tab
- Add ask_investigation_question() function for natural language queries with RAG
- Agent now dynamically understands questions and fetches relevant database data
- Users can ask any question (e.g. 'vehicles in Adyar at midnight', 'suspicious activity in industrial zones')
- Mistral model acts as RAG agent to analyze data and provide intelligent answers
- Add example quick-start buttons for common investigation types
- ai_investigation.py +281 -3
- app.py +122 -159
ai_investigation.py
CHANGED
|
@@ -536,6 +536,283 @@ def find_most_suspicious_today():
|
|
| 536 |
# FORMAT OUTPUT
|
| 537 |
# =====================================================
|
| 538 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
def format_investigation_output(result):
|
| 540 |
"""Format investigation result for display"""
|
| 541 |
if result.get("status") == "error":
|
|
@@ -544,11 +821,12 @@ def format_investigation_output(result):
|
|
| 544 |
}
|
| 545 |
|
| 546 |
return {
|
| 547 |
-
"query": result.get("plate") or result.get("location") or "
|
| 548 |
"investigation_narrative": result.get("investigation_narrative", ""),
|
|
|
|
| 549 |
"analysis": result.get("analysis", {}),
|
| 550 |
-
"key_findings":
|
| 551 |
-
"recommendations": _extract_recommendations(result.get("investigation_narrative", ""))
|
| 552 |
}
|
| 553 |
|
| 554 |
|
|
|
|
| 536 |
# FORMAT OUTPUT
|
| 537 |
# =====================================================
|
| 538 |
|
| 539 |
+
# =====================================================
|
| 540 |
+
# UNIFIED AI AGENT - NATURAL LANGUAGE QUESTIONS
|
| 541 |
+
# =====================================================
|
| 542 |
+
|
| 543 |
+
def ask_investigation_question(question):
|
| 544 |
+
"""
|
| 545 |
+
Unified AI Investigation Agent
|
| 546 |
+
|
| 547 |
+
Takes any natural language question and:
|
| 548 |
+
1. Uses Mistral to understand what data is needed
|
| 549 |
+
2. Queries the database based on the question intent
|
| 550 |
+
3. Uses Mistral to generate intelligent answer with RAG
|
| 551 |
+
"""
|
| 552 |
+
|
| 553 |
+
if not question or len(question.strip()) < 3:
|
| 554 |
+
return {
|
| 555 |
+
"status": "error",
|
| 556 |
+
"message": "Please enter a valid question"
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
+
client = get_mistral_client()
|
| 560 |
+
|
| 561 |
+
# Step 1: Understand query intent
|
| 562 |
+
intent_prompt = f"""Analyze this surveillance question and determine what type of data is needed.
|
| 563 |
+
|
| 564 |
+
Question: {question}
|
| 565 |
+
|
| 566 |
+
Respond with ONLY one of these intents in the format:
|
| 567 |
+
INTENT: [type]
|
| 568 |
+
QUERY: [specific value or description]
|
| 569 |
+
|
| 570 |
+
Types and examples:
|
| 571 |
+
1. VEHICLE - plate number like "TN12UP3854" or "find vehicle in [location]"
|
| 572 |
+
2. LOCATION - area name like "Adyar", "Nungambakkam", "Guindy"
|
| 573 |
+
3. MIDNIGHT - late night activity in specific area or general
|
| 574 |
+
4. ACTIVITY - activity patterns in area during specific times
|
| 575 |
+
5. SUSPICIOUS - find suspicious vehicles matching criteria
|
| 576 |
+
6. TIME_RANGE - activity in specific time period or area
|
| 577 |
+
|
| 578 |
+
Extract the specific target from the question (plate, location, time, etc)."""
|
| 579 |
+
|
| 580 |
+
try:
|
| 581 |
+
if client:
|
| 582 |
+
intent_response = client.chat(
|
| 583 |
+
model="mistral-small",
|
| 584 |
+
messages=[{"role": "user", "content": intent_prompt}],
|
| 585 |
+
temperature=0.3,
|
| 586 |
+
max_tokens=100
|
| 587 |
+
)
|
| 588 |
+
intent_text = intent_response.choices[0].message.content
|
| 589 |
+
else:
|
| 590 |
+
intent_text = _parse_intent_fallback(question)
|
| 591 |
+
|
| 592 |
+
print(f"Intent parsed: {intent_text}")
|
| 593 |
+
|
| 594 |
+
# Step 2: Execute query based on intent
|
| 595 |
+
data_context = _execute_intelligent_query(intent_text, question)
|
| 596 |
+
|
| 597 |
+
# Step 3: Generate answer with RAG
|
| 598 |
+
answer = _generate_rag_answer(question, data_context, client)
|
| 599 |
+
|
| 600 |
+
return {
|
| 601 |
+
"status": "success",
|
| 602 |
+
"question": question,
|
| 603 |
+
"answer": answer,
|
| 604 |
+
"data_summary": data_context.get("summary", ""),
|
| 605 |
+
"analysis": data_context.get("analysis", {}),
|
| 606 |
+
"findings": data_context.get("findings", [])
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
except Exception as e:
|
| 610 |
+
print(f"Error in investigation: {e}")
|
| 611 |
+
return {
|
| 612 |
+
"status": "error",
|
| 613 |
+
"message": f"Investigation failed: {str(e)}"
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
|
| 617 |
+
def _execute_intelligent_query(intent_text, question):
|
| 618 |
+
"""Execute database query based on parsed intent"""
|
| 619 |
+
|
| 620 |
+
intent_text = intent_text.upper()
|
| 621 |
+
data = {
|
| 622 |
+
"summary": "",
|
| 623 |
+
"analysis": {},
|
| 624 |
+
"findings": [],
|
| 625 |
+
"raw_data": None
|
| 626 |
+
}
|
| 627 |
+
|
| 628 |
+
try:
|
| 629 |
+
# Parse intent and query
|
| 630 |
+
if "INTENT: VEHICLE" in intent_text:
|
| 631 |
+
# Extract plate from question or intent
|
| 632 |
+
plate = _extract_plate_from_text(intent_text + " " + question)
|
| 633 |
+
if plate:
|
| 634 |
+
df = get_vehicle_detections(plate, days=30)
|
| 635 |
+
if not df.empty:
|
| 636 |
+
analysis = analyze_vehicle_behavior(df)
|
| 637 |
+
data["analysis"] = analysis
|
| 638 |
+
data["summary"] = f"Found {len(df)} detections for vehicle {plate}"
|
| 639 |
+
data["findings"] = _extract_key_findings({"analysis": analysis})
|
| 640 |
+
data["raw_data"] = df.to_dict(orient="records")[:10]
|
| 641 |
+
|
| 642 |
+
elif "INTENT: LOCATION" in intent_text:
|
| 643 |
+
# Extract location
|
| 644 |
+
location = _extract_location_from_text(intent_text + " " + question)
|
| 645 |
+
if location:
|
| 646 |
+
df = get_area_activity(location, days=7)
|
| 647 |
+
if not df.empty:
|
| 648 |
+
analysis = analyze_area_patterns(df)
|
| 649 |
+
data["analysis"] = analysis
|
| 650 |
+
data["summary"] = f"Found {len(df)} detections in {location}"
|
| 651 |
+
data["findings"] = _extract_key_findings({"analysis": analysis})
|
| 652 |
+
data["raw_data"] = df.to_dict(orient="records")[:10]
|
| 653 |
+
|
| 654 |
+
elif "INTENT: MIDNIGHT" in intent_text:
|
| 655 |
+
# Extract location if mentioned
|
| 656 |
+
location = _extract_location_from_text(question)
|
| 657 |
+
df = get_midnight_activity(hours_start=22, hours_end=4, days=7)
|
| 658 |
+
|
| 659 |
+
if location:
|
| 660 |
+
df = df[df["location"].str.contains(location, case=False, na=False)]
|
| 661 |
+
|
| 662 |
+
if not df.empty:
|
| 663 |
+
analysis = analyze_midnight_patterns(df)
|
| 664 |
+
data["analysis"] = analysis
|
| 665 |
+
data["summary"] = f"Found {len(df)} midnight detections"
|
| 666 |
+
data["findings"] = _extract_key_findings({"analysis": analysis})
|
| 667 |
+
data["raw_data"] = df.to_dict(orient="records")[:10]
|
| 668 |
+
|
| 669 |
+
elif "INTENT: ACTIVITY" in intent_text:
|
| 670 |
+
# Get activity in specific area
|
| 671 |
+
location = _extract_location_from_text(question)
|
| 672 |
+
if location:
|
| 673 |
+
df = get_area_activity(location, days=7)
|
| 674 |
+
if not df.empty:
|
| 675 |
+
analysis = analyze_area_patterns(df)
|
| 676 |
+
data["analysis"] = analysis
|
| 677 |
+
data["summary"] = f"Analyzed activity in {location}"
|
| 678 |
+
data["findings"] = _extract_key_findings({"analysis": analysis})
|
| 679 |
+
data["raw_data"] = df.to_dict(orient="records")[:10]
|
| 680 |
+
|
| 681 |
+
if not data["summary"]:
|
| 682 |
+
# Fallback: get general statistics
|
| 683 |
+
query = "SELECT COUNT(*) as total_detections, COUNT(DISTINCT plate) as unique_vehicles, COUNT(DISTINCT location) as unique_locations FROM vehicle_logs WHERE date >= CURRENT_DATE - INTERVAL '7 days'"
|
| 684 |
+
with engine.connect() as conn:
|
| 685 |
+
result = conn.execute(text(query))
|
| 686 |
+
row = result.fetchone()
|
| 687 |
+
if row:
|
| 688 |
+
data["summary"] = f"Database has {row[0]} total detections, {row[1]} unique vehicles, {row[2]} unique locations"
|
| 689 |
+
|
| 690 |
+
except Exception as e:
|
| 691 |
+
print(f"Error executing query: {e}")
|
| 692 |
+
data["summary"] = "Unable to fetch data from database"
|
| 693 |
+
|
| 694 |
+
return data
|
| 695 |
+
|
| 696 |
+
|
| 697 |
+
def _generate_rag_answer(question, data_context, client):
|
| 698 |
+
"""Generate RAG-based answer using Mistral"""
|
| 699 |
+
|
| 700 |
+
context_str = json.dumps(data_context, indent=2, default=str)
|
| 701 |
+
|
| 702 |
+
rag_prompt = f"""You are a professional surveillance and traffic analysis AI agent.
|
| 703 |
+
|
| 704 |
+
User Question: {question}
|
| 705 |
+
|
| 706 |
+
Available Data:
|
| 707 |
+
{context_str}
|
| 708 |
+
|
| 709 |
+
Generate a detailed, professional answer to the user's question using the available data.
|
| 710 |
+
|
| 711 |
+
Guidelines:
|
| 712 |
+
1. Answer directly and concisely
|
| 713 |
+
2. Use the data to provide specific insights
|
| 714 |
+
3. Highlight patterns, anomalies, and recommendations
|
| 715 |
+
4. Be factual and data-driven
|
| 716 |
+
5. If data is limited, explain what additional data would help
|
| 717 |
+
|
| 718 |
+
Format your response in a clear, organized manner with sections if needed."""
|
| 719 |
+
|
| 720 |
+
try:
|
| 721 |
+
if client:
|
| 722 |
+
response = client.chat(
|
| 723 |
+
model="mistral-small",
|
| 724 |
+
messages=[{"role": "user", "content": rag_prompt}],
|
| 725 |
+
temperature=0.3,
|
| 726 |
+
max_tokens=800
|
| 727 |
+
)
|
| 728 |
+
return response.choices[0].message.content
|
| 729 |
+
else:
|
| 730 |
+
return _generate_fallback_rag_answer(question, data_context)
|
| 731 |
+
|
| 732 |
+
except Exception as e:
|
| 733 |
+
print(f"Error generating answer: {e}")
|
| 734 |
+
return _generate_fallback_rag_answer(question, data_context)
|
| 735 |
+
|
| 736 |
+
|
| 737 |
+
def _generate_fallback_rag_answer(question, data_context):
|
| 738 |
+
"""Fallback answer generation without Mistral"""
|
| 739 |
+
|
| 740 |
+
answer = f"**Analysis for:** {question}\n\n"
|
| 741 |
+
answer += f"**Summary:** {data_context.get('summary', 'No data available')}\n\n"
|
| 742 |
+
|
| 743 |
+
if data_context.get("analysis"):
|
| 744 |
+
answer += "**Key Metrics:**\n"
|
| 745 |
+
for key, value in list(data_context["analysis"].items())[:5]:
|
| 746 |
+
answer += f"β’ {key}: {value}\n"
|
| 747 |
+
|
| 748 |
+
if data_context.get("findings"):
|
| 749 |
+
answer += "\n**Findings:**\n"
|
| 750 |
+
for finding in data_context["findings"]:
|
| 751 |
+
answer += f"β’ {finding}\n"
|
| 752 |
+
|
| 753 |
+
return answer
|
| 754 |
+
|
| 755 |
+
|
| 756 |
+
def _extract_plate_from_text(text):
|
| 757 |
+
"""Extract license plate from text"""
|
| 758 |
+
import re
|
| 759 |
+
# Look for plate patterns like "TN12UP3854" or "TN 12 UP 3854"
|
| 760 |
+
plate_patterns = [
|
| 761 |
+
r'([A-Z]{2}\d{2}[A-Z]{2}\d{4})',
|
| 762 |
+
r'([A-Z]{2} \d{2} [A-Z]{2} \d{4})',
|
| 763 |
+
]
|
| 764 |
+
|
| 765 |
+
for pattern in plate_patterns:
|
| 766 |
+
match = re.search(pattern, text.upper())
|
| 767 |
+
if match:
|
| 768 |
+
return match.group(1).replace(" ", "")
|
| 769 |
+
|
| 770 |
+
return None
|
| 771 |
+
|
| 772 |
+
|
| 773 |
+
def _extract_location_from_text(text):
|
| 774 |
+
"""Extract location from text"""
|
| 775 |
+
# Common locations in Chennai
|
| 776 |
+
locations = [
|
| 777 |
+
"adyar", "anna nagar", "nungambakkam", "guindy", "chetpet",
|
| 778 |
+
"tnagar", "t nagar", "mylapore", "velachery", "tambaram",
|
| 779 |
+
"chromepet", "madhavaram", "tiruvottiyur", "ayanavaram",
|
| 780 |
+
"anna salai", "mg road", "mount road", "geek city",
|
| 781 |
+
"industrial estate", "delphi", "siruseri", "sholinganallur"
|
| 782 |
+
]
|
| 783 |
+
|
| 784 |
+
text_lower = text.lower()
|
| 785 |
+
for location in locations:
|
| 786 |
+
if location in text_lower:
|
| 787 |
+
return location.title()
|
| 788 |
+
|
| 789 |
+
# Try to find any capitalized word that might be a location
|
| 790 |
+
import re
|
| 791 |
+
words = re.findall(r'\b[A-Z][a-z]+(?:\s[A-Z][a-z]+)*\b', text)
|
| 792 |
+
if words:
|
| 793 |
+
return words[-1]
|
| 794 |
+
|
| 795 |
+
return None
|
| 796 |
+
|
| 797 |
+
|
| 798 |
+
def _parse_intent_fallback(question):
|
| 799 |
+
"""Fallback intent parsing without Mistral"""
|
| 800 |
+
q_lower = question.lower()
|
| 801 |
+
|
| 802 |
+
if any(word in q_lower for word in ["vehicle", "plate", "car", "auto"]):
|
| 803 |
+
return "INTENT: VEHICLE"
|
| 804 |
+
elif any(word in q_lower for word in ["midnight", "night", "late", "early morning", "2am", "3am"]):
|
| 805 |
+
return "INTENT: MIDNIGHT"
|
| 806 |
+
elif any(word in q_lower for word in ["area", "location", "place", "zone", "street", "road"]):
|
| 807 |
+
return "INTENT: LOCATION"
|
| 808 |
+
elif any(word in q_lower for word in ["activity", "movement", "traffic", "busy"]):
|
| 809 |
+
return "INTENT: ACTIVITY"
|
| 810 |
+
elif any(word in q_lower for word in ["suspicious", "alert", "danger", "risk", "threat"]):
|
| 811 |
+
return "INTENT: SUSPICIOUS"
|
| 812 |
+
else:
|
| 813 |
+
return "INTENT: ACTIVITY"
|
| 814 |
+
|
| 815 |
+
|
| 816 |
def format_investigation_output(result):
|
| 817 |
"""Format investigation result for display"""
|
| 818 |
if result.get("status") == "error":
|
|
|
|
| 821 |
}
|
| 822 |
|
| 823 |
return {
|
| 824 |
+
"query": result.get("plate") or result.get("location") or "Investigation",
|
| 825 |
"investigation_narrative": result.get("investigation_narrative", ""),
|
| 826 |
+
"answer": result.get("answer", ""),
|
| 827 |
"analysis": result.get("analysis", {}),
|
| 828 |
+
"key_findings": result.get("findings", []),
|
| 829 |
+
"recommendations": _extract_recommendations(result.get("investigation_narrative", "") or result.get("answer", ""))
|
| 830 |
}
|
| 831 |
|
| 832 |
|
app.py
CHANGED
|
@@ -40,6 +40,7 @@ from ai_investigation import (
|
|
| 40 |
investigate_area,
|
| 41 |
investigate_midnight_activity,
|
| 42 |
find_most_suspicious_today,
|
|
|
|
| 43 |
format_investigation_output
|
| 44 |
)
|
| 45 |
|
|
@@ -99,6 +100,56 @@ def investigate_area_wrapper(location):
|
|
| 99 |
)
|
| 100 |
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
def investigate_midnight_wrapper():
|
| 103 |
"""Wrapper for midnight activity investigation"""
|
| 104 |
result = investigate_midnight_activity()
|
|
@@ -623,181 +674,93 @@ with gr.Blocks(
|
|
| 623 |
|
| 624 |
|
| 625 |
# =====================================================
|
| 626 |
-
# TAB 3: AI Assistant
|
| 627 |
# =====================================================
|
| 628 |
|
| 629 |
with gr.Tab("π AI Investigation", id="tab_investigation"):
|
| 630 |
|
| 631 |
gr.Markdown("""
|
| 632 |
-
# π AI Investigation Assistant
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 633 |
|
| 634 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 635 |
""")
|
| 636 |
-
|
| 637 |
-
with gr.
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
gr.Markdown("### Investigate single vehicle behavior")
|
| 645 |
-
|
| 646 |
-
invest_plate = gr.Textbox(
|
| 647 |
-
placeholder="TN12UP3854",
|
| 648 |
-
label="License Plate",
|
| 649 |
-
scale=2
|
| 650 |
-
)
|
| 651 |
-
|
| 652 |
-
investigate_btn = gr.Button("π Investigate Vehicle", variant="primary")
|
| 653 |
-
|
| 654 |
-
with gr.Row():
|
| 655 |
-
invest_narrative = gr.Textbox(
|
| 656 |
-
label="π Investigation Narrative",
|
| 657 |
-
lines=8,
|
| 658 |
-
interactive=False
|
| 659 |
-
)
|
| 660 |
-
|
| 661 |
-
invest_findings = gr.Textbox(
|
| 662 |
-
label="π¨ Key Findings",
|
| 663 |
-
lines=8,
|
| 664 |
-
interactive=False
|
| 665 |
-
)
|
| 666 |
-
|
| 667 |
-
invest_analysis = gr.JSON(
|
| 668 |
-
label="π Detailed Analysis"
|
| 669 |
-
)
|
| 670 |
-
|
| 671 |
-
invest_recommendations = gr.Textbox(
|
| 672 |
-
label="β
Recommendations",
|
| 673 |
-
lines=6,
|
| 674 |
-
interactive=False
|
| 675 |
-
)
|
| 676 |
-
|
| 677 |
-
investigate_btn.click(
|
| 678 |
-
fn=investigate_vehicle_wrapper,
|
| 679 |
-
inputs=[invest_plate],
|
| 680 |
-
outputs=[invest_narrative, invest_findings, invest_analysis, invest_recommendations]
|
| 681 |
-
)
|
| 682 |
-
|
| 683 |
-
# =====================================================
|
| 684 |
-
# INVESTIGATION TYPE 2: AREA
|
| 685 |
-
# =====================================================
|
| 686 |
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
area_investigate_btn = gr.Button("π Analyze Area", variant="primary")
|
| 697 |
-
|
| 698 |
-
with gr.Row():
|
| 699 |
-
area_narrative = gr.Textbox(
|
| 700 |
-
label="π Area Intelligence Report",
|
| 701 |
-
lines=8,
|
| 702 |
-
interactive=False
|
| 703 |
-
)
|
| 704 |
-
|
| 705 |
-
area_findings = gr.Textbox(
|
| 706 |
-
label="π¨ Key Findings",
|
| 707 |
-
lines=8,
|
| 708 |
-
interactive=False
|
| 709 |
-
)
|
| 710 |
-
|
| 711 |
-
area_analysis = gr.JSON(
|
| 712 |
-
label="π Area Analysis"
|
| 713 |
-
)
|
| 714 |
-
|
| 715 |
-
area_recommendations = gr.Textbox(
|
| 716 |
-
label="β
Security Recommendations",
|
| 717 |
-
lines=6,
|
| 718 |
interactive=False
|
| 719 |
)
|
| 720 |
-
|
| 721 |
-
area_investigate_btn.click(
|
| 722 |
-
fn=investigate_area_wrapper,
|
| 723 |
-
inputs=[invest_location],
|
| 724 |
-
outputs=[area_narrative, area_findings, area_analysis, area_recommendations]
|
| 725 |
-
)
|
| 726 |
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
with gr.Tab("π Midnight Activity"):
|
| 732 |
-
gr.Markdown("### Find suspicious midnight/late-night movements")
|
| 733 |
-
|
| 734 |
-
midnight_btn = gr.Button("π Analyze Midnight Activity", variant="primary", scale=2)
|
| 735 |
-
|
| 736 |
-
with gr.Row():
|
| 737 |
-
midnight_narrative = gr.Textbox(
|
| 738 |
-
label="π Night Activity Report",
|
| 739 |
-
lines=8,
|
| 740 |
-
interactive=False
|
| 741 |
-
)
|
| 742 |
-
|
| 743 |
-
midnight_findings = gr.Textbox(
|
| 744 |
-
label="π¨ Suspicious Patterns",
|
| 745 |
-
lines=8,
|
| 746 |
-
interactive=False
|
| 747 |
-
)
|
| 748 |
-
|
| 749 |
-
midnight_analysis = gr.JSON(
|
| 750 |
-
label="π Night Analysis"
|
| 751 |
-
)
|
| 752 |
-
|
| 753 |
-
midnight_recommendations = gr.Textbox(
|
| 754 |
-
label="β
Tactical Recommendations",
|
| 755 |
-
lines=6,
|
| 756 |
interactive=False
|
| 757 |
)
|
| 758 |
-
|
| 759 |
-
midnight_btn.click(
|
| 760 |
-
fn=investigate_midnight_wrapper,
|
| 761 |
-
inputs=[],
|
| 762 |
-
outputs=[midnight_narrative, midnight_findings, midnight_analysis, midnight_recommendations]
|
| 763 |
-
)
|
| 764 |
-
|
| 765 |
-
# =====================================================
|
| 766 |
-
# INVESTIGATION TYPE 4: TODAY'S MOST SUSPICIOUS
|
| 767 |
-
# =====================================================
|
| 768 |
|
| 769 |
-
with gr.Tab("
|
| 770 |
-
gr.
|
| 771 |
-
|
| 772 |
-
suspicious_btn = gr.Button("π Find Most Suspicious", variant="primary", scale=2)
|
| 773 |
-
|
| 774 |
-
with gr.Row():
|
| 775 |
-
suspicious_narrative = gr.Textbox(
|
| 776 |
-
label="π Investigation Summary",
|
| 777 |
-
lines=8,
|
| 778 |
-
interactive=False
|
| 779 |
-
)
|
| 780 |
-
|
| 781 |
-
suspicious_findings = gr.Textbox(
|
| 782 |
-
label="π¨ Alert Details",
|
| 783 |
-
lines=8,
|
| 784 |
-
interactive=False
|
| 785 |
-
)
|
| 786 |
-
|
| 787 |
-
suspicious_analysis = gr.JSON(
|
| 788 |
-
label="π Detection Analysis"
|
| 789 |
)
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 795 |
)
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
|
|
|
| 801 |
)
|
| 802 |
|
| 803 |
# =====================================================
|
|
|
|
| 40 |
investigate_area,
|
| 41 |
investigate_midnight_activity,
|
| 42 |
find_most_suspicious_today,
|
| 43 |
+
ask_investigation_question,
|
| 44 |
format_investigation_output
|
| 45 |
)
|
| 46 |
|
|
|
|
| 100 |
)
|
| 101 |
|
| 102 |
|
| 103 |
+
def investigate_midnight_wrapper():
|
| 104 |
+
"""Wrapper for midnight investigation"""
|
| 105 |
+
result = investigate_midnight_activity()
|
| 106 |
+
if result.get("status") == "error":
|
| 107 |
+
error_msg = result.get("message", "Investigation failed")
|
| 108 |
+
return (f"β Error: {error_msg}", "", {}, "")
|
| 109 |
+
|
| 110 |
+
output = format_investigation_output(result)
|
| 111 |
+
return (
|
| 112 |
+
output.get("investigation_narrative", ""),
|
| 113 |
+
"\n".join(output.get("key_findings", [])),
|
| 114 |
+
output.get("analysis", {}),
|
| 115 |
+
"\n".join(output.get("recommendations", []))
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def find_suspicious_wrapper():
|
| 120 |
+
"""Wrapper for finding most suspicious today"""
|
| 121 |
+
result = find_most_suspicious_today()
|
| 122 |
+
if result.get("status") == "error":
|
| 123 |
+
error_msg = result.get("message", "Investigation failed")
|
| 124 |
+
return (f"β Error: {error_msg}", "", {}, "")
|
| 125 |
+
|
| 126 |
+
output = format_investigation_output(result)
|
| 127 |
+
return (
|
| 128 |
+
output.get("investigation_narrative", ""),
|
| 129 |
+
"\n".join(output.get("key_findings", [])),
|
| 130 |
+
output.get("analysis", {}),
|
| 131 |
+
"\n".join(output.get("recommendations", []))
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def ask_question_wrapper(question):
|
| 136 |
+
"""Unified AI Investigation Agent - handles any natural language question"""
|
| 137 |
+
if not question or len(question.strip()) < 3:
|
| 138 |
+
return ("Please ask a valid question about vehicles, locations, or activities", "", {}, "")
|
| 139 |
+
|
| 140 |
+
result = ask_investigation_question(question)
|
| 141 |
+
if result.get("status") == "error":
|
| 142 |
+
error_msg = result.get("message", "Investigation failed")
|
| 143 |
+
return (f"β Error: {error_msg}", "", {}, "")
|
| 144 |
+
|
| 145 |
+
return (
|
| 146 |
+
result.get("answer", ""),
|
| 147 |
+
"\n".join(result.get("findings", [])),
|
| 148 |
+
result.get("analysis", {}),
|
| 149 |
+
""
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
def investigate_midnight_wrapper():
|
| 154 |
"""Wrapper for midnight activity investigation"""
|
| 155 |
result = investigate_midnight_activity()
|
|
|
|
| 674 |
|
| 675 |
|
| 676 |
# =====================================================
|
| 677 |
+
# TAB 3: AI Assistant - Unified Investigation Agent
|
| 678 |
# =====================================================
|
| 679 |
|
| 680 |
with gr.Tab("π AI Investigation", id="tab_investigation"):
|
| 681 |
|
| 682 |
gr.Markdown("""
|
| 683 |
+
# π AI Investigation Assistant - Smart Agent with RAG
|
| 684 |
+
|
| 685 |
+
Ask any question about vehicles, locations, or activities. The AI agent will:
|
| 686 |
+
- π€ Understand your question
|
| 687 |
+
- π Fetch relevant data from the database
|
| 688 |
+
- π§ Analyze patterns and provide intelligent insights
|
| 689 |
+
- π Generate detailed investigation reports
|
| 690 |
|
| 691 |
+
**Examples of questions you can ask:**
|
| 692 |
+
- "What vehicles were in Adyar at midnight?"
|
| 693 |
+
- "Show me suspicious activity in Nungambakkam between 10PM and 3AM"
|
| 694 |
+
- "Which vehicles are most active in industrial zones?"
|
| 695 |
+
- "What's the activity level in Guindy today?"
|
| 696 |
+
- "Find vehicles with multiple detections in different areas"
|
| 697 |
+
- "Which plates appear most frequently at night?"
|
| 698 |
+
- "Analyze traffic patterns in Anna Nagar"
|
| 699 |
""")
|
| 700 |
+
|
| 701 |
+
with gr.Row():
|
| 702 |
+
question_input = gr.Textbox(
|
| 703 |
+
placeholder="e.g., What vehicles were detected in Adyar at midnight? Or: Show me suspicious activity in industrial zones tonight",
|
| 704 |
+
label="π€ Ask Any Question About Vehicles, Locations, or Activities",
|
| 705 |
+
lines=3,
|
| 706 |
+
scale=5
|
| 707 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 708 |
|
| 709 |
+
investigate_btn = gr.Button("π Investigate", variant="primary", scale=1, size="lg")
|
| 710 |
+
|
| 711 |
+
# Output sections
|
| 712 |
+
with gr.Tabs():
|
| 713 |
+
with gr.Tab("π AI Response"):
|
| 714 |
+
response_output = gr.Textbox(
|
| 715 |
+
label="π€ AI Investigation Analysis",
|
| 716 |
+
lines=12,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 717 |
interactive=False
|
| 718 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 719 |
|
| 720 |
+
with gr.Tab("π¨ Key Findings"):
|
| 721 |
+
findings_output = gr.Textbox(
|
| 722 |
+
label="Key Findings & Anomalies",
|
| 723 |
+
lines=10,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
interactive=False
|
| 725 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 726 |
|
| 727 |
+
with gr.Tab("π Data Analysis"):
|
| 728 |
+
analysis_output = gr.JSON(
|
| 729 |
+
label="Detailed Metrics & Statistics"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 730 |
)
|
| 731 |
+
|
| 732 |
+
investigate_btn.click(
|
| 733 |
+
fn=ask_question_wrapper,
|
| 734 |
+
inputs=[question_input],
|
| 735 |
+
outputs=[response_output, findings_output, analysis_output, gr.State(value="")]
|
| 736 |
+
)
|
| 737 |
+
|
| 738 |
+
# Example questions
|
| 739 |
+
gr.Markdown("""
|
| 740 |
+
### π‘ Quick Investigation Starters
|
| 741 |
+
Click any example to start exploring:
|
| 742 |
+
""")
|
| 743 |
+
|
| 744 |
+
example_questions = [
|
| 745 |
+
"What vehicles were in Adyar at midnight last night?",
|
| 746 |
+
"Show me suspicious midnight activity in industrial zones",
|
| 747 |
+
"Which plates appear most frequently in different locations?",
|
| 748 |
+
"Analyze activity patterns in Nungambakkam",
|
| 749 |
+
"Find vehicles with multiple detections in one day",
|
| 750 |
+
]
|
| 751 |
+
|
| 752 |
+
with gr.Row():
|
| 753 |
+
for example in example_questions[:3]:
|
| 754 |
+
gr.Button(example, size="sm", variant="secondary").click(
|
| 755 |
+
fn=lambda q=example: ask_question_wrapper(q),
|
| 756 |
+
outputs=[response_output, findings_output, analysis_output]
|
| 757 |
)
|
| 758 |
+
|
| 759 |
+
with gr.Row():
|
| 760 |
+
for example in example_questions[3:]:
|
| 761 |
+
gr.Button(example, size="sm", variant="secondary").click(
|
| 762 |
+
fn=lambda q=example: ask_question_wrapper(q),
|
| 763 |
+
outputs=[response_output, findings_output, analysis_output]
|
| 764 |
)
|
| 765 |
|
| 766 |
# =====================================================
|