@zeyos/client 0.4.0 → 0.6.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +16 -1
  3. package/agents/README.md +8 -0
  4. package/agents/shared/zeyos-agent-operating-guide.md +54 -3
  5. package/agents/shared/zeyos-entity-reference.md +5 -4
  6. package/agents/shared/zeyos-query-patterns.md +40 -1
  7. package/agents/zeyos/SKILL.md +50 -7
  8. package/agents/zeyos-account-intelligence/references/workflows.md +9 -3
  9. package/agents/zeyos-billing-insights/SKILL.md +1 -1
  10. package/agents/zeyos-billing-insights/references/workflows.md +26 -3
  11. package/agents/zeyos-calendar-and-scheduling/SKILL.md +45 -0
  12. package/agents/zeyos-calendar-and-scheduling/references/workflows.md +49 -0
  13. package/agents/zeyos-collections-and-dunning/SKILL.md +3 -1
  14. package/agents/zeyos-collections-and-dunning/references/workflows.md +7 -4
  15. package/agents/zeyos-data-quality-and-governance/SKILL.md +62 -0
  16. package/agents/zeyos-data-quality-and-governance/references/workflows.md +59 -0
  17. package/agents/zeyos-document-and-approval/SKILL.md +41 -0
  18. package/agents/zeyos-document-and-approval/references/workflows.md +43 -0
  19. package/agents/zeyos-mail-operations/SKILL.md +21 -0
  20. package/agents/zeyos-mail-operations/references/workflows.md +20 -0
  21. package/agents/zeyos-procurement-and-supplier-performance/SKILL.md +36 -0
  22. package/agents/zeyos-procurement-and-supplier-performance/references/workflows.md +46 -0
  23. package/agents/zeyos-time-tracking/SKILL.md +2 -0
  24. package/agents/zeyos-time-tracking/references/workflows.md +68 -0
  25. package/agents/zeyos-work-management/SKILL.md +4 -3
  26. package/agents/zeyos-work-management/references/workflows.md +63 -2
  27. package/docs/03-cli/02-commands.md +54 -2
  28. package/docs/03-cli/03-configuration.md +1 -0
  29. package/okf/concepts/calendar-timezones.md +10 -0
  30. package/okf/concepts/confirmation-and-side-effects.md +14 -0
  31. package/okf/concepts/currency-and-rounding.md +10 -0
  32. package/okf/concepts/idempotency-and-deduplication.md +10 -0
  33. package/okf/concepts/index.md +8 -0
  34. package/okf/concepts/null-empty-missing.md +10 -0
  35. package/okf/concepts/official-versus-latest.md +10 -0
  36. package/okf/concepts/ownership-versus-attention.md +15 -0
  37. package/okf/concepts/untrusted-business-content.md +10 -0
  38. package/okf/metrics/account-address-completeness.md +10 -0
  39. package/okf/metrics/index.md +3 -0
  40. package/okf/metrics/stock-movement-by-storage.md +10 -0
  41. package/okf/metrics/supplier-delivery-performance.md +10 -0
  42. package/okf/playbooks/activity-timeline.md +11 -0
  43. package/okf/playbooks/calendar-availability.md +11 -0
  44. package/okf/playbooks/campaign-recipient-coverage.md +12 -0
  45. package/okf/playbooks/document-approval.md +10 -0
  46. package/okf/playbooks/duplicate-account-review.md +11 -0
  47. package/okf/playbooks/effective-customer-price.md +11 -0
  48. package/okf/playbooks/index.md +8 -0
  49. package/okf/playbooks/missing-billing-addresses.md +12 -0
  50. package/okf/playbooks/supplier-scorecard.md +10 -0
  51. package/package.json +4 -2
  52. package/scripts/data/okf-curation.mjs +188 -0
  53. package/scripts/lib/live-test-config.mjs +20 -0
@@ -26,7 +26,9 @@ Typical prompts:
26
26
  3. Use the correct primary resource:
27
27
  - `transactions` for invoice and credit obligations
28
28
  - `payments` for cash actually received
