feat/powerradar
needs attentionviewing older commit230784f · fullPR #283reviewed 2026-07-10 00:15 UTC2H · 11M · 14L · 7I- Purpose
- Port 31 legacy PowerRadar (Centrica/Smarter) energy-meter devices from the old batu-monorepo system into the v2 metrics engine (Tinybird), enabling CFE bill cross-validation and multi-site dashboards for this brand.
- Goal
- CSV-first intake worker serving the PowerRadar hourly portfolio CSV format (latin-1, DST-aware, kW→W) plus the D3 unblock (powerradarChannel passthrough), CDK stack, and 91/91 parity day validation.
- Sub-goals
- SG-1: CSV worker + parser + 37 unit tests (DST, latin-1, channel mapping)
- SG-2: CDK stack (intake bucket + Lambda) + coordinator grant + synth clean
- SG-3: Preview data plane + powerradarChannel D3 unblock + real CSV characterization
- SG-4: Live e2e intake → Tinybird → 91/91 parity days (60 CDMX exact + 31 Tijuana ≤0.0112%)
- SG-N: 8 framework learnings → scope.md + integrations CLAUDE.md Tier-3 section
- SG-6: Rebase onto §9 multi-granularity base (groupChannelsByGranularity + widened Granularity union)
- SG-7: §9 handler conformance — grouped fetch shape, resilient non-1h skips, zero-point drops, +5 tests (43 total)
- What
- New CSV-first Lambda intake worker for PowerRadar (Centrica), new S3 intake bucket CDK stack, powerradarChannel passthrough in asset-provisioning cross-domain shell, 11 new integration manifests for future brand ports, legacy registry/secrets migration scripts, metric domain query updates, seed catalog updates.
- Why
- PowerRadar is a portfolio-CSV vendor with 31 legacy devices in batu-monorepo. This is one of 12 planned brand ports to the v2 metrics engine.
- Areas
- services/metrics/integrations/powerradar/+1013−0scripts/metrics/+1285−0infra/cdk/src/stacks/services/metrics/integrations/powerradar/+222−0packages/integration-manifests/src/+699−0domains/cross-domain/src/+45−8domains/metrics/src/+369−36packages/database/src/+178−14infra/cdk/src/app/+24−3services/metrics/engine/src/+87−15
- Blast
- 77 files, +6645/−201. New CDK stack isolated (no existing stack changes). Cross-domain shell change is additive (optional field, no break). 11 new manifests are pure data. Metric domain changes are schema/query updates.
Findings · 34
correctness3
NaN Date when window.from/to is non-ISO — window filtering silently disabled
services/metrics/integrations/powerradar/src/handlers/metrics.lambda.ts:127
Unsafe sources as MetricChannelSpec[] cast — missing granularity → spurious unsupported-granularity skip
services/metrics/integrations/powerradar/src/handlers/metrics.lambda.ts:69
Fall-back disambiguation occurrence counter semantics correct — third occurrence correctly dropped
security7
csvS3Key not validated against a prefix — unscoped s3:GetObject covers whole intake bucket
services/metrics/integrations/powerradar/src/handlers/metrics.lambda.ts:101
A manipulated SFN start input could read any object any tenant uploaded. Guard: csvS3Key.startsWith('powerradar/uploads/').
CORS allowedHeaders wildcard includes Authorization — scope to presigned-PUT required headers
infra/cdk/src/stacks/services/metrics/integrations/powerradar/lambda.stack.ts:100
Use content-type, x-amz-date, x-amz-content-sha256, authorization instead of ['*'].
powerradarChannel absent from ProvisionAssetStreamCreateSchema — silently dropped or unvalidated JSONB write
packages/api/src/schemas/metrics-wizards.schemas.ts
Field either never reaches the shell (breaking D3 apply) or persists without validation. Add to Zod schema with z.string().min(1).max(200).
No file-size guard before S3 download — large CSV can OOM 512 MB Lambda
services/metrics/integrations/powerradar/src/handlers/metrics.lambda.ts:133
Log injection via csvS3Key and channel names in JSON logs
services/metrics/integrations/powerradar/src/handlers/metrics.lambda.ts:94
No bucket versioning — overwritten CSV is unrecoverable
infra/cdk/src/stacks/services/metrics/integrations/powerradar/lambda.stack.ts:92
CORS GET on write-only intake bucket — remove to tighten surface
infra/cdk/src/stacks/services/metrics/integrations/powerradar/lambda.stack.ts:95
conventions9
ProvisionAssetStreamCreate: flat vendor fields will proliferate across 12 ports — use sourceConfig passthrough
domains/cross-domain/src/asset-provisioning.shells.ts:72
Single sourceConfig?: Record<string,unknown> with manifest-level validation is the canonical pattern. Each of the 12 planned ports adding a field requires a cross-cutting signature change.
manifest required:'powerradarChannel' not enforced at provision — silent gap between TS (optional) and manifest (required)
domains/cross-domain/src/asset-provisioning.shells.ts:883
Source inserted without powerradarChannel passes provision and fails silently at collection time with channel-not-configured.
Outer try/catch tags all exceptions as UpstreamUnavailable (502, retryable) — misclassifies logic errors
services/metrics/integrations/powerradar/src/handlers/metrics.lambda.ts:218
Pre-existing eGauge pattern but semantically wrong for pure-function failures.
INFRA_DESIGN.md missing — INTEGRATION_STANDARDS §8 checklist requires one
services/metrics/integrations/powerradar/
Growatt and eGauge both have INFRA_DESIGN.md. Architecture is documented only in Lambda handler JSDoc and CLAUDE.md.
INTEGRATION_STANDARDS §8 checklist has stale descriptor.ts requirements
services/metrics/integrations/INTEGRATION_STANDARDS.md:224
diagnostics spread of undefined — prefer initialized empty object
services/metrics/integrations/powerradar/src/handlers/metrics.lambda.ts:215
§9 conformance correct — groupChannelsByGranularity, resilient skips, zero-point drops
Manifest registration, CDK dependency wiring, and seed entries are correct
Error discriminated unions use canonical InvocationError tags
tests11
Scalar params fallback (direct-invoke) has no test
services/metrics/integrations/powerradar/src/__tests__/metrics-handler.test.ts
Empty sources:[] → TranslationFailed not tested
services/metrics/integrations/powerradar/src/__tests__/metrics-handler.test.ts
M/D/YYYY re-export: only cell-level test, no full-CSV no-parseable-rows test
services/metrics/integrations/powerradar/src/__tests__/csv.test.ts
CRLF line-ending variant not tested
services/metrics/integrations/powerradar/src/__tests__/csv.test.ts
T-separator ISO timestamp accepted by TS_RE but not tested
services/metrics/integrations/powerradar/src/__tests__/csv.test.ts
(kWh) suffix normalization: kWh ≠ kW — ×1000 scale would be wrong for energy cells
services/metrics/integrations/powerradar/src/__tests__/rows-to-batu.test.ts
Lambda handler export (ArnEnvVarMissing) untested
services/metrics/integrations/powerradar/src/__tests__/metrics-handler.test.ts
Multi-channel migration plan (DuplicateChannel risk) not tested
scripts/metrics/__tests__/legacy-registry-core.test.ts:182
Triplet local-timestamp correct-drop behavior not asserted
services/metrics/integrations/powerradar/src/__tests__/rows-to-batu.test.ts
toUtc exclusive boundary not isolated — bundled in rowsOutsideWindow===2 assertion
services/metrics/integrations/powerradar/src/__tests__/rows-to-batu.test.ts:121
America/Phoenix timezone recognized in header but not exercised end-to-end in rowsToBatu
improvement4
splitCsvLine duplicated from helioscope
services/metrics/integrations/powerradar/src/engine/csv.ts:63
DEFAULT_GRANULARITY = NATIVE_GRANULARITY alias is redundant
services/metrics/integrations/powerradar/src/handlers/metrics.lambda.ts:60
resolveChannels scalar fallback is dead weight for a new integration
services/metrics/integrations/powerradar/src/handlers/metrics.lambda.ts:66
diagnostics overwritten per group — defensive accumulation for future multi-granularity ports
History · 15 commits
- f34e4ceneeds attentionincremental0H · 1M · 3L2026-07-23 00:32
- 1bc8aedneeds attentionincremental0H · 2M · 3L2026-07-22 21:34
- 31e63bbneeds attentionincremental0H · 2M · 5L2026-07-22 20:53
- 482cb88safeincremental0H · 0M · 1L2026-07-22 17:56
- 9a73b79safeincremental0H · 0M · 0L2026-07-22 17:34
- 87f8298needs attentionincremental0H · 1M · 3L2026-07-22 16:59
- 1e80096safeincremental0H · 0M · 1L2026-07-22 16:42
- 6ed65b4safeincremental0H · 1M · 3L2026-07-22 00:00
- 74a1131needs attentionfull0H · 7M · 12L2026-07-21 19:06
- 563252bneeds attentionincremental4H · 9M · 8L2026-07-21 18:32
- beced58needs attentionincremental0H · 3M · 3L2026-07-21 01:13
- 7fa8684needs attentionincremental4H · 9M · 7L2026-07-20 22:56
- 230784fneeds attentionfull2H · 11M · 14L2026-07-10 00:15current
- b876b54needs attentionincremental2H · 2M · 5L2026-07-08 04:44
- ec4847fneeds attentionfull2H · 11M · 8L2026-07-08 03:32