From 711be495af6f7e04c52adfb29e8b7a23cd3a3e39 Mon Sep 17 00:00:00 2001 From: Jaco Bezuidenhout Date: Sat, 30 May 2026 14:13:30 +0000 Subject: [PATCH] fix: extract clean task title from natural language phrasings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Item noun phrases (job item, work item) checked before "add X to Y" so the wrong " to " isn't captured in multi-clause sentences - "please add X to [project], title" — comma after project = explicit title - "work item to [project], title" — strips "to " filler then takes after comma - Restructured function: explicit prefixes → item nouns → add-to pattern Co-Authored-By: Claude Sonnet 4.6 --- app/intent.py | 52 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/app/intent.py b/app/intent.py index 19b9f06..7dbb5fe 100644 --- a/app/intent.py +++ b/app/intent.py @@ -203,22 +203,48 @@ def extract_task_title(message: str) -> str: import re as _re msg = message.strip() lower = msg.lower() - if lower.startswith("add ") and " to " in lower: - parts = msg.rsplit(" to ", 1) - title = parts[0][4:].strip() - return title if title else msg + + # Explicit prefixes for prefix in ("create task:", "add task:", "log task:", "new task:", "log this:"): if lower.startswith(prefix): return msg[len(prefix):].strip() - # "add [who] a job item [description]" — extract description after "job item" - m = _re.search(r'\bjob item\b\s*(.*)', lower) - if m: - remainder = msg[m.start(1):].strip() - for filler in ("that we need to ", "that is ", "which is ", "to "): - if remainder.lower().startswith(filler): - remainder = remainder[len(filler):] - if remainder.strip(): - return remainder.strip() + + # Item noun phrases — "add X a job/work item [description]" + # Check these before "add X to Y" so the wrong " to " isn't captured + for noun in (r'\bjob item\b', r'\bwork item\b', r'\bwork order\b', r'\bjob ticket\b'): + m = _re.search(noun + r'\s*(.*)', lower) + if m: + remainder = msg[m.start(1):].strip() + for filler in ("that we need to ", "that is ", "which is ", "to "): + if remainder.lower().startswith(filler): + remainder = remainder[len(filler):] + # "project, title" leftover → take after the comma + if "," in remainder: + remainder = remainder[remainder.index(",") + 1:].strip() + if remainder.strip(): + return remainder.strip() + + # "please add X to [project], title" or "add X to [project], title" + work_msg = msg[7:].strip() if lower.startswith("please ") else msg + work_lower = work_msg.lower() + if work_lower.startswith("add ") and " to " in work_lower: + to_idx = work_lower.rfind(" to ") + after_to = work_msg[to_idx + 4:] + if "," in after_to: + title_candidate = after_to[after_to.index(",") + 1:].strip() + if title_candidate: + return title_candidate + # No comma — what's between "add " and " to [project]" is the title + before_to = work_msg[4:to_idx].strip() + for noise in ("a work item", "an issue", "a task", "a job item", "a ticket", + "an item", "a to-do", "a todo"): + if before_to.lower().startswith(noise): + remainder = before_to[len(noise):].strip() + if remainder: + return remainder + if before_to: + return before_to + return msg