29
- - `dunning` and `dunning2transactions` for reminder and notice state (operationIds: `listDunningNotices`, `listDunningToTransactions` — these dbref nouns do not map naively; see [../shared/zeyos-entity-reference.md](../shared/zeyos-entity-reference.md#entity-noun-to-rest-operationid))
29
+ - `dunning` and `dunning2transactions` for reminder and notice state. In the CLI, use
30
+ `zeyos count/list dunning` and `zeyos list dunning2transactions`; in `@zeyos/client`,
31
+ call `listDunningNotices` and `listDunningToTransactions`.
30
32
  4. State what "overdue" means in the answer:
31
33
  - past `duedate`
32
34
  - unpaid or not fully settled
@@ -2,8 +2,9 @@
2
2
 
3
3
  ## Primary Resources
4
4
 
5
- The DB-table noun is not the operationId. Use the operationIds below with `@zeyos/client`
6
- (`client.api.<operationId>(...)`); see [../../shared/zeyos-entity-reference.md](../../shared/zeyos-entity-reference.md#entity-noun-to-rest-operationid) for the full mapping.
5
+ The DB-table noun is not always the operationId. Use the CLI resource names below for
6
+ `zeyos count/list ...`; use the operationIds below with `@zeyos/client`
7
+ (`client.api.<operationId>(...)`). See [../../shared/zeyos-entity-reference.md](../../shared/zeyos-entity-reference.md#entity-noun-to-rest-operationid) for the full mapping.
7
8
 
8
9
  | Concept | dbref noun | list operationId | single-record operationIds |
9
10
  |---------|------------|------------------|----------------------------|
@@ -14,8 +15,10 @@ The DB-table noun is not the operationId. Use the operationIds below with `@zeyo
14
15
  | Dunning-to-transaction links | `dunning2transactions` | `listDunningToTransactions` | `getDunningToTransaction`, `createDunningToTransaction`, `deleteDunningToTransaction` |
15
16
 
16
17
  Note: `dunning` is the **`DunningNotice`** entity (not `Dunning`), and `dunning2transactions`
17
- is the `DunningToTransaction` junction. Calling `client.api.listDunning(...)` or
18
- `client.api.listDunning2transactions(...)` will fail with "operation not found".
18
+ is the `DunningToTransaction` junction. The CLI maps those nouns directly, so
19
+ `zeyos count dunning` is the shortest path for a notice count. Calling
20
+ `client.api.listDunning(...)` or `client.api.listDunning2transactions(...)` will fail with
21
+ "operation not found"; use the operationIds in the table when writing JavaScript.
19
22
 
20
23
  ## Important Status Caution
21
24
 
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: zeyos-data-quality-and-governance
3
+ description: Detect duplicate accounts, completeness gaps, stale data and schema hygiene issues in ZeyOS, and produce safe, explainable remediation previews. Use for "find duplicate customer accounts", "which customers are missing billing addresses", "show stale contacts with no email", "clean up duplicate records", "which custom fields are unused". Read-only by default — detection is separate from remediation, which requires a human decision per record.
4
+ ---
5
+
6
+ # ZeyOS Data Quality and Governance
7
+
8
+ Read [../shared/zeyos-agent-operating-guide.md](../shared/zeyos-agent-operating-guide.md) and [../shared/zeyos-query-patterns.md](../shared/zeyos-query-patterns.md) first. See the OKF `playbooks/duplicate-account-review` playbook and `concepts/null-empty-missing` concept.
9
+ Read [references/workflows.md](references/workflows.md) for concrete duplicate, anti-join,
10
+ and remediation-preview query plans.
11
+
12
+ > **Detection is not remediation.** Find and explain; never bulk-merge, archive or delete
13
+ > from a fuzzy match. Each fix is a human decision on a named ID.
14
+
15
+ Primary entities (cross-domain): `accounts`, `contacts`, `addresses`, `users`, `customfields`, `objects`, plus the domain records the user names.
16
+
17
+ Typical prompts:
18
+
19
+ - "Find duplicate customer accounts."
20
+ - "Which customers are missing billing addresses?"
21
+ - "Show stale contacts with no email."
22
+ - "Clean up duplicate records." (→ becomes a preview, not an action)
23
+ - "Which custom fields are unused or inconsistent?"
24
+
25
+ ## Workflow
26
+
27
+ 1. Define the population and the active/archived scope (R-012).
28
+ 2. Normalize comparison fields (lowercase, trim) **without losing the original values** (R-020).
29
+ 3. Generate candidate pairs from deterministic evidence and **score + explain** each:
30
+ - exact customer number → strong
31
+ - exact normalized email (incl. via `contacts`) → strong
32
+ - exact normalized name/address → strong
33
+ - near/fuzzy name only → weak (low confidence)
34
+ 4. Sort candidates by score descending; label confidence high/medium/low.
35
+ 5. Keep detection separate from remediation. For a "clean up" request, return a bounded
36
+ preview: exact IDs + proposed per-ID action, and request a human decision (R-009, R-023).
37
+ 6. Re-query after any approved, bounded remediation.
38
+
39
+ ## Fast Path: Missing Billing Addresses
40
+
41
+ For "active customers whose name starts with X and missing a billing address", use
42
+ `accounts.lastname` with the ZeyOS pattern operator. Do **not** invent `name`,
43
+ `name_starts`, or `lastname_starts` filters.
44
+ If the prompt already states that billing is address type `1`, shipping is type `0`, and
45
+ `addresses` has no visibility column, skip schema discovery and run the two list queries.
46
+ Batch address lookup with `account:[ids]`; do not loop one account at a time.
47
+
48
+ ```bash
49
+ zeyos list accounts --filter '{"type":1,"visibility":0,"lastname":{"~~*":"<prefix>%"}}' --fields ID,lastname --limit 1000 --json
50
+ zeyos list addresses --filter '{"account":[<accountIds>],"type":[0,1]}' --fields ID,account,type --limit 1000 --json
51
+ ```
52
+
53
+ `addresses` has no `visibility` column. Keep accounts with no type `1` address; derive
54
+ `has_shipping` from presence of a type `0` address.
55
+
56
+ ## Safety
57
+
58
+ - Read-only by default.
59
+ - No automated merge until ZeyOS exposes a documented, reversible merge operation.
60
+ - Never bulk archive/delete from fuzzy matching (R-009, R-011).
61
+ - Never treat a shared generic email domain or similar name as conclusive.
62
+ - Preserve source IDs and explain confidence (R-020).
@@ -0,0 +1,59 @@
1
+ # Data Quality and Governance Workflows
2
+
3
+ ## Duplicate-account detection (deterministic scoring)
4
+
5
+ 1. Pull the active population:
6
+
7
+ ```bash
8
+ zeyos list accounts --filter '{"type":1,"visibility":0}' \
9
+ --fields ID,customernum,lastname,firstname --limit 10000 --json
10
+ ```
11
+
12
+ 2. Normalize comparison keys (lowercase, trim, collapse whitespace) but keep originals.
13
+ 3. Score candidate pairs with a published, deterministic policy:
14
+
15
+ | Evidence | Weight | Confidence |
16
+ |---|---|---|
17
+ | Exact `customernum` | 1.0 | high |
18
+ | Exact normalized email (via `contacts`) | 0.9 | high |
19
+ | Exact normalized name + address | 0.8 | high |
20
+ | Near name only (edit distance) | 0.4 | low |
21
+
22
+ 4. Sort pairs by score descending; emit `{accountA, accountB, score, reasons, confidence}`.
23
+ A clearly different account is not a candidate. Shared generic email domains and similar
24
+ names are never conclusive on their own.
25
+
26
+ ## Completeness gaps (anti-join)
27
+
28
+ "Customers missing a billing address": list customers, list `addresses` of `type: 1`
29
+ (billing), keep customers with no matching `account`. `addresses` has **no** `visibility`
30
+ column — do not filter it. For scoped/prefix tasks, batch address lookup with
31
+ `account:[ids]`; do not loop one account at a time. Use:
32
+
33
+ ```bash
34
+ zeyos list accounts --filter '{"type":1,"visibility":0,"lastname":{"~~*":"<prefix>%"}}' --fields ID,lastname --limit 1000 --json
35
+ zeyos list addresses --filter '{"account":[<accountIds>],"type":[0,1]}' --fields ID,account,type --limit 1000 --json
36
+ ```
37
+
38
+ State whether you treat empty string, null and missing the same (R-020); by default they
39
+ are distinct.
40
+
41
+ ## Remediation is a preview, not an action
42
+
43
+ A "clean up duplicates" request returns:
44
+
45
+ ```json
46
+ { "executed": false,
47
+ "candidates": [ { "accountA": 1, "accountB": 2 } ],
48
+ "proposedActions": [ { "accountId": 2, "action": "archive (needs human review)" } ] }
49
+ ```
50
+
51
+ Never delete, archive or merge from this analysis (R-009, R-011, R-023). Each action is a
52
+ human decision on an exact ID. Re-query only after an approved, bounded change.
53
+
54
+ ## Common failure modes
55
+
56
+ - Treating a fuzzy name match as a confirmed duplicate.
57
+ - Collapsing null / empty / zero into one bucket without saying so.
58
+ - Executing a bulk archive/delete from a "clean up" instruction.
59
+ - Filtering `visibility` on `addresses` (no such column → HTTP 400).
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: zeyos-document-and-approval
3
+ description: Find formal ZeyOS documents, judge draft/final/obsolete status, run approval and finalization gates, and compare notes against formal SOPs. Use when status transitions, approval authority, version choice or formal file artifacts matter ("find the current approved onboarding SOP", "which contracts await approval", "make document 812 final", "compare the draft with the final version"). For lightweight note retrieval use zeyos-notes-and-sops.
4
+ ---
5
+
6
+ # ZeyOS Document and Approval
7
+
8
+ Read [../shared/zeyos-agent-operating-guide.md](../shared/zeyos-agent-operating-guide.md) and [../shared/zeyos-query-patterns.md](../shared/zeyos-query-patterns.md) first. See the OKF `concepts/official-versus-latest` concept and the `playbooks/document-approval` playbook.
9
+
10
+ > **Status, not freshness, decides authority.** The newest artifact is not necessarily the
11
+ > current official one. Retrieve content before claiming what a document says.
12
+
13
+ Primary entities: `documents` (`listDocuments`, `getDocument`, `createDocument`, `updateDocument`), `notes` (`listNotes`, `getNote`), `binfiles` (`listBinFiles`), `files`, plus `users`/`groups` for approval context. Document `status`: 0 DRAFT, 1 FEEDBACKREQUIRED, 2 INREVISION, 3 AWAITINGAPPROVAL, 4 FINAL, 5 OBSOLETE.
14
+
15
+ Typical prompts:
16
+
17
+ - "Find the current approved onboarding SOP."
18
+ - "Which contracts are awaiting approval?"
19
+ - "Compare the draft with the final version."
20
+ - "Make document 812 final."
21
+
22
+ ## Workflow
23
+
24
+ 1. Search formal `documents` first for "official", "approved", "final" or "current".
25
+ 2. Select by **status plus freshness** — a FINAL document outranks a newer OBSOLETE one and a draft note (R-018).
26
+ 3. Retrieve binary/file content (`binfile`/`files`) before claiming its contents.
27
+ 4. Keep `notes` (lightweight) and `documents` (formal) as separate sources; do not silently merge conflicting sources (surface the conflict, name the authoritative source).
28
+ 5. For finalization/approval: fetch the exact ID + current status, show a preview and any naming conflict, require exact confirmation, `updateDocument` one ID, then re-read and report old/new status (R-005, R-006, R-011).
29
+
30
+ ## Routing boundaries
31
+
32
+ - Note-centric retrieval/summarization → `zeyos-notes-and-sops`.
33
+ - Use this skill when status transitions, approval authority, version choice or formal files matter.
34
+
35
+ ## Safety
36
+
37
+ - FINAL, APPROVED, OBSOLETE and cancellation transitions are high impact (R-011).
38
+ - Never approve on behalf of an unidentified user/group.
39
+ - Never bulk-finalize or bulk-obsolete documents (R-009).
40
+ - Never overwrite a binary file without an explicit target and confirmation.
41
+ - Preserve provenance and revision identifiers in summaries.
@@ -0,0 +1,43 @@
1
+ # Document and Approval Workflows
2
+
3
+ ## Select the current official artifact
4
+
5
+ 1. Query formal `documents` matching the topic; read `status`, `name`, `documentnum`, `filename`.
6
+
7
+ ```bash
8
+ zeyos list documents --filter '{"visibility":0,"name":{"~~*":"onboarding%"}}' \
9
+ --fields ID,name,status,filename,documentnum --limit 100 --json
10
+ ```
11
+
12
+ 2. Rank by authority, not date: prefer `status = 4` (FINAL). A newer `status = 5`
13
+ (OBSOLETE) does **not** win, and a draft `note` is only fallback context (R-018).
14
+ 3. State the selection reason (which status, why it outranks the alternatives).
15
+
16
+ ## Compare a note against a formal SOP
17
+
18
+ Retrieve the actual text of both (`notes.text`; `documents.description`/binary content).
19
+ Report agreements and conflicts explicitly; never synthesize a single answer that hides a
20
+ contradiction. The formal FINAL document is the authoritative source.
21
+
22
+ ## Finalization / approval gate
23
+
24
+ ```bash
25
+ # 1. Fetch exact target + current status
26
+ zeyos get document <id> --json
27
+ # 2. Preview the change (no write) and surface any same-name conflict
28
+ zeyos update document <id> --query --status 4
29
+ # 3. Only after explicit confirmation of the exact ID:
30
+ zeyos update document <id> --status 4
31
+ # 4. Re-read and report old/new status
32
+ zeyos get document <id> --fields ID,name,status --json
33
+ ```
34
+
35
+ Update exactly one ID. Never fuzzy-match a name to a set of documents and finalize them in
36
+ bulk (R-009, R-011). Leave similarly-named documents untouched.
37
+
38
+ ## Common failure modes
39
+
40
+ - Picking the newest artifact instead of the FINAL one.
41
+ - Claiming document contents without retrieving the file/binary.
42
+ - Bulk-finalizing by fuzzy name match.
43
+ - Approving without identifying the approver / authority.
@@ -28,6 +28,27 @@ Typical prompts:
28
28
  7. Treat textual drafts as safe. Treat message record creation/update as a write and sending or marking `mailbox=2` as high risk; require explicit confirmation plus verified sender context before any real mail mutation.
29
29
  8. Escalate to `@zeyos/client` when you need binary content, MIME expansion, or richer message correlation than the CLI can express cleanly.
30
30
 
31
+ ## Fast Path: Unanswered Inbox Count On Open Tickets
32
+
33
+ For "how many inbox messages (`mailbox` 0) are linked to open tickets and still
34
+ unanswered", use the workflow directly. This is a joined count, not a simple
35
+ `zeyos count messages` task.
36
+
37
+ If the prompt already states mailbox values and closed ticket statuses, skip schema
38
+ discovery. Do **not** use `notin`, `status_neq`, or `messageid` for this count.
39
+ Use the CLI commands below directly; do not write a scratch JavaScript client script for
40
+ this count because the CLI normalizes array filters to the API's native `IN` operator.
41
+
42
+ ```bash
43
+ zeyos list tickets --fields ID,status --filter '{"visibility":0,"status":[0,1,2,3,4,5,6,7,11]}' --limit 10000 --json
44
+ zeyos list messages --fields ID,ticket,reference,date --filter '{"mailbox":0,"ticket":[<ticketIds>]}' --limit 10000 --json
45
+ zeyos list messages --fields ID,ticket,reference,date --filter '{"mailbox":2,"ticket":[<ticketIds>]}' --limit 10000 --json
46
+ ```
47
+
48
+ Count inbound rows where no sent row has the same `ticket`, `reference == inbound.ID`,
49
+ and `sent.date >= inbound.date`. Do not run a separate raw inbox count after listing the
50
+ rows; the join logic is the answer.
51
+
31
52
  ## Safety
32
53
 
33
54
  - Never send email from an agent test or from a summary/draft request.
@@ -98,6 +98,26 @@ Recommended approach:
98
98
 
99
99
  For an operational count, use this exact definition unless the user specifies another one: inbox message (`mailbox = 0`) linked to an open ticket, with no later sent message (`mailbox = 2`) whose `reference` points back to that inbound message.
100
100
 
101
+ Fast path for "how many inbox messages on open tickets are still unanswered":
102
+
103
+ 1. If the prompt already states mailbox values and closed ticket statuses, skip schema
104
+ discovery. Do not use `notin`; open tickets are status `IN [0,1,2,3,4,5,6,7,11]`
105
+ with `visibility:0`.
106
+ Use the CLI commands below directly; do not write a scratch JavaScript client script
107
+ for this count because the CLI normalizes array filters to the API's native `IN`
108
+ operator.
109
+ 2. Query open ticket IDs once:
110
+ `zeyos list tickets --fields ID,status --filter '{"visibility":0,"status":[0,1,2,3,4,5,6,7,11]}' --limit 10000 --json`
111
+ 3. Query inbox and sent messages for those ticket IDs using batched `ticket:[ids]`:
112
+ `zeyos list messages --fields ID,ticket,reference,date --filter '{"mailbox":0,"ticket":[<ticketIds>]}' --limit 10000 --json`
113
+ `zeyos list messages --fields ID,ticket,reference,date --filter '{"mailbox":2,"ticket":[<ticketIds>]}' --limit 10000 --json`
114
+ 4. Count inbound rows where no sent row has the same `ticket`, `reference == inbound.ID`,
115
+ and `sent.date >= inbound.date`. Do not run a separate `zeyos count messages` after
116
+ listing the rows; the join logic, not the raw inbox count, is the answer.
117
+
118
+ For this operational count, do not select `messageid`; `ID`, `ticket`, `reference`, and
119
+ `date` are sufficient, and some instances reject `messageid` in list field selection.
120
+
101
121
  ## Pattern: Draft A Reply
102
122
 
103
123
  Use this for prompts like:
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: zeyos-procurement-and-supplier-performance
3
+ description: Compare suppliers, analyze procurement orders/deliveries/invoices, lead times, price variance and reorder worklists in ZeyOS. Use for "which supplier is best for 20 units of item ABC", "which purchase orders are late", "compare supplier delivery performance", "where did procurement prices exceed the order". Read-only analysis — never place, book, cancel or transmit a procurement transaction.
4
+ ---
5
+
6
+ # ZeyOS Procurement and Supplier Performance
7
+
8
+ Read [../shared/zeyos-agent-operating-guide.md](../shared/zeyos-agent-operating-guide.md) and [../shared/zeyos-query-patterns.md](../shared/zeyos-query-patterns.md) first. See the OKF `playbooks/supplier-scorecard` playbook and `metrics/supplier-delivery-performance` metric.
9
+
10
+ > **Keep ordered, delivered and invoiced quantities/values distinct**, and state the
11
+ > ranking policy before you rank. Analysis is read-only.
12
+
13
+ Primary entities: `transactions` types **5 PROCUREMENT_REQUEST, 6 ORDER, 7 DELIVERY, 8 INVOICE, 9 CREDIT** (`listTransactions`), `suppliers` (item↔supplier links: `listSuppliers`), `items`, `accounts` (the supplier), `stocktransactions`, `storages`, `payments`, and `contracts` where supplier agreements matter. `transactions` has **no** `visibility` column.
14
+
15
+ Typical prompts:
16
+
17
+ - "Which supplier is best for 20 units of item ABC?"
18
+ - "Which purchase orders are late?"
19
+ - "Compare supplier delivery performance."
20
+ - "Where did procurement prices exceed the order?"
21
+
22
+ ## Workflow
23
+
24
+ 1. Resolve the item and the supplier `accounts` (type 2 SUPPLIER).
25
+ 2. For sourcing decisions, read `suppliers` links: `price`, `minamount`, `deliverytime`, `stock`.
26
+ 3. Apply the order quantity: a supplier is eligible only if `minamount <= quantity` — never hide a minimum-quantity constraint (R-003).
27
+ 4. State the ranking policy first (e.g. eligible, then price ascending, then delivery time), then rank.
28
+ 5. For performance, keep ordered (type 6), delivered (type 7) and invoiced (type 8) values separate; compute price variance as invoiced − ordered.
29
+ 6. Compute only over a declared time window and one currency (R-014, R-019). Exclude cancelled records by documented status policy.
30
+ 7. Label reorder advice as heuristic unless a formal stock policy exists.
31
+
32
+ ## Safety
33
+
34
+ - Never place, approve, book, cancel or transmit a procurement transaction automatically (R-010, R-011).
35
+ - Never change supplier master data or price lists from an analytical request.
36
+ - Any future write workflow requires exact supplier, item, quantity, currency and confirmation.
@@ -0,0 +1,46 @@
1
+ # Procurement and Supplier Performance Workflows
2
+
3
+ ## Procurement transaction types
4
+
5
+ `transactions.type`: 5 PROCUREMENT_REQUEST, 6 PROCUREMENT_ORDER, 7 PROCUREMENT_DELIVERY,
6
+ 8 PROCUREMENT_INVOICE, 9 PROCUREMENT_CREDIT. (Billing types 0–4 are a different domain.)
7
+ `transactions` has **no** `visibility` column — do not add `"visibility":0`.
8
+
9
+ ## Supplier comparison for an order quantity
10
+
11
+ 1. Resolve the item, then read its supplier links:
12
+
13
+ ```bash
14
+ zeyos list suppliers --filter '{"item":<itemId>}' \
15
+ --fields ID,account,price,minamount,deliverytime,stock --limit 1000 --json
16
+ ```
17
+
18
+ 2. Resolve each `account` to a supplier name.
19
+ 3. Eligibility: `minamount <= orderQuantity`. State eligibility explicitly — never drop a
20
+ supplier silently because of its minimum.
21
+ 4. Ranking policy (declare before ranking): eligible first, then `price` ascending, then
22
+ `deliverytime` ascending. Report `rank` and `eligible` per option.
23
+
24
+ ## Supplier scorecard over a period
25
+
26
+ Group procurement `transactions` by supplier `account` within a declared window + currency:
27
+
28
+ - `ordered_value` = Σ `netamount` where type 6
29
+ - `invoiced_value` = Σ `netamount` where type 8
30
+ - `price_variance` = invoiced_value − ordered_value
31
+ - delivery timing from type 7 dates vs the order `duedate` (on-time when delivered by duedate)
32
+
33
+ Keep all values in one currency (R-019); exclude cancelled records per documented policy.
34
+
35
+ ## Reorder advice
36
+
37
+ Label reorder suggestions as heuristic unless the instance has a formal stock policy
38
+ (min/max levels). Combine `stocktransactions` net-booked stock by `storages` with supplier
39
+ `deliverytime` to flag at-risk items — but present it as advice, not an action.
40
+
41
+ ## Common failure modes
42
+
43
+ - Mixing billing types (0–4) with procurement types (5–9).
44
+ - Hiding a minimum-order-quantity constraint when ranking.
45
+ - Conflating ordered, delivered and invoiced values.
46
+ - Summing across currencies.
@@ -15,6 +15,7 @@ Typical prompts:
15
15
  - "Record 2 hours on ticket 812." / "Book 90 minutes against Project Atlas."
16
16
  - "Log half an hour for ACME, I was on a call about the renewal."
17
17
  - "How much time did I log this week?" / "Summarize my logged hours by account."
18
+ - "Give me a summary of logged ticket time from the last four weeks."
18
19
  - "Actually make that 90 minutes, not 60." / "Move that time to ticket 813."
19
20
 
20
21
  ## Two jobs
@@ -44,5 +45,6 @@ Interactivity here means **act first, then ask only when real data is ambiguous*
44
45
  ## Output discipline
45
46
 
46
47
  - For "my work": state the resolved user and the open-status definition you used, then the list.
48
+ - For ticket time summaries: include both actionsteps directly linked by `actionstep.ticket` and actionsteps linked by `actionstep.task` where `task.ticket` is the summarized ticket; dedupe by actionstep ID before summing.
47
49
  - For a logged entry: report the created actionstep id, the attached record (ticket/task/account), the effort in minutes, and the date.
48
50
  - Separate what you resolved from what you assumed; call out any account or work-item ambiguity you had to break.
@@ -17,6 +17,7 @@ See [../../shared/zeyos-entity-reference.md](../../shared/zeyos-entity-reference
17
17
 
18
18
  - **Time entries are `actionsteps`.** The `effort` field is **minutes** (integer). There is no separate time-entry resource.
19
19
  - **Actionstep foreign keys:** `task`, `ticket`, `account`, `transaction` — plus `assigneduser`, `date`, `duedate`, `status`, `effort`. **There is no `project` FK on an actionstep**, so project-level time attaches through a ticket/task in that project, or to the project's `account`.
20
+ - **Ticket time rollups cross the task layer.** For "logged ticket time", count actionsteps with `ticket = <ticketId>` and actionsteps with `task = <taskId>` where that task has `ticket = <ticketId>`. Always dedupe by actionstep `ID` before summing in case a row carries both FKs.
20
21
  - **Actionstep status:** `0` DRAFT · `1` COMPLETED · `2` CANCELLED · `3` BOOKED. Log already-done work as **COMPLETED (1)**; use **BOOKED (3)** only when the instance treats booked effort as the billed/locked record and the user says so.
21
22
  - **Ticket/task status:** `0` NOT_STARTED · `1` AWAITING_ACCEPTANCE · `2` ACCEPTED · `3` REJECTED · `4` ACTIVE · `5` INACTIVE · `6` FEEDBACK_REQUIRED · `7` TESTING · `8` CANCELLED · `9` COMPLETED · `10` FAILED · `11` BOOKED. Treat **open/current = status `!IN [8,9,10,11]`** (exclude cancelled/completed/failed/booked). State this definition in the answer; the user can narrow it (e.g. only `4` ACTIVE).
22
23
  - **Foreign keys per resource:** `tickets` carry `account`, `project`, `assigneduser`; `tasks` carry `ticket`, `project`, `assigneduser` (no `account` — reach the account through the task's ticket/project); `projects` carry `account`, `assigneduser`; `accounts` carry `lastname`, `firstname`, `customernum` (no `name` field).
@@ -195,6 +196,72 @@ const totalMinutes = rows.reduce((sum, r) => sum + (Number(r.effort) || 0), 0);
195
196
 
196
197
  4. Sum `effort` as minutes; convert to hours only if asked. To break down "by account/ticket", group the rows by the FK and resolve the account/ticket names with a second query. Report the total and the breakdown, and state the window and status set you used.
197
198
 
199
+ ## Pattern: "Give me a summary of logged ticket time from the last four weeks"
200
+
201
+ Use this whenever the user asks for ticket time, ticket hours, support time by ticket, or a ticket-level timesheet summary. A time entry can be attached directly to a ticket or indirectly to a task that belongs to a ticket; both are ticket time.
202
+
203
+ 1. Resolve the date window to Unix seconds and count only `actionsteps.status IN [1,3]` unless the user explicitly asks for drafts/open follow-ups.
204
+ 2. If the user named a specific ticket, resolve it first, then query:
205
+
206
+ ```bash
207
+ # direct ticket time
208
+ zeyos list actionsteps \
209
+ --fields ID,name,effort,date,status,ticket,task,assigneduser \
210
+ --filter '{"ticket":<ticketId>,"status":{"IN":[1,3]},"date":{">=":<start>,"<=":<end>}}' \
211
+ --limit 10000 --json
212
+
213
+ # tasks that belong to the ticket; do not filter task status for historical time
214
+ zeyos list tasks \
215
+ --fields ID,tasknum,name,ticket \
216
+ --filter '{"ticket":<ticketId>}' \
217
+ --limit 10000 --json
218
+
219
+ # time logged on those tasks
220
+ zeyos list actionsteps \
221
+ --fields ID,name,effort,date,status,ticket,task,assigneduser \
222
+ --filter '{"task":{"IN":[<taskIds>]},"status":{"IN":[1,3]},"date":{">=":<start>,"<=":<end>}}' \
223
+ --limit 10000 --json
224
+ ```
225
+
226
+ 3. If the user asks for all ticket time in a period, list actionsteps in the window with `ticket` and `task`, then resolve every referenced task to `task.ticket` and group by the resulting ticket ID. Page through results if the window is large.
227
+ 4. Build each actionstep's ticket attribution:
228
+ - direct row: `actionstep.ticket`
229
+ - task row: `taskById[actionstep.task].ticket`
230
+ - both present: count the actionstep once; prefer the direct ticket if it conflicts and call out the conflict
231
+ - neither present: keep it out of the ticket-time total unless reporting an "unassigned/general time" bucket
232
+ 5. Dedupe by actionstep `ID`, sum `effort` in minutes, and resolve ticket labels (`ticketnum`, `name`, `account.lastname`) for the summary.
233
+
234
+ Client shape for a specific ticket:
235
+
236
+ ```js
237
+ const [directRows, taskRows] = await Promise.all([
238
+ client.api.listActionSteps({
239
+ fields: ['ID', 'name', 'effort', 'date', 'status', 'ticket', 'task', 'assigneduser'],
240
+ filters: { ticket: ticketId, status: { IN: [1, 3] }, date: { '>=': start, '<=': end } },
241
+ limit: 10000,
242
+ }),
243
+ client.api.listTasks({
244
+ fields: ['ID', 'tasknum', 'name', 'ticket'],
245
+ filters: { ticket: ticketId },
246
+ limit: 10000,
247
+ }),
248
+ ]);
249
+
250
+ const taskIds = taskRows.map((task) => task.ID);
251
+ const taskTimeRows = taskIds.length
252
+ ? await client.api.listActionSteps({
253
+ fields: ['ID', 'name', 'effort', 'date', 'status', 'ticket', 'task', 'assigneduser'],
254
+ filters: { task: { IN: taskIds }, status: { IN: [1, 3] }, date: { '>=': start, '<=': end } },
255
+ limit: 10000,
256
+ })
257
+ : [];
258
+
259
+ const rowsById = new Map();
260
+ for (const row of [...directRows, ...taskTimeRows]) rowsById.set(String(row.ID), row);
261
+ const totalMinutes = [...rowsById.values()]
262
+ .reduce((sum, row) => sum + (Number(row.effort) || 0), 0);
263
+ ```
264
+
198
265
  ## Pattern: Adjust or correct a logged entry
199
266
 
200
267
  For "actually that was 90 minutes, not 60" or "move that time to ticket 813" right after logging — or any later correction.
@@ -226,5 +293,6 @@ Only correct entries the user pointed to (by id, or one you just created in this
226
293
  - Filtering `visibility` on `transactions` (no such column -> HTTP 400).
227
294
  - Putting effort in hours — `effort` is **minutes**.
228
295
  - Trying to set a `project` FK on an actionstep — it does not exist; anchor on a ticket/task/account instead.
296
+ - Summing only `actionstep.ticket` for ticket time — time logged on tasks with `task.ticket = <ticketId>` belongs in the ticket total too.
229
297
  - Treating BOOKED (status 11) tickets as current — they are terminal; the open set excludes `[8,9,10,11]`.
230
298
  - Logging time against a user id you guessed — always read `sub` from `whoami`.
@@ -30,9 +30,10 @@ Typical prompts:
30
30
  4. Follow relationships only after the primary record set is clear.
31
31
  5. Treat "worked on" as a proxy unless actionstep effort/date evidence exists. Assignment and timestamps show involvement; `actionsteps.effort` on `COMPLETED` or `BOOKED` records is stronger time-entry evidence.
32
32
  6. Distinguish direct project assignment from project inference through linked tickets.
33
- 7. When the question is really about account or transaction follow-up, check `actionsteps` before inventing a new task.
34
- 8. For mutations, preview the affected record first and update with an explicit PATCH body.
35
- 9. Escalate from the CLI to `@zeyos/client` if the workflow needs unsupported joins, additional request control, or correlation across multiple list responses.
33
+ 7. For logged ticket-time summaries, include actionsteps directly linked to the ticket and actionsteps linked to tasks whose `task.ticket` is that ticket; dedupe actionstep IDs before summing.
34
+ 8. When the question is really about account or transaction follow-up, check `actionsteps` before inventing a new task.
35
+ 9. For mutations, preview the affected record first and update with an explicit PATCH body.
36
+ 10. Escalate from the CLI to `@zeyos/client` if the workflow needs unsupported joins, additional request control, or correlation across multiple list responses.
36
37
 
37
38
  ## Destructive Operations
38
39
 
@@ -155,11 +155,33 @@ For counts:
155
155
  zeyos count actionsteps --filter '{"ticket":812,"status":0}' --json
156
156
  ```
157
157
 
158
+ For unanchored due-date counts, first narrow by equality filters and due-date presence
159
+ server-side. The API can return misleading zero counts for comparison filters like
160
+ `{"duedate":{"<=":4102444800}}`, and `duedate:null` is not a due item. If the
161
+ presence count is zero, the due-date count is zero.
162
+
163
+ ```bash
164
+ zeyos count actionsteps --filter '{"status":0,"duedate":{"!=":null}}' --json
165
+ ```
166
+
167
+ If due dates exist, list those rows and count the timestamp range client-side.
168
+
169
+ ```bash
170
+ zeyos list actionsteps \
171
+ --fields ID,status,duedate \
172
+ --filter '{"status":0,"duedate":{"!=":null}}' \
173
+ --limit 10000 \
174
+ --json
175
+ ```
176
+
177
+ Then parse the JSON and count rows where `duedate != null && Number(duedate) <= cutoff`.
178
+
158
179
  ## Pattern: Time Entry / Effort Summaries
159
180
 
160
181
  Use this for prompts like:
161
182
 
162
183
  - "How many minutes were booked on ticket 812 last week?"
184
+ - "Give me a summary of logged ticket time from the last four weeks."
163
185
  - "Summarize completed actionstep effort for this account."
164
186
  - "Which user logged effort against this project?"
165
187
 
@@ -168,8 +190,47 @@ Recommended approach:
168
190
  1. Resolve the anchor and the date window.
169
191
  2. Query `actionsteps` with fields `ID`, `date`, `status`, `effort`, and the relevant FK (`ticket`, `task`, `account`, or `transaction`).
170
192
  3. Include statuses `1` COMPLETED and `3` BOOKED for time-entry totals unless the user asks for drafts/open follow-ups.
171
- 4. Sum `effort` as minutes; convert to hours only if the user asked for hours.
172
- 5. Keep task/ticket assignment separate from effort totals.
193
+ 4. For a simple ungrouped total, run `zeyos sum actionsteps effort --filter '{"status":[1,3]}'`.
194
+ For anchored or grouped totals, list the needed rows and sum `effort` as minutes.
195
+ Convert to hours only if the user asked for hours.
196
+ 5. For ticket-level totals, roll task time up to the ticket:
197
+ - direct ticket time is `actionsteps.ticket = <ticketId>`
198
+ - task ticket time is `actionsteps.task = <taskId>` where `tasks.ticket = <ticketId>`
199
+ - fetch tasks by `ticket` for a named ticket, or resolve every referenced `actionstep.task` to `task.ticket` for a period-wide ticket summary
200
+ - do not filter tasks by status when resolving historical time; a completed task can still carry valid logged time
201
+ - dedupe by `actionsteps.ID` before summing in case a row has both `ticket` and `task`
202
+ 6. Keep task/ticket assignment separate from effort totals.
203
+
204
+ Ticket-specific client example:
205
+
206
+ ```js
207
+ const [directRows, tasks] = await Promise.all([
208
+ client.api.listActionSteps({
209
+ fields: ['ID', 'date', 'status', 'effort', 'ticket', 'task', 'assigneduser'],
210
+ filters: { ticket: ticketId, status: { IN: [1, 3] }, date: { '>=': start, '<=': end } },
211
+ limit: 10000,
212
+ }),
213
+ client.api.listTasks({
214
+ fields: ['ID', 'ticket', 'name'],
215
+ filters: { ticket: ticketId },
216
+ limit: 10000,
217
+ }),
218
+ ]);
219
+
220
+ const taskIds = tasks.map((task) => task.ID);
221
+ const taskRows = taskIds.length
222
+ ? await client.api.listActionSteps({
223
+ fields: ['ID', 'date', 'status', 'effort', 'ticket', 'task', 'assigneduser'],
224
+ filters: { task: { IN: taskIds }, status: { IN: [1, 3] }, date: { '>=': start, '<=': end } },
225
+ limit: 10000,
226
+ })
227
+ : [];
228
+
229
+ const uniqueRows = new Map();
230
+ for (const row of [...directRows, ...taskRows]) uniqueRows.set(String(row.ID), row);
231
+ const totalMinutes = [...uniqueRows.values()]
232
+ .reduce((sum, row) => sum + (Number(row.effort) || 0), 0);
233
+ ```
173
234
 
174
235
  ## Pattern: Create Follow-Up Work
175
236