Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 27 additions & 53 deletions .github/workflows/sync-cloud-run-env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,17 @@ jobs:
IBKR_MIN_RESERVED_CASH_USD: ${{ vars.IBKR_MIN_RESERVED_CASH_USD }}
IBKR_RESERVED_CASH_RATIO: ${{ vars.IBKR_RESERVED_CASH_RATIO }}
IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD: ${{ vars.IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD }}
CRISIS_ALERT_GOOGLE_VOICE_TO: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_TO }}
CRISIS_ALERT_SMTP_FROM: ${{ vars.CRISIS_ALERT_SMTP_FROM }}
CRISIS_ALERT_SMTP_HOST: ${{ vars.CRISIS_ALERT_SMTP_HOST }}
CRISIS_ALERT_SMTP_PORT: ${{ vars.CRISIS_ALERT_SMTP_PORT }}
CRISIS_ALERT_SMTP_USERNAME: ${{ vars.CRISIS_ALERT_SMTP_USERNAME }}
CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME: ${{ vars.CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME }}
CRISIS_ALERT_SMTP_STARTTLS: ${{ vars.CRISIS_ALERT_SMTP_STARTTLS }}
CRISIS_ALERT_SMTP_SSL: ${{ vars.CRISIS_ALERT_SMTP_SSL }}
CRISIS_ALERT_GOOGLE_VOICE_GATEWAY: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_GATEWAY }}
CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER }}
CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME }}
# Strategy-owned defaults continue to come from UsEquityStrategies; this workflow only syncs platform/runtime inputs.
EXECUTION_REPORT_GCS_URI: ${{ vars.EXECUTION_REPORT_GCS_URI }}
IB_GATEWAY_ZONE: ${{ vars.IB_GATEWAY_ZONE }}
IB_GATEWAY_IP_MODE: ${{ vars.IB_GATEWAY_IP_MODE }}
GLOBAL_TELEGRAM_CHAT_ID: ${{ vars.GLOBAL_TELEGRAM_CHAT_ID }}
NOTIFY_LANG: ${{ vars.NOTIFY_LANG }}
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
CRISIS_ALERT_SMTP_PASSWORD: ${{ secrets.CRISIS_ALERT_SMTP_PASSWORD }}
CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD: ${{ secrets.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD }}
steps:
- name: Check whether env sync is enabled
id: config
Expand Down Expand Up @@ -272,8 +267,16 @@ jobs:
"IB_CLIENT_ID"
"IB_GATEWAY_INSTANCE_NAME"
"IB_GATEWAY_MODE"
"CRISIS_ALERT_GOOGLE_VOICE_TO"
"CRISIS_ALERT_SMTP_FROM"
"CRISIS_ALERT_SMTP_HOST"
"CRISIS_ALERT_SMTP_PORT"
"CRISIS_ALERT_SMTP_USERNAME"
"CRISIS_ALERT_SMTP_PASSWORD"
"CRISIS_ALERT_SMTP_STARTTLS"
"CRISIS_ALERT_SMTP_SSL"
)
remove_secret_vars=()
remove_secret_vars=("CRISIS_ALERT_SMTP_PASSWORD")

if [ -n "${TELEGRAM_TOKEN_SECRET_NAME:-}" ]; then
secret_pairs+=("TELEGRAM_TOKEN=${TELEGRAM_TOKEN_SECRET_NAME}:latest")
Expand All @@ -282,14 +285,15 @@ jobs:
remove_secret_vars+=("TELEGRAM_TOKEN")
fi

if [ -n "${CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME:-}" ]; then
secret_pairs+=("CRISIS_ALERT_SMTP_PASSWORD=${CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME}:latest")
elif [ -n "${CRISIS_ALERT_SMTP_PASSWORD:-}" ]; then
env_pairs+=("CRISIS_ALERT_SMTP_PASSWORD=${CRISIS_ALERT_SMTP_PASSWORD}")
remove_secret_vars+=("CRISIS_ALERT_SMTP_PASSWORD")
if [ -n "${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME:-}" ]; then
secret_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD=${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME}:latest")
remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD")
elif [ -n "${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD:-}" ]; then
env_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD=${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD}")
remove_secret_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD")
else
remove_env_vars+=("CRISIS_ALERT_SMTP_PASSWORD")
remove_secret_vars+=("CRISIS_ALERT_SMTP_PASSWORD")
remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD")
remove_secret_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD")
fi

