Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 8 additions & 20 deletions uc-0a/agents.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
# agents.md — UC-0A Complaint Classifier
# INSTRUCTIONS:
# 1. Open your AI tool
# 2. Paste the full contents of uc-0a/README.md
# 3. Use this prompt:
# "Read this UC README. Using the R.I.C.E framework, generate an
# agents.md YAML with four fields: role, intent, context, enforcement.
# Enforcement must include every rule listed under
# 'Enforcement Rules Your agents.md Must Include'.
# Output only valid YAML."
# 4. Paste the output below

role: >
[FILL IN]
An AI classifier reading each complaint for the City Operations team to output a category, priority, reason, and flag.

intent: >
[FILL IN]
To accurately classify complaints, avoiding false confidence on ambiguity, missing justifications, or hallucinated categories, so the output can feed the Director's dashboard every Monday.

context: >
[FILL IN]
The City Operations team receives hundreds of complaints per week. A naive prompt produces a classifier that invents category names, misses urgent complaints involving children and injuries, and gives confident answers on genuinely ambiguous inputs.

enforcement:
- "[FILL IN: category enum rule]"
- "[FILL IN: severity keyword rule — list the keywords]"
- "[FILL IN: reason field rule]"
- "[FILL IN: ambiguity refusal rule]"
- "[FILL IN: no invented categories rule]"
- "Category must be exactly one value from the allowed list: Pothole, Flooding, Streetlight, Waste, Noise, Road Damage, Heritage Damage, Heat Hazard, Drain Blockage, Other. No variations."
- "Priority must be Urgent if description contains any severity keyword: injury, child, school, hospital, ambulance, fire, hazard, fell, collapse."
- "Every output row must include a reason field citing specific words from the description."
- "If category cannot be determined confidently — output `category: Other` and `flag: NEEDS_REVIEW`."
- "Never invent category names outside the allowed list."
115 changes: 111 additions & 4 deletions uc-0a/classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,128 @@
"""
import argparse
import csv
import sys
import re

ALLOWED_CATEGORIES = {
"Pothole", "Flooding", "Streetlight", "Waste", "Noise",
"Road Damage", "Heritage Damage", "Heat Hazard", "Drain Blockage", "Other"
}

SEVERITY_KEYWORDS = ["injury", "child", "school", "hospital", "ambulance", "fire", "hazard", "fell", "collapse"]

CATEGORY_KEYWORDS = {
"Pothole": ["pothole", "crater"],
"Flooding": ["flood", "waterlog", "water logged"],
"Streetlight": ["street light", "streetlight", "dark", "no light"],
"Waste": ["garbage", "waste", "trash", "rubbish"],
"Noise": ["noise", "loud", "music", "party", "sound"],
"Road Damage": ["crack", "road damage", "broken road", "sinkhole"],
"Heritage Damage": ["heritage", "monument", "statue", "ruins"],
"Heat Hazard": ["heat", "sun", "blazing", "heatwave"],
"Drain Blockage": ["drain", "clog", "choke", "blockage"],
}

def classify_complaint(row: dict) -> dict:
"""
Classify a single complaint row.
Classify a single complaint row based on enforcement rules.
Returns dict with: complaint_id, category, priority, reason, flag
"""
raise NotImplementedError("Build this using your AI tool + agents.md")
description = row.get("description", "").lower()

# Error handling: vague/short descriptions
if len(description.split()) < 3:
return {
"complaint_id": row.get("complaint_id", row.get("id", "")),
"category": "Other",
"priority": "Low",
"reason": "Vague or very short description.",
"flag": "NEEDS_REVIEW"
}

priority = "Standard"
reason_words = []

# Priority enforcement rule
for kw in SEVERITY_KEYWORDS:
if re.search(r'\b' + kw + r'\b', description):
priority = "Urgent"
reason_words.append(kw)

# Category enforcement rule
category = "Other"
found_categories = set()
for cat, kws in CATEGORY_KEYWORDS.items():
for kw in kws:
if re.search(r'\b' + kw + r'\b', description):
found_categories.add(cat)
reason_words.append(kw)

if len(found_categories) == 1:
category = list(found_categories)[0]

# Ambiguity refusal rule
flag = ""
if category == "Other" or len(found_categories) > 1:
category = "Other"
flag = "NEEDS_REVIEW"

reason_text = "Found relevant keywords: " + ", ".join(set(reason_words)) if reason_words else "No specific keywords mapped."

return {
"complaint_id": row.get("complaint_id", row.get("id", "")),
"category": category,
"priority": priority,
"reason": reason_text,
"flag": flag
}

def batch_classify(input_path: str, output_path: str):
"""Read input CSV, classify each row, write results CSV."""
raise NotImplementedError("Build this using your AI tool + agents.md")
results = []
try:
with open(input_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
# Find ID column
id_col = None
if reader.fieldnames:
for col in reader.fieldnames:
if "id" in col.lower():
id_col = col
break

for row in reader:
try:
# Malformed rows logging and skipping
if not row.get('description'):
print(f"Skipping malformed row: {row}")
continue

res = classify_complaint(row)
# Use the correct original ID column if available
if id_col:
res["complaint_id"] = row.get(id_col, "")
results.append(res)
except Exception as e:
print(f"Error processing row {row}: {e}")
continue
except FileNotFoundError:
print(f"Input file not found: {input_path}")
sys.exit(1)

if not results:
print("No valid rows processed.")
return

fieldnames = ["complaint_id", "category", "priority", "reason", "flag"]
with open(output_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(results)

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="UC-0A Complaint Classifier")
parser.add_argument("--input", required=True)
parser.add_argument("--input", required=True)
parser.add_argument("--output", required=True)
args = parser.parse_args()
batch_classify(args.input, args.output)
Expand Down
16 changes: 16 additions & 0 deletions uc-0a/results_ahmedabad.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
complaint_id,category,priority,reason,flag
AM-202401,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
AM-202402,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
AM-202405,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
AM-202406,Heat Hazard,Standard,Found relevant keywords: heatwave,
AM-202407,Other,Urgent,Found relevant keywords: child,NEEDS_REVIEW
AM-202410,Pothole,Standard,Found relevant keywords: pothole,
AM-202414,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
AM-202417,Other,Standard,"Found relevant keywords: heritage, waste",NEEDS_REVIEW
AM-202421,Noise,Standard,Found relevant keywords: music,
AM-202424,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
AM-202429,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
AM-202431,Heritage Damage,Standard,Found relevant keywords: heritage,
AM-202435,Heat Hazard,Standard,Found relevant keywords: heat,
AM-202444,Waste,Standard,Found relevant keywords: waste,
AM-202445,Heat Hazard,Standard,Found relevant keywords: sun,
16 changes: 16 additions & 0 deletions uc-0a/results_hyderabad.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
complaint_id,category,priority,reason,flag
GH-202401,Other,Urgent,Found relevant keywords: ambulance,NEEDS_REVIEW
GH-202402,Drain Blockage,Standard,Found relevant keywords: drain,
GH-202406,Drain Blockage,Standard,Found relevant keywords: drain,
GH-202407,Drain Blockage,Standard,Found relevant keywords: drain,
GH-202410,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
GH-202411,Pothole,Standard,Found relevant keywords: pothole,
GH-202412,Other,Urgent,Found relevant keywords: school,NEEDS_REVIEW
GH-202417,Other,Standard,"Found relevant keywords: heritage, waste, garbage",NEEDS_REVIEW
GH-202420,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
GH-202422,Pothole,Standard,Found relevant keywords: crater,
GH-202424,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
GH-202428,Waste,Standard,Found relevant keywords: waste,
GH-202432,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
GH-202448,Drain Blockage,Standard,Found relevant keywords: drain,
GH-202438,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
16 changes: 16 additions & 0 deletions uc-0a/results_kolkata.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
complaint_id,category,priority,reason,flag
KM-202401,Heritage Damage,Standard,Found relevant keywords: heritage,
KM-202402,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
KM-202405,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
KM-202409,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
KM-202410,Pothole,Standard,Found relevant keywords: pothole,
KM-202411,Pothole,Standard,Found relevant keywords: pothole,
KM-202415,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
KM-202418,Waste,Standard,Found relevant keywords: waste,
KM-202421,Other,Urgent,"Found relevant keywords: fell, hospital",NEEDS_REVIEW
KM-202422,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
KM-202426,Heritage Damage,Standard,Found relevant keywords: heritage,
KM-202430,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
KM-202434,Heritage Damage,Standard,Found relevant keywords: heritage,
KM-202436,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
KM-202438,Heritage Damage,Standard,Found relevant keywords: heritage,
16 changes: 16 additions & 0 deletions uc-0a/results_pune.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
complaint_id,category,priority,reason,flag
PM-202401,Pothole,Standard,Found relevant keywords: pothole,
PM-202402,Pothole,Urgent,"Found relevant keywords: pothole, school",
PM-202406,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
PM-202408,Drain Blockage,Standard,Found relevant keywords: drain,
PM-202410,Streetlight,Standard,Found relevant keywords: dark,
PM-202411,Streetlight,Urgent,"Found relevant keywords: hazard, streetlight",
PM-202413,Waste,Standard,Found relevant keywords: garbage,
PM-202418,Noise,Standard,Found relevant keywords: music,
PM-202419,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
PM-202420,Other,Urgent,Found relevant keywords: injury,NEEDS_REVIEW
PM-202427,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
PM-202428,Other,Standard,No specific keywords mapped.,NEEDS_REVIEW
PM-202430,Other,Standard,"Found relevant keywords: dark, heritage",NEEDS_REVIEW
PM-202433,Waste,Standard,Found relevant keywords: waste,
PM-202446,Other,Urgent,Found relevant keywords: fell,NEEDS_REVIEW
22 changes: 9 additions & 13 deletions uc-0a/skills.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
# skills.md — UC-0A Complaint Classifier
# INSTRUCTIONS: Same as agents.md — paste README into AI, ask for skills.md YAML
# Skills

skills:
- name: classify_complaint
description: "[FILL IN]"
input: "[FILL IN]"
output: "[FILL IN]"
error_handling: "[FILL IN]"
## `classify_complaint`
- **Input:** one complaint row (dict with description, location fields)
- **Output:** dict with category, priority, reason, flag
- **Error handling:** vague/short descriptions → Other + NEEDS_REVIEW

- name: batch_classify
description: "[FILL IN]"
input: "[FILL IN]"
output: "[FILL IN]"
error_handling: "[FILL IN]"
## `batch_classify`
- **Input:** path to test CSV file
- **Output:** path to results CSV file
- **Error handling:** malformed rows logged and skipped, processing continues
33 changes: 8 additions & 25 deletions uc-mcp/agents.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,15 @@
# agents.md — UC-MCP MCP Server
# INSTRUCTIONS:
# 1. Open your AI tool
# 2. Paste the full contents of uc-mcp/README.md
# 3. Use this prompt:
# "Read this UC README. Using the R.I.C.E framework, generate an
# agents.md YAML with four fields: role, intent, context, enforcement.
# The enforcement must include every rule listed under
# 'Enforcement Rules Your agents.md Must Include'.
# Output only valid YAML."
# 4. Paste the output below, replacing this placeholder
# 5. Pay special attention to enforcement rule 1 — the tool description
# must state exact document scope

role: >
[FILL IN: Who is this agent? What layer of the stack does it operate at?
Hint: an MCP server that exposes policy retrieval as a tool]
An MCP server operating at the integration layer that exposes semantic search and policy retrieval over CMC policies as a standard MCP tool.

intent: >
[FILL IN: What does a correctly implemented MCP server produce?
Hint: JSON-RPC compliant responses, scoped tool description, correct refusals]
Produces JSON-RPC 2.0 compliant responses, exposes the query_policy_documents tool with a highly specific scoped description, and returns correct refusals for out-of-scope queries.

context: >
[FILL IN: What does this server have access to?
Hint: RAG server results only — no direct LLM calls, no outside knowledge]
Has access to RAG server results only (specifically CMC HR, IT, and Finance policies). Makes no direct LLM calls and has no outside knowledge.

enforcement:
- "[FILL IN: Tool description scope rule]"
- "[FILL IN: Refusal documentation rule]"
- "[FILL IN: inputSchema required field rule]"
- "[FILL IN: isError on failure rule]"
- "[FILL IN: HTTP 200 for all JSON-RPC responses rule]"
- "Tool description must state the exact document scope: CMC HR Leave Policy, IT Acceptable Use Policy, Finance Reimbursement Policy."
- "Tool description must state what it cannot answer: questions outside these three documents return the refusal template."
- "inputSchema must require `question` as a non-empty string."
- "Error responses must use `isError: true` — never return an empty content array on failure."
- "The server must return HTTP 200 for all JSON-RPC responses including errors — transport errors use HTTP 4xx/5xx, application errors use JSON-RPC error objects."
8 changes: 7 additions & 1 deletion uc-mcp/llm_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ def call_llm(prompt: str) -> str:
Call Gemini Flash with the given prompt.
Returns the text response as a string.
"""
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass

api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
return (
Expand All @@ -42,7 +48,7 @@ def call_llm(prompt: str) -> str:
try:
import google.generativeai as genai
genai.configure(api_key=api_key)
model = genai.GenerativeModel("gemini-1.5-flash")
model = genai.GenerativeModel("gemini-2.5-flash")
response = model.generate_content(prompt)
return response.text
except ImportError:
Expand Down
Loading