wiq-cli 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/docs/deferred.md +16 -5
- data/docs/wiq_api_notes.md +70 -10
- data/lib/wiq/cli.rb +6 -0
- data/lib/wiq/commands/events.rb +13 -3
- data/lib/wiq/commands/locations.rb +64 -0
- data/lib/wiq/commands/metrics.rb +16 -2
- data/lib/wiq/commands/paid_sessions.rb +6 -0
- data/lib/wiq/commands/parents.rb +114 -0
- data/lib/wiq/commands/reports.rb +36 -18
- data/lib/wiq/commands/rosters.rb +8 -0
- data/lib/wiq/commands/wrestlers.rb +27 -6
- data/lib/wiq/version.rb +1 -1
- data/lib/wiq.rb +2 -0
- data/share/skills/wiq/SKILL.md +74 -5
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ee99cfc6ad08abdf7e1318883bcfa3ecc45635d2d521de150ed1622d867b0ebc
|
|
4
|
+
data.tar.gz: d9b6f3d18c731b4ddfe1060a03a40310e4d58b940d1f00f97dd3ef480f22dd74
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 89f8f34ae2184fab2069c8a0d51eaf53936dcb69e538e2fd7c0f659900e1be6ea3ee2f43101726ecb25bcd933a77beb20b2b954af4facbbfb248f4f0e40ae914
|
|
7
|
+
data.tar.gz: b63afbb5f6755e1821f268d0a797fc1d1f12d97148f790aa340ad0ec55bdf86df91ee649d003caaabe33351e91a25f848217fd6e26da09c3cf6dcc136b173d15
|
data/docs/deferred.md
CHANGED
|
@@ -68,9 +68,16 @@ Last updated: 2026-05-12 (after L1 ships).
|
|
|
68
68
|
--all flag, base payload kept tight. Filters translate to Ransack
|
|
69
69
|
(--first-name, --last-name, --weight-class, --academic-class, --age,
|
|
70
70
|
--roster, --profile-type) + legacy free-text --query. --expand opts
|
|
71
|
-
into rosters / registration_answers
|
|
71
|
+
into rosters / registration_answers / notification_preferences
|
|
72
|
+
(coach-only, Aug 2026 — see wiq_api_notes.md) per row. Multi-roster
|
|
72
73
|
intersection deferred (API supports it; CLI surface didn't justify
|
|
73
74
|
the complexity for v1). `wiq wrestlers show <id>` paired.
|
|
75
|
+
- ~~**`wiq parents list/show`**~~ SHIPPED narrow (Aug 2026, alongside
|
|
76
|
+
the `expand_notification_preferences` param from wrestling PR #2460).
|
|
77
|
+
Filters: --query (legacy name search), --first-name / --last-name
|
|
78
|
+
(Ransack). --expand notification_preferences (coach-only). Slim
|
|
79
|
+
payload by design (id, user_id, type, names); no wrestler refs —
|
|
80
|
+
family linkage stays on the wrestlers side.
|
|
74
81
|
- **`wiq url parse <url>`** — extract team/roster/event IDs from WIQ web
|
|
75
82
|
URLs. Initial-plan item; useful for agents pasted URLs by humans.
|
|
76
83
|
- **`wiq paid_sessions create/update`**, **`wiq rosters create/update`**,
|
|
@@ -144,10 +151,14 @@ Last updated: 2026-05-12 (after L1 ships).
|
|
|
144
151
|
- **Echo `request_id`** in the response body or `X-Request-ID` header,
|
|
145
152
|
for support correlation. Today the CLI can't give a customer a request
|
|
146
153
|
ID to ship to support.
|
|
147
|
-
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
154
|
+
- ~~**Real location/site filter on events.**~~ — SHIPPED (June 2026,
|
|
155
|
+
wre-506). The backend grew a structured Location model. The CLI now
|
|
156
|
+
exposes `wiq locations list|show` plus `--location` flags on
|
|
157
|
+
`events list` (repeatable → `location_ids[]`), `rosters list`
|
|
158
|
+
(`q[location_id_eq]`), `paid_sessions list`, `wrestlers list`,
|
|
159
|
+
`metrics show`, and `reports run` (args.location_id, honored by 13
|
|
160
|
+
report types). The legacy free-text `Event.location` remains for old
|
|
161
|
+
rows; serialized `location` is the display form.
|
|
151
162
|
- **Subdomain discovery flow for `wiq auth login`.** PAT settings URL
|
|
152
163
|
lives at `<team-subdomain>.wrestlingiq.com/settings/personal_access_tokens`.
|
|
153
164
|
If a customer doesn't know their subdomain, the CLI can't deep-link
|
data/docs/wiq_api_notes.md
CHANGED
|
@@ -390,6 +390,60 @@ them. PATs minted by parents/wrestlers will 403 here.
|
|
|
390
390
|
shape (`category`, `subcategory`, `detail_category`, `total_net_amount`)
|
|
391
391
|
— special-case it in the formatter.
|
|
392
392
|
|
|
393
|
+
## Wrestlers + parents: `expand_notification_preferences`
|
|
394
|
+
|
|
395
|
+
Shipped Aug 2026 (wrestling PR #2460). All four endpoints accept
|
|
396
|
+
`expand_notification_preferences=true`:
|
|
397
|
+
|
|
398
|
+
- `GET /api/v1/wrestlers` / `GET /api/v1/wrestlers/:id`
|
|
399
|
+
- `GET /api/v1/parents` / `GET /api/v1/parents/:id`
|
|
400
|
+
|
|
401
|
+
When set, each profile gains two fields, mirroring the "Notified via"
|
|
402
|
+
section on the web profile pages:
|
|
403
|
+
|
|
404
|
+
```json
|
|
405
|
+
{
|
|
406
|
+
"wiq_app_installed": true,
|
|
407
|
+
"notification_preferences": {
|
|
408
|
+
"email": true, "sms": false, "push": true, "push_user_pref": true
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Things to know:
|
|
414
|
+
|
|
415
|
+
- **Coach-gated, silently.** `expand_notification_preferences?` in
|
|
416
|
+
`base_controller.rb` requires `current_profile.coach?`. A parent- or
|
|
417
|
+
wrestler-bound PAT gets a normal 200 with the fields simply absent —
|
|
418
|
+
no error. Don't diagnose missing fields as a bug; check the PAT's
|
|
419
|
+
profile type first (`wiq auth status`).
|
|
420
|
+
- **Nullable prefs.** `notification_preference` is a `has_one` that may
|
|
421
|
+
not exist; each key can be `null` (rendered via `np&.email` etc.).
|
|
422
|
+
Treat `null` as "no explicit preference recorded", not "off".
|
|
423
|
+
- The controller eager-loads `:notification_preference` only when the
|
|
424
|
+
flag is set, so the index cost is opt-in.
|
|
425
|
+
|
|
426
|
+
CLI exposure: `wiq wrestlers list|show --expand notification_preferences`
|
|
427
|
+
(composes with `rosters`, `registration_answers` in the same CSV) and
|
|
428
|
+
`wiq parents list|show --expand notification_preferences`.
|
|
429
|
+
|
|
430
|
+
While we're here, the parents endpoints themselves
|
|
431
|
+
(`app/controllers/api/v1/parents_controller.rb`):
|
|
432
|
+
|
|
433
|
+
- Index scope is `team.parent_profiles.teammates` ordered by first
|
|
434
|
+
name — guest parents (camp signups) are excluded. Policy scope:
|
|
435
|
+
coaches see the whole team (roster-restricted coaches see only their
|
|
436
|
+
allowed parents), wrestlers see their own guardians, parents see
|
|
437
|
+
nobody.
|
|
438
|
+
- Filters: legacy `?query=` name search, plus Ransack limited to
|
|
439
|
+
`id, first_name, last_name` (`ParentProfile.ransackable_attributes`).
|
|
440
|
+
- The jbuilder payload is slim: `id, user_id, type, first_name,
|
|
441
|
+
last_name, full_name` (+ the expand fields above). No wrestler refs —
|
|
442
|
+
walk the family from the wrestler side (`wiq wrestlers show <id>`
|
|
443
|
+
embeds parent refs).
|
|
444
|
+
- `show` uses `ParentProfile.with_deleted` — soft-deleted parents still
|
|
445
|
+
resolve by id.
|
|
446
|
+
|
|
393
447
|
## Check-ins (attendance)
|
|
394
448
|
|
|
395
449
|
Two index endpoints + create/update on the event-scoped path.
|
|
@@ -468,13 +522,16 @@ Other registration surface:
|
|
|
468
522
|
- `expand=event_invites,event_bookings,private_lessons` — comma-separated
|
|
469
523
|
CSV; pulls in nested data on the events index/show.
|
|
470
524
|
- `Event.ransackable_attributes` allows
|
|
471
|
-
`id, name, start_at, end_at, event_type, paid_session_id`.
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
525
|
+
`id, name, start_at, end_at, event_type, paid_session_id`. The
|
|
526
|
+
free-text `location` column is still not ransackable, but the
|
|
527
|
+
structured location filter shipped (June 2026): pass `location_ids[]`
|
|
528
|
+
(repeatable, dedicated param handled in `apply_event_filters`, not
|
|
529
|
+
Ransack) to filter on the event's `location_id`. Events with no
|
|
530
|
+
`location_id` are excluded from a filtered listing. The serialized
|
|
531
|
+
`location` field is now `Event#display_location` — the structured
|
|
532
|
+
Location's name when set, else the legacy free text — and `location_id`
|
|
533
|
+
is serialized alongside it. The CLI exposes this as
|
|
534
|
+
`wiq events list --location <id> [<id>...]`.
|
|
478
535
|
- Soft deletes: events use `acts_as_paranoid`; the index calls
|
|
479
536
|
`.without_deleted`. No "include deleted" param exposed.
|
|
480
537
|
- `POST /api/v1/events` — recurring practice creation:
|
|
@@ -642,9 +699,12 @@ PaidSession-overlap convention proves load-bearing for many customers.
|
|
|
642
699
|
demand. Until then, jbuilders are the source of truth.
|
|
643
700
|
2. `request_id` echoed in response body and/or `X-Request-ID` header for
|
|
644
701
|
support correlation.
|
|
645
|
-
3. Real location/site filter on `GET /api/v1/events
|
|
646
|
-
|
|
647
|
-
|
|
702
|
+
3. ~~Real location/site filter on `GET /api/v1/events`~~ — SHIPPED
|
|
703
|
+
(June 2026, wre-506). Structured Location model + `location_ids[]` on
|
|
704
|
+
events, `location_id` on paid_sessions/wrestlers/metrics/reports,
|
|
705
|
+
`q[location_id_eq]` on rosters, and a `GET /api/v1/locations`
|
|
706
|
+
resource. All exposed in the CLI via `wiq locations` and `--location`
|
|
707
|
+
flags.
|
|
648
708
|
4. Subdomain discovery for `wiq auth login`. The PAT settings URL lives at
|
|
649
709
|
`<team-subdomain>.wrestlingiq.com/settings/personal_access_tokens`; if
|
|
650
710
|
the customer doesn't know their subdomain, the CLI can't deep-link
|
data/lib/wiq/cli.rb
CHANGED
|
@@ -65,6 +65,9 @@ module Wiq
|
|
|
65
65
|
desc "rosters SUBCOMMAND", "Rosters (list, show)"
|
|
66
66
|
subcommand "rosters", Wiq::Commands::Rosters
|
|
67
67
|
|
|
68
|
+
desc "locations SUBCOMMAND", "Team locations / sites — ID discovery for --location flags (list, show)"
|
|
69
|
+
subcommand "locations", Wiq::Commands::Locations
|
|
70
|
+
|
|
68
71
|
desc "prospects SUBCOMMAND", "Prospect pipeline — individual kids (list, show, summary)"
|
|
69
72
|
subcommand "prospects", Wiq::Commands::Prospects
|
|
70
73
|
|
|
@@ -80,6 +83,9 @@ module Wiq
|
|
|
80
83
|
desc "wrestlers SUBCOMMAND", "Search wrestlers — narrow ID-discovery surface (list, show)"
|
|
81
84
|
subcommand "wrestlers", Wiq::Commands::Wrestlers
|
|
82
85
|
|
|
86
|
+
desc "parents SUBCOMMAND", "Search parents — narrow ID-discovery surface (list, show)"
|
|
87
|
+
subcommand "parents", Wiq::Commands::Parents
|
|
88
|
+
|
|
83
89
|
desc "charges SUBCOMMAND", "Payment history — list charges by family, status, type, date (admin only)"
|
|
84
90
|
subcommand "charges", Wiq::Commands::Charges
|
|
85
91
|
|
data/lib/wiq/commands/events.rb
CHANGED
|
@@ -25,18 +25,25 @@ module Wiq
|
|
|
25
25
|
--event-type One of `wiq events types` (practice, dual_meet, ...)
|
|
26
26
|
--roster One or more roster ids (the API joins through
|
|
27
27
|
roster_events)
|
|
28
|
+
--location One or more location ids (structured Location
|
|
29
|
+
records; discover via `wiq locations list`)
|
|
28
30
|
--expand CSV of event_invites,event_bookings,private_lessons.
|
|
29
31
|
Drops nested data into each event row.
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
Location caveat: --location matches the event's own structured
|
|
34
|
+
location_id. Events with NO location set are excluded from a
|
|
35
|
+
filtered listing — they only appear when you don't filter. The
|
|
36
|
+
legacy free-text `location` field still exists on old events; the
|
|
37
|
+
serialized `location` value is the display form (structured record
|
|
38
|
+
when set, else the free text).
|
|
34
39
|
|
|
35
40
|
Use --all to walk every page; default is page 1, per_page=100.
|
|
36
41
|
DESC
|
|
37
42
|
method_option :start, type: :string, required: true, desc: "YYYY-MM-DD (team timezone)"
|
|
38
43
|
method_option :end, type: :string, required: true, desc: "YYYY-MM-DD (team timezone)"
|
|
39
44
|
method_option :roster, type: :array, desc: "One or more roster ids"
|
|
45
|
+
method_option :location, type: :array,
|
|
46
|
+
desc: "One or more location ids (events with no location are excluded)"
|
|
40
47
|
method_option :event_type, type: :string, enum: %w[practice dual_meet tournament scramble private_lesson other],
|
|
41
48
|
desc: "Filter to one event_type (see `wiq events types`)"
|
|
42
49
|
method_option :expand, type: :string,
|
|
@@ -53,6 +60,9 @@ module Wiq
|
|
|
53
60
|
if options[:roster]
|
|
54
61
|
options[:roster].each { |rid| (params["roster_ids[]"] ||= []) << rid }
|
|
55
62
|
end
|
|
63
|
+
if options[:location]
|
|
64
|
+
options[:location].each { |lid| (params["location_ids[]"] ||= []) << lid }
|
|
65
|
+
end
|
|
56
66
|
|
|
57
67
|
records, total = fetch_index("/api/v1/events", params, key: "events")
|
|
58
68
|
render_index(
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wiq
|
|
4
|
+
module Commands
|
|
5
|
+
class Locations < Base
|
|
6
|
+
desc "list", "List the team's structured locations (sites/gyms)"
|
|
7
|
+
long_desc <<~DESC
|
|
8
|
+
Locations are WIQ's structured multi-site concept — a named place
|
|
9
|
+
(with optional street address) that rosters, events, paid sessions,
|
|
10
|
+
wrestlers, metrics, and reports can be scoped to. Single-site clubs
|
|
11
|
+
typically have zero or one; multi-site clubs use them to slice
|
|
12
|
+
everything per gym.
|
|
13
|
+
|
|
14
|
+
Archived locations are hidden by default; pass --include-archived
|
|
15
|
+
to see them. Ordered by name server-side.
|
|
16
|
+
|
|
17
|
+
This is the ID-discovery surface for every --location flag in the
|
|
18
|
+
CLI:
|
|
19
|
+
wiq rosters list --location <id>
|
|
20
|
+
wiq events list --location <id> [...more ids]
|
|
21
|
+
wiq wrestlers list --location <id>
|
|
22
|
+
wiq paid_sessions list --location <id>
|
|
23
|
+
wiq metrics show <name> --location <id>
|
|
24
|
+
wiq reports run <Type> --location <id>
|
|
25
|
+
DESC
|
|
26
|
+
method_option :include_archived, type: :boolean, default: false,
|
|
27
|
+
desc: "Include archived locations"
|
|
28
|
+
method_option :all, type: :boolean, default: false
|
|
29
|
+
def list
|
|
30
|
+
params = { "per_page" => 100 }
|
|
31
|
+
params["include_archived"] = true if options[:include_archived]
|
|
32
|
+
|
|
33
|
+
records, total = fetch_index("/api/v1/locations", params, key: "locations")
|
|
34
|
+
render_index(
|
|
35
|
+
records, total: total,
|
|
36
|
+
summary: "Listed #{records.size} locations#{options[:include_archived] ? " (including archived)" : ""}.",
|
|
37
|
+
breadcrumbs: [
|
|
38
|
+
{ "cmd" => "wiq locations show <id>", "description" => "Inspect a single location" },
|
|
39
|
+
{ "cmd" => "wiq rosters list --location <id>", "description" => "Rosters at a location" },
|
|
40
|
+
{ "cmd" => "wiq metrics show mrr --location <id>",
|
|
41
|
+
"description" => "Finance metrics scoped to a location (admin only)" }
|
|
42
|
+
]
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc "show ID", "Fetch a single location"
|
|
47
|
+
long_desc <<~DESC
|
|
48
|
+
Full location payload: id, name, address_line1, address_line2,
|
|
49
|
+
city, state, postal_code, country, archived.
|
|
50
|
+
DESC
|
|
51
|
+
def show(id)
|
|
52
|
+
location = client.get("/api/v1/locations/#{id}")
|
|
53
|
+
render(location,
|
|
54
|
+
summary: "Location #{location["id"]} — #{location["name"]}",
|
|
55
|
+
breadcrumbs: [
|
|
56
|
+
{ "cmd" => "wiq rosters list --location #{location["id"]}",
|
|
57
|
+
"description" => "Rosters at this location" },
|
|
58
|
+
{ "cmd" => "wiq wrestlers list --location #{location["id"]}",
|
|
59
|
+
"description" => "Wrestlers on any roster at this location" }
|
|
60
|
+
])
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
data/lib/wiq/commands/metrics.rb
CHANGED
|
@@ -62,6 +62,13 @@ module Wiq
|
|
|
62
62
|
Intervals: hourly, daily, weekly, monthly. Invalid values are
|
|
63
63
|
silently coerced to `daily` server-side.
|
|
64
64
|
|
|
65
|
+
--location <id> scopes every metric to one structured location
|
|
66
|
+
(discover ids via `wiq locations list`). CAUTION: the server
|
|
67
|
+
validates the id against the team's own locations and silently
|
|
68
|
+
falls back to ALL locations when it doesn't match — a typo'd or
|
|
69
|
+
foreign id returns team-wide numbers, not an error. The CLI echoes
|
|
70
|
+
the requested location in `meta` so you can sanity-check.
|
|
71
|
+
|
|
65
72
|
Currency metrics return integer cents (divide by 100 for dollars).
|
|
66
73
|
Non-currency metrics return raw counts.
|
|
67
74
|
DESC
|
|
@@ -70,6 +77,8 @@ module Wiq
|
|
|
70
77
|
enum: VALID_INTERVALS, desc: "interval_group"
|
|
71
78
|
method_option :start_date, type: :string, desc: "Required if --range=custom"
|
|
72
79
|
method_option :end_date, type: :string, desc: "Required if --range=custom"
|
|
80
|
+
method_option :location, type: :numeric,
|
|
81
|
+
desc: "Scope to one location id (unknown ids silently mean ALL locations)"
|
|
73
82
|
method_option :no_comparison, type: :boolean, default: false,
|
|
74
83
|
desc: "Drop comparison_series/comparison_total from output"
|
|
75
84
|
def show(name)
|
|
@@ -91,6 +100,7 @@ module Wiq
|
|
|
91
100
|
params["start_date"] = options[:start_date]
|
|
92
101
|
params["end_date"] = options[:end_date]
|
|
93
102
|
end
|
|
103
|
+
params["location_id"] = options[:location] if options[:location]
|
|
94
104
|
|
|
95
105
|
payload = client.get("/api/v1/metrics/#{name}", params)
|
|
96
106
|
metrics = payload["metrics"] || {}
|
|
@@ -106,9 +116,13 @@ module Wiq
|
|
|
106
116
|
data["comparison_total"] = metrics["comparison_total"]
|
|
107
117
|
end
|
|
108
118
|
|
|
119
|
+
meta = { "currency_unit" => CURRENCY_METRICS.include?(name) ? "cents" : "count" }
|
|
120
|
+
meta["location_id"] = options[:location] if options[:location]
|
|
121
|
+
|
|
109
122
|
render(data,
|
|
110
|
-
summary: "metric=#{name} range=#{options[:range]} interval=#{options[:interval]}"
|
|
111
|
-
|
|
123
|
+
summary: "metric=#{name} range=#{options[:range]} interval=#{options[:interval]}" \
|
|
124
|
+
"#{options[:location] ? " location=#{options[:location]}" : ""}",
|
|
125
|
+
meta: meta,
|
|
112
126
|
breadcrumbs: [
|
|
113
127
|
{ "cmd" => "wiq metrics list", "description" => "See all supported metrics" }
|
|
114
128
|
])
|
|
@@ -20,6 +20,10 @@ module Wiq
|
|
|
20
20
|
recurring_registerable, not_recurring, not_recurring_with_archived,
|
|
21
21
|
not_archived, dropin, trial, trial_or_dropin
|
|
22
22
|
|
|
23
|
+
--location <id> filters server-side to sessions stamped with that
|
|
24
|
+
structured location (discover ids via `wiq locations list`).
|
|
25
|
+
Sessions with no location are excluded when the filter is on.
|
|
26
|
+
|
|
23
27
|
--season filters CLI-side to sessions whose [start_at, end_at] window
|
|
24
28
|
overlaps the given calendar year. Pair with --all to get an exhaustive
|
|
25
29
|
list.
|
|
@@ -30,11 +34,13 @@ module Wiq
|
|
|
30
34
|
you usually don't need a follow-up call.
|
|
31
35
|
DESC
|
|
32
36
|
method_option :type, type: :string, enum: PRESET_TYPES, desc: "Preset scope filter"
|
|
37
|
+
method_option :location, type: :numeric, desc: "Filter to sessions at one location id"
|
|
33
38
|
method_option :season, type: :numeric, desc: "Filter to sessions overlapping calendar year"
|
|
34
39
|
method_option :all, type: :boolean, default: false
|
|
35
40
|
def list
|
|
36
41
|
params = { "per_page" => 50 }
|
|
37
42
|
params[:type] = options[:type] if options[:type]
|
|
43
|
+
params["location_id"] = options[:location] if options[:location]
|
|
38
44
|
|
|
39
45
|
records, total = fetch_index("/api/v1/paid_sessions", params, key: "paid_sessions")
|
|
40
46
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wiq
|
|
4
|
+
module Commands
|
|
5
|
+
class Parents < Base
|
|
6
|
+
DEFAULT_PER_PAGE = 20
|
|
7
|
+
|
|
8
|
+
desc "list", "Search parents — narrow ID-discovery surface"
|
|
9
|
+
long_desc <<~DESC
|
|
10
|
+
Narrow listing over the team's parent profiles (teammates only —
|
|
11
|
+
guest parents from camp signups are excluded server-side). Rows
|
|
12
|
+
are slim: id, user_id, type, first_name, last_name, full_name.
|
|
13
|
+
Sorted by first name. Default page size is 20; there's no --all
|
|
14
|
+
flag.
|
|
15
|
+
|
|
16
|
+
Filters:
|
|
17
|
+
--query free-text name search (legacy `?query=`)
|
|
18
|
+
--first-name q[first_name_cont]
|
|
19
|
+
--last-name q[last_name_cont]
|
|
20
|
+
|
|
21
|
+
--expand opts into a wider payload:
|
|
22
|
+
notification_preferences Adds wiq_app_installed + a
|
|
23
|
+
notification_preferences object
|
|
24
|
+
(email, sms, push, push_user_pref)
|
|
25
|
+
per parent — "which parents can we
|
|
26
|
+
reach, and how?". Coach PATs only;
|
|
27
|
+
the server silently omits it for
|
|
28
|
+
parent/wrestler tokens.
|
|
29
|
+
|
|
30
|
+
The parent → wrestler linkage is NOT in this payload. To see a
|
|
31
|
+
family's kids, go the other direction: `wiq wrestlers show <id>`
|
|
32
|
+
embeds parent refs, or `wiq wrestlers list --query <last name>`.
|
|
33
|
+
DESC
|
|
34
|
+
method_option :query, type: :string, desc: "Free-text name search"
|
|
35
|
+
method_option :first_name, type: :string, desc: "First name (contains)"
|
|
36
|
+
method_option :last_name, type: :string, desc: "Last name (contains)"
|
|
37
|
+
method_option :expand, type: :string,
|
|
38
|
+
desc: "CSV: notification_preferences"
|
|
39
|
+
method_option :per_page, type: :numeric, default: DEFAULT_PER_PAGE,
|
|
40
|
+
desc: "Page size (default 20; narrow surface)"
|
|
41
|
+
def list
|
|
42
|
+
params = build_list_params
|
|
43
|
+
|
|
44
|
+
records, total = fetch_index("/api/v1/parents", params, key: "parent_profiles")
|
|
45
|
+
render_index(
|
|
46
|
+
records, total: total,
|
|
47
|
+
summary: "Listed #{records.size} parents#{summary_filters_suffix}.",
|
|
48
|
+
breadcrumbs: [
|
|
49
|
+
{ "cmd" => "wiq parents show <id>", "description" => "Drill into one parent" },
|
|
50
|
+
{ "cmd" => "wiq billing_profiles show <id> --profile-type ParentProfile",
|
|
51
|
+
"description" => "Billing profile for a parent (admin only)" },
|
|
52
|
+
{ "cmd" => "wiq wrestlers list --last-name <name>",
|
|
53
|
+
"description" => "Find the family's wrestlers (parent refs embedded)" }
|
|
54
|
+
]
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
desc "show ID", "Fetch a single parent profile"
|
|
59
|
+
long_desc <<~DESC
|
|
60
|
+
Single-parent drill-down. Same slim payload as the index (id,
|
|
61
|
+
user_id, type, names); soft-deleted parents still resolve here.
|
|
62
|
+
|
|
63
|
+
Pass --expand for additional sections:
|
|
64
|
+
notification_preferences wiq_app_installed + notification_preferences
|
|
65
|
+
(email, sms, push, push_user_pref) —
|
|
66
|
+
"can this parent be reached, and how?"
|
|
67
|
+
Coach PATs only; silently omitted for
|
|
68
|
+
parent/wrestler tokens.
|
|
69
|
+
DESC
|
|
70
|
+
method_option :expand, type: :string,
|
|
71
|
+
desc: "CSV: notification_preferences"
|
|
72
|
+
def show(id)
|
|
73
|
+
params = {}
|
|
74
|
+
params["expand_notification_preferences"] = true if expand_includes?("notification_preferences")
|
|
75
|
+
parent = client.get("/api/v1/parents/#{id}", params)
|
|
76
|
+
render(parent,
|
|
77
|
+
summary: "Parent #{parent["id"]} — #{parent["full_name"]}",
|
|
78
|
+
breadcrumbs: [
|
|
79
|
+
{ "cmd" => "wiq billing_profiles show #{parent["id"]} --profile-type ParentProfile",
|
|
80
|
+
"description" => "Billing profile for this parent (admin only)" },
|
|
81
|
+
{ "cmd" => "wiq wrestlers list --last-name #{parent["last_name"]}",
|
|
82
|
+
"description" => "Likely wrestlers for this family" }
|
|
83
|
+
])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
no_commands do
|
|
87
|
+
# Extracted so the spec can verify the option → query-param mapping
|
|
88
|
+
# without round-tripping a real Faraday connection.
|
|
89
|
+
def build_list_params
|
|
90
|
+
params = { "per_page" => options[:per_page] || DEFAULT_PER_PAGE }
|
|
91
|
+
params["query"] = options[:query] if options[:query]
|
|
92
|
+
params["q[first_name_cont]"] = options[:first_name] if options[:first_name]
|
|
93
|
+
params["q[last_name_cont]"] = options[:last_name] if options[:last_name]
|
|
94
|
+
params["expand_notification_preferences"] = true if expand_includes?("notification_preferences")
|
|
95
|
+
params
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def expand_includes?(part)
|
|
99
|
+
return false unless options[:expand]
|
|
100
|
+
|
|
101
|
+
options[:expand].split(",").map(&:strip).include?(part)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def summary_filters_suffix
|
|
105
|
+
bits = []
|
|
106
|
+
bits << "matching #{options[:query].inspect}" if options[:query]
|
|
107
|
+
bits << "first name ~ #{options[:first_name].inspect}" if options[:first_name]
|
|
108
|
+
bits << "last name ~ #{options[:last_name].inspect}" if options[:last_name]
|
|
109
|
+
bits.empty? ? "" : " (#{bits.join(", ")})"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
data/lib/wiq/commands/reports.rb
CHANGED
|
@@ -26,7 +26,7 @@ module Wiq
|
|
|
26
26
|
TYPES = {
|
|
27
27
|
# ── Roster tab ───────────────────────────────────────────────
|
|
28
28
|
"RosterReport" => {
|
|
29
|
-
args: %w[roster_id append_property_ids include_archived_roster_tags],
|
|
29
|
+
args: %w[roster_id location_id append_property_ids include_archived_roster_tags],
|
|
30
30
|
dates: :optional,
|
|
31
31
|
desc: "Roster snapshot — name, weight class, academic class, age",
|
|
32
32
|
recommended: true,
|
|
@@ -41,8 +41,11 @@ module Wiq
|
|
|
41
41
|
"to roster at\" column (roster_memberships.created_at — when the " \
|
|
42
42
|
"wrestler landed on the roster, NOT their registration date), " \
|
|
43
43
|
"populated only for a specific --roster <id> (id > 0), not " \
|
|
44
|
-
"--roster 0. --
|
|
45
|
-
"that
|
|
44
|
+
"--roster 0. With --location <id> instead, the report scopes to " \
|
|
45
|
+
"wrestlers on any roster at that location (one row each) and " \
|
|
46
|
+
"\"Added to roster at\" becomes their EARLIEST membership across " \
|
|
47
|
+
"that location's rosters. --v1 returns fuller per-wrestler " \
|
|
48
|
+
"objects but drops that column.",
|
|
46
49
|
example: "wiq reports run RosterReport --roster 42"
|
|
47
50
|
},
|
|
48
51
|
"FullExportWrestlerReport" => {
|
|
@@ -67,52 +70,52 @@ module Wiq
|
|
|
67
70
|
|
|
68
71
|
# ── USAW / AAU tab ───────────────────────────────────────────
|
|
69
72
|
"UsawReport" => {
|
|
70
|
-
args: %w[roster_id],
|
|
73
|
+
args: %w[roster_id location_id],
|
|
71
74
|
dates: :optional,
|
|
72
75
|
desc: "All USA Wrestling card info on file, one row per wrestler",
|
|
73
76
|
recommended: true,
|
|
74
77
|
notes: "UI hides this tab when USAW collection is off (team setting)."
|
|
75
78
|
},
|
|
76
79
|
"UsawExpiredReport" => {
|
|
77
|
-
args: %w[roster_id],
|
|
80
|
+
args: %w[roster_id location_id],
|
|
78
81
|
dates: :optional,
|
|
79
82
|
desc: "Wrestlers missing or with expired USAW memberships",
|
|
80
83
|
notes: "Output is also a bulk-purchase upload format for USAW's system."
|
|
81
84
|
},
|
|
82
85
|
"UsawExportReport" => {
|
|
83
|
-
args: %w[roster_id paid_session_id],
|
|
86
|
+
args: %w[roster_id location_id paid_session_id],
|
|
84
87
|
dates: :optional,
|
|
85
88
|
desc: "USAW bulk-purchase upload format",
|
|
86
89
|
notes: "Only report that accepts paid_session_id=0 to mean " \
|
|
87
90
|
"\"all sessions\"."
|
|
88
91
|
},
|
|
89
92
|
"AauReport" => {
|
|
90
|
-
args: %w[roster_id],
|
|
93
|
+
args: %w[roster_id location_id],
|
|
91
94
|
dates: :optional,
|
|
92
95
|
desc: "All AAU card info on file, one row per wrestler",
|
|
93
96
|
recommended: true,
|
|
94
97
|
notes: "UI hides this tab when AAU collection is off (team setting)."
|
|
95
98
|
},
|
|
96
99
|
"AauExpiredReport" => {
|
|
97
|
-
args: %w[roster_id],
|
|
100
|
+
args: %w[roster_id location_id],
|
|
98
101
|
dates: :optional,
|
|
99
102
|
desc: "Wrestlers missing or with expired AAU memberships"
|
|
100
103
|
},
|
|
101
104
|
"AauExportReport" => {
|
|
102
|
-
args: %w[roster_id],
|
|
105
|
+
args: %w[roster_id location_id],
|
|
103
106
|
dates: :optional,
|
|
104
107
|
desc: "AAU bulk-purchase upload format"
|
|
105
108
|
},
|
|
106
109
|
|
|
107
110
|
# ── Stats tab ────────────────────────────────────────────────
|
|
108
111
|
"WinLossReport" => {
|
|
109
|
-
args: %w[roster_id],
|
|
112
|
+
args: %w[roster_id location_id],
|
|
110
113
|
dates: :required,
|
|
111
114
|
desc: "Wins and losses per wrestler",
|
|
112
115
|
recommended: true
|
|
113
116
|
},
|
|
114
117
|
"RosterStatsReport" => {
|
|
115
|
-
args: %w[roster_id],
|
|
118
|
+
args: %w[roster_id location_id],
|
|
116
119
|
dates: :required,
|
|
117
120
|
desc: "Wrestling stats per wrestler (takedowns, nearfall, etc.)",
|
|
118
121
|
recommended: true
|
|
@@ -133,8 +136,10 @@ module Wiq
|
|
|
133
136
|
recommended: true,
|
|
134
137
|
notes: "One row per wrestler over the date range — totals across all " \
|
|
135
138
|
"their check-ins in the window. If you want one row per " \
|
|
136
|
-
"check-in event use CheckInFeedReport.
|
|
137
|
-
"
|
|
139
|
+
"check-in event use CheckInFeedReport. Always team-wide: the " \
|
|
140
|
+
"model ignores roster_id AND location_id args. For a " \
|
|
141
|
+
"location-scoped attendance report use CheckInReport " \
|
|
142
|
+
"--location <id> instead.",
|
|
138
143
|
example: "wiq reports run CheckInSummaryReport --start 2026-05-01 --end 2026-05-31"
|
|
139
144
|
},
|
|
140
145
|
"CheckInFeedReport" => {
|
|
@@ -144,11 +149,12 @@ module Wiq
|
|
|
144
149
|
recommended: true,
|
|
145
150
|
notes: "Useful for \"who came to the club today and what registrations " \
|
|
146
151
|
"did they have at check-in time.\" Larger payload than the " \
|
|
147
|
-
"summary."
|
|
152
|
+
"summary. Always team-wide: the model ignores roster_id AND " \
|
|
153
|
+
"location_id args.",
|
|
148
154
|
example: "wiq reports run CheckInFeedReport --start 2026-05-01 --end 2026-05-31"
|
|
149
155
|
},
|
|
150
156
|
"PracticeAttendanceReport" => {
|
|
151
|
-
args: %w[roster_id],
|
|
157
|
+
args: %w[roster_id location_id],
|
|
152
158
|
dates: :required,
|
|
153
159
|
desc: "Practice-event attendance roll-up across a date range",
|
|
154
160
|
recommended: false,
|
|
@@ -159,7 +165,7 @@ module Wiq
|
|
|
159
165
|
"use CheckInSummaryReport or CheckInFeedReport instead."
|
|
160
166
|
},
|
|
161
167
|
"LastPracticeAttendedReport" => {
|
|
162
|
-
args: %w[roster_id],
|
|
168
|
+
args: %w[roster_id location_id],
|
|
163
169
|
dates: :optional,
|
|
164
170
|
desc: "Days since last practice attended, per wrestler",
|
|
165
171
|
recommended: true,
|
|
@@ -167,7 +173,7 @@ module Wiq
|
|
|
167
173
|
"practice in a while — particularly for seasonal clubs."
|
|
168
174
|
},
|
|
169
175
|
"ChurnRiskReport" => {
|
|
170
|
-
args: %w[roster_id days_threshold],
|
|
176
|
+
args: %w[roster_id location_id days_threshold],
|
|
171
177
|
dates: :optional,
|
|
172
178
|
desc: "Active recurring subscribers who haven't checked in within a window",
|
|
173
179
|
recommended: true,
|
|
@@ -178,7 +184,7 @@ module Wiq
|
|
|
178
184
|
example: "wiq reports run ChurnRiskReport --roster 0 --days-threshold 30"
|
|
179
185
|
},
|
|
180
186
|
"CheckInReport" => {
|
|
181
|
-
args: %w[roster_id],
|
|
187
|
+
args: %w[roster_id location_id],
|
|
182
188
|
dates: :required,
|
|
183
189
|
desc: "Extended attendance — one row per check-in INCLUDING Q&A responses",
|
|
184
190
|
notes: "UI labels this \"Attendance Extended (with questions).\" Same " \
|
|
@@ -333,6 +339,14 @@ module Wiq
|
|
|
333
339
|
Common args (only those documented in TYPES are honored
|
|
334
340
|
server-side):
|
|
335
341
|
--roster <id> roster_id (0 = all rosters)
|
|
342
|
+
--location <id> location_id — scope the wrestler set to
|
|
343
|
+
one location (wrestlers on ANY roster at
|
|
344
|
+
that location, deduped to one row each).
|
|
345
|
+
Precedence: a specific --roster <id> (> 0)
|
|
346
|
+
WINS over --location; pass --location
|
|
347
|
+
alone (or with --roster 0) to get
|
|
348
|
+
location scoping. Discover ids via
|
|
349
|
+
`wiq locations list`.
|
|
336
350
|
--paid-session <id> paid_session_id (0 = all, UsawExport only)
|
|
337
351
|
--event <id> event_id (EventStatsReport)
|
|
338
352
|
--fundraiser <id> fundraiser_id (Fundraiser* reports)
|
|
@@ -369,6 +383,9 @@ module Wiq
|
|
|
369
383
|
method_option :start, type: :string, desc: "YYYY-MM-DD"
|
|
370
384
|
method_option :end, type: :string, desc: "YYYY-MM-DD"
|
|
371
385
|
method_option :roster, type: :numeric, desc: "args.roster_id (0 = all rosters)"
|
|
386
|
+
method_option :location, type: :numeric,
|
|
387
|
+
desc: "args.location_id — scope wrestlers to one location " \
|
|
388
|
+
"(a specific --roster <id> > 0 takes precedence)"
|
|
372
389
|
method_option :paid_session, type: :numeric, desc: "args.paid_session_id"
|
|
373
390
|
method_option :event, type: :numeric, desc: "args.event_id"
|
|
374
391
|
method_option :fundraiser, type: :numeric, desc: "args.fundraiser_id"
|
|
@@ -529,6 +546,7 @@ module Wiq
|
|
|
529
546
|
|
|
530
547
|
args = {}
|
|
531
548
|
args["roster_id"] = options[:roster] if options[:roster] || options[:roster] == 0
|
|
549
|
+
args["location_id"] = options[:location] if options[:location]
|
|
532
550
|
args["paid_session_id"] = options[:paid_session] if options[:paid_session] || options[:paid_session] == 0
|
|
533
551
|
args["event_id"] = options[:event] if options[:event]
|
|
534
552
|
args["fundraiser_id"] = options[:fundraiser] if options[:fundraiser]
|
data/lib/wiq/commands/rosters.rb
CHANGED
|
@@ -17,12 +17,19 @@ module Wiq
|
|
|
17
17
|
exact name. Some teams tag rosters with
|
|
18
18
|
conventions like "2025-26".
|
|
19
19
|
|
|
20
|
+
--location <id> filters server-side (Ransack q[location_id_eq]) to
|
|
21
|
+
rosters stamped with that structured location. Discover ids via
|
|
22
|
+
`wiq locations list`. Rosters with no location are excluded when
|
|
23
|
+
the filter is on. Each roster row embeds its location object (or
|
|
24
|
+
null) so you can also group client-side without the filter.
|
|
25
|
+
|
|
20
26
|
Each roster row embeds roster_syncers and taggings, which is what
|
|
21
27
|
--season uses to filter without needing extra calls.
|
|
22
28
|
DESC
|
|
23
29
|
method_option :season, type: :numeric,
|
|
24
30
|
desc: "Filter to rosters whose syncers point at paid sessions overlapping this year"
|
|
25
31
|
method_option :season_tag, type: :string, desc: "Filter to rosters carrying this tag"
|
|
32
|
+
method_option :location, type: :numeric, desc: "Filter to rosters at one location id"
|
|
26
33
|
method_option :archived, type: :boolean, desc: "Show only archived (true) or active (false)"
|
|
27
34
|
method_option :all, type: :boolean, default: false
|
|
28
35
|
def list
|
|
@@ -30,6 +37,7 @@ module Wiq
|
|
|
30
37
|
unless options[:archived].nil?
|
|
31
38
|
params["q[archived_eq]"] = options[:archived]
|
|
32
39
|
end
|
|
40
|
+
params["q[location_id_eq]"] = options[:location] if options[:location]
|
|
33
41
|
|
|
34
42
|
records, total = fetch_index("/api/v1/rosters", params, key: "rosters")
|
|
35
43
|
|
|
@@ -20,6 +20,10 @@ module Wiq
|
|
|
20
20
|
--first-name q[first_name_cont]
|
|
21
21
|
--last-name q[last_name_cont]
|
|
22
22
|
--roster <id> q[rosters_id_eq] — filter to one roster
|
|
23
|
+
--location <id> location_id (dedicated param, not Ransack) —
|
|
24
|
+
wrestlers on ANY roster at that location.
|
|
25
|
+
Composes with the other filters. Discover
|
|
26
|
+
ids via `wiq locations list`.
|
|
23
27
|
--weight-class q[weight_class_numeric_eq] — exact numeric
|
|
24
28
|
--academic-class q[academic_class_eq] (senior, junior, …)
|
|
25
29
|
--age q[age_eq]
|
|
@@ -31,8 +35,14 @@ module Wiq
|
|
|
31
35
|
client-side.
|
|
32
36
|
|
|
33
37
|
--expand opts into a wider payload:
|
|
34
|
-
rosters
|
|
35
|
-
registration_answers
|
|
38
|
+
rosters Adds full roster details per wrestler
|
|
39
|
+
registration_answers Adds intake-form answers per wrestler
|
|
40
|
+
notification_preferences Adds wiq_app_installed + a
|
|
41
|
+
notification_preferences object
|
|
42
|
+
(email, sms, push, push_user_pref)
|
|
43
|
+
per wrestler. Coach PATs only — the
|
|
44
|
+
server silently omits it for
|
|
45
|
+
parent/wrestler tokens.
|
|
36
46
|
|
|
37
47
|
Base payload is already wider than most index endpoints (parents,
|
|
38
48
|
coach_guardians, profile_photos, basic roster refs all render by
|
|
@@ -43,6 +53,8 @@ module Wiq
|
|
|
43
53
|
method_option :first_name, type: :string, desc: "First name (contains)"
|
|
44
54
|
method_option :last_name, type: :string, desc: "Last name (contains)"
|
|
45
55
|
method_option :roster, type: :numeric, desc: "Filter to a single roster id"
|
|
56
|
+
method_option :location, type: :numeric,
|
|
57
|
+
desc: "Filter to wrestlers on any roster at this location id"
|
|
46
58
|
method_option :weight_class, type: :string,
|
|
47
59
|
desc: "Weight class (exact numeric, e.g. 132)"
|
|
48
60
|
method_option :academic_class, type: :string,
|
|
@@ -52,7 +64,7 @@ module Wiq
|
|
|
52
64
|
desc: "Default 'teammate' matches the WIQ web UI default; " \
|
|
53
65
|
"'all' removes the filter entirely"
|
|
54
66
|
method_option :expand, type: :string,
|
|
55
|
-
desc: "CSV: rosters, registration_answers"
|
|
67
|
+
desc: "CSV: rosters, registration_answers, notification_preferences"
|
|
56
68
|
method_option :per_page, type: :numeric, default: DEFAULT_PER_PAGE,
|
|
57
69
|
desc: "Page size (default 20; narrow surface)"
|
|
58
70
|
def list
|
|
@@ -78,15 +90,21 @@ module Wiq
|
|
|
78
90
|
rosters refs).
|
|
79
91
|
|
|
80
92
|
Pass --expand for additional sections:
|
|
81
|
-
rosters
|
|
82
|
-
registration_answers
|
|
93
|
+
rosters Full roster details with tags
|
|
94
|
+
registration_answers Intake-form answers
|
|
95
|
+
notification_preferences wiq_app_installed + notification_preferences
|
|
96
|
+
(email, sms, push, push_user_pref) —
|
|
97
|
+
"can this family be reached, and how?"
|
|
98
|
+
Coach PATs only; silently omitted for
|
|
99
|
+
parent/wrestler tokens.
|
|
83
100
|
DESC
|
|
84
101
|
method_option :expand, type: :string,
|
|
85
|
-
desc: "CSV: rosters, registration_answers"
|
|
102
|
+
desc: "CSV: rosters, registration_answers, notification_preferences"
|
|
86
103
|
def show(id)
|
|
87
104
|
params = {}
|
|
88
105
|
params["expand_rosters"] = true if expand_includes?("rosters")
|
|
89
106
|
params["expand_registration_answers"] = true if expand_includes?("registration_answers")
|
|
107
|
+
params["expand_notification_preferences"] = true if expand_includes?("notification_preferences")
|
|
90
108
|
wrestler = client.get("/api/v1/wrestlers/#{id}", params)
|
|
91
109
|
render(wrestler,
|
|
92
110
|
summary: "Wrestler #{wrestler["id"]} — #{wrestler["full_name"] || wrestler["display_name"]}",
|
|
@@ -107,12 +125,14 @@ module Wiq
|
|
|
107
125
|
params["q[first_name_cont]"] = options[:first_name] if options[:first_name]
|
|
108
126
|
params["q[last_name_cont]"] = options[:last_name] if options[:last_name]
|
|
109
127
|
params["q[rosters_id_eq]"] = options[:roster] if options[:roster]
|
|
128
|
+
params["location_id"] = options[:location] if options[:location]
|
|
110
129
|
params["q[weight_class_numeric_eq]"] = options[:weight_class] if options[:weight_class]
|
|
111
130
|
params["q[academic_class_eq]"] = options[:academic_class] if options[:academic_class]
|
|
112
131
|
params["q[age_eq]"] = options[:age] if options[:age]
|
|
113
132
|
params["q[profile_type_eq]"] = options[:profile_type] if profile_type_filter?
|
|
114
133
|
params["expand_rosters"] = true if expand_includes?("rosters")
|
|
115
134
|
params["expand_registration_answers"] = true if expand_includes?("registration_answers")
|
|
135
|
+
params["expand_notification_preferences"] = true if expand_includes?("notification_preferences")
|
|
116
136
|
params
|
|
117
137
|
end
|
|
118
138
|
|
|
@@ -130,6 +150,7 @@ module Wiq
|
|
|
130
150
|
bits = []
|
|
131
151
|
bits << "matching #{options[:query].inspect}" if options[:query]
|
|
132
152
|
bits << "in roster #{options[:roster]}" if options[:roster]
|
|
153
|
+
bits << "at location #{options[:location]}" if options[:location]
|
|
133
154
|
bits << "weight class #{options[:weight_class]}" if options[:weight_class]
|
|
134
155
|
bits << "profile_type=#{options[:profile_type]}" if profile_type_filter? && options[:profile_type] != "teammate"
|
|
135
156
|
bits.empty? ? "" : " (#{bits.join(", ")})"
|
data/lib/wiq/version.rb
CHANGED
data/lib/wiq.rb
CHANGED
|
@@ -20,11 +20,13 @@ require "wiq/commands/registrations"
|
|
|
20
20
|
require "wiq/commands/metrics"
|
|
21
21
|
require "wiq/commands/events"
|
|
22
22
|
require "wiq/commands/rosters"
|
|
23
|
+
require "wiq/commands/locations"
|
|
23
24
|
require "wiq/commands/prospects"
|
|
24
25
|
require "wiq/commands/prospect_families"
|
|
25
26
|
require "wiq/commands/workflows"
|
|
26
27
|
require "wiq/commands/setup"
|
|
27
28
|
require "wiq/commands/wrestlers"
|
|
29
|
+
require "wiq/commands/parents"
|
|
28
30
|
require "wiq/commands/charges"
|
|
29
31
|
require "wiq/commands/billing_profiles"
|
|
30
32
|
require "wiq/cli"
|
data/share/skills/wiq/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wiq
|
|
3
|
-
description: Use this skill when the user asks about their WrestlingIQ data — rosters, attendance, check-ins, paid sessions and registrations, the prospects/leads pipeline, financial metrics, reports, USAW/AAU memberships, fundraising,
|
|
3
|
+
description: Use this skill when the user asks about their WrestlingIQ data — rosters, attendance, check-ins, paid sessions and registrations, the prospects/leads pipeline, financial metrics, reports, USAW/AAU memberships, fundraising, online store orders, or per-location/site breakdowns for multi-gym clubs. The `wiq` CLI provides read-only access to /api/v1 via personal access tokens. Recognize phrasings like "how is our pipeline?", "who came to practice this week?", "show me the roster", "what's our MRR?", "which kids need USAW renewal?", "how is the Eastside gym doing?", or anything that maps to a wrestling club's admin workflows.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# WrestlingIQ CLI Skill
|
|
@@ -223,9 +223,14 @@ join date is in the default output.
|
|
|
223
223
|
resolves to paid_session ids whose date range overlaps the calendar
|
|
224
224
|
year, then filters client-side. There's no first-class Season entity
|
|
225
225
|
in WIQ.
|
|
226
|
-
-
|
|
227
|
-
|
|
228
|
-
|
|
226
|
+
- **Legacy free-text `Event.location` vs structured locations.** Old
|
|
227
|
+
events carry a free-text `location` string; the serialized `location`
|
|
228
|
+
field is the display form (structured Location name/address when
|
|
229
|
+
`location_id` is set, else the legacy text). `--location` filters only
|
|
230
|
+
match the structured `location_id` — events, rosters, and paid sessions
|
|
231
|
+
with NO location set are excluded from filtered listings, so an empty
|
|
232
|
+
filtered result doesn't mean "nothing happened", it may mean "nothing
|
|
233
|
+
is stamped with that location yet."
|
|
229
234
|
- **Index responses are wrapped.** Every `/api/v1` index returns
|
|
230
235
|
`{"<resource>": [...]}` — the CLI unwraps internally, but if you ever
|
|
231
236
|
hit the API directly remember to unwrap.
|
|
@@ -338,13 +343,77 @@ For exhaustive exports go through `wiq reports run RosterReport`
|
|
|
338
343
|
`profile_type=teammate` (matching the WIQ web UI default); pass
|
|
339
344
|
`--profile-type alumnus|guest|all` to widen.
|
|
340
345
|
|
|
346
|
+
For "can this family be reached, and how?" questions, add
|
|
347
|
+
`--expand notification_preferences` to `wrestlers list|show` or
|
|
348
|
+
`parents list|show`. Each row gains `wiq_app_installed` plus a
|
|
349
|
+
`notification_preferences` object (`email`, `sms`, `push`,
|
|
350
|
+
`push_user_pref`; `null` means no explicit preference recorded). Coach
|
|
351
|
+
PATs only — with a parent/wrestler token the server returns 200 with
|
|
352
|
+
the fields silently absent, so a missing field means "check the
|
|
353
|
+
token's profile type", not "API bug".
|
|
354
|
+
|
|
355
|
+
### Parents
|
|
356
|
+
|
|
357
|
+
`wiq parents list [--query <name>] [--first-name X] [--last-name Y]`
|
|
358
|
+
and `wiq parents show <id>` — slim payload (id, user_id, names) over
|
|
359
|
+
the team's teammate parents. There are no wrestler refs on a parent
|
|
360
|
+
row; to walk a family, start from the wrestler side
|
|
361
|
+
(`wiq wrestlers show <id>` embeds parent refs). Main uses: parent-first
|
|
362
|
+
ID discovery for `wiq billing_profiles show <id> --profile-type
|
|
363
|
+
ParentProfile`, and the notification-reachability expand above.
|
|
364
|
+
|
|
365
|
+
## Locations (multi-site clubs)
|
|
366
|
+
|
|
367
|
+
WIQ has a structured Location model (name + street address, per team).
|
|
368
|
+
Multi-gym clubs stamp rosters, events, and paid sessions with a
|
|
369
|
+
location; single-site clubs usually have none, and every `--location`
|
|
370
|
+
flag is simply irrelevant for them.
|
|
371
|
+
|
|
372
|
+
Discover ids first — this is the anchor for everything below:
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
wiq locations list # id, name, address, archived
|
|
376
|
+
wiq locations list --include-archived
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Then scope any of these surfaces:
|
|
380
|
+
|
|
381
|
+
| Command | Flag | Semantics |
|
|
382
|
+
| --- | --- | --- |
|
|
383
|
+
| `wiq rosters list` | `--location <id>` | Rosters stamped with that location (`q[location_id_eq]`) |
|
|
384
|
+
| `wiq events list` | `--location <id> [<id>...]` | Events at those locations (repeatable; unset-location events excluded) |
|
|
385
|
+
| `wiq paid_sessions list` | `--location <id>` | Sessions stamped with that location |
|
|
386
|
+
| `wiq wrestlers list` | `--location <id>` | Wrestlers on ANY roster at that location; composes with other filters |
|
|
387
|
+
| `wiq metrics show <name>` | `--location <id>` | Per-location finance metrics (the payment-dashboard filter) |
|
|
388
|
+
| `wiq reports run <Type>` | `--location <id>` | Scopes the wrestler set for the 13 location-aware report types |
|
|
389
|
+
|
|
390
|
+
Three gotchas an agent must know:
|
|
391
|
+
|
|
392
|
+
1. **Reports precedence:** a specific `--roster <id>` (> 0) WINS over
|
|
393
|
+
`--location` in report args. Pass `--location` alone (or with
|
|
394
|
+
`--roster 0`) to get location scoping. A wrestler on multiple rosters
|
|
395
|
+
at the location collapses to one row; RosterReport's "Added to roster
|
|
396
|
+
at" becomes their EARLIEST membership across that location's rosters.
|
|
397
|
+
`CheckInSummaryReport` / `CheckInFeedReport` ignore both args
|
|
398
|
+
entirely (always team-wide) — use `CheckInReport --location <id>` for
|
|
399
|
+
location-scoped attendance.
|
|
400
|
+
2. **Metrics fail silent, not loud:** an unknown or foreign `--location`
|
|
401
|
+
id on `wiq metrics show` silently falls back to ALL locations —
|
|
402
|
+
team-wide numbers, no error. Verify the id against
|
|
403
|
+
`wiq locations list` before quoting per-site revenue to the user.
|
|
404
|
+
3. **Nothing is auto-stamped retroactively.** Filters only match records
|
|
405
|
+
whose `location_id` is set. Empty filtered results on a club that
|
|
406
|
+
just adopted locations usually mean unstamped data, not zero
|
|
407
|
+
activity. Rosters/events/paid_sessions embed their `location` object
|
|
408
|
+
(or null) in list payloads, so you can check coverage cheaply.
|
|
409
|
+
|
|
341
410
|
## What's NOT available (yet)
|
|
342
411
|
|
|
343
412
|
The CLI is read-only by design except for report submission. You
|
|
344
413
|
cannot via this CLI:
|
|
345
414
|
|
|
346
415
|
- Create/edit prospects, families, notes, check-ins, events, paid
|
|
347
|
-
sessions, rosters, or any other resource
|
|
416
|
+
sessions, rosters, locations, or any other resource
|
|
348
417
|
- Mint, list, or revoke PATs (use the web UI at
|
|
349
418
|
`<host>/settings/personal_access_tokens`)
|
|
350
419
|
- Mark attendance, advance prospect stages, log contact notes
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wiq-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- WrestlingIQ
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: thor
|
|
@@ -90,8 +90,10 @@ files:
|
|
|
90
90
|
- lib/wiq/commands/check_ins.rb
|
|
91
91
|
- lib/wiq/commands/doctor.rb
|
|
92
92
|
- lib/wiq/commands/events.rb
|
|
93
|
+
- lib/wiq/commands/locations.rb
|
|
93
94
|
- lib/wiq/commands/metrics.rb
|
|
94
95
|
- lib/wiq/commands/paid_sessions.rb
|
|
96
|
+
- lib/wiq/commands/parents.rb
|
|
95
97
|
- lib/wiq/commands/prospect_families.rb
|
|
96
98
|
- lib/wiq/commands/prospects.rb
|
|
97
99
|
- lib/wiq/commands/registrations.rb
|