if [ -n "${IB_GATEWAY_ZONE:-}" ]; then
Expand Down Expand Up @@ -364,46 +368,16 @@ jobs:
remove_env_vars+=("IBKR_RESERVED_CASH_RATIO")
fi

if [ -n "${CRISIS_ALERT_GOOGLE_VOICE_TO:-}" ]; then
env_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_TO=${CRISIS_ALERT_GOOGLE_VOICE_TO}")
if [ -n "${CRISIS_ALERT_GOOGLE_VOICE_GATEWAY:-}" ]; then
env_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_GATEWAY=${CRISIS_ALERT_GOOGLE_VOICE_GATEWAY}")
else
remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_TO")
remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GATEWAY")
fi

if [ -n "${CRISIS_ALERT_SMTP_FROM:-}" ]; then
env_pairs+=("CRISIS_ALERT_SMTP_FROM=${CRISIS_ALERT_SMTP_FROM}")
if [ -n "${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER:-}" ]; then
env_pairs+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER=${CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER}")
else
remove_env_vars+=("CRISIS_ALERT_SMTP_FROM")
fi

if [ -n "${CRISIS_ALERT_SMTP_HOST:-}" ]; then
env_pairs+=("CRISIS_ALERT_SMTP_HOST=${CRISIS_ALERT_SMTP_HOST}")
else
remove_env_vars+=("CRISIS_ALERT_SMTP_HOST")
fi

if [ -n "${CRISIS_ALERT_SMTP_PORT:-}" ]; then
env_pairs+=("CRISIS_ALERT_SMTP_PORT=${CRISIS_ALERT_SMTP_PORT}")
else
remove_env_vars+=("CRISIS_ALERT_SMTP_PORT")
fi

if [ -n "${CRISIS_ALERT_SMTP_USERNAME:-}" ]; then
env_pairs+=("CRISIS_ALERT_SMTP_USERNAME=${CRISIS_ALERT_SMTP_USERNAME}")
else
remove_env_vars+=("CRISIS_ALERT_SMTP_USERNAME")
fi

if [ -n "${CRISIS_ALERT_SMTP_STARTTLS:-}" ]; then
env_pairs+=("CRISIS_ALERT_SMTP_STARTTLS=${CRISIS_ALERT_SMTP_STARTTLS}")
else
remove_env_vars+=("CRISIS_ALERT_SMTP_STARTTLS")
fi

if [ -n "${CRISIS_ALERT_SMTP_SSL:-}" ]; then
env_pairs+=("CRISIS_ALERT_SMTP_SSL=${CRISIS_ALERT_SMTP_SSL}")
else
remove_env_vars+=("CRISIS_ALERT_SMTP_SSL")
remove_env_vars+=("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER")
fi

if [ -n "${EXECUTION_REPORT_GCS_URI:-}" ]; then
Expand Down
32 changes: 10 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,9 @@ For IBKR, keep `paper` as a single account-group entry. If you later add live ac
| `TELEGRAM_TOKEN` | Yes | Telegram bot token. For Cloud Run, prefer a Secret Manager reference instead of a literal env var. |
| `GLOBAL_TELEGRAM_CHAT_ID` | Yes | Telegram chat ID used by this service. |
| `NOTIFY_LANG` | No | `en` (default) or `zh` |
| `CRISIS_ALERT_GOOGLE_VOICE_TO` | No | Comma/semicolon/newline-separated Google Voice SMS gateway recipients, usually ending in `@txt.voice.google.com`. |
| `CRISIS_ALERT_SMTP_FROM` | No | SMTP sender address for Google Voice alerts. |
| `CRISIS_ALERT_SMTP_HOST` | No | SMTP host for Google Voice alerts. |
| `CRISIS_ALERT_SMTP_PORT` | No | SMTP port; defaults to `587`. |
| `CRISIS_ALERT_SMTP_USERNAME` | No | Optional SMTP username. |
| `CRISIS_ALERT_SMTP_PASSWORD` | No | Optional SMTP password. For Cloud Run, prefer `CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME` in env sync. |
| `CRISIS_ALERT_SMTP_STARTTLS` | No | Whether to use STARTTLS; defaults to `true`. |
| `CRISIS_ALERT_SMTP_SSL` | No | Whether to use implicit SMTP SSL; defaults to `false`. |
| `CRISIS_ALERT_GOOGLE_VOICE_GATEWAY` | No | Comma/semicolon/newline-separated Google Voice SMS gateway recipients, usually ending in `@txt.voice.google.com`. |
| `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER` | No | Gmail address used to send Google Voice gateway alerts. |
| `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD` | No | Gmail App Password for Google Voice gateway alerts. For Cloud Run, prefer `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME` in env sync. |

