feat: expand vehicle data platform capabilities

This commit is contained in:
lingniu
2026-07-27 16:46:15 +08:00
parent e3a1f80f86
commit 3c4bece72c
650 changed files with 62155 additions and 2552 deletions

View File

@@ -39,6 +39,10 @@ If a keyword cannot be resolved to a VIN, data APIs must not fabricate a VIN. Th
## Core Query APIs
### Vehicle Open Platform
Partner-facing daily hydrogen-consumption and mileage APIs, 32-character appKey authentication, key validity, and per-vehicle grant intervals are documented in [open-platform-api.md](open-platform-api.md). These public endpoints use their own fail-closed appKey authentication instead of the internal user-session middleware.
### Unified Metric Catalog
```http
@@ -47,6 +51,24 @@ GET /api/v2/metrics
Returns the server-owned metric whitelist used by rule configuration and future telemetry/history surfaces. Production definitions come from `vehicle_metric_definition` plus `vehicle_metric_protocol_mapping`; migration `005_metric_catalog.sql` only seeds missing rows, so later database configuration is not overwritten by releases. Each metric includes `key`, localized `label`, `unit`, `category`, `valueType`, supported `protocols`, per-protocol `sourceFields`, and `searchable/chartable/alertable` capabilities. Alert rule writes reject unknown, disabled, non-alertable, type-mismatched, or evaluator-unsupported metrics; clients must not invent metric keys or derive SQL fields from user input. `/api/v2/history/metrics` remains the category/column compatibility catalog for the current history page.
### Alert automations
```http
GET /api/v2/alerts/rules
GET /api/v2/alerts/rules/library?lifecycle=current&keyword=&status=all&protocol=&limit=10&offset=0
POST /api/v2/alerts/rules
PUT /api/v2/alerts/rules/{id}
POST /api/v2/alerts/rules/{id}/archive
POST /api/v2/alerts/rules/{id}/restore
POST /api/v2/alerts/events
```
An alert rule is an automation with one `triggerType`: `metric`, `geofence`, `stationary`, or `offline`. Metric triggers use the unified metric catalog. Stationary triggers normalize to a bounded speed threshold plus a duration; offline triggers normalize to realtime freshness; geofence triggers store a WGS-84 circular center, radius and `enter|exit|inside|outside` mode. A geofence must select exactly one positioning protocol so independent 808/32960/MQTT coordinates cannot oscillate across the boundary or create duplicate events.
Every matching rule creates a traceable event. `notificationChannels=[]` records the event only; `in_app` creates an in-product notification. Rules store `notificationTargets[]` as `{channel, recipientId, label}` references to the server-provided recipient-group catalog, and `notificationChannels` is derived for backward compatibility. External channels cannot be published without a configured gateway and an explicit recipient group. Event records include the normalized `triggerType`, observed evidence, rule snapshot and lifecycle status.
The administrator rule library is server-filtered and paginated. `lifecycle` is `current|archived`, `status` is `all|enabled|disabled`, `protocol` matches the normalized rule protocol array, and `keyword` searches rule identity, condition, vehicle scope, and archive reason. Each page returns `summary.current|enabled|disabled|archived` from the complete rule set rather than the current page. Archiving is reversible but requires a disabled rule, the current optimistic `version`, and a 4200 character reason. The server increments the rule version, removes live evaluator candidates/state, keeps event history and configuration, and writes an immutable `archive` revision. Restore also requires the current version and a reason, returns the rule to the current library still disabled, and writes a `restore` revision. Archived rules are excluded from evaluator reads and reject edit, enable, and rollback writes until restored.
### Latest vehicle telemetry
`GET /api/v2/vehicles/{vin}/telemetry/latest`
@@ -90,6 +112,8 @@ Returns one vehicle service view with identity, realtime summary, source coverag
The response also carries `profile`, the supplemental business master record from `vehicle_profile`. Gateway-owned VIN, plate and OEM identity remain authoritative and are never overwritten by this record. Profile completeness is calculated across model, vehicle type, company, operation status, access provider, first access time and cumulative runtime.
When the active OneOS scope snapshot contains the VIN, the response carries `businessRelation`. It is a read-only projection from `ln_asset_management` containing the current business customer, contract, project, department, responsible user, OneOS operation status, relationship start time, source version and publication evidence. The projection only accepts completed deliveries without a completed return and is joined through `business_scope_state.active_version`; no request performs a live OneOS database query. OneOS BIGINT identifiers are encoded as JSON strings to avoid JavaScript precision loss. If no safe current relation exists, `businessRelation` is omitted and clients must show an explicit unbound state instead of inferring ownership from `vehicle_info.customer_id`.
```http
GET /api/v2/vehicles/{vin}/profile
PUT /api/v2/vehicles/{vin}/profile
@@ -148,6 +172,8 @@ Returns VIN-level source coverage rows for the vehicle service list. Each row in
Coverage summary also exposes `noDataVehicles`, so UI can show vehicles that exist in identity binding but have no GB32960, JT808, or Yutong MQTT source evidence. `/api/vehicles/coverage?serviceStatus=no_data` returns those bound vehicles for follow-up source onboarding.
Vehicle coverage accepts comma-separated multi-select filters: `departmentIds`, `responsibleUserIds`, `customerIds`, and `operationStatuses`. The server applies the authenticated principal VIN scope before these filters. `GET /api/vehicles/business-filters` returns `{departments,responsibleUsers,customers,statuses}` with `{value,label,count}` options already limited to the same principal scope.
Coverage rows also include lightweight `sourceConsistency` so list views can show the same vehicle-level source diagnosis without issuing per-row detail requests.
### History Locations
@@ -169,6 +195,8 @@ This keeps table pagination precise while preventing large routes from overloadi
### History Series And Controlled Export
`GET /api/v2/history/query` accepts one to five comma-separated `keywords`. In addition to aggregate `vehicleCount`, its summary preserves the full requested scope through `requestedVehicleCount` and an ordered `vehicles` receipt. Each receipt includes the original `keyword`, resolved `vin` / `plate` when current-range evidence exists, `rowCount`, and `matched` or `no_data` status. Clients should show these per-vehicle outcomes instead of inferring a missing vehicle from the aggregate count; `no_data` means no evidence matched the selected category, protocol and time window.
```http
GET /api/v2/history/series?keyword=AG18312&dateFrom=2026-07-14T00:00:00%2B08:00&dateTo=2026-07-14T06:00:00%2B08:00&targetPoints=240
@@ -182,18 +210,53 @@ Content-Type: application/json
"dateFrom": "2026-07-14T00:00:00+08:00",
"dateTo": "2026-07-14T06:00:00+08:00",
"metrics": ["speed_kmh", "total_mileage_km"],
"format": "csv"
"format": "csv",
"retentionDays": 14
}
GET /api/v2/exports
GET /api/v2/exports/page?search=AG18312&status=expiring&scope=archived&ownerScope=mine&sort=expiry&limit=20&offset=0
GET /api/v2/exports/cleanup/preview?olderThanDays=180&ownerScope=all
GET /api/v2/exports/cleanup/audit?limit=20
POST /api/v2/exports/cleanup
GET /api/v2/exports/cleanup/automation
PUT /api/v2/exports/cleanup/automation
POST /api/v2/exports/cleanup/automation/{runId}/approve
POST /api/v2/exports/cleanup/automation/{runId}/reject
POST /api/v2/exports/cleanup/automation/{runId}/retry
PUT /api/v2/exports/{id}/cleanup-protection
POST /api/v2/exports/{id}/rebuild
POST /api/v2/exports/{id}/cancel
POST /api/v2/exports/batch
GET /api/v2/exports/{id}/download
GET /api/v2/history/preferences
PUT /api/v2/history/preferences
Content-Type: application/json
{
"retentionDays": 14,
"fieldViews": [
{ "id": "location:", "name": "", "category": "location", "keys": ["speedKmh", "socPercent"] }
]
}
```
The series endpoint returns server-aggregated location telemetry with explicit bucket coverage and missingness. Requests are limited to 31 days and 60600 target points; timestamps must identify an absolute window rather than relying on a database session timezone.
Export creation and listing require `operator` or `admin`. A task accepts 15 vehicles, `location`, `raw`, or `mileage`, CSV format, at most 31 days and 32 metrics. The service runs one export at a time and enforces a 1,000,000-row and 30-minute ceiling. Location and RAW data use stable forward-only cursors; the implementation does not use growing `OFFSET` scans.
Export creation and listing require `operator` or `admin`. A task accepts 15 vehicles, `location`, `raw`, or `mileage`, CSV format, at most 31 days and 32 metrics. `retentionDays` accepts `1`, `3`, `7`, `14`, or `30`; when omitted, the current account preference is used and falls back to 7 days. The service runs one export at a time and enforces a 1,000,000-row and 30-minute ceiling. Location and RAW data use stable forward-only cursors; the implementation does not use growing `OFFSET` scans.
Job status values are `queued`, `running`, `completed`, and `failed`. During execution, clients should display the server-owned `processedRows`, `totalRows`, `progress`, and `evidence` fields rather than estimate progress from elapsed time. On completion, `rowCount`, `processedRows`, and `totalRows` are equal, `fileSizeBytes` and `completedAt` are populated, and `downloadUrl` becomes available. The final CSV is exposed only after its temporary `.part` file has been flushed, synchronized, closed, and atomically renamed. Completed jobs survive API restarts; jobs interrupted while queued or running become explicit failures and may be recreated.
`GET /api/v2/exports/page` is the bounded task-center contract. `search` matches task, vehicle, protocol, category, and creator fields; `status` accepts `active`, `failed` (failed or expired), `completed`, `expiring` (completed and expiring within the next 24 hours), and `cancelled`; `scope` accepts `current` or `archived`; `ownerScope=mine` limits an administrator's otherwise global audit view to jobs created by the current persistent subject; customer accounts remain owner-scoped regardless of this parameter; `sort` accepts `recent` (the default) or `expiry`; `limit` accepts 10, 20, or 50. `recent` orders current tasks by creation time and archived tasks by archive activity, with creation time as the stable batch-archive tie-breaker. `expiry` puts downloadable files with a future expiration first in ascending expiration order, then falls back to the selected scope's recent activity. The response contains the filtered `items`, `total`, `limit`, and `offset`. In `summary`, `current` and `archived` count the accessible owner scope, while `active`, `completed`, `expiring`, `recoverable`, and `cancelled` count only the selected `scope`, before status/search filtering. The legacy `GET /api/v2/exports` remains a first-20 compatibility view.
History preferences are private to the authenticated account and persist independently from browser storage. `fieldViews` supports at most eight named views, each containing 132 fields for `location`, `raw`, or `mileage`; `retentionDays` uses the same export whitelist. The server returns a monotonic `revision` and `updatedAt` receipt after each successful update.
Job status values are `queued`, `running`, `completed`, `failed`, `expired`, and `cancelled`. During execution, clients should display the server-owned `processedRows`, `totalRows`, `progress`, and `evidence` fields rather than estimate progress from elapsed time. Completed files receive an `expiresAt`; once the retention window ends or the published file is missing, the job becomes `expired`, clears `downloadUrl`, and retains its original scope for audit. `POST /api/v2/exports/{id}/rebuild` accepts only `failed` or `expired` jobs, revalidates current vehicle/time authorization, creates a new task linked by `rebuiltFrom`, and refuses a duplicate scope already queued or running.
The owner or an administrator may cancel a queued or running task. Cancellation is idempotent, releases the single execution slot, removes partial output, and preserves `cancelledAt`, `cancelledBy`, the original scope, progress, and evidence for audit; cancelled tasks never expose a download. `POST /api/v2/exports/batch` accepts 120 ids with `cancel` or `rebuild`, executes every item independently, and returns explicit `succeeded` and `skipped` arrays. On completion, `rowCount`, `processedRows`, and `totalRows` are equal, `fileSizeBytes`, `completedAt`, and `expiresAt` are populated, and `downloadUrl` becomes available. The final CSV is exposed only after its temporary `.part` file has been flushed, synchronized, closed, and atomically renamed. Terminal jobs survive API restarts; jobs interrupted while queued or running become explicit failures and may be rebuilt.
Archived task cleanup is an administrator-only, preview-gated operation. `olderThanDays` accepts 30, 90, 180, or 365 and only includes terminal jobs whose `archivedAt` is outside the selected window. Preview returns the full candidate/protected counts, the oldest-first batch of at most 500 records, up to 20 visible samples, remaining file impact, and a deterministic `previewToken`. `POST /api/v2/exports/cleanup` must echo that token; any archive, protection, or task update invalidates the token and forces a new preview. `PUT /api/v2/exports/{id}/cleanup-protection` requires a reason when enabling protection and records the actor, time, and reason in the task evidence. Protected jobs never enter cleanup. Permanent cleanup removes the task record and any remaining file, while an independent cleanup audit retains the actor, policy, counts, cutoff, impact, timestamp, and candidate digest. Export persistence no longer silently truncates records after 500 jobs; growth is handled only through this explicit lifecycle workflow.
Cleanup automation is approval-gated rather than unattended deletion. The singleton policy accepts `olderThanDays` 30/90/180/365, `intervalDays` 7/14/30, and `approvalWindowHours` 24/48/72, with `expectedRevision` optimistic concurrency. Each due review produces an immutable-impact run in `awaiting_approval` or `no_candidates`; no deletion occurs until an administrator submits a reason to the run-specific `approve` endpoint. Approval rechecks the deterministic preview token. If the candidate scope changed, the run becomes `needs_review`, receives the latest counts and a fresh approval window, and must be approved again. Approved runs are claimed with a filesystem-backed cross-process mutex and expiring execution lease. A crashed worker is recovered after lease expiry; technical failures retry at bounded backoff up to three attempts, preserve the original approval and error evidence, and then require the explicit `retry` endpoint with a new administrative reason. `reject` closes a pending run without affecting tasks. Run mutations use per-run revisions, so stale browser actions are rejected rather than overwriting another administrator's decision.
### RAW Frames
@@ -232,7 +295,7 @@ GET /api/mileage/summary?keyword=粤AG18312&protocol=JT808&dateFrom=2026-07-01&d
Returns daily mileage rows and aggregate mileage summary.
The production projection table stores `daily_mileage_km` and `latest_total_mileage_km`; it deliberately no longer stores a duplicate first-total column. The API derives `startMileageKm = latestTotalMileageKm - dailyMileageKm` and normalizes a missing latest value to zero, matching the current gateway storage contract.
The production projection table stores `daily_mileage_km`, `pure_hydrogen_mileage_km`, and `latest_total_mileage_km`; it deliberately no longer stores a duplicate first-total column. The API derives `startMileageKm = latestTotalMileageKm - dailyMileageKm`, returns `pureHydrogenMileageKm` for GB32960 and Yutong fuel-cell work-mode intervals, and normalizes a missing latest value to zero, matching the current gateway storage contract. Daily mileage rows also join quality-approved `vehicle_open_daily_energy` evidence and return nullable `hydrogenConsumptionKg` and `hydrogenConsumptionKgPer100Km`. The rate uses pure-hydrogen mileage only when both metrics are valid for the same vehicle day; missing or suspect hydrogen evidence remains `null` rather than being presented as zero.
### Alert Events
@@ -264,11 +327,18 @@ POST /api/v2/alerts/events
GET /api/v2/alerts/events/{id}
POST /api/v2/alerts/events/{id}/actions
GET /api/v2/alerts/rules
GET /api/v2/alerts/rules/library?lifecycle=current&keyword=&status=all&protocol=&limit=10&offset=0
POST /api/v2/alerts/rules
PUT /api/v2/alerts/rules/{id}
PUT /api/v2/alerts/rules/{id}/enabled
POST /api/v2/alerts/rules/{id}/archive
POST /api/v2/alerts/rules/{id}/restore
GET /api/v2/alerts/notifications?unreadOnly=true&limit=20&offset=0
GET /api/v2/alerts/notification-config
GET /api/v2/alerts/notifications/health
POST /api/v2/alerts/notifications/read
POST /api/v2/alerts/notifications/{id}/retry
GET /api/v2/alerts/notifications/{id}/retry-audit
```
Rule operators are type-aware: numeric metrics support `gt/gte/lt/lte/eq/neq/between/outside`; Boolean metrics support `eq/neq/changed`. Range rules carry `threshold` and `thresholdHigh`. Scopes support `scopeProtocols`, `scopeVins`, authoritative `scopeOems`, and `scopeModels/scopeCompanies` joined from `vehicle_profile`. Each scope list is capped at 500 normalized values with a 128-character per-value bound, protecting evaluator latency. Vehicles without the requested master-data dimension do not match a model/company-scoped rule. `changed` rules persist the previous Boolean state per rule/VIN/protocol, and repeat suppression uses `repeatIntervalSec` against the latest event fingerprint.
@@ -277,7 +347,13 @@ The rule editor reads its selectable metrics from `GET /api/v2/metrics`. The API
`summary` and `events` accept the same JSON filter: `keyword, severity, status, ruleId, protocol, dateFrom, dateTo, limit, offset`. Event states are `unprocessed, processing, recovered, closed, ignored`; event changes require the current `version` and actions are `acknowledge, close, ignore`. Every action writes an immutable timeline item with actor, before/after state, note, and timestamp. Stale event or rule writes return HTTP 409 with a `*_VERSION_CONFLICT` code.
Rules support numeric/Boolean values, bounded duration and repeat interval, recovery hysteresis, protocol/VIN scope, enable state, and a versioned audit snapshot. For telemetry metrics, duration is the difference between distinct, monotonic source event observations rather than time spent rereading one MySQL snapshot; `freshness_sec` is the explicit exception because staleness changes with platform time. Duplicate and late observations are counted but cannot advance a candidate. The evaluator deduplicates active rule+vehicle+protocol fingerprints and automatically recovers only when the configured recovery condition is true. Disabling a rule atomically clears its candidate and Boolean state together with the versioned disable audit. Station notifications have real unread/read state. `sms`, `email`, and `wecom` records are explicitly `reserved`; they are not reported as sent.
Rules support numeric/Boolean values, bounded duration and repeat interval, recovery hysteresis, protocol/VIN scope, enable state, and a versioned audit snapshot. The administrator-only `library` contract adds complete-set filtering, pagination, lifecycle summaries, and reversible archive/restore governance. A rule must be disabled before archive; archive and restore both require an optimistic version and 4200 character audit reason, and restoration never enables the rule automatically. For telemetry metrics, duration is the difference between distinct, monotonic source event observations rather than time spent rereading one MySQL snapshot; `freshness_sec` is the explicit exception because staleness changes with platform time. Duplicate and late observations are counted but cannot advance a candidate. The evaluator deduplicates active rule+vehicle+protocol fingerprints and automatically recovers only when the configured recovery condition is true. Disabling or archiving a rule atomically clears its candidate and Boolean state together with the versioned audit.
Notification queries return every delivery channel and preserve the provider-facing state instead of projecting only station messages. Customer sessions are limited to notifications joined to their granted VINs. Unread/read state applies only to `in_app`; external channels return a not-applicable reading state. A newly written station notification is immediately `sent`, because the durable database row is its delivery evidence. `sms`, `email`, and `wecom` records remain `reserved` until an external delivery worker reports a real outcome; the API never reports queue acceptance as provider delivery.
`notification-config` returns the recipient-group catalog and readiness of `in_app`, `sms`, `email`, and `wecom`; it never exposes gateway URLs, secrets, phone numbers, or email addresses. `notifications/health` returns queue-wide and per-channel counts for queued, failed, dead-letter (failed at the three-attempt cap), and active leased deliveries, plus the oldest queued timestamp. The dispatcher claims `reserved` rows with `FOR UPDATE SKIP LOCKED` and a bounded lease. It sends a JSON payload to the configured trusted gateway with `X-Lingniu-Timestamp`, `X-Lingniu-Signature: sha256=<HMAC-SHA256(timestamp + "\n" + body)>`, `X-Lingniu-Notification-ID`, and `X-Lingniu-Idempotency-Key: notification:{id}:attempt:{attempt}`. A 2xx response must include `messageId` in JSON or `X-Provider-Message-ID`; otherwise the attempt is failed rather than falsely marked sent.
Only administrators and operators can retry a failed notification. A retry requires the current `expectedAttemptCount`, a 4200 character reason, and a unique 1696 character `idempotencyKey`; each notification is capped at three attempts. The server locks the notification, rejects stale or non-failed requests, and writes an immutable retry audit in the same transaction. Reusing the same key returns the original receipt without creating another attempt. Retrying `in_app` writes a new delivered station attempt; retrying an external channel moves the record back to `reserved` so a separate provider worker can process it. `retry-audit` returns the latest 20 receipts without exposing the idempotency key.
### Online And Completeness Statistics
@@ -345,6 +421,61 @@ The diagnostic response exposes every current location candidate, including the
`providerEvidence` is mandatory whenever `providerName` changes, including removal. It is written to the immutable provider audit and is deliberately separate from `remark`, which belongs only to source enable/priority policy. A provider-only update must preserve the existing policy remark. Both change types share the vehicle-level optimistic `version`; stale writes return `SOURCE_POLICY_VERSION_CONFLICT`.
### V2 Reconciliation Batch Actions
```http
POST /api/v2/reconciliation/issues/batch-actions
Content-Type: application/json
{
"items": [
{"id": "reconciliation-issue-1", "version": 3},
{"id": "reconciliation-issue-2", "version": 1}
],
"status": "fixed",
"note": ""
}
```
The batch endpoint accepts 120 unique issue IDs. `status` is limited to `pending`, `no_action`, or `fixed`; non-pending conclusions require a note of at most 500 characters. Each item carries its own optimistic version and is written through the same immutable review history as the single-item action endpoint. Items are independent: a conflict or missing record is returned in `skipped` with its code and message while other valid items continue. `succeeded` returns the complete updated issue, and `requested` always reflects the submitted item count. The authenticated principal supplies the actor; a body actor is never trusted.
### V2 Reconciliation Ownership and SLA
```http
POST /api/v2/reconciliation/issues/{id}/assignment
Content-Type: application/json
{"version":3,"assignee":"","dueAt":"2026-07-24T08:00:00Z"}
POST /api/v2/reconciliation/issues/batch-assignments
Content-Type: application/json
{
"items":[{"id":"reconciliation-issue-1","version":3}],
"assignee":"",
"dueAt":"2026-07-24T00:00:00Z"
}
```
Assignment requires a non-empty assignee (maximum 128 characters), a future RFC 3339 deadline, and the issue's current optimistic version. Successful writes update `assignee`, `assignedBy`, `assignedAt`, and `dueAt`, increment `version`, and append an immutable `assign` action. Batch assignment accepts 120 unique issues and returns the same per-item `succeeded` / `skipped` contract as batch review. The authenticated principal is always the assigner.
The issue-list query accepts `owner=assigned|unassigned|<exact assignee>` and `sla=overdue|due_soon`; `due_soon` means an active issue whose explicit deadline is within eight hours. Summary SLA counts use an explicit `dueAt` when present and otherwise retain the legacy 24-hour first-seen fallback.
### V2 Reconciliation Directory and Export
```http
GET /api/v2/reconciliation/assignees?search=
POST /api/v2/reconciliation/issues/export
Content-Type: application/json
{"keyword":"A","status":"active","owner":"","sla":"overdue"}
```
The assignee directory merges the authenticated principal, enabled administrator accounts, and historical non-empty assignees. Results include `name`, optional `username`, source, active issue count, last assignment time, and a current-account marker; search matches name or username and returns at most 50 entries.
The CSV export uses the same keyword, rule, category, severity, status, exact owner, and SLA filters as the server-paginated queue, but intentionally ignores the visible page offset. It reads in bounded 200-row pages and rejects a result above 50,000 records. The response includes UTF-8 BOM, `Content-Disposition`, `X-Export-Name`, and `X-Export-Count`; spreadsheet-formula prefixes are neutralized before CSV encoding.
## Map Reverse Geocoding
`GET /api/map/reverse-geocode?longitude=<WGS-84>&latitude=<WGS-84>` is an authenticated server-side AMap Web Service adapter. It validates the source coordinate, converts WGS-84 to GCJ-02 exactly once, keeps the server API key out of the browser, and requests only the `base` reverse-geocode response documented by [AMap](https://lbs.amap.com/api/webservice/guide/api/georegeo).

View File

@@ -32,6 +32,23 @@ Saving an unchanged vehicle assignment preserves its original start time. Removi
Administrators manage `validFrom` and optional `validTo` per vehicle in account management. The API updates the active projection and its latest history interval in one transaction, validates `validTo > validFrom`, and repairs any pre-existing projection/history drift on save. The admin list reads the complete projection, including future or scheduled-to-end grants; customer principals still contain only grants active at the current database time. Each update audit stores the actor and per-VIN before/after interval.
### OneOS business scope
OneOS customer identities can use `platform_user_business_scope` instead of a manually maintained VIN list:
- `scope_level=department`: `department_ids` is a comma-separated allow-list. The session receives every VIN in the active OneOS business projection for those departments.
- `scope_level=responsible`: the session receives only VINs whose `responsible_user_id` equals the configured user.
- An enabled row with a missing or unknown scope resolves to an empty VIN set. A OneOS account without a row keeps the existing manual-grant behavior for migration compatibility.
The VIN set and the department/responsible/customer/status filter candidates are both calculated on the server. Client query parameters can only narrow this set and cannot widen it.
```sql
INSERT INTO platform_user_business_scope(user_id,scope_level,department_ids,responsible_user_id,source_updated_at)
VALUES(42,'department','40001,40002','',NOW(3))
ON DUPLICATE KEY UPDATE scope_level=VALUES(scope_level),department_ids=VALUES(department_ids),
responsible_user_id=VALUES(responsible_user_id),enabled=1,source_updated_at=VALUES(source_updated_at);
```
## Sessions and password policy
- Passwords are stored with bcrypt cost 12.
@@ -64,6 +81,7 @@ POST /api/v2/auth/login
POST /api/v2/auth/logout
PUT /api/v2/auth/password
GET /api/v2/session
GET /api/vehicles/business-filters
GET /api/v2/admin/users
POST /api/v2/admin/users

View File

@@ -8,13 +8,18 @@ Run from repository root:
cd vehicle-data-platform
pnpm --dir apps/web install
pnpm run web:build
pnpm --dir apps/open-portal install
pnpm run open-portal:build
cd apps/api
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/platform-api ./cmd/platform-api
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/open-platform-api ./cmd/open-platform-api
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/alert-evaluator ./cmd/alert-evaluator
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/alert-stream-evaluator ./cmd/alert-stream-evaluator
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/alert-notification-dispatcher ./cmd/alert-notification-dispatcher
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/platform-migrate ./cmd/platform-migrate
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/oneos-scope-sync ./cmd/oneos-scope-sync
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/reconciliation-evaluator ./cmd/reconciliation-evaluator
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/open-platform-stat ./cmd/open-platform-stat
# Optional release/performance gate; this binary is run on demand, not installed as a service.
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/alert-benchmark ./cmd/alert-benchmark
```
@@ -25,8 +30,16 @@ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../
/opt/lingniu-vehicle-platform/current
/opt/lingniu-vehicle-platform/releases
/opt/lingniu-vehicle-platform/env/platform.env
/opt/lingniu-vehicle-open-platform/current
/opt/lingniu-vehicle-open-platform/releases
/opt/lingniu-vehicle-open-platform/env/open-platform.env
```
The internal platform remains on `20300`. The partner-facing portal, portal
session APIs, Swagger/OpenAPI assets, and `/api/v1/vehicles/*` data APIs run in
the independent `lingniu-vehicle-open-platform` service on `20310`.
## Browser-safe release switch
The web application splits every major route into a hashed lazy-loaded asset. An already-open browser tab can therefore request the previous release's route asset after `current` has switched. Keep exactly one previous generation of original build assets in the new release before the atomic symlink switch:
@@ -67,7 +80,7 @@ PREVIOUS_WEB=/opt/lingniu-vehicle-platform/releases/$PREVIOUS_RELEASE/web
"$PREVIOUS_WEB/.release-assets"
```
`deploy/install-web-release.sh` also accepts an optional third argument containing a newly built `platform-api`, an optional fourth argument containing a newly built `oneos-scope-sync`, and an optional fifth argument containing a newly built `reconciliation-evaluator`. When provided, it atomically publishes those binaries with the Web. The installer inherits every runtime binary that exists in the previous release (`alert-evaluator`, `alert-stream-evaluator`, `platform-migrate`, `oneos-scope-sync`, `reconciliation-evaluator` and the optional benchmark) before switching the symlink, so a later systemd restart cannot fail because a Web-oriented release omitted an unchanged binary:
`deploy/install-web-release.sh` also accepts optional runtime binaries after the Web archive: `platform-api`, `oneos-scope-sync`, `reconciliation-evaluator`, `open-platform-stat`, `alert-evaluator`, `alert-stream-evaluator`, and `alert-notification-dispatcher` (arguments 39 in that order). When provided, it atomically publishes those binaries with the Web. The installer also inherits every runtime binary that exists in the previous release, including the notification dispatcher, before switching the symlink, so a later systemd restart cannot fail because a Web-oriented release omitted an unchanged binary:
```bash
deploy/install-web-release.sh \
@@ -75,7 +88,11 @@ deploy/install-web-release.sh \
"/tmp/$PLATFORM_RELEASE-web.tar.gz" \
"/tmp/$PLATFORM_RELEASE-platform-api" \
"/tmp/$PLATFORM_RELEASE-oneos-scope-sync" \
"/tmp/$PLATFORM_RELEASE-reconciliation-evaluator"
"/tmp/$PLATFORM_RELEASE-reconciliation-evaluator" \
"/tmp/$PLATFORM_RELEASE-open-platform-stat" \
"/tmp/$PLATFORM_RELEASE-alert-evaluator" \
"/tmp/$PLATFORM_RELEASE-alert-stream-evaluator" \
"/tmp/$PLATFORM_RELEASE-alert-notification-dispatcher"
```
## Environment
@@ -124,9 +141,23 @@ ALERT_STREAM_MODE=active
ALERT_STREAM_KAFKA_BROKERS=<kafka-host>:9092
ALERT_STREAM_KAFKA_TOPICS=vehicle.fields.go.gb32960.v1,vehicle.fields.go.jt808.v1,vehicle.fields.go.yutong-mqtt.v1
ALERT_STREAM_KAFKA_GROUP=vehicle-alert-stream-shadow-v1
ALERT_NOTIFICATION_TARGETS_JSON=[{"id":"night-shift","label":"夜班负责人","channels":["sms","wecom"]},{"id":"data-platform","label":"数据平台组","channels":["email","wecom"]}]
ALERT_NOTIFICATION_SMS_URL=https://trusted-notification-gateway.example.internal/sms
ALERT_NOTIFICATION_SMS_SECRET=***
ALERT_NOTIFICATION_EMAIL_URL=https://trusted-notification-gateway.example.internal/email
ALERT_NOTIFICATION_EMAIL_SECRET=***
ALERT_NOTIFICATION_WECOM_URL=https://trusted-notification-gateway.example.internal/wecom
ALERT_NOTIFICATION_WECOM_SECRET=***
ALERT_NOTIFICATION_BATCH_SIZE=20
ALERT_NOTIFICATION_POLL_INTERVAL_MS=1000
ALERT_NOTIFICATION_LEASE_SEC=30
ALERT_NOTIFICATION_TIMEOUT_MS=5000
ALERT_STREAM_BATCH_SIZE=200
ALERT_STREAM_BATCH_WAIT_MS=100
ALERT_STREAM_LATENESS_SEC=120
HISTORY_EXPORT_CLEANUP_AUTOMATION_ENABLED=true
HISTORY_EXPORT_CLEANUP_POLL_SEC=60
HISTORY_EXPORT_CLEANUP_LEASE_SEC=300
```
`ALERT_STREAM_MODE=shadow` only validates envelopes and advances checkpoints; `active` additionally evaluates every non-`freshness_sec` rule in the same MySQL transaction. Keep the existing consumer group when switching so active resumes the shadow-proven offsets instead of replaying retained history. In active mode the snapshot evaluator retains exclusive ownership of `freshness_sec` and skips all dynamic telemetry rules. Roll back the mode to `shadow` and restart the API plus both evaluators if active batches fail; transaction rollback prevents a failed rule effect from advancing the authoritative checkpoint.
@@ -143,6 +174,8 @@ All three platform systemd units first load `/opt/lingniu-go-native/env/base.env
`EXPORT_DIR` must point outside the release symlink. The API writes mode-`0640` CSV files and an atomic `jobs.json` index there; completed tasks survive API restarts, while interrupted queued/running jobs are marked failed and can be recreated. Every job persists its owner account, role/user type, customer/tenant references, resolved VINs and per-vehicle time scope. Non-admin principals can list and download only their own jobs; customers additionally pass a live account-status and vehicle-grant check before execution, every 5,000-row batch and every download. Old ownerless jobs remain visible only to administrators. The browser downloads through an authenticated Blob request rather than a direct anchor, so the Bearer credential is always present.
`HISTORY_EXPORT_CLEANUP_AUTOMATION_ENABLED=true` starts the approval-gated cleanup coordinator inside each API instance. Every instance may poll, but `cleanup-automation.lock` under `EXPORT_DIR` admits only one scheduler tick at a time; a claimed execution also carries an expiring lease so process termination is recoverable without duplicate cleanup. The coordinator only creates review batches until an administrator approves one in the history task center. Keep `EXPORT_DIR` on a filesystem that provides process-shared POSIX advisory locks, and do not place it on an object-store mount that ignores `flock`. The policy, run history, approval evidence and leases persist in `cleanup-automation.json`; back it up with `jobs.json` and `cleanup-audit.json`. A production canary should run two API instances against the same export directory, verify only one creates/claims a due batch, terminate the lease owner during a disposable approved batch, and confirm the survivor resumes only after the recorded lease expiry.
Vehicle authorization dates must be changed through account management, which atomically updates the active `platform_user_vehicle` projection and its matching `platform_user_vehicle_grant_history` interval. Do not edit only one table. After saving, verify both timestamps match through `GET /api/v2/admin/users`; the response includes current `vehicles[].validFrom/validTo` and `grantHistory[]`. A start time after local midnight intentionally makes daily mileage visible from the next complete `Asia/Shanghai` day, while track/location/RAW data begins at the exact timestamp.
Exports run one at a time, use 5,000-row forward-only TDengine cursors instead of `OFFSET`, stream into a `.part` file, and atomically publish the final CSV only after flush, `fsync`, and close succeed. A task is limited to five vehicles, 31 days, 32 metrics, 1,000,000 rows, and 30 minutes. Both the initial count and observed rows enforce the row cap so late-arriving data cannot bypass it. Keep the directory mode `0750`, preserve `jobs.json` and completed CSVs across releases, and include the directory in retention/backup policy. Export routes require an authenticated viewer-capable principal; ownership and current Scope are enforced inside the export service instead of relying only on role middleware.
@@ -209,10 +242,60 @@ test -n "$MYSQL_DSN"
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/019_vehicle_source_provider.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/020_vehicle_profile_brand.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/021_protocol_metric_catalog.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/022_gb32960_reference_catalog.sql
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/022_gb32960_reference_catalog.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/023_vehicle_open_platform.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/024_vehicle_open_portal.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/025_vehicle_open_platform_admin.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/026_hydrogen_stream_state.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/027_hydrogen_rate_stream_state.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/029_hydrogen_pressure_capacity.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/030_hydrogen_pressure_hysteresis.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/031_alert_automation_triggers.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/032_reconciliation_ownership.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/033_open_mileage_range_snapshot.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/034_alert_notification_retry.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/035_alert_notification_dispatch.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/036_alert_rule_archive.sql
```
The API guards the access-threshold tables for compatibility, while alert and reconciliation APIs deliberately require their migrations to exist. Run every numbered migration explicitly before switching traffic so DDL permission and index creation failures are caught early. The migration journal records filename and SHA-256 and refuses a changed file; duplicate forward `ADD COLUMN` and `CREATE INDEX` statements are tolerated only when resuming partially executed MySQL DDL. Full-line SQL comments are removed before statement splitting, so punctuation in a comment cannot become executable SQL. Migration `008` adds forward-compatible access evidence columns and an index to the gateway-owned realtime snapshot table without changing its `(protocol, vin)` primary key. Migration `009` creates the per-group/topic/partition event-time checkpoint used to make MySQL effects authoritative before Kafka offsets are committed. Migration `012` creates the atomic, versioned business Scope projection; its reserved `row_number` column is quoted for production MySQL compatibility. Migration `014` backfills active vehicle-grant start times, creates the grant-interval history table and adds the active time lookup index; apply it before starting an API binary that writes grant history. Migration `015` adds platform-owned optimistic versions and immutable audits for per-vehicle location-source policy changes. It does not alter the gateway election SQL or expose the gateway `source_key`; the API resolves an opaque `sourceRef` server-side and the gateway applies the saved policy on the next valid vehicle report. Migration `016` adds customer name, department and responsible-person dimensions plus bounded lookup indexes to the platform-owned Scope projection. Migration `017` creates the reconciliation run, issue and immutable action tables; the unique fingerprint index is the database-level duplicate-work-item guard. Migration `018` records every OneOS-sourced brand fill before the platform identity OEM field changes; it does not grant access to OneOS or run a cross-database sync. Migration `019` stores administrator-maintained provider names by exact vehicle/protocol/source key and writes an immutable versioned audit. The browser still receives only the SHA-256 `sourceRef`; raw source keys remain server-side. Migration `020` separates the actual vehicle brand in `vehicle_profile.brand_name` from T-Box/GPS provider identity. Migration `021` adds the cross-protocol core metric catalog and correct source paths for JT808 GPS mileage, GB32960/YUTONG instrument mileage and GB32960 fuel-cell/engine telemetry. Migration `022` applies the authoritative GB/T 32960 Chinese terminology, changes business hydrogen-concentration metrics and alert thresholds from ppm to percent, and leaves the ppm field only as disabled historical evidence.
Migration `023` creates vehicle open-platform appKey records, per-vehicle grant intervals, precomputed daily energy, and immutable API/admin audits. Apply it before serving `/api/v1/vehicles/*` or enabling `lingniu-vehicle-open-stat.timer`.
Migration `029` creates the local VIN-to-tank-capacity projection and pressure-calculation evidence columns. Migration `030` adds the per-refuel-cycle low-water mark used to prevent pressure and temperature oscillation from being counted repeatedly. The stat writer synchronizes capacity from `ln_asset_management.vehicle_info.vehicle_model_id → vehicle_model.tank_capacity` at startup and every six hours, then serves frame-time lookups from process memory.
Migration `038` adds daily pure-hydrogen mileage to the elected mileage table and its per-source evidence table. Deploy the migration before the updated stat writer and API so GB32960 `engine_work_state=2` and Yutong `TRIANGLE_STATE=4/11` intervals can be accumulated and returned as `pureHydrogenMileageKm`.
Migration `031` adds explicit metric, geofence, stationary and offline automation triggers. It must be applied before the API and both alert evaluators are restarted; geofence rules deliberately require one positioning protocol to avoid multi-source coordinate drift and duplicate boundary events.
Migration `035` adds rule recipient-group references plus notification leases and dispatch indexes. Apply it before publishing an API/evaluator that writes `notification_targets_json` or enabling `lingniu-vehicle-alert-notification-dispatcher`. The dispatcher must remain disabled until every configured gateway has passed a signed canary that returns a non-empty provider message ID.
Migration `036` adds reversible rule-archive metadata, lifecycle query indexes, and audit reasons. Apply it before publishing an API or Web build that calls `/api/v2/alerts/rules/library`, `/archive`, or `/restore`, and before restarting either alert evaluator. Archive is accepted only for disabled rules and removes live candidate/state rows; restore always returns a rule disabled. During rollback, older evaluators still ignore archived rules because the archive transaction has already disabled them, but operators must not use the new lifecycle UI against an API that predates migration `036`.
Migration `024` creates partner users, role-based app membership, expiring
sessions, login lockout state, and immutable user-operation audits. Apply it
before starting the standalone open-platform service.
Migration `025` lets the standalone service issue its own session after
validating an enabled local `platform_user` administrator. It does not copy or
store a second administrator password. Apply it before deploying a portal that
shows the platform-management workspace.
The standalone API binary embeds its version-matched OpenAPI assets and serves
the compiled partner portal. After switching a release, verify
`http://127.0.0.1:20310/`, `/healthz`, `/portal-api/catalog`,
`/open-api/swagger/`, and `/open-api/openapi.yaml`. Install the unit and deploy
an atomic release with:
```bash
sudo cp deploy/systemd/lingniu-vehicle-open-platform.service /etc/systemd/system/
sudo systemctl daemon-reload
COPYFILE_DISABLE=1 tar -C apps/open-portal/dist -czf /tmp/open-portal-web.tar.gz .
sudo deploy/install-open-platform-release.sh \
"$OPEN_PLATFORM_RELEASE" \
/tmp/open-portal-web.tar.gz \
dist/open-platform-api
```
The API guards the access-threshold tables for compatibility, while alert and reconciliation APIs deliberately require their migrations to exist. Run every numbered migration explicitly before switching traffic so DDL permission and index creation failures are caught early. The migration journal records filename and SHA-256 and refuses a changed file; duplicate forward `ADD COLUMN` and `CREATE INDEX` statements are tolerated only when resuming partially executed MySQL DDL. Full-line SQL comments are removed before statement splitting, so punctuation in a comment cannot become executable SQL. Migration `008` adds forward-compatible access evidence columns and an index to the gateway-owned realtime snapshot table without changing its `(protocol, vin)` primary key. Migration `009` creates the per-group/topic/partition event-time checkpoint used to make MySQL effects authoritative before Kafka offsets are committed. Migration `012` creates the atomic, versioned business Scope projection; its reserved `row_number` column is quoted for production MySQL compatibility. Migration `014` backfills active vehicle-grant start times, creates the grant-interval history table and adds the active time lookup index; apply it before starting an API binary that writes grant history. Migration `015` adds platform-owned optimistic versions and immutable audits for per-vehicle location-source policy changes. It does not alter the gateway election SQL or expose the gateway `source_key`; the API resolves an opaque `sourceRef` server-side and the gateway applies the saved policy on the next valid vehicle report. Migration `016` adds customer name, department and responsible-person dimensions plus bounded lookup indexes to the platform-owned Scope projection. Migration `017` creates the reconciliation run, issue and immutable action tables; the unique fingerprint index is the database-level duplicate-work-item guard. Migration `018` records every OneOS-sourced brand fill before the platform identity OEM field changes; it does not grant access to OneOS or run a cross-database sync. Migration `019` stores administrator-maintained provider names by exact vehicle/protocol/source key and writes an immutable versioned audit. The browser still receives only the SHA-256 `sourceRef`; raw source keys remain server-side. Migration `020` separates the actual vehicle brand in `vehicle_profile.brand_name` from T-Box/GPS provider identity. Migration `021` adds the cross-protocol core metric catalog and correct source paths for JT808 GPS mileage, GB32960/YUTONG instrument mileage and GB32960 fuel-cell/engine telemetry. Migration `022` applies the authoritative GB/T 32960 Chinese terminology, changes business hydrogen-concentration metrics and alert thresholds from ppm to percent, and leaves the ppm field only as disabled historical evidence. Migration `032` adds structured reconciliation responsibility, assignment audit metadata, explicit due times, and owner/SLA lookup indexes; apply it before deploying an API binary that selects or writes assignment fields.
`docs/oneos-brand-backfill.sql` is an explicit one-time calibration, not a service dependency. It reads OneOS vehicle/model master data, inserts immutable source evidence, and fills only empty `vehicle_identity_binding.oem` values. Review the pre-run count, apply migration `018`, run the script once through the migration runner or a transaction-capable MySQL client, and verify its final audit/missing counts. Never overwrite a non-empty platform brand and never schedule this script; future updates belong in the formal OneOS API.
@@ -231,16 +314,19 @@ Install and start the evaluator as a separate unit only after API smoke checks a
```bash
sudo cp deploy/systemd/lingniu-vehicle-alert-evaluator.service /etc/systemd/system/
sudo cp deploy/systemd/lingniu-vehicle-alert-stream-evaluator.service /etc/systemd/system/
sudo cp deploy/systemd/lingniu-vehicle-alert-notification-dispatcher.service /etc/systemd/system/
sudo cp deploy/systemd/lingniu-vehicle-oneos-scope-sync.service /etc/systemd/system/
sudo cp deploy/systemd/lingniu-vehicle-oneos-scope-sync.timer /etc/systemd/system/
sudo cp deploy/systemd/lingniu-vehicle-reconciliation-evaluator.service /etc/systemd/system/
sudo cp deploy/systemd/lingniu-vehicle-reconciliation-evaluator.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now lingniu-vehicle-platform lingniu-vehicle-alert-evaluator lingniu-vehicle-alert-stream-evaluator
sudo systemctl enable --now lingniu-vehicle-platform lingniu-vehicle-alert-evaluator lingniu-vehicle-alert-stream-evaluator lingniu-vehicle-alert-notification-dispatcher
sudo systemctl status --no-pager lingniu-vehicle-alert-evaluator
sudo systemctl status --no-pager lingniu-vehicle-alert-stream-evaluator
sudo systemctl status --no-pager lingniu-vehicle-alert-notification-dispatcher
sudo journalctl -u lingniu-vehicle-alert-evaluator -n 100 --no-pager
sudo journalctl -u lingniu-vehicle-alert-stream-evaluator -n 100 --no-pager
sudo journalctl -u lingniu-vehicle-alert-notification-dispatcher -n 100 --no-pager
# Only after a successful manual OneOS API sync and projection audit:
sudo systemctl enable --now lingniu-vehicle-oneos-scope-sync.timer
sudo systemctl status --no-pager lingniu-vehicle-oneos-scope-sync.timer
@@ -281,6 +367,7 @@ curl -fsS -X POST -H "$AUTH_HEADER" -H 'Content-Type: application/json' \
curl -fsS -X POST -H "$AUTH_HEADER" -H 'Content-Type: application/json' -d '{"limit":5,"offset":0}' \
http://127.0.0.1:20300/api/v2/alerts/events
curl -fsS -H "$AUTH_HEADER" http://127.0.0.1:20300/api/v2/alerts/rules
curl -fsS -H "$AUTH_HEADER" 'http://127.0.0.1:20300/api/v2/alerts/rules/library?lifecycle=current&status=all&limit=10&offset=0'
curl -fsS -H "$AUTH_HEADER" 'http://127.0.0.1:20300/api/v2/alerts/notifications?unreadOnly=true&limit=5'
curl -fsS -H "$AUTH_HEADER" 'http://127.0.0.1:20300/api/v2/reconciliation/summary?days=30'
curl -fsS -X POST -H "$AUTH_HEADER" -H 'Content-Type: application/json' \

View File

@@ -0,0 +1,42 @@
# 接入身份治理连续性审计
## 结论
本轮把接入管理中原本只读的“来源身份待绑定”卡片改造成可完成、可恢复、可审计的处置任务。管理员现在可以在同一个治理工作面中核对来源证据、搜索并选择现有权威车辆、明确确认风险、提交绑定、取得审计回执,并根据车辆档案缺口继续补全主档。
## 关键变化
1. 治理列表新增“开始认领”,不再只描述问题。
2. 认领任务沿用既有治理 SideSheet桌面 640 px 宽,移动端为近全高 bottom sheet内容独立滚动返回与确认操作固定。
3. 认领分为核对来源、选择主车辆、确认并留痕三步;候选项只来自现有权威车辆,不允许手填或猜测 VIN。
4. 保存前必须勾选证据确认;说明支持最多 500 字,冲突和失效来源均有可见反馈。
5. `accessGovernance=claim&accessIdentity=...` 保存任务位置;失效深链会解释可能原因并返回治理列表。
6. 后端事务同时校验来源注册、权威 VIN 和既有手机号绑定,更新 `vehicle_identity_binding``jt808_registration`,并写入不可变 `vehicle_access_identity_audit`
7. 成功回执显示操作者、时间、审计编号、目标 VIN 和车辆档案完整度;档案不完整时直达 `/vehicles/:vin#vehicle-archive-panel`
8. 认领完成后未解析队列即时从 1 归零;关闭工作面后键盘焦点返回治理入口。
## 浏览器证据
- 桌面1440 × 1000验证治理列表、认领入口、三步任务、候选车辆、预览和固定操作区。
- 移动390 × 844验证近全高 bottom sheet、单一内容滚动、固定底栏、提交回执和档案补全入口。
- 失效深链:能展示“待认领来源已失效”及返回动作。
- 焦点:关闭治理抽屉后 `document.activeElement` 恢复为 `aria-label="打开接入治理"` 的按钮。
截图:
- `before/02-desktop-governance.png``before/04-mobile-governance.png`:只读基线。
- `after/01-desktop-governance.png``after/03-mobile-governance.png`:同状态下新增明确认领入口。
- `after/02-desktop-claim-review.png``after/04-mobile-claim-review.png`:桌面与移动端确认状态。
- `after/05-mobile-claim-success.png`:审计回执和 7 项档案待补结果。
- `comparison-before-after.png`:同视口基线与结果的组合审阅图。
## 回归
- Go`go test ./...`
- Web 定向生产入口、API 客户端与接入页面 51 项测试通过。
- Web 全量78 个测试文件、485 项测试通过。
- 构建:`tsc -b`、Vite production build、发布资产校验通过接入页产物约 44.22 kB / gzip 13.94 kB。
## 无障碍边界
已验证对话框名称、步骤/候选列表语义、必选确认、禁用提交、状态回执、失效恢复和关闭后的焦点返回。当前自动化仍会报告 Semi UI 内部 `findDOMNode` 弃用警告;尚未完成 VoiceOver 人工通读、强制色彩模式和浏览器缩放 200% 的整轮人工验收,这些不作为本轮已完成能力声明。

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

View File

@@ -0,0 +1,36 @@
# 账号授权连续性验收
## 结果
账号管理已补齐大规模权限配置中的三个关键保护:草稿差异持续可见、权限收回保存前展示精确影响、切换客户或离开页面前阻止未保存内容丢失。详情内新增“切换客户”入口,桌面和移动端都无需先退出配置工作区。
## 验收证据
- [桌面权限差异预览](./desktop-access-diff.jpg)1440 × 900菜单与车辆收回同时显示保存操作固定在底部。
- [桌面草稿切换确认](./desktop-switch-draft.jpg)1440 × 900显示当前草稿、目标客户与服务器影响。
- [移动端权限收回确认](./mobile-access-reduction-confirm.jpg)390 × 844确认框在首屏内完整呈现“收回 1 个菜单、移除 1 辆车”。
## 设计对照
采用已实现稿作为接受基线:
- `../ui-ux-20260720/desktop-users-implemented.png`
- `../ui-ux-20260720/mobile-users-implemented.png`
通过真实浏览器截图与原图检查完成五点对照:
1. 信息架构:保留“目录 → 详情 → 登录身份/菜单/车辆”的既有层级,只在详情顶部增加可达的客户切换入口。
2. 视觉密度:差异条和切换条各占一行,未引入新的卡片嵌套;主要操作仍固定在底部。
3. 任务连续性:草稿、切换、关闭和权限收回均明确说明影响,保存后详情继续保持打开。
4. 状态语义:新增使用蓝/绿/橙/红表达新增、调整、收回与停用,危险动作仍使用独立确认层。
5. 响应式与可访问性1440 × 900 和 390 × 844 均无横向溢出;切换器、差异区、确认框均有可识别名称和键盘语义。
首屏固定文案与接受基线一致;新增文案仅在账号详情和动态草稿状态中出现,没有改变页面入口含义。检查未发现需要继续修正的实质性偏差。
## 验证
- `UsersPage.test.tsx`14 / 14 通过。
- Web 全量测试75 个文件、445 / 445 通过。
- 生产构建:通过,`web_build_gate=ok`78 个发布资源。
- 已知输出仅为 React Router 未来版本提示、Semi UI `findDOMNode` 提示和 Sass `@import` 弃用提示,不影响本轮功能。

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -0,0 +1,49 @@
# 账号批量导入失败恢复审计
## 结论
当前健康度:良好。账号管理已经从“只能逐个创建”升级为可恢复的批量任务流:管理员可以下载模板、上传 CSV、在创建前完成服务端预检、原位修复单行问题并只创建校验通过的账号。部分成功时保留失败项不要求重新上传整份文件。
## 1. 基线:入口与任务能力
基线页只有“新建客户账号”,批量开户需要重复进入表单,错误也无法集中定位。
![改造前账号目录](before/01-account-directory-desktop.jpg)
## 2. 桌面端:预检优先
桌面端使用 900px 宽任务抽屉同时容纳文件摘要、预检状态、逐行结果和固定底部操作。测试文件共 3 行1 行可创建、1 行用户名冲突、1 行车辆未接入。
![桌面端批量预检](after/01-account-import-preflight-desktop.jpg)
## 3. 桌面端:原位修复
首个失败项会自动展开,管理员可直接修改账号、密码、状态、菜单和 VIN修改后重新预检不丢失已解析的其他行。
![桌面端逐行修复](after/02-account-import-repair-desktop.jpg)
## 4. 移动端:单滚动任务面板
390 × 844 下切换为 94dvh 底部任务面板,摘要、结果和修复区使用同一内容滚动,主操作固定在底部。实测弹层宽 390px、文档宽 390px无横向溢出。
![移动端批量预检](after/03-account-import-preflight-mobile.jpg)
## 5. 行为与恢复规则
- CSV 表头固定为 `username,displayName,password,status,customerRef,tenantRef,menuKeys,vehicleVins`,每次最多 50 个账号。
- 预检复用单账号创建的用户名、密码、菜单和 VIN 校验规则,并检测文件内重复及库内用户名冲突。
- 创建按行隔离事务;单行失败不会回滚已成功账号,结果保留行号、错误类型和修复入口。
- 部分成功后,主操作明确为“仅保留 N 个失败项”;目录刷新后仍保留原搜索、状态、页码与导入窗口 URL 状态。
- 直接访问 `?userImport=open` 可恢复导入工作区;关闭窗口只移除导入参数。
## 6. 无障碍与验证
- 弹层具有“批量导入客户账号”名称,关闭按钮具有中文动作名称;区域、结果列表、字段和主操作均可由语义树识别。
- 已验证桌面和移动端主链路、深链恢复、文件上传、服务端预检、逐行修复和创建回执。
- 自动化测试覆盖 CSV 引号解析、模板输出、URL 保持、预检、修复与创建;全量前端 76 个文件、467 个测试通过API 全量测试通过,生产构建门禁通过。
- 当前限制:本轮未用专用读屏器完成逐键走查,也未用仪器量化所有状态色对比度;键盘语义以浏览器语义树和组件自动化测试为依据。
- 沿用平台现有 Semi UI、图标、颜色和任务抽屉系统因此没有生成新的位图或装饰资产。
## 后续
账号模块下一项是外部身份源接入反馈;平台级下一轮优先处理历史导出保留期、账号级字段视图同步与大任务分页。

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -0,0 +1,29 @@
# 账号目录与编辑深链复核
## 本轮结果
1. 账号目录搜索、状态、页码与每页数量写入 URL刷新、分享与前进/后退可恢复工作位置。
2. 选中账号与登录身份、菜单权限、车辆权限编辑阶段可被精确还原;桌面侧栏与移动端全屏工作区使用同一来源。
3. 目录由一次性长列表改为 10/20/50 个账号分页,移动端固定为每页 10 个,避免超长滚动。
4. 深链账号已删除时显示“账号链接已失效”及清除入口;账号被当前筛选隐藏时保留详情并提示回到所在目录。
5. 账号内部切换和关闭仍复用原有未保存草稿确认,不因 URL 化绕过风险保护。
## 视觉证据
- `before/01-directory-desktop.jpg`:基线长目录,无页码与工作位置恢复提示。
- `after/01-deep-linked-editor-desktop.jpg`:第 2 页账号直接恢复到车辆权限阶段。
- `after/02-invalid-link-recovery-desktop.jpg`:失效账号链接修复提示。
- `after/03-deep-linked-editor-mobile.jpg`:移动端直接恢复到菜单权限阶段,主操作保持固定。
## 验证范围
- 桌面1440 × 1000。
- 移动端390 × 844。
- 自动化:账号页 17 个测试,包括路由规范化、深链恢复、失效链接修复和既有授权编辑流程。
- 可访问性:新增失效链接使用 `role="alert"`,范围外详情使用 `role="status"`;分页与编辑页签保留原生可访问名称。
## 已知限制
- 外部身份源接入仍只展示来源标签,尚未覆盖绑定失败、重新认证与冲突合并流程。
- 批量账号导入尚未建立逐行失败恢复和可下载错误报告。
- 浏览器视觉复核覆盖关键桌面/移动状态,未替代真实读屏器与全键盘人工走查。

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -0,0 +1,35 @@
# 账号外部身份源连续性审计
审计日期2026-07-23。范围覆盖账号目录、外部身份详情、身份映射缺失、桌面侧栏和移动端底部侧栏。
## 结论
1. 修复了本地接口 404 被伪装成“还没有客户账号”的异步状态错误。真实失败现在明确说明“这不是空目录”,保留筛选并提供原位重试。
2. `DATA_MODE=mock` 且未启用 SQL 身份库时提供可操作的本地账号目录,覆盖本地账号、已连接 OneOS、缺少映射的 RuoYi 和停用账号;创建、编辑、批量预检/创建仍可在本地验证。
3. 目录新增本地/外部身份数量与“身份待关联”状态;身份源进入搜索范围,缺少映射不再被菜单或车辆就绪状态掩盖。
4. 编辑器展示身份源、脱敏外部主体、映射状态与凭据归属。外部登录密码、锁定和多因素认证明确归属上游,本平台继续维护启停、菜单和车辆范围。
5. 外部身份密码输入在前端只读,生产 SQL 路径与本地演示路径都以 `EXTERNAL_IDENTITY_PASSWORD_READ_ONLY` 拒绝越界写入。
## 当前运行证据
- 基线桌面:[目录错误被伪装为空目录](before/01-desktop-directory-error-as-empty.png)
- 基线移动端:[移动目录错误被伪装为空目录](before/02-mobile-directory-error-as-empty.png)
- 改进桌面:[真实账号目录与身份状态](after/01-desktop-directory.png)
- 改进桌面:[OneOS 映射与凭据归属](after/02-desktop-external-identity.png)
- 改进桌面:[RuoYi 映射缺失与处置说明](after/03-desktop-identity-warning.png)
- 改进移动端:[紧凑账号目录](after/04-mobile-directory.png)
- 改进移动端:[外部身份详情](after/05-mobile-external-identity.png)
- 同屏对照:[桌面前后对照](comparison-desktop-before-after.png) · [移动端前后对照](comparison-mobile-before-after.png)
## 验证
- 1440 × 980目录、已连接外部身份、缺失映射告警均通过真实浏览器检查。
- 390 × 844目录单滚动、详情固定操作区和外部身份摘要通过真实浏览器检查。
- 语义:身份映射使用 `status` / `alert`;外部凭据输入为 disabled目录条目无障碍名称包含身份源与映射状态从目录打开后关闭详情会把焦点返回原账号按钮。
- 自动化:`UsersPage.test.tsx` 20 项通过Web 生产构建通过;`apps/api/internal/app` 测试通过。
## 已知边界
- 当前没有可写的上游身份适配器管理接口,因此缺失映射只能说明上游/适配器处置边界,不能在本平台直接补写外部主体。
- 外部身份真实同步延迟、重复主体冲突和上游停用回流仍需要生产身份源样本验证。
- 本轮完成语义 DOM、键盘焦点和禁用态验证未执行 VoiceOver/NVDA 人工听读;由深链直接打开详情时没有原始触发按钮,关闭后焦点回到页面主体。

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -0,0 +1,34 @@
# 自动化创建工作区审计
审计日期2026-07-23
## 结论
- 编辑器尺寸、单滚动区和固定操作栏已经满足桌面与移动端任务工作区要求,本轮保留原有结构。
- 原首步以空规则名打开,立即显示“请填写自动化名称”并禁用下一步;移动端只看到灰色按钮,原因不够清晰。
- 新首步默认生成 `速度 > 80 km/h`,条件变化会同步推荐名称和实时摘要,下一步立即可用。
- 用户手动改名后不再被条件变化覆盖,并可通过“恢复推荐名称”重新启用自动同步。
- 模板、既有规则编辑和复制副本继续保留其显式业务名称。
## 证据
1. `01-before-automation-list.png`桌面自动化列表基线1440 × 900。
2. `02-before-create-step1.png`桌面创建首步改进前1440 × 900。
3. `03-before-create-mobile.png`移动创建首步改进前390 × 844。
4. `04-after-create-mobile.png`移动创建首步改进后390 × 844。
5. `05-after-create-desktop.png`桌面创建首步改进后1440 × 900。
6. `06-before-after-desktop-comparison.jpg`:同视口前后并排比较;左侧为空名称、即时错误和禁用按钮,右侧为推荐名称、同步摘要和可用主操作。
## 验证
- `AlertsPage.test.tsx`42/42。
- Web 正式测试504/504。
- Go API`go test ./...` 全部通过。
- 生产构建:`web_build_gate=ok`
- 真实交互390 × 844 下将阈值从 80 改为 90规则名称同步为 `速度 > 90 km/h`,底部“下一步:车辆范围”保持可用。
## 无障碍边界
- 规则名称保留显式文本标签,提示文本不额外抢占 `status` 实时区域,草稿状态仍是编辑器内唯一状态播报。
- 步骤导航、主操作和关闭/放弃草稿流程均保留原有语义与键盘焦点管理。
- 本轮通过 DOM 无障碍快照与键盘可达控件检查;尚未执行独立读屏软件的端到端人工朗读测试。

View File

@@ -0,0 +1,66 @@
# 自动化配置工作区重构
## 目标
把原本偏小、依赖整窗滚动的自动化弹窗改成可连续完成“事件与条件 → 车辆范围 → 执行动作 → 测试并发布”的近全屏工作区。桌面端让步骤、当前表单、实时摘要和主操作同屏;移动端固定步骤与操作区,只滚动当前表单。
## 设计与实现证据
- `automation-editor-desktop-concept.png`:使用内置 Image Gen 生成并选定的 1440 × 900 高保真设计基准。
- `automation-editor-desktop-implementation.png`:真实 React / Semi UI 在 1440 × 900 的桌面实现。
- `automation-editor-mobile-implementation.png`:真实页面在 390 × 844 的移动实现。
内置 Image Gen 最终提示以 `ui-mockup` 为用途,要求近全屏弹窗、固定头尾、四步横向导航、左侧当前表单、右侧 320px 实时摘要,并明确禁止小型居中弹窗、卡片墙、渐变、装饰图和可见外层滚动条。
## 实现结果
- 桌面弹窗由最多 1068px 扩展为最多 1360px视口四周保留 12px 安全边距;最大高度使用 `100dvh - 24px`
- 弹窗头部、步骤导航和底部操作区固定;常规数值规则在 1440 × 900 无需滚动即可完成第一步。
- 左侧表单只在围栏地图或车辆长列表等确有需要时独立滚动;右侧摘要始终保持可见。
- 四个触发类型在桌面改为单行排列,减少无意义纵向占用。
- 校验和保存错误进入固定底部操作区,不再隐藏在滚动内容末端。
- 移动端高度提升到最多 `96dvh`;步骤导航由两行 112px 压缩为单行 58px页面本身不滚动当前表单是唯一滚动容器。
- 移动端摘要保留紧凑标题,隐藏重复的三段明细与发布说明;完整摘要仍在测试发布步骤呈现。
- 自动化搜索、状态、协议与选中规则写入 URL刷新、分享和前进/后退可以恢复工作位置。
- 筛选隐藏当前规则时自动修复为首个可见规则;没有匹配项时清空详情,避免“列表为空但仍显示旧规则”的幽灵详情。
## Fidelity ledger
| 检查项 | 概念要求 | 实现结果 |
| --- | --- | --- |
| 工作区尺寸 | 接近填满 1440 × 900 | 已实现1360 × 876四周 12px |
| 信息同屏 | 步骤、表单、摘要、操作同屏 | 已实现;桌面第一步无需滚动 |
| 滚动边界 | 外层不滚动,长内容局部滚动 | 已实现;桌面页面 900/900移动页面 844/844 |
| 表单效率 | 四触发类型单行,字段双列 | 已实现;常规规则首屏完整 |
| 实时摘要 | 约 320px、持续可见 | 已实现;桌面 320px × 545px |
| 固定操作 | 取消、上一步、下一步持续可见 | 已实现;桌面与移动均固定在底部 |
| 移动导航 | 紧凑步骤与单一内容滚动 | 已实现58px 单行步骤,表单局部滚动 |
| 视觉系统 | 白、冷灰、克制蓝、细边框 | 已实现;沿用平台与 Semi UI 令牌 |
| 路由恢复 | 配置入口外的工作位置可分享 | 已实现;筛选和选中规则由 URL 驱动 |
实现与概念在主要结构、密度、尺寸、固定边界和视觉层级上忠实一致,没有实质性偏差。
## 首屏文案对比
- 概念使用“数值或状态 / 车辆静止 / 车辆离线”;实现保留领域内更精确的“数值触发 / 长时间静止 / 长时间离线”。
- 概念使用“大于”;实现继续显示 API 契约操作符 `>`,避免编辑与事件契约出现两套语义。
- 概念使用“60 秒”;实现同时显示输入值 `60` 与格式化辅助文本“1 分钟”,便于直接编辑又便于理解。
## 有意差异
- 实现保留现有平台导航与真实表单控件,不复制概念图中的装饰性触发图标。
- 移动端不常驻完整实时摘要,因为它会重复当前表单信息并增加约 300px 滚动;紧凑标题与最终测试摘要仍保留。
- 围栏地图、车辆快捷选择属于真实长内容,允许左侧当前表单独立滚动,不能为追求“零滚动”裁剪功能。
## 验证
- `AlertsPage.test.tsx`35 项测试通过,包括创建入口、四步流程、移动 bottom sheet、桌面 review、URL 恢复、幽灵详情修复和未保存草稿保护。
- 事件中心与历史页定向回归63 项测试全部通过;全量前端 75 个文件 / 442 项测试通过;生产构建及发布资产校验通过。
- 桌面 1440 × 900弹窗 1360 × 876左侧表单 `scrollHeight = clientHeight = 543`;页面无纵向滚动;切换车辆范围后底部操作与摘要位置不变。
- 移动 390 × 844弹层 390 × 810步骤栏 58px页面无纵向滚动第一步当前表单只需 127px 局部滚动。
- 桌面与移动均实际填写名称并进入“车辆范围”,步骤标题、固定操作区和摘要/导航位置保持稳定。
- 概念图和两张实现截图均以原始分辨率复核。
## Image Gen 说明
设计基准使用内置 Image Gen 模式生成,没有使用 CLI 回退。最终项目资产保存在本目录的 `automation-editor-desktop-concept.png`

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View File

@@ -0,0 +1,28 @@
# 自动化规则治理审计2026-07-23
## 结果
- 规则库检索、状态、协议、当前/归档范围、摘要与 10/20/50 条分页均由服务端执行,前端不再加载完整规则集后本地筛选。
- 当前规则和审计归档使用同一规则数据源与 URL 状态;归档范围不显示编辑、启停或版本恢复写操作。
- 归档只接受已停用规则,要求当前版本和 4200 字原因;归档会清理评估器候选与状态,但保留配置、版本、事件与通知。
- 恢复同样要求当前版本与原因,并明确保持停用;归档与恢复都写入不可变版本审计。
- 规则库、创建/更新、启停、归档与恢复均为管理员能力;评估器与兼容规则列表主动排除归档规则。
## 验证
- `go test ./...`:后端完整测试通过。
- `pnpm exec vitest run src/api/client.test.ts src/v2/pages/AlertsPage.test.tsx`82 项定向测试通过。
- `pnpm test -- --run`:前端 78 个测试文件、522 项测试全部通过。
- `pnpm build`TypeScript、Vite 生产构建和发布资产门禁通过,共生成 83 项发布资源。
- `bash deploy/install-web-release.test.sh`:发布安装脚本测试通过,并覆盖迁移 `036_alert_rule_archive.sql`
- 本地 Mock API 已验证 `/api/v2/alerts/rules/library` 返回完整范围摘要和分页契约,并以一次性 Mock 数据验证归档回执。
- Codex 内置浏览器连接不可用,因此没有使用其他浏览器替代,也没有生成“改造后”截图冒充真实验收结果。
## 基线证据
- `01-rule-library-before-desktop.png`:改造前桌面规则库,仅有客户端筛选与日常规则列表。
- 待内置浏览器恢复后补测 1440×900 桌面布局、390×844 移动布局、归档/恢复 URL 回放和键盘焦点顺序。
## 发布约束
先执行 `deploy/migrations/036_alert_rule_archive.sql`,再发布 API/Web 并重启两个告警评估器。归档演示和浏览器验收只能使用 Mock 数据,禁止在生产规则上为了截图执行生命周期写操作。

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,27 @@
# 自动化运行记录范围审计
## 问题
自动化详情曾请求全局最近 6 条事件,再在浏览器中按当前规则过滤。高频规则会挤掉其他规则的真实命中,导致详情错误显示“暂无运行记录”。
## 修复
1. 运行记录查询随当前选中规则发送 `ruleId`,由事件服务在完整授权范围内筛选后返回最近 6 条。
2. 标题明确展示“当前自动化最近 n / 总数”,避免把预览误解为全部记录。
3. 桌面详情和移动端摘要都提供“运行记录”入口,进入带规则筛选的完整事件流,并保留返回原自动化的上下文。
## 视觉证据
- `01-before-global-six-run-scope-desktop.png`:修复前的模糊范围说明。
- `02-after-rule-scoped-run-history-desktop.png`:修复后的当前规则计数与完整入口。
- `03-rule-filtered-event-stream-desktop.png`:桌面完整事件流与返回上下文。
- `04-rule-run-entry-mobile.png`:移动端详情中的运行记录入口。
- `05-before-after-run-scope-comparison.png`:桌面前后对比。
- `06-rule-filtered-event-stream-mobile.png`:移动端完整事件流。
## 验证
- `AlertsPage.test.tsx`44 项通过,新增规则范围查询与完整事件流 URL 回归。
- Web 正式套件510 项通过。
- API`go test ./...` 全部通过。
- 生产构建Web、开放门户和 API 二进制全部通过。

View File

@@ -0,0 +1,68 @@
# 自动化版本历史与安全恢复验收
## 结论
自动化从“只能看到当前 vN”补齐为可审计、可比较、可安全恢复的版本工作流。恢复不会覆盖旧记录而是生成新版本恢复配置时始终保持当前启停状态避免历史版本意外启动或停止真实自动化。
## 审计步骤
1. **入口与可发现性**:桌面详情头新增“版本历史”;移动列表新增独立 42 × 42 px 历史按钮,未挤占规则主点击区。
2. **信息层级**:侧栏先给出当前版本、运行状态、历史数量,再按时间倒序展示版本、动作、操作人、时间、范围和执行动作。
3. **差异与决策**:每个历史版本标明与当前配置的实质差异数;配置相同的版本显示“无需恢复”,不提供无效操作。
4. **高风险确认**:恢复前明确目标版本、新版本号、变更数和“保持启用/停用”;说明确认前不会写入事件或发送真实通知。
5. **结果连续性**:恢复成功后列表、详情和版本侧栏原位更新,新产生的 `rollback` 版本出现在时间线顶部。
## 实现边界
- 后端新增版本列表与恢复 API并继续复用乐观锁当前版本变化时拒绝过期恢复请求。
- 审计快照兼容既有 create/update/enable/disable 记录,新增 rollback 动作。
- 恢复只复制规则配置,启停状态取当前线上状态。
- 管理员可以查看和恢复;恢复路由显式保持管理员权限。
- 桌面侧栏与移动 bottom sheet 共用一套数据和操作语义。
## 截图证据
### 改造前
- `audit-before/01-automation-detail-desktop.jpg`:桌面仅显示当前版本号,无历史入口。
- `audit-before/02-automation-list-mobile.jpg`:移动端仅有复制与启停入口。
### 改造后
- `audit-after/01-version-history-desktop.jpg`:桌面版本时间线与差异状态。
- `audit-after/02-rollback-confirm-desktop.jpg`:恢复影响确认。
- `audit-after/03-rollback-success-desktop.jpg`:恢复后生成新版本并原位刷新。
- `audit-after/04-version-history-mobile.jpg`:移动端全高版本工作区。
## 视觉保真检查5 点)
1. **结构**:保持事件中心原有“列表 + 常驻详情”,版本能力作为右侧任务层叠加,不改主导航。
2. **布局**:桌面 560 px 侧栏;移动使用 94dvh bottom sheet、固定底部操作区和单一内容滚动区。
3. **样式**复用平台蓝、现有边框、810 px 圆角、Semi 图标和 Workspace 组件,没有引入第二套视觉语言。
4. **交互**:桌面和移动均可查看、选择版本、确认恢复、看到结果;相同版本不显示恢复按钮。
5. **响应式**1440 × 900 与 390 × 844 均无横向溢出;移动触控入口和主要按钮不小于 42 px。
## 首屏文案差异
- 保留:`事件中心``自动化列表`、规则名称、运行状态与创建/编辑入口。
- 新增:桌面详情头的 `版本历史`;移动端使用图标按钮并提供完整可访问名称 `查看版本历史:{规则名}`
- 未新增冗余说明卡;审计和风险文案只在版本工作区与确认步骤出现。
## 验证
- Go 全量测试:`go test ./...` 通过。
- Web 正式测试集75 个文件、455 个测试通过。
- Web 生产构建与发布资产门禁通过。
- 真实浏览器完成桌面与移动入口、版本列表、恢复确认、恢复成功回写检查。
- 额外运行的旧版 `src/test/App.test.tsx` 存在 14 个与本次改动无关的既有里程/旧路由断言失败;正式测试脚本按项目配置排除该旧版套件。
## 无障碍检查与限制
- 版本面板、摘要、时间线、确认框和所有按钮均有角色与可访问名称;移动图标按钮不依赖图标含义。
- 状态不是只靠颜色表达,同时提供“当前版本”“差异”“无需恢复”“保持启用/停用”等文本。
- 浏览器 DOM 验证确认对话框焦点与关闭入口可达。
- 本轮未使用实体屏幕阅读器或硬件触控设备;语义、焦点与 390 px 模拟触控尺寸已验证,实体设备朗读顺序仍应在发布前抽检。
## 健康度
**健康,可发布(有已知非阻塞限制)**:核心任务链、并发保护、角色权限、桌面/移动布局、空/错/成功状态均已验证;剩余限制是实体辅助技术抽检,以及平台审计队列中的历史批量操作、差异批量处置和账号深链,继续放入下一轮。

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -0,0 +1,46 @@
# 事件中心重构规格
## 产品结论
“告警”不是一级数据模型,而是事件的一种处置策略。三类协议统一进入事件域:
`协议事件源 → 标准事件契约 → 自动化匹配 → 动作/通知 → 运行记录`
事件中心只保留三个一级任务:
1. **事件流**:查看所有业务事件,只有需要人工介入的事件标记为“待关注”。
2. **自动化**:用“当、如果、就”配置触发、范围和动作。
3. **通知记录**:审计通知送达与已读状态,不与事件处置混为一谈。
## 三类协议
| 协议 | 事件源角色 | 标准事件类型示例 |
| --- | --- | --- |
| GB/T 32960 | 整车与新能源遥测 | `vehicle.telemetry.reported` |
| JT/T 808 | 位置、行驶与终端状态 | `vehicle.location.reported` |
| 宇通 MQTT | 厂商扩展遥测 | `vehicle.oem.telemetry.reported` |
统一事件最小契约:`event.type``source.protocol``subject.vin``occurred_at``received_at`。协议专有字段保留在 payload 中,不渗透到自动化框架。
## 参考模式
- PagerDuty Event Orchestration事件进入后按内容匹配嵌套规则并执行动作。
- Amazon EventBridge事件源进入总线规则匹配后路由到一个或多个目标。
- Grafana Alerting规则判断、通知策略和联系点分离。
本产品采用相同分层,但保留车辆业务语义和协议证据。
## 视觉系统
- 背景:真白与冷灰,不使用暖白或渐变光晕。
- 主色:羚牛蓝;红/橙仅用于确实需要关注的事件或执行失败。
- 容器:列表、轨道、分栏和表格优先,避免卡片墙与多层嵌套。
- 签名组件:接收 → 匹配 → 动作 → 结果的细蓝色执行轨迹。
- 桌面主工作区:事件流 70%,检查器 30%;移动端检查器变为底部 SideSheet。
## 视觉概念
- `event-stream-concept.png`:事件流与执行检查器。
- `automation-concept.png`:自动化主从布局、三阶段流程与运行记录。
图片仅是布局与视觉规格;文本、表格、按钮和交互均由 React 组件实现。

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -0,0 +1,89 @@
# 事件中心生产可用性审计
审计日期2026-07-22
审计范围:事件流、事件详情、自动化列表、自动化编辑器、通知记录、移动端事件流
目标用户:重要客户的一线值守、运营管理员与平台管理员
## 用户任务
1. 在大量车辆事件中快速找到需要人工处理的事件。
2. 判断事件是否可信,理解车辆、协议、观测值、规则和发生时间。
3. 接手、补充说明、完成或忽略事件,并追溯每一步操作。
4. 配置可理解、可验证、可审计的事件自动化。
5. 区分事件状态、自动化执行状态和通知送达状态。
## 当前截图证据
截图保存在:
`/Users/lingniu/.codex/visualizations/2026/07/22/019f8768-39da-7c50-a074-6c21efb5a42e/event-center-audit-2/`
1. `01-event-stream-current.jpg`:事件流默认状态。
2. `02-event-detail-current.jpg`:事件详情展开状态。
3. `03-automation-current.jpg`:自动化列表与详情。
4. `04-automation-editor-current.jpg`:自动化创建侧栏。
5. `05-notifications-current.jpg`:通知记录。
6. `06-mobile-event-stream-current.jpg`390 × 844 移动端事件流。
## 已确认的优点
- 已经把 GB/T 32960、JT/T 808、宇通 MQTT 抽象到统一事件模型。
- 事件、自动化规则、通知记录已经拥有独立的数据状态和 API。
- 事件详情具备观测值、匹配条件、动作历史、备注和车辆/轨迹/原始数据入口。
- 自动化支持数值、电子围栏、长时间静止、长时间离线四类触发,并有版本控制。
- 移动端已经改为列表而不是压缩桌面表格。
## 生产阻塞
### P0通知记录布局失效
通知卡片的标题、正文、通道和时间被拆成横向三列,正文所在区域出现大面积空白,通道标签被压缩为“站...”。这不是视觉偏好,而是信息结构错误;客户无法快速确认“什么事件、通过什么通道、何时送达、是否已读”。
### P0事件详情的处置动作不在稳定可见区
详情侧栏宽度不足VIN 与表格字段被截断;关键处置输入和动作位于首屏下方,同时底部又固定了车辆、轨迹、原始事件三个导航动作。值守人员打开事件后不能立即完成主任务。
### P1事件前置控制层过多
用户在看到第一条事件前依次经过:页面分区、筛选栏、状态统计条、协议接入条。状态统计和协议接入信息重复占据垂直空间,却没有直接帮助当前处置任务。
### P1自动化工作区同时展示过多层级
运行概览、规则列表、推荐模板、流程、运行记录和事件契约同时争夺注意力;推荐模板在窄列中逐字换行。事件契约对管理员有价值,但不应默认占据主要工作区。
### P1自动化编辑器缺少分步完成感
编辑器把“当、如果、就”的全部字段放进一条长滚动侧栏。保存按钮与当前错误距离过远,用户无法快速知道还差哪一步,也没有样本事件匹配测试。
### P1排版与可访问性风险
- 多处说明文字接近 1011px浅灰文字对比度偏低。
- 状态同时依赖颜色、圆点和极小文字,长时间值守可读性不足。
- 详情与编辑器需要继续验证键盘焦点顺序、焦点锁定、Esc 关闭与状态变更播报。
- 截图不能证明完整 WCAG 合规;仍需语义、键盘和自动化可访问性测试。
## 成熟产品带来的结构原则
- Datadog Events Explorer搜索、分面、保存视图和事件侧栏是一套连续探索流程而不是独立的仪表盘区块。
<https://docs.datadoghq.com/events/explorer/navigate/>
- PagerDuty事件详情必须把状态变化、人工动作和通知统一放入可过滤时间线。
<https://support.pagerduty.com/main/docs/incidents>
- Sentry列表负责筛选和优先级详情活动流负责完整生命周期。
<https://docs.sentry.io/product/issues/issue-details/>
- Grafana Alerting规则与由规则产生的事件实例是两个不同对象状态生命周期也必须分开表达。
<https://grafana.com/docs/grafana/latest/alerting/fundamentals/>
- Amazon EventBridge自动化核心是事件模式匹配和明确目标并需要用样本事件测试匹配结果。
<https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html>
## 重构验收要求
1. 默认首屏直接呈现事件,不再展示独立 KPI 与协议接入统计带。
2. 事件搜索、状态视图、协议、类型和时间范围构成一套统一查询模型。
3. 事件详情默认展示:身份、证据、规则、时间线和主处置动作;主动作无需滚动即可完成。
4. 状态采用“待处理 / 处理中 / 已恢复 / 已完成 / 已忽略”,事件状态、自动化运行、通知送达互不混用。
5. 自动化采用“事件触发 → 条件/范围 → 单一动作”的可测试编辑流程,并保留版本审计。
6. 通知改为正常的审计列表,明确事件、严重度、通道、送达时间、阅读状态。
7. 桌面、1024px 笔记本和 390px 移动端均无横向溢出、截断主操作或逐字换行。
8. 加载、空数据、错误、无权限、保存冲突和动作失败均有明确可恢复反馈。
9. 核心流程具备键盘可达、可见焦点、语义标签和状态更新播报。
10. 生产构建、前端测试、API 测试、视觉对照和 ECS 发布门禁全部通过后才可交付。

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -0,0 +1,106 @@
# 事件中心生产重构 Design QA
## Automation Editor Fidelity Correction — 2026-07-22
- reported issue: the production implementation opened desktop automation creation/editing in a full-height right SideSheet, while the selected source uses a centered modal.
- source: `automation-editor-desktop.png` (`1487 × 1058`, edit/review state after a successful sample test).
- implementation: `qa/automation-editor-modal-final-desktop.png` (`1487 × 1058`, the same edit/review/test-success state).
- combined comparison: `qa/compare-automation-editor-modal-final.png`, source left and implementation right, inspected at original detail.
- focused geometry: centered `1068 × 936` modal at the source viewport; at `1280 × 720` it remains centered and contracts to `1068 × 672` without document overflow.
- interaction: existing desktop automations open directly in review/test mode; create/template flows enter editable configuration first; publishing is disabled until the sample event test succeeds; mobile retains the bottom SideSheet.
- accessibility: the dialog, configuration summary, sample event test, execution trace, close control, edit controls and publishing controls all have stable accessible names.
- verification: `AlertsPage.test.tsx` passed `15 / 15`; TypeScript, Vite production build and artifact gate passed.
- production release: `automation-modal-fidelity-20260722145249`; ECS Web archive SHA-256 matched locally/remotely, the atomic Web installer passed its asset smoke, authenticated health reported this exact release, and the platform plus both alert evaluators remained active.
- intentional deviation: the footer uses `返回编辑` instead of the source's `保存草稿`, because the current API has no draft lifecycle and the UI does not pretend to persist one.
- pass history: P1 right-SideSheet mismatch fixed; P2 close-button alignment and unresolved modal color-token issue found during comparison, fixed and rechecked.
final result: passed
- selected direction: A「事件收件箱」
- product model: `Event → Automation → Execution → Delivery`
- protocol scope: `GB32960``JT808``YUTONG_MQTT`
- local prototype: `http://127.0.0.1:20301/alerts`
- browser method: Codex in-app Browser / IAB
- desktop viewport: `1487 × 1058`
- mobile viewport: `390 × 844`
## Visual Evidence
| Surface | Source | Implementation | Combined comparison |
| --- | --- | --- | --- |
| Event inbox | `event-inbox-desktop.png` | `qa/event-inbox-desktop.png` | `qa/compare-event-inbox.png` |
| Automation workspace | `automation-workspace-desktop.png` | `qa/automation-workspace-desktop.png` | `qa/compare-automation-workspace.png` |
| Delivery audit | `notification-audit-desktop.png` | `qa/notification-audit-desktop.png` | `qa/compare-notification-audit.png` |
| Automation editor | `automation-editor-desktop.png` | `qa/automation-editor-modal-final-desktop.png` | `qa/compare-automation-editor-modal-final.png` |
| Mobile event list | `event-inbox-mobile.png` | `qa/event-inbox-mobile.png` | same-viewport visual review |
| Mobile event detail | `event-inbox-mobile.png` | `qa/event-detail-mobile.png` | same-viewport visual review |
All paths are relative to this directory. Every combined comparison was inspected at original detail with the source on the left and implementation on the right.
## Fidelity Ledger
| Surface | Required fidelity | Implementation evidence | Result |
| --- | --- | --- | --- |
| Information architecture | The default task is to triage events, with automations and notification records as adjacent workspaces. | Text tabs preserve one clear primary task; the event stream is the default view. | Passed |
| Event contract | Alerts must be abstracted into canonical events independent of protocol. | Event rows and detail expose canonical event type, category, source protocol, source identifier, occurred/received timestamps, automation and execution state. | Passed |
| Event inbox | Operators need scan-first density and a stable inspector. | Compact table, semantic status tabs, selected-row state and a fixed `400px` inspector match the chosen direction. | Passed |
| Triage actions | Acknowledgement, completion and ignore must stay reachable. | Sticky footer actions remain visible at desktop `720px` height and in the mobile full-height sheet. | Passed |
| Automation model | Configuration should read as “when / if / then”. | Split workspace and four-step editor cover event trigger, conditions, actions, sample test and publish. | Passed |
| Execution evidence | Every event needs automation and execution traceability. | Inspector presents event facts, original event fields and execution timeline without mixing them into the list. | Passed |
| Delivery audit | Notification is a delivery result, not the event itself. | Independent delivery table and inspector expose channel, recipient, attempts, provider message ID, read state and delivery timeline. | Passed |
| Responsive continuity | Mobile must retain the same task model and actions. | `390 × 844` list and detail states use a full-height sheet, no horizontal overflow and visible actions. | Passed |
| Visual system | Reuse the existing product shell and Semi UI semantics. | Existing logo, navigation, typography, blue selection, neutral surfaces and red/orange/green status colors are preserved. | Passed |
## Interaction And Accessibility Checks
- Search, protocol filters, advanced filters, status tabs, reset and refresh are functional and keyboard-reachable.
- Event rows expose an accessible detail action; inspector and editor provide named close controls.
- Automation selection, edit, enable state and publish flow are connected to the existing API contract.
- Notification records support filter, mark-all-read and per-delivery inspection.
- Mobile list and detail have zero horizontal overflow at `390 × 844`.
- Canonical event tests, page interaction tests and production-entry tests passed `30 / 30`.
- Go API tests passed with `go test ./...`.
- Production build and route byte gate passed; vehicle route is `29,999` bytes against the `30,000` byte limit.
## Source-to-Implementation Comparison
1. Both use the same shell, cool-gray canvas, white work surfaces and brand-blue selection.
2. Both keep text tabs above a table-led event inbox, rather than competing dashboard cards.
3. Both use a narrow right inspector and preserve context while moving between events.
4. Both use semantic status color only for state and urgency, not decoration.
5. Both keep the event action bar anchored at the bottom of the inspector.
6. The implementation intentionally moves time range and scope into advanced filters to reduce the first-row query burden.
7. The implementation adds an explicit “全部事件” status tab and names the source column “协议”, improving discoverability for the three-protocol product.
8. The implementation turns the editor from a visual summary into a complete, editable four-step production form while preserving the selected visual hierarchy.
## Copy Differences
- “告警中心” becomes “事件中心” across the product surface.
- “告警列表” becomes “事件流”; “告警规则” becomes “自动化”.
- “通知” is consistently described as “通知记录 / 送达”, separating delivery from the event itself.
- Technical labels use stable contract terms such as “事件类型”, “执行状态”, “来源协议” and “服务商消息 ID”.
## Intentional Deviations And Follow-up
- The current local mock backend predates the new API binary, so a local sample can still display `vehicle.geofence.changed`; the new API normalizes this to `vehicle.geofence.exited` after release.
- The UI and contract support multiple delivery channels, while the current environment only has in-app delivery configured. Email, SMS or enterprise IM require provider credentials and are not simulated.
- The remaining full-Web-suite failures are fourteen stale mileage-page copy assertions in unrelated worktree changes. The event-center test surface, Go suite and production build are green.
## Result
No open P0, P1 or P2 issue was found in the event-center desktop, mobile, automation-editor or delivery-audit flows.
## Production Release Evidence
- release: `event-center-production-20260722120255`
- previous release: `event-center-simplified-20260722102200`
- artifact verification: local and ECS SHA-256 matched for the Web archive and Linux API binary
- release switch: immutable release directory plus atomic symlink switch; automatic rollback remained armed until all gates passed
- Web smoke: `76` current assets and `117` compatibility assets verified byte-for-byte
- authenticated health: `/api/ops/health` reported the exact new release
- runtime: platform API, alert evaluator and alert stream evaluator all remained `active`
- real event evidence: canonical events included `vehicle.connectivity.offline / connectivity / JT808 / pending` and the same event type from `YUTONG_MQTT`
- real automation evidence: `9` automations loaded, `2` enabled
- real delivery evidence: `in_app / delivered / JT808`, with vehicle VIN/plate, provider message ID and attempt count populated
final result: passed

View File

@@ -0,0 +1,105 @@
# 事件中心生产设计基线(方案 A事件收件箱
## 产品模型
事件中心不是“告警列表”。它由四个可追溯对象组成:
1. `Event`:三类协议统一抽象出的不可变事实。
2. `Automation`:按“当—如果—就”匹配事件并产生动作。
3. `Execution`:事件从接收、命中、通知到人工处置的执行轨迹。
4. `Delivery`:通知动作的排队、发送、送达、已读或失败审计。
事件页负责处理事件,自动化页负责定义规则,通知记录页只负责审计送达;三者不再混用状态。
## 视觉源
- `event-inbox-desktop.png`:桌面事件收件箱与常驻详情面板,主视觉基线。
- `automation-workspace-desktop.png`:自动化清单、逻辑画布、运行记录和契约面板。
- `automation-editor-desktop.png`:四步编辑器与样例事件测试。
- `notification-audit-desktop.png`:通知送达表与送达详情。
- `event-inbox-mobile.png`:移动端边到边事件列表与全高详情。
## 设计令牌
| 角色 | 值 | 用法 |
| --- | --- | --- |
| 页面背景 | `#F6F8FB` | 工作区底色 |
| 表面 | `#FFFFFF` | 表格、面板、抽屉 |
| 主文字 | `#17243D` | 标题、关键数据 |
| 次文字 | `#65748A` | 标签、元数据 |
| 边框 | `#E2E8F0` | 表格分隔、控件轮廓 |
| 主色 | `#1267E8` | 选中、焦点、主操作 |
| 危险 | `#D43C3C` | 待处理、失败 |
| 警示 | `#C66A16` | 地理围栏、重要提示 |
| 成功 | `#278254` | 恢复、完成、送达 |
| 中性 | `#78869A` | 忽略、禁用 |
- 仅使用真实白色表面,不使用奶油色、渐变或装饰性光晕。
- 圆角:控件 `6px`,面板 `8px`;不使用大圆角卡片容器。
- 阴影:默认无阴影,仅详情面板/模态焦点允许 `0 8px 28px rgba(23,36,61,.12)`
- 间距:`4 / 8 / 12 / 16 / 24 / 32px`;桌面主 gutter `16px`
## 字体与密度
- 字体沿用系统中文无衬线栈;正文与控件均显式定义。
- 页面标题 `20px/28px 700`;面板标题 `16px/24px 650`;正文/控件 `14px/20px 500`;元数据 `12px/18px 400`
- 表头 `12px/18px 600`;事件行最小高度 `72px`;移动事件行最小高度 `108px`
- 桌面控件高度 `40px`,移动触控目标不小于 `44px`
## 容器模型
- 顶部只保留文本标签页与单行查询工具栏。
- 事件流:主表格 + 选中时 400px 常驻详情;不再显示 KPI 条和协议来源条。
- 自动化340px 列表 rail + 逻辑画布 + 最近运行表;事件契约作为窄 inspector。
- 通知记录:单一审计表 + 400px 送达详情;禁止碎片化三列。
- 移动端:边到边列表;详情为全高 sheet处置按钮粘底。
## 事件契约
三类协议只影响来源,不改变事件顶层字段:
```text
event.id
event.type
event.category
event.title
subject.type = vehicle
subject.vin
subject.plate
source.protocol = GB32960 | JT808 | YUTONG_MQTT
source.event_id
occurred_at
received_at
automation.id
automation.version
execution.state = pending | processing | recovered | completed | ignored
payload.*
```
事件类型必须由触发语义生成,例如:
- `vehicle.geofence.exited`
- `vehicle.telemetry.soc_low`
- `vehicle.motion.acceleration_high`
- `vehicle.connectivity.offline`
- `vehicle.mileage.daily_completed`
禁止把分类和指标简单拼接成重复或实现细节型名称。
## 状态与交互
- 事件状态:`待处理 / 处理中 / 已恢复 / 已完成 / 已忽略`
- 点击事件行选中并打开详情;键盘 Enter/Space 等价。
- 过滤条件写入 URL切换标签页保留未提交草稿。
- 人工处置采用版本号并发控制,成功后同步刷新列表、汇总、详情和通知。
- 自动化编辑器:`事件 → 条件 → 动作 → 测试并发布`;发布前可运行样例事件测试。
- 通知送达与已读是独立维度;失败记录必须可见失败原因与重试入口。
- 所有异步区域覆盖加载、空、错误、重试与陈旧数据保留状态。
## 响应式与无障碍
- `>= 1180px`:表格 + 常驻 inspector。
- `7681179px`:表格全宽,详情用覆盖式 side sheet。
- `< 768px`:事件行列表 + 全高 detail sheet过滤器为 bottom sheet。
- 焦点环使用 2px 主色外框;状态不只依赖颜色;图标按钮有可读标签。
- 支持键盘、200% 文本缩放、`prefers-reduced-motion`、安全区和长 VIN/中文文本换行。

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Some files were not shown because too many files have changed in this diff Show More