task(03452fc3-d6cb-4549-b79d-7d389c7e5e8e): Add M6 stats & upload module (2.13/2.10/2.11/2.12 + queue retransmit)
补偿提交:commitOnArchive 连续第四轮未落盘(M3/M4/M5 同款故障),由主控在子会话工作树补交。 M6 统计与数据上传模块交付: - M6-02 (2.10) 项目监测计划统计列表(完成度进度条+百分比 Chip) - M6-03 (2.11) / M6-04 (2.12) 测组统计与监测点统计两层下钻 - M6-01 (2.13) SAF 选原始文件 multipart 直传 AnalysisMonitorData + 解析结果反馈 - 上传队列补传闭环 UploadQueueSyncer(应用启动 + ConnectivityManager VALIDATED onAvailable 双触发,Mutex 串行,成功 SUCCESS/失败回退 PENDING 记 retry) - Tab.STATS 业务化:挂载 StatsWorkspaceFragment,删除占位 StatsFragment - 新增 StatApi/UploadApi/StatModels + StatRepository/UploadRepository - 单测 StatsModelsTest/UploadPayloadWrapTest;静态核查 7 项 + 生产网关负向契约 4 端点
This commit is contained in:
@@ -3,7 +3,9 @@ package com.stec.cmd.core.network
|
||||
import com.stec.cmd.core.network.api.AuthApi
|
||||
import com.stec.cmd.core.network.api.PointApi
|
||||
import com.stec.cmd.core.network.api.ProjectApi
|
||||
import com.stec.cmd.core.network.api.StatApi
|
||||
import com.stec.cmd.core.network.api.TaskApi
|
||||
import com.stec.cmd.core.network.api.UploadApi
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -90,6 +92,16 @@ object NetworkModule {
|
||||
@Singleton
|
||||
fun providePointApi(retrofit: Retrofit): PointApi = retrofit.create(PointApi::class.java)
|
||||
|
||||
/** 统计接口(M6,2.10/2.11/2.12)。 */
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStatApi(retrofit: Retrofit): StatApi = retrofit.create(StatApi::class.java)
|
||||
|
||||
/** 数据上传接口(M6,2.8.2 队列补传 / 2.13 原始文件解析)。 */
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUploadApi(retrofit: Retrofit): UploadApi = retrofit.create(UploadApi::class.java)
|
||||
|
||||
/** 仅满足 Retrofit 构造约束;真实地址请求期经 DynamicBaseUrlInterceptor 改写。 */
|
||||
private const val PLACEHOLDER_BASE_URL = "https://placeholder.stec.invalid/"
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.stec.cmd.core.network.api
|
||||
|
||||
import com.stec.cmd.core.network.ApiEnvelope
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
/**
|
||||
* 统计接口(接口管理规范 V4.5):
|
||||
* - 2.10 获取项目监测计划统计表(M6-02,P0)
|
||||
* - 2.11 获取指定监测计划的测组统计表(M6-03,P1)
|
||||
* - 2.12 监测点统计表(M6-04,P1,历史数据)
|
||||
*
|
||||
* 三要素请求头(SecretKey/SystemCode/Token)由 AuthInterceptor 统一注入。
|
||||
*
|
||||
* 契约宽容:文档约定 data 为 {"PlanList"/"GroupList"/"PointsList":[…]};
|
||||
* 与项目/任务/测点列表(M2/M3/M5 先例)同理保留形态偏差可能,故以 [JsonElement]
|
||||
* 宽容承载,由 app 层 StatRepository 归一提取。
|
||||
*/
|
||||
interface StatApi {
|
||||
|
||||
/** 2.10 项目监测计划统计表:[projectId] 必填。 */
|
||||
@GET(PATH_GET_PLAN_STATISTICAL)
|
||||
suspend fun getPlanStatistical(
|
||||
@Query(QUERY_PROJECT_ID) projectId: String,
|
||||
): ApiEnvelope<JsonElement>
|
||||
|
||||
/** 2.11 指定监测计划的测组统计表:[planId] 必填。 */
|
||||
@GET(PATH_GET_SURVEY_GROUP_LIST)
|
||||
suspend fun getSurveyGroupList(
|
||||
@Query(QUERY_PLAN_ID) planId: String,
|
||||
): ApiEnvelope<JsonElement>
|
||||
|
||||
/** 2.12 监测点统计表:[planId]/[groupId] 均必填。 */
|
||||
@GET(PATH_GET_POINT_STATISTICS)
|
||||
suspend fun getPointStatistics(
|
||||
@Query(QUERY_PLAN_ID) planId: String,
|
||||
@Query(QUERY_GROUP_ID) groupId: String,
|
||||
): ApiEnvelope<JsonElement>
|
||||
|
||||
companion object {
|
||||
// ---- 接口管理规范 V4.5 · 章节 2.1 所有 API 目录 ----
|
||||
const val PATH_GET_PLAN_STATISTICAL = "OutWebApi/api/GetPlanStatistical"
|
||||
const val PATH_GET_SURVEY_GROUP_LIST = "OutWebApi/api/GetSurveyGroupList"
|
||||
const val PATH_GET_POINT_STATISTICS = "OutWebApi/api/GetPointStatistics"
|
||||
|
||||
/** 查询参数名(2.12 复用 PlanID;2.10/2.11/2.12 各取所需)。 */
|
||||
const val QUERY_PROJECT_ID = "ProjectID"
|
||||
const val QUERY_PLAN_ID = "PlanID"
|
||||
const val QUERY_GROUP_ID = "GroupID"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.stec.cmd.core.network.api
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* 统计 DTO(接口管理规范 V4.5 · 2.10/2.11/2.12 返回值示例)。
|
||||
*
|
||||
* 字段名与平台契约一致(PascalCase);文档未给类型,全 `String?` 容错,
|
||||
* 响应侧配合 Json.ignoreUnknownKeys 容忍字段演进。
|
||||
* 2.10 仅统计含「APP上传」测组的监测计划,非 APP 上传测组不计入完成度。
|
||||
*/
|
||||
|
||||
/** 2.10 监测计划统计条目(data.PlanList)。 */
|
||||
@Serializable
|
||||
data class PlanStatItem(
|
||||
/** 监测计划 ID(2.11/2.12 下钻与 2.13 上传 PlanID 的取值来源)。 */
|
||||
val PlanID: String? = null,
|
||||
/** 期数。 */
|
||||
val PeriodsNum: String? = null,
|
||||
/** 截止时间。 */
|
||||
val EndTime: String? = null,
|
||||
/** 施工工况。 */
|
||||
val Working: String? = null,
|
||||
/** 完成度(未开始上传的计划为 0)。 */
|
||||
val CompleteDegree: String? = null,
|
||||
/** 类型:常规 | 加测。 */
|
||||
val Mold: String? = null,
|
||||
)
|
||||
|
||||
/** 2.11 测组统计条目(data.GroupList)。 */
|
||||
@Serializable
|
||||
data class GroupStatItem(
|
||||
/** 测组 ID(2.12 下钻 GroupID 取值来源)。 */
|
||||
val GroupID: String? = null,
|
||||
/** 测组名称。 */
|
||||
val GroupName: String? = null,
|
||||
/** 责任人。 */
|
||||
val DutyName: String? = null,
|
||||
/** 完成度。 */
|
||||
val CompleteDegree: String? = null,
|
||||
/** 监测类型:水平位移/竖向位移/测斜/水位/轴力等。 */
|
||||
val MonitoringType: String? = null,
|
||||
)
|
||||
|
||||
/** 2.12 监测点统计条目(data.PointsList,历史数据)。 */
|
||||
@Serializable
|
||||
data class PointStatItem(
|
||||
/** 点名。 */
|
||||
val PointName: String? = null,
|
||||
/** 上传时间。 */
|
||||
val ImportTime: String? = null,
|
||||
/** 设备。 */
|
||||
val MonitorEqu: String? = null,
|
||||
)
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.stec.cmd.core.network.api
|
||||
|
||||
import com.stec.cmd.core.network.ApiEnvelope
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Part
|
||||
|
||||
/**
|
||||
* 数据上传接口(接口管理规范 V4.5):
|
||||
* - 2.8.2 上传本次监测值(M4 蓝牙采集 / M5 手动录入 Room 队列的统一补传通道)
|
||||
* - 2.13 数据解析·原始文件上传(M6-01,采集原始文件兜底通道;全程序唯一 multipart 端点)
|
||||
*
|
||||
* 三要素请求头(SecretKey/SystemCode/Token)由 AuthInterceptor 统一注入(multipart 同样生效),
|
||||
* 真实目标地址由 DynamicBaseUrlInterceptor 请求期改写。
|
||||
*
|
||||
* 契约宽容:两端点成功均返回 data:null,仍以 [JsonElement] 承载以复用
|
||||
* ApiEnvelope/ApiCaller 统一错误解包链(HTTP500+ExceptionMessage 内嵌信封)。
|
||||
*/
|
||||
interface UploadApi {
|
||||
|
||||
/**
|
||||
* 2.8.2 上传本次监测值:文档「Post 请求传递 Json 参数」,参数名为
|
||||
* MonitorValue,故 body 为 `{"MonitorValue":{…}}` 包装对象
|
||||
* (队列 payloadJson 为 MonitorValue 对象本身,由 app 层包装)。
|
||||
*/
|
||||
@POST(PATH_UPLOAD_MONITOR_VALUE)
|
||||
suspend fun uploadMonitorValue(
|
||||
@Body body: JsonElement,
|
||||
): ApiEnvelope<JsonElement>
|
||||
|
||||
/**
|
||||
* 2.13 数据解析(原始文件上传):multipart/form-data。
|
||||
* 必填 ProjectID/WorkPointID/SurveyGroupID/PlanID;[monitorDate] 可选(传 null 省略);
|
||||
* 文档约定 Files 参数名可省(服务端自动识别文件),此处显式命名并携带原始文件名。
|
||||
*/
|
||||
@Multipart
|
||||
@POST(PATH_ANALYSIS_MONITOR_DATA)
|
||||
suspend fun analysisMonitorData(
|
||||
@Part(PROJECT_ID_PART) projectId: RequestBody,
|
||||
@Part(WORK_POINT_ID_PART) workPointId: RequestBody,
|
||||
@Part(SURVEY_GROUP_ID_PART) surveyGroupId: RequestBody,
|
||||
@Part(PLAN_ID_PART) planId: RequestBody,
|
||||
@Part(MONITOR_DATE_PART) monitorDate: RequestBody?,
|
||||
@Part file: MultipartBody.Part,
|
||||
): ApiEnvelope<JsonElement>
|
||||
|
||||
companion object {
|
||||
// ---- 接口管理规范 V4.5 · 章节 2.1 所有 API 目录 ----
|
||||
const val PATH_UPLOAD_MONITOR_VALUE = "OutWebApi/api/UploadMonitorValue"
|
||||
const val PATH_ANALYSIS_MONITOR_DATA = "OutWebApi/api/AnalysisMonitorData"
|
||||
|
||||
/** 2.13 multipart 字段名(文档 2.13.4 接口参数表)。 */
|
||||
const val PROJECT_ID_PART = "ProjectID"
|
||||
const val WORK_POINT_ID_PART = "WorkPointID"
|
||||
const val SURVEY_GROUP_ID_PART = "SurveyGroupID"
|
||||
const val PLAN_ID_PART = "PlanID"
|
||||
const val MONITOR_DATE_PART = "MonitorDate"
|
||||
|
||||
/** 2.13 文件字段名(服务端自动识别,命名仅作自描述)。 */
|
||||
const val FILE_PART = "Files"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user