The selected account-group entry must provide at least:

Expand Down Expand Up @@ -215,7 +210,7 @@ Current behavior is fail-fast:
- missing account-group config source → startup error
- missing key fields in the selected group (`ib_gateway_instance_name`, `ib_gateway_mode`, `ib_client_id`) → startup error

When `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` includes the `crisis_response_shadow` plugin, the normal strategy-cycle Telegram message still includes the compact plugin line. If the plugin signal escalates beyond `no_action` (for example `canonical_route=true_crisis`, `suggested_action=defend`/`blocked`, or `would_trade_if_enabled=true`), the service also sends an independent crisis Google Voice notification when the `CRISIS_ALERT_*` SMTP settings are complete.
When `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` includes the `crisis_response_shadow` plugin, the normal strategy-cycle Telegram message still includes the compact plugin line. If the plugin signal escalates beyond `no_action` (for example `canonical_route=true_crisis`, `suggested_action=defend`/`blocked`, or `would_trade_if_enabled=true`), the service also sends an independent crisis Google Voice notification when the `CRISIS_ALERT_GOOGLE_VOICE_*` settings are complete.
Google Voice alert results are written into the runtime report. Duplicate suppression uses stable plugin alert keys and stores markers under `STRATEGY_PLUGIN_ALERT_STATE_GCS_URI` when set, otherwise `EXECUTION_REPORT_GCS_URI`, with a local `/tmp` marker fallback.

### GitHub-managed Cloud Run env sync
Expand All @@ -233,12 +228,11 @@ Recommended setup:
- `ACCOUNT_GROUP` (recommended: `paper`)
- `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME`
- Optional: `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON`, `IBKR_MIN_RESERVED_CASH_USD`, `IBKR_RESERVED_CASH_RATIO`, `IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD`
- Optional crisis Google Voice alerts: `CRISIS_ALERT_GOOGLE_VOICE_TO`, `CRISIS_ALERT_SMTP_FROM`, `CRISIS_ALERT_SMTP_HOST`, `CRISIS_ALERT_SMTP_PORT`, `CRISIS_ALERT_SMTP_USERNAME`, `CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME`, `CRISIS_ALERT_SMTP_STARTTLS`, `CRISIS_ALERT_SMTP_SSL`
- `GLOBAL_TELEGRAM_CHAT_ID`
- `NOTIFY_LANG`
- **Repository Secrets**
- `TELEGRAM_TOKEN` (fallback only when `TELEGRAM_TOKEN_SECRET_NAME` is not set)
- `CRISIS_ALERT_SMTP_PASSWORD` (fallback only when `CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME` is not set)
- `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD` (fallback only when `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME` is not set)
- **Optional transition Variables**
- `IB_GATEWAY_ZONE`
- `IB_GATEWAY_IP_MODE`
Expand Down Expand Up @@ -364,14 +358,9 @@ IBKR 账户
| `TELEGRAM_TOKEN` | 是 | Telegram 机器人 Token。Cloud Run 上更推荐走 Secret Manager 引用,不要直接写成明文 env。 |
| `GLOBAL_TELEGRAM_CHAT_ID` | 是 | 这个服务使用的 Telegram Chat ID。 |
| `NOTIFY_LANG` | 否 | `en`(默认)或 `zh` |
| `CRISIS_ALERT_GOOGLE_VOICE_TO` | 否 | Google Voice 短信网关收件人,通常以 `@txt.voice.google.com` 结尾,支持逗号、分号或换行分隔。 |
| `CRISIS_ALERT_SMTP_FROM` | 否 | Google Voice 告警的 SMTP 发件人。 |
| `CRISIS_ALERT_SMTP_HOST` | 否 | Google Voice 告警的 SMTP host。 |
| `CRISIS_ALERT_SMTP_PORT` | 否 | SMTP 端口,默认 `587`。 |
| `CRISIS_ALERT_SMTP_USERNAME` | 否 | 可选 SMTP 用户名。 |
| `CRISIS_ALERT_SMTP_PASSWORD` | 否 | 可选 SMTP 密码。Cloud Run env sync 建议配置 `CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME`。 |
| `CRISIS_ALERT_SMTP_STARTTLS` | 否 | 是否使用 STARTTLS,默认 `true`。 |
| `CRISIS_ALERT_SMTP_SSL` | 否 | 是否使用隐式 SMTP SSL,默认 `false`。 |
| `CRISIS_ALERT_GOOGLE_VOICE_GATEWAY` | 否 | Google Voice 短信网关收件人,通常以 `@txt.voice.google.com` 结尾,支持逗号、分号或换行分隔。 |
| `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER` | 否 | Google Voice 通知使用的 Gmail 地址。 |
| `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD` | 否 | Google Voice 通知使用的 Gmail App Password。Cloud Run env sync 建议配置 `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME`。 |

