feat/hoymiles
needs attention4de48f3 · fullPR #272reviewed 2026-07-08 02:23 UTC5H · 8M · 9L · 4I- Purpose
- Port the Hoymiles solar integration (largest legacy fleet, 311 devices) to the v2 metrics engine at 5m/15m native granularity, replacing the legacy 15m→3×5m triplication and curve-smearing.
- Goal
- Production-ready Hoymiles metrics + connection workers with CDK stack, live-validated against 3 representative sites in the branch preview.
- Sub-goals
- SG-1: API probe + worker package (metrics + connection + translation); 60 unit tests
- SG-2: CDK IntegrationHoymilesLambdaStack + main.ts + coordinator grants
- SG-3: Preview data plane (catalog seed, D2 secrets, snapshot-scoped D3, ARN seed)
- SG-4: Live Site-SFN validation + Tinybird + parity vs prod-ddb
- SG-N: Framework learnings #12–#20 → scope.md; hoymiles Tier-3 → integrations CLAUDE.md
- What
- New Hoymiles worker package (metrics.lambda.ts + connection.lambda.ts + points-to-batu.ts translation layer + hoymiles-client.ts); CDK stack (IntegrationHoymilesLambdaStack, 120s/512MB metrics + 30s/256MB connection, IAM scoped to GetSecretValue {env}/hoymiles/*); hoymiles manifest (api_key credential, ['5m','15m','1d'] solar, ['1d'] grid); migration scripts (seed-migration-sites.ts); framework learnings documentation.
- Why
- Hoymiles is the largest fleet (311 devices) of the 12 legacy brand ports. This is the second port (after victron) and validates the end-to-end recipe for the remaining 10 brands.
- Areas
- services/metrics/integrations/hoymiles/+1100−0infra/cdk/src/stacks/services/metrics/integrations/hoymiles/+150−0packages/integration-manifests/src/manifests/+60−0scripts/metrics/+80−0infra/cdk/src/app/main.ts + coordinator+25−0.branch/ (scope, intent, tools)+400−0
- Blast
- 29 files in feat/hoymiles itself (stacked on feat/int-base #268); new Lambda stack; seeds for hoymiles only. The 12-manifest and seed changes are on the base branch.
Findings · 27
correctness7
Credential field drift: seed stores 'token', worker reads 'api_key'
packages/database/src/seed-metrics-catalog.ts:159
FE wizard onboarding stores key under 'token'; worker reads parsed['api_key'] → MissingSecret on every collection. Fix: change name to 'api_key' in the DB seed row.
Retention pre-check off-by-one: allows the 16th day back (not 15)
services/metrics/integrations/hoymiles/src/handlers/metrics.lambda.ts:169
probeFailurePayload: credentialValid:false for transient failures (timeout, rate-limit)
services/metrics/integrations/hoymiles/src/handlers/connection.lambda.ts:97
Cadence detection skips mismatch when every day has ≤1 point
services/metrics/integrations/hoymiles/src/translation/points-to-batu.ts:248
gpw bogus-plant: reachable:true even when defaultLooking is set
services/metrics/integrations/hoymiles/src/handlers/connection.lambda.ts:119
Single per-day fetch failure aborts entire multi-day intraday window
services/metrics/integrations/hoymiles/src/handlers/metrics.lambda.ts:203
Cadence tie-breaking non-deterministic at the pooled-day level
services/metrics/integrations/hoymiles/src/translation/points-to-batu.ts:251
security3
API key sent as URL query param — logged by HTTP infrastructure
services/metrics/integrations/hoymiles/src/engine/hoymiles-client.ts:61
IAM wildcard {env}/hoymiles/* also covers the /portal/ credential path
infra/cdk/src/stacks/services/metrics/integrations/hoymiles/iam.ts:43
copy-legacy-secrets.ts: error messages may partially expose secret-adjacent data
scripts/metrics/copy-legacy-secrets.ts:128
conventions3
externalVariableId carries legacy vendor channel names, not canonical keys
packages/integration-manifests/src/manifests/hoymiles.ts:53
INTEGRATION_STANDARDS §8 references descriptor.ts (acknowledged dead)
services/metrics/integrations/INTEGRATION_STANDARDS.md
Translation layer and handler error boundaries are FCIS-compliant
services/metrics/integrations/hoymiles/src/translation/points-to-batu.ts
tests7
Status code string parsing ('0'/'1'/'2') untested at HTTP client layer
services/metrics/integrations/hoymiles/src/__tests__/metrics-handler.test.ts:255
Retention window 1d fallback path not tested
services/metrics/integrations/hoymiles/src/__tests__/metrics-handler.test.ts:139
enumerateLocalDays: fall-back DST transition untested
services/metrics/integrations/hoymiles/src/__tests__/points-to-batu.test.ts:76
gpw unknown-plant caveat: tested at pure-function level only, not through handler
services/metrics/integrations/hoymiles/src/__tests__/connection-handler.test.ts:137
Cadence detection multi-day mixed evidence not covered
services/metrics/integrations/hoymiles/src/__tests__/points-to-batu.test.ts:130
localWallTimeToUtcMs fall-back DST hour (ambiguous wall time) not tested
services/metrics/integrations/hoymiles/src/__tests__/points-to-batu.test.ts:67
dominantSpacingMinutes tie-breaking (equal votes) not tested
services/metrics/integrations/hoymiles/src/__tests__/points-to-batu.test.ts:87
improvement7
Billing-critical timezone helpers copy-pasted verbatim across brands
services/metrics/integrations/hoymiles/src/translation/points-to-batu.ts:65
Move to @batu/metrics-engine/lib/local-time.ts — 10+ brand ports coming, any DST fix must apply to N copies.
InvocationResult envelope helpers triplicated across integrations
services/metrics/integrations/hoymiles/src/handlers/metrics.lambda.ts:309
resolveChannels duplicated across 3 integrations without sharing
services/metrics/integrations/hoymiles/src/handlers/metrics.lambda.ts:79
Two intraday retention checks are redundant
services/metrics/integrations/hoymiles/src/handlers/metrics.lambda.ts:167
allPoints flatMap allocates 4320-element array for an emptiness check
services/metrics/integrations/hoymiles/src/translation/points-to-batu.ts:235
secretsClient() singleton pattern will be copied to 10+ more brands
services/metrics/integrations/hoymiles/src/engine/hoymiles-client.ts:263
powerDaysToBatu hardcodes 'solar_generation' key in units object
services/metrics/integrations/hoymiles/src/translation/points-to-batu.ts:279