设计方案:穴位定位功能
一、用户使用流程
1.1 完整流程图
┌─────────────────────────────────────────────────────────────────────┐
│ 第一阶段:创建用户档案(首次使用) │
├─────────────────────────────────────────────────────────────────────┤
│ 1. 输入基本信息 │
│ - 姓名、性别、年龄 │
│ - 肩宽(用户自行测量) │
│ - 身长(用户自行测量) │
│ - 指寸(选填):一寸、1.5寸、三寸 │
│ │
│ 2. 系统生成【穴位对照表】 │
│ - 基于用户体型数据计算各穴位相对位置 │
│ - 存储到用户档案 │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 第二阶段:艾灸治疗(每次使用) │
├─────────────────────────────────────────────────────────────────────┤
│ 3. 用户就座 │
│ - 坐在艾灸椅上 │
│ - 传感器检测用户肩高 │
│ │
│ 4. 设备定位大椎穴 │
│ - 传感器获取大椎穴实际位置 │
│ - 作为所有穴位的基准点 │
│ │
│ 5. 用户选择治疗方案 │
│ - 选择功效(驱寒、祛湿等) │
│ - 或自定义穴位组合 │
│ │
│ 6. 开始艾灸 │
│ - 设备根据【穴位对照表】+ 大椎穴位置 │
│ - 计算各穴位实际坐标 │
│ - 控制灸头移动施灸 │
└─────────────────────────────────────────────────────────────────────┘
1.2 传感器作用
| 传感器 |
功能 |
说明 |
| 肩高传感器 |
检测大椎穴位置 |
第七颈椎棘突下,作为穴位定位基准 |
| 无人检测 |
检测用户是否在座 |
安全保护,4分钟无人自动关机 |
注意:肩宽由用户自行输入,传感器不检测肩宽。
二、数据模型
2.1 用户指寸数据
// 用户指寸(三寸同身寸)
interface FingerWidth {
cun1: number; // 一寸 = 拇指指关节横纹宽度
cun1_5: number; // 1.5寸 = 食指+中指并拢宽度
cun3: number; // 三寸 = 四指并拢宽度
}
2.2 用户体型数据
// 用户体型数据
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 穴位对照表
// 穴位对照表(根据用户体型预计算)
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 穴位偏移数据结构
// 穴位偏移基准数据(以指寸为单位)
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 指寸估算算法
/**
* 当用户未提供指寸数据时,根据肩宽和身长估算
* 考虑男女体型差异:男性肩宽影响更大,女性身长影响更大
*/
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 穴位对照表生成算法
/**
* 生成用户专属的穴位对照表
*/
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 实时穴位定位算法
/**
* 治疗时根据传感器检测的大椎穴位置计算实际穴位坐标
*/
function calculateActualPositions(
referenceTable: AcupointReferenceTable,
dazhuiPosition: { x: number; y: number }
): Map<string, { x: number; y: number }> {
const positions = new Map<string, { x: number; y: number }>();
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 验证规则
// 验证结果
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 身体数据验证
/**
* 验证用户输入的身体数据
*/
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 测量异常处理
/**
* 处理测量异常,提供修正建议
*/
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 实时输入验证(前端)
/**
* 输入时实时验证(用于前端表单)
*/
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 置信度数据结构
// 单个穴位的置信度
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<string, AcupointConfidence>;
dataQuality: DataQualityAssessment;
recommendations: string[];
}
interface DataQualityAssessment {
fingerWidthScore: number; // 指寸数据质量(0-100)
bodyDataScore: number; // 体型数据质量(0-100)
consistencyScore: number; // 数据一致性(0-100)
overallScore: number; // 综合质量分
}
6.2 基础置信度计算
/**
* 计算整体置信度(原有简化版,保留向后兼容)
*/
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 详细置信度计算(按穴位)
/**
* 计算每个穴位的独立置信度
*/
function calculateDetailedConfidence(
bodyData: UserBodyData,
gender: 'male' | 'female',
acupoints: CalculatedAcupoint[]
): ConfidenceReport {
const byAcupoint = new Map<string, AcupointConfidence>();
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<string, AcupointConfidence>,
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.5寸:食指和中指自然并拢,测量两指总宽度
- 三寸:四指自然并拢,测量总宽度
八、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 请求/响应示例
// 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: 数据层
- 创建指寸和穴位数据类型
- 实现指寸估算算法
- 实现穴位对照表生成算法
- 编写单元测试
Wave 2: API 层
- 创建用户穴位对照表 API
- 创建治疗会话 API
- 编写 API 文档
Wave 3: 前端实现
- 用户指寸录入界面
- 穴位对照表展示
- 经络图可视化组件
Wave 4: 集成测试
- 与硬件传感器集成
- 端到端测试
- 验收测试
十一、参考标准
- GB/T 12346-2006《腧穴名称与定位》
- GB/T 10000-1988《中国成年人人体尺寸》
- 《针灸学》教材(穴位定位方法)