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
22 changes: 11 additions & 11 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.

role: >
[FILL IN: Who is this agent? What is its operational boundary?]
You are a meticulous Complaint Classifier agent. Your operational boundary is strictly processing citizen complaint descriptions to classify their category and priority.

intent: >
[FILL IN: What does a correct output look like — make it verifiable]
Output a verifiable classification for each complaint containing `category` (from a strict list), `priority` (based on severity keywords), a one-sentence `reason` (citing the description), and an optional `flag`.

context: >
[FILL IN: What information is the agent allowed to use? State exclusions explicitly.]
You must only use the text provided in the citizen complaint description. You are strictly prohibited from hallucinating sub-categories, varying the exact category strings, or inferring severity without specific keywords.

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]"
- "One category per complaint"
- "No hallucination"
- "No new categories"
- "Classify every complaint"
- "Category must be exactly one of: Pothole, Flooding, Streetlight, Waste, Noise, Road Damage, Heritage Damage, Heat Hazard, Drain Blockage, Other. Exact strings only — no variations."
- "Priority must be Urgent if the description contains any of the following severity keywords: injury, child, school, hospital, ambulance, fire, hazard, fell, collapse. Otherwise it must be Standard or Low."
- "Every output must include a reason field that is exactly one sentence long and explicitly cites specific words from the description."
- "If the category is genuinely ambiguous, you must set the flag field to NEEDS_REVIEW."
114 changes: 93 additions & 21 deletions uc-0a/classifier.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,106 @@
"""
UC-0A — Complaint Classifier
Starter file. Build this using the RICE → agents.md → skills.md → CRAFT workflow.
"""
import argparse
import csv

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"
]

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

