meter-lib 0.0.6__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.
- meter_lib-0.0.6/.github/workflows/publish.yml +156 -0
- meter_lib-0.0.6/.gitignore +9 -0
- meter_lib-0.0.6/PKG-INFO +249 -0
- meter_lib-0.0.6/README.md +231 -0
- meter_lib-0.0.6/meter_lib/__init__.py +1 -0
- meter_lib-0.0.6/meter_lib/core.py +115 -0
- meter_lib-0.0.6/pyproject.toml +29 -0
- meter_lib-0.0.6/requirements.txt +1 -0
- meter_lib-0.0.6/setup.py +44 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
name: Publish Python Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
19
|
+
uses: actions/setup-python@v4
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: |
|
|
25
|
+
python -m pip install --upgrade pip
|
|
26
|
+
pip install -r requirements.txt
|
|
27
|
+
pip install pytest pytest-cov flake8
|
|
28
|
+
|
|
29
|
+
- name: Lint with flake8
|
|
30
|
+
run: |
|
|
31
|
+
# Stop the build if there are Python syntax errors or undefined names
|
|
32
|
+
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
|
33
|
+
# Exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
|
34
|
+
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
|
35
|
+
|
|
36
|
+
- name: Test with pytest (if tests exist)
|
|
37
|
+
run: |
|
|
38
|
+
if [ -d "tests" ] || [ -f "test_*.py" ]; then
|
|
39
|
+
pytest
|
|
40
|
+
else
|
|
41
|
+
echo "No tests found, skipping test step"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
publish:
|
|
45
|
+
needs: test
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
|
48
|
+
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
with:
|
|
52
|
+
fetch-depth: 0 # Fetch all history for tags
|
|
53
|
+
token: ${{ secrets.PAT_TOKEN }}
|
|
54
|
+
ref: main
|
|
55
|
+
|
|
56
|
+
- name: Set up Python
|
|
57
|
+
uses: actions/setup-python@v4
|
|
58
|
+
with:
|
|
59
|
+
python-version: "3.9"
|
|
60
|
+
|
|
61
|
+
- name: Extract version from release
|
|
62
|
+
id: get_version
|
|
63
|
+
run: |
|
|
64
|
+
if [ "${{ github.event_name }}" = "release" ]; then
|
|
65
|
+
VERSION="${{ github.event.release.tag_name }}"
|
|
66
|
+
echo "Version from release: $VERSION"
|
|
67
|
+
else
|
|
68
|
+
# For manual workflow dispatch, use latest tag
|
|
69
|
+
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "0.1.0")
|
|
70
|
+
echo "Version from git tag: $VERSION"
|
|
71
|
+
fi
|
|
72
|
+
# Remove 'v' prefix if present
|
|
73
|
+
VERSION=$(echo "$VERSION" | sed 's/^v//')
|
|
74
|
+
echo "PACKAGE_VERSION=$VERSION" >> $GITHUB_ENV
|
|
75
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
76
|
+
echo "Clean version: $VERSION"
|
|
77
|
+
|
|
78
|
+
- name: Update version in setup.py and pyproject.toml
|
|
79
|
+
run: |
|
|
80
|
+
echo "Updating versions to: ${{ env.PACKAGE_VERSION }}"
|
|
81
|
+
|
|
82
|
+
# Update setup.py version
|
|
83
|
+
sed -i 's/version="[^"]*"/version="${{ env.PACKAGE_VERSION }}"/' setup.py
|
|
84
|
+
|
|
85
|
+
# Update pyproject.toml version
|
|
86
|
+
sed -i 's/version = "[^"]*"/version = "${{ env.PACKAGE_VERSION }}"/' pyproject.toml
|
|
87
|
+
|
|
88
|
+
# Verify the changes
|
|
89
|
+
echo "Updated setup.py version line:"
|
|
90
|
+
grep 'version=' setup.py
|
|
91
|
+
echo "Updated pyproject.toml version line:"
|
|
92
|
+
grep 'version =' pyproject.toml
|
|
93
|
+
|
|
94
|
+
- name: Commit version update to main branch
|
|
95
|
+
if: github.event_name == 'release'
|
|
96
|
+
run: |
|
|
97
|
+
# Configure git
|
|
98
|
+
git config --global user.name "github-actions[bot]"
|
|
99
|
+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
|
100
|
+
|
|
101
|
+
# Configure git to use the PAT token
|
|
102
|
+
# git remote set-url origin https://x-access-token:${{ secrets.PAT_TOKEN }}@github.com/${{ github.repository }}.git
|
|
103
|
+
|
|
104
|
+
# Check if there are changes to commit
|
|
105
|
+
if git diff --quiet setup.py pyproject.toml; then
|
|
106
|
+
echo "No changes to version files, skipping commit"
|
|
107
|
+
else
|
|
108
|
+
echo "Committing version update to main branch"
|
|
109
|
+
git add setup.py pyproject.toml
|
|
110
|
+
git commit -m "chore: update version to ${{ env.PACKAGE_VERSION }} [skip ci]"
|
|
111
|
+
|
|
112
|
+
# Push to main branch
|
|
113
|
+
git push origin HEAD:main
|
|
114
|
+
echo "Successfully pushed version update to main branch"
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
- name: Install build dependencies
|
|
118
|
+
run: |
|
|
119
|
+
python -m pip install --upgrade pip
|
|
120
|
+
pip install build twine
|
|
121
|
+
|
|
122
|
+
- name: Display version info
|
|
123
|
+
run: |
|
|
124
|
+
echo "Building package with version: ${{ env.PACKAGE_VERSION }}"
|
|
125
|
+
python setup.py --version
|
|
126
|
+
|
|
127
|
+
- name: Build package
|
|
128
|
+
run: python -m build
|
|
129
|
+
|
|
130
|
+
- name: Check built package
|
|
131
|
+
run: twine check dist/*
|
|
132
|
+
|
|
133
|
+
- name: Publish to Test PyPI
|
|
134
|
+
if: github.event_name == 'workflow_dispatch'
|
|
135
|
+
env:
|
|
136
|
+
TWINE_USERNAME: __token__
|
|
137
|
+
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
|
138
|
+
run: |
|
|
139
|
+
twine upload --repository testpypi dist/*
|
|
140
|
+
|
|
141
|
+
- name: Publish to PyPI
|
|
142
|
+
if: github.event_name == 'release'
|
|
143
|
+
env:
|
|
144
|
+
TWINE_USERNAME: __token__
|
|
145
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
146
|
+
run: |
|
|
147
|
+
echo "Checking if PYPI_API_TOKEN is set..."
|
|
148
|
+
if [ -z "$TWINE_PASSWORD" ]; then
|
|
149
|
+
echo "ERROR: PYPI_API_TOKEN secret is not set!"
|
|
150
|
+
exit 1
|
|
151
|
+
else
|
|
152
|
+
echo "PYPI_API_TOKEN is set (length: ${#TWINE_PASSWORD})"
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
echo "Uploading to PyPI..."
|
|
156
|
+
twine upload dist/* --verbose
|
meter_lib-0.0.6/PKG-INFO
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: meter-lib
|
|
3
|
+
Version: 0.0.6
|
|
4
|
+
Summary: A litewave library to collect the customer credit usage
|
|
5
|
+
Project-URL: Homepage, https://github.com/aiorch/meter-lib
|
|
6
|
+
Project-URL: Repository, https://github.com/aiorch/meter-lib
|
|
7
|
+
Project-URL: Issues, https://github.com/aiorch/meter-lib/issues
|
|
8
|
+
Author-email: Sruthi <sruthi@litewave.ai>
|
|
9
|
+
License: MIT
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: requests>=2.28
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
## meter-lib — Usage Guide
|
|
20
|
+
|
|
21
|
+
### Overview
|
|
22
|
+
|
|
23
|
+
`meter-lib` is a lightweight helper library for sending metering events to the Litewave backend and for looking up a customer account by `tenant_id`.
|
|
24
|
+
|
|
25
|
+
### Requirements
|
|
26
|
+
|
|
27
|
+
- Python 3.10+
|
|
28
|
+
|
|
29
|
+
### Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install meter-lib
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Parameters Required
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
tenant_id,
|
|
39
|
+
device_id,
|
|
40
|
+
meter_id,
|
|
41
|
+
kafka_url,
|
|
42
|
+
portal_url,
|
|
43
|
+
api_key,
|
|
44
|
+
total_usage,
|
|
45
|
+
start_time,
|
|
46
|
+
end_time
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Quickstart
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from meter_lib import post_meter_usage
|
|
53
|
+
|
|
54
|
+
tenant_id = "tenant_123"
|
|
55
|
+
device_id = "us-east-ing1"
|
|
56
|
+
meter_id = "document.basic.page"
|
|
57
|
+
kafka_url="https://stream.app.domain.com",
|
|
58
|
+
portal_url="https://portal.app.domain.com",
|
|
59
|
+
api_key="sk-lw-40ea6ac9ee3xxxx_305e790xxxx"
|
|
60
|
+
|
|
61
|
+
result = post_meter_usage(
|
|
62
|
+
tenant_id=tenant_id,
|
|
63
|
+
device_id=device_id,
|
|
64
|
+
meter_id=meter_id,
|
|
65
|
+
kafka_url=kafka_url,
|
|
66
|
+
portal_url=portal_url,
|
|
67
|
+
api_key=api_key,
|
|
68
|
+
total_usage=24, # integer units as defined by your meter
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### For AI enabled
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from meter_lib import post_ai_meter_usage
|
|
76
|
+
|
|
77
|
+
tenant_id = "tenant_123"
|
|
78
|
+
device_id = "us-east-ing1"
|
|
79
|
+
meter_id = "chat.on.time_hours"
|
|
80
|
+
kafka_url="https://stream.app.domain.com",
|
|
81
|
+
portal_url="https://portal.app.domain.com",
|
|
82
|
+
api_key="sk-lw-40ea6ac9ee3xxxx_305e790xxxx"
|
|
83
|
+
start_time = 1759791552000 # Timestamp in milliseconds
|
|
84
|
+
|
|
85
|
+
result = post_ai_meter_usage(
|
|
86
|
+
tenant_id=tenant_id,
|
|
87
|
+
device_id=device_id,
|
|
88
|
+
meter_id=meter_id,
|
|
89
|
+
kafka_url=kafka_url,
|
|
90
|
+
portal_url=portal_url,
|
|
91
|
+
api_key=api_key
|
|
92
|
+
start_time= start_time # Timestamp in milliseconds
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### For AI disabled
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from meter_lib import post_ai_meter_usage
|
|
101
|
+
|
|
102
|
+
tenant_id = "tenant_123"
|
|
103
|
+
device_id = "us-east-ing1"
|
|
104
|
+
meter_id = "chat.on.time_hours"
|
|
105
|
+
kafka_url="https://stream.app.domain.com",
|
|
106
|
+
portal_url="https://portal.app.domain.com",
|
|
107
|
+
api_key="sk-lw-40ea6ac9ee3xxxx_305e790xxxx"
|
|
108
|
+
end_time = 1779799552000 # Timestamp in milliseconds
|
|
109
|
+
|
|
110
|
+
result = post_ai_meter_usage(
|
|
111
|
+
tenant_id=tenant_id,
|
|
112
|
+
device_id=device_id,
|
|
113
|
+
meter_id=meter_id,
|
|
114
|
+
kafka_url=kafka_url,
|
|
115
|
+
portal_url=portal_url,
|
|
116
|
+
api_key=api_key
|
|
117
|
+
end_time= end_time # Timestamp in milliseconds
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
if result is None:
|
|
123
|
+
print("Failed to post meter usage event")
|
|
124
|
+
else:
|
|
125
|
+
print("Event accepted:", result)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Error Handling
|
|
129
|
+
|
|
130
|
+
- `post_meter_usage` returns `None` for network errors or non-success HTTP statuses.
|
|
131
|
+
- Prefer explicit checks for `None` and add retries or backoff in your application layer if needed.
|
|
132
|
+
|
|
133
|
+
### API Reference
|
|
134
|
+
|
|
135
|
+
#### post_meter_usage(tenant_id: str, device_id: str, meter_id: str, kafka_url: str, portal_url: str, api_key: str, total_usage: int) -> dict | None
|
|
136
|
+
|
|
137
|
+
- **Description**: Posts a metering event for a device and meter under a given tenant.
|
|
138
|
+
- **Headers**:
|
|
139
|
+
- `x-tenant-id`: the tenant identifier (string)
|
|
140
|
+
- `x-device-id`: the device identifier (string)
|
|
141
|
+
- **Payload (JSON)**:
|
|
142
|
+
- `meter_id` (string)
|
|
143
|
+
- `total_usage` (integer)
|
|
144
|
+
- `customer_id` (string) — auto-filled by the library.`
|
|
145
|
+
- **Returns**: The backend JSON response (`dict`) on success, otherwise `None`.
|
|
146
|
+
- **Timeout**: 10 seconds.
|
|
147
|
+
- **Notes**:
|
|
148
|
+
- If the customer lookup fails, the call is skipped and `None` is returned.
|
|
149
|
+
- This function is synchronous and will block until the request completes or times out.
|
|
150
|
+
|
|
151
|
+
### List Of Meters:
|
|
152
|
+
- meter_id: "page.processed.basic"
|
|
153
|
+
name: "Basic Document Scanning"
|
|
154
|
+
type: "volume"
|
|
155
|
+
description: "Total number of basic pages processed"
|
|
156
|
+
|
|
157
|
+
- meter_id: "page.processed.advanced"
|
|
158
|
+
name: "Advanced Document Scanning"
|
|
159
|
+
type: "volume"
|
|
160
|
+
description: "Total number of advanced pages processed"
|
|
161
|
+
|
|
162
|
+
- meter_id: "report.generated.small"
|
|
163
|
+
name: "Small Report Generation"
|
|
164
|
+
type: "volume"
|
|
165
|
+
description: "Total number of small reports generated"
|
|
166
|
+
|
|
167
|
+
- meter_id: "report.generated.medium"
|
|
168
|
+
name: "Medium Report Generation"
|
|
169
|
+
type: "volume"
|
|
170
|
+
description: "Total number of medium reports generated"
|
|
171
|
+
|
|
172
|
+
- meter_id: "report.generated.large"
|
|
173
|
+
name: "Large Report Generation"
|
|
174
|
+
type: "volume"
|
|
175
|
+
description: "Total number of large reports generated"
|
|
176
|
+
|
|
177
|
+
- meter_id: "report.generated.dataquery"
|
|
178
|
+
name: "Data Query Report Generation"
|
|
179
|
+
type: "volume"
|
|
180
|
+
description: "Total number of data query reports generated"
|
|
181
|
+
|
|
182
|
+
- meter_id: "report.generated.insights"
|
|
183
|
+
name: "Insights Report Generation"
|
|
184
|
+
type: "volume"
|
|
185
|
+
description: "Total number of insights reports generated"
|
|
186
|
+
|
|
187
|
+
- meter_id: "rule.executed.small"
|
|
188
|
+
name: "Small Rule Execution"
|
|
189
|
+
type: "volume"
|
|
190
|
+
description: "Total number of rules executed with runtime less than 1 minute"
|
|
191
|
+
|
|
192
|
+
- meter_id: "rule.executed.medium"
|
|
193
|
+
name: "Medium Rule Execution"
|
|
194
|
+
type: "volume"
|
|
195
|
+
description: "Total number of rules executed with runtime between 1 and 3 minutes"
|
|
196
|
+
|
|
197
|
+
- meter_id: "rule.executed.large"
|
|
198
|
+
name: "Large Rule Execution"
|
|
199
|
+
type: "volume"
|
|
200
|
+
description: "Total number of rules executed with runtime greater than 4 minutes"
|
|
201
|
+
|
|
202
|
+
- meter_id: "chat.on.time_hours"
|
|
203
|
+
name: "Litewave AI Assistant Usage (Hours)"
|
|
204
|
+
type: "performance"
|
|
205
|
+
description: "Total active chat usage time in hours"
|
|
206
|
+
|
|
207
|
+
- meter_id: "chat.query.time_secs"
|
|
208
|
+
name: "Litewave AI Assistant Query Time (Seconds)"
|
|
209
|
+
type: "performance"
|
|
210
|
+
description: "Total time spent per query in seconds"
|
|
211
|
+
|
|
212
|
+
- meter_id: "document.storage.size_gb"
|
|
213
|
+
name: "Document Storage Size"
|
|
214
|
+
type: "volume"
|
|
215
|
+
description: "Total document storage consumed in GB"
|
|
216
|
+
|
|
217
|
+
- meter_id: "template.processed.small"
|
|
218
|
+
name: "Small Template Processing"
|
|
219
|
+
type: "volume"
|
|
220
|
+
description: "Total number of small templates processed"
|
|
221
|
+
|
|
222
|
+
- meter_id: "template.processed.medium"
|
|
223
|
+
name: "Medium Template Processing"
|
|
224
|
+
type: "volume"
|
|
225
|
+
description: "Total number of medium templates processed"
|
|
226
|
+
|
|
227
|
+
- meter_id: "template.processed.large"
|
|
228
|
+
name: "Large Template Processing"
|
|
229
|
+
type: "volume"
|
|
230
|
+
description: "Total number of large templates processed"
|
|
231
|
+
|
|
232
|
+
- meter_id: "template.processed.count"
|
|
233
|
+
name: "Template Licensing Count"
|
|
234
|
+
type: "volume"
|
|
235
|
+
description: "Total number of licensed templates processed"
|
|
236
|
+
|
|
237
|
+
- meter_id: "template.licensed.total"
|
|
238
|
+
name: "Yearly Template Setup"
|
|
239
|
+
type: "volume"
|
|
240
|
+
description: "Total yearly template setup count"
|
|
241
|
+
|
|
242
|
+
### Troubleshooting
|
|
243
|
+
|
|
244
|
+
- Confirm your `tenant_id`, `device_id`, `meter_id`, `kafka_url`, `portal_url`, `api_key` values are correct.
|
|
245
|
+
|
|
246
|
+
### Support
|
|
247
|
+
|
|
248
|
+
- Homepage: `https://github.com/aiorch/meter-lib`
|
|
249
|
+
- Issues: `https://github.com/aiorch/meter-lib/issues`
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
## meter-lib — Usage Guide
|
|
2
|
+
|
|
3
|
+
### Overview
|
|
4
|
+
|
|
5
|
+
`meter-lib` is a lightweight helper library for sending metering events to the Litewave backend and for looking up a customer account by `tenant_id`.
|
|
6
|
+
|
|
7
|
+
### Requirements
|
|
8
|
+
|
|
9
|
+
- Python 3.10+
|
|
10
|
+
|
|
11
|
+
### Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install meter-lib
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Parameters Required
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
tenant_id,
|
|
21
|
+
device_id,
|
|
22
|
+
meter_id,
|
|
23
|
+
kafka_url,
|
|
24
|
+
portal_url,
|
|
25
|
+
api_key,
|
|
26
|
+
total_usage,
|
|
27
|
+
start_time,
|
|
28
|
+
end_time
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Quickstart
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from meter_lib import post_meter_usage
|
|
35
|
+
|
|
36
|
+
tenant_id = "tenant_123"
|
|
37
|
+
device_id = "us-east-ing1"
|
|
38
|
+
meter_id = "document.basic.page"
|
|
39
|
+
kafka_url="https://stream.app.domain.com",
|
|
40
|
+
portal_url="https://portal.app.domain.com",
|
|
41
|
+
api_key="sk-lw-40ea6ac9ee3xxxx_305e790xxxx"
|
|
42
|
+
|
|
43
|
+
result = post_meter_usage(
|
|
44
|
+
tenant_id=tenant_id,
|
|
45
|
+
device_id=device_id,
|
|
46
|
+
meter_id=meter_id,
|
|
47
|
+
kafka_url=kafka_url,
|
|
48
|
+
portal_url=portal_url,
|
|
49
|
+
api_key=api_key,
|
|
50
|
+
total_usage=24, # integer units as defined by your meter
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### For AI enabled
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from meter_lib import post_ai_meter_usage
|
|
58
|
+
|
|
59
|
+
tenant_id = "tenant_123"
|
|
60
|
+
device_id = "us-east-ing1"
|
|
61
|
+
meter_id = "chat.on.time_hours"
|
|
62
|
+
kafka_url="https://stream.app.domain.com",
|
|
63
|
+
portal_url="https://portal.app.domain.com",
|
|
64
|
+
api_key="sk-lw-40ea6ac9ee3xxxx_305e790xxxx"
|
|
65
|
+
start_time = 1759791552000 # Timestamp in milliseconds
|
|
66
|
+
|
|
67
|
+
result = post_ai_meter_usage(
|
|
68
|
+
tenant_id=tenant_id,
|
|
69
|
+
device_id=device_id,
|
|
70
|
+
meter_id=meter_id,
|
|
71
|
+
kafka_url=kafka_url,
|
|
72
|
+
portal_url=portal_url,
|
|
73
|
+
api_key=api_key
|
|
74
|
+
start_time= start_time # Timestamp in milliseconds
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### For AI disabled
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from meter_lib import post_ai_meter_usage
|
|
83
|
+
|
|
84
|
+
tenant_id = "tenant_123"
|
|
85
|
+
device_id = "us-east-ing1"
|
|
86
|
+
meter_id = "chat.on.time_hours"
|
|
87
|
+
kafka_url="https://stream.app.domain.com",
|
|
88
|
+
portal_url="https://portal.app.domain.com",
|
|
89
|
+
api_key="sk-lw-40ea6ac9ee3xxxx_305e790xxxx"
|
|
90
|
+
end_time = 1779799552000 # Timestamp in milliseconds
|
|
91
|
+
|
|
92
|
+
result = post_ai_meter_usage(
|
|
93
|
+
tenant_id=tenant_id,
|
|
94
|
+
device_id=device_id,
|
|
95
|
+
meter_id=meter_id,
|
|
96
|
+
kafka_url=kafka_url,
|
|
97
|
+
portal_url=portal_url,
|
|
98
|
+
api_key=api_key
|
|
99
|
+
end_time= end_time # Timestamp in milliseconds
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
if result is None:
|
|
105
|
+
print("Failed to post meter usage event")
|
|
106
|
+
else:
|
|
107
|
+
print("Event accepted:", result)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Error Handling
|
|
111
|
+
|
|
112
|
+
- `post_meter_usage` returns `None` for network errors or non-success HTTP statuses.
|
|
113
|
+
- Prefer explicit checks for `None` and add retries or backoff in your application layer if needed.
|
|
114
|
+
|
|
115
|
+
### API Reference
|
|
116
|
+
|
|
117
|
+
#### post_meter_usage(tenant_id: str, device_id: str, meter_id: str, kafka_url: str, portal_url: str, api_key: str, total_usage: int) -> dict | None
|
|
118
|
+
|
|
119
|
+
- **Description**: Posts a metering event for a device and meter under a given tenant.
|
|
120
|
+
- **Headers**:
|
|
121
|
+
- `x-tenant-id`: the tenant identifier (string)
|
|
122
|
+
- `x-device-id`: the device identifier (string)
|
|
123
|
+
- **Payload (JSON)**:
|
|
124
|
+
- `meter_id` (string)
|
|
125
|
+
- `total_usage` (integer)
|
|
126
|
+
- `customer_id` (string) — auto-filled by the library.`
|
|
127
|
+
- **Returns**: The backend JSON response (`dict`) on success, otherwise `None`.
|
|
128
|
+
- **Timeout**: 10 seconds.
|
|
129
|
+
- **Notes**:
|
|
130
|
+
- If the customer lookup fails, the call is skipped and `None` is returned.
|
|
131
|
+
- This function is synchronous and will block until the request completes or times out.
|
|
132
|
+
|
|
133
|
+
### List Of Meters:
|
|
134
|
+
- meter_id: "page.processed.basic"
|
|
135
|
+
name: "Basic Document Scanning"
|
|
136
|
+
type: "volume"
|
|
137
|
+
description: "Total number of basic pages processed"
|
|
138
|
+
|
|
139
|
+
- meter_id: "page.processed.advanced"
|
|
140
|
+
name: "Advanced Document Scanning"
|
|
141
|
+
type: "volume"
|
|
142
|
+
description: "Total number of advanced pages processed"
|
|
143
|
+
|
|
144
|
+
- meter_id: "report.generated.small"
|
|
145
|
+
name: "Small Report Generation"
|
|
146
|
+
type: "volume"
|
|
147
|
+
description: "Total number of small reports generated"
|
|
148
|
+
|
|
149
|
+
- meter_id: "report.generated.medium"
|
|
150
|
+
name: "Medium Report Generation"
|
|
151
|
+
type: "volume"
|
|
152
|
+
description: "Total number of medium reports generated"
|
|
153
|
+
|
|
154
|
+
- meter_id: "report.generated.large"
|
|
155
|
+
name: "Large Report Generation"
|
|
156
|
+
type: "volume"
|
|
157
|
+
description: "Total number of large reports generated"
|
|
158
|
+
|
|
159
|
+
- meter_id: "report.generated.dataquery"
|
|
160
|
+
name: "Data Query Report Generation"
|
|
161
|
+
type: "volume"
|
|
162
|
+
description: "Total number of data query reports generated"
|
|
163
|
+
|
|
164
|
+
- meter_id: "report.generated.insights"
|
|
165
|
+
name: "Insights Report Generation"
|
|
166
|
+
type: "volume"
|
|
167
|
+
description: "Total number of insights reports generated"
|
|
168
|
+
|
|
169
|
+
- meter_id: "rule.executed.small"
|
|
170
|
+
name: "Small Rule Execution"
|
|
171
|
+
type: "volume"
|
|
172
|
+
description: "Total number of rules executed with runtime less than 1 minute"
|
|
173
|
+
|
|
174
|
+
- meter_id: "rule.executed.medium"
|
|
175
|
+
name: "Medium Rule Execution"
|
|
176
|
+
type: "volume"
|
|
177
|
+
description: "Total number of rules executed with runtime between 1 and 3 minutes"
|
|
178
|
+
|
|
179
|
+
- meter_id: "rule.executed.large"
|
|
180
|
+
name: "Large Rule Execution"
|
|
181
|
+
type: "volume"
|
|
182
|
+
description: "Total number of rules executed with runtime greater than 4 minutes"
|
|
183
|
+
|
|
184
|
+
- meter_id: "chat.on.time_hours"
|
|
185
|
+
name: "Litewave AI Assistant Usage (Hours)"
|
|
186
|
+
type: "performance"
|
|
187
|
+
description: "Total active chat usage time in hours"
|
|
188
|
+
|
|
189
|
+
- meter_id: "chat.query.time_secs"
|
|
190
|
+
name: "Litewave AI Assistant Query Time (Seconds)"
|
|
191
|
+
type: "performance"
|
|
192
|
+
description: "Total time spent per query in seconds"
|
|
193
|
+
|
|
194
|
+
- meter_id: "document.storage.size_gb"
|
|
195
|
+
name: "Document Storage Size"
|
|
196
|
+
type: "volume"
|
|
197
|
+
description: "Total document storage consumed in GB"
|
|
198
|
+
|
|
199
|
+
- meter_id: "template.processed.small"
|
|
200
|
+
name: "Small Template Processing"
|
|
201
|
+
type: "volume"
|
|
202
|
+
description: "Total number of small templates processed"
|
|
203
|
+
|
|
204
|
+
- meter_id: "template.processed.medium"
|
|
205
|
+
name: "Medium Template Processing"
|
|
206
|
+
type: "volume"
|
|
207
|
+
description: "Total number of medium templates processed"
|
|
208
|
+
|
|
209
|
+
- meter_id: "template.processed.large"
|
|
210
|
+
name: "Large Template Processing"
|
|
211
|
+
type: "volume"
|
|
212
|
+
description: "Total number of large templates processed"
|
|
213
|
+
|
|
214
|
+
- meter_id: "template.processed.count"
|
|
215
|
+
name: "Template Licensing Count"
|
|
216
|
+
type: "volume"
|
|
217
|
+
description: "Total number of licensed templates processed"
|
|
218
|
+
|
|
219
|
+
- meter_id: "template.licensed.total"
|
|
220
|
+
name: "Yearly Template Setup"
|
|
221
|
+
type: "volume"
|
|
222
|
+
description: "Total yearly template setup count"
|
|
223
|
+
|
|
224
|
+
### Troubleshooting
|
|
225
|
+
|
|
226
|
+
- Confirm your `tenant_id`, `device_id`, `meter_id`, `kafka_url`, `portal_url`, `api_key` values are correct.
|
|
227
|
+
|
|
228
|
+
### Support
|
|
229
|
+
|
|
230
|
+
- Homepage: `https://github.com/aiorch/meter-lib`
|
|
231
|
+
- Issues: `https://github.com/aiorch/meter-lib/issues`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .core import post_meter_usage, post_ai_meter_usage
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
def fetch_customer_id(tenant_id: str, portal_url: str, api_key: str):
|
|
6
|
+
"""
|
|
7
|
+
Fetch the customer account for a given tenant_id.
|
|
8
|
+
Returns a dict with customer info, or None if not found/error.
|
|
9
|
+
"""
|
|
10
|
+
url = f"{portal_url}/metering/v1alpha1/customer-accounts/tenant/{tenant_id}"
|
|
11
|
+
headers = {
|
|
12
|
+
"x-api-key": api_key
|
|
13
|
+
}
|
|
14
|
+
try:
|
|
15
|
+
response = requests.get(url, headers=headers, timeout=10)
|
|
16
|
+
response.raise_for_status()
|
|
17
|
+
data = response.json()
|
|
18
|
+
|
|
19
|
+
# Handle list response
|
|
20
|
+
if isinstance(data, list) and len(data) > 0:
|
|
21
|
+
return data[0]
|
|
22
|
+
elif isinstance(data, dict):
|
|
23
|
+
return data
|
|
24
|
+
else:
|
|
25
|
+
print(f"No customer account found for tenant {tenant_id}")
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
except requests.RequestException as e:
|
|
29
|
+
print(f"Error fetching {url}: {e}")
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def post_meter_usage(tenant_id: str, device_id: str, meter_id: str, kafka_url: str, portal_url: str, api_key: str, total_usage: int ) :
|
|
34
|
+
"""
|
|
35
|
+
Posts meter usage to the events API.
|
|
36
|
+
Uses tenant_id + device_id in headers and includes customer_id in payload.
|
|
37
|
+
"""
|
|
38
|
+
customer_account = fetch_customer_id(tenant_id, portal_url, api_key)
|
|
39
|
+
if not customer_account:
|
|
40
|
+
print("No customer account available, skipping meter usage post")
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
url = f"{kafka_url}/topics/usage-events"
|
|
44
|
+
headers = {
|
|
45
|
+
"Content-Type": "application/vnd.kafka.json.v2+json",
|
|
46
|
+
}
|
|
47
|
+
payload = {
|
|
48
|
+
"records": [
|
|
49
|
+
{
|
|
50
|
+
"value": {
|
|
51
|
+
"meter_id": meter_id,
|
|
52
|
+
"customer_id": customer_account["id"],
|
|
53
|
+
"total_usage": total_usage
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
response = requests.post(url, headers=headers, json=payload, timeout=10)
|
|
61
|
+
response.raise_for_status()
|
|
62
|
+
return response.json()
|
|
63
|
+
except requests.RequestException as e:
|
|
64
|
+
print(f" Error posting to {url}: {e}")
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
def post_ai_meter_usage(tenant_id: str, device_id: str, meter_id: str, kafka_url: str, portal_url: str, api_key: str, start_time:Optional[datetime]= None, end_time:Optional[datetime]=None) :
|
|
68
|
+
"""
|
|
69
|
+
Posts meter usage to the ai events API.
|
|
70
|
+
Uses tenant_id + device_id in headers and includes customer_id in payload.
|
|
71
|
+
"""
|
|
72
|
+
customer_account = fetch_customer_id(tenant_id, portal_url, api_key)
|
|
73
|
+
if not customer_account:
|
|
74
|
+
print("No customer account available, skipping meter usage post")
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
url = f"{kafka_url}/topics/usage-events"
|
|
78
|
+
headers = {
|
|
79
|
+
"Content-Type": "application/vnd.kafka.json.v2+json",
|
|
80
|
+
}
|
|
81
|
+
if start_time:
|
|
82
|
+
payload = {
|
|
83
|
+
"records": [
|
|
84
|
+
{
|
|
85
|
+
"value": {
|
|
86
|
+
"ai_event": True,
|
|
87
|
+
"meter_id": meter_id,
|
|
88
|
+
"customer_id": customer_account["id"],
|
|
89
|
+
"start_time": start_time
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
else:
|
|
95
|
+
payload = {
|
|
96
|
+
"records": [
|
|
97
|
+
{
|
|
98
|
+
"value": {
|
|
99
|
+
"ai_event": True,
|
|
100
|
+
"meter_id": meter_id,
|
|
101
|
+
"customer_id": customer_account["id"],
|
|
102
|
+
"end_time": end_time
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
response = requests.post(url, headers=headers, json=payload, timeout=10)
|
|
110
|
+
response.raise_for_status()
|
|
111
|
+
return response.json()
|
|
112
|
+
except requests.RequestException as e:
|
|
113
|
+
print(f" Error posting to {url}: {e}")
|
|
114
|
+
return None
|
|
115
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "meter-lib"
|
|
7
|
+
version = "0.0.6"
|
|
8
|
+
description = "A litewave library to collect the customer credit usage"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Sruthi", email = "sruthi@litewave.ai" }
|
|
13
|
+
]
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
dependencies = [
|
|
16
|
+
"requests>=2.28"
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/aiorch/meter-lib"
|
|
28
|
+
Repository = "https://github.com/aiorch/meter-lib"
|
|
29
|
+
Issues = "https://github.com/aiorch/meter-lib/issues"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.28
|
meter_lib-0.0.6/setup.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
# Read the README file for the long description
|
|
4
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
5
|
+
long_description = fh.read()
|
|
6
|
+
|
|
7
|
+
# Read the requirements file
|
|
8
|
+
with open("requirements.txt", "r", encoding="utf-8") as fh:
|
|
9
|
+
requirements = [line.strip() for line in fh.readlines() if line.strip() and not line.startswith("#")]
|
|
10
|
+
|
|
11
|
+
setup(
|
|
12
|
+
name="meter-lib",
|
|
13
|
+
version="0.0.6",
|
|
14
|
+
author="Sruthi R",
|
|
15
|
+
author_email="sruthi@litewave.ai",
|
|
16
|
+
description="A litewave library to collect the customer credit usage",
|
|
17
|
+
long_description=long_description,
|
|
18
|
+
long_description_content_type="text/markdown",
|
|
19
|
+
url="https://github.com/aiorch/meter-lib",
|
|
20
|
+
packages=find_packages(),
|
|
21
|
+
classifiers=[
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Topic :: System :: Logging",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.9",
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Operating System :: OS Independent",
|
|
33
|
+
],
|
|
34
|
+
python_requires=">=3.7",
|
|
35
|
+
install_requires=requirements,
|
|
36
|
+
keywords="customer credit usage ",
|
|
37
|
+
project_urls={
|
|
38
|
+
"Bug Reports": "https://github.com/aiorch/meter-lib/issues",
|
|
39
|
+
"Source": "https://github.com/aiorch/meter-lib",
|
|
40
|
+
"Documentation": "https://github.com/aiorch/meter-lib#readme",
|
|
41
|
+
},
|
|
42
|
+
include_package_data=True,
|
|
43
|
+
zip_safe=False,
|
|
44
|
+
)
|