选中的账号组配置里,至少要有:

Expand Down Expand Up @@ -433,7 +422,7 @@ IB_GATEWAY_IP_MODE=internal
- 没有账号组配置来源 → 启动直接报错
- 选中的账号组缺少关键字段(`ib_gateway_instance_name`、`ib_gateway_mode`、`ib_client_id`)→ 启动直接报错

如果 `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` 挂载了 `crisis_response_shadow` 插件,常规策略周期 Telegram 仍会包含插件摘要行。当插件信号升级到非 `no_action`(例如 `canonical_route=true_crisis`、`suggested_action=defend`/`blocked`,或 `would_trade_if_enabled=true`)时,只要 `CRISIS_ALERT_*` SMTP 配置完整,服务还会额外发一封独立 Google Voice 危机通知。
如果 `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` 挂载了 `crisis_response_shadow` 插件,常规策略周期 Telegram 仍会包含插件摘要行。当插件信号升级到非 `no_action`(例如 `canonical_route=true_crisis`、`suggested_action=defend`/`blocked`,或 `would_trade_if_enabled=true`)时,只要 `CRISIS_ALERT_GOOGLE_VOICE_*` 配置完整,服务还会额外发送一条独立 Google Voice 危机通知。
Google Voice 告警结果会写入 runtime report。重复发送抑制使用稳定的插件告警 key;如配置了 `STRATEGY_PLUGIN_ALERT_STATE_GCS_URI` 则写入该前缀,否则复用 `EXECUTION_REPORT_GCS_URI`,并有本地 `/tmp` marker fallback。

### GitHub 统一管理 Cloud Run 环境变量
Expand All @@ -451,12 +440,11 @@ Google Voice 告警结果会写入 runtime report。重复发送抑制使用稳
- `ACCOUNT_GROUP`(建议设为 `paper`)
- `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME`
- 可选:`IBKR_STRATEGY_PLUGIN_MOUNTS_JSON`、`IBKR_MIN_RESERVED_CASH_USD`、`IBKR_RESERVED_CASH_RATIO`、`IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD`
- 可选危机插件 Google Voice 告警:`CRISIS_ALERT_GOOGLE_VOICE_TO`、`CRISIS_ALERT_SMTP_FROM`、`CRISIS_ALERT_SMTP_HOST`、`CRISIS_ALERT_SMTP_PORT`、`CRISIS_ALERT_SMTP_USERNAME`、`CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME`、`CRISIS_ALERT_SMTP_STARTTLS`、`CRISIS_ALERT_SMTP_SSL`
- `GLOBAL_TELEGRAM_CHAT_ID`
- `NOTIFY_LANG`
- **仓库级 Secrets**
- `TELEGRAM_TOKEN`(仅在没设置 `TELEGRAM_TOKEN_SECRET_NAME` 时作为 fallback)
- `CRISIS_ALERT_SMTP_PASSWORD`(仅在没设置 `CRISIS_ALERT_SMTP_PASSWORD_SECRET_NAME` 时作为 fallback)
- `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD`(仅在没设置 `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME` 时作为 fallback)
- **可选过渡 Variables**
- `IB_GATEWAY_ZONE`
- `IB_GATEWAY_IP_MODE`
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
flask
gunicorn
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@4305a3c01151ced7e78b39519959444309326cd7
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@03e23c5d6a620ca9ecb763550452f9ca3870481a
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@34a2c74deb7aafef0f6f4b278444d7b0efb76794
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@861eaedc1caecf1e33ff12f55bbe9af87a221df1
pandas
numpy
requests
Expand Down
48 changes: 12 additions & 36 deletions runtime_config_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,9 @@ class PlatformRuntimeSettings:
tg_chat_id: str | None = None
notify_lang: str = "en"
strategy_plugin_mounts_json: str | None = None
crisis_alert_google_voice_to: tuple[str, ...] = ()
crisis_alert_smtp_from: str | None = None
crisis_alert_smtp_host: str | None = None
crisis_alert_smtp_port: int = 587
crisis_alert_smtp_username: str | None = None
crisis_alert_smtp_password: str | None = None
crisis_alert_smtp_starttls: bool = True
crisis_alert_smtp_ssl: bool = False
crisis_alert_google_voice_gateway: tuple[str, ...] = ()
crisis_alert_google_voice_gmail_user: str | None = None
crisis_alert_google_voice_gmail_app_password: str | None = None
runtime_target: RuntimeTarget | None = None