def batch_classify(input_path: str, output_path: str):
"""
Read input CSV, classify each row, write results CSV.
CATEGORY_KEYWORDS = {
"Pothole": ["pothole", "crater"],
"Flooding": ["flood", "waterlogging", "submerged"],
"Streetlight": ["streetlight", "light", "dark"],
"Waste": ["waste", "garbage", "trash", "rubbish"],
"Noise": ["noise", "loud", "music"],
"Road Damage": ["road damage", "broken road", "cracked"],
"Heritage Damage": ["heritage", "monument", "statue"],
"Heat Hazard": ["heat", "wave"],
"Drain Blockage": ["drain", "blocked", "sewer", "sewage"],
}

def classify_complaint(row: dict) -> dict:
description = row.get("complaint", row.get("description", ""))
description_lower = description.lower()

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")
# Determine Category
matched_categories = []
for category, keywords in CATEGORY_KEYWORDS.items():
if any(keyword in description_lower for keyword in keywords):
matched_categories.append(category)

if len(matched_categories) == 1:
category = matched_categories[0]
flag = ""
elif len(matched_categories) > 1:
category = "Other"
flag = "NEEDS_REVIEW"
else:
category = "Other"
flag = ""

# Determine Priority
urgent_keywords_found = [kw for kw in SEVERITY_KEYWORDS if kw in description_lower]
if urgent_keywords_found:
priority = "Urgent"
else:
priority = "Standard"

# Reason
if urgent_keywords_found:
reason = f"The description mentions the severity keyword '{urgent_keywords_found[0]}'."
elif matched_categories and category != "Other":
matched_kw = [kw for kw in CATEGORY_KEYWORDS[category] if kw in description_lower][0]
reason = f"The description mentions the keyword '{matched_kw}'."
else:
first_word = description.split()[0] if description.split() else "unclear"
reason = f"The description starts with '{first_word}'."

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

def batch_classify(input_path: str, output_path: str):
try:
with open(input_path, 'r', encoding='utf-8') as f_in:
reader = csv.DictReader(f_in)
fieldnames = reader.fieldnames if reader.fieldnames else []

output_fieldnames = fieldnames.copy()
for col in ["category", "priority", "reason", "flag"]:
if col not in output_fieldnames:
output_fieldnames.append(col)

results = []
for row in reader:
try:
classification = classify_complaint(row)
row.update(classification)
results.append(row)
except Exception:
row["category"] = "Other"
row["priority"] = "Low"
row["reason"] = "Error processing row."
row["flag"] = "NEEDS_REVIEW"
results.append(row)

with open(output_path, 'w', encoding='utf-8', newline='') as f_out:
writer = csv.DictWriter(f_out, fieldnames=output_fieldnames)
writer.writeheader()
writer.writerows(results)
except Exception as e:
print(f"Failed to process dataset: {e}")

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("--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)
Expand Down
22 changes: 8 additions & 14 deletions uc-0a/skills.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
# skills.md
# INSTRUCTIONS: Generate a draft by prompting AI, then manually refine this file.
# Delete these comments before committing.

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: load_dataset
input: A file path to the input CSV containing complaints (e.g., test_[city].csv).
output: A parsed list or structured representation of complaint rows.
error_handling: Aborts and raises an error if the input file cannot be read, is missing, or lacks the expected columns.

- 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: classify_complaint
input: A single complaint description string.
output: A structured record containing category, priority, a one-sentence reason, and an optional flag.
error_handling: Sets the flag to NEEDS_REVIEW if genuinely ambiguous; fails if category deviates from exact allowed list.
18 changes: 7 additions & 11 deletions uc-0b/agents.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
# agents.md
# INSTRUCTIONS: Generate a draft using your RICE prompt, then manually refine this file.
# Delete these comments before committing.

role: >
[FILL IN: Who is this agent? What is its operational boundary?]
You are a highly precise Policy Summarization agent. Your operational boundary is strictly summarising policy documents without altering meaning or scope.

intent: >
[FILL IN: What does a correct output look like — make it verifiable]
Output a comprehensive summary of the provided policy document that accurately reflects all obligations, conditions, and numbered clauses without omission or softening.

context: >
[FILL IN: What information is the agent allowed to use? State exclusions explicitly.]
You will receive a policy document containing various numbered clauses. You must strictly adhere to the source text. You are prohibited from omitting clauses, dropping conditions (such as multiple required approvers), or introducing external knowledge (scope bleed).

enforcement:
- "[FILL IN: Specific testable rule 1]"
- "[FILL IN: Specific testable rule 2]"
- "[FILL IN: Specific testable rule 3]"
- "[FILL IN: Refusal condition — when should the system refuse rather than guess?]"
- "Every numbered clause must be present in the summary."
- "Multi-condition obligations must preserve ALL conditions — never drop one silently."
- "Never add information not present in the source document."
- "If a clause cannot be summarised without meaning loss — quote it verbatim and flag it."
65 changes: 59 additions & 6 deletions uc-0b/app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,65 @@
"""
UC-0B app.py — Starter file.
Build this using the RICE + agents.md + skills.md + CRAFT workflow.
See README.md for run command and expected behaviour.
"""
import argparse
import re

def retrieve_policy(filepath: str) -> dict:
"""Loads .txt policy file, returns content as structured numbered sections."""
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
except Exception as e:
raise RuntimeError(f"Error reading file {filepath}: {e}")

clauses = {}
lines = content.split('\n')
current_clause = None
clause_text = []

for line in lines:
match = re.match(r'^(\d+\.\d+)\s+(.*)', line.strip())
if match:
if current_clause:
clauses[current_clause] = " ".join(clause_text).strip()
current_clause = match.group(1)
clause_text = [match.group(2)]
elif current_clause and line.strip():
clause_text.append(line.strip())

if current_clause:
clauses[current_clause] = " ".join(clause_text).strip()

if not clauses:
raise ValueError("No numbered clauses found in the document.")

return clauses

def summarize_policy(clauses: dict) -> str:
"""Takes structured sections, produces compliant summary with clause references."""
summary_lines = []
summary_lines.append("POLICY SUMMARY")
summary_lines.append("=" * 14)
summary_lines.append("Note: Clauses are quoted verbatim to prevent meaning loss and condition dropping.\n")

for clause_id in sorted(clauses.keys(), key=lambda x: [int(p) for p in x.split('.')]):
summary_lines.append(f"Clause {clause_id}: [VERBATIM] {clauses[clause_id]}")

return "\n".join(summary_lines)

def main():
raise NotImplementedError("Build this using your AI tool + RICE prompt")
parser = argparse.ArgumentParser(description="UC-0B Policy Summarizer")
parser.add_argument("--input", required=True, help="Path to input policy .txt")
parser.add_argument("--output", required=True, help="Path to output summary .txt")
args = parser.parse_args()

try:
clauses = retrieve_policy(args.input)
summary = summarize_policy(clauses)

with open(args.output, 'w', encoding='utf-8') as f:
f.write(summary)

print(f"Summary successfully written to {args.output}")
except Exception as e:
print(f"Failed to process policy: {e}")

if __name__ == "__main__":
main()
22 changes: 8 additions & 14 deletions uc-0b/skills.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
# skills.md
# INSTRUCTIONS: Generate a draft by prompting AI, then manually refine this file.
# Delete these comments before committing.

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: retrieve_policy
input: File path to the policy document (.txt).
output: Structured content mapping each numbered clause exactly as it appears in the source text.
error_handling: Raise an error if the file cannot be read or if no numbered clauses are found.

- 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: summarize_policy
input: Structured policy content containing numbered clauses.
output: A compliant summary retaining all clauses and multi-condition obligations.
error_handling: Fails validation if any numbered clause from the input is omitted in the output. Raises an error if multi-condition obligations (e.g., dual approvers) are partially dropped or if unauthorized scope bleed is detected.
Loading