thuhome-alert 0.1.0__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.
@@ -0,0 +1,22 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+
5
+ build/
6
+ dist/
7
+ wheels/
8
+ *.egg-info
9
+ .eggs/
10
+
11
+ .venv/
12
+ venv/
13
+ env/
14
+
15
+ .coverage
16
+ .coverage.*
17
+ htmlcov/
18
+ .pytest_cache/
19
+ .mypy_cache/
20
+ .ruff_cache/
21
+
22
+ .python-version
@@ -0,0 +1,57 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-08-10
11
+
12
+ ### Added
13
+
14
+ - Initial Python port of the R `thuhomeAlert` package.
15
+ - `Account` dataclass with CSV I/O matching the R `account.csv` schema
16
+ byte-for-byte (zero-friction migration for existing R users).
17
+ - `setup()` function mirroring R's `setup()` — interactive prompts via
18
+ stdlib `getpass`, `force` / `update` semantics, `test_email` round-trip.
19
+ - `login()` — ASP.NET form submission against `myhome.tsinghua.edu.cn`
20
+ with `noLogin` redirect-based success detection.
21
+ - `scrape_balance()` — extracts water (`元` suffix) and electricity
22
+ balances via `#Netweb_Home_*_DetailCtrl1_lblele` DOM IDs.
23
+ - `send_alert()` — SMTP email via stdlib `smtplib` (replaces R's
24
+ `mailR`/rJava dependency). Hardcoded to `smtp.qq.com:587` to match R.
25
+ - `run_monitor()` — full orchestrator: login → scrape → record → alert →
26
+ periodic report. Auto-disables thresholds when portal returns no
27
+ balance for a utility.
28
+ - `schedule_daily()` — **unified** scheduler that auto-detects OS and
29
+ installs via `crontab` (POSIX) or `schtasks.exe` (Windows). Replaces
30
+ the R package's separate `schedule_daily()` / `schedule_daily_win()`.
31
+ - `thuhome-alert` CLI with `setup`, `run`, `schedule`, `migrate`
32
+ subcommands. Console script entry point: `pip install thuhome-alert`.
33
+ - XDG-compliant default paths (`$XDG_CONFIG_HOME/thuhomeAlert/account.csv`,
34
+ `$XDG_DATA_HOME/thuhomeAlert/stat.dorm.csv`).
35
+ - Full test suite (106 tests) with `responses` (HTTP), `pytest-mock`
36
+ (subprocess/SMTP), and `pytest-cov` — runs fully offline, fixes the
37
+ R tests' anti-patterns (real HTTP login, never-called `scrape_balance`).
38
+ - 91% line coverage.
39
+ - CI via GitHub Actions: matrix on Python 3.12/3.13 × Linux/macOS/Windows;
40
+ ruff lint + format check, mypy --strict, pytest with coverage.
41
+ - Release workflow using PyPI Trusted Publisher (OIDC, no API tokens).
42
+
43
+ ### Changed
44
+
45
+ - N/A (initial release).
46
+
47
+ ### Notes
48
+
49
+ - This is a sibling implementation to the R package, which remains
50
+ maintained in parallel at
51
+ [bill0628/thuhome-alert](https://github.com/bill0628/thuhome-alert).
52
+ Both share the same `account.csv` schema so users can switch freely.
53
+ - Runtime dependencies: `requests`, `beautifulsoup4` only (SMTP, cron,
54
+ paths, dates all via stdlib). Down from R's 8 imports + `rJava` JVM.
55
+
56
+ [Unreleased]: https://github.com/bill0628/thuhome-alert-py/compare/v0.1.0...HEAD
57
+ [0.1.0]: https://github.com/bill0628/thuhome-alert-py/releases/tag/v0.1.0
@@ -0,0 +1,2 @@
1
+ YEAR: 2025
2
+ COPYRIGHT HOLDER: Bill Gao
@@ -0,0 +1,318 @@
1
+ Metadata-Version: 2.4
2
+ Name: thuhome-alert
3
+ Version: 0.1.0
4
+ Summary: Monitor water and electricity balance on Tsinghua University's myhome portal (myhome.tsinghua.edu.cn) and send email alerts when balances fall below user-defined thresholds.
5
+ Project-URL: Homepage, https://github.com/bill0628/thuhome-alert-py
6
+ Project-URL: Repository, https://github.com/bill0628/thuhome-alert-py
7
+ Project-URL: Issues, https://github.com/bill0628/thuhome-alert-py/issues
8
+ Project-URL: Changelog, https://github.com/bill0628/thuhome-alert-py/blob/main/CHANGELOG.md
9
+ Author-email: Bill Gao <gaobill@foxmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: alert,balance,dorm,myhome,tsinghua,utilities
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Communications :: Email
22
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
23
+ Classifier: Topic :: System :: Monitoring
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.12
26
+ Requires-Dist: beautifulsoup4>=4.11
27
+ Requires-Dist: requests>=2.28
28
+ Provides-Extra: dev
29
+ Requires-Dist: build>=1.2; extra == 'dev'
30
+ Requires-Dist: mypy>=1.10; extra == 'dev'
31
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
32
+ Requires-Dist: pytest-mock>=3.12; extra == 'dev'
33
+ Requires-Dist: pytest>=8.0; extra == 'dev'
34
+ Requires-Dist: responses>=0.24; extra == 'dev'
35
+ Requires-Dist: ruff>=0.5; extra == 'dev'
36
+ Requires-Dist: types-beautifulsoup4>=4.11; extra == 'dev'
37
+ Requires-Dist: types-requests>=2.28; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # thuhome-alert
41
+
42
+ Monitor water and electricity balance on Tsinghua University's myhome portal
43
+ (`myhome.tsinghua.edu.cn`) and send email alerts when balances fall below
44
+ user-defined thresholds.
45
+
46
+ Python port of the R [`thuhomeAlert`](https://github.com/bill0628/thuhome-alert)
47
+ package. The two share the same `account.csv` schema — existing R users can
48
+ `pip install thuhome-alert` and keep their current config without migration.
49
+
50
+ ## Features
51
+
52
+ - Login to Tsinghua myhome portal
53
+ - Fetch water and electricity balances
54
+ - Support monitoring water-only or electricity-only (for single-account users)
55
+ - Customizable threshold alerts
56
+ - Local history storage (append-only CSV)
57
+ - Schedule daily checks (Linux/macOS `crontab`, Windows Task Scheduler) via a
58
+ single unified `schedule` command that auto-detects the OS
59
+ - CLI: `thuhome-alert setup | run | schedule | migrate`
60
+ - **Two runtime dependencies only** — `requests` and `beautifulsoup4`. SMTP,
61
+ cron, paths, dates all via Python stdlib.
62
+
63
+ ## Installation
64
+
65
+ ```bash
66
+ pip install thuhome-alert
67
+ ```
68
+
69
+ Or with [`pipx`](https://pypa.github.io/pipx/) for an isolated install:
70
+
71
+ ```bash
72
+ pipx install thuhome-alert
73
+ ```
74
+
75
+ Requires Python 3.12+.
76
+
77
+ ## Quick Start
78
+
79
+ ### 1. Configure Account
80
+
81
+ ```bash
82
+ thuhome-alert setup \
83
+ --user your_username \
84
+ --passwd your_password \
85
+ --sender sender@qq.com \
86
+ --token your_smtp_token \
87
+ --recipient recipient@email.com \
88
+ --water 20 \
89
+ --electr 20 \
90
+ --no-test-email
91
+ ```
92
+
93
+ Or from Python:
94
+
95
+ ```python
96
+ import thuhome_alert
97
+
98
+ # Monitor both water and electricity
99
+ thuhome_alert.setup(
100
+ user_name="your_username",
101
+ user_pswd="your_password",
102
+ email_sender="sender@qq.com",
103
+ sender_token="your_smtp_token",
104
+ email_recipient="recipient@email.com",
105
+ water_threshold=20,
106
+ electr_threshold=20,
107
+ test_email=False,
108
+ )
109
+
110
+ # Monitor electricity only (set water_threshold = None)
111
+ thuhome_alert.setup(
112
+ user_name="your_username",
113
+ user_pswd="your_password",
114
+ email_sender="sender@qq.com",
115
+ sender_token="your_smtp_token",
116
+ email_recipient="recipient@email.com",
117
+ water_threshold=None,
118
+ electr_threshold=20,
119
+ test_email=False,
120
+ )
121
+
122
+ # Monitor water only (set electr_threshold = None)
123
+ thuhome_alert.setup(
124
+ user_name="your_username",
125
+ user_pswd="your_password",
126
+ email_sender="sender@qq.com",
127
+ sender_token="your_smtp_token",
128
+ email_recipient="recipient@email.com",
129
+ water_threshold=20,
130
+ electr_threshold=None,
131
+ test_email=False,
132
+ )
133
+ ```
134
+
135
+ Config is saved to `$XDG_CONFIG_HOME/thuhomeAlert/account.csv` (default
136
+ `~/.config/thuhomeAlert/account.csv`, XDG-compliant). Balance history is
137
+ stored at `$XDG_DATA_HOME/thuhomeAlert/stat.dorm.csv` (default
138
+ `~/.local/share/thuhomeAlert/stat.dorm.csv`).
139
+
140
+ ### 2. Run Monitor
141
+
142
+ ```bash
143
+ # Check balances and send alerts if needed
144
+ thuhome-alert run
145
+
146
+ # Check only (no recording, no alerts)
147
+ thuhome-alert run --no-record --no-alert
148
+ ```
149
+
150
+ Or from Python:
151
+
152
+ ```python
153
+ import thuhome_alert
154
+
155
+ # Check balances and send alerts if needed
156
+ thuhome_alert.run_monitor()
157
+
158
+ # Check only (no recording, no alerts)
159
+ thuhome_alert.run_monitor(record=False, alert=False)
160
+ ```
161
+
162
+ ### 3. Schedule Daily Checks
163
+
164
+ ```bash
165
+ # Run daily at 8 AM (auto-detects OS: crontab on Linux/macOS, schtasks on Windows)
166
+ thuhome-alert schedule --time 08:00
167
+
168
+ # Run daily at 6 PM
169
+ thuhome-alert schedule --time 18:00
170
+
171
+ # Uninstall the scheduled job
172
+ thuhome-alert schedule --uninstall
173
+ ```
174
+
175
+ Or from Python:
176
+
177
+ ```python
178
+ import thuhome_alert
179
+
180
+ # Run daily at 8 AM
181
+ thuhome_alert.schedule_daily("08:00")
182
+
183
+ # Run daily at 6 PM
184
+ thuhome_alert.schedule_daily("18:00")
185
+ ```
186
+
187
+ ### 4. Migrate (no-op for compatibility)
188
+
189
+ ```bash
190
+ thuhome-alert migrate
191
+ ```
192
+
193
+ The config schema is already compatible with this version, so this command
194
+ is a no-op that simply reports the config/data paths. It exists to provide
195
+ a forward-compatible migration hook for future versions.
196
+
197
+ ## Configuration
198
+
199
+ The `account.csv` file (CSV format) has these columns — names match the R
200
+ package exactly, including the non-obvious `passwd` and plural `recipients`:
201
+
202
+ | column | meaning |
203
+ |--------------------|-------------------------------------------|
204
+ | `user` | portal username |
205
+ | `passwd` | portal password (note: not `password`) |
206
+ | `sender` | SMTP sender email |
207
+ | `recipients` | alert recipient email (note: plural) |
208
+ | `token` | SMTP password/token |
209
+ | `water_threshold` | RMB; empty disables water monitoring |
210
+ | `electr_threshold` | kWh; empty disables electricity monitoring |
211
+ | `report_freq` | `none` / `weekly` / `monthly` |
212
+ | `last_report` | ISO date string; updated after each report |
213
+
214
+ ### Behavior notes
215
+
216
+ - `setup(update=True)` merges into existing config (only overwrites provided
217
+ fields); `setup(force=True)` overwrites the whole file. With neither, an
218
+ existing config is left untouched.
219
+ - `setup()` is **interactive** — it prompts via `input()` / `getpass.getpass()`
220
+ for any missing param. In non-interactive contexts (cron, tests, agents)
221
+ pass every param explicitly.
222
+ - `run_monitor()` will **auto-disable** a threshold by writing `None` back to
223
+ `account.csv` if the portal returns no balance for that utility (e.g. user
224
+ has no water account). Both thresholds `None` triggers a `RuntimeError`.
225
+ - `report_freq = "weekly"` fires when ≥7 days since `last_report`; `"monthly"`
226
+ fires when the calendar month/year changes. After sending, `last_report`
227
+ is overwritten in `account.csv`.
228
+
229
+ ## SMTP limitation
230
+
231
+ `send_alert()` is **hardcoded to `smtp.qq.com:587`** — only works with a QQ
232
+ Mail sender account. This mirrors the R package's behavior (per its
233
+ `AGENTS.md`) and is intentionally not parameterized. Do not try to change
234
+ this without explicit coordination — the limitation is documented as a known
235
+ issue in both implementations.
236
+
237
+ ## Dependencies
238
+
239
+ - Python >= 3.12
240
+ - `requests` (HTTP client)
241
+ - `beautifulsoup4` (HTML parsing)
242
+
243
+ Optional (only for development):
244
+
245
+ - `pytest`, `pytest-cov`, `pytest-mock`, `responses` (testing)
246
+ - `ruff` (lint + format)
247
+ - `mypy` (typecheck)
248
+ - `build` (sdist/wheel building)
249
+
250
+ ## Development
251
+
252
+ ```bash
253
+ # Clone
254
+ git clone https://github.com/bill0628/thuhome-alert-py.git
255
+ cd thuhome-alert-py
256
+
257
+ # Install with dev dependencies (uses uv)
258
+ uv sync --extra dev
259
+
260
+ # Run tests
261
+ uv run pytest
262
+
263
+ # Lint
264
+ uv run ruff check src/ tests/
265
+ uv run ruff format --check src/ tests/
266
+
267
+ # Typecheck
268
+ uv run mypy --strict src/thuhome_alert
269
+
270
+ # Build sdist + wheel
271
+ uv build
272
+ ```
273
+
274
+ ## Comparison with R package
275
+
276
+ | Aspect | R `thuhomeAlert` | Python `thuhome-alert` |
277
+ |---|---|---|
278
+ | Language | R >= 4.1 | Python >= 3.12 |
279
+ | Runtime deps | 8 packages + `rJava` JVM | 2 packages (`requests`, `beautifulsoup4`) |
280
+ | SMTP | `mailR` (requires JVM) | stdlib `smtplib` |
281
+ | Cron scheduling | `cronR` (Linux/mac) + `taskscheduleR` (Windows), separate functions | stdlib `subprocess` calling `crontab` / `schtasks`, **single unified** `schedule_daily()` |
282
+ | Interactive prompts | `readline()` / `getPass::getPass()` | stdlib `input()` / `getpass.getpass()` |
283
+ | Data frames | `tibble` / `dplyr` | `dataclass(frozen=True)` |
284
+ | Cold start | ~1-2s (R + tidyverse load) | ~150ms |
285
+ | `account.csv` schema | (canonical) | byte-for-byte identical |
286
+ | CLI | none (R functions only) | `thuhome-alert` console script |
287
+
288
+ The two implementations are designed to coexist: users can switch freely
289
+ between them since the config and data files are interchangeable.
290
+
291
+ ## Scraping brittleness
292
+
293
+ The portal scraping depends on ASP.NET form field names and DOM element IDs
294
+ (mirrored exactly from the R implementation):
295
+
296
+ - Login form fields: `net_Default_LoginCtrl1$txtUserName`,
297
+ `net_Default_LoginCtrl1$txtPassword`
298
+ - Login success is detected by absence of `noLogin` in the redirect URL.
299
+ - Balance elements:
300
+ - `#Netweb_Home_water_DetailCtrl1_lblele` (water, suffix `元`)
301
+ - `#Netweb_Home_electricity_DetailCtrl1_lblele` (electricity, numeric)
302
+
303
+ If scraping breaks, these selectors are the first thing to verify against the
304
+ live portal HTML.
305
+
306
+ ## License
307
+
308
+ MIT — see [LICENSE](LICENSE).
309
+
310
+ ## Contributing
311
+
312
+ Issues and Pull Requests are welcome at
313
+ [github.com/bill0628/thuhome-alert-py](https://github.com/bill0628/thuhome-alert-py).
314
+
315
+ ## See also
316
+
317
+ - [thuhome-alert (R package)](https://github.com/bill0628/thuhome-alert) —
318
+ the original R implementation, maintained in parallel.
@@ -0,0 +1,279 @@
1
+ # thuhome-alert
2
+
3
+ Monitor water and electricity balance on Tsinghua University's myhome portal
4
+ (`myhome.tsinghua.edu.cn`) and send email alerts when balances fall below
5
+ user-defined thresholds.
6
+
7
+ Python port of the R [`thuhomeAlert`](https://github.com/bill0628/thuhome-alert)
8
+ package. The two share the same `account.csv` schema — existing R users can
9
+ `pip install thuhome-alert` and keep their current config without migration.
10
+
11
+ ## Features
12
+
13
+ - Login to Tsinghua myhome portal
14
+ - Fetch water and electricity balances
15
+ - Support monitoring water-only or electricity-only (for single-account users)
16
+ - Customizable threshold alerts
17
+ - Local history storage (append-only CSV)
18
+ - Schedule daily checks (Linux/macOS `crontab`, Windows Task Scheduler) via a
19
+ single unified `schedule` command that auto-detects the OS
20
+ - CLI: `thuhome-alert setup | run | schedule | migrate`
21
+ - **Two runtime dependencies only** — `requests` and `beautifulsoup4`. SMTP,
22
+ cron, paths, dates all via Python stdlib.
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install thuhome-alert
28
+ ```
29
+
30
+ Or with [`pipx`](https://pypa.github.io/pipx/) for an isolated install:
31
+
32
+ ```bash
33
+ pipx install thuhome-alert
34
+ ```
35
+
36
+ Requires Python 3.12+.
37
+
38
+ ## Quick Start
39
+
40
+ ### 1. Configure Account
41
+
42
+ ```bash
43
+ thuhome-alert setup \
44
+ --user your_username \
45
+ --passwd your_password \
46
+ --sender sender@qq.com \
47
+ --token your_smtp_token \
48
+ --recipient recipient@email.com \
49
+ --water 20 \
50
+ --electr 20 \
51
+ --no-test-email
52
+ ```
53
+
54
+ Or from Python:
55
+
56
+ ```python
57
+ import thuhome_alert
58
+
59
+ # Monitor both water and electricity
60
+ thuhome_alert.setup(
61
+ user_name="your_username",
62
+ user_pswd="your_password",
63
+ email_sender="sender@qq.com",
64
+ sender_token="your_smtp_token",
65
+ email_recipient="recipient@email.com",
66
+ water_threshold=20,
67
+ electr_threshold=20,
68
+ test_email=False,
69
+ )
70
+
71
+ # Monitor electricity only (set water_threshold = None)
72
+ thuhome_alert.setup(
73
+ user_name="your_username",
74
+ user_pswd="your_password",
75
+ email_sender="sender@qq.com",
76
+ sender_token="your_smtp_token",
77
+ email_recipient="recipient@email.com",
78
+ water_threshold=None,
79
+ electr_threshold=20,
80
+ test_email=False,
81
+ )
82
+
83
+ # Monitor water only (set electr_threshold = None)
84
+ thuhome_alert.setup(
85
+ user_name="your_username",
86
+ user_pswd="your_password",
87
+ email_sender="sender@qq.com",
88
+ sender_token="your_smtp_token",
89
+ email_recipient="recipient@email.com",
90
+ water_threshold=20,
91
+ electr_threshold=None,
92
+ test_email=False,
93
+ )
94
+ ```
95
+
96
+ Config is saved to `$XDG_CONFIG_HOME/thuhomeAlert/account.csv` (default
97
+ `~/.config/thuhomeAlert/account.csv`, XDG-compliant). Balance history is
98
+ stored at `$XDG_DATA_HOME/thuhomeAlert/stat.dorm.csv` (default
99
+ `~/.local/share/thuhomeAlert/stat.dorm.csv`).
100
+
101
+ ### 2. Run Monitor
102
+
103
+ ```bash
104
+ # Check balances and send alerts if needed
105
+ thuhome-alert run
106
+
107
+ # Check only (no recording, no alerts)
108
+ thuhome-alert run --no-record --no-alert
109
+ ```
110
+
111
+ Or from Python:
112
+
113
+ ```python
114
+ import thuhome_alert
115
+
116
+ # Check balances and send alerts if needed
117
+ thuhome_alert.run_monitor()
118
+
119
+ # Check only (no recording, no alerts)
120
+ thuhome_alert.run_monitor(record=False, alert=False)
121
+ ```
122
+
123
+ ### 3. Schedule Daily Checks
124
+
125
+ ```bash
126
+ # Run daily at 8 AM (auto-detects OS: crontab on Linux/macOS, schtasks on Windows)
127
+ thuhome-alert schedule --time 08:00
128
+
129
+ # Run daily at 6 PM
130
+ thuhome-alert schedule --time 18:00
131
+
132
+ # Uninstall the scheduled job
133
+ thuhome-alert schedule --uninstall
134
+ ```
135
+
136
+ Or from Python:
137
+
138
+ ```python
139
+ import thuhome_alert
140
+
141
+ # Run daily at 8 AM
142
+ thuhome_alert.schedule_daily("08:00")
143
+
144
+ # Run daily at 6 PM
145
+ thuhome_alert.schedule_daily("18:00")
146
+ ```
147
+
148
+ ### 4. Migrate (no-op for compatibility)
149
+
150
+ ```bash
151
+ thuhome-alert migrate
152
+ ```
153
+
154
+ The config schema is already compatible with this version, so this command
155
+ is a no-op that simply reports the config/data paths. It exists to provide
156
+ a forward-compatible migration hook for future versions.
157
+
158
+ ## Configuration
159
+
160
+ The `account.csv` file (CSV format) has these columns — names match the R
161
+ package exactly, including the non-obvious `passwd` and plural `recipients`:
162
+
163
+ | column | meaning |
164
+ |--------------------|-------------------------------------------|
165
+ | `user` | portal username |
166
+ | `passwd` | portal password (note: not `password`) |
167
+ | `sender` | SMTP sender email |
168
+ | `recipients` | alert recipient email (note: plural) |
169
+ | `token` | SMTP password/token |
170
+ | `water_threshold` | RMB; empty disables water monitoring |
171
+ | `electr_threshold` | kWh; empty disables electricity monitoring |
172
+ | `report_freq` | `none` / `weekly` / `monthly` |
173
+ | `last_report` | ISO date string; updated after each report |
174
+
175
+ ### Behavior notes
176
+
177
+ - `setup(update=True)` merges into existing config (only overwrites provided
178
+ fields); `setup(force=True)` overwrites the whole file. With neither, an
179
+ existing config is left untouched.
180
+ - `setup()` is **interactive** — it prompts via `input()` / `getpass.getpass()`
181
+ for any missing param. In non-interactive contexts (cron, tests, agents)
182
+ pass every param explicitly.
183
+ - `run_monitor()` will **auto-disable** a threshold by writing `None` back to
184
+ `account.csv` if the portal returns no balance for that utility (e.g. user
185
+ has no water account). Both thresholds `None` triggers a `RuntimeError`.
186
+ - `report_freq = "weekly"` fires when ≥7 days since `last_report`; `"monthly"`
187
+ fires when the calendar month/year changes. After sending, `last_report`
188
+ is overwritten in `account.csv`.
189
+
190
+ ## SMTP limitation
191
+
192
+ `send_alert()` is **hardcoded to `smtp.qq.com:587`** — only works with a QQ
193
+ Mail sender account. This mirrors the R package's behavior (per its
194
+ `AGENTS.md`) and is intentionally not parameterized. Do not try to change
195
+ this without explicit coordination — the limitation is documented as a known
196
+ issue in both implementations.
197
+
198
+ ## Dependencies
199
+
200
+ - Python >= 3.12
201
+ - `requests` (HTTP client)
202
+ - `beautifulsoup4` (HTML parsing)
203
+
204
+ Optional (only for development):
205
+
206
+ - `pytest`, `pytest-cov`, `pytest-mock`, `responses` (testing)
207
+ - `ruff` (lint + format)
208
+ - `mypy` (typecheck)
209
+ - `build` (sdist/wheel building)
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ # Clone
215
+ git clone https://github.com/bill0628/thuhome-alert-py.git
216
+ cd thuhome-alert-py
217
+
218
+ # Install with dev dependencies (uses uv)
219
+ uv sync --extra dev
220
+
221
+ # Run tests
222
+ uv run pytest
223
+
224
+ # Lint
225
+ uv run ruff check src/ tests/
226
+ uv run ruff format --check src/ tests/
227
+
228
+ # Typecheck
229
+ uv run mypy --strict src/thuhome_alert
230
+
231
+ # Build sdist + wheel
232
+ uv build
233
+ ```
234
+
235
+ ## Comparison with R package
236
+
237
+ | Aspect | R `thuhomeAlert` | Python `thuhome-alert` |
238
+ |---|---|---|
239
+ | Language | R >= 4.1 | Python >= 3.12 |
240
+ | Runtime deps | 8 packages + `rJava` JVM | 2 packages (`requests`, `beautifulsoup4`) |
241
+ | SMTP | `mailR` (requires JVM) | stdlib `smtplib` |
242
+ | Cron scheduling | `cronR` (Linux/mac) + `taskscheduleR` (Windows), separate functions | stdlib `subprocess` calling `crontab` / `schtasks`, **single unified** `schedule_daily()` |
243
+ | Interactive prompts | `readline()` / `getPass::getPass()` | stdlib `input()` / `getpass.getpass()` |
244
+ | Data frames | `tibble` / `dplyr` | `dataclass(frozen=True)` |
245
+ | Cold start | ~1-2s (R + tidyverse load) | ~150ms |
246
+ | `account.csv` schema | (canonical) | byte-for-byte identical |
247
+ | CLI | none (R functions only) | `thuhome-alert` console script |
248
+
249
+ The two implementations are designed to coexist: users can switch freely
250
+ between them since the config and data files are interchangeable.
251
+
252
+ ## Scraping brittleness
253
+
254
+ The portal scraping depends on ASP.NET form field names and DOM element IDs
255
+ (mirrored exactly from the R implementation):
256
+
257
+ - Login form fields: `net_Default_LoginCtrl1$txtUserName`,
258
+ `net_Default_LoginCtrl1$txtPassword`
259
+ - Login success is detected by absence of `noLogin` in the redirect URL.
260
+ - Balance elements:
261
+ - `#Netweb_Home_water_DetailCtrl1_lblele` (water, suffix `元`)
262
+ - `#Netweb_Home_electricity_DetailCtrl1_lblele` (electricity, numeric)
263
+
264
+ If scraping breaks, these selectors are the first thing to verify against the
265
+ live portal HTML.
266
+
267
+ ## License
268
+
269
+ MIT — see [LICENSE](LICENSE).
270
+
271
+ ## Contributing
272
+
273
+ Issues and Pull Requests are welcome at
274
+ [github.com/bill0628/thuhome-alert-py](https://github.com/bill0628/thuhome-alert-py).
275
+
276
+ ## See also
277
+
278
+ - [thuhome-alert (R package)](https://github.com/bill0628/thuhome-alert) —
279
+ the original R implementation, maintained in parallel.