# 设计方案:穴位定位功能 ## 一、用户使用流程 ### 1.1 完整流程图 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 第一阶段:创建用户档案(首次使用) │ ├─────────────────────────────────────────────────────────────────────┤ │ 1. 输入基本信息 │ │ - 姓名、性别、年龄 │ │ - 肩宽(用户自行测量) │ │ - 身长(用户自行测量) │ │ - 指寸(选填):一寸、1.5寸、三寸 │ │ │ │ 2. 系统生成【穴位对照表】 │ │ - 基于用户体型数据计算各穴位相对位置 │ │ - 存储到用户档案 │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 第二阶段:艾灸治疗(每次使用) │ ├─────────────────────────────────────────────────────────────────────┤ │ 3. 用户就座 │ │ - 坐在艾灸椅上 │ │ - 传感器检测用户肩高 │ │ │ │ 4. 设备定位大椎穴 │ │ - 传感器获取大椎穴实际位置 │ │ - 作为所有穴位的基准点 │ │ │ │ 5. 用户选择治疗方案 │ │ - 选择功效(驱寒、祛湿等) │ │ - 或自定义穴位组合 │ │ │ │ 6. 开始艾灸 │ │ - 设备根据【穴位对照表】+ 大椎穴位置 │ │ - 计算各穴位实际坐标 │ │ - 控制灸头移动施灸 │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 1.2 传感器作用 | 传感器 | 功能 | 说明 | |--------|------|------| | 肩高传感器 | 检测大椎穴位置 | 第七颈椎棘突下,作为穴位定位基准 | | 无人检测 | 检测用户是否在座 | 安全保护,4分钟无人自动关机 | **注意**:肩宽由用户自行输入,传感器不检测肩宽。 --- ## 二、数据模型 ### 2.1 用户指寸数据 ```typescript // 用户指寸(三寸同身寸) interface FingerWidth { cun1: number; // 一寸 = 拇指指关节横纹宽度 cun1_5: number; // 1.5寸 = 食指+中指并拢宽度 cun3: number; // 三寸 = 四指并拢宽度 } ``` ### 2.2 用户体型数据 ```typescript // 用户体型数据 interface UserBodyData { shoulderWidth: number; // 肩宽(厘米),用户输入 bodyLength: number; // 身长(厘米),从头顶到坐骨结节 fingerWidth?: FingerWidth; // 指寸(选填) } // 用户档案 interface UserProfile { id: string; name: string; gender: 'male' | 'female'; age: number; bodyData: UserBodyData; acupointTable: AcupointReferenceTable; // 穴位对照表 createdAt: Date; updatedAt: Date; } ``` ### 2.3 穴位对照表 ```typescript // 穴位对照表(根据用户体型预计算) interface AcupointReferenceTable { userId: string; bodyData: UserBodyData; fingerWidthUsed: FingerWidth; // 实际使用的指寸值(记录或估算) acupoints: CalculatedAcupoint[]; createdAt: Date; } // 计算后的单个穴位 interface CalculatedAcupoint { id: string; // 如 'dazhui', 'fengmen_left' name: string; // 穴位名称 side: 'center' | 'left' | 'right'; offsetFromDazhui: { x: number; // 相对大椎穴的横向偏移(厘米) y: number; // 相对大椎穴的纵向偏移(厘米) }; offsetInCun: number; // 传统指寸表示 cunType: 'cun1' | 'cun1_5' | 'cun3'; } // 治疗会话 interface TreatmentSession { sessionId: string; userId: string; dazhuiPosition: { x: number; y: number }; // 传感器检测的大椎穴位置 selectedAcupoints: string[]; // 用户选择的穴位 status: 'preparing' | 'running' | 'paused' | 'completed'; } ``` --- ## 三、穴位基础数据 ### 3.1 十二常用穴位 | 序号 | 穴位名称 | 所属经络 | 相对大椎穴位置 | 功效 | |------|----------|----------|----------------|------| | 1 | 大椎穴 | 督脉 | 基准点 (0, 0) | 驱寒、祛湿、解表 | | 2 | 身柱穴 | 督脉 | 大椎穴下3寸 | 止咳平喘、安神 | | 3 | 至阳穴 | 督脉 | 大椎穴下7寸 | 宽胸理气 | | 4 | 命门穴 | 督脉 | 大椎穴下14寸 | 补肾壮阳 | | 5 | 腰阳关穴 | 督脉 | 大椎穴下17寸 | 强腰健肾 | | 6 | 风门穴(双) | 足太阳膀胱经 | 大椎穴下1.5寸,旁开1.5寸 | 祛风解表 | | 7 | 秉风穴(双) | 手太阳小肠经 | 大椎穴下3寸,旁开3寸 | 祛风散寒 | | 8 | 肺俞穴(双) | 足太阳膀胱经 | 大椎穴下3寸,旁开1.5寸 | 化痰止咳 | | 9 | 膈俞穴(双) | 足太阳膀胱经 | 大椎穴下7寸,旁开1.5寸 | 活血化瘀 | | 10 | 脾俞穴(双) | 足太阳膀胱经 | 大椎穴下11寸,旁开1.5寸 | 健脾养血 | | 11 | 肾俞穴(双) | 足太阳膀胱经 | 大椎穴下14寸,旁开1.5寸 | 补肾益气 | | 12 | 八髎穴 | 足太阳膀胱经 | 大椎穴下16寸 | 调理下焦 | ### 3.2 穴位偏移数据结构 ```typescript // 穴位偏移基准数据(以指寸为单位) interface AcupointOffset { id: string; name: string; cun: number; // 距离大椎穴的指寸数 cunType: 'cun1' | 'cun1_5' | 'cun3'; // 使用的指寸类型 side: 'center' | 'left' | 'right'; lateralOffset?: number; // 横向偏移(指寸) } const ACUPOINT_OFFSETS: AcupointOffset[] = [ // 督脉穴位(正中) { id: 'dazhui', name: '大椎穴', cun: 0, cunType: 'cun1', side: 'center' }, { id: 'shenzhu', name: '身柱穴', cun: 3, cunType: 'cun1', side: 'center' }, { id: 'zhiyang', name: '至阳穴', cun: 7, cunType: 'cun1', side: 'center' }, { id: 'mingmen', name: '命门穴', cun: 14, cunType: 'cun1', side: 'center' }, { id: 'yaoyangguan', name: '腰阳关穴', cun: 17, cunType: 'cun1', side: 'center' }, // 膀胱经穴位(双侧) { id: 'fengmen', name: '风门穴', cun: 1.5, cunType: 'cun1_5', side: 'left', lateralOffset: 1.5 }, { id: 'fengmen', name: '风门穴', cun: 1.5, cunType: 'cun1_5', side: 'right', lateralOffset: 1.5 }, { id: 'feishu', name: '肺俞穴', cun: 3, cunType: 'cun1', side: 'left', lateralOffset: 1.5 }, { id: 'feishu', name: '肺俞穴', cun: 3, cunType: 'cun1', side: 'right', lateralOffset: 1.5 }, { id: 'geshu', name: '膈俞穴', cun: 7, cunType: 'cun1', side: 'left', lateralOffset: 1.5 }, { id: 'geshu', name: '膈俞穴', cun: 7, cunType: 'cun1', side: 'right', lateralOffset: 1.5 }, { id: 'pishu', name: '脾俞穴', cun: 11, cunType: 'cun1', side: 'left', lateralOffset: 1.5 }, { id: 'pishu', name: '脾俞穴', cun: 11, cunType: 'cun1', side: 'right', lateralOffset: 1.5 }, { id: 'shenshu', name: '肾俞穴', cun: 14, cunType: 'cun1', side: 'left', lateralOffset: 1.5 }, { id: 'shenshu', name: '肾俞穴', cun: 14, cunType: 'cun1', side: 'right', lateralOffset: 1.5 }, // 小肠经穴位(双侧) { id: 'bingfeng', name: '秉风穴', cun: 3, cunType: 'cun1', side: 'left', lateralOffset: 3 }, { id: 'bingfeng', name: '秉风穴', cun: 3, cunType: 'cun1', side: 'right', lateralOffset: 3 }, // 八髎穴 { id: 'baliao', name: '八髎穴', cun: 16, cunType: 'cun1', side: 'center' }, ]; ``` --- ## 四、核心算法 ### 4.1 指寸估算算法 ```typescript /** * 当用户未提供指寸数据时,根据肩宽和身长估算 * 考虑男女体型差异:男性肩宽影响更大,女性身长影响更大 */ function estimateFingerWidth( shoulderWidth: number, bodyLength: number, gender: 'male' | 'female' ): FingerWidth { // 标准体型参考(中国成年人平均数据,基于 GB/T 10000-1988) const STANDARD = { male: { shoulderWidth: 42, // 男性平均肩宽(厘米) bodyLength: 85, // 男性平均坐高(厘米) cun1: 2.2, // 男性一寸约2.2cm cun1_5: 3.3, // 男性1.5寸约3.3cm cun3: 6.5, // 男性三寸约6.5cm // 男性特征:肩宽差异更显著 shoulderWeight: 0.6, // 肩宽权重 lengthWeight: 0.4 // 身长权重 }, female: { shoulderWidth: 36, // 女性平均肩宽(厘米) bodyLength: 78, // 女性平均坐高(厘米) cun1: 1.9, // 女性一寸约1.9cm cun1_5: 2.8, // 女性1.5寸约2.8cm cun3: 5.6, // 女性三寸约5.6cm // 女性特征:身长差异更显著 shoulderWeight: 0.4, // 肩宽权重 lengthWeight: 0.6 // 身长权重 } }; const ref = STANDARD[gender]; // 分性别加权缩放因子 // 男性:肩宽权重更高(0.6),因为男性肩宽差异对手指粗细影响更明显 // 女性:身长权重更高(0.6),因为女性体型比例更依赖身高 const widthScale = shoulderWidth / ref.shoulderWidth; const lengthScale = bodyLength / ref.bodyLength; // 加权平均(考虑性别差异) const weightedScale = widthScale * ref.shoulderWeight + lengthScale * ref.lengthWeight; // 指寸比例关系:三寸≈一寸×3,1.5寸≈一寸×1.5 // 确保估算结果符合中医指寸定义 const estimatedCun1 = ref.cun1 * weightedScale; return { cun1: estimatedCun1, cun1_5: estimatedCun1 * 1.5, // 严格按比例 cun3: estimatedCun1 * 3 // 严格按比例 }; } /** * 估算公式的适用性检查 * 返回估算结果的可靠性评估 */ function assessEstimationReliability( shoulderWidth: number, bodyLength: number, gender: 'male' | 'female' ): { reliable: boolean; warning?: string } { const RANGES = { male: { shoulder: [36, 52], bodyLength: [75, 95] }, female: { shoulder: [32, 46], bodyLength: [70, 88] } }; const range = RANGES[gender]; // 检查是否在正常范围内 const shoulderInRange = shoulderWidth >= range.shoulder[0] && shoulderWidth <= range.shoulder[1]; const bodyLengthInRange = bodyLength >= range.bodyLength[0] && bodyLength <= range.bodyLength[1]; if (!shoulderInRange && !bodyLengthInRange) { return { reliable: false, warning: '肩宽和身长均超出正常范围,估算结果可能不准确,建议提供实测指寸数据' }; } if (!shoulderInRange) { return { reliable: true, warning: '肩宽超出正常范围,建议提供实测指寸数据以提高精度' }; } if (!bodyLengthInRange) { return { reliable: true, warning: '身长超出正常范围,建议提供实测指寸数据以提高精度' }; } return { reliable: true }; } ``` ### 4.2 穴位对照表生成算法 ```typescript /** * 生成用户专属的穴位对照表 */ function generateAcupointReferenceTable( bodyData: UserBodyData, gender: 'male' | 'female' ): AcupointReferenceTable { const { shoulderWidth, bodyLength, fingerWidth } = bodyData; // 获取或估算指寸 const cun: FingerWidth = fingerWidth ? fingerWidth : estimateFingerWidth(shoulderWidth, bodyLength, gender); // 计算各穴位位置 const acupoints: CalculatedAcupoint[] = ACUPOINT_OFFSETS.map(ap => { // Y方向偏移:指寸数 × 对应指寸值 const distanceY = ap.cun * cun[ap.cunType]; // X方向偏移:横向指寸 × 一寸值 const distanceX = ap.side === 'center' ? 0 : (ap.side === 'left' ? -1 : 1) * (ap.lateralOffset || 0) * cun.cun1; return { id: ap.id + (ap.side !== 'center' ? '_' + ap.side : ''), name: ap.name, side: ap.side, offsetFromDazhui: { x: distanceX, y: -distanceY // 负值表示向下 }, offsetInCun: ap.cun, cunType: ap.cunType }; }); return { bodyData, fingerWidthUsed: cun, acupoints, createdAt: new Date() }; } ``` ### 4.3 实时穴位定位算法 ```typescript /** * 治疗时根据传感器检测的大椎穴位置计算实际穴位坐标 */ function calculateActualPositions( referenceTable: AcupointReferenceTable, dazhuiPosition: { x: number; y: number } ): Map { const positions = new Map(); for (const acupoint of referenceTable.acupoints) { positions.set(acupoint.id, { x: dazhuiPosition.x + acupoint.offsetFromDazhui.x, y: dazhuiPosition.y + acupoint.offsetFromDazhui.y }); } return positions; } ``` --- ## 五、输入数据验证 ### 5.1 验证规则 ```typescript // 验证结果 interface ValidationResult { isValid: boolean; errors: ValidationError[]; warnings: ValidationWarning[]; suggestions: string[]; } interface ValidationError { field: string; message: string; actualValue: number; expectedRange: { min: number; max: number }; } interface ValidationWarning { field: string; message: string; suggestion: string; } ``` ### 5.2 身体数据验证 ```typescript /** * 验证用户输入的身体数据 */ function validateBodyData(bodyData: UserBodyData, gender: 'male' | 'female'): ValidationResult { const errors: ValidationError[] = []; const warnings: ValidationWarning[] = []; const suggestions: string[] = []; // 身体数据范围(基于 GB/T 10000-1988) const RANGES = { male: { shoulderWidth: { min: 36, max: 52, typical: 42 }, // 单位:厘米 bodyLength: { min: 75, max: 95, typical: 85 }, // 坐高 cun1: { min: 1.5, max: 2.8, typical: 2.0 }, cun1_5: { min: 2.2, max: 4.0, typical: 3.0 }, cun3: { min: 4.5, max: 8.0, typical: 6.0 } }, female: { shoulderWidth: { min: 32, max: 46, typical: 36 }, bodyLength: { min: 70, max: 88, typical: 78 }, cun1: { min: 1.3, max: 2.4, typical: 1.8 }, cun1_5: { min: 2.0, max: 3.5, typical: 2.7 }, cun3: { min: 4.0, max: 7.0, typical: 5.4 } } }; const range = RANGES[gender]; // 验证肩宽 if (bodyData.shoulderWidth < range.shoulderWidth.min || bodyData.shoulderWidth > range.shoulderWidth.max) { errors.push({ field: 'shoulderWidth', message: '肩宽数值超出正常范围', actualValue: bodyData.shoulderWidth, expectedRange: range.shoulderWidth }); } // 验证身长(坐高) if (bodyData.bodyLength < range.bodyLength.min || bodyData.bodyLength > range.bodyLength.max) { errors.push({ field: 'bodyLength', message: '身长数值超出正常范围', actualValue: bodyData.bodyLength, expectedRange: range.bodyLength }); } // 验证指寸数据(如果提供) if (bodyData.fingerWidth) { const fw = bodyData.fingerWidth; // 验证一寸 if (fw.cun1 < range.cun1.min || fw.cun1 > range.cun1.max) { warnings.push({ field: 'fingerWidth.cun1', message: '一寸数值异常', suggestion: `正常范围:${range.cun1.min}-${range.cun1.max}cm,您输入的是 ${fw.cun1}cm` }); } // 验证1.5寸 if (fw.cun1_5 < range.cun1_5.min || fw.cun1_5 > range.cun1_5.max) { warnings.push({ field: 'fingerWidth.cun1_5', message: '1.5寸数值异常', suggestion: `正常范围:${range.cun1_5.min}-${range.cun1_5.max}cm,您输入的是 ${fw.cun1_5}cm` }); } // 验证三寸 if (fw.cun3 < range.cun3.min || fw.cun3 > range.cun3.max) { warnings.push({ field: 'fingerWidth.cun3', message: '三寸数值异常', suggestion: `正常范围:${range.cun3.min}-${range.cun3.max}cm,您输入的是 ${fw.cun3}cm` }); } // 内部一致性检查:三寸应该约等于一寸的3倍 const cun3Expected = fw.cun1 * 3; const cun3Diff = Math.abs(fw.cun3 - cun3Expected); if (cun3Diff > fw.cun1 * 0.3) { // 允许30%偏差 warnings.push({ field: 'fingerWidth', message: '指寸数据不一致', suggestion: `三寸应该约为一寸的3倍(${cun3Expected.toFixed(1)}cm),您输入的三寸是 ${fw.cun3}cm` }); } // 1.5寸应该约等于一寸的1.5倍 const cun1_5Expected = fw.cun1 * 1.5; const cun1_5Diff = Math.abs(fw.cun1_5 - cun1_5Expected); if (cun1_5Diff > fw.cun1 * 0.25) { // 允许25%偏差 warnings.push({ field: 'fingerWidth', message: '指寸数据不一致', suggestion: `1.5寸应该约为一寸的1.5倍(${cun1_5Expected.toFixed(1)}cm),您输入的是 ${fw.cun1_5}cm` }); } } // 生成建议 if (errors.length > 0) { suggestions.push('请检查输入数据是否正确,参考测量指南重新测量'); } if (warnings.length > 0 && bodyData.fingerWidth) { suggestions.push('建议重新测量指寸数据,确保测量方法正确'); } return { isValid: errors.length === 0, errors, warnings, suggestions }; } ``` ### 5.3 测量异常处理 ```typescript /** * 处理测量异常,提供修正建议 */ function handleMeasurementAnomaly( validationResult: ValidationResult, bodyData: UserBodyData, gender: 'male' | 'female' ): { action: 'reject' | 'accept_with_warning' | 'auto_correct'; correctedData?: UserBodyData } { // 有错误数据,拒绝处理 if (validationResult.errors.length > 0) { return { action: 'reject' }; } // 有警告但无错误,检查严重程度 if (validationResult.warnings.length > 0) { // 如果只是轻微不一致,接受但警告 const hasSevereWarning = validationResult.warnings.some( w => w.field === 'fingerWidth' && w.message.includes('不一致') ); if (!hasSevereWarning) { return { action: 'accept_with_warning' }; } } return { action: 'accept_with_warning' }; } ``` ### 5.4 实时输入验证(前端) ```typescript /** * 输入时实时验证(用于前端表单) */ function validateInputField( field: 'shoulderWidth' | 'bodyLength' | 'cun1' | 'cun1_5' | 'cun3', value: number, gender: 'male' | 'female' ): { valid: boolean; message?: string } { const RANGES = { male: { shoulderWidth: { min: 36, max: 52 }, bodyLength: { min: 75, max: 95 }, cun1: { min: 1.5, max: 2.8 }, cun1_5: { min: 2.2, max: 4.0 }, cun3: { min: 4.5, max: 8.0 } }, female: { shoulderWidth: { min: 32, max: 46 }, bodyLength: { min: 70, max: 88 }, cun1: { min: 1.3, max: 2.4 }, cun1_5: { min: 2.0, max: 3.5 }, cun3: { min: 4.0, max: 7.0 } } }; const range = RANGES[gender][field]; if (value < range.min || value > range.max) { return { valid: false, message: `正常范围:${range.min}-${range.max}cm` }; } return { valid: true }; } ``` ### 5.5 验证错误码 | 错误码 | 描述 | 处理建议 | |--------|------|----------| | `E001` | 肩宽超出范围 | 检查测量单位是否为厘米 | | `E002` | 身长超出范围 | 确认为坐高而非身高 | | `E003` | 指寸数据缺失 | 建议用户提供指寸以获得更高精度 | | `W001` | 一寸数值异常 | 检查测量方法,参考测量指南 | | `W002` | 指寸数据不一致 | 建议重新测量全部指寸数据 | | `W003` | 指寸与体型比例不符 | 检查是否测量错误 | --- ## 六、置信度计算 ### 6.1 置信度数据结构 ```typescript // 单个穴位的置信度 interface AcupointConfidence { acupointId: string; confidence: number; // 0-1 accuracyRange: number; // 精度范围(厘米) factors: ConfidenceFactor[]; // 影响因素 } interface ConfidenceFactor { name: string; impact: number; // 正值提升,负值降低 description: string; } // 整体置信度报告 interface ConfidenceReport { overall: number; // 综合置信度 byAcupoint: Map; dataQuality: DataQualityAssessment; recommendations: string[]; } interface DataQualityAssessment { fingerWidthScore: number; // 指寸数据质量(0-100) bodyDataScore: number; // 体型数据质量(0-100) consistencyScore: number; // 数据一致性(0-100) overallScore: number; // 综合质量分 } ``` ### 6.2 基础置信度计算 ```typescript /** * 计算整体置信度(原有简化版,保留向后兼容) */ function calculateConfidence(bodyData: UserBodyData): number { let confidence = 0.5; // 基础置信度(仅有肩宽、身长) if (bodyData.fingerWidth) { if (bodyData.fingerWidth.cun1) confidence += 0.15; if (bodyData.fingerWidth.cun1_5) confidence += 0.15; if (bodyData.fingerWidth.cun3) confidence += 0.15; } return Math.min(confidence, 1.0); } ``` ### 6.3 详细置信度计算(按穴位) ```typescript /** * 计算每个穴位的独立置信度 */ function calculateDetailedConfidence( bodyData: UserBodyData, gender: 'male' | 'female', acupoints: CalculatedAcupoint[] ): ConfidenceReport { const byAcupoint = new Map(); const recommendations: string[] = []; // 计算数据质量评分 const dataQuality = assessDataQuality(bodyData, gender); // 为每个穴位计算置信度 for (const acupoint of acupoints) { const confidence = calculateAcupointConfidence( acupoint, bodyData, gender, dataQuality ); byAcupoint.set(acupoint.id, confidence); } // 计算综合置信度 const overall = calculateOverallConfidence(byAcupoint, dataQuality); // 生成建议 if (dataQuality.fingerWidthScore < 70) { recommendations.push('建议提供指寸数据以提高定位精度'); } if (dataQuality.consistencyScore < 80) { recommendations.push('指寸数据存在不一致,建议重新测量'); } return { overall, byAcupoint, dataQuality, recommendations }; } /** * 评估数据质量 */ function assessDataQuality( bodyData: UserBodyData, gender: 'male' | 'female' ): DataQualityAssessment { let fingerWidthScore = 0; let consistencyScore = 100; let bodyDataScore = 100; // 指寸数据评分 if (bodyData.fingerWidth) { const fw = bodyData.fingerWidth; let score = 0; // 每个指寸数据贡献33分 if (fw.cun1 > 0) score += 33; if (fw.cun1_5 > 0) score += 33; if (fw.cun3 > 0) score += 34; fingerWidthScore = score; // 一致性检查 const cun3Expected = fw.cun1 * 3; const cun3Ratio = Math.abs(fw.cun3 - cun3Expected) / fw.cun1; if (cun3Ratio > 0.3) { consistencyScore -= 20; } const cun1_5Expected = fw.cun1 * 1.5; const cun1_5Ratio = Math.abs(fw.cun1_5 - cun1_5Expected) / fw.cun1; if (cun1_5Ratio > 0.25) { consistencyScore -= 15; } } // 体型数据评分(检查是否在合理范围) const RANGES = gender === 'male' ? { shoulder: [36, 52], bodyLength: [75, 95] } : { shoulder: [32, 46], bodyLength: [70, 88] }; if (bodyData.shoulderWidth < RANGES.shoulder[0] || bodyData.shoulderWidth > RANGES.shoulder[1]) { bodyDataScore -= 30; } if (bodyData.bodyLength < RANGES.bodyLength[0] || bodyData.bodyLength > RANGES.bodyLength[1]) { bodyDataScore -= 30; } const overallScore = (fingerWidthScore + bodyDataScore + consistencyScore) / 3; return { fingerWidthScore, bodyDataScore, consistencyScore, overallScore }; } /** * 计算单个穴位的置信度 */ function calculateAcupointConfidence( acupoint: CalculatedAcupoint, bodyData: UserBodyData, gender: 'male' | 'female', dataQuality: DataQualityAssessment ): AcupointConfidence { const factors: ConfidenceFactor[] = []; let confidence = 0.5; // 因素1:指寸数据完整性 if (bodyData.fingerWidth) { const fw = bodyData.fingerWidth; if (acupoint.cunType === 'cun1' && fw.cun1 > 0) { confidence += 0.15; factors.push({ name: '一寸实测', impact: 0.15, description: '使用实测一寸数据' }); } if (acupoint.cunType === 'cun1_5' && fw.cun1_5 > 0) { confidence += 0.20; // 1.5寸对旁开穴位更关键 factors.push({ name: '1.5寸实测', impact: 0.20, description: '使用实测1.5寸数据' }); } if (acupoint.cunType === 'cun3' && fw.cun3 > 0) { confidence += 0.15; factors.push({ name: '三寸实测', impact: 0.15, description: '使用实测三寸数据' }); } } // 因素2:穴位类型影响 // 督脉穴位(正中)比双侧穴位更可靠 if (acupoint.side === 'center') { confidence += 0.05; factors.push({ name: '督脉穴位', impact: 0.05, description: '正中线穴位定位更可靠' }); } else { // 双侧穴位受横向偏移影响 confidence -= 0.03; factors.push({ name: '双侧穴位', impact: -0.03, description: '横向偏移增加不确定性' }); } // 因素3:距离大椎穴的远近 // 距离越远,累积误差越大 const distanceY = Math.abs(acupoint.offsetFromDazhui.y); if (distanceY > 30) { // 超过30cm confidence -= 0.05; factors.push({ name: '远距离穴位', impact: -0.05, description: '距离基准点较远' }); } // 因素4:数据一致性惩罚 if (dataQuality.consistencyScore < 80) { const penalty = (80 - dataQuality.consistencyScore) / 100; confidence -= penalty; factors.push({ name: '数据一致性', impact: -penalty, description: '指寸数据存在不一致' }); } // 确保置信度在0-1范围内 confidence = Math.max(0, Math.min(1, confidence)); // 计算精度范围 const accuracyRange = calculateAccuracyRange(confidence); return { acupointId: acupoint.id, confidence, accuracyRange, factors }; } /** * 根据置信度计算精度范围 */ function calculateAccuracyRange(confidence: number): number { // 置信度 50% → ±5cm // 置信度 95% → ±2cm // 置信度 100% → ±1.5cm // 线性插值 if (confidence >= 0.95) return 2.0; if (confidence >= 0.80) return 3.0; if (confidence >= 0.65) return 4.0; return 5.0; } /** * 计算综合置信度 */ function calculateOverallConfidence( byAcupoint: Map, dataQuality: DataQualityAssessment ): number { if (byAcupoint.size === 0) return 0; // 加权平均:考虑穴位重要性 // 督脉穴位权重更高(定位基准) let totalWeight = 0; let weightedSum = 0; for (const [id, conf] of byAcupoint) { const weight = id.includes('left') || id.includes('right') ? 1.0 : 1.5; weightedSum += conf.confidence * weight; totalWeight += weight; } const avgConfidence = weightedSum / totalWeight; // 数据质量修正 const qualityFactor = dataQuality.overallScore / 100; return Math.min(avgConfidence * (0.7 + 0.3 * qualityFactor), 1.0); } ``` ### 6.5 置信度与精度对照表(更新) | 数据完整度 | 综合置信度 | 预估精度 | 适用穴位 | 说明 | |-----------|-----------|---------|---------|------| | 仅肩宽+身长 | 50% | ±5cm | 全部 | 指寸为估算值 | | + 一寸数据 | 65% | ±4cm | 督脉穴位 | 部分指寸实测 | | + 一寸+1.5寸 | 80% | ±3cm | 全部 | 大部分指寸实测 | | + 完整指寸 | 95% | ±2cm | 全部 | 指寸全部实测 | ### 6.6 不同穴位的典型置信度 | 穴位 | 典型置信度 | 影响因素 | 精度范围 | |------|-----------|---------|---------| | 大椎穴 | 100% | 基准点 | ±2cm | | 身柱穴 | 95% | 督脉正中,距离近 | ±2cm | | 至阳穴 | 92% | 督脉正中,距离中等 | ±2.5cm | | 命门穴 | 88% | 督脉正中,距离远 | ±3cm | | 风门穴(双) | 85% | 双侧,需横向偏移 | ±3cm | | 肺俞穴(双) | 83% | 双侧,需横向偏移 | ±3.5cm | | 肾俞穴(双) | 78% | 双侧,距离远 | ±4cm | | 八髎穴 | 80% | 正中,距离最远 | ±4cm | --- ## 七、指寸测量指南 ### 7.1 测量方法 | 指寸 | 测量方法 | 图示 | |------|---------|------| | 一寸 | 拇指指关节横纹宽度 | 🤜 拇指 | | 1.5寸 | 食指+中指并拢宽度 | ✌️ 两指 | | 三寸 | 四指并拢宽度(除拇指) | 🖖 四指 | ### 7.2 测量注意事项 1. **一寸**:拇指弯曲,测量指关节最宽处 2. **1.5寸**:食指和中指自然并拢,测量两指总宽度 3. **三寸**:四指自然并拢,测量总宽度 --- ## 八、API 设计 ### 8.1 端点列表 | 方法 | 路径 | 描述 | |------|------|------| | POST | `/api/users/:id/acupoint-table` | 生成用户穴位对照表 | | POST | `/api/users/:id/acupoint-table/validate` | 验证输入数据(生成前预检查) | | GET | `/api/users/:id/acupoint-table` | 获取用户穴位对照表 | | POST | `/api/treatment-sessions` | 创建治疗会话 | | POST | `/api/treatment-sessions/:id/start` | 开始治疗(传入大椎穴位置) | | GET | `/api/acupoints` | 获取所有穴位基础数据 | | GET | `/api/acupoints/:id` | 获取单个穴位详情 | ### 8.2 请求/响应示例 ```typescript // POST /api/users/:id/acupoint-table/validate // Request { "bodyData": { "shoulderWidth": 42, "bodyLength": 85, "fingerWidth": { "cun1": 2.1, "cun1_5": 3.2, "cun3": 6.3 } }, "gender": "male" } // Response (验证通过) { "isValid": true, "errors": [], "warnings": [], "suggestions": [] } // Response (有警告) { "isValid": true, "errors": [], "warnings": [ { "field": "fingerWidth", "message": "指寸数据不一致", "suggestion": "三寸应该约为一寸的3倍(6.3cm),您输入的三寸是 5.5cm" } ], "suggestions": ["建议重新测量指寸数据,确保测量方法正确"] } // POST /api/users/:id/acupoint-table // Request { "bodyData": { "shoulderWidth": 42, "bodyLength": 85, "fingerWidth": { "cun1": 2.1, "cun1_5": 3.2, "cun3": 6.3 } } } // Response { "userId": "user_001", "acupointTable": { "bodyData": { ... }, "fingerWidthUsed": { "cun1": 2.1, "cun1_5": 3.2, "cun3": 6.3 }, "acupoints": [ { "id": "dazhui", "name": "大椎穴", "side": "center", "offsetFromDazhui": { "x": 0, "y": 0 }, "offsetInCun": 0, "cunType": "cun1" }, { "id": "shenzhu", "name": "身柱穴", "side": "center", "offsetFromDazhui": { "x": 0, "y": -6.3 }, "offsetInCun": 3, "cunType": "cun1" }, { "id": "fengmen_left", "name": "风门穴", "side": "left", "offsetFromDazhui": { "x": -3.15, "y": -4.8 }, "offsetInCun": 1.5, "cunType": "cun1_5" } // ... 其他穴位 ], "createdAt": "2026-03-31T12:00:00Z" }, "confidence": 0.95 } // POST /api/treatment-sessions/:id/start // Request { "dazhuiPosition": { "x": 0, "y": 150 }, // 传感器检测的大椎穴位置 "selectedAcupoints": ["dazhui", "shenzhu", "zhiyang"] } // Response { "sessionId": "session_001", "actualPositions": { "dazhui": { "x": 0, "y": 150 }, "shenzhu": { "x": 0, "y": 143.7 }, "zhiyang": { "x": 0, "y": 135.9 } }, "status": "running" } ``` --- ## 九、技术选型 | 层级 | 技术 | 理由 | |------|------|------| | 前端可视化 | Canvas + SVG | 经络图绘制,支持缩放、点击交互 | | 状态管理 | Zustand | 穴位选择状态,轻量高效 | | 数据格式 | JSON | 穴位数据存储,易于维护 | | 坐标计算 | TypeScript | 类型安全,精度控制 | | API | RESTful | 与现有架构一致 | --- ## 十、实施步骤 ### Wave 1: 数据层 1. 创建指寸和穴位数据类型 2. 实现指寸估算算法 3. 实现穴位对照表生成算法 4. 编写单元测试 ### Wave 2: API 层 1. 创建用户穴位对照表 API 2. 创建治疗会话 API 3. 编写 API 文档 ### Wave 3: 前端实现 1. 用户指寸录入界面 2. 穴位对照表展示 3. 经络图可视化组件 ### Wave 4: 集成测试 1. 与硬件传感器集成 2. 端到端测试 3. 验收测试 --- ## 十一、参考标准 - **GB/T 12346-2006**《腧穴名称与定位》 - **GB/T 10000-1988**《中国成年人人体尺寸》 - 《针灸学》教材(穴位定位方法)