import { FASTAPI_BASE_URL, authHeaders, createTrainingSession, readError, type PatientConfigPayload, type TrainingMode } from './session' export type ScenarioRecommendation = { id: string title: string desc: string tags: string[] defaults: ScenarioForm } export type ScenarioOption = { value: string label: string description?: string } export type ScenarioForm = { environment: string ageGroup: string education: string personality: string } export type ScenarioOptions = { environments: ScenarioOption[] ageGroups: ScenarioOption[] educations: ScenarioOption[] personalities: ScenarioOption[] } export type TrainingConfigRecommended = { recommended: ScenarioForm recommendedLabels: Record options: ScenarioOptions } export type ScenarioConfigPayload = ScenarioForm & { caseId: string caseNo: string mode: TrainingMode recommendationId?: string } type TrainingConfigApiKey = 'visit_environment' | 'age_group' | 'education_level' | 'personality' const DEFAULT_CASE_ID = 1 type ApiEnvelope = { code: string message: string data: T } type ScenarioFormKey = keyof ScenarioForm type TrainingConfigRecommendedResponse = { case_id: number recommended: { visit_environment: string age_group: string education_level: string personality: string } recommended_labels: { visit_environment: string age_group: string education_level: string personality: string } options?: Partial>> } export const DEFAULT_SCENARIO_OPTIONS: ScenarioOptions = { environments: [ { value: 'outpatient', label: '门诊' }, { value: 'emergency', label: '急诊' }, { value: 'ward', label: '病房' } ], ageGroups: [ { value: 'child', label: '儿童' }, { value: 'youth', label: '青年' }, { value: 'middle_aged', label: '中年' }, { value: 'elderly', label: '老年' } ], educations: [ { value: 'primary_or_below', label: '小学及以下' }, { value: 'secondary', label: '中等教育' }, { value: 'higher', label: '高等教育' } ], personalities: [ { value: 'calm', label: '平和' }, { value: 'anxious', label: '焦虑' }, { value: 'impatient', label: '急躁' }, { value: 'cooperative', label: '配合' }, { value: 'suspicious', label: '多疑' } ] } export function fetchScenarioOptions() { return Promise.resolve({ recommendations: [ { id: 'typical-outpatient', title: '典型门诊病例', desc: '门诊常规病例:针对初学者设计的标准化沟通流程。', tags: ['中年', '配合'], defaults: { environment: 'outpatient', ageGroup: 'middle_aged', education: 'higher', personality: 'calm' } }, { id: 'emergency-crisis', title: '急诊重症危机', desc: '急诊危重:急性心梗紧急入院。', tags: ['老年', '急躁'], defaults: { environment: 'emergency', ageGroup: 'elderly', education: 'secondary', personality: 'impatient' } } ] as ScenarioRecommendation[], options: DEFAULT_SCENARIO_OPTIONS }) } function normalizeApiOptions( source: TrainingConfigRecommendedResponse['options'] | undefined, key: keyof TrainingConfigRecommendedResponse['recommended'], fallback: ScenarioOption[] ) { const value = source?.[key] if (Array.isArray(value)) return value if (value && typeof value === 'object') { return Object.entries(value).map(([optionValue, label]) => ({ value: optionValue, label: String(label) })) } return fallback } function normalizeTrainingConfig(payload: TrainingConfigRecommendedResponse): TrainingConfigRecommended { return { recommended: { environment: payload.recommended.visit_environment, ageGroup: payload.recommended.age_group, education: payload.recommended.education_level, personality: payload.recommended.personality }, recommendedLabels: { environment: payload.recommended_labels.visit_environment, ageGroup: payload.recommended_labels.age_group, education: payload.recommended_labels.education_level, personality: payload.recommended_labels.personality }, options: { environments: normalizeApiOptions(payload.options, 'visit_environment', DEFAULT_SCENARIO_OPTIONS.environments), ageGroups: normalizeApiOptions(payload.options, 'age_group', DEFAULT_SCENARIO_OPTIONS.ageGroups), educations: normalizeApiOptions(payload.options, 'education_level', DEFAULT_SCENARIO_OPTIONS.educations), personalities: normalizeApiOptions(payload.options, 'personality', DEFAULT_SCENARIO_OPTIONS.personalities) } } } export async function fetchTrainingConfigOptions(caseId: number) { const response = await fetch(`${FASTAPI_BASE_URL}/training-config/options?case_id=${encodeURIComponent(caseId)}`, { method: 'GET', headers: authHeaders() }) if (!response.ok) { throw new Error(await readError(response)) } const result = (await response.json()) as ApiEnvelope if (result.code !== 'OK' || !result.data?.recommended) { throw new Error(result.message || '推荐配置加载失败') } return normalizeTrainingConfig(result.data) } export function createScenarioConfig(payload: ScenarioConfigPayload) { return createTrainingSession({ case_id: DEFAULT_CASE_ID, training_type: 'diagnosis_treatment', mode: payload.mode, score_type: 'percentage', patient_config: { visit_environment: payload.environment as PatientConfigPayload['visit_environment'], age_group: payload.ageGroup as PatientConfigPayload['age_group'], education_level: payload.education as PatientConfigPayload['education_level'], personality: payload.personality as PatientConfigPayload['personality'] } }).then(session => ({ id: session.session_code, ...payload, session, createdAt: new Date().toISOString() })) } function resolveCaseId(payload: ScenarioConfigPayload) { const explicitId = Number(payload.caseId) if (Number.isInteger(explicitId) && explicitId > 0) return explicitId const caseNo = Number(payload.caseNo) if (Number.isInteger(caseNo) && caseNo > 0) return caseNo const matched = payload.caseId.match(/\d+/) const fallbackId = matched ? Number(matched[0]) : 0 if (Number.isInteger(fallbackId) && fallbackId > 0) return fallbackId throw new Error('病例 ID 无效,无法新建会话') }