task(740424d0-c60b-47bd-a601-ded7a24bf7a6): Add M5 survey point data module (2.9/2.14/2.15 + manual entry)

补偿提交:commitOnArchive 连续第三轮未落盘(M3/M4 同款故障),由主控在子会话工作树直接补交。

M5 采集·测点数据模块四功能点:
- M5-01 (2.9) PointApi/PointModels/PointRepository 归一提取 + SurveyGroupPointSource
  平台测点选择器(pointId 贯通 payload)+ 测组监测点列表页(TaskDetail 入口)
- M5-02 (2.14) + M5-03 (2.15) 采集页上下文信息卡(并发拉取、失败降级不阻塞)
- M5-04 手动录入:buildManual 同构 payload 入同一条 Room 上传队列
  (entrySource=MANUAL),复核确认 + 已入队反馈
- upload_queue entry_source 列迁移 v1→v2(ALTER TABLE 非破坏迁移)
This commit is contained in:
阿猫
2026-09-04 02:26:32 +08:00
parent 3cb82169b6
commit 65a693052e
25 changed files with 1839 additions and 34 deletions

View File

@@ -1,6 +1,7 @@
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.TaskApi
import dagger.Module
@@ -84,6 +85,11 @@ object NetworkModule {
@Singleton
fun provideTaskApi(retrofit: Retrofit): TaskApi = retrofit.create(TaskApi::class.java)
/** 测点数据接口M52.9/2.14/2.15)。 */
@Provides
@Singleton
fun providePointApi(retrofit: Retrofit): PointApi = retrofit.create(PointApi::class.java)
/** 仅满足 Retrofit 构造约束;真实地址请求期经 DynamicBaseUrlInterceptor 改写。 */
private const val PLACEHOLDER_BASE_URL = "https://placeholder.stec.invalid/"

View File

@@ -0,0 +1,55 @@
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 · 测试环境 https://jcd.stec.p-q.co
* - 2.9 获取测组包含的监测点列表M5-01
* - 2.14 获取基准点M5-02水平位移等计算的基准
* - 2.15 获取工点数据M5-03目录页 2.15 笔误 ControlPoint以正文为准
*
* 三要素请求头SecretKey/SystemCode/Token由 AuthInterceptor 统一注入。
*
* 契约宽容:文档约定 data 为 {"PointList"/"ControlPointList"/"WorkPointList":[…]}
* 与项目/任务列表(任务 M2/M3 先例)同理保留形态偏差可能,故以 [JsonElement]
* 宽容承载,由 app 层 PointRepository 归一提取。
*/
interface PointApi {
/** 2.9 测组监测点列表:[groupId] 必填(测组 ID来自 2.6/2.7 任务 GroupID。 */
@GET(PATH_GET_POINT_BY_SURVEY_GROUP)
suspend fun getPointBySurveyGroup(
@Query(QUERY_GROUP_ID) groupId: String,
): ApiEnvelope<JsonElement>
/** 2.14 项目基准点列表:[projectId] 必填。 */
@GET(PATH_GET_CONTROL_POINT)
suspend fun getControlPoints(
@Query(QUERY_PROJECT_ID) projectId: String,
): ApiEnvelope<JsonElement>
/**
* 2.15 工点列表数据:[projectId]/[workPointId] 均非必填,传 null 时 Retrofit
* 省略参数(空 = 本人全部「进行中」项目工点)。
*/
@GET(PATH_GET_WORK_POINT)
suspend fun getWorkPoints(
@Query(QUERY_PROJECT_ID) projectId: String? = null,
@Query(QUERY_WORK_POINT_ID) workPointId: String? = null,
): ApiEnvelope<JsonElement>
companion object {
// ---- 接口管理规范 V4.5 · 章节 2.1 所有 API 目录 ----
const val PATH_GET_POINT_BY_SURVEY_GROUP = "OutWebApi/api/GetPointBySurveyGroup"
const val PATH_GET_CONTROL_POINT = "OutWebApi/api/ControlPoint"
const val PATH_GET_WORK_POINT = "OutWebApi/api/GetWorkPoint"
/** 查询参数名2.14/2.15 复用 ProjectID2.15 双参可空)。 */
const val QUERY_GROUP_ID = "GroupID"
const val QUERY_PROJECT_ID = "ProjectID"
const val QUERY_WORK_POINT_ID = "WorkPointID"
}
}

View File

@@ -0,0 +1,102 @@
package com.stec.cmd.core.network.api
import kotlinx.serialization.Serializable
/**
* 测点数据 DTO接口管理规范 V4.5 · 2.9/2.14/2.15 返回值示例)。
*
* 字段名与平台契约一致PascalCase文档未给类型全 `String?` 容错,
* 响应侧配合 Json.ignoreUnknownKeys 容忍字段演进。
*/
/**
* 2.9 监测点条目data.PointList
*
* 文档字段拼写歧义正文属性表列「LastMonitorDate 上次观值列表」,
* 2.9.6.2 属性说明与变更记录均为「LastMonitorData」——两键都声明
* 由 [lastMonitorValues] 归一,响应侧任一拼写均可承接。
*/
@Serializable
data class MonitorPointItem(
/** 监测点 ID上传 MonitorValue.PointID 的取值来源)。 */
val ID: String? = null,
/** 点名简称。 */
val PointSimpleName: String? = null,
/** 点名。 */
val PointName: String? = null,
/** 初值列表2.9.6.1:测斜=孔深值、轴力=轴力、其他=高程)。 */
val InitialValueData: List<PointValueItem> = emptyList(),
/** 速率报警值。 */
val RateAlarm: String? = null,
/** 累计报警值。 */
val TotalAlarm: String? = null,
/** 上次观值列表(文档 LastMonitorData/LastMonitorDate 双拼写,见类注释)。 */
val LastMonitorData: List<PointValueItem> = emptyList(),
/** 同 [LastMonitorData],文档正文属性表拼写,双键兼容。 */
val LastMonitorDate: List<PointValueItem> = emptyList(),
/** 上次观测时间。 */
val MonitorDate: String? = null,
/** 支撑轴力监测类型(钢支撑/钢筋混凝土支撑 4 种),非轴力测点为空。 */
val AxiaType: String? = null,
) {
/** 归一上次观值列表:优先 LastMonitorData2.9.6.2 口径),空则回退 LastMonitorDate。 */
val lastMonitorValues: List<PointValueItem>
get() = LastMonitorData.ifEmpty { LastMonitorDate }
/** 展示点名PointName 优先,空则回退点名简称。 */
val displayName: String?
get() = PointName?.takeIf { it.isNotBlank() } ?: PointSimpleName?.takeIf { it.isNotBlank() }
}
/**
* 2.9 初值/上次观值条目InitialValueData 与 LastMonitorData 共用一模型):
* - InitialValueData[Attribute]=孔深值/轴力/高程,[initialValue]=初值;
* - LastMonitorData[Attribute]=孔深值/传感器编号/高程,[lastMonitorValue]=上次观测值。
*/
@Serializable
data class PointValueItem(
/** 属性(含义随监测类型,见类注释)。 */
val Attribute: String? = null,
/** 初值InitialValueData。 */
val InitialValue: String? = null,
/** 上次观测值LastMonitorData。 */
val LastMonitorValue: String? = null,
) {
/** 取本条数值文本:初值优先,其次上次观测值。 */
val valueText: String?
get() = InitialValue?.takeIf { it.isNotBlank() } ?: LastMonitorValue?.takeIf { it.isNotBlank() }
}
/** 2.14 基准点条目data.ControlPointList水平位移等计算的基准坐标。 */
@Serializable
data class ControlPointItem(
/** 点名。 */
val Point: String? = null,
/** X 值。 */
val PointX: String? = null,
/** Y 值。 */
val PointY: String? = null,
/** H 值。 */
val PointZ: String? = null,
)
/** 2.15 工点条目data.WorkPointList现场负责人本人的「进行中」工点。 */
@Serializable
data class WorkPointItem(
/** 工点 ID。 */
val ID: String? = null,
/** 工点名称。 */
val Name: String? = null,
/** 项目 ID。 */
val ProjectID: String? = null,
/** 项目全称。 */
val ProjectName: String? = null,
/** 经度。 */
val Lon: String? = null,
/** 纬度(文档字段名即为 Dim。 */
val Dim: String? = null,
/** 开工时间yyyy-MM-dd。 */
val StartDate: String? = null,
/** 结束时间yyyy-MM-dd。 */
val EndDate: String? = null,
)