Files
redmine/docs/redmine_contacts_search.md
Jason Thistlethwaite 9ee9eb3ec6 Organized
2026-10-17 20:50:35 -04:00

6.0 KiB

Contact search (redmine_contacts.py)

./redmine_contacts.py search --help prints three short examples. This page is the situation list. Gaps are in redmine_contacts_search_proposals.md.

This CLI was not changed to write this page. Commands below match the current search / show / helpdesk-issues behavior.

What search actually does

  1. Load the contact cache, or reload it with --refresh.
  2. Apply exact filters: --with-tag, --without-tag, --no-tags, --created-after, --created-before, --updated-after, --updated-before.
  3. Fuzzy-score the remaining contacts against the required positional query.
  4. Print text lines, capped by --limit (default 20).

It does not search tickets. It does not print JSON. A normal search still requires REDMINE_API_KEY even when it only reads the cache. --offline-cache is the exception.

Default host is the LAN copy, http://192.168.50.170. Production:

cd /home/jason/projects/redmine
./redmine_contacts.py --base-url https://rm.ldrprep.com search "Sahar Daod"

Cache file: .cache/redmine_contacts/<host>_<project>.json, relative to the current directory. A cache built for one host is rejected for another. This is not bin/rtl contacts-search.

A hit is a substring of a scored field after lowercasing, or a fuzzy score of at least 0.68. Scored fields: name, email, phone, company, job title, background, website, skype, tags, address, and custom-field values. A phone query is also matched on digits only, so 847-313-1342 hits 8473131342.

Output shape: id: name | company | email | phone | score=1.00 via email. Use show <id> for the record.

--refresh pages through every project contact before scoring. Without it, a tag or field written since the last fetch is invisible.

Supported

1. Find a person by name, email, or phone

./redmine_contacts.py --base-url https://rm.ldrprep.com search "Sahar Daod"
./redmine_contacts.py --base-url https://rm.ldrprep.com search sahardaod4@groovinfulfillment.com
./redmine_contacts.py --base-url https://rm.ldrprep.com search 847-313-1342

Then the record, including tag_list:

./redmine_contacts.py --base-url https://rm.ldrprep.com show 4589

show prints JSON. --source api skips a stale cache. --source cache does not call Redmine.

2. Find people at a company, or everyone on one email domain

Company and email are scored fields, not dedicated filters. A domain that appears in the address scores 1.00.

./redmine_contacts.py --base-url https://rm.ldrprep.com search gatsbylady.com --refresh
./redmine_contacts.py --base-url https://rm.ldrprep.com search "Cyberstorm" --min-score 0.9

Raise --min-score when a short company name also fuzzy-matches unrelated contacts. There is no --company or --email-domain flag. An exact company filter is proposal 1.

3. Exact tag

--with-tag is exact and case-insensitive. The positional query is still required and still fuzzy, so repeat the tag as the query. Otherwise a contact with the tag can be filtered in and then dropped because the name did not fuzzy-match.

./redmine_contacts.py --base-url https://rm.ldrprep.com \
  search promotional-spam --with-tag promotional-spam --refresh --limit 50

Offline, against the cache already on disk for that host:

./redmine_contacts.py --base-url https://rm.ldrprep.com \
  search promotional-spam --with-tag promotional-spam --offline-cache

Repeat --with-tag to require every tag. Repeat --without-tag to exclude any of those tags.

./redmine_contacts.py --base-url https://rm.ldrprep.com \
  search customer --with-tag customer --without-tag promotional-spam --limit 20

--no-tags cannot be combined with --with-tag. Listing every untagged contact is not this command. See proposal 2.

4. A tag, narrowed by when the contact was created or updated

Date flags are exact. The query is still required, so this finds "this text, inside this window," not "everyone touched this week."

./redmine_contacts.py --base-url https://rm.ldrprep.com \
  search customer --with-tag customer --updated-after 2026-09-01 --refresh

Dates are YYYY-MM-DD or a full ISO datetime. A date-only --updated-before includes that whole UTC day. Listing by date with no text query is proposal 2.

5. A known account number or other custom-field value

Custom-field values are scored if they are already on the cached contact. Account Number is custom field 70.

./redmine_contacts.py --base-url https://rm.ldrprep.com search 7385Y6 --refresh

This finds a value you already know. It cannot list customers whose Account Number is empty. That is proposal 3.

6. Their tickets, once you have the contact id

search stops at the contact. These follow-ups are live reads, not the contact cache.

./redmine_contacts.py --base-url https://rm.ldrprep.com helpdesk-issues 4589 --limit 20
./redmine_contacts.py --base-url https://rm.ldrprep.com helpdesk-timeline 4589 --limit 20

Both print JSON from /helpdesk_search/contacts/:id/....

Cached Customer Service tickets, any status:

bin/rtl search --order newest-updated-first --max-tickets 20 \
  --where 'helpdesk_ticket.contact_id == 4589 AND project.name == "Customer Service"'

Open New only:

bin/rtl search --status New --open-only \
  --where 'helpdesk_ticket.contact_id == 4589'

bin/rtl search cannot filter on tag_list. The ticket's contact stub is id, name, email, company. This returns nothing and does not error:

bin/rtl search --where 'helpdesk_ticket.contact.tag_list.includes("promotional-spam")'

One command that means "New tickets from contacts tagged promotional-spam" is proposal 4. bin/rtl contacts-search --tag is a different cache and can lag a live tag.

fetch --with-tag customer writes a cache and prints a count. It does not list contacts.

batch-update can select by the same tag and date filters, but it requires an update payload and exists to write. Do not use it as a lister.

loop-run is a triage worker, not a search.