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
78 changes: 78 additions & 0 deletions docs/notion-task-triager-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Notion Task Triager — ChittyCommand Dispute Integration

Add the following to the Task Triager agent's instructions in Notion Settings > Connections > Task Triager > Edit instructions.

---

## Dispute & Legal Task Classification

When you receive an email or message related to any of the following topics, classify it as a **legal task** so it automatically syncs into ChittyCommand's dispute tracker:

- Disputes with vendors, landlords, tenants, contractors, or counterparties
- Insurance claims or denials
- Court filings, summons, motions, or docket updates
- Property damage, water damage, or maintenance disputes
- Legal deadlines, statute of limitations, or response due dates
- Demand letters, cease & desist, or settlement offers
- Payment disputes, chargebacks, or billing errors
- Government notices (IRS, county, city violations)

### Required Properties for Legal Tasks

When creating a task page in the **Business Task Tracker** database for dispute/legal items:

| Property | Value | Notes |
|----------|-------|-------|
| **Title** | Clear, descriptive title | e.g. "Water damage claim — 123 Main St — Allstate denial" |
| **Type** | `Legal` | MUST be "Legal" for ChittyCommand to pick it up as a dispute |
| **Source** | `Email` | Use "Email" for email-ingested items, "Mention" for @-mentions |
| **Priority 1** | 1-10 (number) | 1 = most urgent. Use 1-3 for court deadlines, 4-6 for active disputes, 7-10 for monitoring |
| **Tags** | One or more from the list below | Helps categorize the dispute type |
| **Description** | Summarize the key facts | Include: who, what, amounts, dates, and any deadlines mentioned |
| **Due Date** | Set if there's an explicit deadline | Court dates, response deadlines, filing windows |

Comment on lines +20 to +33
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Document the counterparty limitation.

The required properties table does not mention that the counterparty field will be auto-populated as 'Unknown' when disputes are created from Notion legal tasks. Users may expect this to be extracted from Notion task properties (e.g., from the Description or a dedicated field), but context snippet 2 (src/lib/dispute-sync.ts:123) shows it is hardcoded. Consider adding a row or note explaining this limitation so users understand they may need to update the counterparty field manually in ChittyCommand after sync.

📝 Suggested addition to the table

Add a note below the table:

 | **Due Date** | Set if there's an explicit deadline | Court dates, response deadlines, filing windows |
