Organized
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# Feature detail: Ticket attachments
|
||||
|
||||
**Parent roadmap:** [[../features-for-redmine_tickets-sync]]
|
||||
**Status:** MVP 2026-07-15
|
||||
**Module:** `redmine_ticket_attachments.py`
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
./bin/rtl attachments list --issue-id 39305 --refresh
|
||||
./bin/rtl attachments download --issue-id 39305 --attachment-id 55917
|
||||
./bin/rtl attachments download --issue-id 39305 --all
|
||||
./bin/rtd attachments upload --issue-id 39305 --file ./scan.pdf # dry-run
|
||||
./bin/rtd attachments upload --issue-id 39305 --file ./scan.pdf --apply --notes 'scan attached'
|
||||
```
|
||||
|
||||
| Op | Emails customer? | Notes |
|
||||
|----|------------------|--------|
|
||||
| list | no | cache-first; `--refresh` pulls `include=attachments` |
|
||||
| download | no | needs API key; default dir `<cache>/<project>/attachments/<issue_id>/` |
|
||||
| upload | **no** | Redmine `POST /uploads.json` + issue `uploads` token; prod needs `--allow-prod-writes` |
|
||||
|
||||
Sync now also requests `attachments` on full issue fetch so metadata lands in cache over time.
|
||||
|
||||
## Still open
|
||||
|
||||
- OCR rename workflow (see [[../ocr-ticket-attachment-to-rewrite-update-ticket]])
|
||||
- Draft-queue binary uploads (paths) if desired
|
||||
@@ -0,0 +1,44 @@
|
||||
# Feature detail: Interactive browse loop
|
||||
|
||||
**Parent roadmap:** [[../features-for-redmine_tickets-sync]]
|
||||
**Status:** MVP+ polish 2026-07-15
|
||||
**Module:** `redmine_ticket_browse.py`
|
||||
|
||||
## Flow
|
||||
|
||||
1. Filtered ticket list via **fzf** (preview pane) or numbered pick
|
||||
2. **Enter** → Markdown pager (`glow` / `bat` / `batcat` / `cat`)
|
||||
3. After pager → **action menu**
|
||||
4. Back to list until Esc/q
|
||||
|
||||
## Actions (queue-safe)
|
||||
|
||||
| Key | Behavior |
|
||||
|-----|----------|
|
||||
| v / Enter | View again |
|
||||
| n / p | Queue internal / private note |
|
||||
| f | Queue field update (names) |
|
||||
| t | Local control-tag |
|
||||
| c | ticket-contact handoff |
|
||||
| e | Pattern entities |
|
||||
| **m** | NMI cs-brief → confirm → **private** note draft |
|
||||
| i | Print id |
|
||||
| l / q | List / quit |
|
||||
|
||||
## Flags
|
||||
|
||||
- `--where`, `--status`, `--control-tag`, `--has-attachments`, **`--open-only`**
|
||||
- `--max-tickets`, `--order`, `--pager`, `--once`, **`--no-preview`**
|
||||
|
||||
## Done polish
|
||||
|
||||
- [x] fzf header lists actions
|
||||
- [x] fzf preview pane
|
||||
- [x] loud draft banner
|
||||
- [x] NMI menu action
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Full curses app (separate spike)
|
||||
- Customer-email from browse (CLI-only by design)
|
||||
- Applying prod writes from browse without draft step
|
||||
@@ -0,0 +1,41 @@
|
||||
# Config files / short aliases (minimal)
|
||||
|
||||
**Parent:** [[../features-for-redmine_tickets-sync]]
|
||||
**Status:** minimal MVP (global defaults + bin wrappers). Nested per-command defaults deferred.
|
||||
|
||||
## Runtime config
|
||||
|
||||
```bash
|
||||
./redmine_tickets.py --config configs/live.json search --max-tickets 10
|
||||
./redmine_tickets.py --config configs/dev.json note --issue-id 39305 --body 'x' --apply
|
||||
```
|
||||
|
||||
JSON (always works) or YAML if PyYAML is installed.
|
||||
|
||||
Supported top-level keys (also under `redmine_tickets:`):
|
||||
|
||||
- `base-url` / `base_url`
|
||||
- `project`
|
||||
- `cache-dir` / `cache_dir`
|
||||
- `allow-prod-writes` (bool)
|
||||
|
||||
CLI flags already present win over config.
|
||||
|
||||
## Short wrappers (`bin/`)
|
||||
|
||||
| Command | Maps to |
|
||||
|---------|---------|
|
||||
| `bin/rtl` | live/prod CS (`configs/live.json`) |
|
||||
| `bin/rtd` | LAN/dev CS (`configs/dev.json`) |
|
||||
| `bin/qaa` | alias → `rtl` (muscle-memory example) |
|
||||
|
||||
```bash
|
||||
./bin/rtl search --where 'assigned_to IS NULL' --max-tickets 5
|
||||
./bin/rtl --allow-prod-writes draft apply <id>
|
||||
./bin/rtd note --issue-id 39305 --body 'LAN note' --apply
|
||||
```
|
||||
|
||||
## Not yet
|
||||
|
||||
- Nested `search.max-tickets` / command-specific defaults from YAML namespaces
|
||||
- Auto-discovery of many named profiles beyond live/dev
|
||||
@@ -0,0 +1,5 @@
|
||||
## Contact Triage / Fixing
|
||||
|
||||
- [ ] Properly link "call transcript" and "voicemail" tickets to the correct person by searching the contact database with the phone number involved
|
||||
- [ ] Ingest call transcripts into our timeline
|
||||
- [ ] Fix contact company is set to their email address, when the name of their business is easily found through context in tickets
|
||||
@@ -0,0 +1,38 @@
|
||||
# Feature detail: Contacts sync + mutation (tie-in)
|
||||
|
||||
**Parent roadmap:** [[../features-for-redmine_tickets-sync]]
|
||||
**Status:** Sync/search/show + draftable `contact-set` + `ticket-contact` bridge (2026-07-15)
|
||||
**Module:** `redmine_ticket_contacts.py`
|
||||
**Still separate from:** full `redmine_contacts.py` CRM CLI (batch/loops/custom-field tooling)
|
||||
|
||||
## Why not one tool
|
||||
|
||||
- Tickets workbench wants co-located per-id JSON + draft/apply safety next to tickets.
|
||||
- `redmine_contacts.py` already owns heavy CRM workflows and a different cache layout.
|
||||
- Do not rewrite/delete the CRM CLI; add bridges so a ticket-started workflow can end on contacts.
|
||||
|
||||
## Implemented
|
||||
|
||||
```bash
|
||||
./bin/rtl contacts-sync --max-contacts 100
|
||||
./bin/rtl contacts-search --query ada
|
||||
./bin/rtl contact-show --contact-id 14 --fetch
|
||||
|
||||
# Bridge ticket → contact id
|
||||
./bin/rtl ticket-contact --issue-id 41934
|
||||
./bin/rtl ticket-contact --issue-id 41934 --show --fetch
|
||||
|
||||
# CRM mutation (draft by default; same prod/LAN gates as ticket writes)
|
||||
./bin/rtl contact-set --contact-id 4515 --add-tag maybe-customer
|
||||
./bin/rtd contact-set --contact-id 2284 --add-tag maybe-customer --apply --refresh-cache
|
||||
```
|
||||
|
||||
- Durable path: `<cache-dir>/contacts/<id>.json` + `manifest.json`
|
||||
- Draft kind: `contact_fields` (apply via `draft apply`)
|
||||
- Handoff hints print `redmine_contacts.py show …` for full CRM when needed
|
||||
|
||||
## Still open
|
||||
|
||||
- Richer custom-field helpers (use `redmine_contacts.py` or extend `--set`)
|
||||
- Optional cron for contacts next to ticket sync
|
||||
- Shared library / single on-disk layout if dual-cache friction becomes painful
|
||||
@@ -0,0 +1,31 @@
|
||||
# Feature detail: Local control tags (filter-assist labeling)
|
||||
|
||||
**Parent roadmap:** [[../features-for-redmine_tickets-sync]]
|
||||
**Status:** MVP implemented 2026-07-15 (`control-tag` CLI + `search --control-tag`)
|
||||
**Module:** `redmine_ticket_control_tags.py`
|
||||
**Critical distinction:** these tags are **local-only**. They are **not** Redmine issue tags, **not** contact CRM tags.
|
||||
|
||||
## Storage
|
||||
|
||||
`<cache-dir>/control-tags/tags.json` — wipe without touching Redmine or ticket JSON.
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
./redmine_tickets.py ... control-tag add --tag bavaria --issue-id 41934 \
|
||||
--property subject --property helpdesk_ticket.contact.company \
|
||||
--annotation 'BavariaTrachten shape'
|
||||
|
||||
./redmine_tickets.py ... control-tag list
|
||||
./redmine_tickets.py ... control-tag show --tag bavaria
|
||||
./redmine_tickets.py ... control-tag for-issue --issue-id 41934
|
||||
./redmine_tickets.py ... control-tag remove --tag bavaria --issue-id 41934
|
||||
./redmine_tickets.py ... control-tag wipe --i-understand-this-wipes-local-control-tags
|
||||
|
||||
./redmine_tickets.py ... search --control-tag bavaria
|
||||
```
|
||||
|
||||
## Still open
|
||||
|
||||
- Suggest/refine loop (LLM or rules + human correct/incorrect)
|
||||
- Training-export format for classifier experiments
|
||||
@@ -0,0 +1,25 @@
|
||||
# Feature detail: External entity type mapper / identifier
|
||||
|
||||
**Parent roadmap:** [[../features-for-redmine_tickets-sync]]
|
||||
**Status:** pattern MVP + NMI CLI bridge + browse `m` (2026-07-15)
|
||||
**Modules:** `redmine_ticket_entities.py`, `redmine_ticket_nmi.py`
|
||||
**NMI env docs:** [[nmi-bridge]]
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
./bin/rtl entities show --issue-id 41394
|
||||
./bin/rtl entities scan --where 'subject.includes("1Z")' --max-tickets 20
|
||||
./bin/rtl entities nmi --issue-id 41394
|
||||
```
|
||||
|
||||
## Done
|
||||
|
||||
- Pattern types: UPS/USPS, FedEx candidates, Amazon order, order_id, item_or_sku, **item_no** (LDR 9-digit)
|
||||
- Markdown view section: **Detected entities**
|
||||
- NMI bridge + browse **`m`** → private note draft
|
||||
|
||||
## Open
|
||||
|
||||
- [ ] Optional membership index of recent NMI order/item ids
|
||||
- [ ] Postal addresses / phones / social (much later)
|
||||
@@ -0,0 +1,39 @@
|
||||
# Feature detail: Local search + CLI Redmine client
|
||||
|
||||
**Parent roadmap:** [[../features-for-redmine_tickets-sync]]
|
||||
**Status:** MVP+ (2026-07-14 … 2026-07-15) — ops-usable
|
||||
**Cookbook:** `docs/cli_power_user_client.md`
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Role |
|
||||
|--------|------|
|
||||
| `redmine_ticket_query.py` | Property AST (`--where`) |
|
||||
| `redmine_ticket_view.py` | Markdown + pager |
|
||||
| `redmine_ticket_browse.py` | fzf → pager → action menu loop |
|
||||
| `redmine_ticket_filters.py` | Saved filters + symlink queues |
|
||||
| `redmine_ticket_drafts.py` | Draft queue + host safety |
|
||||
| `redmine_ticket_meta.py` | Name→id for set-fields |
|
||||
| `redmine_ticket_ocr.py` | Attachment text extract + subject suggest |
|
||||
|
||||
## Done
|
||||
|
||||
- [x] Local search / `@saved` filters / queues
|
||||
- [x] view / walk (`--open-only` on search/browse/walk)
|
||||
- [x] note vs customer-email vs set-fields (draft/apply)
|
||||
- [x] `--body-file` + `--to-textile` (pandoc)
|
||||
- [x] browse **loop** with action menu (queue-only mutations + NMI `m`)
|
||||
- [x] configs: `bin/rtl`, `bin/rtd`, `--config`
|
||||
- [x] `ocr-rename` + `@scanned-document`
|
||||
- [x] `categories list`
|
||||
|
||||
## Open
|
||||
|
||||
- [ ] Nested config defaults
|
||||
- [ ] Full curses TUI (optional later)
|
||||
- [ ] categories create/update
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Replacing Redmine web UI for everything
|
||||
- Silent customer email from browse
|
||||
@@ -0,0 +1,41 @@
|
||||
# NMI bridge env + usage
|
||||
|
||||
**Parent:** [[entity-type-mapper]] · module `redmine_ticket_nmi.py`
|
||||
|
||||
## Environment
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `NMI_CLI` | `/home/jason/projects/mrscClient-php/bin/nmi` | Path to NMI PHP CLI |
|
||||
| `NMI_ENV_FILE` | `/home/jason/projects/mrscClient-php/examples/modern/.env` | Env file for credentials/endpoint |
|
||||
| `NMI_MODE` | `dev` | CLI `--mode` (dev/prod as supported by nmi) |
|
||||
|
||||
Requires `php` on PATH.
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
# Pattern extract only
|
||||
./bin/rtl entities show --issue-id 41394
|
||||
./bin/rtl entities scan --type tracking_ups --max-tickets 20
|
||||
|
||||
# Resolve / cs-brief via NMI CLI
|
||||
./bin/rtl entities nmi --issue-id 41394
|
||||
./bin/rtl entities nmi --issue-id 41394 --nmi-mode dev
|
||||
./bin/rtl entities nmi --issue-id 41394 --as-private-note-draft # if wired; else use browse `m`
|
||||
```
|
||||
|
||||
## Browse
|
||||
|
||||
After opening a ticket in `browse`, press **`m`**:
|
||||
|
||||
1. Runs NMI brief from extracted entities
|
||||
2. Shows brief text
|
||||
3. Confirms → queues **private** internal note draft
|
||||
|
||||
Never emails the customer.
|
||||
|
||||
## Safety
|
||||
|
||||
- NMI is read-side ops context, not customer mail
|
||||
- Private note drafts still need `draft apply` (+ prod flags) to land in Redmine
|
||||
@@ -0,0 +1,46 @@
|
||||
# Feature detail: OCR attachment → subject rename
|
||||
|
||||
**Parent roadmap:** [[../features-for-redmine_tickets-sync]]
|
||||
**Status:** MVP 2026-07-15
|
||||
**Module:** `redmine_ticket_ocr.py` · CLI: `ocr-rename`
|
||||
|
||||
## Intent
|
||||
|
||||
Scanned docs land in Helpdesk as tickets titled “Scanned document…”.
|
||||
Ops need: open attachment → understand contents → rename subject (+ optional notes).
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# One ticket: download OCR-able attachments, propose subject, queue private note (+ subject draft)
|
||||
./bin/rtl ocr-rename --issue-id 12345
|
||||
|
||||
# Scan cache for generic scan subjects
|
||||
./bin/rtl ocr-rename --where '@scanned-document' --only-generic-subjects --max-tickets 20
|
||||
|
||||
# Apply subject now (LAN/prod safety gates); still queues private OCR note by default
|
||||
./bin/rtd ocr-rename --issue-id 12345 --apply-subject
|
||||
|
||||
# Extract only (no drafts)
|
||||
./bin/rtl ocr-rename --issue-id 12345 --no-queue-subject --no-queue-note --json
|
||||
```
|
||||
|
||||
## Pipeline
|
||||
|
||||
1. Load ticket + attachments (`include=attachments` refresh if needed)
|
||||
2. Download PDF/image candidates (multi-URL fallback)
|
||||
3. Extract text: `pdftotext` → else `pdftoppm` + `tesseract`
|
||||
4. Heuristic `suggest_subject_from_text` (doc type, person/company, date)
|
||||
5. Queue drafts:
|
||||
- `fields` subject change (unless `--no-queue-subject`)
|
||||
- private `internal_note` with OCR snippet (unless `--no-queue-note`)
|
||||
|
||||
## Saved filter
|
||||
|
||||
`saved-filters/scanned-document.expr` → `@scanned-document`
|
||||
|
||||
## Still open
|
||||
|
||||
- [ ] Stronger LLM subject summarization (optional; keep heuristics default)
|
||||
- [ ] Cron wrapper that only processes new generic-scan tickets
|
||||
- [ ] HR-project default project config example
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
- [ ] Download voicemails and call recordings from voip.ms automatically
|
||||
- [ ] helper PHP client library https://git.int.ldrprep.com/dusk/php-voip.ms
|
||||
@@ -0,0 +1,91 @@
|
||||
# Feature detail: Ticket sync foundation
|
||||
|
||||
**Parent roadmap:** [[../features-for-redmine_tickets-sync]]
|
||||
**Status:** largely implemented (2026-07-14); cron wiring still open
|
||||
**Primary script:** `redmine_tickets.py`
|
||||
**Target project (first):** `customer-service`
|
||||
|
||||
## Intent
|
||||
|
||||
Durable, incremental local mirror of Redmine issues so humans and agents can work offline-first, cron-refresh frequently, and not re-pull full payloads for unchanged tickets.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Sync tickets updated within a window (e.g. past 30 days) into a **stable** cache path (not timestamped per run).
|
||||
- Skip full fetch when cached `updated_on` still matches the list stub.
|
||||
- Support frequent refresh (every few minutes / couple times per hour) using a tight remote window after the initial warm-up.
|
||||
- Never delete old ticket JSON unless explicitly asked.
|
||||
- Optional small run-stats ring buffer for cron observability.
|
||||
|
||||
## Cache locations
|
||||
|
||||
| Environment | Path |
|
||||
|-------------|------|
|
||||
| Production (`https://rm.ldrprep.com`) | `/home/jason/projects/redmine/cache/redmine_tickets` |
|
||||
| Dev / LAN (`http://192.168.50.170`) | `/home/jason/projects/redmine/cache-dev/redmine_tickets` |
|
||||
|
||||
Layout:
|
||||
|
||||
```text
|
||||
<cache-dir>/
|
||||
manifest.json
|
||||
run-stats.json # optional, with --run-log
|
||||
<project>/
|
||||
<issue-id>.json
|
||||
```
|
||||
|
||||
## Server-side vs local filters (sync)
|
||||
|
||||
| Flag | Side | Meaning |
|
||||
|------|------|---------|
|
||||
| `--status-id` / `--remote-status-id` | **remote** | Redmine API `status_id` (`*`, `open`, `closed`, numeric) |
|
||||
| `--remote-updated-on` | **remote** | Raw Redmine syntax: `>=2026-06-14`, `>=2014-01-02T08:12:32Z`, `><a\|b` |
|
||||
| `--remote-updated-since` | **remote** | Convenience → `>=...` (YYYY-MM-DD, ISO, `30d`, `2 days ago`, …) |
|
||||
| `--remote-updated-before` | **remote** | Convenience → `<=...`; with since → `><since\|before` |
|
||||
| `--updated-since` / `--updated-before` / `--status` / `--category` | **local** | Post-filters after API list only |
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- List stubs intentionally omit heavy `include=journals,helpdesk`; full include is only on per-ticket fetch when `should_fetch` is true.
|
||||
- Progress on by default; `--quiet` for cron.
|
||||
- Unit tests: `tests/test_redmine_tickets_remote_filters.py`
|
||||
- Run log: `--run-log` → default `<cache-dir>/run-stats.json`, keep last N (`--run-log-keep`, default 3).
|
||||
|
||||
## Recommended commands
|
||||
|
||||
```bash
|
||||
cd /home/jason/projects/redmine
|
||||
|
||||
# Initial 30-day window (server-side)
|
||||
./redmine_tickets.py \
|
||||
--base-url https://rm.ldrprep.com \
|
||||
--project customer-service \
|
||||
--cache-dir /home/jason/projects/redmine/cache/redmine_tickets \
|
||||
sync \
|
||||
--remote-status-id '*' \
|
||||
--remote-updated-since 30d \
|
||||
--run-log
|
||||
|
||||
# Frequent refresh
|
||||
./redmine_tickets.py \
|
||||
--base-url https://rm.ldrprep.com \
|
||||
--project customer-service \
|
||||
--cache-dir /home/jason/projects/redmine/cache/redmine_tickets \
|
||||
sync \
|
||||
--remote-status-id '*' \
|
||||
--remote-updated-since 2h \
|
||||
--run-log \
|
||||
--quiet
|
||||
```
|
||||
|
||||
Raw Redmine form:
|
||||
|
||||
```bash
|
||||
./redmine_tickets.py ... sync --remote-updated-on '>=2026-06-14' --status-id '*'
|
||||
```
|
||||
|
||||
## Open
|
||||
|
||||
- [x] Scripts under `cron-scripts/` installed + crontab running (live + dev)
|
||||
- [ ] **ADJUST LATER (do not forget):** cron still uses `--remote-updated-since 30d` on a few-minute cadence. Fine while validating (~3–4s/run after warm cache) but should tighten to `2h`/`6h` (or marker-based since-last-success) so list pages stay cheap long-term. `run-log-keep 20` is already set.
|
||||
- [ ] Optional: master storage + symlink organization of JSON (see parent roadmap)
|
||||
Reference in New Issue
Block a user