Skip to content
Open
30 changes: 16 additions & 14 deletions uc-0a/agents.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
# agents.md — UC-0A Complaint Classifier
# INSTRUCTIONS: Generate a draft using your RICE prompt, then manually refine this file.
# Delete these comments before committing.
role: "Complaint classification agent that assigns category, priority, reason, and review flag to civic complaint records strictly within the defined schema and without introducing new taxonomy or assumptions."

role: >
[FILL IN: Who is this agent? What is its operational boundary?]
intent: "For each input complaint row, produce exactly one output row with valid fields: category (one of the allowed values), priority (Urgent, Standard, or Low based on rules), reason (one sentence citing exact words from the complaint), and flag (NEEDS_REVIEW only if ambiguity is genuine). Output must be consistent, deterministic, and fully compliant with the schema so it can be programmatically validated."

intent: >
[FILL IN: What does a correct output look like — make it verifiable]

context: >
[FILL IN: What information is the agent allowed to use? State exclusions explicitly.]
context: "May use only the complaint text provided in each CSV row and the predefined classification schema, including the exact allowed category values and severity keywords. Must not use external knowledge, inferred categories, unstated assumptions, or modified taxonomy. Must not rely on prior rows for labeling decisions beyond consistency checking."

enforcement:
- "[FILL IN: Specific testable rule 1 — e.g. Category must be exactly one of: Pothole, Flooding, ...]"
- "[FILL IN: Specific testable rule 2 — e.g. Priority must be Urgent if description contains: injury, child, school, ...]"
- "[FILL IN: Specific testable rule 3 — e.g. Every output row must include a reason field citing specific words from the description]"
- "[FILL IN: Refusal condition — e.g. If category cannot be determined from description alone, output category: Other and flag: NEEDS_REVIEW]"

* "Category must be exactly one of: Pothole, Flooding, Streetlight, Waste, Noise, Road Damage, Heritage Damage, Heat Hazard, Drain Blockage, Other — no variations, synonyms, or new labels."
* "Priority must be exactly one of: Urgent, Standard, Low."
* "If any severity keyword appears (injury, child, school, hospital, ambulance, fire, hazard, fell, collapse), priority must be set to Urgent."
* "Reason must be exactly one sentence and must explicitly cite specific words or phrases from the complaint text."
* "Flag must be either NEEDS_REVIEW or blank."
* "Flag must be set to NEEDS_REVIEW only when the complaint cannot be confidently mapped to a single category from the allowed list."
* "Do not hallucinate sub-categories or extend the taxonomy under any circumstance."
* "Do not omit the reason field in any output."
* "Do not assign confident categories when the complaint is ambiguous; use NEEDS_REVIEW instead."
* "Ensure consistent category usage across similar complaints; no drift in labeling for the same issue type."
* "Do not downgrade priority when severity keywords are present."
* "Output must strictly follow the required schema fields for every row with no missing or extra fields."
200 changes: 174 additions & 26 deletions uc-0a/classifier.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,183 @@
"""
UC-0A — Complaint Classifier
Starter file. Build this using the RICE → agents.md → skills.md → CRAFT workflow.
"""
import argparse
import csv
import os
import sys

def classify_complaint(row: dict) -> dict:
"""
Classify a single complaint row.
Returns: dict with keys: complaint_id, category, priority, reason, flag

TODO: Build this using your AI tool guided by your agents.md and skills.md.
Your RICE enforcement rules must be reflected in this function's behaviour.
"""
raise NotImplementedError("Build this using your AI tool + RICE prompt")
ALLOWED_CATEGORIES = [
"Pothole", "Flooding", "Streetlight", "Waste", "Noise",
"Road Damage", "Heritage Damage", "Heat Hazard",
"Drain Blockage", "Other"
]

ALLOWED_PRIORITIES = ["Urgent", "Standard", "Low"]

def batch_classify(input_path: str, output_path: str):
"""
Read input CSV, classify each row, write results CSV.

TODO: Build this using your AI tool.
Must: flag nulls, not crash on bad rows, produce output even if some rows fail.
"""
raise NotImplementedError("Build this using your AI tool + RICE prompt")
SEVERITY_KEYWORDS = [
"injury", "child", "school", "hospital",
"ambulance", "fire", "hazard", "fell", "collapse"
]

CATEGORY_KEYWORDS = {
"Pothole": ["pothole"],
"Flooding": ["flood", "waterlogging", "water logging"],
"Streetlight": ["streetlight", "street light", "light not working", "no light"],
"Waste": ["garbage", "waste", "trash", "bin"],
"Noise": ["noise", "loud", "sound"],
"Road Damage": ["road damage", "broken road", "cracked road"],
"Heritage Damage": ["heritage", "monument"],
"Heat Hazard": ["heat", "hot"],
"Drain Blockage": ["drain", "sewer", "blockage", "clog"]
}