Expand Down Expand Up @@ -188,14 +183,15 @@ def load_platform_runtime_settings(
os.getenv("IBKR_STRATEGY_PLUGIN_MOUNTS_JSON")
or os.getenv("STRATEGY_PLUGIN_MOUNTS_JSON")
),
crisis_alert_google_voice_to=split_env_list(os.getenv("CRISIS_ALERT_GOOGLE_VOICE_TO")),
crisis_alert_smtp_from=first_non_empty(os.getenv("CRISIS_ALERT_SMTP_FROM")),
crisis_alert_smtp_host=first_non_empty(os.getenv("CRISIS_ALERT_SMTP_HOST")),
crisis_alert_smtp_port=resolve_positive_int_env("CRISIS_ALERT_SMTP_PORT", default=587),
crisis_alert_smtp_username=first_non_empty(os.getenv("CRISIS_ALERT_SMTP_USERNAME")),
crisis_alert_smtp_password=first_non_empty(os.getenv("CRISIS_ALERT_SMTP_PASSWORD")),
crisis_alert_smtp_starttls=resolve_bool_env("CRISIS_ALERT_SMTP_STARTTLS", default=True),
crisis_alert_smtp_ssl=resolve_bool_value(os.getenv("CRISIS_ALERT_SMTP_SSL")),
crisis_alert_google_voice_gateway=split_env_list(
os.getenv("CRISIS_ALERT_GOOGLE_VOICE_GATEWAY")
),
crisis_alert_google_voice_gmail_user=first_non_empty(
os.getenv("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER")
),
crisis_alert_google_voice_gmail_app_password=first_non_empty(
os.getenv("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD")
),
runtime_target=runtime_target,
)

Expand Down Expand Up @@ -226,26 +222,6 @@ def resolve_optional_ratio_env(name: str) -> float | None:
return value


def resolve_bool_env(name: str, *, default: bool) -> bool:
raw_value = os.getenv(name)
if raw_value is None or str(raw_value).strip() == "":
return bool(default)
return resolve_bool_value(raw_value)


def resolve_positive_int_env(name: str, *, default: int) -> int:
raw_value = os.getenv(name)
if raw_value is None or str(raw_value).strip() == "":
return int(default)
try:
value = int(raw_value)
except (TypeError, ValueError):
raise ValueError(f"{name} must be a positive integer, got {raw_value!r}") from None
if value <= 0:
raise ValueError(f"{name} must be a positive integer, got {raw_value!r}")
return value


def split_env_list(raw_value: str | None) -> tuple[str, ...]:
if raw_value is None:
return ()
Expand Down
11 changes: 3 additions & 8 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,9 @@ def load_strategy_module(**env_overrides):
),
"IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME": None,
"GLOBAL_TELEGRAM_CHAT_ID": None,
"CRISIS_ALERT_GOOGLE_VOICE_TO": None,
"CRISIS_ALERT_SMTP_FROM": None,
"CRISIS_ALERT_SMTP_HOST": None,
"CRISIS_ALERT_SMTP_PORT": None,
"CRISIS_ALERT_SMTP_USERNAME": None,
"CRISIS_ALERT_SMTP_PASSWORD": None,
"CRISIS_ALERT_SMTP_STARTTLS": None,
"CRISIS_ALERT_SMTP_SSL": None,
"CRISIS_ALERT_GOOGLE_VOICE_GATEWAY": None,
"CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER": None,
"CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD": None,
}
defaults.update(env_overrides)
if "RUNTIME_TARGET_JSON" not in defaults:
Expand Down
Loading