feat: cms overview and mobile case query
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user