+
+> **Note**: The `Counterparty` field is automatically set to "Unknown" during sync. You can update it manually in ChittyCommand after the dispute is created.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Required Properties for Legal Tasks
When creating a task page in the **Business Task Tracker** database for dispute/legal items:
| Property | Value | Notes |
|----------|-------|-------|
| **Title** | Clear, descriptive title | e.g. "Water damage claim — 123 Main St — Allstate denial" |
| **Type** | `Legal` | MUST be "Legal" for ChittyCommand to pick it up as a dispute |
| **Source** | `Email` | Use "Email" for email-ingested items, "Mention" for @-mentions |
| **Priority 1** | 1-10 (number) | 1 = most urgent. Use 1-3 for court deadlines, 4-6 for active disputes, 7-10 for monitoring |
| **Tags** | One or more from the list below | Helps categorize the dispute type |
| **Description** | Summarize the key facts | Include: who, what, amounts, dates, and any deadlines mentioned |
| **Due Date** | Set if there's an explicit deadline | Court dates, response deadlines, filing windows |
### Required Properties for Legal Tasks
When creating a task page in the **Business Task Tracker** database for dispute/legal items:
| Property | Value | Notes |
|----------|-------|-------|
| **Title** | Clear, descriptive title | e.g. "Water damage claim — 123 Main St — Allstate denial" |
| **Type** | `Legal` | MUST be "Legal" for ChittyCommand to pick it up as a dispute |
| **Source** | `Email` | Use "Email" for email-ingested items, "Mention" for `@-mentions` |
| **Priority 1** | 1-10 (number) | 1 = most urgent. Use 1-3 for court deadlines, 4-6 for active disputes, 7-10 for monitoring |
| **Tags** | One or more from the list below | Helps categorize the dispute type |
| **Description** | Summarize the key facts | Include: who, what, amounts, dates, and any deadlines mentioned |
| **Due Date** | Set if there's an explicit deadline | Court dates, response deadlines, filing windows |
> **Note**: The `Counterparty` field is automatically set to "Unknown" during sync. You can update it manually in ChittyCommand after the dispute is created.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/notion-task-triager-instructions.md` around lines 20 - 33, The docs fail
to state that the counterparty property is auto-populated as "Unknown" for
disputes created from Notion; add a new row or a short note directly below the
Required Properties table stating: "counterparty — defaults to 'Unknown' for
Notion-imported disputes; update manually in ChittyCommand if known." Reference
the code that enforces this default (the assignment setting counterparty =
"Unknown" in the dispute-sync logic) so readers know why the field may need
manual correction after sync.

### Tag Guidelines

Apply one or more of these tags based on the content:

- `Dispute` — General disputes with any counterparty
- `Insurance-claim` — Insurance claims, denials, appeals
- `Court-filing` — Court documents, motions, hearings, docket activity
- `Property-issue` — Property damage, maintenance, HOA issues
- `Vendor-dispute` — Contractor, vendor, or service provider disputes
- `Legal-deadline` — Time-sensitive legal obligations
- `Payment` — Payment disputes, chargebacks, billing errors
- `Tax` — IRS notices, property tax disputes, assessments
- `Utility` — Utility billing disputes (ComEd, Peoples Gas, etc.)

### Priority Guidance

| Priority | Use When |
|----------|----------|
| 1-2 | Court deadline within 7 days, active hearing, imminent statute expiry |
| 3-4 | Response needed within 30 days, active negotiations, pending insurance decision |
| 5-6 | Monitoring active disputes, follow-up needed, no immediate deadline |
| 7-8 | Informational notices, early-stage inquiries, low-stakes items |
| 9-10 | Archive-worthy, resolved but tracking, general awareness |

### What Happens After Creation

Once you create a legal task in the Business Task Tracker:

1. **ChittyCommand's daily cron** (6 AM CT) syncs new legal tasks into `cc_disputes`
2. **TriageAgent** automatically scores the dispute for severity and priority
3. **ChittyLedger** creates a case record for chain-of-custody tracking
4. The dispute appears in the ChittyCommand dashboard for action tracking

You do NOT need to create anything in ChittyCommand directly — the sync is automatic.

### Examples

**Email**: "Allstate denied claim #CLM-2024-8847 for water damage at 4521 S Drexel..."
→ Type: `Legal` | Priority 1: `3` | Tags: `Insurance-claim`, `Property-issue` | Due Date: appeal deadline if mentioned

**Email**: "Cook County Circuit Court — Notice of hearing, Case 2024D007847, March 15..."
→ Type: `Legal` | Priority 1: `1` | Tags: `Court-filing`, `Legal-deadline` | Due Date: `2026-03-15`

**Email**: "Mr. Cooper mortgage — escrow shortage notice, payment increase effective..."
→ Type: `Legal` | Priority 1: `5` | Tags: `Payment`, `Property-issue` | Due Date: effective date
19 changes: 19 additions & 0 deletions migrations/0012_dispute_sync_indexes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- 0012_dispute_sync_indexes.sql
-- Indexes for dispute ↔ Notion ↔ TriageAgent sync lookups.

-- Fast lookup: find disputes by notion_task_id (loop guard in reconcileNotionDisputes)
CREATE INDEX IF NOT EXISTS idx_cc_disputes_notion_task_id
ON cc_disputes ((metadata->>'notion_task_id'))
WHERE metadata->>'notion_task_id' IS NOT NULL;
Comment on lines +5 to +7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Enforce one dispute per Notion page.

The reconciliation flow does an existence check and then inserts. Without a uniqueness guarantee on metadata->>'notion_task_id', concurrent cron/manual runs can materialize two disputes for the same Notion page and fan out duplicate triage/ledger side effects.

💡 Suggested migration tweak
-CREATE INDEX IF NOT EXISTS idx_cc_disputes_notion_task_id
+CREATE UNIQUE INDEX IF NOT EXISTS idx_cc_disputes_notion_task_id
   ON cc_disputes ((metadata->>'notion_task_id'))
   WHERE metadata->>'notion_task_id' IS NOT NULL;

Please pair this with duplicate-key handling in reconcileNotionDisputes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CREATE INDEX IF NOT EXISTS idx_cc_disputes_notion_task_id
ON cc_disputes ((metadata->>'notion_task_id'))
WHERE metadata->>'notion_task_id' IS NOT NULL;
CREATE UNIQUE INDEX IF NOT EXISTS idx_cc_disputes_notion_task_id
ON cc_disputes ((metadata->>'notion_task_id'))
WHERE metadata->>'notion_task_id' IS NOT NULL;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/0012_dispute_sync_indexes.sql` around lines 5 - 7, Add a
uniqueness constraint on the Notion page id to prevent duplicate disputes by
replacing the non-unique index idx_cc_disputes_notion_task_id with a UNIQUE
index on cc_disputes ((metadata->>'notion_task_id')) WHERE
metadata->>'notion_task_id' IS NOT NULL, and update the reconcileNotionDisputes
code to handle duplicate-key races (use INSERT ... ON CONFLICT DO NOTHING or
catch unique-violation errors and proceed) so concurrent runs cannot create two
disputes for the same Notion page.


