Files
medical_training/test/swagger_cms_stats.py
T

149 lines
7.3 KiB
Python

"""Swagger Try-it-out 等效脚本:CMS 各角色概览大屏(第八章 CMS-STATS-*)。
CMS-STATS-SUP-1 平台总览 / HOS-1 医院驾驶舱 / CNT-1 内容概览 / TEA-1 教学概览。
为产出**真实出参示例**,先在示例机构内播种一份连贯数据(机构/4 类管理员/2 学生/师生关系/
2 病例/3 条训练记录),调用后把完整 JSON 落到 logs/swagger-cms-stats-examples.json,最后清理。
运行方式:.venv\\Scripts\\python.exe test/swagger_cms_stats.py
前提:Django dev server 已在 http://127.0.0.1:8000 运行,Redis 已启动。
"""
import sys
import json
import subprocess
from pathlib import Path
import requests
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
BASE = 'http://127.0.0.1:8000'
PYTHON = r'D:\01Agent\medical_training\.venv\Scripts\python.exe'
CWD = r'D:\01Agent\medical_training'
PASS, FAIL = 'PASS', 'FAIL'
results = []
EXAMPLES = {}
OVERVIEW = '/api/cms/stats/overview/'
HOSPITAL = '/api/cms/stats/hospital/overview/'
CONTENT = '/api/cms/stats/content/overview/'
TEACHING = '/api/cms/stats/teaching/overview/'
PHONES = ['13700008001', '13700008002', '13700008003', '13700008004', '13700008005', '13700008006']
def log(api_id, method, url, expected, actual, detail=''):
exp = expected if isinstance(expected, (list, tuple)) else [expected]
status = PASS if actual in exp else FAIL
results.append((api_id, status))
print(f' {status} {api_id:<18} {method:<5} {url:<40} expect={str(expected):<9} got={actual} {detail}')
def django_eval(code):
pre = 'import django, os; os.environ.setdefault("DJANGO_SETTINGS_MODULE","config.settings"); django.setup()\n'
p = subprocess.run([PYTHON, '-c', pre + code], capture_output=True, text=True, cwd=CWD)
if p.returncode != 0:
print('[django_eval ERROR]', p.stderr[-1500:])
return p.stdout.strip()
def jb(r):
return r.json() if r.headers.get('content-type', '').startswith('application/json') else None
SEED = r'''
from apps.user.models import User, Institution, Department, TeacherStudentRelation
from apps.case.models import CaseBase
from rest_framework_simplejwt.tokens import RefreshToken
from django.db import connection
from django.utils import timezone
import json
PH = ["13700008001","13700008002","13700008003","13700008004","13700008005","13700008006"]
User.all_objects.filter(phone__in=PH).delete()
CaseBase.all_objects.filter(title__startswith="概览示例-").hard_delete()
with connection.cursor() as c:
c.execute("DELETE FROM training_record WHERE external_user_id='SWGSTAT'")
inst,_ = Institution.objects.get_or_create(code="SWG_STAT", defaults={"name":"概览示例院","type":"hospital","level":"三甲"})
dept,_ = Department.objects.get_or_create(name="心内科", defaults={"category":"临床"})
mk = lambda ph,nm,rt: User.objects.create_user(username=ph,password=None,phone=ph,real_name=nm,role_type=rt,institution=inst,status=1)
su=mk(PH[0],"示例超管","super_admin"); hu=mk(PH[1],"示例院管","hospital_admin")
cu=mk(PH[2],"示例内容","content_admin"); du=mk(PH[3],"示例带教","doctor")
s1=mk(PH[4],"学生甲","student"); s2=mk(PH[5],"学生乙","student")
s1.department=dept; s1.save(update_fields=["department"]); s2.department=dept; s2.save(update_fields=["department"])
TeacherStudentRelation.objects.create(teacher=du,student=s1,status=1)
TeacherStudentRelation.objects.create(teacher=du,student=s2,status=1)
c1=CaseBase.objects.create(title="概览示例-急性心梗",case_type="traditional",institution=inst,department=dept,publish_status=2,difficulty="medium",created_by=cu)
c2=CaseBase.objects.create(title="概览示例-稳定型心绞痛",case_type="traditional",institution=inst,department=dept,publish_status=1,difficulty="easy",created_by=cu)
DIMS=[{"dimension":"信息获取","score":18,"max_score":20},{"dimension":"分析推理","score":17,"max_score":20},{"dimension":"处置决策","score":8,"max_score":10},{"dimension":"沟通人文","score":7,"max_score":10},{"dimension":"临床整合","score":8,"max_score":10}]
now=timezone.now()
rows=[(s1.id,c1.id,88),(s2.id,c1.id,76),(s1.id,c2.id,92)]
with connection.cursor() as c:
for uid,cid,sc in rows:
c.execute("INSERT INTO training_record (user_id,case_id,training_mode,case_type,status,total_score,duration_seconds,end_time,start_time,score_type,evaluation_level,feedback,thinking_chain,diagnosis_path,wrong_points,missed_questions,recommendation_result,osce_station_score,emotion_analysis,ai_feedback_structured,external_user_id,created_at,updated_at) VALUES (%s,%s,'practice','traditional','completed',%s,1800,%s,%s,'percentage','good','','','','[]','[]','{}','{}','{}',%s,'SWGSTAT',%s,%s)",[uid,cid,sc,now,now,json.dumps({"dimension_scores":DIMS},ensure_ascii=False),now,now])
print("|".join(str(RefreshToken.for_user(x).access_token) for x in [su,hu,cu,du]))
'''
CLEANUP = r'''
from apps.user.models import User
from apps.case.models import CaseBase
from django.db import connection
PH = ["13700008001","13700008002","13700008003","13700008004","13700008005","13700008006"]
with connection.cursor() as c:
c.execute("DELETE FROM training_record WHERE external_user_id='SWGSTAT'")
CaseBase.all_objects.filter(title__startswith="概览示例-").hard_delete()
User.all_objects.filter(phone__in=PH).delete()
print("cleaned")
'''
print('\n[准备] 播种示例机构数据(机构/4 管理员/2 学生/师生关系/2 病例/3 训练记录)...')
out = django_eval(SEED)
su, hu, cu, du = out.split('|')
SU = {'Authorization': f'Bearer {su}'}
HU = {'Authorization': f'Bearer {hu}'}
CU = {'Authorization': f'Bearer {cu}'}
DU = {'Authorization': f'Bearer {du}'}
print('[准备] 完成\n')
print('=' * 100)
print(' CMS 各角色概览大屏 Swagger Try-it-out')
print('=' * 100)
# 权限
log('anon-401', 'GET', OVERVIEW, 401, requests.get(f'{BASE}{OVERVIEW}').status_code)
log('SUP-role403', 'GET', OVERVIEW, 403, requests.get(f'{BASE}{OVERVIEW}', headers=HU).status_code)
log('HOS-role403', 'GET', HOSPITAL, 403, requests.get(f'{BASE}{HOSPITAL}', headers=SU).status_code)
log('CNT-role403', 'GET', CONTENT, 403, requests.get(f'{BASE}{CONTENT}', headers=DU).status_code)
log('TEA-role403', 'GET', TEACHING, 403, requests.get(f'{BASE}{TEACHING}', headers=CU).status_code)
def call(api_id, url, headers):
r = requests.get(f'{BASE}{url}', headers=headers)
b = jb(r)
log(api_id, 'GET', url, 200, r.status_code)
if r.status_code == 200:
EXAMPLES[api_id] = {'request': {'method': 'GET', 'url': url,
'headers': {'Authorization': 'Bearer <access>'}},
'response': b}
return b
call('CMS-STATS-SUP-1', OVERVIEW, SU)
call('CMS-STATS-HOS-1', HOSPITAL, HU)
call('CMS-STATS-CNT-1', CONTENT, CU)
call('CMS-STATS-TEA-1', TEACHING, DU)
# 落盘完整示例
exf = Path(CWD) / 'logs' / 'swagger-cms-stats-examples.json'
exf.parent.mkdir(exist_ok=True)
exf.write_text(json.dumps(EXAMPLES, ensure_ascii=False, indent=2), encoding='utf-8')
print(f'\n[示例] 完整出参已写入 {exf}')
django_eval(CLEANUP)
print('=' * 100)
total = len(results); passed = sum(1 for _, s in results if s == PASS); failed = total - passed
print(f' 总计: {total} | 通过: {passed} | 失败: {failed}')
if failed:
print(' 失败:', [a for a, s in results if s == FAIL]); sys.exit(1)
print(' ALL PASSED — CMS 各角色概览大屏接口验证通过!')
sys.exit(0)