Files
lingniu-vehicle-ingest/vehicle-data-platform/apps/api/internal/openplatform/assets/docs.html
T

260 lines
58 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>羚牛 · 车辆数据开放平台</title>
<style>
:root{color-scheme:light;--page:#f5f5f7;--surface:#fff;--ink:#1d1d1f;--muted:#6e6e73;--line:rgba(0,0,0,.10);--blue:#0071e3;--blue-soft:#e8f2ff;--green:#007a46;--code:#1d1d1f;--radius:18px;--sidebar:264px}
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;font:15px/1.58 -apple-system,BlinkMacSystemFont,"SF Pro Text","PingFang SC","Microsoft YaHei",sans-serif;color:var(--ink);background:var(--page);-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none}.layout{min-height:100vh;display:grid;grid-template-columns:var(--sidebar) minmax(0,1fr)}
.sidebar{position:sticky;top:0;height:100vh;padding:26px 14px 20px;border-right:1px solid var(--line);background:rgba(255,255,255,.84);backdrop-filter:blur(18px);overflow-y:auto}.brand{display:block;padding:7px 11px 27px}.brand-name{display:block;font-size:17px;font-weight:650;letter-spacing:-.02em}.brand-subtitle{display:block;margin-top:3px;color:var(--muted);font-size:12px}
.nav-group{padding:15px 0 5px;border-top:1px solid var(--line)}.nav-group:first-of-type{border-top:0;padding-top:0}.nav-title{display:block;padding:0 11px 7px;color:var(--muted);font-size:12px;font-weight:600}.nav-link{display:flex;align-items:center;gap:10px;margin:2px 0;padding:8px 11px;border-radius:9px;color:#424245;font-size:14px}.nav-link:hover{color:var(--blue);background:#f3f7fc}.nav-link::before{content:"";width:5px;height:5px;border-radius:50%;background:#c7c7cc;flex:0 0 auto}.nav-link.primary::before{background:var(--blue)}.nav-link.station::before{background:var(--green)}.nav-link.docs::before{width:6px;height:6px;border-radius:2px;background:#8e8e93}.external{display:inline-flex;align-items:center;gap:8px;margin:22px 11px 0;padding:9px 10px;color:var(--blue);font-size:13px;font-weight:550}.external svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:1.8}
.content{max-width:1200px;width:100%;padding:48px 56px 96px}.intro{padding:0 0 34px;border-bottom:1px solid var(--line)}h1{margin:0;letter-spacing:-.04em;font-size:40px;line-height:1.15;font-weight:680}.intro p{max-width:720px;margin:13px 0 0;color:var(--muted);font-size:16px}.base-url{display:inline-flex;gap:8px;align-items:center;margin-top:21px;padding:8px 11px;border:1px solid var(--line);border-radius:9px;background:var(--surface);font:12px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--muted)}.base-url code{color:var(--blue)}
.section{scroll-margin-top:26px;padding:36px 0;border-bottom:1px solid var(--line)}.section:target{margin:0 -18px;padding-right:18px;padding-left:18px;border-radius:var(--radius);background:rgba(0,113,227,.035);border-bottom-color:transparent}.section:last-child{border-bottom:0}.section-head{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}.section h2{margin:0;font-size:24px;letter-spacing:-.025em;line-height:1.25}.section p{margin:8px 0 0;color:var(--muted)}.endpoint{display:inline-flex;align-items:center;gap:8px;margin-top:15px;font:13px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;color:#38383a}.method{padding:4px 7px;border-radius:6px;background:#e4f5eb;color:var(--green);font:700 11px/1 -apple-system,BlinkMacSystemFont,"SF Pro Text",sans-serif;letter-spacing:.04em}.anchor{margin-top:4px;color:#8e8e93;font-size:12px}.anchor:hover{color:var(--blue)}
.grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(300px,.92fr);gap:34px;margin-top:23px}.subhead{margin:0 0 9px;font-size:13px;font-weight:650;color:#3a3a3c}.param-table{width:100%;border-collapse:collapse;font-size:13px}.param-table th,.param-table td{padding:10px 8px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}.param-table th{color:var(--muted);font-weight:500}.param-table td:first-child{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:#303033}.required{color:var(--green);font-size:12px}.optional{color:var(--muted);font-size:12px}.code{margin:0;overflow:auto;padding:15px;border-radius:12px;background:var(--code);color:#f5f5f7;font:12px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre}.code .key{color:#82b9ff}.code .number{color:#a8d5a2}.code .string{color:#f5cc88}.note{margin:14px 0 0;padding:11px 12px;border-left:2px solid var(--blue);background:#f4f8fd;color:#59595d;font-size:13px}.note strong{color:#303033}.errors{margin:14px 0 0;padding:0;color:#59595d;font-size:13px;list-style:none}.errors li{position:relative;padding:3px 0 3px 13px}.errors li::before{content:"";position:absolute;top:11px;left:0;width:4px;height:4px;border-radius:50%;background:#ff9f0a}.empty{margin-top:16px;color:var(--muted);font-size:13px}.field-tabs{display:flex;gap:4px;margin:0 0 14px;padding:3px;width:max-content;border-radius:9px;background:#ececf1}.field-tab{appearance:none;border:0;border-radius:7px;padding:7px 10px;background:transparent;color:#6e6e73;font:600 12px/1 -apple-system,BlinkMacSystemFont,"SF Pro Text",sans-serif;cursor:pointer}.field-tab[aria-selected="true"]{background:#fff;color:#1d1d1f;box-shadow:0 1px 3px rgba(0,0,0,.12)}.response-panel{display:none}.response-panel.active{display:block}.response-caption{margin:0 0 9px;color:var(--muted);font-size:12px}.response-table th:nth-child(1){width:31%}.response-table th:nth-child(2){width:17%}.response-table td:nth-child(2){color:#6e6e73;font:12px ui-monospace,SFMono-Regular,Menlo,monospace}.response-table td:nth-child(3){color:#4b4b4f}.tree-field{display:flex;align-items:center;gap:6px;min-height:20px;white-space:nowrap}.tree-field.depth-1{padding-left:14px}.tree-field.depth-2{padding-left:31px}.tree-field.depth-3{padding-left:48px}.tree-branch{width:9px;height:16px;border-left:1px solid #c7c7cc;border-bottom:1px solid #c7c7cc;border-radius:0 0 0 3px}.tree-root{display:inline-flex;align-items:center;gap:5px;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text",sans-serif;font-size:12px;font-weight:650;color:#303033}.tree-root::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--blue)}.tree-node{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:#303033}.tree-kind{display:inline-block;margin-left:5px;padding:1px 5px;border-radius:5px;background:#f0f1f4;color:#6e6e73;font:10px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace}.enum-value{display:inline-block;margin:2px 3px 2px 0;padding:2px 5px;border-radius:5px;background:#f1f6fd;color:#1665b5;font:11px ui-monospace,SFMono-Regular,Menlo,monospace}
.auth{margin-top:20px;padding:17px 18px;border:1px solid var(--line);border-radius:14px;background:var(--surface)}.auth code{display:block;margin-top:8px;color:#38383a;font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}.protocol-table{width:100%;margin-top:18px;border-collapse:collapse}.protocol-table th,.protocol-table td{padding:12px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}.protocol-table th{color:var(--muted);font-size:13px;font-weight:500}.protocol-table td:first-child{width:170px;font:12px ui-monospace,SFMono-Regular,Menlo,monospace}.error-table{margin-top:18px}.request-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;margin-top:21px}.request-sample{padding:15px;border:1px solid var(--line);border-radius:14px;background:var(--surface)}.request-sample h3{margin:0 0 6px;font-size:13px;font-weight:650}.request-sample .endpoint{margin:0 0 11px;font-size:11px}.request-sample .code{padding:12px;border-radius:9px;font-size:11px}@media(max-width:680px){.request-grid{grid-template-columns:1fr}.request-sample{padding:13px}}
@media(max-width:900px){:root{--sidebar:218px}.content{padding:42px 34px 76px}.grid{grid-template-columns:1fr;gap:22px}}
@media(max-width:680px){body{background:var(--surface)}.layout{display:block}.sidebar{z-index:3;display:flex;align-items:center;gap:0;position:sticky;width:100%;height:auto;padding:0 12px;border-right:0;border-bottom:1px solid var(--line);overflow-x:auto;overflow-y:hidden;white-space:nowrap;background:rgba(255,255,255,.92)}.brand{padding:13px 10px 13px 0}.brand-name{font-size:14px}.brand-subtitle,.nav-title{display:none}.nav-group{display:contents;border:0;padding:0}.nav-link{margin:0;padding:13px 9px;font-size:13px}.nav-link::before{display:none}.external{display:none}.content{padding:31px 20px 64px}.intro{padding-bottom:27px}h1{font-size:31px}.intro p{font-size:15px}.section{padding:31px 0;scroll-margin-top:54px}.section:target{margin:0 -8px;padding-right:8px;padding-left:8px}.section-head{gap:9px}.section h2{font-size:21px}.anchor{display:none}.grid{margin-top:19px}.param-table th,.param-table td{padding:9px 5px}.param-table th:nth-child(2),.param-table td:nth-child(2){width:47px}.base-url{max-width:100%;overflow:auto}.code{font-size:11px}}
</style>
</head>
<body>
<div class="layout">
<aside class="sidebar" aria-label="接口导航">
<a class="brand" href="#overview"><span class="brand-name">羚牛 · 数据开放平台</span><span class="brand-subtitle">车辆数据 API Reference</span></a>
<nav>
<div class="nav-group"><span class="nav-title">开始使用</span><a class="nav-link docs" href="#overview">平台简介</a><a class="nav-link docs" href="#authentication">认证与授权</a><a class="nav-link docs" href="#request-examples">请求体样例</a></div>
<div class="nav-group"><span class="nav-title">车辆数据</span><a class="nav-link primary" href="#daily-hydrogen">车辆单日用氢量</a><a class="nav-link primary" href="#historical-hydrogen">历史剩余氢质量</a><a class="nav-link primary" href="#daily-mileage">车辆单日里程</a><a class="nav-link primary" href="#mileage-range">车辆区间日里程</a><a class="nav-link primary" href="#total-mileage">指定时刻总里程</a><a class="nav-link primary" href="#realtime">实时位置与状态</a></div>
<div class="nav-group"><span class="nav-title">加氢服务</span><a class="nav-link station" href="#stationary">加氢车辆停留核验</a><a class="nav-link station" href="#stations">加氢站地图点位</a></div>
<div class="nav-group"><span class="nav-title">通用说明</span><a class="nav-link docs" href="#response-fields">返回字段与枚举</a><a class="nav-link docs" href="#protocols">总里程协议口径</a><a class="nav-link docs" href="#errors">异常与状态码</a></div>
</nav>
<a class="external" href="/open-api/swagger/">Swagger 在线调试 <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6 3h7v7M13 3 6.5 9.5M12 9.5V13H3V4h3.5"/></svg></a>
</aside>
<main class="content">
<header class="intro" id="overview">
<h1>接口文档</h1>
<p>面向合作方开放车辆用氢量、里程、实时状态及加氢车辆停留核验数据。每个接口独立说明请求参数、成功返回与异常情况。</p>
<span class="base-url">Base URL <code>https://open.d.lnoneos.com</code></span>
</header>
<section class="section" id="authentication"><div class="section-head"><div><h2>认证与授权</h2><p>所有接口均使用平台管理员签发的 32 位 appKey。appKey 有效期及车辆授权期必须完整覆盖查询时间。</p></div><a class="anchor" href="#authentication">#</a></div><div class="auth"><strong>请求头</strong><code>Authorization: Bearer &lt;YOUR_APP_KEY&gt;<br>Content-Type: application/json</code></div><div class="note"><strong>授权边界:</strong>不传车牌时,仅返回该 appKey 在查询日期或时间段内有效授权的全部车辆;显式传入的车牌未授权时,接口返回 403。所有接口响应均以两空格缩进的 JSON 返回,便于直接阅读和留存。</div></section>
<section class="section" id="request-examples"><div class="section-head"><div><h2>请求体样例</h2><p>所有接口使用 <code>Content-Type: application/json</code>。以下 JSON 可直接复制;未列出的可选字段可省略。</p></div><a class="anchor" href="#request-examples">#</a></div><div class="request-grid"><article class="request-sample"><h3>车辆单日用氢量</h3><div class="endpoint">/vehicles/hydrogen-consumption/query</div><pre class="code">{
"date": "2026-08-06",
"plateNumbers": ["浙F06618F"]
}</pre></article><article class="request-sample"><h3>车辆单日里程</h3><div class="endpoint">/vehicles/mileage/query</div><pre class="code">{
"date": "2026-08-06",
"plateNumbers": ["浙F06618F"],
"protocolPriority": ["GB32960", "MQTT", "JT808"]
}</pre></article><article class="request-sample"><h3>车辆区间日里程</h3><div class="endpoint">/vehicles/mileage/range/query</div><pre class="code">{
"startDate": "2026-08-01",
"endDate": "2026-08-06",
"pageSize": 100
}</pre></article><article class="request-sample"><h3>指定时刻总里程</h3><div class="endpoint">/vehicles/total-mileage/query</div><pre class="code">{
"vin": "LA9GG68L2PBAF4790",
"time": "2026-08-06 10:30:00",
"protocol": "GB32960"
}</pre></article><article class="request-sample"><h3>实时位置与状态</h3><div class="endpoint">/vehicles/realtime/query</div><pre class="code">{
"plateNumbers": ["浙F06618F"]
}</pre></article><article class="request-sample"><h3>加氢车辆停留核验</h3><div class="endpoint">/vehicles/stationary/query</div><pre class="code">{
"startTime": "2026-08-06 10:00:00",
"endTime": "2026-08-06 12:00:00",
"longitude": 120.752312,
"latitude": 30.746281,
"coordinateSystem": "GCJ02",
"radiusMeters": 8
}</pre></article><article class="request-sample"><h3>加氢站地图点位</h3><div class="endpoint">/hydrogen-stations/query</div><pre class="code">{
"province": "浙江省",
"city": "嘉兴市",
"cooperateOnly": true
}</pre></article></div></section>
<section class="section" id="historical-hydrogen"><div class="section-head"><div><h2>历史时刻剩余氢质量 · 1.10.0</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/hydrogen-remaining/history/query</div><p>仅查询2026-08-01起的真实历史 event_time,不使用未来采样、实时值或日用氢量填补。最近氢相关帧异常或字段不全时显式返回,不跳过它找旧正常值。</p></div><a class="anchor" href="#historical-hydrogen">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>queries</td><td></td><td>1200项</td></tr><tr><td>queries[].requestId</td><td></td><td>本批唯一非空,最多128字节,原样回显</td></tr><tr><td>queries[].vin / time</td><td></td><td>VIN规范化大写17位且不含I/O/Q;北京时间yyyy-MM-dd HH:mm:ss,不可未来或早于2026-08-01</td></tr><tr><td>queries[].protocol</td><td></td><td>GB32960 / YUTONG_MQTT / JT808;省略固定GB32960,后两者当前UNSUPPORTED,不跨协议回退</td></tr><tr><td>maxTimeDifferenceSeconds</td><td></td><td>整数0–300,默认300;0要求精确命中</td></tr></table><div class="note">应用与逐车授权需同时覆盖查询时刻和采样时刻(结束时间不含端点);FORBIDDEN不泄露采样元数据。当前appKey无效/过期为401,非法结构400。</div></div><div><h3 class="subhead">请求示例(非真实读数)</h3><pre class="code">{
"queries": [{
"requestId": "job-start",
"vin": "LA9GG68L2PBAF4773",
"time": "2026-08-03 16:02:43",
"protocol": "GB32960"
}],
"maxTimeDifferenceSeconds": 300
}</pre><p>合法批量HTTP200/SUCCESS,逐项状态判断:NORMAL、NO_DATA、MISSING、STALE、UNSUPPORTED、INVALID、FORBIDDEN、ERROR。非NORMAL质量null,不丢项,不补零。</p><p>每app每实例30批/60秒,最多2个并发批;429读取Retry-After秒后重试。整批9秒预算、4个工作协程,超时项ERROR而不是NO_DATA。200项是结构上限,不保证200个不同点均能按时完成;建议从20个不同点开始,超时缩小批量并退避重试。</p></div></div><div class="note">历史容量需要历史适用版本;当前表不能回套历史。当前只有REPORTED质量可用,缺历史容量版本时不估算,返回MISSING。同批按规范化VIN+time+protocol去重,同点共享单次查询/授权结果和来源版本,所有requestId独立回显;不同点仍是非原子快照,保留各项版本和原始证据。质量保留原精度,Seeker自行计算两端净减少量并在负差值时归零。</div></section>
<section class="section" id="daily-hydrogen"><div class="section-head"><div><h2>车辆单日用氢量</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/hydrogen-consumption/query</div><p>查询车辆单日用氢量,单位 kg。NORMAL + OK + PRELIMINARY 仅供初步监控;正式报表要求 FINAL 并核对证据与版本。两个日统计接口没有共同快照,区间缺失或不同不能直接计算百公里氢耗。</p></div><a class="anchor" href="#daily-hydrogen">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>date</td><td class="required"></td><td>日期,格式 yyyy-MM-dd</td></tr><tr><td>plateNumbers</td><td class="optional"></td><td>车牌数组;省略或 [] 时查询全部授权车辆</td></tr></table></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"code"</span>: <span class="string">"SUCCESS"</span>,<br> <span class="key">"data"</span>: [{<br> <span class="key">"plateNumber"</span>: <span class="string">"浙F06618F"</span>,<br> <span class="key">"hydrogenConsumptionKg"</span>: <span class="number">12.315</span>,<br> <span class="key">"status"</span>: <span class="string">"NORMAL"</span><br> }]<br>}</pre><ul class="errors"><li>400:日期或车牌格式不正确</li><li>401appKey 无效、停用或过期</li><li>403:指定车辆未授权</li></ul></div></div></section>
<section class="section" id="daily-mileage"><div class="section-head"><div><h2>车辆单日里程</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/mileage/query</div><p>返回当日行驶里程、当日累计总里程和实际选用的数据协议,单位 km。</p></div><a class="anchor" href="#daily-mileage">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>date</td><td class="required"></td><td>日期,格式 yyyy-MM-dd</td></tr><tr><td>plateNumbers</td><td class="optional"></td><td>省略时查询全部授权车辆</td></tr><tr><td>protocolPriority</td><td class="optional"></td><td>协议选源顺序,例如 ["GB32960","MQTT","JT808"]</td></tr></table><div class="note"><strong>缺数规则:</strong>日里程按同来源相邻日累计差计算;缺报日沿用累计值、日里程补 0(CARRIED_FORWARD),跨缺报期增量计入恢复日。首次基线、来源切换或累计回退返回 DATA_ANOMALY,日里程为 null。GPS 估算不参与本接口。</div></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"code"</span>: <span class="string">"SUCCESS"</span>,<br> <span class="key">"data"</span>: [{<br> <span class="key">"dailyMileageKm"</span>: <span class="number">182.437</span>,<br> <span class="key">"totalMileageKm"</span>: <span class="number">12345.679</span>,<br> <span class="key">"sourceProtocol"</span>: <span class="string">"GB32960"</span>,<br> <span class="key">"status"</span>: <span class="string">"NORMAL"</span><br> }]<br>}</pre><ul class="errors"><li>400:日期、车牌或协议参数错误</li><li>403:授权期未覆盖查询日</li><li>无统计:单车以 NO_DATA 返回</li></ul></div></div></section>
<section class="section" id="mileage-range"><div class="section-head"><div><h2>车辆区间日里程</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/mileage/range/query</div><p>按车辆、日期分页返回区间日里程,最长查询区间为 366 天。</p></div><a class="anchor" href="#mileage-range">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>startDate / endDate</td><td class="required"></td><td>日期区间,yyyy-MM-dd</td></tr><tr><td>plateNumbers</td><td class="optional"></td><td>车牌数组,最多 5000 辆</td></tr><tr><td>protocolPriority</td><td class="optional"></td><td>协议选源顺序</td></tr><tr><td>pageSize / cursor</td><td class="optional"></td><td>分页大小及下一页游标</td></tr></table></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"code"</span>: <span class="string">"SUCCESS"</span>,<br> <span class="key">"data"</span>: [{<br> <span class="key">"date"</span>: <span class="string">"2026-08-06"</span>,<br> <span class="key">"dailyMileageKm"</span>: <span class="number">182.437</span>,<br> <span class="key">"totalMileageKm"</span>: <span class="number">12345.679</span><br> }],<br> <span class="key">"nextCursor"</span>: <span class="string">null</span><br>}</pre><ul class="errors"><li>400:区间超限或游标与原参数不一致</li><li>403:授权未完整覆盖查询区间</li></ul></div></div></section>
<section class="section" id="total-mileage"><div class="section-head"><div><h2>指定时刻总里程</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/total-mileage/query</div><p>按 VIN 返回不晚于指定时刻的最近一条总里程及实际采集协议。</p></div><a class="anchor" href="#total-mileage">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>vin</td><td class="required"></td><td>17 位已授权 VIN</td></tr><tr><td>time</td><td class="required"></td><td>北京时间 yyyy-MM-dd HH:mm:ss</td></tr><tr><td>protocol</td><td class="optional"></td><td>GB32960、MQTT 或 JT808;省略时按默认顺序选取</td></tr></table></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"code"</span>: <span class="string">"SUCCESS"</span>,<br> <span class="key">"data"</span>: {<br> <span class="key">"totalMileageKm"</span>: <span class="number">12345.678</span>,<br> <span class="key">"protocol"</span>: <span class="string">"GB32960"</span>,<br> <span class="key">"recordTime"</span>: <span class="string">"2026-08-06 10:29:45"</span>,<br> <span class="key">"timeDifferenceSeconds"</span>: <span class="number">15</span><br> }<br>}</pre><ul class="errors"><li>400VIN、时间或协议不合法</li><li>403VIN 未授权</li><li>无记录:返回 NO_DATA</li></ul></div></div></section>
<section class="section" id="realtime"><div class="section-head"><div><h2>车辆实时位置与状态</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/realtime/query</div><p>返回位置、在线状态、速度、总里程、储氢及独立 GPS 状态。储氢优先终端上报质量,缺质量可按同帧温压和 VIN 容积估算;百分比采用35 MPa、15°C满充参考,来源和质量分别说明。</p><p>比例 = 剩余质量 / PressureHydrogenMassKg(35,15,VIN容积) × 100。参考温度与密度比定义见 <a href="https://unece.org/sites/default/files/2024-07/ECE_TRANS_WP.29_2023_110E.pdf">UNECE 定义文件</a>;35 MPa 为本次业务参数,非所有车型额定压力的自动认证。</p></div><a class="anchor" href="#realtime">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>plateNumbers</td><td class="optional"></td><td>省略或 [] 时返回全部当前有效授权车辆</td></tr></table></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"data"</span>: [{<br> <span class="key">"vin"</span>: <span class="string">"LA9GG68L2PBAF4790"</span>,<br> <span class="key">"plateNumber"</span>: <span class="string">"浙F06618F"</span>,<br> <span class="key">"protocol"</span>: <span class="string">"GB32960"</span>,<br> <span class="key">"longitude"</span>: <span class="number">120.75</span>,<br> <span class="key">"latitude"</span>: <span class="number">30.74</span>,<br> <span class="key">"speedKmh"</span>: <span class="number">0</span>,<br> <span class="key">"totalMileageKm"</span>: <span class="number">12345.678</span>,<br> <span class="key">"recordTime"</span>: <span class="string">"2026-08-17 17:40:12"</span>,<br> <span class="key">"timeDifferenceSeconds"</span>: <span class="number">8</span>,<br> <span class="key">"online"</span>: <span class="number">true</span>,<br> <span class="key">"activeToday"</span>: <span class="number">true</span>,<br> <span class="key">"motionStatus"</span>: <span class="string">"idle"</span>,<br> <span class="key">"locationAvailable"</span>: <span class="number">true</span>,<br> <span class="key">"status"</span>: <span class="string">"NORMAL"</span><br> }]<br>}</pre><ul class="errors"><li>400:车牌格式错误</li><li>403:指定车牌未授权</li><li>无实时记录:返回 NO_DATA</li></ul></div></div></section>
<section class="section" id="stationary"><div class="section-head"><div><h2>加氢车辆停留核验</h2><div class="endpoint"><span class="method">POST</span>/api/v1/vehicles/stationary/query</div><p>按站点坐标、坐标系、时间段和半径,核验速度接近 0 的车辆停留记录,并按匹配度降序返回。</p></div><a class="anchor" href="#stationary">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>startTime / endTime</td><td class="required"></td><td>北京时间;查询区间最长 24 小时</td></tr><tr><td>longitude / latitude</td><td class="required"></td><td>加氢站经纬度;由 coordinateSystem 说明坐标系</td></tr><tr><td>coordinateSystem</td><td class="optional"></td><td>WGS84(默认)或 GCJ02(高德);GCJ02 自动转换后核验</td></tr><tr><td>radiusMeters</td><td class="optional"></td><td>核验半径,单位 m;1–100,默认 5</td></tr><tr><td>plateNumbers</td><td class="optional"></td><td>省略时核验全部有效授权车辆</td></tr></table><div class="note"><strong>匹配规则:</strong>速度 ≤ 3 km/h,至少 2 个样本且停留 ≥ 60 秒;相邻定位点间隔超过 10 分钟时拆分停留区间。</div></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"data"</span>: [{<br> <span class="key">"plateNumber"</span>: <span class="string">"浙F06618F"</span>,<br> <span class="key">"stayStartTime"</span>: <span class="string">"2026-08-06 10:16:02"</span>,<br> <span class="key">"stayEndTime"</span>: <span class="string">"2026-08-06 10:42:18"</span>,<br> <span class="key">"stayDurationSeconds"</span>: <span class="number">1576</span>,<br> <span class="key">"matchScore"</span>: <span class="number">93.842</span><br> }]<br>}</pre><ul class="errors"><li>400:时间、坐标、坐标系或半径不合法</li><li>403:授权期未覆盖整个时间段</li><li>无匹配:data 为空数组</li></ul></div></div></section>
<section class="section" id="stations"><div class="section-head"><div><h2>加氢站地图点位</h2><div class="endpoint"><span class="method">POST</span>/api/v1/hydrogen-stations/query</div><p>只读查询有有效坐标的加氢站,可按行政区划和合作属性筛选。</p></div><a class="anchor" href="#stations">#</a></div><div class="grid"><div><h3 class="subhead">请求参数</h3><table class="param-table"><tr><th>字段</th><th>必填</th><th>说明</th></tr><tr><td>province / city</td><td class="optional"></td><td>行政区划筛选</td></tr><tr><td>cooperateOnly</td><td class="optional"></td><td>true 仅合作站;false 仅外部站</td></tr></table></div><div><h3 class="subhead">成功返回 · 200</h3><pre class="code">{<br> <span class="key">"data"</span>: [{<br> <span class="key">"id"</span>: <span class="string">"1"</span>,<br> <span class="key">"name"</span>: <span class="string">"示例加氢站"</span>,<br> <span class="key">"longitude"</span>: <span class="number">120.752312</span>,<br> <span class="key">"latitude"</span>: <span class="number">30.746281</span>,<br> <span class="key">"cooperative"</span>: <span class="number">true</span><br> }]<br>}</pre><ul class="errors"><li>400:行政区划参数过长</li><li>401appKey 无效</li><li>无符合站点:data 为空数组</li></ul></div></div></section>
<section class="section" id="response-fields"><div class="section-head"><div><h2>返回字段与枚举说明</h2><p>所有成功响应外层固定包含 code、message、data 和 traceId;以下为 data 内的字段。无数据时按对应接口返回 null、NO_DATA 或空数组。</p></div><a class="anchor" href="#response-fields">#</a></div><h3 class="subhead" style="margin-top:22px">通用响应与状态</h3><table class="protocol-table"><tr><th>字段/枚举</th><th>说明</th></tr><tr><td>code = SUCCESS</td><td>请求已成功处理。业务数据是否存在由每条数据的 status 判断。</td></tr><tr><td>message</td><td>成功时固定为 success。</td></tr><tr><td>traceId</td><td>本次请求唯一追踪标识;异常排查时请完整提供。</td></tr><tr><td>status = NORMAL</td><td>存在可用数据,相关数值、协议和时间字段有效。</td></tr><tr><td>status = NO_DATA</td><td>在授权范围内未找到可用数据;可选数值、位置、时间字段为 null 或省略。</td></tr><tr><td>status = DATA_ANOMALY</td><td>检测到数据异常,里程异常不返回可能错误的里程;用氢 SUSPECT 保留审计数值。原因分别见 dataQuality / qualityReason。</td></tr><tr><td>dataQuality = TOTAL_MILEAGE_ROLLBACK</td><td>终端累计里程明显回退,已阻止其作为正常累计里程返回。</td></tr></table><h3 class="subhead" style="margin-top:28px">车辆单日用氢量</h3><table class="protocol-table"><tr><th>字段</th><th>说明</th></tr><tr><td>vin / plateNumber / date</td><td>授权车辆 VIN、车牌与查询自然日(Asia/Shanghai)。</td></tr><tr><td>hydrogenConsumptionKg</td><td>当日用氢量,单位 kg;无有效数据时为 null。</td></tr><tr><td>status / qualityStatus</td><td>NORMAL / OK 为可用;DATA_ANOMALY / SUSPECT 保留数值供审计,不参与正常百公里氢耗;NO_DATA 不可用。</td></tr><tr><td>calculationPhase / algorithmVersion</td><td>PRELIMINARY 为初步监控结果,FINAL 为批量重算结果;历史结果仍可能再重算,保留实际版本。</td></tr><tr><td>statisticsStartTime / statisticsEndTime / updatedAt</td><td>证据包络起止和统计行更新时间;PRELIMINARY 开始时间为 null,结束为数据水位。与里程无共同快照保证。</td></tr></table><h3 class="subhead" style="margin-top:28px">车辆单日里程与区间日里程</h3><table class="protocol-table"><tr><th>字段</th><th>说明</th></tr><tr><td>vin / plateNumber / date</td><td>车辆唯一 VIN、车牌和统计自然日。</td></tr><tr><td>dailyMileageKm</td><td>当日行驶里程,单位 km。缺少当日记录且可前向补齐时为 0。</td></tr><tr><td>totalMileageKm</td><td>所选协议的日末累计总里程,单位 km;不会用 GPS 日里程估算值替代。</td></tr><tr><td>sourceProtocol</td><td>实际采用的协议:GB32960、MQTT 或 JT808。</td></tr><tr><td>dataTime</td><td>实际采用的最后一条车辆源数据时间。</td></tr><tr><td>updatedAt</td><td>该统计周期的计算时间;前向补齐时保持上一有效统计周期时间。</td></tr><tr><td>snapshotId / nextCursor</td><td>仅区间接口返回;snapshotId 固定本次分页车辆范围,nextCursor 为下一页游标,最后一页为 null。</td></tr></table><h3 class="subhead" style="margin-top:28px">指定时刻总里程</h3><table class="protocol-table"><tr><th>字段</th><th>说明</th></tr><tr><td>vin / queryTime</td><td>查询车辆 VIN 与请求的北京时间。</td></tr><tr><td>totalMileageKm</td><td>不晚于请求时间的最近一条累计总里程,单位 km。</td></tr><tr><td>protocol / protocolInput</td><td>protocol 为实际命中协议;protocolInput 为请求显式传入的协议,未传则省略。</td></tr><tr><td>recordTime / timeDifferenceSeconds</td><td>实际命中记录时间,以及请求时间与记录时间的差值,单位秒。</td></tr><tr><td>mileageMeaning</td><td>该协议累计里程的业务口径;JT808 为定位终端/GPS侧累计值。</td></tr></table><h3 class="subhead" style="margin-top:28px">车辆实时位置与状态</h3><table class="protocol-table"><tr><th>字段/枚举</th><th>说明</th></tr><tr><td>protocol</td><td>本条位置、速度、累计里程和记录时间实际采用的协议:GB32960、MQTT 或 JT808。</td></tr><tr><td>remainingHydrogenKg / remainingHydrogenPercent</td><td>全车储氢 kg / %,真实零保留。质量优先终端上报,缺值可用同帧温压和 VIN 容积估算;百分比按35 MPa、15°C满充质量计算,缺条件独立返回 null。</td></tr><tr><td>hydrogenRecordTime / hydrogenDataStatus</td><td>同帧采集时间和聚合状态 NORMAL / PARTIAL / STALE / MISSING / UNSUPPORTED / INVALID;必须同时检查两个字段级状态。</td></tr><tr><td>gpsFixStatus / locationRecordTime / coordinateSystem</td><td>真实定位位、位置采集时间和 WGS84 / GCJ02 / UNKNOWN。离线不清除历史 FIXED,陈旧不证明无定位。</td></tr><tr><td>longitude / latitude</td><td>无有效位置或 NO_FIX 时为 nulllocationAvailable 为 falseUNKNOWN 坐标系需由调用方确认后上图。</td></tr><tr><td>speedKmh / totalMileageKm</td><td>所选来源的瞬时速度(km/h)和累计总里程(km)。</td></tr><tr><td>recordTime / timeDifferenceSeconds</td><td>所选来源的记录时间,以及与当前查询时间的差值,单位秒。</td></tr><tr><td>online</td><td>true:任一协议最近 60 秒内上报;false:没有任一协议满足该实时阈值。</td></tr><tr><td>activeToday</td><td>true:任一协议在当前自然日曾上报;不等同于实时 online。</td></tr><tr><td>motionStatus = driving</td><td>在线且所选来源速度大于 3 km/h。</td></tr><tr><td>motionStatus = idle</td><td>在线且所选来源速度不大于 3 km/h。</td></tr><tr><td>motionStatus = offline</td><td>当前不在线,或没有实时记录。</td></tr></table><h3 class="subhead" style="margin-top:28px">加氢车辆停留核验</h3><table class="protocol-table"><tr><th>字段</th><th>说明</th></tr><tr><td>stayStartTime / stayEndTime</td><td>满足核验条件的连续停留开始、结束时间。</td></tr><tr><td>stayDurationSeconds / stayDurationMinutes</td><td>停留时长,分别以秒、分钟表示。</td></tr><tr><td>matchScore</td><td>0–100;位置越接近、速度越低、停留越久、样本越充分,分数越高。</td></tr><tr><td>averageDistanceMeters / maxDistanceMeters</td><td>定位点相对输入站点坐标的平均/最大距离,单位 m。</td></tr><tr><td>averageSpeedKmh / maxSpeedKmh / matchedSamples</td><td>核验区间内速度统计与匹配定位样本数。</td></tr><tr><td>sourceProtocols</td><td>本停留片段采用过的协议数组,枚举值为 GB32960、MQTT、JT808。</td></tr></table><h3 class="subhead" style="margin-top:28px">加氢站地图点位</h3><table class="protocol-table"><tr><th>字段</th><th>说明</th></tr><tr><td>id / name / shortName</td><td>站点唯一标识、标准名称和简称;id 为字符串以避免 JavaScript 精度丢失。</td></tr><tr><td>address / province / city / district</td><td>站点地址及行政区划。</td></tr><tr><td>longitude / latitude</td><td>站点坐标。</td></tr><tr><td>cooperative</td><td>true:合作站(内部站或已导入合作名录);false:外部站。</td></tr></table></section>
<section class="section" id="protocols"><div class="section-head"><div><h2>总里程协议口径</h2><p>未指定 protocol 时,按 GB32960 &gt; MQTT &gt; JT808 选择第一个有数据的协议;不会跨协议拼接里程。</p></div><a class="anchor" href="#protocols">#</a></div><table class="protocol-table"><tr><th>规范值</th><th>总里程含义</th></tr><tr><td>GB32960</td><td>车辆仪表盘累计总里程,对应 GB/T 32960 整车数据累计里程。</td></tr><tr><td>MQTT</td><td>车辆仪表盘或车端控制器累计总里程,由 MQTT 平台上报。</td></tr><tr><td>JT808</td><td>定位终端累计里程,由 GPS/终端侧计算,不等同于车辆仪表盘里程。</td></tr></table></section>
<section class="section" id="errors"><div class="section-head"><div><h2>异常与状态码</h2><p>所有响应均包含 <code>code</code><code>message</code><code>traceId</code>;请在工单中提供 traceId 以便排查。</p></div><a class="anchor" href="#errors">#</a></div><table class="protocol-table error-table"><tr><th>HTTP</th><th>code</th><th>说明</th></tr><tr><td>200</td><td>SUCCESS</td><td>查询成功;无统计数据时,数据行的 status 为 NO_DATA。</td></tr><tr><td>400</td><td>INVALID_REQUEST</td><td>请求格式、数量、日期、时间、坐标或协议不正确。</td></tr><tr><td>401</td><td>UNAUTHORIZED</td><td>appKey 不存在、停用或已过期。</td></tr><tr><td>403</td><td>FORBIDDEN</td><td>车辆或 appKey 授权期未覆盖请求时间。</td></tr><tr><td>500</td><td>INTERNAL_ERROR</td><td>服务内部异常;请提供 traceId。</td></tr></table></section>
</main>
</div>
<script>
(() => {
const common = [
["code", "string", "固定为 SUCCESS,表示 HTTP 请求处理成功;请结合 data 内每条 status 判断是否有业务数据。"],
["message", "string", "成功时固定为 success。"],
["traceId", "string", "本次请求的唯一追踪标识;出现问题时请完整提供给羚牛技术支持。"]
];
const fields = {
"historical-hydrogen": [
["data[].requestId / vin / queryTime", "string", "逐项对应请求;不得只依赖数组位置配对作业起终点。"],
["data[].plateNumber", "string | null", "当前缺少历史车牌证据,为null,不把当前车牌冒充历史车牌。"],
["data[].remainingHydrogenKg / remainingHydrogenKgStatus", "number | null / enum", "仅NORMAL给原始精度kg;其他状态null。有效质量超过容差STALE;最新INVALID/MISSING保留原状态。无历史氢相关帧NO_DATA;分块重组失败或扫描预算耗尽ERROR,不得回退。"],
["data[].hydrogenRecordTime / timeDifferenceSeconds", "datetime | null / number | null", "真实event_timeRFC3339带时区)与查询采样偏差秒数,绝不使用查询之后的帧。"],
["data[].hydrogenValueSource / hydrogenSourceProtocol", "string | null", "当前REPORTED表示终端上报(不等同直接测量),GB32960为实际氢协议。ESTIMATED保留但缺历史容积版本时不输出估算值。"],
["data[].sourceRecordId", "string | null", "raw-sha256采样身份指纹,便于识别同一采样命中;内容版本另看sourceDataVersion,不是批次快照。"],
["data[].sourceDataVersion", "string | null", "sha256原始JSON与解析状态内容指纹,识别同采样修订;不同起终点内容不同是正常的,不要求两端此字段相等。"],
["data[].hydrogenCalculationVersion / hydrogenCapacityVersion", "string | null", "REPORTED_HYDROGEN_KG_V1解码转换版本;历史容量版本缺证据为null,不能用当前值回套。"],
["data[].updatedAt", "datetime | null", "原始记录接收时间,不代替采集时间;即使时间不变,内容修订仍会改变sourceDataVersion。"],
["data[].reasonCode / message", "string | null", "可程序识别原因与中文说明;ERROR应重试,不能当NO_DATAFORBIDDEN不返回采样证据。"],
["data[].hydrogenEstimatePressureMPa / hydrogenEstimateTemperatureC / hydrogenTankCapacityL / hydrogenPressureTemperatureSource", "number | null / string | null", "有同帧温压可返回诊断证据;MAX_SENSOR_AGGREGATE不保证同瓶。没有历史容量证据仍MISSING,容积null。"]
],
"daily-hydrogen": [
["data[]", "array", "按请求车牌顺序返回的车辆数据数组。"],
["data[].vin", "string", "授权车辆 VIN,必须与实时、里程记录共同核对。"],
["data[].plateNumber", "string", "车辆车牌号。"],
["data[].date", "date", "查询自然日,格式 yyyy-MM-dd,时区 Asia/Shanghai。"],
["data[].hydrogenConsumptionKg", "number | null", "当日用氢量,单位 kg;无有效统计时为 null。"],
["data[].statisticsStartTime / statisticsEndTime", "datetime | null", "FINAL 取证据区间最早开始/最晚结束,是证据包络,不保证连续覆盖;PRELIMINARY 仅结束水位,开始为 null;异常或缺失证据为 null。"],
["data[].updatedAt", "datetime | null", "同一用氢统计行的数据库更新时间,RFC 3339 带时区。"],
["data[].calculationPhase", "enum (optional)", "PRELIMINARY:初步监控值;FINAL:已批量重算,仍可因补传或算法变更再次重算。"],
["data[].algorithmVersion", "string (optional)", "实际计算算法版本;压力/温度/容积模型估算,特殊能量积分兜底由质量原因说明。"],
["data[].qualityStatus / qualityReason", "string (optional)", "OK:可用;SUSPECT:异常/估算兜底,保留数值供审计;NO_DATA:不可用。qualityReason 解释原因。"],
["data[].status", "enum", "NORMAL 对应 OKDATA_ANOMALY 对应 SUSPECT,禁止参与正常百公里氢耗;NO_DATA 为无可用统计。"]
],
"daily-mileage": [
["data[]", "array", "按请求车牌顺序返回的车辆数据数组。"],
["data[].vin", "string", "车辆唯一 VIN。"], ["data[].plateNumber", "string", "车辆车牌号。"], ["data[].date", "date", "查询自然日,yyyy-MM-ddAsia/Shanghai。"],
["data[].dailyMileageKm", "number | null", "当日行驶里程,单位 km;若当日无记录但存在此前有效累计里程,则返回 0。"],
["data[].totalMileageKm", "number | null", "日末累计总里程,单位 km;仅采用终端累计里程,不以 GPS 日里程估算替代。"],
["data[].sourceProtocol", "enum | null", "<span class='enum-value'>GB32960</span> GB/T 32960 车辆仪表累计里程;<span class='enum-value'>MQTT</span> 车端/MQTT 平台累计里程;<span class='enum-value'>JT808</span> 定位终端/GPS侧累计里程。"],
["data[].statisticsStartTime / statisticsEndTime", "datetime | null", "当日统计所选来源首末采集时间,跨日基线开始可早于当日零点,不是自然日起始;历史结转补零时均为 null。与用氢无共同快照,不可仅凭同日直接相除。"],
["data[].dataTime", "datetime | null", "本条统计实际采用的最后一条车辆源数据时间。"], ["data[].updatedAt", "datetime | null", "本行统计计算时间;前向补齐时为上一个有效统计周期的计算时间。"],
["data[].status", "enum", "<span class='enum-value'>NORMAL</span> 有效;<span class='enum-value'>NO_DATA</span> 无数据;<span class='enum-value'>DATA_ANOMALY</span> 发现异常,里程不返回。"],
["data[].dataQuality", "enum | null", "仅 DATA_ANOMALY 时返回。<span class='enum-value'>TOTAL_MILEAGE_ROLLBACK</span> 表示终端累计里程明显回退,已拦截。"]
],
"mileage-range": [
["data[]", "array", "当前页的车辆×日期数据,按请求区间与授权车辆快照排序。字段说明见下表;statisticsStartTime / statisticsEndTime 仅单日接口新增。"],
["data[].vin / plateNumber / date", "string / date", "车辆 VIN、车牌和该行统计自然日。"], ["data[].dailyMileageKm", "number | null", "当日里程,单位 km。"], ["data[].totalMileageKm", "number | null", "该日累计总里程,单位 km。"],
["data[].sourceProtocol", "enum | null", "<span class='enum-value'>GB32960</span> / <span class='enum-value'>MQTT</span> / <span class='enum-value'>JT808</span>;含义见单日里程接口。"],
["data[].dataTime / updatedAt", "datetime | null", "采用源数据时间 / 统计计算时间。"], ["data[].status / dataQuality", "enum | null", "NORMAL、NO_DATA、DATA_ANOMALY;异常原因目前为 TOTAL_MILEAGE_ROLLBACK。"],
["snapshotId", "string", "本次分页授权车辆快照标识;后续翻页必须保持原参数并继续使用。"], ["nextCursor", "string | null", "下一页游标;为 null 表示已到最后一页。"]
],
"total-mileage": [
["data.vin", "string", "查询车辆 VIN。"], ["data.queryTime", "datetime", "请求指定的北京时间。"], ["data.totalMileageKm", "number | null", "不晚于 queryTime 的最近一条累计总里程,单位 km;无记录时为 null。"],
["data.protocol", "enum | null", "实际命中协议。<span class='enum-value'>GB32960</span> 仪表累计;<span class='enum-value'>MQTT</span> 车端累计;<span class='enum-value'>JT808</span> 定位/GPS累计。"],
["data.protocolInput", "string | null", "请求中传入的协议原值;未指定时为 null/省略。"], ["data.mileageMeaning", "string | null", "当前 protocol 的累计里程业务口径说明。"],
["data.recordTime", "datetime | null", "实际命中的源数据记录时间,北京时间。"], ["data.timeDifferenceSeconds", "integer | null", "queryTime 减 recordTime 的秒数。"],
["data.selectionPolicy", "string", "未指定协议时使用的默认选源顺序。"], ["data.status", "enum", "<span class='enum-value'>NORMAL</span> 找到记录;<span class='enum-value'>NO_DATA</span> 未找到不晚于请求时间的记录。"]
],
"realtime": [
["data[]", "array", "按请求车牌顺序返回;省略车牌时返回当前授权范围内全部车辆。"], ["data[].vin / plateNumber", "string", "车辆唯一 VIN / 车牌。"],
["data[].remainingHydrogenKg / remainingHydrogenPercent", "number | null", "全车质量 kg / 比例 %;优先 GB32960 广东扩展终端上报 kg(0–200),缺kg可由同帧最大氢压/氢温与VIN容积估算(0–500)。比例按35 MPa、15°C满充质量计算;真实0保留,不以动力电池SOC替代。"],
["data[].hydrogenRecordTime", "datetime | null", "氢量原始同帧的实际采集时间,RFC 3339 带时区;不是查询/缓存时间。仅 GB 最新快照有引用且精确帧氢量 MISSING 时,回查快照 received_at 向前 5 分钟的候选,按采集时间优先检查最近5条;不完整温压可取次新完整帧,INVALID 不回退,离线回补保留真实 STALE。"],
["data[].hydrogenDataStatus", "enum", "NORMAL 全部有效;PARTIAL 部分字段支持;STALE 陈旧;MISSING 缺记录或本次未取得可信证据(补充查询失败/3 秒超时会降级);UNSUPPORTED 不支持;INVALID 异常。两项有效时 NORMAL(是否估算看来源);仅一项有效时 PARTIAL。"],
["data[].remainingHydrogenKgStatus / remainingHydrogenPercentStatus", "enum", "逐字段 NORMAL / STALE / MISSING / UNSUPPORTED / INVALID;独立判断两个数值,聚合状态不代表两者都有效。"],
["data[].hydrogenValueSource / hydrogenSourceProtocol", "string | null", "kg 来源 REPORTED 仅确认终端上报,ESTIMATED 为平台温压/容积实气估算;储氢协议独立于位置 protocol。"],
["data[].remainingHydrogenPercentSource", "enum | null", "ESTIMATED:比例按模型满充质量计算,与kg来源独立;计算缺条件时null。超过100比例null/INVALIDkg独立保留,不截断成100。"],
["data[].hydrogenFullCapacityKg / hydrogenTankCapacityL", "number | null", "未舍入满充质量分母kg / 已启用VIN全车水容积L0&lt;V≤10000)。无默认车型容积;缺容量百分比MISSING。"],
["data[].hydrogenFullPressureMPa / hydrogenReferenceTemperatureC", "number | null", "35 MPa / 15°C;业务满充参考,不是自动核验每车铭牌额定压力,也不是可用容量扣除残余后的分母。"],
["data[].hydrogenEstimatePressureMPa / hydrogenEstimateTemperatureC", "number | null", "仅估算kg时采用的同帧最大氢压/氢温;压力0–70 MPa,温度−40–726.85°C,不补默认温度。"],
["data[].hydrogenPressureTemperatureSource", "enum | null", "MAX_SENSOR_AGGREGATE:最大温压聚合值,不保证来自同一瓶,不能当逐瓶质量求和或完整性证明。"],
["data[].hydrogenCalculationVersion / hydrogenCapacitySource", "string | null", "REAL_GAS_35MPA_15C_V1 / vehicle_hydrogen_tank_capacity。复用实气模型与VIN配置,不改变日用氢算法。"],
["data[].hydrogenPercentReason", "enum | null", "MISSING_HYDROGEN_MEASUREMENT、INVALID_MASS_READING、INVALID_PRESSURE_TEMPERATURE、INCOMPLETE_PRESSURE_TEMPERATURE、MISSING_TANK_CAPACITY、MASS_CALCULATION_FAILED、EXCEEDS_NOMINAL_FULL_CAPACITY;超满充参考拒绝百分比,不据此判断车辆安全状态。"],
["data[].hydrogenStaleAfterSeconds / hydrogenExpectedIntervalSeconds", "integer | null", "GB32960 服务陈旧阈值 300 秒;协议期望上报周期未约定为 null,阈值不是采样周期承诺。"],
["data[].gpsFixStatus", "enum", "FIXED / NO_FIX / UNKNOWN:实际位置报文定位位,独立于在线和数据年龄;MQTT 无可信定位位为 UNKNOWN。"],
["data[].locationRecordTime / coordinateSystem", "datetime | null / enum", "位置实际采集时间。坐标系 WGS84 / GCJ02 / UNKNOWN;仅 GB2025 显式类型 1/2 确定 WGS84/GCJ02GB2016/JT808/MQTT 无确证为 UNKNOWN。不能默认 GCJ02。"],
["data[].protocol", "enum | null", "本条位置、速度、累计里程和记录时间实际采用的唯一协议字段。<span class='enum-value'>GB32960</span> 车辆协议;<span class='enum-value'>MQTT</span> MQTT 车端来源;<span class='enum-value'>JT808</span> 定位终端来源。"],
["data[].longitude / latitude", "number | null", "所选来源的最新有效经度/纬度;无位置时为 null。"], ["data[].speedKmh", "number | null", "所选来源瞬时速度,单位 km/h。"], ["data[].socPercent", "number", "所选来源的动力电池荷电状态,单位 %;仅在采集值为 0–100 时返回,无值或无效值时字段省略。"], ["data[].totalMileageKm", "number | null", "所选来源累计总里程,单位 km。"],
["data[].recordTime", "datetime | null", "所选来源实际记录时间,北京时间。"], ["data[].timeDifferenceSeconds", "integer | null", "当前查询时间减 recordTime,单位秒。"],
["data[].online", "boolean", "true:任一采集协议在最近 60 秒内上报;false:没有协议满足实时阈值。"], ["data[].activeToday", "boolean", "true:任一采集协议在当前自然日曾上报;它不等同于 online。"],
["data[].motionStatus", "enum", "<span class='enum-value'>driving</span> 在线且所选速度 &gt; 3 km/h<span class='enum-value'>idle</span> 在线且速度 ≤ 3 km/h<span class='enum-value'>offline</span> 当前不在线或无实时记录。"],
["data[].locationAvailable", "boolean", "truelongitude 与 latitude 有效;false:当前无有效位置。"], ["data[].status", "enum", "<span class='enum-value'>NORMAL</span> 找到实时记录;<span class='enum-value'>NO_DATA</span> 无实时记录。"]
],
"stationary": [
["data[]", "array", "满足站点停留匹配条件的车辆片段;无匹配时为空数组。"], ["data[].vin / plateNumber", "string", "车辆唯一 VIN / 车牌。"],
["data[].stayStartTime / stayEndTime", "datetime", "连续停留片段的起止定位时间,北京时间。"], ["data[].stayDurationSeconds / stayDurationMinutes", "integer / number", "停留时长,分别以秒/分钟表示。"],
["data[].matchScore", "number", "0–100;位置越接近、速度越低、停留越久、样本越充分,分数越高。"], ["data[].averageDistanceMeters / maxDistanceMeters", "number", "定位点相对输入站点坐标的平均/最大距离,单位 m。"],
["data[].averageSpeedKmh / maxSpeedKmh", "number", "停留片段内平均/最大速度,单位 km/h。"], ["data[].matchedSamples", "integer", "用于该停留片段的定位样本数,至少为 2。"],
["data[].sourceProtocols", "array<enum>", "片段中实际采用过的协议数组:GB32960、MQTT、JT808。"]
],
"stations": [
["data[]", "array", "符合行政区划与合作属性筛选条件的站点数组;无符合项时为空数组。"], ["data[].id", "string", "站点 ID 使用字符串,避免 JavaScript 大整数精度丢失。"], ["data[].name / shortName", "string", "站点标准名称 / 简称。"],
["data[].address", "string", "站点地址。"], ["data[].longitude / latitude", "number", "站点经纬度。"], ["data[].province / city / district", "string", "站点省、市、区县行政区划。"],
["data[].cooperative", "boolean", "true:合作站(内部站或已导入合作名录);false:外部站。"]
]
};
const sectionIds = Object.keys(fields);
const esc = value => String(value).replace(/[&<>\"]/g, char => ({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;"}[char]));
const expandFields = rows => rows.flatMap(([path, type, description]) => {
if (path === "data[]") return [[path, type, description]];
const prefix = path.startsWith("data[].") ? "data[]." : (path.startsWith("data.") ? "data." : "");
const names = path.slice(prefix.length).split(" / ");
const types = type.split(" / ");
return names.map((name, index) => [prefix + name, types[index] || type, description]);
});
const treeCell = (depth, name, kind, root) => {
const branch = root ? "" : "<span class='tree-branch' aria-hidden='true'></span>";
const label = root ? "<span class='tree-root'>" + esc(name) + "</span>" : "<span class='tree-node'>" + esc(name) + "</span>";
return "<div class='tree-field depth-" + depth + "'>" + branch + label + (kind ? "<span class='tree-kind'>" + esc(kind) + "</span>" : "") + "</div>";
};
const renderResponseTree = rows => {
const expanded = expandFields(rows);
const dataArray = expanded.find(row => row[0] === "data[]");
const dataObject = expanded.some(row => row[0].startsWith("data."));
const regular = expanded.filter(row => row[0] !== "data[]");
const outer = regular.filter(row => !row[0].startsWith("data.") && !row[0].startsWith("data[]."));
const nested = regular.filter(row => row[0].startsWith("data.") || row[0].startsWith("data[]."));
let html = "<tr class='tree-root-row'><td>" + treeCell(0, "响应对象", "object", true) + "</td><td>object</td><td>本接口成功响应的 JSON 根对象。</td></tr>";
html += common.map(row => "<tr><td>" + treeCell(1, row[0], "", false) + "</td><td>" + esc(row[1]) + "</td><td>" + row[2] + "</td></tr>").join("");
if (dataArray) {
html += "<tr><td>" + treeCell(1, "data", "array", false) + "</td><td>array</td><td>" + dataArray[2] + "</td></tr>";
html += "<tr><td>" + treeCell(2, "[]", "object", false) + "</td><td>object</td><td>data 数组中的一条车辆/站点数据。</td></tr>";
} else if (dataObject) {
html += "<tr><td>" + treeCell(1, "data", "object", false) + "</td><td>object</td><td>本次查询对应的一条业务数据。</td></tr>";
}
html += nested.map(row => {
const name = row[0].replace(/^data(?:\[\])?\./, "");
return "<tr><td>" + treeCell(dataArray ? 3 : 2, name, "", false) + "</td><td>" + esc(row[1]) + "</td><td>" + row[2] + "</td></tr>";
}).join("");
html += outer.map(row => "<tr><td>" + treeCell(1, row[0], "", false) + "</td><td>" + esc(row[1]) + "</td><td>" + row[2] + "</td></tr>").join("");
return html;
};
sectionIds.forEach(id => {
const section = document.getElementById(id);
const left = section && section.querySelector(".grid > div:first-child");
if (!left) return;
const requestTitle = left.querySelector(".subhead");
const requestTable = left.querySelector("table");
if (!requestTitle || !requestTable) return;
requestTitle.remove();
const tabs = document.createElement("div"); tabs.className = "field-tabs";
const requestButton = document.createElement("button"); requestButton.className = "field-tab"; requestButton.type = "button"; requestButton.textContent = "请求参数"; requestButton.setAttribute("aria-selected", "true");
const responseButton = document.createElement("button"); responseButton.className = "field-tab"; responseButton.type = "button"; responseButton.textContent = "返回字段"; responseButton.setAttribute("aria-selected", "false");
tabs.append(requestButton, responseButton);
const requestPanel = document.createElement("div"); requestPanel.className = "request-panel"; requestPanel.append(requestTitle, requestTable);
const responsePanel = document.createElement("div"); responsePanel.className = "response-panel";
responsePanel.innerHTML = "<h3 class='subhead'>返回字段</h3><p class='response-caption'>按响应 JSON 的对象层级展示;数组元素以 <code>[]</code> 标记,枚举与空值规则已逐项说明。</p><table class='param-table response-table'><tr><th>响应结构</th><th>类型</th><th>说明/枚举</th></tr>" + renderResponseTree(fields[id]) + "</table>";
left.replaceChildren(tabs, requestPanel, responsePanel);
requestButton.addEventListener("click", () => { requestButton.setAttribute("aria-selected", "true"); responseButton.setAttribute("aria-selected", "false"); requestPanel.style.display = "block"; responsePanel.classList.remove("active"); });
responseButton.addEventListener("click", () => { responseButton.setAttribute("aria-selected", "true"); requestButton.setAttribute("aria-selected", "false"); requestPanel.style.display = "none"; responsePanel.classList.add("active"); });
});
const realtimeSample = document.querySelector("#realtime .code");
if (realtimeSample) realtimeSample.innerHTML = realtimeSample.innerHTML.replace("sourceProtocol", "protocol").replace('<span class="key">"speedKmh"</span>:', '<span class="key">"socPercent"</span>: <span class="number">78.5</span>,<br> <span class="key">"speedKmh"</span>:');
})();
</script>
</body>
</html>