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
18 changes: 9 additions & 9 deletions uc-0a/agents.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# agents.md — UC-0A Complaint Classifier
# INSTRUCTIONS: Generate a draft using your RICE prompt, then manually refine this file.
# Delete these comments before committing.
# This agent defines the UC-0A complaint classification task and its enforceable rules.

role: >
[FILL IN: Who is this agent? What is its operational boundary?]
UC-0A complaint classifier agent for the city test files. It assigns a single allowed category, a priority level, a reason sentence, and an optional review flag for each complaint.

intent: >
[FILL IN: What does a correct output look like — make it verifiable]
For each input complaint row, output exactly one `category` from the UC-0A taxonomy, one `priority` value, one sentence `reason` citing specific words from the description, and `flag: NEEDS_REVIEW` only when the category is genuinely ambiguous.

context: >
[FILL IN: What information is the agent allowed to use? State exclusions explicitly.]
The agent may use only the input complaint description and the UC-0A schema rules from `uc-0a/README.md`. It must not invent new category labels, extend the allowed priority values, or hallucinate details outside the complaint text.

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"
- "priority must be one of: Urgent, Standard, Low"
- "priority must be Urgent if the description contains any severity keyword from: injury, child, school, hospital, ambulance, fire, hazard, fell, collapse"
- "reason must be a single sentence that cites specific words or phrases from the complaint description"
- "flag must be NEEDS_REVIEW only for genuinely ambiguous complaints and blank otherwise"
114 changes: 105 additions & 9 deletions uc-0a/classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,118 @@
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.
Returns: dict with keys: category, priority, reason, flag
"""
raise NotImplementedError("Build this using your AI tool + RICE prompt")
description = (row.get("description") or "").strip()
lower = description.lower()

severity_keywords = [
"injury", "child", "school", "hospital", "ambulance",
"fire", "hazard", "fell", "collapse"
]
category_keywords = {
"Pothole": ["pothole"],
"Flooding": ["flood", "flooding", "flooded"],
"Streetlight": ["streetlight", "street light", "light out", "light not working"],
"Waste": ["garbage", "trash", "waste", "litter", "dumping", "dump"] ,
"Noise": ["noise", "loud", "sound", "honking", "honk", "music"],
"Road Damage": ["road damage", "pavement", "asphalt", "crack", "cracks", "uneven", "sinkhole"],
"Heritage Damage": ["heritage", "monument", "statue", "temple", "historic", "heritage site", "protected"],
"Heat Hazard": ["heat", "hot", "heat hazard", "extreme heat", "heatwave", "burn"],
"Drain Blockage": ["drain", "blocked", "clogged", "sewer", "gutter", "spout", "drainage"],
}

matched_categories = []
matched_keywords = {}
for category, keywords in category_keywords.items():
for keyword in keywords:
if keyword in lower:
matched_categories.append(category)
matched_keywords[category] = keyword
break

severity_matches = [keyword for keyword in severity_keywords if keyword in lower]

if not description:
category = "Other"
reason = "Description is missing, so the complaint is marked as Other and flagged for review."
priority = "Standard"
flag = "NEEDS_REVIEW"
else:
if len(matched_categories) == 1:
category = matched_categories[0]
elif len(matched_categories) > 1:
category = "Other"
else:
category = "Other"

urgent = bool(severity_matches)
priority = "Urgent" if urgent else "Standard"

if category == "Other":
if matched_categories:
reason = (
"Description matches multiple categories (" + ", ".join(matched_categories) + ") "
"and is therefore set to Other with review."
)
flag = "NEEDS_REVIEW"
else:
reason = "No exact UC-0A category keywords were found in the description."
flag = "NEEDS_REVIEW"
else:
matched_keyword = matched_keywords.get(category, category)
reason = (
f"Classified as {category} because the description mentions '{matched_keyword}'."
)
flag = ""

if urgent and flag != "NEEDS_REVIEW":
keyword_str = ", ".join(severity_matches)
reason = (
f"Classified as {category} with Urgent priority because the description mentions "
f"severity keyword(s): {keyword_str}."
)

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


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")
with open(input_path, newline="", encoding="utf-8") as infile:
reader = csv.DictReader(infile)
rows = list(reader)
if not rows:
fieldnames = reader.fieldnames or []
else:
fieldnames = rows[0].keys()

output_fields = list(fieldnames) + ["category", "priority", "reason", "flag"]

with open(output_path, "w", newline="", encoding="utf-8") as outfile:
writer = csv.DictWriter(outfile, fieldnames=output_fields)
writer.writeheader()

for index, row in enumerate(rows, start=1):
try:
result = classify_complaint(row)
except Exception as exc:
result = {
"category": "Other",
"priority": "Standard",
"reason": f"Failed to classify row {index}: {exc}",
"flag": "NEEDS_REVIEW",
}

output_row = {**row}
output_row.update(result)
writer.writerow(output_row)


if __name__ == "__main__":
Expand Down
31 changes: 19 additions & 12 deletions uc-0a/skills.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
# skills.md
# INSTRUCTIONS: Generate a draft by prompting AI, then manually refine this file.
# Delete these comments before committing.
# Defines the UC-0A skills used by the complaint classifier agent.

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?]
- name: classify_complaint
description: Classify a single complaint row into the UC-0A schema with category, priority, reason, and flag.
input: |
A single complaint record containing fields such as `description` and any other relevant row data.
output: |
An object with keys `category`, `priority`, `reason`, and `flag`.
- `category`: one of the allowed UC-0A categories
- `priority`: Urgent, Standard, or Low
- `reason`: a one-sentence explanation citing specific words from the description
- `flag`: `NEEDS_REVIEW` or blank
error_handling: If the complaint is ambiguous, set `category: Other`, `flag: NEEDS_REVIEW`, and still return a reason citing the ambiguity.

- 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: Read the input CSV file, apply `classify_complaint` to each row, and write the output CSV file.
input: |
A path to the input CSV file and a path to the desired output CSV file.
output: |
A completed output CSV containing the original rows plus the enforced `category`, `priority`, `reason`, and `flag` columns.
error_handling: If input data is missing required fields or is malformed, report the issue and do not produce partial output.
Loading