-- Fast lookup: find tasks by dispute_id (loop guard)
CREATE INDEX IF NOT EXISTS idx_cc_tasks_dispute_id
ON cc_tasks ((metadata->>'dispute_id'))
WHERE metadata->>'dispute_id' IS NOT NULL;

-- Partial index for the reconciliation query (legal tasks not yet linked to disputes)
CREATE INDEX IF NOT EXISTS idx_cc_tasks_legal_unlinked
ON cc_tasks (priority ASC, created_at ASC)
WHERE task_type = 'legal'
AND backend_status NOT IN ('done', 'verified')
AND (metadata->>'dispute_id') IS NULL;
15 changes: 14 additions & 1 deletion src/lib/cron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { matchTransactions } from './matcher';
import { generateProjections } from './projections';
import { discoverRevenueSources } from './revenue';
import { generatePaymentPlan, savePaymentPlan } from './payment-planner';
import { reconcileNotionDisputes } from './dispute-sync';

/**
* Cron sync orchestrator.
Expand Down Expand Up @@ -120,6 +121,18 @@ export async function runCronSync(
} catch (err) {
console.error('[cron:notion_tasks] failed:', err);
}

// Phase 10: Dispute-Notion reconciliation
// Auto-creates cc_disputes from legal tasks not yet linked.
try {
const disputesSynced = await reconcileNotionDisputes(env, sql);
if (disputesSynced > 0) {
recordsSynced += disputesSynced;
console.log(`[cron:dispute_reconcile] created ${disputesSynced} disputes from Notion legal tasks`);
}
} catch (err) {
console.error('[cron:dispute_reconcile] failed:', err);
}
Comment on lines +125 to +135
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add a DB-enforced dedupe guard before running this on cron.

reconcileNotionDisputes() in src/lib/dispute-sync.ts:67-171 currently does a SELECT on metadata->>'notion_task_id' and then an INSERT, but the schema only has a non-unique index for that key in migrations/0012_dispute_sync_indexes.sql:1-10, and cc_disputes has no unique constraint in migrations/0002_command_legal.sql:28-44. Now that this path also runs on the daily cron, it can race with the bridge route or another worker and create duplicate disputes plus duplicate downstream side effects for the same Notion task.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/cron.ts` around lines 125 - 135, reconcileNotionDisputes can race and
create duplicate cc_disputes because there is no DB-enforced uniqueness; add a
DB-level dedupe by creating a UNIQUE expression index (or constraint) on the
notion task id extracted from metadata (e.g. UNIQUE
((metadata->>'notion_task_id'))) for the cc_disputes table via a new migration
(or update migrations/0012_dispute_sync_indexes.sql), and then update
reconcileNotionDisputes to use an idempotent insert (INSERT ... ON CONFLICT DO
NOTHING or handle unique-violation error) so the cron path and bridge route
cannot both create duplicates; reference reconcileNotionDisputes, cc_disputes,
migrations/0012_dispute_sync_indexes.sql and migrations/0002_command_legal.sql
when making these changes.

}

if (source === 'utility_scrape') {
Expand Down Expand Up @@ -637,7 +650,7 @@ export async function syncNotionTasks(env: Env, sql: NeonQueryFunction<false, fa

const description = extractNotionRichText(props['Description']);
const taskType = extractNotionSelect(props['Type']) || 'general';
const priority = extractNotionNumber(props['Priority']) || 5;
const priority = extractNotionNumber(props['Priority 1']) || extractNotionNumber(props['Priority']) || 5;
const dueDate = extractNotionDate(props['Due Date']);
const source = extractNotionSelect(props['Source']) || 'notion';
const verificationType = extractNotionSelect(props['Verification']) || 'soft';
Expand Down
Loading
Loading