def contains_severity(text):
text_lower = text.lower()
return any(word in text_lower for word in SEVERITY_KEYWORDS)


def detect_category(text):
text_lower = text.lower()
matches = []

for category, keywords in CATEGORY_KEYWORDS.items():
for kw in keywords:
if kw in text_lower:
matches.append(category)
break

matches = list(set(matches))

if len(matches) == 1:
return matches[0], False
elif len(matches) == 0:
return "Other", True
else:
return matches[0], True # ambiguous


def generate_reason(text):
words = text.strip().split()
snippet = " ".join(words[:8]) if words else "missing description"
reason = f'Based on "{snippet}" in the complaint.'
if not reason.endswith("."):
reason += "."
return reason


def classify_complaint(row):
description = ""

if isinstance(row, dict):
description = row.get("description", "") or row.get("complaint", "")
else:
description = str(row)

if not description or not description.strip():
return {
"category": "Other",
"priority": "Low",
"reason": 'Complaint has "missing description".',
"flag": "NEEDS_REVIEW"
}

category, ambiguous = detect_category(description)

if category not in ALLOWED_CATEGORIES:
category = "Other"
ambiguous = True

priority = "Standard"
if contains_severity(description):
priority = "Urgent"

if priority not in ALLOWED_PRIORITIES:
priority = "Standard"

reason = generate_reason(description)

if '"' not in reason:
reason = f'Based on "{description[:15]}" in the complaint.'

flag = "NEEDS_REVIEW" if ambiguous else ""

return {
"category": category,
"priority": priority,
"reason": reason,
"flag": flag
}


def batch_classify(input_path, output_path):
if not os.path.exists(input_path):
print(f"Error: Input file not found: {input_path}")
sys.exit(1)

results = []

try:
with open(input_path, newline='', encoding='utf-8') as infile:
reader = csv.DictReader(infile)

if reader.fieldnames is None:
print("Error: Malformed CSV file.")
sys.exit(1)

for row in reader:
result = classify_complaint(row)

# Enforcement corrections
if contains_severity(str(row)):
result["priority"] = "Urgent"

if result["category"] not in ALLOWED_CATEGORIES:
result["category"] = "Other"
result["flag"] = "NEEDS_REVIEW"

if not result.get("reason"):
result["reason"] = 'Based on "missing description" in the complaint.'

if result["flag"] not in ["NEEDS_REVIEW", ""]:
result["flag"] = "NEEDS_REVIEW"

results.append(result)

except Exception as e:
print(f"Error reading input file: {e}")
sys.exit(1)

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

writer.writeheader()

for r in results:
for field in fieldnames:
if field not in r:
r[field] = ""

writer.writerow({
"category": r["category"],
"priority": r["priority"],
"reason": r["reason"],
"flag": r["flag"]
})

except Exception as e:
print(f"Error writing output file: {e}")
sys.exit(1)


def main():
parser = argparse.ArgumentParser()
parser.add_argument("--input", required=True)
parser.add_argument("--output", required=True)

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="UC-0A Complaint Classifier")
parser.add_argument("--input", required=True, help="Path to test_[city].csv")
parser.add_argument("--output", required=True, help="Path to write results CSV")
args = parser.parse_args()

batch_classify(args.input, args.output)
print(f"Done. Results written to {args.output}")


