makea-cli 0.2.22__tar.gz → 0.2.24__tar.gz
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.
- {makea_cli-0.2.22 → makea_cli-0.2.24}/CHANGELOG.md +22 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/PKG-INFO +4 -2
- {makea_cli-0.2.22 → makea_cli-0.2.24}/README.md +3 -1
- makea_cli-0.2.24/makea_cli/__init__.py +1 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/api_client.py +18 -23
- makea_cli-0.2.24/makea_cli/commands/metrics/cmd.py +227 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/pyproject.toml +1 -1
- {makea_cli-0.2.22 → makea_cli-0.2.24}/tests/golden_manifest.json +213 -84
- {makea_cli-0.2.22 → makea_cli-0.2.24}/uv.lock +1 -1
- makea_cli-0.2.22/makea_cli/__init__.py +0 -1
- makea_cli-0.2.22/makea_cli/commands/metrics/cmd.py +0 -167
- {makea_cli-0.2.22 → makea_cli-0.2.24}/.claude/skills/create-supplier-profile/SKILL.md +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/.claude/skills/lark-cli-setup/SKILL.md +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/.claude/skills/link-supplier-user/SKILL.md +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/.claude/skills/makea-cli-setup/SKILL.md +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/.claude/skills/payment-revenue-report/SKILL.md +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/.claude/skills/supplier-user-id-migration/SKILL.md +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/.gitignore +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea-collection-upload/SKILL.md +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea-collection-upload/scripts/create_products.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/__main__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/auth_pkce.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/broker.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/_util.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/about/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/about/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/analytics/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/analytics/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/auth/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/auth/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/brand/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/brand/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/designer/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/designer/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/directory/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/directory/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/document/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/document/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/email/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/email/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/factory/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/factory/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/library/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/library/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/metrics/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/orders/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/orders/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/playbook/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/playbook/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/product/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/product/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/production/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/production/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/production/pricing_spec.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/shipping/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/shipping/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/supplier/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/supplier/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/tasks/__init__.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/commands/tasks/cmd.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/config.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/errors.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/identity.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/main.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/output.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/posthog_client.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/makea_cli/registry.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/npm/makea-cli/README.md +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/npm/makea-cli/makea-cli.mjs +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/npm/makea-cli/package.json +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/tests/test_broker.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/tests/test_currency_conversion.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/tests/test_output_contract.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/tests/test_persona_commands.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/tests/test_pm_reads.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/tests/test_registry.py +0 -0
- {makea_cli-0.2.22 → makea_cli-0.2.24}/tests/test_supplier_scorecard.py +0 -0
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.24
|
|
4
|
+
|
|
5
|
+
Removed: `admin get revenue-metrics` (legacy `get-cumulative-payment-and-platform-revenue`).
|
|
6
|
+
The backend endpoint is gone; it was never the reconciled figure. Use
|
|
7
|
+
`admin get growth-snapshot` for platform money.
|
|
8
|
+
|
|
9
|
+
## 0.2.23
|
|
10
|
+
|
|
11
|
+
New: Growth Analysis snapshots, the numbers on admin Metrics. Needs the
|
|
12
|
+
backend from the same day (`/admin/growth/snapshots`).
|
|
13
|
+
|
|
14
|
+
- `admin upload growth-snapshot --analysis … --derived … --audit … --forecast …
|
|
15
|
+
--review … --rule-version …` (or `--sources one.json`): publish a reconciled
|
|
16
|
+
snapshot. The server runs the ten publication gates (Stripe allocations
|
|
17
|
+
checked against live Stripe) and refuses a snapshot that fails any of them.
|
|
18
|
+
`--check` runs the gates without publishing. A snapshot id is written once;
|
|
19
|
+
publish a correction under a new id such as `2026-10-26.b`.
|
|
20
|
+
- `admin list growth-snapshot`: every published snapshot, newest first.
|
|
21
|
+
- `admin get growth-snapshot [ID|latest] [--block NAME]`: one snapshot, or one
|
|
22
|
+
block of it. Audited actuals (`ledger`) and Booked GMV (`forecast`) are
|
|
23
|
+
separate blocks; never add one to the other.
|
|
24
|
+
|
|
3
25
|
## 0.2.22
|
|
4
26
|
|
|
5
27
|
New: what the brand reads under a rule, and its card name. Needs the backend
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: makea-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.24
|
|
4
4
|
Summary: Makea admin CLI (browser login + Makea admin HTTP API)
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: click<9,>=8.1
|
|
@@ -114,11 +114,11 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
114
114
|
| `makea-cli admin get analytics-query` | read | `POST /admin/analytics/query` | Run an arbitrary HogQL query (escape hatch). Example: analytics-query "SELECT count() FROM events" | `analytics-query` |
|
|
115
115
|
| `makea-cli admin get currency-conversion` | read | `GET /admin/fx/rates` | the platform's own exchange rates and rounding (rate table for several --from; one --from with --amount converts it). | `convert-currency` |
|
|
116
116
|
| `makea-cli admin get feature-usage` | read | `POST /admin/analytics/query` | How many distinct users used a feature recently, plus event volume. Matches $pageview path and feature_time_spent eve… | `analytics-feature-usage` |
|
|
117
|
+
| `makea-cli admin get growth-snapshot` | read | `GET /admin/growth/snapshots/{id}` | one reconciled Growth Analysis snapshot (default `latest`); `--block` prints one block (headline, gates, review_items, …). | — |
|
|
117
118
|
| `makea-cli admin get library-schema` | read | `GET /admin/library_components/schema` | the tech-pack library taxonomy: sections and their library-role field keys (run this before create/update). | `library-schema` |
|
|
118
119
|
| `makea-cli admin get playbook-preview` | read | `GET /admin/playbook/preview` | the playbook block one AI surface would get for a person or factory, and every rule left out with its reason; --product previews a conversation about one product, --rule answers for one rule. | — |
|
|
119
120
|
| `makea-cli admin get production-pricing` | read | `GET /admin/production_orders/{id}/details` | show a production order's price tiers, MOQ, sizes/colours, pricing groups and cart coverage. | `production-pricing-show` |
|
|
120
121
|
| `makea-cli admin get quote-request` | read | `GET /admin/quote_requests/{quote_request_id}` | one quote request in full. | — |
|
|
121
|
-
| `makea-cli admin get revenue-metrics` | read | `GET /admin/business-metrics/cumulative-payment-and-platform-revenue` | platform-wide payment received and platform revenue (service fee + transaction fee) bucketed over a date range. | `get-cumulative-payment-and-platform-revenue` |
|
|
122
122
|
| `makea-cli admin get rule` | read | `GET /admin/playbook/{target}/rules/{rule_id}` | one rule in full (`--user` / `--supplier` / `--global`), with its evidence: where it came from, who changed it and why. | — |
|
|
123
123
|
| `makea-cli admin get rule-history` | read | `GET /admin/playbook/rules/{rule_id}/history` | every revision of one rule: its words, who changed it and why, and the rule that replaced it. | — |
|
|
124
124
|
| `makea-cli admin get shipping-order` | read | `GET /admin/shipping_orders/{shipping_order_id}/details` | one shipment with its production and sampling orders, freight quotes, fees, tracking and address. | — |
|
|
@@ -128,6 +128,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
128
128
|
| `makea-cli admin get top-clicks` | read | `POST /admin/analytics/query` | which buttons/elements get clicked most. | `analytics-top-clicks` |
|
|
129
129
|
| `makea-cli admin get top-features` | read | `POST /admin/analytics/query` | a quick map of which features get used most. | `analytics-top-features` |
|
|
130
130
|
| `makea-cli admin list library-element` | read | `GET /admin/designer/{user_id}/library_components` | list a designer's tech-pack library (org-fanned). Optional --section scopes to one type. | `library-list` |
|
|
131
|
+
| `makea-cli admin list growth-snapshot` | read | `GET /admin/growth/snapshots` | published Growth Analysis snapshots, newest first (the change log). | — |
|
|
131
132
|
| `makea-cli admin list misc-payment` | read | `GET /admin/products/{id}/misc-payments` | misc payments for one product (admin). | `list-misc-payments-by-product` |
|
|
132
133
|
| `makea-cli admin list product-order` | read | `GET /admin/designer/get_all_product_orders` | designer product (SKU) list. For 大货 use admin list production-order or admin list production-order --product. | `list-product-orders` |
|
|
133
134
|
| `makea-cli admin list production-order` | read | `GET /admin/production_orders?type=PRODUCTION` | 大货 (production) orders only. For restock use admin list restock-order; for shipping use admin list shipping-order. | `list-all-production-orders` |
|
|
@@ -158,6 +159,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
158
159
|
| `makea-cli admin update tracking-status` | write(ask) | `POST /admin/tracking/carrier_status` | apply a carrier status looked up by hand (tracking-sweep skill) to one order: status, fan-out, delivered emails (--silent: state only). | — |
|
|
159
160
|
| `makea-cli admin update user` | write(ask) | `PUT /admin/user-profiles/{user_id}/update` | admin updates DynamoDB user profile fields and/or Cognito custom:userType. Allowed --user-type values: "Fashion brand… | `update-user-profile` |
|
|
160
161
|
| `makea-cli admin upload financial-document` | write(auto) | `POST /admin/products/{product_id}/financial_documents` | upload one or more files to AWS S3 and return financial document metadata. | `upload-financial-documents` |
|
|
162
|
+
| `makea-cli admin upload growth-snapshot` | write(ask) | `POST /admin/growth/snapshots` | publish a reconciled snapshot to admin Metrics; the server runs the ten gates and refuses a failure; `--check` gates only. | — |
|
|
161
163
|
| `makea-cli admin upload link-document` | write(auto) | `POST /admin/upload_link_document` | upload a file to AWS S3 and return document domain metadata for reuse as input in other commands. | `upload-link-document` |
|
|
162
164
|
| `makea-cli admin upload product-document` | write(auto) | `POST /admin/designer/upload_document` | upload a file to AWS S3 under a product and return document domain metadata for reuse by other commands. | `upload-product-document` |
|
|
163
165
|
| `makea-cli admin upload production-order-document` | write(auto) | `POST /admin/production_orders/{production_order_id}/documents` | upload a file to AWS S3 for a production order and return document domain metadata. | `upload-production-order-document` |
|
|
@@ -102,11 +102,11 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
102
102
|
| `makea-cli admin get analytics-query` | read | `POST /admin/analytics/query` | Run an arbitrary HogQL query (escape hatch). Example: analytics-query "SELECT count() FROM events" | `analytics-query` |
|
|
103
103
|
| `makea-cli admin get currency-conversion` | read | `GET /admin/fx/rates` | the platform's own exchange rates and rounding (rate table for several --from; one --from with --amount converts it). | `convert-currency` |
|
|
104
104
|
| `makea-cli admin get feature-usage` | read | `POST /admin/analytics/query` | How many distinct users used a feature recently, plus event volume. Matches $pageview path and feature_time_spent eve… | `analytics-feature-usage` |
|
|
105
|
+
| `makea-cli admin get growth-snapshot` | read | `GET /admin/growth/snapshots/{id}` | one reconciled Growth Analysis snapshot (default `latest`); `--block` prints one block (headline, gates, review_items, …). | — |
|
|
105
106
|
| `makea-cli admin get library-schema` | read | `GET /admin/library_components/schema` | the tech-pack library taxonomy: sections and their library-role field keys (run this before create/update). | `library-schema` |
|
|
106
107
|
| `makea-cli admin get playbook-preview` | read | `GET /admin/playbook/preview` | the playbook block one AI surface would get for a person or factory, and every rule left out with its reason; --product previews a conversation about one product, --rule answers for one rule. | — |
|
|
107
108
|
| `makea-cli admin get production-pricing` | read | `GET /admin/production_orders/{id}/details` | show a production order's price tiers, MOQ, sizes/colours, pricing groups and cart coverage. | `production-pricing-show` |
|
|
108
109
|
| `makea-cli admin get quote-request` | read | `GET /admin/quote_requests/{quote_request_id}` | one quote request in full. | — |
|
|
109
|
-
| `makea-cli admin get revenue-metrics` | read | `GET /admin/business-metrics/cumulative-payment-and-platform-revenue` | platform-wide payment received and platform revenue (service fee + transaction fee) bucketed over a date range. | `get-cumulative-payment-and-platform-revenue` |
|
|
110
110
|
| `makea-cli admin get rule` | read | `GET /admin/playbook/{target}/rules/{rule_id}` | one rule in full (`--user` / `--supplier` / `--global`), with its evidence: where it came from, who changed it and why. | — |
|
|
111
111
|
| `makea-cli admin get rule-history` | read | `GET /admin/playbook/rules/{rule_id}/history` | every revision of one rule: its words, who changed it and why, and the rule that replaced it. | — |
|
|
112
112
|
| `makea-cli admin get shipping-order` | read | `GET /admin/shipping_orders/{shipping_order_id}/details` | one shipment with its production and sampling orders, freight quotes, fees, tracking and address. | — |
|
|
@@ -116,6 +116,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
116
116
|
| `makea-cli admin get top-clicks` | read | `POST /admin/analytics/query` | which buttons/elements get clicked most. | `analytics-top-clicks` |
|
|
117
117
|
| `makea-cli admin get top-features` | read | `POST /admin/analytics/query` | a quick map of which features get used most. | `analytics-top-features` |
|
|
118
118
|
| `makea-cli admin list library-element` | read | `GET /admin/designer/{user_id}/library_components` | list a designer's tech-pack library (org-fanned). Optional --section scopes to one type. | `library-list` |
|
|
119
|
+
| `makea-cli admin list growth-snapshot` | read | `GET /admin/growth/snapshots` | published Growth Analysis snapshots, newest first (the change log). | — |
|
|
119
120
|
| `makea-cli admin list misc-payment` | read | `GET /admin/products/{id}/misc-payments` | misc payments for one product (admin). | `list-misc-payments-by-product` |
|
|
120
121
|
| `makea-cli admin list product-order` | read | `GET /admin/designer/get_all_product_orders` | designer product (SKU) list. For 大货 use admin list production-order or admin list production-order --product. | `list-product-orders` |
|
|
121
122
|
| `makea-cli admin list production-order` | read | `GET /admin/production_orders?type=PRODUCTION` | 大货 (production) orders only. For restock use admin list restock-order; for shipping use admin list shipping-order. | `list-all-production-orders` |
|
|
@@ -146,6 +147,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
|
|
|
146
147
|
| `makea-cli admin update tracking-status` | write(ask) | `POST /admin/tracking/carrier_status` | apply a carrier status looked up by hand (tracking-sweep skill) to one order: status, fan-out, delivered emails (--silent: state only). | — |
|
|
147
148
|
| `makea-cli admin update user` | write(ask) | `PUT /admin/user-profiles/{user_id}/update` | admin updates DynamoDB user profile fields and/or Cognito custom:userType. Allowed --user-type values: "Fashion brand… | `update-user-profile` |
|
|
148
149
|
| `makea-cli admin upload financial-document` | write(auto) | `POST /admin/products/{product_id}/financial_documents` | upload one or more files to AWS S3 and return financial document metadata. | `upload-financial-documents` |
|
|
150
|
+
| `makea-cli admin upload growth-snapshot` | write(ask) | `POST /admin/growth/snapshots` | publish a reconciled snapshot to admin Metrics; the server runs the ten gates and refuses a failure; `--check` gates only. | — |
|
|
149
151
|
| `makea-cli admin upload link-document` | write(auto) | `POST /admin/upload_link_document` | upload a file to AWS S3 and return document domain metadata for reuse as input in other commands. | `upload-link-document` |
|
|
150
152
|
| `makea-cli admin upload product-document` | write(auto) | `POST /admin/designer/upload_document` | upload a file to AWS S3 under a product and return document domain metadata for reuse by other commands. | `upload-product-document` |
|
|
151
153
|
| `makea-cli admin upload production-order-document` | write(auto) | `POST /admin/production_orders/{production_order_id}/documents` | upload a file to AWS S3 for a production order and return document domain metadata. | `upload-production-order-document` |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.24"
|
|
@@ -907,30 +907,25 @@ def update_production_order_details(
|
|
|
907
907
|
)
|
|
908
908
|
|
|
909
909
|
|
|
910
|
-
def
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
)
|
|
910
|
+
def list_growth_snapshots() -> Any:
|
|
911
|
+
"""GET /admin/growth/snapshots — published Growth Analysis snapshots, newest first."""
|
|
912
|
+
return get_json("/admin/growth/snapshots")
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def get_growth_snapshot(snapshot_id: str = "latest") -> Any:
|
|
916
|
+
"""GET /admin/growth/snapshots/{id|latest} — one snapshot, every block of it."""
|
|
917
|
+
return get_json(f"/admin/growth/snapshots/{quote(snapshot_id, safe='')}", timeout=120.0)
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
def import_growth_snapshot(sources_path: Path, *, check_only: bool = False) -> Any:
|
|
921
|
+
"""POST /admin/growth/snapshots — gate and publish one reconciled snapshot.
|
|
922
|
+
|
|
923
|
+
Sent as a file part, not a JSON body: the server caps an in-memory body at
|
|
924
|
+
2.5MB and a snapshot with a long forecast passes that. ``check_only`` runs
|
|
925
|
+
the gates without publishing (``?dry_run=1``).
|
|
918
926
|
"""
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
transaction fee) within a date range, bucketed by day/week/month.
|
|
922
|
-
"""
|
|
923
|
-
params: dict[str, Any] = {"start_date": start_date, "end_date": end_date}
|
|
924
|
-
if granularity is not None:
|
|
925
|
-
params["granularity"] = granularity
|
|
926
|
-
if currency is not None:
|
|
927
|
-
params["currency"] = currency
|
|
928
|
-
if order_type is not None:
|
|
929
|
-
params["order_type"] = order_type
|
|
930
|
-
return get_json(
|
|
931
|
-
"/admin/business-metrics/cumulative-payment-and-platform-revenue",
|
|
932
|
-
params=params,
|
|
933
|
-
)
|
|
927
|
+
path = "/admin/growth/snapshots" + ("?dry_run=1" if check_only else "")
|
|
928
|
+
return post_multipart_with_file(path, file_path=sources_path, timeout=300.0)
|
|
934
929
|
|
|
935
930
|
|
|
936
931
|
def get_fx_rates(*, to: str, sources: list[str], amount: float | None = None) -> Any:
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import tempfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from makea_cli.registry import command
|
|
11
|
+
|
|
12
|
+
from makea_cli import api_client
|
|
13
|
+
from makea_cli.commands._util import print_json_or_exit
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register(app: typer.Typer) -> None:
|
|
19
|
+
@command(
|
|
20
|
+
app,
|
|
21
|
+
"admin",
|
|
22
|
+
"get",
|
|
23
|
+
"currency-conversion",
|
|
24
|
+
legacy="convert-currency",
|
|
25
|
+
api="GET /admin/fx/rates",
|
|
26
|
+
help=(
|
|
27
|
+
"GET /admin/fx/rates — convert money between currencies at the "
|
|
28
|
+
"platform's own rates and rounding, so totals reconcile with its "
|
|
29
|
+
"prices and revenue metrics. Pass several source currencies via --from "
|
|
30
|
+
"(comma-separated) to get a full rate table in one call, then convert "
|
|
31
|
+
"locally — avoids repeated lookups."
|
|
32
|
+
),
|
|
33
|
+
)
|
|
34
|
+
def convert_currency(
|
|
35
|
+
to: str = typer.Option(..., "--to", "-t", help="Target currency, e.g. GBP."),
|
|
36
|
+
from_: str = typer.Option(
|
|
37
|
+
...,
|
|
38
|
+
"--from",
|
|
39
|
+
"-f",
|
|
40
|
+
help="Source currency code, or comma-separated list (e.g. usd,eur,cny,gbp).",
|
|
41
|
+
),
|
|
42
|
+
amount: float = typer.Option(
|
|
43
|
+
None,
|
|
44
|
+
"--amount",
|
|
45
|
+
"-a",
|
|
46
|
+
help="Optional amount to convert (only meaningful with a single --from).",
|
|
47
|
+
),
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Return source->target rates, and optionally a converted amount, as the
|
|
50
|
+
platform computes them.
|
|
51
|
+
|
|
52
|
+
Use it to collapse a mixed-currency set into one currency: collect the
|
|
53
|
+
distinct currencies, call once with --from a,b,c to get every rate, then
|
|
54
|
+
multiply locally. The rates are the platform's (FreeCurrencyService,
|
|
55
|
+
rounded up to ``rate_decimals``) and come from the backend rather than
|
|
56
|
+
being fetched and rounded here, so they cannot drift from it.
|
|
57
|
+
|
|
58
|
+
Output JSON: {"to", "rates": {CUR: rate, ...}, "rate_decimals",
|
|
59
|
+
"rounding", "source", "unavailable"} plus {"from", "rate", "amount",
|
|
60
|
+
"converted"} when a single --from and --amount are given; "converted" is
|
|
61
|
+
the platform's own conversion, rounding included. A currency with no
|
|
62
|
+
rate fails the command rather than coming back null.
|
|
63
|
+
"""
|
|
64
|
+
sources = [s.strip() for s in from_.split(",") if s.strip()]
|
|
65
|
+
if not sources:
|
|
66
|
+
console.print("[red]--from must contain at least one currency code.[/red]")
|
|
67
|
+
raise typer.Exit(1)
|
|
68
|
+
|
|
69
|
+
def _do() -> dict:
|
|
70
|
+
payload = api_client.get_fx_rates(
|
|
71
|
+
to=to,
|
|
72
|
+
sources=sources,
|
|
73
|
+
amount=amount if len(sources) == 1 else None,
|
|
74
|
+
)
|
|
75
|
+
out = (payload or {}).get("result") or {}
|
|
76
|
+
if out.get("unavailable"):
|
|
77
|
+
raise RuntimeError(
|
|
78
|
+
"FX rate unavailable: "
|
|
79
|
+
+ ", ".join(f"{s}->{to.upper()}" for s in out["unavailable"])
|
|
80
|
+
)
|
|
81
|
+
return out
|
|
82
|
+
|
|
83
|
+
print_json_or_exit(console, _do)
|
|
84
|
+
|
|
85
|
+
@command(
|
|
86
|
+
app,
|
|
87
|
+
"admin",
|
|
88
|
+
"list",
|
|
89
|
+
"growth-snapshot",
|
|
90
|
+
api="GET /admin/growth/snapshots",
|
|
91
|
+
help=(
|
|
92
|
+
"GET /admin/growth/snapshots — published Growth Analysis snapshots "
|
|
93
|
+
"(Metrics page), newest first, with cut-off, rule version, audit "
|
|
94
|
+
"status and headline GMV/revenue."
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
def list_growth_snapshots() -> None:
|
|
98
|
+
"""Every published snapshot's index entry. Snapshots are immutable; a
|
|
99
|
+
correction is a new id, so this list is also the change log."""
|
|
100
|
+
print_json_or_exit(console, api_client.list_growth_snapshots)
|
|
101
|
+
|
|
102
|
+
@command(
|
|
103
|
+
app,
|
|
104
|
+
"admin",
|
|
105
|
+
"get",
|
|
106
|
+
"growth-snapshot",
|
|
107
|
+
api="GET /admin/growth/snapshots/{id}",
|
|
108
|
+
help=(
|
|
109
|
+
"GET /admin/growth/snapshots/{id|latest} — one reconciled snapshot: "
|
|
110
|
+
"headline, economics, monthly, customers, ledger, suppliers, forecast, "
|
|
111
|
+
"gates and review items."
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
def get_growth_snapshot(
|
|
115
|
+
snapshot_id: str = typer.Argument("latest", help="Snapshot id, or latest."),
|
|
116
|
+
block: str = typer.Option(
|
|
117
|
+
None,
|
|
118
|
+
"--block",
|
|
119
|
+
"-b",
|
|
120
|
+
help="Print only one block, e.g. headline, gates, review_items, economics.",
|
|
121
|
+
),
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Audited actuals and Booked GMV live in separate blocks (``ledger`` vs
|
|
124
|
+
``forecast``); never add one to the other."""
|
|
125
|
+
|
|
126
|
+
def _do():
|
|
127
|
+
snapshot = (api_client.get_growth_snapshot(snapshot_id) or {}).get("snapshot") or {}
|
|
128
|
+
if block:
|
|
129
|
+
if block not in snapshot:
|
|
130
|
+
raise RuntimeError(f"No block {block!r}. Blocks: {', '.join(sorted(snapshot))}")
|
|
131
|
+
return snapshot[block]
|
|
132
|
+
return snapshot
|
|
133
|
+
|
|
134
|
+
print_json_or_exit(console, _do)
|
|
135
|
+
|
|
136
|
+
@command(
|
|
137
|
+
app,
|
|
138
|
+
"admin",
|
|
139
|
+
"upload",
|
|
140
|
+
"growth-snapshot",
|
|
141
|
+
api="POST /admin/growth/snapshots",
|
|
142
|
+
side_effects=[],
|
|
143
|
+
approval="ask",
|
|
144
|
+
reversible=False,
|
|
145
|
+
help=(
|
|
146
|
+
"POST /admin/growth/snapshots — publish a reconciled Growth Analysis "
|
|
147
|
+
"snapshot to the Metrics page. The server runs the ten publication "
|
|
148
|
+
"gates and refuses anything that fails; --check runs them without "
|
|
149
|
+
"publishing."
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
def upload_growth_snapshot(
|
|
153
|
+
analysis: Path = typer.Option(
|
|
154
|
+
None,
|
|
155
|
+
"--analysis",
|
|
156
|
+
help="analysis_data.json (headline, fx, monthly, customers, orders, ...).",
|
|
157
|
+
),
|
|
158
|
+
derived: Path = typer.Option(
|
|
159
|
+
None, "--derived", help="derived_metrics.json (AOV, supplier populations)."
|
|
160
|
+
),
|
|
161
|
+
audit: Path = typer.Option(
|
|
162
|
+
None, "--audit", help="audit_checks.json (source pipeline assertions)."
|
|
163
|
+
),
|
|
164
|
+
forecast: Path = typer.Option(
|
|
165
|
+
None, "--forecast", help="projection_data.json (Booked GMV)."
|
|
166
|
+
),
|
|
167
|
+
review: Path = typer.Option(
|
|
168
|
+
None,
|
|
169
|
+
"--review",
|
|
170
|
+
help="JSON list of review items; an open item needs accepted_open: true to pass.",
|
|
171
|
+
),
|
|
172
|
+
rule_version: str = typer.Option(
|
|
173
|
+
None, "--rule-version", help="The rule set these numbers follow."
|
|
174
|
+
),
|
|
175
|
+
snapshot_id: str = typer.Option(
|
|
176
|
+
None,
|
|
177
|
+
"--snapshot-id",
|
|
178
|
+
help="Defaults to the cut-off date; suffix a correction, e.g. 2026-10-26.b.",
|
|
179
|
+
),
|
|
180
|
+
fx_basis: str = typer.Option(None, "--fx-basis", help="e.g. 'ECB reference, 31 Jul 2026'."),
|
|
181
|
+
note: str = typer.Option(None, "--note", help="What changed since the previous snapshot."),
|
|
182
|
+
sources: Path = typer.Option(
|
|
183
|
+
None,
|
|
184
|
+
"--sources",
|
|
185
|
+
help="One prebuilt JSON holding every block; replaces the file options.",
|
|
186
|
+
),
|
|
187
|
+
check: bool = typer.Option(False, "--check", help="Run the gates only; publish nothing."),
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Publish once per refresh. An id is written once: a correction is a new
|
|
190
|
+
snapshot, and the previous one stays readable for comparison.
|
|
191
|
+
|
|
192
|
+
Output: {"published", "entry", "gates"}. A failed gate exits 4 with every
|
|
193
|
+
gate and its detail, and nothing is stored.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
def _load(path: Path | None):
|
|
197
|
+
if path is None:
|
|
198
|
+
return None
|
|
199
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
200
|
+
|
|
201
|
+
def _do():
|
|
202
|
+
if sources is not None:
|
|
203
|
+
body = _load(sources)
|
|
204
|
+
else:
|
|
205
|
+
if analysis is None or not rule_version:
|
|
206
|
+
raise RuntimeError("Pass --sources, or --analysis with --rule-version.")
|
|
207
|
+
body = {
|
|
208
|
+
"analysis": _load(analysis),
|
|
209
|
+
"derived": _load(derived) or {},
|
|
210
|
+
"audit": _load(audit) or {},
|
|
211
|
+
"forecast": _load(forecast) or {},
|
|
212
|
+
"review_items": _load(review) or [],
|
|
213
|
+
}
|
|
214
|
+
for key, value in (
|
|
215
|
+
("rule_version", rule_version),
|
|
216
|
+
("snapshot_id", snapshot_id),
|
|
217
|
+
("fx_basis", fx_basis),
|
|
218
|
+
("note", note),
|
|
219
|
+
):
|
|
220
|
+
if value:
|
|
221
|
+
body[key] = value
|
|
222
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
223
|
+
path = Path(tmp) / "growth_snapshot.json"
|
|
224
|
+
path.write_text(json.dumps(body, ensure_ascii=False), encoding="utf-8")
|
|
225
|
+
return api_client.import_growth_snapshot(path, check_only=check)
|
|
226
|
+
|
|
227
|
+
print_json_or_exit(console, _do)
|
|
@@ -2107,6 +2107,50 @@
|
|
|
2107
2107
|
}
|
|
2108
2108
|
]
|
|
2109
2109
|
},
|
|
2110
|
+
{
|
|
2111
|
+
"name": "admin get growth-snapshot",
|
|
2112
|
+
"persona": "admin",
|
|
2113
|
+
"verb": "get",
|
|
2114
|
+
"noun": "growth-snapshot",
|
|
2115
|
+
"access": "read",
|
|
2116
|
+
"api": "GET /admin/growth/snapshots/{id}",
|
|
2117
|
+
"external": null,
|
|
2118
|
+
"side_effects": [],
|
|
2119
|
+
"approval": "auto",
|
|
2120
|
+
"reversible": null,
|
|
2121
|
+
"idempotent": null,
|
|
2122
|
+
"limit": null,
|
|
2123
|
+
"legacy": [],
|
|
2124
|
+
"help": "GET /admin/growth/snapshots/{id|latest} — one reconciled snapshot: headline, economics, monthly, customers, ledger, suppliers, forecast, gates and review items.",
|
|
2125
|
+
"function": "makea_cli.commands.metrics.cmd.register.<locals>.get_growth_snapshot",
|
|
2126
|
+
"flags": [
|
|
2127
|
+
{
|
|
2128
|
+
"name": "snapshot_id",
|
|
2129
|
+
"kind": "argument",
|
|
2130
|
+
"opts": [
|
|
2131
|
+
"snapshot_id"
|
|
2132
|
+
],
|
|
2133
|
+
"type": "text",
|
|
2134
|
+
"required": false,
|
|
2135
|
+
"multiple": false,
|
|
2136
|
+
"default": "latest",
|
|
2137
|
+
"help": "Snapshot id, or latest."
|
|
2138
|
+
},
|
|
2139
|
+
{
|
|
2140
|
+
"name": "block",
|
|
2141
|
+
"kind": "option",
|
|
2142
|
+
"opts": [
|
|
2143
|
+
"--block",
|
|
2144
|
+
"-b"
|
|
2145
|
+
],
|
|
2146
|
+
"type": "text",
|
|
2147
|
+
"required": false,
|
|
2148
|
+
"multiple": false,
|
|
2149
|
+
"default": null,
|
|
2150
|
+
"help": "Print only one block, e.g. headline, gates, review_items, economics."
|
|
2151
|
+
}
|
|
2152
|
+
]
|
|
2153
|
+
},
|
|
2110
2154
|
{
|
|
2111
2155
|
"name": "admin get library-schema",
|
|
2112
2156
|
"persona": "admin",
|
|
@@ -2298,90 +2342,6 @@
|
|
|
2298
2342
|
}
|
|
2299
2343
|
]
|
|
2300
2344
|
},
|
|
2301
|
-
{
|
|
2302
|
-
"name": "admin get revenue-metrics",
|
|
2303
|
-
"persona": "admin",
|
|
2304
|
-
"verb": "get",
|
|
2305
|
-
"noun": "revenue-metrics",
|
|
2306
|
-
"access": "read",
|
|
2307
|
-
"api": "GET /admin/business-metrics/cumulative-payment-and-platform-revenue",
|
|
2308
|
-
"external": null,
|
|
2309
|
-
"side_effects": [],
|
|
2310
|
-
"approval": "auto",
|
|
2311
|
-
"reversible": null,
|
|
2312
|
-
"idempotent": null,
|
|
2313
|
-
"limit": null,
|
|
2314
|
-
"legacy": [
|
|
2315
|
-
"get-cumulative-payment-and-platform-revenue"
|
|
2316
|
-
],
|
|
2317
|
-
"help": "GET /admin/business-metrics/cumulative-payment-and-platform-revenue — platform-wide payment received and platform revenue (service fee + transaction fee) bucketed over a date range.",
|
|
2318
|
-
"function": "makea_cli.commands.metrics.cmd.register.<locals>.get_cumulative_payment_and_platform_revenue",
|
|
2319
|
-
"flags": [
|
|
2320
|
-
{
|
|
2321
|
-
"name": "start_date",
|
|
2322
|
-
"kind": "option",
|
|
2323
|
-
"opts": [
|
|
2324
|
-
"--start-date"
|
|
2325
|
-
],
|
|
2326
|
-
"type": "text",
|
|
2327
|
-
"required": true,
|
|
2328
|
-
"multiple": false,
|
|
2329
|
-
"default": null,
|
|
2330
|
-
"help": "Start date (inclusive), ISO format YYYY-MM-DD."
|
|
2331
|
-
},
|
|
2332
|
-
{
|
|
2333
|
-
"name": "end_date",
|
|
2334
|
-
"kind": "option",
|
|
2335
|
-
"opts": [
|
|
2336
|
-
"--end-date"
|
|
2337
|
-
],
|
|
2338
|
-
"type": "text",
|
|
2339
|
-
"required": true,
|
|
2340
|
-
"multiple": false,
|
|
2341
|
-
"default": null,
|
|
2342
|
-
"help": "End date (inclusive), ISO format YYYY-MM-DD."
|
|
2343
|
-
},
|
|
2344
|
-
{
|
|
2345
|
-
"name": "granularity",
|
|
2346
|
-
"kind": "option",
|
|
2347
|
-
"opts": [
|
|
2348
|
-
"--granularity",
|
|
2349
|
-
"-g"
|
|
2350
|
-
],
|
|
2351
|
-
"type": "text",
|
|
2352
|
-
"required": false,
|
|
2353
|
-
"multiple": false,
|
|
2354
|
-
"default": "day",
|
|
2355
|
-
"help": "Time bucket: day, week, or month. Default: day."
|
|
2356
|
-
},
|
|
2357
|
-
{
|
|
2358
|
-
"name": "currency",
|
|
2359
|
-
"kind": "option",
|
|
2360
|
-
"opts": [
|
|
2361
|
-
"--currency",
|
|
2362
|
-
"-c"
|
|
2363
|
-
],
|
|
2364
|
-
"type": "text",
|
|
2365
|
-
"required": false,
|
|
2366
|
-
"multiple": false,
|
|
2367
|
-
"default": "GBP",
|
|
2368
|
-
"help": "Target currency for conversion. Default: GBP."
|
|
2369
|
-
},
|
|
2370
|
-
{
|
|
2371
|
-
"name": "order_type",
|
|
2372
|
-
"kind": "option",
|
|
2373
|
-
"opts": [
|
|
2374
|
-
"--order-type",
|
|
2375
|
-
"-t"
|
|
2376
|
-
],
|
|
2377
|
-
"type": "text",
|
|
2378
|
-
"required": false,
|
|
2379
|
-
"multiple": false,
|
|
2380
|
-
"default": "all",
|
|
2381
|
-
"help": "Filter: all, sampling, or production. Default: all."
|
|
2382
|
-
}
|
|
2383
|
-
]
|
|
2384
|
-
},
|
|
2385
2345
|
{
|
|
2386
2346
|
"name": "admin get rule",
|
|
2387
2347
|
"persona": "admin",
|
|
@@ -2703,6 +2663,24 @@
|
|
|
2703
2663
|
}
|
|
2704
2664
|
]
|
|
2705
2665
|
},
|
|
2666
|
+
{
|
|
2667
|
+
"name": "admin list growth-snapshot",
|
|
2668
|
+
"persona": "admin",
|
|
2669
|
+
"verb": "list",
|
|
2670
|
+
"noun": "growth-snapshot",
|
|
2671
|
+
"access": "read",
|
|
2672
|
+
"api": "GET /admin/growth/snapshots",
|
|
2673
|
+
"external": null,
|
|
2674
|
+
"side_effects": [],
|
|
2675
|
+
"approval": "auto",
|
|
2676
|
+
"reversible": null,
|
|
2677
|
+
"idempotent": null,
|
|
2678
|
+
"limit": "client",
|
|
2679
|
+
"legacy": [],
|
|
2680
|
+
"help": "GET /admin/growth/snapshots — published Growth Analysis snapshots (Metrics page), newest first, with cut-off, rule version, audit status and headline GMV/revenue.",
|
|
2681
|
+
"function": "makea_cli.commands.metrics.cmd.register.<locals>.list_growth_snapshots",
|
|
2682
|
+
"flags": []
|
|
2683
|
+
},
|
|
2706
2684
|
{
|
|
2707
2685
|
"name": "admin list library-element",
|
|
2708
2686
|
"persona": "admin",
|
|
@@ -6079,6 +6057,157 @@
|
|
|
6079
6057
|
}
|
|
6080
6058
|
]
|
|
6081
6059
|
},
|
|
6060
|
+
{
|
|
6061
|
+
"name": "admin upload growth-snapshot",
|
|
6062
|
+
"persona": "admin",
|
|
6063
|
+
"verb": "upload",
|
|
6064
|
+
"noun": "growth-snapshot",
|
|
6065
|
+
"access": "write",
|
|
6066
|
+
"api": "POST /admin/growth/snapshots",
|
|
6067
|
+
"external": null,
|
|
6068
|
+
"side_effects": [],
|
|
6069
|
+
"approval": "ask",
|
|
6070
|
+
"reversible": false,
|
|
6071
|
+
"idempotent": null,
|
|
6072
|
+
"limit": null,
|
|
6073
|
+
"legacy": [],
|
|
6074
|
+
"help": "POST /admin/growth/snapshots — publish a reconciled Growth Analysis snapshot to the Metrics page. The server runs the ten publication gates and refuses anything that fails; --check runs them without publishing.",
|
|
6075
|
+
"function": "makea_cli.commands.metrics.cmd.register.<locals>.upload_growth_snapshot",
|
|
6076
|
+
"flags": [
|
|
6077
|
+
{
|
|
6078
|
+
"name": "analysis",
|
|
6079
|
+
"kind": "option",
|
|
6080
|
+
"opts": [
|
|
6081
|
+
"--analysis"
|
|
6082
|
+
],
|
|
6083
|
+
"type": "path",
|
|
6084
|
+
"required": false,
|
|
6085
|
+
"multiple": false,
|
|
6086
|
+
"default": null,
|
|
6087
|
+
"help": "analysis_data.json (headline, fx, monthly, customers, orders, ...)."
|
|
6088
|
+
},
|
|
6089
|
+
{
|
|
6090
|
+
"name": "derived",
|
|
6091
|
+
"kind": "option",
|
|
6092
|
+
"opts": [
|
|
6093
|
+
"--derived"
|
|
6094
|
+
],
|
|
6095
|
+
"type": "path",
|
|
6096
|
+
"required": false,
|
|
6097
|
+
"multiple": false,
|
|
6098
|
+
"default": null,
|
|
6099
|
+
"help": "derived_metrics.json (AOV, supplier populations)."
|
|
6100
|
+
},
|
|
6101
|
+
{
|
|
6102
|
+
"name": "audit",
|
|
6103
|
+
"kind": "option",
|
|
6104
|
+
"opts": [
|
|
6105
|
+
"--audit"
|
|
6106
|
+
],
|
|
6107
|
+
"type": "path",
|
|
6108
|
+
"required": false,
|
|
6109
|
+
"multiple": false,
|
|
6110
|
+
"default": null,
|
|
6111
|
+
"help": "audit_checks.json (source pipeline assertions)."
|
|
6112
|
+
},
|
|
6113
|
+
{
|
|
6114
|
+
"name": "forecast",
|
|
6115
|
+
"kind": "option",
|
|
6116
|
+
"opts": [
|
|
6117
|
+
"--forecast"
|
|
6118
|
+
],
|
|
6119
|
+
"type": "path",
|
|
6120
|
+
"required": false,
|
|
6121
|
+
"multiple": false,
|
|
6122
|
+
"default": null,
|
|
6123
|
+
"help": "projection_data.json (Booked GMV)."
|
|
6124
|
+
},
|
|
6125
|
+
{
|
|
6126
|
+
"name": "review",
|
|
6127
|
+
"kind": "option",
|
|
6128
|
+
"opts": [
|
|
6129
|
+
"--review"
|
|
6130
|
+
],
|
|
6131
|
+
"type": "path",
|
|
6132
|
+
"required": false,
|
|
6133
|
+
"multiple": false,
|
|
6134
|
+
"default": null,
|
|
6135
|
+
"help": "JSON list of review items; an open item needs accepted_open: true to pass."
|
|
6136
|
+
},
|
|
6137
|
+
{
|
|
6138
|
+
"name": "rule_version",
|
|
6139
|
+
"kind": "option",
|
|
6140
|
+
"opts": [
|
|
6141
|
+
"--rule-version"
|
|
6142
|
+
],
|
|
6143
|
+
"type": "text",
|
|
6144
|
+
"required": false,
|
|
6145
|
+
"multiple": false,
|
|
6146
|
+
"default": null,
|
|
6147
|
+
"help": "The rule set these numbers follow."
|
|
6148
|
+
},
|
|
6149
|
+
{
|
|
6150
|
+
"name": "snapshot_id",
|
|
6151
|
+
"kind": "option",
|
|
6152
|
+
"opts": [
|
|
6153
|
+
"--snapshot-id"
|
|
6154
|
+
],
|
|
6155
|
+
"type": "text",
|
|
6156
|
+
"required": false,
|
|
6157
|
+
"multiple": false,
|
|
6158
|
+
"default": null,
|
|
6159
|
+
"help": "Defaults to the cut-off date; suffix a correction, e.g. 2026-10-26.b."
|
|
6160
|
+
},
|
|
6161
|
+
{
|
|
6162
|
+
"name": "fx_basis",
|
|
6163
|
+
"kind": "option",
|
|
6164
|
+
"opts": [
|
|
6165
|
+
"--fx-basis"
|
|
6166
|
+
],
|
|
6167
|
+
"type": "text",
|
|
6168
|
+
"required": false,
|
|
6169
|
+
"multiple": false,
|
|
6170
|
+
"default": null,
|
|
6171
|
+
"help": "e.g. 'ECB reference, 31 Jul 2026'."
|
|
6172
|
+
},
|
|
6173
|
+
{
|
|
6174
|
+
"name": "note",
|
|
6175
|
+
"kind": "option",
|
|
6176
|
+
"opts": [
|
|
6177
|
+
"--note"
|
|
6178
|
+
],
|
|
6179
|
+
"type": "text",
|
|
6180
|
+
"required": false,
|
|
6181
|
+
"multiple": false,
|
|
6182
|
+
"default": null,
|
|
6183
|
+
"help": "What changed since the previous snapshot."
|
|
6184
|
+
},
|
|
6185
|
+
{
|
|
6186
|
+
"name": "sources",
|
|
6187
|
+
"kind": "option",
|
|
6188
|
+
"opts": [
|
|
6189
|
+
"--sources"
|
|
6190
|
+
],
|
|
6191
|
+
"type": "path",
|
|
6192
|
+
"required": false,
|
|
6193
|
+
"multiple": false,
|
|
6194
|
+
"default": null,
|
|
6195
|
+
"help": "One prebuilt JSON holding every block; replaces the file options."
|
|
6196
|
+
},
|
|
6197
|
+
{
|
|
6198
|
+
"name": "check",
|
|
6199
|
+
"kind": "option",
|
|
6200
|
+
"opts": [
|
|
6201
|
+
"--check"
|
|
6202
|
+
],
|
|
6203
|
+
"type": "flag",
|
|
6204
|
+
"required": false,
|
|
6205
|
+
"multiple": false,
|
|
6206
|
+
"default": false,
|
|
6207
|
+
"help": "Run the gates only; publish nothing."
|
|
6208
|
+
}
|
|
6209
|
+
]
|
|
6210
|
+
},
|
|
6082
6211
|
{
|
|
6083
6212
|
"name": "admin upload link-document",
|
|
6084
6213
|
"persona": "admin",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.22"
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import typer
|
|
4
|
-
from rich.console import Console
|
|
5
|
-
|
|
6
|
-
from makea_cli.registry import command
|
|
7
|
-
|
|
8
|
-
from makea_cli import api_client
|
|
9
|
-
from makea_cli.commands._util import print_json_or_exit
|
|
10
|
-
|
|
11
|
-
console = Console()
|
|
12
|
-
|
|
13
|
-
_GRANULARITIES = ("day", "week", "month")
|
|
14
|
-
_ORDER_TYPES = ("all", "sampling", "production")
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def register(app: typer.Typer) -> None:
|
|
18
|
-
@command(
|
|
19
|
-
app,
|
|
20
|
-
"admin",
|
|
21
|
-
"get",
|
|
22
|
-
"revenue-metrics",
|
|
23
|
-
legacy="get-cumulative-payment-and-platform-revenue",
|
|
24
|
-
api="GET /admin/business-metrics/cumulative-payment-and-platform-revenue",
|
|
25
|
-
help=(
|
|
26
|
-
"GET /admin/business-metrics/cumulative-payment-and-platform-revenue — "
|
|
27
|
-
"platform-wide payment received and platform revenue (service fee + "
|
|
28
|
-
"transaction fee) bucketed over a date range."
|
|
29
|
-
),
|
|
30
|
-
)
|
|
31
|
-
def get_cumulative_payment_and_platform_revenue(
|
|
32
|
-
start_date: str = typer.Option(
|
|
33
|
-
...,
|
|
34
|
-
"--start-date",
|
|
35
|
-
help="Start date (inclusive), ISO format YYYY-MM-DD.",
|
|
36
|
-
),
|
|
37
|
-
end_date: str = typer.Option(
|
|
38
|
-
...,
|
|
39
|
-
"--end-date",
|
|
40
|
-
help="End date (inclusive), ISO format YYYY-MM-DD.",
|
|
41
|
-
),
|
|
42
|
-
granularity: str = typer.Option(
|
|
43
|
-
"day",
|
|
44
|
-
"--granularity",
|
|
45
|
-
"-g",
|
|
46
|
-
help="Time bucket: day, week, or month. Default: day.",
|
|
47
|
-
),
|
|
48
|
-
currency: str = typer.Option(
|
|
49
|
-
"GBP",
|
|
50
|
-
"--currency",
|
|
51
|
-
"-c",
|
|
52
|
-
help="Target currency for conversion. Default: GBP.",
|
|
53
|
-
),
|
|
54
|
-
order_type: str = typer.Option(
|
|
55
|
-
"all",
|
|
56
|
-
"--order-type",
|
|
57
|
-
"-t",
|
|
58
|
-
help="Filter: all, sampling, or production. Default: all.",
|
|
59
|
-
),
|
|
60
|
-
) -> None:
|
|
61
|
-
"""
|
|
62
|
-
Platform-wide cumulative payment received and platform revenue over a date range.
|
|
63
|
-
|
|
64
|
-
Calls GET /admin/business-metrics/cumulative-payment-and-platform-revenue.
|
|
65
|
-
|
|
66
|
-
Platform revenue = service fee + transaction fee (the 2.8% transaction fee is
|
|
67
|
-
applied by the server). The response contains two siblings under ``result``:
|
|
68
|
-
|
|
69
|
-
- ``payment_received``: cumulative/period amounts per bucket, plus a
|
|
70
|
-
``payment_breakdown`` (sampling / production_deposit / production_final).
|
|
71
|
-
- ``platform_revenue``: cumulative/period amounts per bucket.
|
|
72
|
-
|
|
73
|
-
Input: required --start-date and --end-date (YYYY-MM-DD); optional
|
|
74
|
-
--granularity (day|week|month), --currency, --order-type (all|sampling|production).
|
|
75
|
-
|
|
76
|
-
Output: raw JSON body from the admin business-metrics endpoint.
|
|
77
|
-
|
|
78
|
-
This endpoint is **platform-wide only** and does not support filtering by
|
|
79
|
-
user_id, product, or category. For per-user / per-product drill-downs, use
|
|
80
|
-
admin list product-order / admin list sampling-order --product /
|
|
81
|
-
admin list production-order --product and aggregate client-side.
|
|
82
|
-
"""
|
|
83
|
-
g = granularity.strip().lower()
|
|
84
|
-
if g not in _GRANULARITIES:
|
|
85
|
-
console.print(f"[red]--granularity must be one of: {', '.join(_GRANULARITIES)}.[/red]")
|
|
86
|
-
raise typer.Exit(1)
|
|
87
|
-
ot = order_type.strip().lower()
|
|
88
|
-
if ot not in _ORDER_TYPES:
|
|
89
|
-
console.print(f"[red]--order-type must be one of: {', '.join(_ORDER_TYPES)}.[/red]")
|
|
90
|
-
raise typer.Exit(1)
|
|
91
|
-
|
|
92
|
-
print_json_or_exit(
|
|
93
|
-
console,
|
|
94
|
-
lambda: api_client.get_cumulative_payment_and_platform_revenue(
|
|
95
|
-
start_date=start_date,
|
|
96
|
-
end_date=end_date,
|
|
97
|
-
granularity=g,
|
|
98
|
-
currency=currency,
|
|
99
|
-
order_type=ot,
|
|
100
|
-
),
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
@command(
|
|
104
|
-
app,
|
|
105
|
-
"admin",
|
|
106
|
-
"get",
|
|
107
|
-
"currency-conversion",
|
|
108
|
-
legacy="convert-currency",
|
|
109
|
-
api="GET /admin/fx/rates",
|
|
110
|
-
help=(
|
|
111
|
-
"GET /admin/fx/rates — convert money between currencies at the "
|
|
112
|
-
"platform's own rates and rounding, so totals reconcile with its "
|
|
113
|
-
"prices and revenue metrics. Pass several source currencies via --from "
|
|
114
|
-
"(comma-separated) to get a full rate table in one call, then convert "
|
|
115
|
-
"locally — avoids repeated lookups."
|
|
116
|
-
),
|
|
117
|
-
)
|
|
118
|
-
def convert_currency(
|
|
119
|
-
to: str = typer.Option(..., "--to", "-t", help="Target currency, e.g. GBP."),
|
|
120
|
-
from_: str = typer.Option(
|
|
121
|
-
...,
|
|
122
|
-
"--from",
|
|
123
|
-
"-f",
|
|
124
|
-
help="Source currency code, or comma-separated list (e.g. usd,eur,cny,gbp).",
|
|
125
|
-
),
|
|
126
|
-
amount: float = typer.Option(
|
|
127
|
-
None,
|
|
128
|
-
"--amount",
|
|
129
|
-
"-a",
|
|
130
|
-
help="Optional amount to convert (only meaningful with a single --from).",
|
|
131
|
-
),
|
|
132
|
-
) -> None:
|
|
133
|
-
"""Return source->target rates, and optionally a converted amount, as the
|
|
134
|
-
platform computes them.
|
|
135
|
-
|
|
136
|
-
Use it to collapse a mixed-currency set into one currency: collect the
|
|
137
|
-
distinct currencies, call once with --from a,b,c to get every rate, then
|
|
138
|
-
multiply locally. The rates are the platform's (FreeCurrencyService,
|
|
139
|
-
rounded up to ``rate_decimals``) and come from the backend rather than
|
|
140
|
-
being fetched and rounded here, so they cannot drift from it.
|
|
141
|
-
|
|
142
|
-
Output JSON: {"to", "rates": {CUR: rate, ...}, "rate_decimals",
|
|
143
|
-
"rounding", "source", "unavailable"} plus {"from", "rate", "amount",
|
|
144
|
-
"converted"} when a single --from and --amount are given; "converted" is
|
|
145
|
-
the platform's own conversion, rounding included. A currency with no
|
|
146
|
-
rate fails the command rather than coming back null.
|
|
147
|
-
"""
|
|
148
|
-
sources = [s.strip() for s in from_.split(",") if s.strip()]
|
|
149
|
-
if not sources:
|
|
150
|
-
console.print("[red]--from must contain at least one currency code.[/red]")
|
|
151
|
-
raise typer.Exit(1)
|
|
152
|
-
|
|
153
|
-
def _do() -> dict:
|
|
154
|
-
payload = api_client.get_fx_rates(
|
|
155
|
-
to=to,
|
|
156
|
-
sources=sources,
|
|
157
|
-
amount=amount if len(sources) == 1 else None,
|
|
158
|
-
)
|
|
159
|
-
out = (payload or {}).get("result") or {}
|
|
160
|
-
if out.get("unavailable"):
|
|
161
|
-
raise RuntimeError(
|
|
162
|
-
"FX rate unavailable: "
|
|
163
|
-
+ ", ".join(f"{s}->{to.upper()}" for s in out["unavailable"])
|
|
164
|
-
)
|
|
165
|
-
return out
|
|
166
|
-
|
|
167
|
-
print_json_or_exit(console, _do)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|