if __name__ == "__main__":
main()
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 @@
category,priority,reason,flag
Other,Standard,"Based on ""Tarmac surface melting at 44°C. Footwear sticking. Park"" in the complaint.",NEEDS_REVIEW
Other,Standard,"Based on ""Metal bus shelter reaching dangerous temperatures. Commuters refusing"" in the complaint.",NEEDS_REVIEW
Other,Standard,"Based on ""Dead trees with split branches. Fall risk to"" in the complaint.",NEEDS_REVIEW
Heat Hazard,Standard,"Based on ""Irrigation system broken. Grass dying in heatwave conditions."" in the complaint.",
Other,Urgent,"Based on ""Broken bench and upturned paving. Child injured last"" in the complaint.",NEEDS_REVIEW
Pothole,Standard,"Based on ""Pothole on main highway causing morning rush lane"" in the complaint.",
Other,Standard,"Based on ""Residential colony unlit after 9pm. Wiring theft reported."" in the complaint.",NEEDS_REVIEW
Heritage Damage,Standard,"Based on ""Night market waste not cleared before morning. Heritage"" in the complaint.",NEEDS_REVIEW
Other,Standard,"Based on ""Club music audible at residential buildings at 2am."" in the complaint.",NEEDS_REVIEW
Other,Standard,"Based on ""Zoo approach road surface bubbling at 45°C. Visitor"" in the complaint.",NEEDS_REVIEW
Other,Standard,"Based on ""River walk surface temperature unbearable. Installed temperature reads"" in the complaint.",NEEDS_REVIEW
Heritage Damage,Standard,"Based on ""Old city road subsidence near ancient step well."" in the complaint.",
Heat Hazard,Standard,"Based on ""Black metal road dividers storing heat. Motorists reporting"" in the complaint.",
Waste,Standard,"Based on ""Restaurant waste bins overflowing on Sunday night. Health"" in the complaint.",
Other,Standard,"Based on ""BRT shelter roof glass broken. Users exposed to"" in the complaint.",NEEDS_REVIEW
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 @@
category,priority,reason,flag
Flooding,Urgent,"Based on ""Underpass flooded after 1hr rain. Ambulance diverted. Lives"" in the complaint.",
Drain Blockage,Standard,"Based on ""Market area flooded. Traders suffering losses. Drain completely"" in the complaint.",NEEDS_REVIEW
Drain Blockage,Standard,"Based on ""Main stormwater drain 100% blocked with construction debris."" in the complaint.",
Drain Blockage,Standard,"Based on ""Drain blocked and mosquito breeding. Dengue concern."" in the complaint.",
Pothole,Standard,"Based on ""Potholes causing vehicles to slow to 20kmph on"" in the complaint.",
Pothole,Urgent,"Based on ""Pothole swallowed entire motorcycle wheel. Rider hospitalised."" in the complaint.",
Pothole,Urgent,"Based on ""School bus struggling to navigate 6 potholes in"" in the complaint.",
Heat Hazard,Standard,"Based on ""Heritage zone garbage overflow. Tourist photographs showing piles"" in the complaint.",NEEDS_REVIEW
Other,Standard,"Based on ""Construction drilling from 5am daily near residential towers."" in the complaint.",NEEDS_REVIEW
Other,Urgent,"Based on ""Road collapsed partially. Crater 1m deep near residential"" in the complaint.",NEEDS_REVIEW
Flooding,Standard,"Based on ""Underpass floods in light rain. Cars regularly abandoned."" in the complaint.",
Waste,Standard,"Based on ""Post-market waste not cleared. Area unusable by Sunday"" in the complaint.",
Other,Standard,"Based on ""24hr supermarket delivery trucks idling with engines on."" in the complaint.",NEEDS_REVIEW
Drain Blockage,Standard,"Based on ""Main drain blocked — entire locality at flooding"" in the complaint.",NEEDS_REVIEW
Other,Standard,"Based on ""Colony surrounded by fields that channel rainwater through"" in the complaint.",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 @@
category,priority,reason,flag
Heritage Damage,Standard,"Based on ""Heritage lamp post knocked over by delivery vehicle."" in the complaint.",
Other,Standard,"Based on ""Historic tram road cobblestones broken up by cable"" in the complaint.",NEEDS_REVIEW
Other,Standard,"Based on ""Wedding band playing near Tagore Museum at 11pm."" in the complaint.",NEEDS_REVIEW
Pothole,Standard,"Based on ""Airport access road full of potholes. Diplomatic complaint"" in the complaint.",
Pothole,Standard,"Based on ""Pothole causing tyre blowouts. Three incidents this week."" in the complaint.",
Pothole,Standard,"Based on ""Deep pothole filling with rainwater. Depth invisible. Accident"" in the complaint.",
Drain Blockage,Standard,"Based on ""New residential complex draining directly onto public road."" in the complaint.",
Waste,Standard,"Based on ""Tourist zone waste overflowing. Foreign visitors photographing piles."" in the complaint.",NEEDS_REVIEW
Other,Urgent,"Based on ""Footpath broken and sinking. Elderly pedestrian fell. Hospital"" in the complaint.",NEEDS_REVIEW
Other,Standard,"Based on ""Road surface buckled near bridge. Structural concern raised."" in the complaint.",NEEDS_REVIEW
Heritage Damage,Standard,"Based on ""Heritage residential building exterior defaced by billboard installation."" in the complaint.",
Other,Standard,"Based on ""Road subsided near gas pipeline. Gas leak smell"" in the complaint.",NEEDS_REVIEW
Heritage Damage,Standard,"Based on ""Street paving removed for utility work — heritage"" in the complaint.",
Other,Standard,"Based on ""Entire colony substation tripped. Darkness for 3 nights."" in the complaint.",NEEDS_REVIEW
Heritage Damage,Standard,"Based on ""Street vendors using amplifiers illegally in heritage precinct."" in the complaint.",
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 @@
category,priority,reason,flag
Pothole,Standard,"Based on ""Large pothole 60cm wide causing tyre damage. Three"" in the complaint.",
Pothole,Urgent,"Based on ""Deep pothole near bus stop. School children at"" in the complaint.",
Flooding,Standard,"Based on ""Underpass flooded knee-deep after 2hrs rain. Commuters stranded."" in the complaint.",
Flooding,Standard,"Based on ""Bus stand flooded. Passengers standing in water. Drain"" in the complaint.",NEEDS_REVIEW
Streetlight,Standard,"Based on ""Three consecutive streetlights out for 10 days. Area"" in the complaint.",
Streetlight,Urgent,"Based on ""Streetlight flickering and sparking. Electrical hazard reported."" in the complaint.",
Waste,Standard,"Based on ""Overflowing garbage bins near vegetable market. Smell affecting"" in the complaint.",
Other,Standard,"Based on ""Wedding venue playing music past midnight on weeknights."" in the complaint.",NEEDS_REVIEW
Other,Standard,"Based on ""Road surface cracked and sinking near utility work"" in the complaint.",NEEDS_REVIEW
Other,Urgent,"Based on ""Manhole cover missing. Risk of serious injury to"" in the complaint.",NEEDS_REVIEW
Flooding,Standard,"Based on ""Bridge approach floods in 30mins of rain. Bridge"" in the complaint.",
Other,Standard,"Based on ""Dead animal not removed for 36 hours. Health"" in the complaint.",NEEDS_REVIEW
Heritage Damage,Standard,"Based on ""Heritage street, lights out. Safety concern for pedestrians"" in the complaint.",
Waste,Standard,"Based on ""Bulk waste from apartment renovation dumped on public"" in the complaint.",
Other,Urgent,"Based on ""Footpath tiles broken and upturned. Elderly resident fell"" in the complaint.",NEEDS_REVIEW
47 changes: 33 additions & 14 deletions uc-0a/skills.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
# skills.md
# INSTRUCTIONS: Generate a draft by prompting AI, then manually refine this file.
# Delete these comments before committing.
* name: classify_complaint
description: Classifies a single civic complaint into category, priority, reason, and flag based strictly on the predefined schema.
input:
type: object
format: "Single complaint row containing a free-text description field."
output:
type: object
format: "{ category: one of [Pothole, Flooding, Streetlight, Waste, Noise, Road Damage, Heritage Damage, Heat Hazard, Drain Blockage, Other], priority: one of [Urgent, Standard, Low], reason: one sentence citing exact words from the description, flag: NEEDS_REVIEW or blank }"
error_handling:

skills:
- name: [skill_name]
description: [One sentence — what does this skill do?]
input: [What does it receive? Type and format.]
output: [What does it return? Type and format.]
error_handling: [What does it do when input is invalid or ambiguous?]
* "If the complaint text is missing or empty, return category as Other, priority as Low, reason indicating missing description, and flag as NEEDS_REVIEW."
* "If the complaint cannot be confidently mapped to a single allowed category, set flag to NEEDS_REVIEW and avoid guessing."
* "If severity keywords (injury, child, school, hospital, ambulance, fire, hazard, fell, collapse) are present but priority is not Urgent, override to Urgent."
* "If a category outside the allowed list is generated, replace it with Other and set flag to NEEDS_REVIEW."
* "If reason cannot cite exact words from the description, regenerate reason to include explicit quoted terms from the input."
* "Prevent taxonomy drift by enforcing exact category strings across all outputs."
* "Avoid hallucinated sub-categories by restricting outputs strictly to the allowed category list."

- name: [second_skill_name]
description: [One sentence]
input: [Type and format]
output: [Type and format]
error_handling: [What does it do when input is invalid or ambiguous?]
* name: batch_classify
description: Processes an input CSV of complaint rows, applies classify_complaint to each row, and writes a structured output CSV.
input:
type: file
format: "CSV file at ../data/city-test-files/test_[your-city].csv containing 15 rows with complaint descriptions and no category or priority_flag columns."
output:
type: file
format: "CSV file at uc-0a/results_[your-city].csv where each row includes category, priority, reason, and flag fields populated per schema."
error_handling:

* "If the input file is missing, unreadable, or malformed, abort processing and return an explicit error message."
* "If any row is invalid or missing a description, process it using classify_complaint fallback (Other, Low, NEEDS_REVIEW)."
* "Ensure all output rows contain all required fields; if any field is missing, regenerate that row."
* "Maintain consistent category naming across rows to prevent taxonomy drift."
* "Ensure severity keywords in any row trigger Urgent priority; correct any violations during processing."
* "If ambiguous complaints are classified without NEEDS_REVIEW, reprocess and set the flag appropriately."
* "Do not introduce new columns or omit required ones in the output CSV."
Loading