Compare commits

..
2 Commits
Author SHA1 Message Date
Your Name dbf474ddd7 更新 2026-09-21 10:26:14 +08:00
Your Name bbe3e870a1 更新 2026-09-10 15:33:21 +08:00
68 changed files with 10748 additions and 289 deletions
+1 -1
View File
@@ -467,7 +467,7 @@ export function prescriptionOrderEditTime(params: { id: number; create_time: str
return request.post({ url: '/tcm.prescriptionOrder/editTime', params }) return request.post({ url: '/tcm.prescriptionOrder/editTime', params })
} }
/** 仅修改业务订单的承运商与快递单号;所有履约状态均可使用 */ /** 仅修改业务订单的承运商与快递单号;处方和支付单均审核通过后,所有履约状态均可使用 */
export function prescriptionOrderDdcode(params: { export function prescriptionOrderDdcode(params: {
id: number id: number
express_company: string express_company: string
@@ -871,7 +871,16 @@
<el-option label="京东快递" value="jd" /> <el-option label="京东快递" value="jd" />
<el-option label="极兔速递" value="jt" /> <el-option label="极兔速递" value="jt" />
</el-select> </el-select>
<el-input v-model="editForm.tracking_number" maxlength="80" placeholder="快递单号" class="flex-1 min-w-0" /> <el-input
v-model="editForm.tracking_number"
disabled
maxlength="80"
placeholder="快递单号"
class="flex-1 min-w-0"
/>
</div>
<div class="text-xs text-gray-400 mt-1">
编辑订单后需重新审核双审通过后请通过列表的单号操作填写或修改
</div> </div>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -1067,7 +1076,16 @@
<el-option label="京东快递" value="jd" /> <el-option label="京东快递" value="jd" />
<el-option label="极兔速递" value="jt" /> <el-option label="极兔速递" value="jt" />
</el-select> </el-select>
<el-input v-model="editForm.tracking_number" maxlength="80" placeholder="快递单号" class="flex-1 min-w-0" /> <el-input
v-model="editForm.tracking_number"
disabled
maxlength="80"
placeholder="快递单号"
class="flex-1 min-w-0"
/>
</div>
<div class="text-xs text-gray-400 mt-1">
编辑订单后需重新审核双审通过后请通过列表的单号操作填写或修改
</div> </div>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -2975,9 +2993,13 @@ function canWithdrawRow(row: { fulfillment_status?: number }) {
return Number(row.fulfillment_status) === 1 return Number(row.fulfillment_status) === 1
} }
function canShipRow(row: { fulfillment_status?: number }) { function canShipRow(row: {
// 履约中(2) 可发货 fulfillment_status?: number
return Number(row.fulfillment_status) === 2 prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
// 履约中(2) 且处方、支付单均审核通过才可发货
return Number(row.fulfillment_status) === 2 && isDualAuditPassed(row)
} }
type ShipMode = 'gancao' | 'direct' type ShipMode = 'gancao' | 'direct'
@@ -3073,8 +3095,11 @@ function canRefundRow(row: { fulfillment_status?: number; payment_slip_audit_sta
return (fs === 5 || fs === 6 || fs === 3 || fs === 9) && Number(row.payment_slip_audit_status) === 1 return (fs === 5 || fs === 6 || fs === 3 || fs === 9) && Number(row.payment_slip_audit_status) === 1
} }
function canQuickTrackRow(_row: { fulfillment_status?: number }) { function canQuickTrackRow(row: {
return true prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
return isDualAuditPassed(row)
} }
function canUploadPharmacyRow(row: { function canUploadPharmacyRow(row: {
@@ -3627,7 +3652,6 @@ async function submitEdit() {
service_channel: editForm.service_channel || '', service_channel: editForm.service_channel || '',
service_package: Array.isArray(editForm.service_package) ? editForm.service_package.join(',') : '', service_package: Array.isArray(editForm.service_package) ? editForm.service_package.join(',') : '',
express_company: editForm.express_company || 'auto', express_company: editForm.express_company || 'auto',
tracking_number: editForm.tracking_number || '',
fee_type: editForm.fee_type, fee_type: editForm.fee_type,
amount: editForm.amount, amount: editForm.amount,
remark_extra: editForm.remark_extra || '', remark_extra: editForm.remark_extra || '',
@@ -3915,7 +3939,17 @@ const quickTrackForm = reactive({
tracking_number: '' tracking_number: ''
}) })
function openQuickTrack(row: { id: number; express_company?: unknown; tracking_number?: unknown }) { function openQuickTrack(row: {
id: number
express_company?: unknown
tracking_number?: unknown
prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
if (!canQuickTrackRow(row)) {
feedback.msgWarning('处方审核和支付单审核均通过后,才可填写或修改快递单号')
return
}
quickTrackRowId.value = row.id quickTrackRowId.value = row.id
quickTrackForm.express_company = String(row.express_company || 'auto') || 'auto' quickTrackForm.express_company = String(row.express_company || 'auto') || 'auto'
quickTrackForm.tracking_number = String(row.tracking_number || '') quickTrackForm.tracking_number = String(row.tracking_number || '')
@@ -3963,7 +3997,19 @@ function resolveShipModeForRow(row: { id: number; ship_mode?: unknown }) {
return normalizeShipMode(row.ship_mode) return normalizeShipMode(row.ship_mode)
} }
function openShip(row: { id: number; express_company?: unknown; tracking_number?: unknown; ship_mode?: unknown }) { function openShip(row: {
id: number
express_company?: unknown
tracking_number?: unknown
ship_mode?: unknown
fulfillment_status?: number
prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
if (!canShipRow(row)) {
feedback.msgWarning('仅处方审核和支付单审核均通过的履约中订单可填写单号并发货')
return
}
shipRowId.value = Number(row.id) shipRowId.value = Number(row.id)
shipForm.ship_mode = resolveShipModeForRow(row) shipForm.ship_mode = resolveShipModeForRow(row)
shipForm.express_company = String(row.express_company || 'auto') || 'auto' shipForm.express_company = String(row.express_company || 'auto') || 'auto'
@@ -1624,7 +1624,16 @@
<el-option label="京东快递" value="jd" /> <el-option label="京东快递" value="jd" />
<el-option label="极兔速递" value="jt" /> <el-option label="极兔速递" value="jt" />
</el-select> </el-select>
<el-input v-model="editForm.tracking_number" maxlength="80" placeholder="快递单号" class="flex-1 min-w-0" /> <el-input
v-model="editForm.tracking_number"
disabled
maxlength="80"
placeholder="快递单号"
class="flex-1 min-w-0"
/>
</div>
<div class="text-xs text-gray-400 mt-1">
编辑订单后需重新审核双审通过后请通过列表的单号操作填写或修改
</div> </div>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -3422,9 +3431,13 @@ function canWithdrawRow(row: { fulfillment_status?: number }) {
return Number(row.fulfillment_status) === 1 return Number(row.fulfillment_status) === 1
} }
function canShipRow(row: { fulfillment_status?: number }) { function canShipRow(row: {
// (2) fulfillment_status?: number
return Number(row.fulfillment_status) === 2 prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
// (2)
return Number(row.fulfillment_status) === 2 && isDualAuditPassed(row)
} }
function canAddPayOrderRow(row: { fulfillment_status?: number }) { function canAddPayOrderRow(row: { fulfillment_status?: number }) {
@@ -3444,8 +3457,11 @@ function canRefundRow(row: { fulfillment_status?: number; payment_slip_audit_sta
return (fs === 5 || fs === 6 || fs === 3 || fs === 9) && Number(row.payment_slip_audit_status) === 1 return (fs === 5 || fs === 6 || fs === 3 || fs === 9) && Number(row.payment_slip_audit_status) === 1
} }
function canQuickTrackRow(_row: { fulfillment_status?: number }) { function canQuickTrackRow(row: {
return true prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
return isDualAuditPassed(row)
} }
function canUploadPharmacyRow(row: { function canUploadPharmacyRow(row: {
@@ -4540,7 +4556,6 @@ async function submitEdit() {
service_channel: editForm.service_channel || '', service_channel: editForm.service_channel || '',
service_package: Array.isArray(editForm.service_package) ? editForm.service_package.join(',') : '', service_package: Array.isArray(editForm.service_package) ? editForm.service_package.join(',') : '',
express_company: editForm.express_company || 'auto', express_company: editForm.express_company || 'auto',
tracking_number: editForm.tracking_number || '',
fee_type: editForm.fee_type, fee_type: editForm.fee_type,
amount: editForm.amount, amount: editForm.amount,
remark_extra: editForm.remark_extra || '', remark_extra: editForm.remark_extra || '',
@@ -4788,7 +4803,17 @@ const quickTrackForm = reactive({
tracking_number: '' tracking_number: ''
}) })
function openQuickTrack(row: { id: number; express_company?: unknown; tracking_number?: unknown }) { function openQuickTrack(row: {
id: number
express_company?: unknown
tracking_number?: unknown
prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
if (!canQuickTrackRow(row)) {
feedback.msgWarning('处方审核和支付单审核均通过后,才可填写或修改快递单号')
return
}
quickTrackRowId.value = row.id quickTrackRowId.value = row.id
quickTrackForm.express_company = String(row.express_company || 'auto') || 'auto' quickTrackForm.express_company = String(row.express_company || 'auto') || 'auto'
quickTrackForm.tracking_number = String(row.tracking_number || '') quickTrackForm.tracking_number = String(row.tracking_number || '')
@@ -4840,7 +4865,19 @@ const shipDialogModeDisplay = computed(() =>
shipForm.ship_mode === 'direct' ? '洛阳药房直发' : '甘草药房直发' shipForm.ship_mode === 'direct' ? '洛阳药房直发' : '甘草药房直发'
) )
function openShip(row: { id: number; express_company?: unknown; tracking_number?: unknown; ship_mode?: unknown }) { function openShip(row: {
id: number
express_company?: unknown
tracking_number?: unknown
ship_mode?: unknown
fulfillment_status?: number
prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
if (!canShipRow(row)) {
feedback.msgWarning('仅处方审核和支付单审核均通过的履约中订单可填写单号并发货')
return
}
shipRowId.value = row.id shipRowId.value = row.id
shipForm.ship_mode = String(row.ship_mode || 'gancao') || 'gancao' shipForm.ship_mode = String(row.ship_mode || 'gancao') || 'gancao'
shipForm.express_company = String(row.express_company || 'auto') || 'auto' shipForm.express_company = String(row.express_company || 'auto') || 'auto'
@@ -0,0 +1,156 @@
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const test = require('node:test')
const ts = require('typescript')
const vue = require('vue')
const { parse, compileScript, compileTemplate } = require('@vue/compiler-sfc')
const handlers = [
'isDualAuditPassed', 'canQuickTrackRow', 'canShipRow', 'openQuickTrack',
'submitQuickTrack', 'openShip', 'openEdit', 'submitEdit', 'resetEditOrderDialog'
]
const stateNames = [
'quickTrackVisible', 'quickTrackSaving', 'quickTrackRowId', 'quickTrackForm',
'shipVisible', 'shipRowId', 'shipForm', 'editVisible', 'editDialogLoading',
'editSaving', 'editOrderStep', 'editOrderPrescription',
'editFormRef', 'editForm', 'editDepositMin'
]
for (const page of ['order_list.vue', 'order_list_h5.vue']) {
const filename = path.join(__dirname, '../src/views/consumer/prescription', page)
const { descriptor, errors } = parse(fs.readFileSync(filename, 'utf8'), { filename })
assert.deepEqual(errors, [])
const script = compileScript(descriptor, { id: page })
const ast = ts.createSourceFile(filename + '.ts', descriptor.scriptSetup.content, ts.ScriptTarget.Latest, true)
// Execute the actual order handlers with only network and unrelated UI dependencies stubbed.
const declarations = new Map()
for (const node of ast.statements) {
if (ts.isFunctionDeclaration(node) && node.name) declarations.set(node.name.text, node.getText(ast))
if (ts.isVariableStatement(node)) {
for (const declaration of node.declarationList.declarations) {
declarations.set(declaration.name.getText(ast), `const ${declaration.getText(ast)}`)
}
}
}
const selected = [...handlers, ...stateNames]
for (const name of selected) assert.ok(declarations.has(name), `${page}: missing ${name}`)
const compiled = ts.transpileModule(selected.map(name => declarations.get(name)).join('\n'), {
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS }
}).outputText
function instance(detail = {}, saveTracking = async () => {}) {
const warnings = []
const errors = []
const edits = []
const tracks = []
const globals = {
ref: vue.ref, reactive: vue.reactive, nextTick: vue.nextTick,
feedback: { msgWarning: message => warnings.push(message), msgError: message => errors.push(message), msgSuccess() {} },
prescriptionOrderDetail: async () => ({ data: detail }),
prescriptionOrderEdit: async payload => edits.push(payload),
prescriptionOrderDdcode: async payload => { tracks.push(payload); await saveTracking(payload) },
canEditRow: () => true,
parseServicePackageValues: () => [],
loadEditPaidOrders: async () => {},
resolveShipModeForRow: row => row.ship_mode || 'gancao',
editGancaoLogisticsOnlyMode: vue.ref(false),
editGancaoDisplayNo: vue.ref(''),
detailDrawerRef: vue.ref(null),
detailVisible: vue.ref(false),
getLists() {}
}
const state = new Function(...Object.keys(globals), `${compiled}\nreturn { ${selected.join(', ')} }`)(...Object.values(globals))
state.editFormRef.value = { validate: async () => {}, clearValidate() {} }
return { state, warnings, errors, edits, tracks }
}
test(`${page}: only two approved audits unlock tracking, including legacy string statuses`, () => {
const { state } = instance()
for (const rx of [undefined, null, 0, 1, 2, '0', '1', '2']) {
for (const pay of [undefined, null, 0, 1, 2, '0', '1', '2']) {
const row = { prescription_audit_status: rx, payment_slip_audit_status: pay, fulfillment_status: 2 }
const allowed = [1, '1'].includes(rx) && [1, '1'].includes(pay)
assert.equal(state.canQuickTrackRow(row), allowed, `tracking: ${rx}/${pay}`)
assert.equal(state.canShipRow(row), allowed, `shipping: ${rx}/${pay}`)
}
}
for (const status of [1, 3, 4, 5, 6, 9]) {
assert.equal(state.canShipRow({ prescription_audit_status: 1, payment_slip_audit_status: 1, fulfillment_status: status }), false)
}
})
test(`${page}: direct handler calls cannot open tracking or shipping before both approvals`, () => {
for (const [rx, pay] of [[0, 0], [1, 0], [0, 1], [2, 1], [1, 2]]) {
const { state, warnings } = instance()
const row = { id: 42, prescription_audit_status: rx, payment_slip_audit_status: pay, fulfillment_status: 2 }
state.openQuickTrack(row)
state.openShip(row)
assert.equal(state.quickTrackVisible.value, false)
assert.equal(state.shipVisible.value, false)
assert.equal(state.quickTrackRowId.value, 0)
assert.equal(state.shipRowId.value, 0)
assert.equal(warnings.length, 2)
}
})
test(`${page}: approved orders can fill or replace the number and preserve the carrier`, async () => {
for (const oldNumber of ['', 'SF-OLD']) {
const { state, tracks } = instance()
const row = { id: 42, prescription_audit_status: 1, payment_slip_audit_status: 1, fulfillment_status: 2, express_company: 'sf', tracking_number: oldNumber }
state.openQuickTrack(row)
assert.equal(state.quickTrackVisible.value, true)
assert.equal(state.quickTrackForm.tracking_number, oldNumber)
state.quickTrackForm.tracking_number = ' SF-NEW '
await state.submitQuickTrack()
assert.deepEqual(tracks, [{ id: 42, express_company: 'sf', tracking_number: 'SF-NEW' }])
assert.equal(state.quickTrackVisible.value, false)
state.openShip(row)
assert.equal(state.shipVisible.value, true)
}
})
test(`${page}: a server rejection after audit revocation retains the dialog and entered number`, async () => {
const { state } = instance({}, async () => { throw new Error('audit revoked') })
state.openQuickTrack({ id: 42, prescription_audit_status: 1, payment_slip_audit_status: 1 })
state.quickTrackForm.tracking_number = 'SF-NEW'
await state.submitQuickTrack()
assert.equal(state.quickTrackVisible.value, true)
assert.equal(state.quickTrackSaving.value, false)
assert.equal(state.quickTrackForm.tracking_number, 'SF-NEW')
})
test(`${page}: editing preserves the existing number while saving other fields before approval`, async () => {
const { state, edits, errors } = instance({ id: 42, prescription_audit_status: 1, payment_slip_audit_status: 0, tracking_number: 'SF-EXISTING' })
await state.openEdit({ id: 42, prescription_audit_status: 1, payment_slip_audit_status: 1 })
assert.deepEqual(errors, [])
assert.equal(state.editForm.tracking_number, 'SF-EXISTING')
state.editForm.tracking_number = 'FORGED'
state.editForm.recipient_name = '修改后的收货人'
state.editOrderStep.value = 2
await state.submitEdit()
assert.equal(edits.length, 1)
assert.equal(edits[0].recipient_name, '修改后的收货人')
assert.equal(Object.hasOwn(edits[0], 'tracking_number'), false)
})
test(`${page}: ordinary editing cannot change tracking while resetting an approved payment audit`, async () => {
const { state, edits, errors } = instance({ id: 42, prescription_audit_status: '1', payment_slip_audit_status: '1' })
await state.openEdit({ id: 42 })
assert.deepEqual(errors, [])
state.editForm.tracking_number = 'SF-NEW'
state.editOrderStep.value = 2
await state.submitEdit()
assert.equal(edits.length, 1)
assert.equal(Object.hasOwn(edits[0], 'tracking_number'), false)
})
test(`${page}: template compiles with tracking controls bound to audit restrictions`, () => {
const template = compileTemplate({ source: descriptor.template.content, filename, id: page, compilerOptions: { bindingMetadata: script.bindings } })
assert.deepEqual(template.errors, [])
const inputs = [...descriptor.template.content.matchAll(/<el-input\b[^>]*v-model="editForm\.tracking_number"[^>]*>/g)]
assert.equal(inputs.length, page === 'order_list.vue' ? 2 : 1)
for (const [input] of inputs) assert.match(input, /\sdisabled(?:\s|\/?>)/)
assert.match(descriptor.template.content, /v-if="canQuickTrackRow\(row\)"/)
})
}
@@ -0,0 +1,26 @@
# 已开处方 → AI 界面改造
## 功能分析
这是基于已保存资料快照的双模型分析与医生复核工作台。主流程是确认患者和批次、查看模型完成情况、对照原方差异、核查资料缺口、记录所选模型的复核意见。
保留六个入口:对比总览、完整报告、候选与逐味、资料与缺口、处理进度、历史与趋势。历史批次切换、刷新、重新分析、单模型重试、独立复核草稿及保存继续使用现有服务和权限逻辑。医生原方及支持报告仍可访问。
## 设计决定
- 主程序 `shell.py` 将 prescriptions 等业务页列为科技蓝页面。因此继续复用 `reception_style.TECH_BLUE`:主色 #1769E8,背景 #F3F7FD,白色面板,分割线 #DBE5F2。没有采用主程序其他页面的靛蓝令牌,也没有修改全局主题。
- 深蓝渐变横幅改为白色标题工具栏。批次状态放在顶行,患者及原方信息单独成行,当前导航用浅蓝背景与蓝色底线标识。
- 缩小一致度圆环和数字,保留共同药味、候选药味与药味重合的计算依据;修复指标区样式误把分隔线应用到每个子标签的问题。
- 总览由三栏改成剂量差异主区与固定复核侧栏。三方交集和附件覆盖改为可展开的摘要,减少初始屏幕空白和对主图的挤压。
- 复核模型和复核状态并排显示,意见输入和保存按钮放在一起。顶部保存动作显示当前模型名,切换时同步更新。
- 短窗口收起次要信息,保留剂量主图和复核输入;展开图表时剂量滚动区域可让出高度。列表内容保持最小高度,避免刷新后条目重叠。
- 保留未知值与零值区分、不可比/历史状态处理、模型各自失败重试以及现有一致度说明。
## 验证
- 处方 AI 五组回归测试覆盖数据处理、权限、异步读取、逐味比较、页面、模型复核与布局。
- 新增八种窗口/展开状态组合的控件边界检查,以及展开/收起不改变比较数据和复核清单的检查。
- 离线模拟数据预览覆盖 1440×940、1280×860、1024×700、940×640,各内容页面、展开图表、运行中、失败、历史及统计窗口。
- 预览输出:`app/artifacts/issued_prescription_redesign/`。模拟数据仅供 UI 验证,不对应真实患者。
本次改动在现有未提交工作区基础上完成,仅调整桌面呈现与相关回归检查;没有重新打包或发布桌面安装程序。
@@ -0,0 +1,281 @@
"""Render the prescription comparison window with synthetic, offline data."""
from __future__ import annotations
import os
from copy import deepcopy
from pathlib import Path
from typing import Any
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PySide6.QtWidgets import QApplication
from doctor_workstation.ui.dialogs import issued_prescription_ai as ai
from doctor_workstation.ui.theme import apply_theme
def example_batch() -> dict[str, Any]:
"""Three prescriptions that actually differ, so every state of the page has something to draw."""
doctor = {"生地黄": 16, "天花粉": 15, "干石斛": 20, "醋五味子": 6,
"生麦冬": 12, "茯苓": 10, "红参片": 6, "生牡丹皮": 10}
candidates = {
"qwen": {"生地黄": 15, "干石斛": 12, "醋五味子": 6, "生麦冬": 12, "茯苓": 15, "麸炒白术": 12, "丹参": 15},
"openai": {"生地黄": 12, "醋五味子": 6, "生麦冬": 10, "茯苓": 12, "生白芍": 5, "炒酸枣仁": 6},
}
reports = {
"qwen": {"summary": "界面演示数据:对照药味组成、剂量及用法,辅助医生逐项复核。",
"diagnosis": "消渴病,气阴两虚兼血热。兼证需结合舌脉资料核实。",
"analysis": "阅读药方对比页,查看同名药材的剂量差异、仅医生方药味与仅候选方药味。",
"risk_assessment": [{"level": "high", "label": "血压数据缺失,影响补气药安全性评估"},
{"level": "medium", "label": "肝肾功能具体指标未提供"}],
"missing_information": ["甲状腺功能及眼底检查报告缺失", "舌象与脉诊仅有附件,无文本记录"]},
"openai": {"summary": "界面演示数据:两份候选方独立生成,引用编号可回查原始资料。",
"diagnosis": "已记录气阴两虚证;辨证依据需补充。",
"analysis": "候选方以益气养阴为主,安神药味为本模型新增,需要医师确认。",
"risk_assessment": [{"level": "high", "label": "缺少当前用药记录,无法排除配伍风险"}],
"missing_information": ["舌象与脉诊仅有附件,无文本记录", "近期体重变化及 BMI 数据缺失"]},
}
models = {}
for key, doses in candidates.items():
herbs, rows = [], []
for name in sorted(set(doctor) | set(doses), key=lambda item: (item not in doses, item)):
common = {"name": name, "unit": "g", "dose_basis": "per_dose", "formula_type": "主方"}
left, right = doctor.get(name), doses.get(name)
if right is not None:
herbs.append({**common, "dosage": right})
rows.append({
**common, "key": name,
"doctor": {**common, "dosage": left} if left is not None else None,
"candidate": {**common, "dosage": right, "source_rows": [len(herbs) - 1]} if right is not None else None,
"doctor_dosage": left, "candidate_dosage": right,
"match_type": "candidate_only" if left is None else "doctor_only" if right is None else "matched",
"contribution": min(left, right) / max(left, right) if left and right else None,
})
matched = sum(row["match_type"] == "matched" for row in rows)
denominator = len(doctor) + len(herbs)
models[key] = {
"status": "success", "report_id": 10 if key == "qwen" else 11,
"candidate": {"status": "available_for_review", "prescription_name": "候选药方 · 界面示例",
"herbs": herbs, "dose_basis": "per_dose", "prescription_type": "浓缩水丸",
"usage_instruction": "服法由医生复核后确认。", "usage_days": 7, "times_per_day": 2,
"rationale": "这是用于检查界面排版的模拟药方,不对应真实患者。",
"risk_warnings": ["药味与剂量差异需要逐项复核。"]},
"comparison": {"status": "comparable",
"score": 200 * sum(row["contribution"] or 0 for row in rows) / denominator,
"herb_score": 200 * matched / denominator, "matched_count": matched,
"doctor_count": len(doctor), "candidate_count": len(herbs), "rows": rows,
"reason": "药味与剂量可比;服法与疗程需单独复核。", "usage_differences": []},
"report": reports[key],
"coverage": {"status": "incomplete", "complete": False,
"files": [{"file_id": f"a{index:04d}", "type": "image" if index % 3 else "document",
"status": "processed", "transmitted": True, "version_verified": True}
for index in range(20 if key == "qwen" else 17)]
+ [{"file_id": f"z{index:04d}", "type": "document", "status": "restricted",
"transmitted": False, "version_verified": False,
"reason": "FILE_UNAVAILABLE_OR_UNSUPPORTED"} for index in range(2)]},
"progress": {"stage_label": "处理完成", "elapsed_seconds": 135 if key == "qwen" else 591,
"attempt": 1},
"usage": {"total_calls": 3, "calls": [
{"stage": "text:0", "ok": True, "latency_ms": 3120, "file_count": 0,
"usage": {"completion_tokens": 980}, "error_code": ""},
{"stage": "files:0", "ok": True, "latency_ms": 22400,
"file_count": 20 if key == "qwen" else 17,
"usage": {"completion_tokens": 2140}, "error_code": ""},
{"stage": "final", "ok": True, "latency_ms": 18800, "file_count": 0,
"usage": {"completion_tokens": 5617}, "error_code": ""}]},
"review": {"status": "viewed", "comment": ""},
"algorithm_version": "prescription-soft-dice-v1.1.0",
"prompt_version": "manual-prescription-required-candidate-v4",
}
return {"id": 40, "prescription_id": 7556, "patient_id": 1391, "diagnosis_id": 1391,
"prescription_revision": 1, "status": "success", "validity": "current",
"comparison_type": "non_independent", "models": models, "coverage_status": "partial",
"created_at": "2026-09-10 15:29:00", "cutoff_at": "2026-09-10 15:29:00",
"source_summary": {"diagnoses_count": 2, "attachment_count": 22, "video_calls_count": 4,
"chat_messages_count": 137, "source_record_count": 31},
"doctor_snapshot": {"patient": {"name": "张卫君", "gender": 2, "gender_label": "", "age": 58},
"diagnosis": {"clinical_diagnosis": "2型糖尿病 消渴病 · 气阴两虚兼血热",
"chief_complaint": "咳嗽反复1月余"},
"prescription": {"herbs": [{"name": name, "dosage": dose, "unit": "g",
"dose_basis": "per_dose", "formula_type": "主方"}
for name, dose in doctor.items()],
"prescription_type": "浓缩水丸", "dose_count": 1,
"usage_instruction": "每日1剂,水煎分服。"}},
"missing": [{"code": "TRANSCRIPT_NOT_VERIFIED_COMPLETE", "critical": True},
{"code": "TRANSCRIPT_NOT_VERIFIED_COMPLETE", "critical": True},
{"code": "ARCHIVE_SYNC_WATERMARK_UNAVAILABLE", "critical": False}]}
def example_statistics() -> dict[str, Any]:
"""Doctor-level shape the statistics window renders; synthetic, but structurally complete."""
def doctor(identifier: int, name: str, totals: tuple[int, int, int],
qwen: tuple[int, float | None], openai: tuple[int, float | None],
review: tuple[int, int]) -> dict[str, Any]:
total, patients, paired = totals
models = {}
for key, (eligible, mean) in (("qwen", qwen), ("openai", openai)):
share = (0.34, 0.38, 0.18, 0.07, 0.03)
models[key] = {"eligible_count": eligible, "mean": mean,
"median": None if mean is None else round(mean - 1.4, 1),
"excluded_reasons": {"SOURCE_HISTORY_VERSIONS_UNAVAILABLE": max(0, total - eligible - 2),
"incomplete_coverage": min(2, max(0, total - eligible))},
"distribution": {name: round(eligible * fraction)
for name, fraction in zip(
("[0,20)", "[20,40)", "[40,60)", "[60,80)", "[80,100]"),
share, strict=True)}}
evaluated, qualified = review
return {"doctor_id": identifier, "doctor_name": name, "total_count": total,
"patient_count": patients, "paired_count": paired, "models": models,
"review": {"evaluated_count": evaluated, "qualified_count": qualified,
"qualified_rate": None if not evaluated else round(100 * qualified / evaluated, 1)}}
doctors = [doctor(26, "何医生", (18, 14, 9), (12, 21.4), (9, 18.9), (6, 4)),
doctor(31, "李医生", (11, 9, 4), (7, 26.8), (5, 24.1), (3, 1)),
doctor(44, "王医生", (6, 5, 1), (2, 15.2), (0, None), (0, 0))]
return {"total_count": sum(item["total_count"] for item in doctors),
"patient_count": sum(item["patient_count"] for item in doctors), "doctors": doctors}
class PreviewRepository:
def __init__(self) -> None:
self.batch = example_batch()
def list_prescription_ai_reports(self, **_params: Any) -> dict[str, Any]:
return {"enabled": True, "lists": deepcopy(self.history()), "count": len(self.history())}
def history(self) -> list[dict[str, Any]]:
"""The current batch plus the earlier ones it supersedes, newest first."""
older = [
{"id": 39, "created_at": "2026-09-10 15:18:00", "status": "success", "validity": "superseded",
"comparison_type": "non_independent",
"models": {"qwen": {"comparison": {"status": "comparable", "score": 48.9},
"algorithm_version": "prescription-soft-dice-v1.0.1", "prompt_version": "v3"},
"openai": {"comparison": {"status": "comparable", "score": 41.7},
"algorithm_version": "prescription-soft-dice-v1.0.1", "prompt_version": "v3"}}},
{"id": 38, "created_at": "2026-09-10 14:05:00", "status": "success", "validity": "superseded",
"models": {"qwen": {"comparison": {"status": "comparable", "score": 33.4},
"algorithm_version": "prescription-soft-dice-v1.0.1", "prompt_version": "v3"},
"openai": {"comparison": {"status": "comparable", "score": 31.1},
"algorithm_version": "prescription-soft-dice-v1.0.1", "prompt_version": "v3"}}},
{"id": 37, "created_at": "2026-09-10 13:25:00", "status": "failed", "validity": "superseded",
"models": {"qwen": {"error_message": "模型返回未通过校验",
"algorithm_version": "prescription-soft-dice-v1.0.1"},
"openai": {"comparison": {"status": "not_comparable"}}}},
{"id": 36, "created_at": "2026-09-10 12:34:00", "status": "success", "validity": "superseded",
"models": {"qwen": {"comparison": {"status": "not_comparable"},
"algorithm_version": "prescription-soft-dice-v1.0.0"},
"openai": {"comparison": {"status": "not_comparable"}}}},
]
return [self.batch, *older]
def get_prescription_ai_report(self, _batch_id: int) -> dict[str, Any]:
return deepcopy(self.batch)
def prescription_ai_statistics(self, *_args: Any, **_params: Any) -> dict[str, Any]:
return deepcopy(example_statistics())
def main() -> None:
application = QApplication.instance() or QApplication([])
apply_theme(application)
output = Path(__file__).resolve().parents[1] / "artifacts" / "issued_prescription_redesign"
output.mkdir(parents=True, exist_ok=True)
def immediate(function: Any, **callbacks: Any) -> None:
result = function()
if callbacks.get("on_success"):
callbacks["on_success"](result)
if callbacks.get("on_finished"):
callbacks["on_finished"]()
ai.run_async = immediate
repository = PreviewRepository()
dialog = ai.IssuedPrescriptionAiDialog(repository, ["*"], prescription_id=7556,
current_user={"name": "张医生"})
dialog.show()
names = {dialog.tabs.tabText(index): index for index in range(dialog.tabs.count())}
for filename, width, height, tab in (
("prescription-1440.png", 1440, 940, "对比总览"),
("prescription-1280.png", 1280, 860, "对比总览"),
("prescription-1024.png", 1024, 700, "对比总览"),
("prescription-940.png", 940, 640, "对比总览"),
("original-1280.png", 1280, 860, "原方记录"),
("analysis-1280.png", 1280, 860, "完整报告"),
("candidate-1280.png", 1280, 860, "方义与用法"),
("per-herb-1440.png", 1440, 940, "候选与逐味"),
("sources-1440.png", 1440, 940, "资料与缺口"),
("history-1440.png", 1440, 940, "历史与趋势"),
("pipeline-1440.png", 1440, 940, "处理进度"),
):
dialog.resize(width, height)
dialog.tabs.setCurrentIndex(names[tab])
for _ in range(4):
application.processEvents()
assert (dialog.width(), dialog.height()) == (width, height), (filename, dialog.size())
assert dialog.grab().save(str(output / filename))
print(filename, "window", width, height, "workspace", dialog.tabs.width(), dialog.tabs.height())
dialog.tabs.setCurrentIndex(names["完整报告"])
dialog._set_report_mode("differences")
application.processEvents()
assert dialog.grab().save(str(output / "report-differences-1440.png"))
dialog._set_report_mode("both")
dialog.tabs.setCurrentIndex(names["对比总览"])
dialog.resize(1280, 860)
for _ in range(4):
application.processEvents()
dialog.review_model.setCurrentIndex(1)
application.processEvents()
assert dialog.grab().save(str(output / "openai-1280.png"))
dialog.review_model.setCurrentIndex(0)
repository.batch["status"] = "running"
repository.batch["models"]["openai"].update(status="running", candidate=None, comparison=None, report=None)
dialog.refresh()
application.processEvents()
assert dialog.grab().save(str(output / "partial-1280.png"))
repository.batch.update(validity="superseded", status="success")
dialog.refresh()
application.processEvents()
assert dialog.grab().save(str(output / "historical-1280.png"))
repository.batch.update(validity="current", status="running")
repository.batch["models"]["qwen"].update(status="running", candidate=None, comparison=None, report=None)
dialog.refresh()
application.processEvents()
assert dialog.grab().save(str(output / "pending-1280.png"))
# A failed model must state the reason and offer its own retry on the card itself.
repository.batch.update(validity="current", status="partial")
repository.batch["models"]["qwen"].update(
status="failed", error_code="upstream_timeout", error_message="上游模型超时,未返回结果",
candidate=None, comparison=None, report=None, retry_count=1, max_retry=3)
repository.batch["models"]["openai"] = deepcopy(example_batch()["models"]["openai"])
dialog.refresh()
application.processEvents()
assert dialog.grab().save(str(output / "failed-1280.png"))
# The light theme is the same window with the other palette; capture it once.
dialog.resize(1440, 940)
dialog._switch_theme()
for _ in range(12):
application.processEvents()
dialog.repaint()
assert dialog.grab().save(str(output / "light-1440.png"))
dialog._switch_theme()
for _ in range(4):
application.processEvents()
dialog.close()
statistics = ai.PrescriptionAiStatisticsDialog(repository, ["*"])
statistics.resize(1240, 880)
statistics.show()
for _ in range(4):
application.processEvents()
assert statistics.grab().save(str(output / "statistics-1240.png"))
statistics.close()
print("statistics-1240.png", statistics.panel.doctors.rowCount(), "doctors")
print(output)
if __name__ == "__main__":
main()
+2 -2
View File
@@ -3,9 +3,9 @@
__all__ = ["DEBUG_MODE", "ONLINE_API_BASE_URL", "__version__"] __all__ = ["DEBUG_MODE", "ONLINE_API_BASE_URL", "__version__"]
# Single source of truth for runtime, package, installer, and executable versions. # Single source of truth for runtime, package, installer, and executable versions.
__version__ = "1.4.2" __version__ = "1.4.5"
# 调试模式开启时,登录页显示“演示模式”和“服务器设置”。 # 调试模式开启时,登录页显示“演示模式”和“服务器设置”。
# 正式发布请保持 False;此时程序只使用下面配置的线上域名。 # 正式发布请保持 False;此时程序只使用下面配置的线上域名。
DEBUG_MODE = True DEBUG_MODE = False
ONLINE_API_BASE_URL = "https://admin.zhenyangtang.com.cn" ONLINE_API_BASE_URL = "https://admin.zhenyangtang.com.cn"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,375 @@
"""Painted charts for the prescription analysis window, in the workstation's tech blue.
Every widget draws only what the saved report contains: a value that is missing stays visibly
absent instead of being drawn as zero, and no chart implies a medical judgement by colour.
"""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from PySide6.QtCore import QPointF, QRectF, QSize, Qt
from PySide6.QtGui import QColor, QFont, QPainter, QPaintEvent
from PySide6.QtWidgets import QSizePolicy, QWidget
from .issued_prescription_ai_theme import CONSOLE as TECH_BLUE
# Colour names resolve through the active palette at paint time, so a theme switch needs no
# rebuild here: the next repaint already draws in the new colours.
class _Palette:
"""Attribute access into the live palette: ``COLOUR.qwen`` is always the current hue."""
__slots__ = ("_keys",)
def __init__(self, **keys: str) -> None:
object.__setattr__(self, "_keys", dict(keys))
def __getattr__(self, name: str) -> str:
return TECH_BLUE[self._keys[name]]
COLOUR = _Palette(doctor="muted", qwen="qwen", openai="openai", track="raised",
limited="amber", ink="heading", muted="muted")
def _mapping(value: Any) -> dict[str, Any]:
return dict(value) if isinstance(value, Mapping) else {}
def _count(value: Any) -> int:
if value is None or isinstance(value, bool):
return 0
try:
number = int(float(value))
except (TypeError, ValueError):
return 0
return max(0, number)
class DonutGauge(QWidget):
"""A ring reading one percentage. An unavailable value leaves the track empty, never zero."""
def __init__(self, accent: str, parent: QWidget | None = None, *, diameter: int = 88,
thickness: int = 11, track: str = COLOUR.track) -> None:
super().__init__(parent)
self._accent, self._track = accent, track
self._thickness = thickness
self._value: float | None = None
self.setFixedSize(diameter, diameter)
self.setAccessibleName("一致度环形图")
self.set_value(None)
def set_value(self, value: Any) -> None:
parsed = None
try:
parsed = None if value is None or isinstance(value, bool) else float(value)
except (TypeError, ValueError):
parsed = None
self._value = None if parsed is None or parsed < 0 or parsed > 100 else parsed
self.setAccessibleDescription("暂无可比结果" if self._value is None else f"{self._value:.1f}%")
self.update()
def value(self) -> float | None:
return self._value
def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802 - Qt virtual
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
inset = self._thickness / 2 + 1
box = QRectF(inset, inset, self.width() - 2 * inset, self.height() - 2 * inset)
pen = painter.pen()
pen.setWidthF(self._thickness)
pen.setCapStyle(Qt.PenCapStyle.FlatCap)
pen.setColor(QColor(self._track))
painter.setPen(pen)
painter.setBrush(Qt.BrushStyle.NoBrush)
painter.drawEllipse(box)
if self._value:
pen.setColor(QColor(self._accent))
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
painter.setPen(pen)
# Qt angles are sixteenths of a degree; start at twelve o'clock and run clockwise.
painter.drawArc(box, 90 * 16, -int(360 * 16 * self._value / 100))
painter.end()
class VennChart(QWidget):
"""Doctor / model-A / model-B herb sets with their real intersection counts."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self._sets: dict[str, int] = {}
self._labels = ("医生原方", "千问", "OpenAI")
self.setMinimumHeight(96)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
def set_counts(self, counts: Mapping[str, int], labels: tuple[str, str, str] | None = None) -> None:
"""Regions: doctor_only, qwen_only, openai_only, doctor_qwen, doctor_openai, qwen_openai, all."""
self._sets = {key: _count(value) for key, value in _mapping(counts).items()}
if labels:
self._labels = labels
total = sum(self._sets.values())
self.setAccessibleDescription(
"三方用药交集:" + " · ".join(f"{key} {value}" for key, value in self._sets.items()) if total
else "尚无可比较的候选药方")
self.update()
def has_data(self) -> bool:
return any(self._sets.values())
def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802 - Qt virtual
if not self.has_data():
return
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
# The three set names sit above and below the circles, so their bands are reserved first.
top_band, bottom_band = 18.0, 18.0
available = max(40.0, self.height() - top_band - bottom_band)
side = min(self.width() * 0.62, available / 1.34)
radius = side / 2
offset = radius * 0.52
centre_x = self.width() / 2
centre_y = top_band + available / 2 - offset * 0.1
circles = (
(centre_x - offset, centre_y - offset * 0.55, COLOUR.doctor),
(centre_x + offset, centre_y - offset * 0.55, COLOUR.qwen),
(centre_x, centre_y + offset * 0.75, COLOUR.openai),
)
painter.setPen(Qt.PenStyle.NoPen)
for x, y, colour in circles:
fill = QColor(colour)
fill.setAlpha(52)
painter.setBrush(fill)
painter.drawEllipse(QPointF(x, y), radius, radius)
font = QFont(self.font())
font.setPixelSize(13)
font.setBold(True)
painter.setFont(font)
regions = (
("doctor_only", centre_x - offset * 1.5, centre_y - offset * 0.75, COLOUR.doctor),
("qwen_only", centre_x + offset * 1.5, centre_y - offset * 0.75, COLOUR.qwen),
("openai_only", centre_x, centre_y + offset * 1.45, COLOUR.openai),
("doctor_qwen", centre_x, centre_y - offset * 0.95, COLOUR.ink),
("doctor_openai", centre_x - offset * 0.85, centre_y + offset * 0.55, COLOUR.ink),
("qwen_openai", centre_x + offset * 0.85, centre_y + offset * 0.55, COLOUR.ink),
("all", centre_x, centre_y + offset * 0.1, COLOUR.ink),
)
for key, x, y, colour in regions:
value = self._sets.get(key, 0)
if not value:
continue
painter.setPen(QColor(colour))
painter.drawText(QRectF(x - 22, y - 10, 44, 20), Qt.AlignmentFlag.AlignCenter, str(value))
font.setPixelSize(11)
font.setBold(False)
painter.setFont(font)
painter.setPen(QColor(COLOUR.muted))
top = centre_y - offset * 0.55 - radius - 17
painter.setPen(QColor(COLOUR.doctor))
painter.drawText(QRectF(0, top, centre_x - 6, 16),
Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, self._labels[0])
painter.setPen(QColor(COLOUR.qwen))
painter.drawText(QRectF(centre_x + 6, top, centre_x - 6, 16),
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter, self._labels[1])
painter.setPen(QColor(COLOUR.openai))
painter.drawText(QRectF(0, centre_y + offset * 0.75 + radius + 1, self.width(), 16),
Qt.AlignmentFlag.AlignCenter, self._labels[2])
painter.end()
def minimumSizeHint(self) -> QSize:
return QSize(200, 96)
class DivergingDoses(QWidget):
"""Per-herb dose difference against the doctor's prescription, one row per herb."""
ROW = 30
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self._rows: list[dict[str, Any]] = []
self._span = 1.0
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
def set_rows(self, rows: list[Mapping[str, Any]]) -> None:
"""Each row: name, doctor, qwen, openai (floats or None), unit."""
self._rows = []
for row in rows:
value = _mapping(row)
entry = {"name": str(value.get("name") or ""), "unit": str(value.get("unit") or "")}
for key in ("doctor", "qwen", "openai"):
raw = value.get(key)
entry[key] = None if raw is None or isinstance(raw, bool) else float(raw)
self._rows.append(entry)
deltas = [abs((row[key] or 0) - (row["doctor"] or 0))
for row in self._rows for key in ("qwen", "openai")
if row[key] is not None and row["doctor"] is not None]
self._span = max(1.0, max(deltas, default=1.0))
self.setAccessibleDescription("剂量差异:" + " · ".join(
f"{row['name']} 千问 {self._delta_text(row, 'qwen')} OpenAI {self._delta_text(row, 'openai')}"
for row in self._rows) if self._rows else "暂无可比药味")
self.setMinimumHeight(self.ROW * max(1, len(self._rows)) + 18)
self.updateGeometry()
self.update()
def _delta_text(self, row: Mapping[str, Any], key: str) -> str:
if row.get(key) is None or row.get("doctor") is None:
return ""
delta = row[key] - row["doctor"]
return "一致" if abs(delta) < 1e-9 else f"{delta:+g}{row.get('unit') or ''}"
def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802 - Qt virtual
if not self._rows:
return
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
label_width, value_width = 96, 96
left = label_width + 10
right = self.width() - value_width - 10
middle = (left + right) / 2
scale = max(1.0, (right - left) / 2) / self._span
font = QFont(self.font())
font.setPixelSize(12)
painter.setFont(font)
for index, row in enumerate(self._rows):
top = index * self.ROW + 4
painter.setPen(QColor(COLOUR.ink))
painter.drawText(QRectF(0, top, label_width, self.ROW - 8),
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter, row["name"])
painter.setPen(QColor(COLOUR.track))
painter.drawLine(QPointF(middle, top), QPointF(middle, top + self.ROW - 10))
for offset, key, colour in ((0, "qwen", COLOUR.qwen), (10, "openai", COLOUR.openai)):
if row[key] is None or row["doctor"] is None:
continue
delta = (row[key] - row["doctor"]) * scale
bar = QRectF(min(middle, middle + delta), top + offset, max(abs(delta), 2.0), 8)
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(QColor(colour))
painter.drawRoundedRect(bar, 3, 3)
painter.setPen(QColor(COLOUR.muted))
painter.drawText(QRectF(right + 8, top, value_width - 8, self.ROW - 8),
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
f"{self._delta_text(row, 'qwen')} / {self._delta_text(row, 'openai')}")
painter.end()
class WaffleCoverage(QWidget):
"""One square per attachment: read, limited, or not delivered."""
def __init__(self, parent: QWidget | None = None, *, columns: int = 11) -> None:
super().__init__(parent)
self._columns = max(4, columns)
self._read = self._limited = self._total = 0
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
def set_counts(self, read: int, limited: int, total: int | None = None) -> None:
self._read, self._limited = _count(read), _count(limited)
self._total = max(_count(total), self._read + self._limited)
self.setAccessibleDescription(
f"附件 {self._total} 个:已读 {self._read},受限或不支持 {self._limited}" if self._total else "本次没有附件")
rows = max(1, -(-self._total // self._columns)) if self._total else 0
self.setMinimumHeight(rows * 16 + max(0, rows - 1) * 4)
self.updateGeometry()
self.update()
def has_data(self) -> bool:
return self._total > 0
def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802 - Qt virtual
if not self._total:
return
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
painter.setPen(Qt.PenStyle.NoPen)
gap = 4
size = max(8.0, min(16.0, (self.width() - gap * (self._columns - 1)) / self._columns))
for index in range(self._total):
column, row = index % self._columns, index // self._columns
colour = COLOUR.qwen if index < self._read else (COLOUR.limited if index < self._read + self._limited else COLOUR.track)
painter.setBrush(QColor(colour))
painter.drawRoundedRect(QRectF(column * (size + gap), row * (size + gap), size, size), 4, 4)
painter.end()
class TrendBars(QWidget):
"""Grouped bars per batch: one column per model, oldest batch first."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self._points: list[dict[str, Any]] = []
self.setMinimumHeight(170)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
def set_points(self, points: list[Mapping[str, Any]]) -> None:
"""Each point: label plus qwen/openai scores; None keeps the slot visibly empty."""
self._points = []
for point in points:
value = _mapping(point)
entry = {"label": str(value.get("label") or "")}
for key in ("qwen", "openai"):
raw = value.get(key)
entry[key] = None if raw is None or isinstance(raw, bool) else float(raw)
self._points.append(entry)
described = [f"{entry['label']} 千问 {self._text(entry['qwen'])} OpenAI {self._text(entry['openai'])}"
for entry in self._points]
self.setAccessibleDescription("一致度趋势:" + (" · ".join(described) or "暂无批次"))
self.setToolTip("\n".join(described) or "暂无批次")
self.update()
@staticmethod
def _text(value: float | None) -> str:
return "" if value is None else f"{value:.1f}%"
def has_data(self) -> bool:
return any(point.get(key) is not None for point in self._points for key in ("qwen", "openai"))
def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802 - Qt virtual
if not self._points:
return
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
# The top band is left free so each bar can print its own figure above it.
left, right, top, bottom = 44.0, self.width() - 10.0, 24.0, self.height() - 24.0
values = [point[key] for point in self._points for key in ("qwen", "openai") if point[key] is not None]
ceiling = max(10.0, max(values, default=10.0))
font = QFont(self.font())
font.setPixelSize(10)
painter.setFont(font)
for fraction in (0.0, 0.5, 1.0):
y = bottom - (bottom - top) * fraction
painter.setPen(QColor(COLOUR.track))
painter.drawLine(QPointF(left, y), QPointF(right, y))
painter.setPen(QColor(COLOUR.muted))
painter.drawText(QRectF(0, y - 8, left - 6, 16),
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
f"{ceiling * fraction:.0f}%")
slot = (right - left) / max(1, len(self._points))
width = min(26.0, slot / 3.6)
# The two bars of a batch sit side by side, far enough apart for each figure to fit above.
gap = width / 2 + 4
for index, point in enumerate(self._points):
centre = left + slot * (index + 0.5)
for offset, key, colour in ((-gap, "qwen", COLOUR.qwen), (gap, "openai", COLOUR.openai)):
value = point[key]
if value is None:
continue
height = (bottom - top) * min(1.0, value / ceiling)
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(QColor(colour))
painter.drawRoundedRect(QRectF(centre + offset - width / 2, bottom - height, width, height), 3, 3)
# The design prints each figure above its bar, in that model's own colour.
painter.setPen(QColor(colour))
painter.drawText(QRectF(centre + offset - 21, bottom - height - 17, 42, 14),
Qt.AlignmentFlag.AlignCenter, f"{value:.1f}%")
painter.setPen(QColor(COLOUR.muted))
painter.drawText(QRectF(centre - slot / 2, bottom + 4, slot, 16),
Qt.AlignmentFlag.AlignCenter, point["label"])
painter.end()
@@ -0,0 +1,644 @@
"""Native, read-only prescription comparison using saved report snapshots only."""
from __future__ import annotations
from collections.abc import Mapping
from copy import deepcopy
from dataclasses import dataclass, replace
from decimal import Decimal, InvalidOperation
from typing import Any
from PySide6.QtCore import QRectF, QSize, Qt
from PySide6.QtGui import QColor, QFont, QPainter, QPaintEvent
from PySide6.QtWidgets import (
QAbstractItemView,
QButtonGroup,
QFrame,
QHBoxLayout,
QHeaderView,
QLabel,
QLineEdit,
QPushButton,
QScrollArea,
QSizePolicy,
QTableWidget,
QTableWidgetItem,
QVBoxLayout,
QWidget,
)
from .issued_prescription_ai_labels import EXTRA_FIELD_LABELS, SYSTEM_LABELS, system_text
from .issued_prescription_ai_progress import ACTIVE_STATES, SUCCESS_STATES
MODEL_NAMES = {"qwen": "千问", "openai": "OpenAI"}
MODEL_COLORS = {"qwen": "#3676C8", "openai": "#268578"}
DOCTOR_COLOR = "#526479"
INK, MUTED, LINE = "#23384C", "#758395", "#E6EDF3"
_UNITS = {
"g": "", "": "", "mg": "毫克", "毫克": "毫克", "kg": "千克", "千克": "千克",
"ml": "毫升", "毫升": "毫升", "l": "", "": "", "iu": "国际单位", "国际单位": "国际单位",
**{unit: unit for unit in ("", "", "", "", "", "", "", "", "", "", "", "", "", "")},
}
_BASES = {"per_dose": "每剂", "每剂": "每剂", "per_day": "每日", "每日": "每日", "每天": "每日"}
_FORMULAS = {"main": "主方", "1": "主方", "主方": "主方", "aux": "辅方", "auxiliary": "辅方", "2": "辅方", "辅方": "辅方"}
_STALE = {
"stale": "处方已变更", "superseded": "处方已变更", "prescription_changed": "处方已变更",
"source_updated": "资料已更新", "invalid": "报告已失效", "voided": "处方已作废",
"deleted": "处方已删除", "revoked": "资料权限已变更",
}
def _mapping(value: Any) -> dict[str, Any]:
return dict(value) if isinstance(value, Mapping) else {}
def _text(value: Any) -> str:
"""Never stringify a structured payload, which could expose internal identifiers."""
return str(value).strip() if isinstance(value, (str, int, float, Decimal)) and not isinstance(value, bool) else ""
def _reason(value: Any) -> str:
if isinstance(value, Mapping):
return _reason(value.get("reason") or value.get("message") or value.get("code"))
if isinstance(value, list):
return "".join(filter(None, (_reason(item) for item in value)))
return system_text(value, SYSTEM_LABELS, EXTRA_FIELD_LABELS, strict=True) if _text(value) else ""
def _number(value: Any) -> Decimal | None:
if not _text(value):
return None
try:
number = Decimal(str(value))
except (InvalidOperation, ValueError):
return None
return number if number.is_finite() and number >= 0 else None
def _formula(value: Any) -> str:
return _FORMULAS.get(_text(value), "主辅方未注明")
def _metadata(value: Any) -> str:
return system_text(value, SYSTEM_LABELS, EXTRA_FIELD_LABELS, strict=True) if _text(value) else ""
def _join_instructions(values: list[str]) -> str:
parts = [part.strip() for value in values for part in value.split("")]
return "".join(dict.fromkeys(part for part in parts if part and part.lower() not in {"", "明确无", "none"}))
def _instructions(snapshot: dict[str, Any]) -> str:
"""Read both original herb fields and the service's normalized usage snapshot."""
fields = ("instructions", "decoction_instruction", "special_usage", "usage_instruction", "usage_time", "usage_way")
usage = _mapping(snapshot.get("usage"))
values = [_text(owner.get(field)) for owner in (snapshot, usage) for field in fields]
if not usage:
values.append(_text(snapshot.get("usage")))
return _join_instructions(values)
@dataclass(frozen=True)
class _Dose:
raw: Any
unit: str
basis: str
formula: str
processing: str
route: str
group: str
instructions: str = ""
@property
def number(self) -> Decimal | None:
return _number(self.raw)
@property
def scale(self) -> tuple[str, str] | None:
unit, basis = _UNITS.get(self.unit.lower()), _BASES.get(self.basis)
return (unit, basis) if unit and basis else None
@property
def label(self) -> str:
amount = _text(self.raw)
if not amount:
return ""
unit = _UNITS.get(self.unit.lower()) or _metadata(self.unit) or "单位未注明"
basis = _BASES.get(self.basis) or "基准未确认"
return f"{amount} {unit} / {basis}"
@property
def identity(self) -> tuple[str, str, str, str]:
# Unknown enum values must remain distinct even when their display label is generic.
return (_FORMULAS.get(self.formula, self.formula), SYSTEM_LABELS.get(self.processing, self.processing), SYSTEM_LABELS.get(self.route, self.route), self.group)
@dataclass(frozen=True)
class _Row:
name: str
context: str
doctor: _Dose | None
candidate: _Dose | None
match_type: str
origin: str = "comparison"
source_details: str = ""
source_names: tuple[str, ...] = ()
@property
def scale(self) -> tuple[str, str] | None:
if self.origin != "comparison":
return None
doses = [dose for dose in (self.doctor, self.candidate) if dose is not None]
if not doses or any(dose.scale is None for dose in doses):
return None
if len({dose.scale for dose in doses}) != 1 or len({dose.identity for dose in doses}) != 1:
return None
return doses[0].scale
@property
def incompatibility(self) -> str:
if self.origin == "uncompared":
return "未纳入对比,仅保留候选原方;医生剂量未知"
if self.origin == "original":
return "候选原方附列;对应关系未保存,不推断同药"
if self.doctor is not None and self.candidate is not None:
if self.doctor.identity != self.candidate.identity:
return "主辅方、炮制或给药分组不同,不作条形比较"
if self.doctor.scale and self.candidate.scale and self.doctor.scale != self.candidate.scale:
return "单位或剂量基准不同,不作条形比较"
return "单位或每剂/每日基准未明确,不作条形比较"
def usage_description(self, model_name: str) -> str:
return "".join(filter(None, ("医生:" + self.doctor.instructions if self.doctor and self.doctor.instructions else "", model_name + "" + self.candidate.instructions if self.candidate and self.candidate.instructions else "")))
def description(self, model_name: str) -> str:
dosage = f"{self.name}{self.context};医生:{self.doctor.label if self.doctor else ''}{model_name}{self.candidate.label if self.candidate else ''}"
return "".join(filter(None, (dosage, self.usage_description(model_name), self.source_details)))
def _dose(row: dict[str, Any], side: str) -> _Dose | None:
# An explicit null snapshot takes precedence over contradictory legacy flat fields.
if side in row and row[side] is None:
return None
nested = _mapping(row.get(side))
keys = ("doctor_dosage", "doctor_dose") if side == "doctor" else ("candidate_dosage", "candidate_dose", "ai_dose")
raw = nested.get("dosage") if "dosage" in nested else next((row[key] for key in keys if key in row), None)
if not nested and raw is None:
return None
values = {key: _text(nested[key] if key in nested else row.get(key)) for key in ("unit", "dose_basis", "formula_type", "processing", "administration_route", "group")}
instructions = _instructions(nested) or _instructions(row)
return _Dose(raw, values["unit"], values["dose_basis"], values["formula_type"], values["processing"], values["administration_route"], values["group"], instructions)
def _row(value: dict[str, Any]) -> _Row:
doctor, candidate = _dose(value, "doctor"), _dose(value, "candidate")
details = []
for dose in (doctor, candidate):
if dose is not None:
detail = " · ".join(filter(None, (_formula(dose.formula), _metadata(dose.processing), _metadata(dose.route), _metadata(dose.group))))
if detail not in details:
details.append(detail)
context = " / ".join(details) or _formula(value.get("formula_type"))
if len(details) > 1:
context = "医生与模型:" + context
return _Row(_text(value.get("name") or value.get("canonical_name") or value.get("herb_name")) or "药名未保存", context, doctor, candidate, _text(value.get("match_type")))
def _original_row(herb: Any, *, origin: str) -> _Row:
saved = dict(herb) if isinstance(herb, Mapping) else {"name": _text(herb) or "药材记录需核对"}
row = _row({"name": saved.get("name"), "doctor": None, "candidate": saved, "match_type": "candidate_only"})
note = "未纳入对比" if origin == "uncompared" else "候选原方附列 · 对应关系未保存"
return replace(row, origin=origin, context=note + " · " + row.context)
def _saved_rows(candidate: dict[str, Any], comparison: dict[str, Any]) -> list[_Row]:
saved = comparison.get("rows")
comparison_rows = [dict(value) for value in saved if isinstance(value, Mapping)] if isinstance(saved, list) else []
herbs = candidate.get("herbs")
herbs = herbs if isinstance(herbs, list) else []
result: list[_Row] = []
covered: set[int] = set()
missing_correspondence = False
for value in comparison_rows:
row = _row(value)
if row.candidate is not None and herbs:
indices = _mapping(value.get("candidate")).get("source_rows")
# The service uses zero-based array_values indices; never infer correspondence
# from names, doses, order, or the number of normalized rows.
valid_trace = isinstance(indices, list) and bool(indices) and all(isinstance(index, int) and not isinstance(index, bool) and 0 <= index < len(herbs) for index in indices)
if valid_trace:
indices = list(dict.fromkeys(indices))
covered.update(indices)
originals = [_original_row(herbs[index], origin="original") for index in indices]
details = "".join(f"{index + 1}{original.name} {original.candidate.label if original.candidate else ''}" + ("" + original.candidate.instructions if original.candidate and original.candidate.instructions else "") for index, original in zip(indices, originals, strict=True))
instructions = _join_instructions([row.candidate.instructions, *[original.candidate.instructions for original in originals if original.candidate]])
row = replace(row, candidate=replace(row.candidate, instructions=instructions), source_details="候选原方记录:" + details, source_names=tuple(original.name for original in originals))
else:
missing_correspondence = True
result.append(row)
# Normalization can omit unknown names, processing conflicts, or incompatible duplicate
# entries. Keep every unaccounted original, but do not claim its doctor counterpart.
origin = "original" if missing_correspondence else "uncompared"
result.extend(_original_row(herb, origin=origin) for index, herb in enumerate(herbs) if index not in covered)
return result
class _DoseChart(QWidget):
"""A scrollable painted chart; the adjacent table provides native accessibility."""
ROW_HEIGHT = 80
GROUP_HEIGHT = 38
def __init__(self, parent: QWidget) -> None:
super().__init__(parent)
self.setObjectName("PrescriptionDoseChart")
self.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Preferred)
self.setAccessibleName("药方逐味剂量对比图")
self.rows: list[_Row] = []
self.groups: list[tuple[tuple[str, str] | None, list[_Row], Decimal]] = []
self.model_key = "qwen"
self.message = "暂无药方数据"
self.bars_enabled = False
def set_rows(self, rows: list[_Row], model_key: str, *, bars_enabled: bool, message: str) -> None:
self.rows, self.model_key, self.bars_enabled, self.message = rows, model_key, bars_enabled, message
grouped: dict[tuple[str, str] | None, list[_Row]] = {}
for row in rows:
grouped.setdefault(row.scale if bars_enabled else None, []).append(row)
self.groups = []
for scale, members in grouped.items():
numbers = [dose.number for row in members for dose in (row.doctor, row.candidate) if dose is not None and dose.number is not None]
self.groups.append((scale, members, max(numbers, default=Decimal(0))))
color_name = "蓝色" if model_key == "qwen" else "青绿色"
descriptions = [message, f"医生为深灰蓝;{MODEL_NAMES[model_key]}{color_name}。各单位与基准组独立标尺,组间长度不可比较。缺失值为—,不按零计算。"]
for row in rows:
descriptions.append(row.description(MODEL_NAMES[model_key]))
if bars_enabled and row.scale is None:
descriptions.append(row.incompatibility)
if any(dose is not None and _text(dose.raw) and dose.number is None for dose in (row.doctor, row.candidate)):
descriptions.append("非数值、负数及非有限剂量保留原值,未绘制条形。")
self.setAccessibleDescription("\n".join(descriptions))
height = 16 + sum(self.GROUP_HEIGHT + len(members) * self.ROW_HEIGHT for _, members, _ in self.groups)
self.setMinimumHeight(max(120, height))
self.updateGeometry()
self.update()
def sizeHint(self) -> QSize:
return QSize(440, self.minimumHeight())
def minimumSizeHint(self) -> QSize:
return QSize(0, 0)
def paintEvent(self, event: QPaintEvent) -> None:
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
painter.fillRect(self.rect(), QColor("#FFFFFF"))
painter.setFont(self.font())
if not self.rows:
painter.setPen(QColor(MUTED))
painter.drawText(self.rect().adjusted(24, 12, -24, -12), Qt.AlignmentFlag.AlignCenter | Qt.TextFlag.TextWordWrap, self.message)
return
width, y = max(0, self.width() - 32), 8
normal = QFont(self.font())
bold = QFont(normal)
bold.setBold(True)
for scale, members, maximum in self.groups:
painter.fillRect(QRectF(16, y, width, self.GROUP_HEIGHT - 8), QColor("#F3F6F9"))
painter.setFont(bold)
painter.setPen(QColor(INK))
heading = f"{scale[0]} · {scale[1]} 独立标尺 0{maximum}" if scale else "原始剂量 · 不作条形比较"
painter.drawText(QRectF(24, y, max(0, width - 16), self.GROUP_HEIGHT - 8), Qt.AlignmentFlag.AlignVCenter, painter.fontMetrics().elidedText(heading, Qt.TextElideMode.ElideRight, max(0, width - 16)))
y += self.GROUP_HEIGHT
for row in members:
if y + self.ROW_HEIGHT >= event.rect().top() and y <= event.rect().bottom():
painter.setFont(bold)
painter.setPen(QColor(INK))
title = f"{row.name} · {row.context}"
painter.drawText(QRectF(16, y, width, 21), Qt.AlignmentFlag.AlignVCenter, painter.fontMetrics().elidedText(title, Qt.TextElideMode.ElideRight, width))
painter.setFont(normal)
if scale is None:
painter.setPen(QColor(MUTED))
detail = "".join(("医生 " + (row.doctor.label if row.doctor else ""), MODEL_NAMES[self.model_key] + " " + (row.candidate.label if row.candidate else "")))
painter.drawText(QRectF(16, y + 24, width, 22), Qt.AlignmentFlag.AlignVCenter, painter.fontMetrics().elidedText(detail, Qt.TextElideMode.ElideRight, width))
note = row.incompatibility if self.bars_enabled else "按已保存原值列示,详见左侧药材表"
painter.drawText(QRectF(16, y + 49, width, 22), Qt.AlignmentFlag.AlignVCenter, painter.fontMetrics().elidedText(note, Qt.TextElideMode.ElideRight, width))
else:
for index, (dose, color, name) in enumerate(((row.doctor, DOCTOR_COLOR, "医生"), (row.candidate, MODEL_COLORS[self.model_key], MODEL_NAMES[self.model_key]))):
bar_y = y + 24 + index * 20
painter.setPen(QColor(color))
painter.drawText(QRectF(16, bar_y - 5, 54, 22), Qt.AlignmentFlag.AlignVCenter, name)
value = dose.label if dose else ""
label_width = min(max(118, painter.fontMetrics().horizontalAdvance(value) + 8), max(118, width // 2))
bar_x, bar_width = 76, max(8, width - 66 - label_width - 12)
if dose is not None and dose.number is not None:
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(QColor("#F0F3F7"))
painter.drawRoundedRect(QRectF(bar_x, bar_y, bar_width, 9), 3, 3)
fraction = float(dose.number / maximum) if maximum else 0.0
if fraction > 0:
painter.setBrush(QColor(color))
painter.drawRoundedRect(QRectF(bar_x, bar_y, bar_width * fraction, 9), 3, 3)
painter.setPen(QColor(INK))
painter.drawText(QRectF(bar_x + bar_width + 10, bar_y - 5, label_width, 22), Qt.AlignmentFlag.AlignVCenter, painter.fontMetrics().elidedText(value, Qt.TextElideMode.ElideRight, int(label_width)))
painter.setPen(QColor(LINE))
painter.drawLine(16, y + self.ROW_HEIGHT - 6, self.width() - 16, y + self.ROW_HEIGHT - 6)
y += self.ROW_HEIGHT
class PrescriptionComparisonPanel(QWidget):
"""Switch between saved candidate prescriptions without issuing any requests."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setObjectName("PrescriptionComparisonPanel")
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self._batch: dict[str, Any] = {}
self._rows: list[_Row] = []
self._model_key = "qwen"
self._bars_enabled = False
self._chart_message = "暂无药方数据"
self._candidate_count: int | None = None
self._updating_rows = False
self.setStyleSheet(f"""
QWidget#PrescriptionComparisonPanel {{ background: transparent; }}
QFrame#PrescriptionPane {{ background: white; border: 1px solid {LINE}; border-radius: 9px; }}
QLabel {{ color: {INK}; background: transparent; }}
QLabel#PrescriptionTitle {{ font-size: 20px; font-weight: 700; }}
QLabel#PrescriptionCaption {{ color: {MUTED}; font-size: 12px; }}
QLabel#PrescriptionStatus {{ color: #5F7287; font-size: 12px; }}
QPushButton#PrescriptionModel {{ border: 1px solid #DAE4EE; background: white; color: #607388;
border-radius: 6px; padding: 6px 19px; font-weight: 600; }}
QPushButton#PrescriptionModel[model="qwen"]:checked {{ background: #EDF4FE; color: #3676C8; border-color: #AAC7EB; }}
QPushButton#PrescriptionModel[model="openai"]:checked {{ background: #EDF7F4; color: #268578; border-color: #A5D2C6; }}
QLineEdit#PrescriptionSearch {{ background: white; border: 1px solid #DAE4EE; border-radius: 6px; padding: 6px 10px; }}
QTableWidget#PrescriptionHerbs {{ background: white; border: 0; color: {INK}; gridline-color: {LINE}; }}
QTableWidget#PrescriptionHerbs::item {{ padding: 4px 7px; border-bottom: 1px solid #EDF1F5; }}
QTableWidget#PrescriptionHerbs::item:selected {{ background: #EDF4FC; color: {INK}; }}
QHeaderView::section {{ background: #F4F7FA; color: #687C91; border: 0; padding: 7px; font-weight: 600; }}
QScrollArea {{ background: white; border: 0; }}
""")
layout = QVBoxLayout(self)
layout.setContentsMargins(10, 8, 10, 8)
layout.setSpacing(10)
toolbar = QHBoxLayout()
toolbar.setSpacing(7)
self.model_buttons: dict[str, QPushButton] = {}
self.model_group = QButtonGroup(self)
for model_key, name in MODEL_NAMES.items():
button = QPushButton(name, self)
button.setObjectName("PrescriptionModel")
button.setProperty("model", model_key)
button.setCheckable(True)
button.setChecked(model_key == self._model_key)
button.setAccessibleName(f"查看{name}候选方与医生方对比")
button.clicked.connect(lambda _checked=False, key=model_key: self._select_model(key))
self.model_group.addButton(button)
self.model_buttons[model_key] = button
toolbar.addWidget(button)
toolbar.addStretch(1)
self.search = QLineEdit(self)
self.search.setObjectName("PrescriptionSearch")
self.search.setPlaceholderText("搜索药名")
self.search.setAccessibleName("搜索药名,同时筛选药材表和图表")
self.search.setClearButtonEnabled(True)
self.search.setMaximumWidth(240)
self.search.textChanged.connect(self._filter_rows)
toolbar.addWidget(self.search)
layout.addLayout(toolbar)
panes = QHBoxLayout()
panes.setSpacing(12)
left = QFrame(self)
left.setObjectName("PrescriptionPane")
left.setMinimumWidth(0)
left.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Expanding)
left_layout = QVBoxLayout(left)
left_layout.setContentsMargins(15, 13, 15, 10)
left_layout.setSpacing(6)
self.caption_label = self._label("千问 · 候选药方", left, "PrescriptionCaption")
self.name_label = self._label("暂无候选药方", left, "PrescriptionTitle")
self.name_label.setWordWrap(True)
self.name_label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Preferred)
self.usage_label = self._label("", left, "PrescriptionCaption")
self.usage_label.setWordWrap(True)
left_layout.addWidget(self.caption_label)
left_layout.addWidget(self.name_label)
left_layout.addWidget(self.usage_label)
self.herb_table = QTableWidget(0, 3, left)
self.herb_table.setObjectName("PrescriptionHerbs")
self.herb_table.setAccessibleName("候选药方药材与医生剂量对照表")
self.herb_table.setHorizontalHeaderLabels(["药材 / 主辅方", "医生剂量", "千问剂量"])
self.herb_table.verticalHeader().hide()
self.herb_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
self.herb_table.horizontalHeader().setMinimumSectionSize(36)
self.herb_table.setShowGrid(False)
self.herb_table.setWordWrap(True)
self.herb_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
self.herb_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.herb_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.herb_table.itemSelectionChanged.connect(self._focus_chart_row)
self.herb_table.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self.herb_table.setMinimumSize(0, 0)
self.herb_table.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Expanding)
left_layout.addWidget(self.herb_table, 1)
self.empty_label = self._label("选择报告后显示已保存的候选药方", left, "PrescriptionCaption")
self.empty_label.setWordWrap(True)
left_layout.addWidget(self.empty_label)
self.count_label = self._label("", left, "PrescriptionCaption")
self.count_label.setWordWrap(True)
left_layout.addWidget(self.count_label)
panes.addWidget(left, 5)
right = QFrame(self)
right.setObjectName("PrescriptionPane")
right.setMinimumWidth(0)
right.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Expanding)
right_layout = QVBoxLayout(right)
right_layout.setContentsMargins(15, 13, 15, 10)
right_layout.setSpacing(6)
title = self._label("逐味剂量对比", right)
title.setStyleSheet("font-size: 15px; font-weight: 700;")
legend_row = QHBoxLayout()
legend_row.addWidget(title)
legend_row.addStretch()
self.legend = self._label("● 医生 ● 千问", right, "PrescriptionCaption")
self.legend.setTextFormat(Qt.TextFormat.RichText)
legend_row.addWidget(self.legend)
right_layout.addLayout(legend_row)
self.status_label = self._label("", right, "PrescriptionStatus")
self.status_label.setWordWrap(True)
self.status_label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Preferred)
right_layout.addWidget(self.status_label)
self.chart_scroll = QScrollArea(right)
self.chart_scroll.setWidgetResizable(True)
self.chart_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.chart_scroll.setMinimumSize(0, 0)
self.chart_scroll.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Expanding)
self.chart_scroll.setAccessibleName("可滚动查看全部药味的剂量图")
self.chart = _DoseChart(self.chart_scroll)
self.chart_scroll.setWidget(self.chart)
right_layout.addWidget(self.chart_scroll, 1)
footnote = self._label("同组同标尺,组间勿比较;剂量差异不代表医疗优劣。", right, "PrescriptionCaption")
footnote.setWordWrap(True)
right_layout.addWidget(footnote)
panes.addWidget(right, 5)
layout.addLayout(panes, 1)
self._render()
@staticmethod
def _label(text: str, parent: QWidget, name: str = "") -> QLabel:
label = QLabel(text, parent)
label.setTextFormat(Qt.TextFormat.PlainText)
label.setObjectName(name)
return label
@property
def selected_model(self) -> str:
return self._model_key
def minimumSizeHint(self) -> QSize:
return QSize(0, 0)
def set_batch(self, batch: dict) -> None:
data = _mapping(batch)
if data == self._batch:
return
self._batch = deepcopy(data)
self._render()
def _select_model(self, model_key: str) -> None:
if model_key == self._model_key:
return
self._model_key = model_key
self._render()
def _state(self, model: dict[str, Any], candidate: dict[str, Any], comparison: dict[str, Any]) -> tuple[bool, str]:
if not self._batch:
return False, "选择一份报告后,查看已保存的候选药方与剂量对比。"
validity = _text(self._batch.get("validity"))
if validity and validity not in {"current", "valid"}:
return False, (_STALE.get(validity) or "报告有效性未确认") + ";仅查看历史原值,暂停条形比较。"
status = _text(model.get("status"))
candidate_status = _text(candidate.get("status"))
if status in ACTIVE_STATES or candidate_status in ACTIVE_STATES:
return False, f"{MODEL_NAMES[self._model_key]}正在生成候选方,完成后显示剂量对比。"
if status in _STALE:
return False, _STALE[status] + ";仅查看历史原值。"
if status in {"failed", "cancelled", "canceled", "blocked"}:
return False, "候选方分析未完成;已保存内容仅供查看。"
if candidate_status in {"insufficient_data", "withheld_for_risk", "no_medication", "no_medication_recommended"}:
headline = {"insufficient_data": "资料不足,暂未提供候选药方", "withheld_for_risk": "因风险暂缓候选用药", "no_medication": "建议暂不使用药物", "no_medication_recommended": "建议暂不使用药物"}[candidate_status]
return False, headline + ("" + _reason(candidate.get("reason")) if candidate.get("reason") else "")
if status and status not in SUCCESS_STATES | {"partial"}:
return False, "模型状态未确认;仅列示已保存原值。"
if candidate_status and candidate_status not in {"available_for_review", "success", "succeeded", "completed"}:
return False, "候选方状态未确认;仅列示已保存原值。"
if comparison.get("status") == "not_comparable":
return False, "本报告不可比:" + (_reason(comparison.get("reason") or comparison.get("reason_code")) or "未通过单位、剂量或资料完整性核验。")
saved_rows = comparison.get("rows")
if not isinstance(saved_rows, list) or not any(isinstance(row, Mapping) for row in saved_rows):
return False, "尚无已保存的逐味对比;医生剂量以—表示,不推算历史处方。"
if comparison.get("status") != "comparable":
return False, "可比状态未确认;仅列示已保存原值。"
prefix = "报告有效性未注明;" if not validity else ""
return True, prefix + "按明确单位与每剂/每日基准分组;缺失剂量不按零计算。"
def _render(self) -> None:
model = _mapping(_mapping(self._batch.get("models")).get(self._model_key))
candidate, comparison = _mapping(model.get("candidate")), _mapping(model.get("comparison"))
self._rows = _saved_rows(candidate, comparison)
self._bars_enabled, self._chart_message = self._state(model, candidate, comparison)
name = MODEL_NAMES[self._model_key]
self.caption_label.setText(f"{name} · 候选药方")
candidate_herbs = candidate.get("herbs")
self._candidate_count = len(candidate_herbs) if isinstance(candidate_herbs, list) else None
has_herbs = isinstance(candidate_herbs, list) and bool(candidate_herbs)
if any(row.origin == "original" for row in self._rows):
self._chart_message += " 候选原方另行附列;历史对应关系未保存,不推断同药。"
elif comparison.get("rows") and any(row.origin == "uncompared" for row in self._rows):
self._chart_message += " 未纳入对比的候选药材已补列原值。"
empty_title = "候选方生成中" if model.get("status") in ACTIVE_STATES else "暂无候选药方"
self.name_label.setText(_text(candidate.get("prescription_name")) or ("已保存候选方" if has_herbs else empty_title))
self.name_label.setToolTip(self.name_label.text())
usage = []
if _text(candidate.get("usage_instruction")):
usage.append(_text(candidate["usage_instruction"]))
if _number(candidate.get("times_per_day")) is not None:
usage.append(f"每日 {candidate['times_per_day']}")
if _number(candidate.get("usage_days")) is not None:
usage.append(f"{candidate['usage_days']}")
usage_text = " · ".join(usage)
self.usage_label.setText(usage_text[:100] + ("" if len(usage_text) > 100 else ""))
self.usage_label.setToolTip(usage_text)
self.usage_label.setVisible(bool(usage_text))
self.status_label.setText(self._chart_message)
self.legend.setText(f'<span style="color:{DOCTOR_COLOR}">● 医生</span>&nbsp;&nbsp;<span style="color:{MODEL_COLORS[self._model_key]}">● {name}</span>')
self.herb_table.setHorizontalHeaderLabels(["药材 / 主辅方", "医生剂量", f"{name}剂量"])
self._filter_rows()
def _filter_rows(self) -> None:
self._updating_rows = True
query = self.search.text().strip().casefold()
rows = [row for row in self._rows if any(query in name.casefold() for name in (row.name, *row.source_names))]
table_scroll = self.herb_table.verticalScrollBar().value()
chart_scroll = self.chart_scroll.verticalScrollBar().value()
selected_row = self.herb_table.currentRow()
selected_name = self.herb_table.item(selected_row, 0).text() if selected_row >= 0 and self.herb_table.item(selected_row, 0) else ""
self.herb_table.setRowCount(len(rows))
for index, row in enumerate(rows):
name_text = row.name + "\n" + row.context
instructions = row.usage_description(MODEL_NAMES[self._model_key])
if instructions:
name_text += "\n" + instructions
values = (name_text, row.doctor.label if row.doctor else "", row.candidate.label if row.candidate else "")
for column, value in enumerate(values):
item = QTableWidgetItem(value)
item.setToolTip(row.description(MODEL_NAMES[self._model_key]))
item.setData(Qt.ItemDataRole.AccessibleTextRole, value)
if column == 0:
item.setData(Qt.ItemDataRole.UserRole, row)
item.setData(Qt.ItemDataRole.AccessibleDescriptionRole, row.description(MODEL_NAMES[self._model_key]))
item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
if column == 1:
item.setForeground(QColor(DOCTOR_COLOR))
if column == 2:
item.setForeground(QColor(MODEL_COLORS[self._model_key]))
self.herb_table.setItem(index, column, item)
self.herb_table.setRowHeight(index, max(48, self.herb_table.fontMetrics().height() * (3 if instructions else 2) + 12))
if name_text == selected_name:
self.herb_table.selectRow(index)
self.herb_table.verticalScrollBar().setValue(table_scroll)
self.herb_table.setVisible(bool(rows))
self.empty_label.setText("没有匹配的药名,请调整搜索。" if self._rows and not rows else self._chart_message)
self.empty_label.setVisible(not rows)
comparison_count = sum(row.origin == "comparison" for row in self._rows)
uncompared_count = sum(row.origin == "uncompared" for row in self._rows)
original_count = sum(row.origin == "original" for row in self._rows)
counts = [f"候选原方 {self._candidate_count}" if self._candidate_count is not None else "候选原方未保存", f"对比 {comparison_count}"]
if uncompared_count:
counts.append(f"未纳入 {uncompared_count}")
if original_count:
counts.append(f"原方附列 {original_count}")
if query:
counts.append(f"搜索显示 {len(rows)}")
self.count_label.setText(" · ".join(counts))
self.count_label.setToolTip("原方项数来自候选药材完整清单;对比项数来自已保存的标准化记录。附列药材不推断与对比记录的对应关系;— 表示该侧未保存剂量。")
self.count_label.setVisible(bool(self._rows))
empty_message = "没有匹配的药名" if self._rows and not rows else self._chart_message
self.chart.set_rows(rows, self._model_key, bars_enabled=self._bars_enabled, message=empty_message)
self.chart_scroll.verticalScrollBar().setValue(chart_scroll)
self._updating_rows = False
def _focus_chart_row(self) -> None:
if self._updating_rows:
return
item = self.herb_table.item(self.herb_table.currentRow(), 0)
if item is None:
return
target = item.data(Qt.ItemDataRole.UserRole)
y = 8
for _scale, members, _maximum in self.chart.groups:
y += self.chart.GROUP_HEIGHT
for row in members:
if row is target:
self.chart_scroll.verticalScrollBar().setValue(y)
return
y += self.chart.ROW_HEIGHT
@@ -0,0 +1,558 @@
"""The console chrome: the agreement comparison bar and the numbered step rail.
Both widgets read only what the saved batch carries. A model without a comparable candidate keeps
an empty track instead of a zero-length bar, and the rail's counters stay blank until the batch
actually reports them.
"""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from PySide6.QtCore import QPointF, QRectF, QSize, Qt, Signal
from PySide6.QtGui import (
QBrush,
QColor,
QFont,
QFontMetricsF,
QLinearGradient,
QPainter,
QPainterPath,
QPaintEvent,
QPalette,
)
from PySide6.QtWidgets import (
QFrame,
QHBoxLayout,
QLabel,
QProgressBar,
QPushButton,
QSizePolicy,
QVBoxLayout,
QWidget,
)
from .issued_prescription_ai_theme import CONSOLE, MODEL_HUE, MODEL_TEXT, num_font
MODEL_NAMES = {"qwen": "千问", "openai": "OpenAI"}
def _number(value: Any) -> float | None:
if value is None or isinstance(value, bool):
return None
try:
parsed = float(value)
except (TypeError, ValueError):
return None
return parsed
def _mapping(value: Any) -> dict[str, Any]:
return dict(value) if isinstance(value, Mapping) else {}
def _score(model: Mapping[str, Any]) -> float | None:
comparison = _mapping(_mapping(model).get("comparison"))
status = comparison.get("status") or _mapping(model).get("comparison_status")
if status != "comparable":
return None
value = _number(comparison.get("score", _mapping(model).get("score")))
return None if value is None or value < 0 or value > 100 else value
class ScaleTrack(QWidget):
"""A 0100 track: the model's own fill, and a grey mark where the other model stands."""
# The design's scale: a 12px band for the rival's label, a 13px rail, then the axis row.
LABEL_BAND = 13.0
RAIL_TOP = 16.0
RAIL_HEIGHT = 13.0
AXIS_TOP = 31.0
def __init__(self, model_key: str, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.model_key = model_key
self._value: float | None = None
self._other: float | None = None
self._other_label = ""
self.setFixedHeight(44)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
def set_values(self, value: float | None, other: float | None, other_label: str) -> None:
self._value, self._other, self._other_label = value, other, other_label
self.setAccessibleDescription("暂无可比结果" if value is None else f"{value:.1f}%")
self.setToolTip("" if other is None else f"{other_label}{other:.1f}%")
self.update()
def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802 - Qt virtual
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
top, height = self.RAIL_TOP, self.RAIL_HEIGHT
radius = height / 2
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(QColor(CONSOLE["raised"]))
painter.drawRoundedRect(QRectF(0, top, self.width(), height), radius, radius)
if self._value:
filled = self.width() * self._value / 100
hue = QColor(MODEL_HUE[self.model_key])
faded = QColor(hue)
faded.setAlphaF(0.45)
gradient = QLinearGradient(QPointF(0, top), QPointF(filled, top))
gradient.setColorAt(0.0, faded)
gradient.setColorAt(1.0, hue)
painter.setBrush(QBrush(gradient))
painter.drawRoundedRect(QRectF(0, top, filled, height), radius, radius)
# Quarter dividers sit on the rail itself, as the design draws them.
painter.setPen(QColor(CONSOLE["grid_line"]))
for fraction in (0.25, 0.5, 0.75):
x = self.width() * fraction
painter.drawLine(QPointF(x, top), QPointF(x, top + height))
axis = QFont(self.font())
axis.setPixelSize(9)
painter.setFont(axis)
painter.setPen(QColor(CONSOLE["faint"]))
for fraction in (0.0, 0.25, 0.5, 0.75, 1.0):
label = f"{int(fraction * 100)}%"
width = 44.0
left = self.width() * fraction - width / 2
align = Qt.AlignmentFlag.AlignCenter
if fraction == 0.0:
left, align = 0.0, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
elif fraction == 1.0:
left, align = self.width() - width, Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
painter.drawText(QRectF(left, self.AXIS_TOP, width, 12), align, label)
if self._other is not None:
x = self.width() * self._other / 100
marker = QColor(CONSOLE["muted"])
marker.setAlphaF(0.8)
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(marker)
painter.drawRoundedRect(QRectF(x - 1, top - 4, 2, height + 8), 1, 1)
painter.setPen(QColor(CONSOLE["faint"]))
painter.drawText(QRectF(max(0.0, min(x - 60, self.width() - 120)), 0, 120, self.LABEL_BAND),
Qt.AlignmentFlag.AlignCenter, f"{self._other_label} 在此")
painter.end()
class ScoreLabel(QLabel):
"""The big figure with its unit set small and raised, the way the design prints it.
``text()`` still returns the whole string, so callers and tests read one plain value.
"""
SIZE = 27
UNIT_SIZE = 13
def _parts(self) -> tuple[str, str]:
text = self.text()
for unit in ("%", "pt"):
if text.endswith(unit) and len(text) > len(unit):
return text[: -len(unit)], unit
return text, ""
def sizeHint(self) -> QSize: # noqa: N802 - Qt virtual
figure, unit = self._parts()
width = QFontMetricsF(num_font(self.SIZE, weight=QFont.Weight.DemiBold)).horizontalAdvance(figure)
if unit:
width += 2 + QFontMetricsF(num_font(self.UNIT_SIZE)).horizontalAdvance(unit)
return QSize(int(width) + 1, int(self.SIZE * 1.1) + 1)
def minimumSizeHint(self) -> QSize: # noqa: N802 - Qt virtual
return self.sizeHint()
def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802 - Qt virtual
figure, unit = self._parts()
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
figure_font = num_font(self.SIZE, weight=QFont.Weight.DemiBold)
painter.setFont(figure_font)
painter.setPen(self.palette().color(QPalette.ColorRole.WindowText))
metrics = QFontMetricsF(figure_font)
baseline = (self.height() + metrics.capHeight()) / 2
painter.drawText(QPointF(0, baseline), figure)
if unit:
unit_font = num_font(self.UNIT_SIZE)
painter.setFont(unit_font)
painter.setPen(QColor(CONSOLE["muted"]))
painter.drawText(QPointF(metrics.horizontalAdvance(figure) + 2,
baseline - metrics.capHeight() + QFontMetricsF(unit_font).capHeight()),
unit)
painter.end()
class AgreementBar(QFrame):
"""Both models' agreement on one scale, with the gap between them stated in points."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setObjectName("AiAgreement")
layout = QHBoxLayout(self)
layout.setContentsMargins(22, 16, 22, 16)
layout.setSpacing(24)
self.blocks: dict[str, dict[str, Any]] = {}
for index, key in enumerate(("qwen", "openai")):
if index:
divider = QFrame(self)
divider.setObjectName("AiFactDivider")
divider.setFixedWidth(1)
layout.addWidget(divider)
layout.addWidget(self._delta_block())
divider = QFrame(self)
divider.setObjectName("AiFactDivider")
divider.setFixedWidth(1)
layout.addWidget(divider)
# Both heads line up at the top, so a model that failed does not slide its column down.
layout.addWidget(self._model_block(key), 1, Qt.AlignmentFlag.AlignTop)
def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802 - Qt virtual
"""The design runs a blue-to-violet bar down the card's left edge, inside its rounding."""
super().paintEvent(event)
card = QPainterPath()
card.addRoundedRect(QRectF(1, 1, self.width() - 2, self.height() - 2), 9, 9)
strip = QPainterPath()
strip.addRect(QRectF(0, 0, 4, self.height()))
gradient = QLinearGradient(QPointF(0, 0), QPointF(0, self.height()))
gradient.setColorAt(0.0, QColor(CONSOLE["accent"]))
gradient.setColorAt(1.0, QColor(CONSOLE["openai"]))
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
painter.setPen(Qt.PenStyle.NoPen)
painter.fillPath(card.intersected(strip), QBrush(gradient))
painter.end()
def _model_block(self, key: str) -> QWidget:
holder = QWidget(self)
column = QVBoxLayout(holder)
column.setContentsMargins(0, 0, 0, 0)
column.setSpacing(9)
head = QHBoxLayout()
head.setSpacing(9)
dot = QLabel(holder)
dot.setFixedSize(9, 9)
dot.setStyleSheet(f"background: {MODEL_HUE[key]}; border-radius: 3px;")
head.addWidget(dot)
name = QLabel(MODEL_NAMES[key], holder)
name.setStyleSheet(f"color: {CONSOLE['heading']}; font-size: 13.8px; font-weight: 600;")
head.addWidget(name)
status = QLabel("尚无报告", holder)
status.setTextFormat(Qt.TextFormat.PlainText)
head.addWidget(status)
coverage = QLabel("", holder)
coverage.setTextFormat(Qt.TextFormat.PlainText)
coverage.setStyleSheet(
f"background: {CONSOLE['amber_dim']}; color: {CONSOLE['amber_text']};"
f" border: 1px solid {CONSOLE['amber']}; border-radius: 9px; padding: 1px 8px; font-size: 9.9px;")
head.addWidget(coverage)
elapsed = QLabel("", holder)
elapsed.setTextFormat(Qt.TextFormat.PlainText)
elapsed.setStyleSheet(
f"background: transparent; color: {CONSOLE['faint']};"
f" border: 1px solid {CONSOLE['line']}; border-radius: 9px; padding: 1px 8px; font-size: 9.9px;")
head.addWidget(elapsed)
head.addStretch(1)
column.addLayout(head)
score = ScoreLabel("", holder)
score.setObjectName(f"AiAgreementScore{key.capitalize()}")
score.setTextFormat(Qt.TextFormat.PlainText)
column.addWidget(score)
caption = QLabel("药味与剂量一致率", holder)
caption.setStyleSheet(f"color: {CONSOLE['faint']}; font-size: 10.5px;")
column.addWidget(caption)
track = ScaleTrack(key, holder)
column.addWidget(track)
# A failed model states why it stopped and offers its retry on its own block, which is
# the only place in the console that belongs to that model alone.
failure = QWidget(holder)
failure_row = QHBoxLayout(failure)
failure_row.setContentsMargins(0, 2, 0, 0)
failure_row.setSpacing(8)
failure_text = QLabel("", failure)
failure_text.setTextFormat(Qt.TextFormat.PlainText)
failure_text.setWordWrap(True)
failure_text.setStyleSheet(f"color: {CONSOLE['rose_text']}; font-size: 12px;")
failure_row.addWidget(failure_text, 1)
retry_slot = QHBoxLayout()
retry_slot.setContentsMargins(0, 0, 0, 0)
retry_slot.setSpacing(6)
failure_row.addLayout(retry_slot)
failure.setVisible(False)
column.addWidget(failure)
stage = QLabel("等待处理进度", holder)
stage.setTextFormat(Qt.TextFormat.PlainText)
stage.setWordWrap(True)
stage.setStyleSheet(f"color: {MODEL_TEXT[key]}; font-size: 12px;")
stage.hide()
# The plain bar stays as a value carrier for callers and accessibility tools; the painted
# track above is the visual, so it is never added to the layout.
carrier = QProgressBar(holder)
carrier.setRange(0, 1000)
carrier.setTextVisible(False)
carrier.setFixedHeight(4)
carrier.setAccessibleName(f"{MODEL_NAMES[key]}与医生方的药味及剂量一致度")
carrier.setVisible(False)
self.blocks[key] = {"score": score, "coverage": coverage, "elapsed": elapsed, "track": track,
"status": status, "failure": failure, "failure_text": failure_text,
"retry_slot": retry_slot, "stage": stage, "carrier": carrier}
return holder
def views(self, key: str) -> dict[str, Any]:
"""Widget map kept stable for the dialog and its regression tests."""
block = self.blocks[key]
return {"card": self, "model_label": block["score"], "status_chip": block["status"],
"coverage_chip": block["coverage"], "score": block["score"], "elapsed": block["elapsed"],
"agreement_bar": block["carrier"], "gauge": block["track"], "stage": block["stage"],
"failure": block["failure"], "failure_text": block["failure_text"]}
def attach_action(self, key: str, button: QWidget) -> None:
"""Host a dialog-owned action (retry) inside that model's failure row."""
self.blocks[key]["retry_slot"].addWidget(button)
def set_failure(self, key: str, message: str, retryable: bool) -> None:
block = self.blocks[key]
block["failure_text"].setText(message)
block["failure"].setVisible(bool(message))
slot = block["retry_slot"]
for index in range(slot.count()):
widget = slot.itemAt(index).widget()
if widget is not None:
widget.setVisible(retryable)
def _delta_block(self) -> QWidget:
holder = QWidget(self)
holder.setFixedWidth(180)
column = QVBoxLayout(holder)
column.setContentsMargins(0, 6, 0, 0)
column.setSpacing(2)
column.addStretch(1)
self.delta = QLabel("", holder)
self.delta.setTextFormat(Qt.TextFormat.PlainText)
self.delta.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.delta.setFont(num_font(20, weight=QFont.Weight.DemiBold))
self.delta.setStyleSheet(f"color: {CONSOLE['accent_text']}; font-size: 19.5px; font-weight: 600;")
column.addWidget(self.delta)
self.delta_note = QLabel("等待两个模型", holder)
self.delta_note.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.delta_note.setStyleSheet(f"color: {CONSOLE['faint']}; font-size: 10.2px;")
column.addWidget(self.delta_note)
self.overlap = QLabel("", holder)
self.overlap.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.overlap.setStyleSheet(f"color: {CONSOLE['faint']}; font-size: 10.2px;")
column.addWidget(self.overlap)
column.addStretch(1)
return holder
def apply(self, batch: Mapping[str, Any] | None, *, coverage: Mapping[str, str] | None = None,
elapsed: Mapping[str, str] | None = None) -> None:
data = _mapping(batch)
models = _mapping(data.get("models"))
scores = {key: _score(_mapping(models.get(key))) for key in ("qwen", "openai")}
for key in ("qwen", "openai"):
block = self.blocks[key]
value = scores[key]
block["score"].setText("" if value is None else f"{value:.1f}%")
other = scores["openai" if key == "qwen" else "qwen"]
block["track"].set_values(value, other, MODEL_NAMES["openai" if key == "qwen" else "qwen"])
text = _mapping(coverage).get(key, "")
block["coverage"].setText(text)
block["coverage"].setVisible(bool(text))
spent = _mapping(elapsed).get(key, "")
block["elapsed"].setText(spent)
block["elapsed"].setVisible(bool(spent))
if scores["qwen"] is None or scores["openai"] is None:
self.delta.setText("")
self.delta_note.setText("两个模型都可比后才给差值")
self.overlap.setText("")
return
gap = scores["qwen"] - scores["openai"]
leader = MODEL_NAMES["qwen"] if gap >= 0 else MODEL_NAMES["openai"]
self.delta.setText(f"{'+' if gap >= 0 else ''}{abs(gap):.1f}pt")
self.delta_note.setText(f"{leader}领先" if gap else "两模型持平")
overlaps = []
for key in ("qwen", "openai"):
herb = _number(_mapping(_mapping(models.get(key)).get("comparison")).get("herb_score"))
overlaps.append("" if herb is None else f"{herb:.1f}%")
self.overlap.setText("药味重合 " + " / ".join(overlaps))
class _StepButton(QPushButton):
"""A step row; the design marks the selected one with a rounded bar down its left edge."""
BAR_INSET = 9
BAR_WIDTH = 3
def paintEvent(self, event: Any) -> None: # noqa: N802 - Qt virtual
super().paintEvent(event)
if not self.isChecked():
return
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(QColor(CONSOLE["accent"]))
painter.drawRoundedRect(
QRectF(0, self.BAR_INSET, self.BAR_WIDTH, self.height() - self.BAR_INSET * 2), 1.5, 1.5)
painter.end()
class StepRail(QFrame):
"""The six destinations as numbered steps, with what this batch needs looked at below them."""
selected = Signal(str)
save_requested = Signal()
FOCUS_ROWS = (("differences", "剂量差异", "", "amber_text"),
("critical", "关键缺口", "", "rose_text"),
("restricted", "附件受限", "", "text"),
("consensus", "三方共识", "", "accent_text"))
def __init__(self, steps: tuple[tuple[str, str], ...], parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setObjectName("AiRail")
self.setFixedWidth(232)
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(12)
nav = QFrame(self)
nav.setObjectName("AiPanel")
nav_layout = QVBoxLayout(nav)
nav_layout.setContentsMargins(6, 6, 6, 6)
nav_layout.setSpacing(0)
self._tones: dict[str, str] = {}
self.buttons: dict[str, QPushButton] = {}
self.badges: dict[str, QLabel] = {}
self.numbers: dict[str, QLabel] = {}
self.names: dict[str, QLabel] = {}
for index, (key, text) in enumerate(steps, start=1):
button = _StepButton(nav)
button.setObjectName("AiStep")
button.setCheckable(True)
button.setChecked(index == 1)
button.setCursor(Qt.CursorShape.PointingHandCursor)
button.setFixedHeight(42)
button.clicked.connect(lambda _checked=False, target=key: self.selected.emit(target))
row = QHBoxLayout(button)
row.setContentsMargins(11, 0, 11, 0)
row.setSpacing(10)
number = QLabel(f"{index:02d}", button)
number.setObjectName("AiStepNumber")
number.setFixedSize(22, 22)
number.setAlignment(Qt.AlignmentFlag.AlignCenter)
number.setFont(num_font(10, weight=QFont.Weight.Bold))
row.addWidget(number)
name = QLabel(text, button)
name.setObjectName("AiStepName")
row.addWidget(name, 1)
badge = QLabel("", button)
badge.setObjectName("AiStepBadge")
badge.setAlignment(Qt.AlignmentFlag.AlignCenter)
badge.setMinimumWidth(20)
badge.setFixedHeight(17)
badge.setFont(num_font(10))
row.addWidget(badge, 0, Qt.AlignmentFlag.AlignVCenter)
self.buttons[key] = button
self.badges[key] = badge
self.numbers[key] = number
self.names[key] = name
nav_layout.addWidget(button)
layout.addWidget(nav)
self.set_current(steps[0][0] if steps else "")
focus = QFrame(self)
focus.setObjectName("AiPanel")
focus_layout = QVBoxLayout(focus)
focus_layout.setContentsMargins(12, 13, 12, 14)
focus_layout.setSpacing(7)
caption = QLabel("本次关注", focus)
caption.setStyleSheet(f"color: {CONSOLE['faint']}; font-size: 10.2px; letter-spacing: 1.4px;"
" padding-left: 4px;")
focus_layout.addWidget(caption)
self.focus_values: dict[str, QLabel] = {}
for key, text, unit, tone in self.FOCUS_ROWS:
row = QFrame(focus)
row.setObjectName("AiFocusRow")
row_layout = QHBoxLayout(row)
row_layout.setContentsMargins(10, 8, 10, 8)
row_layout.setSpacing(8)
name = QLabel(text, row)
name.setStyleSheet(f"color: {CONSOLE['muted']}; font-size: 11.25px;")
row_layout.addWidget(name)
row_layout.addStretch(1)
value = QLabel(f"{unit}", row)
value.setTextFormat(Qt.TextFormat.PlainText)
value.setFont(num_font(14))
value.setStyleSheet(f"color: {CONSOLE[tone]};")
row_layout.addWidget(value)
self.focus_values[key] = value
focus_layout.addWidget(row)
self.save_button = QPushButton("保存本次复核", focus)
self.save_button.setObjectName("AiPrimaryAction")
self.save_button.setFixedHeight(34)
self.save_button.clicked.connect(self.save_requested.emit)
focus_layout.addWidget(self.save_button)
layout.addWidget(focus)
layout.addStretch(1)
BADGE_TONES = {"hot": ("rose_dim", "rose_text"), "warn": ("amber_dim", "amber_text")}
def set_current(self, key: str) -> None:
"""The selected step is a tinted row with a blue index chip, not a solid blue button."""
for target, button in self.buttons.items():
selected = target == key
button.setChecked(selected)
self.numbers[target].setStyleSheet(
f"background: {CONSOLE['accent'] if selected else CONSOLE['raised']};"
f" color: {'#F2F7FF' if selected else CONSOLE['faint']}; border-radius: 6px;")
self.names[target].setStyleSheet(
f"color: {CONSOLE['heading'] if selected else CONSOLE['muted']}; font-size: 12.6px;"
+ (" font-weight: 600;" if selected else ""))
self._paint_badge(target)
def _paint_badge(self, key: str) -> None:
background, colour = self.BADGE_TONES.get(self._tones.get(key, ""), ("raised", "faint"))
self.badges[key].setStyleSheet(
f"background: {CONSOLE[background]}; color: {CONSOLE[colour]};"
" border-radius: 8px; padding: 0 6px;")
def set_badges(self, counts: Mapping[str, Any], tones: Mapping[str, str] | None = None) -> None:
"""Counts, and the severity that decides whether one reads as rose, amber or quiet."""
self._tones = dict(tones or {})
for key, badge in self.badges.items():
value = counts.get(key)
badge.setText("" if value in (None, "") else str(value))
badge.setVisible(bool(badge.text()))
self._paint_badge(key)
def set_focus(self, counts: Mapping[str, Any]) -> None:
for key, _text, unit, _tone in self.FOCUS_ROWS:
value = counts.get(key)
self.focus_values[key].setText(f"{'' if value is None else value} {unit}")
def rail_qss() -> str:
"""The rail and agreement styles for the palette that is active right now."""
return f"""
QFrame#AiRail {{ background: transparent; border: 0; }}
QFrame#AiFocusRow {{ background: {CONSOLE['surface_2']}; border: 0; border-radius: 7px; }}
QPushButton#AiStep {{ background: transparent; border: 1px solid transparent; border-radius: 7px;
text-align: left; }}
QPushButton#AiStep:hover {{ background: {CONSOLE['surface_2']}; }}
QPushButton#AiStep:checked {{ background: {CONSOLE['selection']};
border-color: {CONSOLE['selection_line']}; }}
QLabel#AiStepNumber {{ color: {CONSOLE['faint']}; }}
QLabel#AiStepName {{ color: {CONSOLE['muted']}; font-size: 12.6px; }}
QLabel#AiStepBadge {{ color: {CONSOLE['faint']}; background: {CONSOLE['raised']};
border-radius: 8px; padding: 0 6px; }}
QFrame#AiAgreement {{ background: {CONSOLE['surface']}; border: 1px solid {CONSOLE['line_soft']};
border-radius: 10px; }}
QLabel#AiAgreementScoreQwen {{ color: {MODEL_TEXT['qwen']}; font-size: 27px; font-weight: 600; }}
QLabel#AiAgreementScoreOpenai {{ color: {MODEL_TEXT['openai']}; font-size: 27px; font-weight: 600; }}
"""
@@ -0,0 +1,254 @@
"""Painted glyphs for the prescription analysis window.
The window ships no image assets, so every icon in the design is drawn here with QPainter at the
size it is used. Each glyph is line art on a transparent background; the colour is supplied by the
caller, which keeps a glyph usable on a card, inside a tinted tile, or on the navigation bar.
"""
from __future__ import annotations
from PySide6.QtCore import QPointF, QRectF, QSize, Qt
from PySide6.QtGui import (
QBrush,
QColor,
QIcon,
QLinearGradient,
QPainter,
QPaintEvent,
QPen,
QPixmap,
QPolygonF,
)
from PySide6.QtWidgets import QSizePolicy, QWidget
from .issued_prescription_ai_theme import CONSOLE as TECH_BLUE
def _pen(painter: QPainter, colour: str, width: float) -> QPen:
pen = QPen(QColor(colour))
pen.setWidthF(width)
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
painter.setPen(pen)
painter.setBrush(Qt.BrushStyle.NoBrush)
return pen
def paint_glyph(painter: QPainter, kind: str, box: QRectF, colour: str) -> None:
"""Draw one glyph inside ``box``. Unknown names draw nothing rather than a placeholder."""
painter.save()
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
x, y, w, h = box.x(), box.y(), box.width(), box.height()
stroke = max(1.2, min(w, h) / 11)
if kind == "bars":
_pen(painter, colour, stroke)
for index, height in enumerate((0.45, 0.75, 0.6)):
left = x + w * (0.22 + index * 0.28)
painter.drawLine(QPointF(left, y + h * 0.82), QPointF(left, y + h * (0.82 - height)))
elif kind == "doc":
_pen(painter, colour, stroke)
painter.drawRoundedRect(QRectF(x + w * 0.22, y + h * 0.12, w * 0.56, h * 0.76), w * 0.08, w * 0.08)
for index in range(2):
top = y + h * (0.38 + index * 0.2)
painter.drawLine(QPointF(x + w * 0.34, top), QPointF(x + w * 0.66, top))
elif kind == "box":
_pen(painter, colour, stroke)
top, bottom, middle = y + h * 0.2, y + h * 0.8, y + h * 0.5
left, right = x + w * 0.16, x + w * 0.84
painter.drawPolygon(QPolygonF([QPointF(x + w * 0.5, top), QPointF(right, middle * 0.75 + top * 0.25),
QPointF(right, bottom - h * 0.12), QPointF(x + w * 0.5, bottom),
QPointF(left, bottom - h * 0.12), QPointF(left, middle * 0.75 + top * 0.25)]))
painter.drawLine(QPointF(x + w * 0.5, y + h * 0.5), QPointF(x + w * 0.5, bottom))
elif kind == "image":
_pen(painter, colour, stroke)
painter.drawRoundedRect(QRectF(x + w * 0.16, y + h * 0.22, w * 0.68, h * 0.56), w * 0.08, w * 0.08)
painter.drawPolyline(QPolygonF([QPointF(x + w * 0.24, y + h * 0.7), QPointF(x + w * 0.42, y + h * 0.48),
QPointF(x + w * 0.58, y + h * 0.66), QPointF(x + w * 0.68, y + h * 0.56)]))
painter.setBrush(QColor(colour))
painter.drawEllipse(QPointF(x + w * 0.64, y + h * 0.36), stroke * 0.9, stroke * 0.9)
elif kind == "clock":
_pen(painter, colour, stroke)
painter.drawEllipse(QRectF(x + w * 0.16, y + h * 0.16, w * 0.68, h * 0.68))
centre = QPointF(x + w * 0.5, y + h * 0.5)
painter.drawLine(centre, QPointF(x + w * 0.5, y + h * 0.3))
painter.drawLine(centre, QPointF(x + w * 0.66, y + h * 0.58))
elif kind == "trend":
_pen(painter, colour, stroke)
painter.drawPolyline(QPolygonF([QPointF(x + w * 0.18, y + h * 0.68), QPointF(x + w * 0.4, y + h * 0.46),
QPointF(x + w * 0.56, y + h * 0.58), QPointF(x + w * 0.82, y + h * 0.28)]))
painter.drawPolyline(QPolygonF([QPointF(x + w * 0.62, y + h * 0.28), QPointF(x + w * 0.82, y + h * 0.28),
QPointF(x + w * 0.82, y + h * 0.48)]))
elif kind == "bell":
_pen(painter, colour, stroke)
painter.drawPolyline(QPolygonF([
QPointF(x + w * 0.24, y + h * 0.68), QPointF(x + w * 0.3, y + h * 0.58),
QPointF(x + w * 0.3, y + h * 0.42), QPointF(x + w * 0.5, y + h * 0.2),
QPointF(x + w * 0.7, y + h * 0.42), QPointF(x + w * 0.7, y + h * 0.58),
QPointF(x + w * 0.76, y + h * 0.68), QPointF(x + w * 0.24, y + h * 0.68)]))
painter.drawArc(QRectF(x + w * 0.4, y + h * 0.66, w * 0.2, h * 0.18), 0, -180 * 16)
elif kind == "clipboard":
_pen(painter, colour, stroke)
painter.drawRoundedRect(QRectF(x + w * 0.22, y + h * 0.2, w * 0.56, h * 0.66), w * 0.08, w * 0.08)
painter.drawLine(QPointF(x + w * 0.36, y + h * 0.48), QPointF(x + w * 0.64, y + h * 0.48))
painter.drawLine(QPointF(x + w * 0.36, y + h * 0.64), QPointF(x + w * 0.56, y + h * 0.64))
elif kind == "pencil":
_pen(painter, colour, stroke)
painter.drawPolyline(QPolygonF([QPointF(x + w * 0.24, y + h * 0.76), QPointF(x + w * 0.28, y + h * 0.6),
QPointF(x + w * 0.64, y + h * 0.24), QPointF(x + w * 0.78, y + h * 0.38),
QPointF(x + w * 0.42, y + h * 0.74), QPointF(x + w * 0.24, y + h * 0.76)]))
elif kind == "bulb":
_pen(painter, colour, stroke)
painter.drawArc(QRectF(x + w * 0.28, y + h * 0.18, w * 0.44, h * 0.46), 0, 180 * 16)
painter.drawLine(QPointF(x + w * 0.28, y + h * 0.41), QPointF(x + w * 0.38, y + h * 0.62))
painter.drawLine(QPointF(x + w * 0.72, y + h * 0.41), QPointF(x + w * 0.62, y + h * 0.62))
painter.drawLine(QPointF(x + w * 0.38, y + h * 0.66), QPointF(x + w * 0.62, y + h * 0.66))
painter.drawLine(QPointF(x + w * 0.42, y + h * 0.78), QPointF(x + w * 0.58, y + h * 0.78))
elif kind == "flask":
_pen(painter, colour, stroke)
painter.drawLine(QPointF(x + w * 0.38, y + h * 0.2), QPointF(x + w * 0.62, y + h * 0.2))
painter.drawPolyline(QPolygonF([QPointF(x + w * 0.44, y + h * 0.2), QPointF(x + w * 0.44, y + h * 0.44),
QPointF(x + w * 0.24, y + h * 0.78), QPointF(x + w * 0.76, y + h * 0.78),
QPointF(x + w * 0.56, y + h * 0.44), QPointF(x + w * 0.56, y + h * 0.2)]))
elif kind == "alert":
_pen(painter, colour, stroke)
painter.drawEllipse(QRectF(x + w * 0.16, y + h * 0.16, w * 0.68, h * 0.68))
painter.drawLine(QPointF(x + w * 0.5, y + h * 0.33), QPointF(x + w * 0.5, y + h * 0.56))
painter.setBrush(QColor(colour))
painter.drawEllipse(QPointF(x + w * 0.5, y + h * 0.68), stroke * 0.7, stroke * 0.7)
elif kind == "paperclip":
_pen(painter, colour, stroke)
painter.drawRoundedRect(QRectF(x + w * 0.18, y + h * 0.22, w * 0.64, h * 0.5), w * 0.1, w * 0.1)
painter.drawLine(QPointF(x + w * 0.3, y + h * 0.72), QPointF(x + w * 0.3, y + h * 0.84))
elif kind == "info":
_pen(painter, colour, stroke)
painter.drawEllipse(QRectF(x + w * 0.16, y + h * 0.16, w * 0.68, h * 0.68))
painter.drawLine(QPointF(x + w * 0.5, y + h * 0.46), QPointF(x + w * 0.5, y + h * 0.68))
painter.setBrush(QColor(colour))
painter.drawEllipse(QPointF(x + w * 0.5, y + h * 0.34), stroke * 0.7, stroke * 0.7)
elif kind == "refresh":
_pen(painter, colour, stroke)
painter.drawArc(QRectF(x + w * 0.2, y + h * 0.2, w * 0.6, h * 0.6), 40 * 16, 280 * 16)
painter.setBrush(QColor(colour))
painter.drawPolygon(QPolygonF([QPointF(x + w * 0.72, y + h * 0.12), QPointF(x + w * 0.86, y + h * 0.34),
QPointF(x + w * 0.6, y + h * 0.32)]))
elif kind == "save":
_pen(painter, colour, stroke)
painter.drawRoundedRect(QRectF(x + w * 0.2, y + h * 0.2, w * 0.6, h * 0.6), w * 0.08, w * 0.08)
painter.drawLine(QPointF(x + w * 0.36, y + h * 0.2), QPointF(x + w * 0.36, y + h * 0.42))
painter.drawLine(QPointF(x + w * 0.36, y + h * 0.42), QPointF(x + w * 0.64, y + h * 0.42))
painter.drawLine(QPointF(x + w * 0.64, y + h * 0.42), QPointF(x + w * 0.64, y + h * 0.2))
painter.drawRect(QRectF(x + w * 0.36, y + h * 0.56, w * 0.28, h * 0.24))
elif kind == "chevron":
_pen(painter, colour, stroke)
painter.drawPolyline(QPolygonF([QPointF(x + w * 0.4, y + h * 0.28), QPointF(x + w * 0.62, y + h * 0.5),
QPointF(x + w * 0.4, y + h * 0.72)]))
elif kind == "shield":
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(QColor(colour))
painter.drawPolygon(QPolygonF([QPointF(x + w * 0.5, y + h * 0.16), QPointF(x + w * 0.82, y + h * 0.3),
QPointF(x + w * 0.82, y + h * 0.56), QPointF(x + w * 0.5, y + h * 0.84),
QPointF(x + w * 0.18, y + h * 0.56), QPointF(x + w * 0.18, y + h * 0.3)]))
elif kind == "check":
_pen(painter, colour, stroke * 1.2)
painter.drawPolyline(QPolygonF([QPointF(x + w * 0.32, y + h * 0.52), QPointF(x + w * 0.45, y + h * 0.65),
QPointF(x + w * 0.7, y + h * 0.37)]))
elif kind == "spark":
# The 千问 mark: three crossing strokes forming a six-pointed star.
_pen(painter, colour, stroke * 1.1)
centre = QPointF(x + w * 0.5, y + h * 0.5)
radius = min(w, h) * 0.3
for angle in (90, 30, -30):
from math import cos, radians, sin
dx, dy = cos(radians(angle)) * radius, -sin(radians(angle)) * radius
painter.drawLine(QPointF(centre.x() - dx, centre.y() - dy), QPointF(centre.x() + dx, centre.y() + dy))
elif kind == "knot":
# The OpenAI mark, reduced to the interlocking hexagon it is built from.
_pen(painter, colour, stroke)
from math import cos, radians, sin
centre = QPointF(x + w * 0.5, y + h * 0.5)
radius = min(w, h) * 0.3
points = [QPointF(centre.x() + cos(radians(angle)) * radius, centre.y() + sin(radians(angle)) * radius)
for angle in range(0, 360, 60)]
painter.drawPolygon(QPolygonF(points))
painter.drawLine(points[0], points[3])
painter.restore()
class Glyph(QWidget):
"""A single painted icon at a fixed size."""
def __init__(self, kind: str, colour: str, size: int = 18, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.kind, self.colour = kind, colour
self.setFixedSize(size, size)
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
def set_colour(self, colour: str) -> None:
if colour != self.colour:
self.colour = colour
self.update()
def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802 - Qt virtual
painter = QPainter(self)
paint_glyph(painter, self.kind, QRectF(0, 0, self.width(), self.height()), self.colour)
painter.end()
class LogoTile(QWidget):
"""A rounded tile with a glyph on it: the window mark and the two model marks."""
def __init__(self, kind: str, *, start: str, end: str, glyph: str = "#FFFFFF",
size: int = 34, radius: float = 10.0, circle: bool = False,
parent: QWidget | None = None) -> None:
super().__init__(parent)
self.kind, self.start, self.end, self.glyph_colour = kind, start, end, glyph
self.radius, self.circle = radius, circle
self.setFixedSize(size, size)
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802 - Qt virtual
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
box = QRectF(0, 0, self.width(), self.height())
gradient = QLinearGradient(box.topLeft(), box.bottomRight())
gradient.setColorAt(0.0, QColor(self.start))
gradient.setColorAt(1.0, QColor(self.end))
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(QBrush(gradient))
if self.circle:
painter.drawEllipse(box)
else:
painter.drawRoundedRect(box, self.radius, self.radius)
paint_glyph(painter, self.kind, box, self.glyph_colour)
painter.end()
def minimumSizeHint(self) -> QSize:
return QSize(self.width(), self.height())
def window_mark(parent: QWidget | None = None, size: int = 38) -> LogoTile:
tile = LogoTile("check", start=TECH_BLUE["accent"], end=TECH_BLUE["accent_pressed"],
size=size, radius=11, parent=parent)
tile.setAccessibleName("诊断与药方对照")
return tile
def model_mark(model_key: str, parent: QWidget | None = None, size: int = 30) -> LogoTile:
if model_key == "openai":
return LogoTile("knot", start=TECH_BLUE["openai_dim"], end=TECH_BLUE["surface_2"],
glyph=TECH_BLUE["openai_text"], size=size, circle=True, parent=parent)
return LogoTile("spark", start=TECH_BLUE["qwen_dim"], end=TECH_BLUE["surface_2"],
glyph=TECH_BLUE["qwen_text"], size=size, radius=9, parent=parent)
def glyph_icon(kind: str, colour: str, size: int = 16) -> QIcon:
"""The same line art as a QIcon, for buttons that place their own icon and label."""
pixmap = QPixmap(size, size)
pixmap.fill(Qt.GlobalColor.transparent)
painter = QPainter(pixmap)
paint_glyph(painter, kind, QRectF(0, 0, size, size), colour)
painter.end()
return QIcon(pixmap)
@@ -283,3 +283,61 @@ def value_text(value: Any, field: str, labels: Mapping[str, str], fields: Mappin
if field and field not in fields: if field and field not in fields:
return system_text(value, labels, fields) return system_text(value, labels, fields)
return plain_text(value) return plain_text(value)
STATE_LABELS = {
"blank": "尚未开方", "not_generated": "尚无分析记录", "unavailable": "暂无可比结果",
"not_applicable": "尚未开方", "not_started": "尚未分析", "pending": "待分析",
"preparing": "准备资料", "waiting_sources": "等待转写/资料", "retry_wait": "等待重试", "blocked": "需完善资料关联",
"queued": "待分析", "waiting": "等待资料", "waiting_transcript": "等待转写",
"waiting_transcription": "等待转写", "running": "分析中", "processing": "分析中",
"retrying": "重试中", "succeeded": "已完成", "completed": "已完成", "success": "已完成",
"partial": "部分完成", "failed": "需重试", "cancelled": "已取消", "canceled": "已取消",
"stale": "处方已变更", "superseded": "处方已变更", "invalid": "已失效",
"prescription_changed": "处方已变更", "source_updated": "资料已更新", "voided": "处方已作废", "deleted": "处方已删除", "revoked": "权限已撤销",
"current": "当前版本", "valid": "当前有效", "complete": "资料清单完整",
"incomplete": "资料不全", "missing": "资料缺失", "unknown": "未确认",
"needs_patient_link": "需完善患者关联", "patient_unlinked": "需完善患者关联",
"independent_baseline": "独立基线", "baseline": "独立基线",
"latest_context": "最新资料对照", "supplemental": "最新资料对照",
"assisted_revision": "AI 辅助后修订", "ai_assisted": "AI 辅助后修订",
"non_independent": "非独立对照", "auxiliary": "辅助复核",
"comparable": "可比", "not_comparable": "不可比",
"available_for_review": "供医生复核", "insufficient_data": "资料不足,暂不提供候选用药",
"withheld_for_risk": "因风险暂缓候选用药", "viewed": "已查看", "needs_information": "需补充资料",
"not_adopted": "不采纳", "reviewed": "已复核",
"per_dose": "每剂", "per_day": "每日", "matched": "共同药味", "doctor_only": "仅医生方", "candidate_only": "仅模型方",
"insufficient_sample": "样本不足", "descriptive_only": "仅作描述性统计",
}
FIELD_LABELS = {
"summary": "概要", "timeline": "病程", "analysis": "综合分析", "tcm_analysis": "中医辨证",
"diagnosis": "辨证分析", "treatment_advice": "治疗与随访建议", "risk_assessment": "需复核风险",
"evidence_references": "证据来源编号", "missing_information": "待补充资料", "level": "风险等级", "label": "说明",
"risk_warnings": "需复核风险", "risks": "风险", "follow_up": "随访建议", "evidence": "依据",
"sources": "来源", "manifest": "来源清单", "missing": "资料缺口", "status": "状态",
"reason": "原因", "name": "药名", "herb_name": "规范药名", "canonical_name": "规范药名",
"processing": "炮制", "dosage": "剂量", "dose": "剂量", "unit": "单位", "dose_basis": "剂量基准",
"formula_type": "主辅方", "doctor_dosage": "医生剂量", "candidate_dosage": "模型剂量",
"doctor_dose": "医生剂量", "candidate_dose": "模型剂量", "ai_dose": "模型剂量",
"contribution": "匹配贡献", "ratio": "匹配贡献", "match_ratio": "匹配贡献", "match": "匹配情况",
"prescription_name": "候选方名称", "prescription_type": "剂型", "herbs": "药味",
"dose_count": "剂数", "usage_days": "疗程(天)", "times_per_day": "每日服次",
"usage_instruction": "服法", "usage_time": "服药时间", "usage_way": "给药途径",
"rationale": "方义与依据", "usage_differences": "用法、疗程与风险差异", "normalization": "规范化记录",
"algorithm_version": "算法版本", "dictionary_version": "药材字典版本", "model_version": "模型版本",
"prompt_version": "提示词版本", "doctor_count": "医生药项数", "candidate_count": "候选药项数",
"matched_count": "共同药项数", "coverage": "模型资料覆盖", "source_summary": "来源汇总",
"cutoff_at": "资料截止时间", "generated_at": "报告生成时间", "comment": "复核意见",
"match_type": "增减药项", "administration_route": "给药途径", "group": "用药组",
"delivered": "已送达", "unreadable": "不可读", "unsupported": "不支持", "parsed": "已解析",
"diagnosis_count": "病历数", "prescription_count": "历史处方数", "chat_count": "聊天记录数",
"daily_record_count": "日常记录数", "transcript_count": "转写数", "attachment_count": "附件数",
"files": "附件处理清单", "source_ids": "已读取来源编号", "source_id": "来源编号", "file_id": "附件编号",
"complete": "资料清单完整", "source_complete": "文字来源齐全", "transmitted": "附件已送达", "critical": "关键资料缺口",
"baseline_eligible": "独立基线统计资格", "baseline_exclusion_reasons": "基线排除原因", "instructions": "特殊煎服说明",
"versions": "版本信息", "strata": "按版本分层", "count": "样本数", "mean": "均值", "median": "中位数",
"distribution": "一致度分布", "sample_status": "样本说明",
}
STATE_LABELS.update(SYSTEM_LABELS)
FIELD_LABELS.update(EXTRA_FIELD_LABELS)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,210 @@
"""The analysis console's palette, and the switch between its dark and light themes.
The prescription analysis window has its own theme, separate from the workstation's light chrome:
tech blue for the interface itself, and two model hues (cyan for 千问, violet for OpenAI) that stay
apart from the interface colour and from each other. Severity keeps amber and rose, so a reading
never depends on hue alone.
``CONSOLE`` is the active palette and is mutated in place by :func:`use_theme`, so every module
that imported it sees the new values. Anything derived from it a stylesheet string, a colour
constant must be rebuilt in a callback registered through :func:`on_theme_changed`.
Key names mirror ``reception_style.TECH_BLUE`` so a widget can take either palette.
"""
from __future__ import annotations
import ctypes
import sys
from collections.abc import Callable
from typing import Any
from PySide6.QtGui import QFont
DARK: dict[str, str] = {
# ground and cards
"canvas": "#080D18",
"canvas_soft": "#0C1322",
"surface": "#0F1726",
"surface_2": "#131D2E",
"raised": "#1A2637",
"line": "#22304A",
"line_soft": "#18233A",
# type
"heading": "#E8EFFB",
"text": "#B6C5DA",
"muted": "#8195AF",
"faint": "#6A7F99",
"selected_text": "#E8EFFB",
# interface colour
"accent": "#2E7BF6",
"accent_text": "#6BA5FF",
"accent_pressed": "#1A5FD0",
"selection": "#142645",
"selection_line": "#1B3E77",
# model hues
"qwen": "#17BFDD",
"qwen_text": "#55D8EE",
"qwen_dim": "#10303C",
"openai": "#9B7BFF",
"openai_text": "#B69FFF",
"openai_dim": "#241F48",
# severity
"amber": "#F5B942",
"amber_text": "#F8C661",
"amber_dim": "#2C2415",
"rose": "#F4697A",
"rose_text": "#F88694",
"rose_dim": "#2C1620",
"ok": "#2E7BF6",
"zebra": "#0E1626",
"grid_line": "#191F2C",
}
LIGHT: dict[str, str] = {
"canvas": "#F1F5FC",
"canvas_soft": "#E7EEFA",
"surface": "#FFFFFF",
"surface_2": "#F5F8FE",
"raised": "#E8EFFA",
"line": "#D5E1F2",
"line_soft": "#E3EBF8",
"heading": "#0A182E",
"text": "#2A3C56",
"muted": "#54697F",
"faint": "#7A8DA3",
"selected_text": "#0A182E",
"accent": "#1A5FD0",
"accent_text": "#124DAE",
"accent_pressed": "#0E3F90",
"selection": "#E8EFFA",
"selection_line": "#AEC7EE",
"qwen": "#0C89AC",
"qwen_text": "#086C88",
"qwen_dim": "#E2F5FA",
"openai": "#6B4AE0",
"openai_text": "#5537C6",
"openai_dim": "#EDE8FE",
"amber": "#B4791A",
"amber_text": "#8F5F0B",
"amber_dim": "#FCF2DF",
"rose": "#D0435A",
"rose_text": "#AE3149",
"rose_dim": "#FCE9EC",
"ok": "#1A5FD0",
"zebra": "#FAFBFE",
"grid_line": "#DDE4EE",
}
CONSOLE: dict[str, str] = dict(DARK)
_THEME = "dark"
_LISTENERS: list[Callable[[], None]] = []
def current_theme() -> str:
return _THEME
def on_theme_changed(callback: Callable[[], None]) -> Callable[[], None]:
"""Register a rebuild for anything derived from the palette; returns the callback."""
_LISTENERS.append(callback)
return callback
def use_theme(name: str) -> str:
"""Switch the active palette in place and let every derived value rebuild itself."""
global _THEME
palette = LIGHT if name == "light" else DARK
_THEME = "light" if name == "light" else "dark"
CONSOLE.clear()
CONSOLE.update(palette)
_rebuild()
return _THEME
def toggle_theme() -> str:
return use_theme("light" if _THEME == "dark" else "dark")
def _rebuild() -> None:
for callback in list(_LISTENERS):
callback()
MODEL_HUE = {"qwen": CONSOLE["qwen"], "openai": CONSOLE["openai"]}
MODEL_TEXT = {"qwen": CONSOLE["qwen_text"], "openai": CONSOLE["openai_text"]}
MODEL_DIM = {"qwen": CONSOLE["qwen_dim"], "openai": CONSOLE["openai_dim"]}
@on_theme_changed
def _rebuild_model_hues() -> None:
MODEL_HUE.update({"qwen": CONSOLE["qwen"], "openai": CONSOLE["openai"]})
MODEL_TEXT.update({"qwen": CONSOLE["qwen_text"], "openai": CONSOLE["openai_text"]})
MODEL_DIM.update({"qwen": CONSOLE["qwen_dim"], "openai": CONSOLE["openai_dim"]})
RADIUS = 10
RADIUS_SM = 7
# The design sets numbers in a condensed face so columns of figures line up; the fallbacks keep
# the same tabular behaviour when Bahnschrift is missing.
NUM_FAMILIES = ("Bahnschrift", "Segoe UI", "Microsoft YaHei UI", "Microsoft YaHei")
def num_font(size: int, *, weight: QFont.Weight | None = None) -> QFont:
"""A tabular face for figures, at the pixel size the design states."""
font = QFont()
font.setFamilies(list(NUM_FAMILIES))
font.setPixelSize(size)
if weight is not None:
font.setWeight(weight)
font.setStyleStrategy(QFont.StyleStrategy.PreferAntialias)
return font
# Windows draws the title bar itself, so the console's dark ground stops at the frame unless the
# window asks DWM for a matching caption. Windows 11 (build 22000+) honours these attributes;
# anywhere else the call fails and the native bar is left as it is.
_DWMWA_USE_IMMERSIVE_DARK_MODE = 20
_DWMWA_BORDER_COLOR = 34
_DWMWA_CAPTION_COLOR = 35
_DWMWA_TEXT_COLOR = 36
def _colorref(value: str) -> int:
"""A ``#RRGGBB`` string as the ``0x00BBGGRR`` integer DWM expects."""
colour = value.lstrip("#")
red, green, blue = (int(colour[index:index + 2], 16) for index in (0, 2, 4))
return (blue << 16) | (green << 8) | red
def apply_window_chrome(widget: Any) -> bool:
"""Paint the native title bar in the palette that is active right now.
Returns whether DWM accepted the change, so a caller can tell "not Windows 11" from "done".
"""
if sys.platform != "win32":
return False
handle = int(widget.winId())
if not handle:
return False
try:
dwm = ctypes.windll.dwmapi
except (AttributeError, OSError): # pragma: no cover - not Windows
return False
dark = ctypes.c_int(1 if _THEME == "dark" else 0)
caption = ctypes.c_uint(_colorref(CONSOLE["canvas"]))
text = ctypes.c_uint(_colorref(CONSOLE["heading"]))
border = ctypes.c_uint(_colorref(CONSOLE["line"]))
applied = False
for attribute, value in ((_DWMWA_USE_IMMERSIVE_DARK_MODE, dark), (_DWMWA_CAPTION_COLOR, caption),
(_DWMWA_TEXT_COLOR, text), (_DWMWA_BORDER_COLOR, border)):
result = dwm.DwmSetWindowAttribute(ctypes.c_void_p(handle), ctypes.c_int(attribute),
ctypes.byref(value), ctypes.sizeof(value))
applied = applied or result == 0
return applied
File diff suppressed because it is too large Load Diff
@@ -445,6 +445,21 @@ def _formula(value: Any) -> str:
return "辅方" if text in {"2", "aux", "auxiliary", "辅方"} else "主方" return "辅方" if text in {"2", "aux", "auxiliary", "辅方"} else "主方"
def _is_blank_prescription(row: Any) -> bool:
if row is None or _truthy(get_value(row, "is_system_auto")):
return True
raw = getattr(row, "raw", None)
source = raw if isinstance(raw, Mapping) and raw else row
missing = object()
herbs = get_value(source, "herbs", missing)
# Compact historical rows may omit herbs; omission is not an empty prescription.
if herbs is missing:
return False
return not isinstance(herbs, (list, tuple)) or not any(
str(get_value(herb, "name", "") or "").strip() for herb in herbs
)
def _order_warnings(row: Any) -> list[str]: def _order_warnings(row: Any) -> list[str]:
"""Match the PC list's linked-order checks, including blank herb rows.""" """Match the PC list's linked-order checks, including blank herb rows."""
@@ -1183,7 +1198,7 @@ class PrescriptionsPage(QWidget):
self.view_button = self._action_button("查看", "cf.prescription/read", self._view_selected) self.view_button = self._action_button("查看", "cf.prescription/read", self._view_selected)
toolbar.addWidget(self.view_button) toolbar.addWidget(self.view_button)
self.ai_report_button = self._action_button("AI 报告", "tcm.prescriptionAi/reports", self._open_ai_report) self.ai_report_button = self._action_button("AI 报告", "tcm.prescriptionAi/reports", self._open_ai_report)
self.ai_report_button.setVisible(can_open_issued_ai(self.permissions) and callable(getattr(self.repository, "list_prescription_ai_reports", None))) self.ai_report_button.hide()
toolbar.addWidget(self.ai_report_button) toolbar.addWidget(self.ai_report_button)
self.patch_button = self._action_button( self.patch_button = self._action_button(
"修改患者", "tcm.prescription/patchPatient", self._patch_selected "修改患者", "tcm.prescription/patchPatient", self._patch_selected
@@ -1565,7 +1580,7 @@ class PrescriptionsPage(QWidget):
actions_host, actions_host,
) )
) )
if self._ai_enabled and can_open_issued_ai(self.permissions): if self._ai_enabled and can_open_issued_ai(self.permissions) and not _is_blank_prescription(row):
actions.addWidget( actions.addWidget(
_row_action_button( _row_action_button(
"eye", "AI 报告", "eye", "AI 报告",
@@ -1618,6 +1633,8 @@ class PrescriptionsPage(QWidget):
if item is None or top + self.table.rowHeight(index) <= 0 or top >= viewport.height(): if item is None or top + self.table.rowHeight(index) <= 0 or top >= viewport.height():
continue continue
row = item.data(Qt.ItemDataRole.UserRole) row = item.data(Qt.ItemDataRole.UserRole)
if _is_blank_prescription(row):
continue
value = _int(first_value(row, "id", "prescription_id"), 0) value = _int(first_value(row, "id", "prescription_id"), 0)
if value > 0: if value > 0:
ids.append(value) ids.append(value)
@@ -1628,6 +1645,15 @@ class PrescriptionsPage(QWidget):
self._ai_scroll_timer.start() self._ai_scroll_timer.start()
def _load_ai_statuses(self) -> None: def _load_ai_statuses(self) -> None:
only_blank = self.table.rowCount() > 0 and all(
_is_blank_prescription(self.table.item(index, 0).data(Qt.ItemDataRole.UserRole))
for index in range(self.table.rowCount())
)
self.ai_status_notice.setVisible(not only_blank)
if only_blank:
self._ai_timer.stop()
self._set_ai_columns(False)
return
method = getattr(self.repository, "list_prescription_ai_statuses", None) method = getattr(self.repository, "list_prescription_ai_statuses", None)
if not has_permission(self.permissions, "tcm.prescriptionAi/statuses", default=False): if not has_permission(self.permissions, "tcm.prescriptionAi/statuses", default=False):
self.ai_status_notice.setText("AI 分析:当前账号没有查看分析状态的权限,请联系管理员授权。") self.ai_status_notice.setText("AI 分析:当前账号没有查看分析状态的权限,请联系管理员授权。")
@@ -1711,15 +1737,17 @@ class PrescriptionsPage(QWidget):
row = self.table.item(index, 0).data(Qt.ItemDataRole.UserRole) row = self.table.item(index, 0).data(Qt.ItemDataRole.UserRole)
value = _int(first_value(row, "id", "prescription_id"), 0) value = _int(first_value(row, "id", "prescription_id"), 0)
batch = self._ai_statuses.get(value) batch = self._ai_statuses.get(value)
if batch is None: blank = _is_blank_prescription(row)
if batch is None and not blank:
continue continue
state = state_text(batch) if batch else ("尚未开方" if _truthy(get_value(row, "is_system_auto")) else "尚无分析记录") state = "" if blank else (state_text(batch) if batch else "尚无分析记录")
for column, text in ((11, state), (12, agreement_text(batch))): agreement = "" if blank else agreement_text(batch)
for column, text in ((11, state), (12, agreement)):
item = self.table.item(index, column) item = self.table.item(index, column)
if item.text() != text: if item.text() != text:
item.setText(text) item.setText(text)
changed = True changed = True
item.setToolTip(status_tooltip(batch)) item.setToolTip("" if blank else status_tooltip(batch))
item.setData(Qt.ItemDataRole.AccessibleTextRole, text) item.setData(Qt.ItemDataRole.AccessibleTextRole, text)
if changed: if changed:
self.table.resizeRowsToContents() self.table.resizeRowsToContents()
@@ -1736,6 +1764,8 @@ class PrescriptionsPage(QWidget):
def _open_ai_report(self) -> None: def _open_ai_report(self) -> None:
row = self._selected() row = self._selected()
if _is_blank_prescription(row):
return
value = _int(first_value(row, "id", "prescription_id"), 0) value = _int(first_value(row, "id", "prescription_id"), 0)
if value > 0: if value > 0:
present_issued_prescription_ai(self.repository, self.permissions, self, prescription_id=value) present_issued_prescription_ai(self.repository, self.permissions, self, prescription_id=value)
@@ -1748,6 +1778,8 @@ class PrescriptionsPage(QWidget):
return return
self.table.selectRow(item.row()) self.table.selectRow(item.row())
row = self._selected() row = self._selected()
if _is_blank_prescription(row):
return
menu = QMenu(self.table) menu = QMenu(self.table)
action = menu.addAction("AI 报告 / 逐味对照") action = menu.addAction("AI 报告 / 逐味对照")
action.triggered.connect(lambda: self._run_row_action(row, self._open_ai_report)) action.triggered.connect(lambda: self._run_row_action(row, self._open_ai_report))
@@ -1795,7 +1827,13 @@ class PrescriptionsPage(QWidget):
row = self.table.current_data() row = self.table.current_data()
active = not self._mutation_pending active = not self._mutation_pending
self.view_button.setEnabled(active and row is not None) self.view_button.setEnabled(active and row is not None)
self.ai_report_button.setEnabled(row is not None) ai_available = (
not _is_blank_prescription(row)
and can_open_issued_ai(self.permissions)
and callable(getattr(self.repository, "list_prescription_ai_reports", None))
)
self.ai_report_button.setVisible(ai_available)
self.ai_report_button.setEnabled(ai_available)
self.patch_button.setEnabled(active and can_patch_patient(row)) self.patch_button.setEnabled(active and can_patch_patient(row))
self.create_order_button.setEnabled(active and can_create_order(row)) self.create_order_button.setEnabled(active and can_create_order(row))
self.edit_button.setEnabled(active and can_edit_or_delete(row)) self.edit_button.setEnabled(active and can_edit_or_delete(row))
+387 -4
View File
@@ -11,8 +11,8 @@ from uuid import UUID
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
import pytest import pytest
from PySide6.QtCore import Qt from PySide6.QtCore import QEvent, QObject, QRect, Qt
from PySide6.QtWidgets import QApplication, QPushButton from PySide6.QtWidgets import QApplication, QPushButton, QWidget
from doctor_workstation.services import DemoDoctorRepository from doctor_workstation.services import DemoDoctorRepository
from doctor_workstation.services.repository import RemoteDoctorRepository from doctor_workstation.services.repository import RemoteDoctorRepository
@@ -138,6 +138,215 @@ def test_shared_report_reads_only_and_renders_each_model_independently(applicati
assert repository.calls == calls assert repository.calls == calls
def test_report_open_and_poll_never_show_auxiliary_windows(application: QApplication, immediate: None) -> None:
shown_windows = []
class WindowObserver(QObject):
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
if event.type() == QEvent.Type.Show and isinstance(watched, QWidget) and watched.isWindow():
shown_windows.append((watched.metaObject().className(), watched.windowTitle()))
return False
observer = WindowObserver()
application.installEventFilter(observer)
dialog = None
try:
repository = Repository()
dialog = ai.IssuedPrescriptionAiDialog(repository, ["*"], prescription_id=801)
dialog.show()
application.processEvents()
initial_windows = list(shown_windows)
shown_windows.clear()
dialog.tabs.setCurrentIndex(3)
dialog.model_views["qwen"]["comment"].setPlainText("正在填写的复核意见")
for _ in range(3):
dialog._timer.timeout.emit()
dialog._progress_timer.timeout.emit()
application.processEvents()
assert len([call for call in repository.calls if call[0] == "detail"]) == 4
assert shown_windows == [], "Polling must not show even transient top-level widgets"
assert initial_windows == [("IssuedPrescriptionAiDialog", dialog.windowTitle())]
assert dialog.tabs.currentIndex() == 3
assert dialog.model_views["qwen"]["comment"].toPlainText() == "正在填写的复核意见"
assert dialog.chip_row.count() == 2
assert all(dialog.chip_row.itemAt(index).widget().isVisible() for index in range(2))
assert dialog._timer.isActive() and dialog._progress_timer.isActive()
finally:
application.removeEventFilter(observer)
if dialog is not None:
dialog.close()
def test_navigation_names_every_destination_once(application: QApplication, immediate: None) -> None:
"""A duplicated tab name makes the navigation unreadable; the live progress lives in the one tab."""
dialog = ai.IssuedPrescriptionAiDialog(Repository(), ["*"], prescription_id=801)
dialog.show()
application.processEvents()
names = [dialog.tabs.tabText(index) for index in range(dialog.tabs.count())]
assert len(names) == len(set(names)), names
assert names.count("处理进度") == 1
# Those pages are hosted in a scroll area, so the tab holds the host, not the page itself.
for key in ("per_herb", "gaps", "history"):
assert dialog.tabs.indexOf(dialog.tab_pages[key]) >= 0
bar = dialog.model_views["qwen"]["progress_bar"]
assert dialog.pipeline_page.isAncestorOf(bar)
assert dialog.pipeline_page.stage_labels["qwen"].isHidden()
dialog.close()
@pytest.mark.parametrize(("width", "height"), [(1280, 860), (1024, 700), (940, 640)])
def test_prescription_workspace_is_the_primary_view(application: QApplication, immediate: None, width: int, height: int) -> None:
dialog = ai.IssuedPrescriptionAiDialog(Repository(), ["*"], prescription_id=801)
dialog.resize(width, height)
dialog.show()
application.processEvents()
assert (dialog.width(), dialog.height()) == (width, height)
assert dialog.tabs.currentWidget() is dialog.comparison_panel
assert dialog.tabs.tabText(0) == "对比总览"
assert dialog.comparison_panel.isVisible()
assert dialog.tabs.height() > height * 0.28
assert dialog.model_views["qwen"]["comment"].isVisible()
# The chart strip is on the first screen but must never outgrow its budget, and a model
# without a comparable score shows no filled bar.
assert dialog.model_views["qwen"]["agreement_bar"].value() == 0
assert dialog.model_views["openai"]["agreement_bar"].isHidden()
assert dialog.model_views["qwen"]["gauge"].accessibleDescription() == "0.0%"
assert dialog.model_views["openai"]["gauge"].accessibleDescription() == "暂无可比结果"
dialog.close()
@pytest.mark.parametrize(("width", "height"), [(1440, 940), (1280, 860), (1024, 700), (940, 640)])
def test_review_controls_remain_inside_the_sidebar(application: QApplication, immediate: None,
width: int, height: int) -> None:
repository = Repository()
model = repository.batches[0]["models"]["qwen"]
herb = model["candidate"]["herbs"][0]
model["comparison"]["rows"][0].update(key="黄芪", doctor={**herb, "dosage": 16}, candidate=herb)
dialog = ai.IssuedPrescriptionAiDialog(repository, ["*"], prescription_id=801)
dialog.resize(width, height)
dialog.show()
panel = dialog.comparison_panel
for _ in range(4):
application.processEvents()
assert dialog.nav_buttons["overview"].isChecked()
for key in ai.MODELS:
dialog.review_model.setCurrentIndex(0 if key == "qwen" else 1)
application.processEvents()
views = dialog.model_views[key]
for control in (dialog.review_model, views["review_state"], views["comment"], views["save"]):
assert control.isVisible()
bounds = QRect(control.mapTo(panel.checklist, control.rect().topLeft()), control.size())
assert panel.checklist.rect().contains(bounds), (key, control.accessibleName(), bounds)
assert ai.MODELS[key] in dialog.save_review_button.toolTip()
assert panel.checklist.isVisible()
dialog.close()
def test_hero_band_carries_the_frozen_identity_and_yields_on_short_windows(application: QApplication, immediate: None) -> None:
repository = Repository()
repository.batches[0]["doctor_snapshot"] = {
"patient": {"name": "测试患者", "gender_label": "", "age": 58},
"diagnosis": {"clinical_diagnosis": "消渴病 气阴两虚"},
"prescription": {"prescription_type": "浓缩水丸", "dose_count": 1, "dose_unit": ""},
}
dialog = ai.IssuedPrescriptionAiDialog(repository, ["*"], prescription_id=801)
dialog.resize(1280, 860)
dialog.show()
application.processEvents()
assert "测试患者 · 女 · 58 岁" in dialog.identity.text()
assert "消渴病 气阴两虚" in dialog.identity.text()
assert "浓缩水丸 · 1 剂" in dialog.identity.text()
assert dialog.fact_values["basis"].text() == "同单位 · 每剂"
assert dialog.fact_values["prescription"].text() == "801 / 501"
dialog.resize(1024, 700)
application.processEvents()
assert dialog.fact_values["batch"].text() == "#40"
dialog.close()
def test_hero_band_shows_dashes_when_the_snapshot_is_not_deployed(application: QApplication, immediate: None) -> None:
repository = Repository()
repository.batches[0].pop("doctor_snapshot", None)
dialog = ai.IssuedPrescriptionAiDialog(repository, ["*"], prescription_id=801)
dialog.resize(1280, 860)
dialog.show()
application.processEvents()
assert dialog.identity.text() == "尚无冻结快照"
assert dialog.fact_values["prescription"].text().startswith("801")
dialog.close()
def test_failed_model_states_the_reason_and_offers_its_own_retry(application: QApplication, immediate: None) -> None:
repository = Repository()
repository.batches[0]["models"]["openai"].update(status="failed", error_code="INVALID_REPORT_OUTPUT",
error_message="INVALID_REPORT_OUTPUT")
dialog = ai.IssuedPrescriptionAiDialog(repository, ["*"], prescription_id=801)
dialog.show()
application.processEvents()
assert dialog.model_views["openai"]["failure"].isVisible()
text = dialog.model_views["openai"]["failure_text"].text()
assert "OpenAI 失败" in text and "格式校验" in text and "原资料快照" in text
assert "INVALID_REPORT_OUTPUT" not in text
assert dialog.model_views["openai"]["retry"].isEnabled()
assert dialog.model_views["openai"]["retry"].isVisible()
# A model that is still running never shows a failure row.
dialog.model_views["openai"]["retry"].click()
application.processEvents()
assert ("retry", (40, "openai")) in repository.calls
dialog.close()
def test_exhausted_manual_retries_point_at_regeneration_instead_of_retry(application: QApplication, immediate: None) -> None:
repository = Repository()
repository.batches[0]["models"]["openai"].update(status="failed", error_code="UPSTREAM_TIMEOUT",
error_message="UPSTREAM_TIMEOUT", manual_retries=2)
dialog = ai.IssuedPrescriptionAiDialog(repository, ["*"], prescription_id=801)
dialog.show()
application.processEvents()
text = dialog.model_views["openai"]["failure_text"].text()
assert "手动重试次数已用完" in text and "重新分析" in text
assert not dialog.model_views["openai"]["retry"].isVisible()
dialog.close()
def test_inline_review_keeps_model_drafts_and_saves_selected_model(application: QApplication, immediate: None) -> None:
repository = Repository()
repository.batches[0]["models"]["openai"].update(report_id=92, report={"summary": "已保存报告"})
dialog = ai.IssuedPrescriptionAiDialog(repository, ["*"], prescription_id=801)
dialog.show()
application.processEvents()
dialog.model_views["qwen"]["comment"].setPlainText("千问复核草稿")
dialog.review_model.setCurrentIndex(1)
dialog.model_views["openai"]["comment"].setPlainText("OpenAI复核草稿")
dialog.model_views["openai"]["review_state"].setCurrentIndex(3)
dialog._poll()
application.processEvents()
assert dialog.review_model.currentData() == "openai"
assert dialog.model_views["qwen"]["comment"].toPlainText() == "千问复核草稿"
assert dialog.model_views["openai"]["comment"].toPlainText() == "OpenAI复核草稿"
dialog.save_review_button.click()
assert ("review", (40, "openai", "reviewed", "OpenAI复核草稿")) in repository.calls
assert not any(call[0] == "review" and call[1][1] == "qwen" for call in repository.calls)
dialog.close()
def test_workspace_links_reach_existing_supporting_reports(application: QApplication, immediate: None) -> None:
dialog = ai.IssuedPrescriptionAiDialog(Repository(), ["*"], prescription_id=801)
dialog.show()
# Each link opens the composed page that answers it, not the raw saved text.
for field, key in (("report", "report"), ("candidate", "candidate"), ("comparison", "per_herb"),
("sources", "gaps"), ("progress", "pipeline"), ("doctor", "original")):
dialog.comparison_panel.open_report.emit(field)
assert dialog.tabs.currentWidget() is dialog.tab_pages[key], field
dialog.nav_buttons["overview"].click()
assert dialog.tabs.currentWidget() is dialog.comparison_panel
assert dialog.nav_buttons["overview"].isChecked()
assert not dialog.nav_buttons["gaps"].isChecked()
dialog.nav_buttons["per_herb"].click()
assert dialog.tabs.currentWidget() is dialog.tab_pages["per_herb"]
dialog.close()
def test_retry_and_review_target_only_selected_model(application: QApplication, immediate: None) -> None: def test_retry_and_review_target_only_selected_model(application: QApplication, immediate: None) -> None:
repository = Repository() repository = Repository()
repository.batches[0]["status"] = "partial" repository.batches[0]["status"] = "partial"
@@ -250,6 +459,69 @@ def test_list_batches_visible_ids_and_stops_on_hide(application: QApplication, i
assert repository.calls == calls assert repository.calls == calls
def test_blank_list_row_has_no_ai_display_or_entry_points(application: QApplication, immediate: None, monkeypatch: pytest.MonkeyPatch) -> None:
repository = Repository()
opened = []
monkeypatch.setattr(page_module, "present_issued_prescription_ai", lambda *args, **kwargs: opened.append(kwargs))
page = page_module.PrescriptionsPage(repository, ["*"])
page.resize(1366, 800)
page.show()
application.processEvents()
# Demo row 801 contains herbs; row 802 is an empty manual prescription.
assert all(call[1] == [801] for call in repository.calls if call[0] == "statuses")
assert not page.table.isColumnHidden(11) and not page.table.isColumnHidden(12)
page._ai_statuses[802] = batch(prescription_id=802)
page._render_ai_statuses()
for column in (11, 12):
item = page.table.item(1, column)
assert item.text() == item.toolTip() == item.data(Qt.ItemDataRole.AccessibleTextRole) == ""
assert not any(button.accessibleName() == "AI 报告" for button in page.table.cellWidget(1, 2).findChildren(QPushButton))
page.table.selectRow(1)
assert page.ai_report_button.isHidden() and not page.ai_report_button.isEnabled()
page._open_ai_report()
page.table.itemClicked.emit(page.table.item(1, 11))
page.table.itemClicked.emit(page.table.item(1, 12))
page._open_ai_context_menu(page.table.visualItemRect(page.table.item(1, 1)).center())
assert not hasattr(page, "_ai_context_menu")
assert opened == []
page.table.selectRow(0)
assert page.ai_report_button.isVisible() and page.ai_report_button.isEnabled()
page.ai_report_button.click()
assert opened == [{"prescription_id": 801}]
page.close()
@pytest.mark.parametrize("blank_fields", [
{"is_system_auto": "1", "herbs": []},
{"is_system_auto": True, "herbs": [{"name": "黄芪"}]},
{"is_system_auto": 0, "herbs": [{}, {"name": " "}]},
{"is_system_auto": 0, "herbs": None},
])
def test_all_blank_list_hides_ai_until_prescription_is_saved(application: QApplication, immediate: None, monkeypatch: pytest.MonkeyPatch, blank_fields: dict[str, Any]) -> None:
repository = Repository()
row = {"id": 803, "sn": "RX-BLANK", "patient_name": "测试患者", **blank_fields}
monkeypatch.setattr(repository, "list_prescriptions", lambda **_filters: {"lists": [row], "count": 1})
page = page_module.PrescriptionsPage(repository, ["*"])
page.resize(1366, 800)
page.show()
application.processEvents()
assert page.table.isColumnHidden(11) and page.table.isColumnHidden(12)
assert page.ai_report_button.isHidden() and page.ai_status_notice.isHidden()
assert not page._ai_timer.isActive()
assert not any(call[0] == "statuses" for call in repository.calls)
assert not any(button.accessibleName() == "AI 报告" for button in page.table.cellWidget(0, 2).findChildren(QPushButton))
row.update(is_system_auto=0, herbs=[{"name": "黄芪", "dosage": 12}])
page.refresh()
application.processEvents()
assert ("statuses", [803]) in repository.calls
assert not page.table.isColumnHidden(11) and not page.table.isColumnHidden(12)
assert page.ai_report_button.isVisible() and page.ai_status_notice.isVisible()
assert "千问 0%" in page.table.item(0, 12).text()
assert any(button.accessibleName() == "AI 报告" for button in page.table.cellWidget(0, 2).findChildren(QPushButton))
page.close()
def test_list_disabled_explains_availability_and_keeps_history(application: QApplication, immediate: None) -> None: def test_list_disabled_explains_availability_and_keeps_history(application: QApplication, immediate: None) -> None:
repository = Repository() repository = Repository()
repository.enabled = False repository.enabled = False
@@ -447,8 +719,9 @@ def test_context_menu_targets_clicked_prescription(application: QApplication, im
page.resize(1366, 800) page.resize(1366, 800)
page.show() page.show()
application.processEvents() application.processEvents()
item = page.table.item(1, 1) page.table.selectRow(1)
expected_id = page.table.item(1, 0).data(Qt.ItemDataRole.UserRole).id item = page.table.item(0, 1)
expected_id = page.table.item(0, 0).data(Qt.ItemDataRole.UserRole).id
page._open_ai_context_menu(page.table.visualItemRect(item).center()) page._open_ai_context_menu(page.table.visualItemRect(item).center())
page._ai_context_menu.actions()[0].trigger() page._ai_context_menu.actions()[0].trigger()
assert opened == [{"prescription_id": expected_id}] assert opened == [{"prescription_id": expected_id}]
@@ -617,6 +890,25 @@ def test_metadata_and_prose_remain_html_escaped(application: QApplication) -> No
browser.close() browser.close()
def test_candidate_reading_view_keeps_full_medicine_instructions(application: QApplication) -> None:
candidate = {
"prescription_name": "测试候选方", "dose_basis": "per_dose", "unit": "g",
"herbs": [{"name": "测试药材", "dosage": 12, "formula_type": "主方",
"instructions": "先煎,具体时长由医生复核", "processing": "生品",
"evidence_references": ["diagnoses:501"]}],
"rationale": "保留完整方义", "risk_warnings": ["复核提示 <script>bad()</script>"],
}
html = ai._candidate_html(candidate)
assert html.index("测试药材") < html.index("保留完整方义")
assert "<script>" not in html
browser = ai._browser()
browser.setHtml(html)
text = browser.toPlainText()
for expected in ("测试药材", "12", "", "每剂", "主方", "先煎", "生品", "诊单(编号:501", "保留完整方义"):
assert expected in text
browser.close()
def chinese_history_batch() -> dict[str, Any]: def chinese_history_batch() -> dict[str, Any]:
"""Synthetic fixture; no patient or network data is used in tests or visual QA.""" """Synthetic fixture; no patient or network data is used in tests or visual QA."""
value = batch(39, status="success") value = batch(39, status="success")
@@ -907,6 +1199,7 @@ def test_progress_updates_preserve_report_document_scroll_and_selection(applicat
repository.batches = [value] repository.batches = [value]
dialog = ai.IssuedPrescriptionAiDialog(repository, ["*"], prescription_id=801) dialog = ai.IssuedPrescriptionAiDialog(repository, ["*"], prescription_id=801)
dialog.show() dialog.show()
dialog.tabs.setCurrentWidget(dialog.report_pages["report"])
application.processEvents() application.processEvents()
report = dialog.model_views["qwen"]["report"] report = dialog.model_views["qwen"]["report"]
report.verticalScrollBar().setValue(300) report.verticalScrollBar().setValue(300)
@@ -1035,3 +1328,93 @@ def test_retry_wait_freezes_attempt_duration_while_countdown_advances_and_poll_r
def test_unknown_or_invalid_attempt_count_is_not_invented(attempt: Any) -> None: def test_unknown_or_invalid_attempt_count_is_not_invented(attempt: Any) -> None:
view = ai.progress_view({"status": "retry_wait", "progress": {"stage": "retry_wait", "attempt": attempt}}) view = ai.progress_view({"status": "retry_wait", "progress": {"stage": "retry_wait", "attempt": attempt}})
assert "次尝试" not in view.detail assert "次尝试" not in view.detail
def test_theme_switch_keeps_the_page_the_drafts_and_every_step(application: QApplication,
immediate: None) -> None:
"""The palette swaps in place; nothing the doctor typed or opened is lost."""
from doctor_workstation.ui.dialogs import issued_prescription_ai_theme as theme
dialog = ai.IssuedPrescriptionAiDialog(Repository(), ["*"], prescription_id=801)
dialog.resize(1280, 860)
dialog.show()
application.processEvents()
dialog._goto("gaps")
dialog.model_views["qwen"]["comment"].setPlainText("切换前写的复核意见")
dark = theme.CONSOLE["canvas"]
try:
dialog._switch_theme()
application.processEvents()
assert theme.CONSOLE["canvas"] != dark
assert theme.current_theme() == "light"
assert dialog.model_views["qwen"]["comment"].toPlainText() == "切换前写的复核意见"
assert dialog.tabs.currentWidget() is dialog.tab_pages["gaps"]
assert len(dialog.rail.buttons) == len(ai.NAV_PRIMARY)
assert all(button.isVisible() for button in dialog.rail.buttons.values())
finally:
dialog._switch_theme()
application.processEvents()
assert theme.CONSOLE["canvas"] == dark
dialog.close()
def test_every_step_stays_readable_in_both_palettes(application: QApplication, immediate: None) -> None:
"""A selected step paints its own labels: a descendant :checked rule would not reach them."""
from doctor_workstation.ui.dialogs import issued_prescription_ai_theme as theme
dialog = ai.IssuedPrescriptionAiDialog(Repository(), ["*"], prescription_id=801)
dialog.show()
application.processEvents()
rail = dialog.rail
assert theme.CONSOLE["heading"] in rail.names["overview"].styleSheet()
assert theme.CONSOLE["muted"] in rail.names["report"].styleSheet()
rail.set_current("report")
assert theme.CONSOLE["heading"] in rail.names["report"].styleSheet()
assert theme.CONSOLE["muted"] in rail.names["overview"].styleSheet()
dialog.close()
def test_native_title_bar_takes_the_console_palette() -> None:
"""The frame Windows draws follows the theme, so the dark body does not stop at the caption."""
from doctor_workstation.ui.dialogs import issued_prescription_ai_theme as theme
# DWM wants 0x00BBGGRR, not the #RRGGBB the palette is written in.
assert theme._colorref("#080D18") == 0x180D08
assert theme._colorref("#FFFFFF") == 0xFFFFFF
class Handleless:
def winId(self) -> int:
return 0
assert theme.apply_window_chrome(Handleless()) is False
def test_report_rail_lists_the_saved_sections_and_filters_to_differences(
application: QApplication, immediate: None) -> None:
repository = Repository()
repository.batches[0]["models"]["qwen"]["report"] = {"summary": "两侧一致的概要", "analysis": "千问的分析"}
repository.batches[0]["models"]["openai"]["report"] = {"summary": "两侧一致的概要", "analysis": "OpenAI 的分析"}
dialog = ai.IssuedPrescriptionAiDialog(repository, ["*"], prescription_id=801)
dialog.show()
dialog._goto("report")
application.processEvents()
titles = [dialog.report_toc.itemAt(index).widget().text()
for index in range(dialog.report_toc.count())]
assert "概要" in titles and "综合分析" in titles
both = dialog.model_views["qwen"]["report"].toPlainText()
assert "两侧一致的概要" in both and "千问的分析" in both
dialog._set_report_mode("differences")
application.processEvents()
filtered = dialog.model_views["qwen"]["report"].toPlainText()
assert "千问的分析" in filtered
assert "两侧一致的概要" not in filtered # 两侧完全相同的段落不算差异
dialog._set_report_mode("qwen")
application.processEvents()
assert not dialog.report_columns["openai"].isVisible()
dialog._set_report_mode("both")
application.processEvents()
assert dialog.report_columns["openai"].isVisible()
dialog.close()
@@ -0,0 +1,399 @@
"""Offline rendering and data-integrity checks for the prescription workspace."""
from __future__ import annotations
import os
import socket
from copy import deepcopy
from typing import Any
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
import pytest
from PySide6.QtCore import QEvent, QObject, Qt
from PySide6.QtTest import QTest
from PySide6.QtWidgets import QApplication, QVBoxLayout, QWidget
from doctor_workstation.ui.dialogs.issued_prescription_ai_comparison import (
PrescriptionComparisonPanel,
)
def saved_row(name: str = "黄芪", *, doctor: Any = 30, candidate: Any = 15, unit: str = "g", basis: str = "per_dose", formula: str = "主方", processing: str = "生品") -> dict[str, Any]:
identity = {"name": name, "processing": processing, "formula_type": formula, "administration_route": "口服", "group": "", "unit": unit, "dose_basis": basis}
return {**identity, "key": "a1b2c3d4" * 8, "doctor": {**identity, "dosage": doctor}, "candidate": {**identity, "dosage": candidate}, "doctor_dosage": doctor, "candidate_dosage": candidate, "match_type": "matched"}
def saved_batch(rows: list[dict[str, Any]] | None = None) -> dict[str, Any]:
rows = rows if rows is not None else [saved_row(), saved_row("白术", doctor=9, candidate=12)]
herbs = []
for row in rows:
if row.get("candidate"):
original = deepcopy(row["candidate"])
original.pop("source_rows", None)
row["candidate"].setdefault("source_rows", [len(herbs)])
herbs.append(original)
candidate = {"status": "available_for_review", "prescription_name": "补中益气汤加减", "herbs": herbs, "usage_instruction": "水煎温服", "times_per_day": 2, "usage_days": 7}
qwen = {"status": "succeeded", "candidate": candidate, "comparison": {"status": "comparable", "score": 68.5, "herb_score": 100, "rows": rows}}
openai = deepcopy(qwen)
openai["candidate"]["prescription_name"] = "益气健脾方"
return {"id": 44, "validity": "current", "status": "completed", "models": {"qwen": qwen, "openai": openai}}
@pytest.fixture(scope="module")
def application() -> QApplication:
return QApplication.instance() or QApplication([])
@pytest.fixture(autouse=True)
def no_network(monkeypatch: pytest.MonkeyPatch) -> None:
def denied(*_args: Any, **_kwargs: Any) -> None:
pytest.fail("The comparison panel must never contact external systems")
monkeypatch.setattr(socket.socket, "connect", denied)
monkeypatch.setattr(socket.socket, "connect_ex", denied)
monkeypatch.setattr(socket, "create_connection", denied)
@pytest.fixture
def panel(application: QApplication):
host = QWidget()
host.resize(1120, 400)
layout = QVBoxLayout(host)
layout.setContentsMargins(0, 0, 0, 0)
widget = PrescriptionComparisonPanel(host)
layout.addWidget(widget)
host.show()
application.processEvents()
yield widget
host.close()
host.deleteLater()
application.processEvents()
def test_saved_snapshot_is_readable_and_model_switch_is_explicit(panel: PrescriptionComparisonPanel, application: QApplication) -> None:
data = saved_batch()
before = deepcopy(data)
panel.set_batch(data)
application.processEvents()
assert data == before
assert panel.selected_model == "qwen"
assert panel.model_buttons["qwen"].isChecked()
assert panel.name_label.text() == "补中益气汤加减"
assert panel.herb_table.rowCount() == 2
assert panel.herb_table.item(0, 1).text() == "30 克 / 每剂"
assert panel.herb_table.item(0, 2).text() == "15 克 / 每剂"
assert "主方" in panel.herb_table.item(0, 0).text()
assert "a1b2c3d4" not in panel.chart.accessibleDescription()
assert "药味剂量一致度" not in panel.chart.accessibleDescription()
assert "水煎温服" in panel.usage_label.text()
assert panel.chart.bars_enabled
assert panel.chart.groups[0][0] == ("", "每剂")
panel.model_buttons["openai"].click()
assert panel.selected_model == "openai"
assert panel.name_label.text() == "益气健脾方"
assert panel.herb_table.horizontalHeaderItem(2).text() == "OpenAI剂量"
assert panel.chart.model_key == "openai"
@pytest.mark.parametrize("candidate_key", ["candidate_dose", "candidate_dosage", "ai_dose"])
def test_legacy_flat_doses_remain_supported(panel: PrescriptionComparisonPanel, candidate_key: str) -> None:
data = saved_batch()
data["models"]["qwen"]["comparison"]["rows"] = [{"name": "黄芪", "doctor_dose": 0, candidate_key: "12.50", "unit": "g", "dose_basis": "每剂", "formula_type": "主方"}]
panel.set_batch(data)
assert panel.herb_table.item(0, 1).text() == "0 克 / 每剂"
assert panel.herb_table.item(0, 2).text() == "12.50 克 / 每剂"
assert panel.chart.rows[0].doctor.number == 0
def test_absent_historical_doctor_is_never_replaced_with_current_prescription(panel: PrescriptionComparisonPanel) -> None:
data = saved_batch()
data["models"]["qwen"]["comparison"]["rows"] = []
data["prescription"] = {"herbs": [{"name": "黄芪", "dosage": 99, "unit": "g", "dose_basis": "per_dose"}]}
panel.set_batch(data)
assert panel.herb_table.rowCount() == 2
assert panel.herb_table.item(0, 1).text() == ""
assert panel.herb_table.item(0, 2).text() == "15 克 / 每剂"
assert not panel.chart.bars_enabled
assert "历史处方" in panel.status_label.text()
assert "99" not in panel.chart.accessibleDescription()
@pytest.mark.parametrize("rows", [None, {}, "unavailable"])
def test_malformed_comparison_keeps_candidate_list_without_claiming_a_snapshot(panel: PrescriptionComparisonPanel, rows: Any) -> None:
data = saved_batch()
data["models"]["qwen"]["comparison"]["rows"] = rows
panel.set_batch(data)
assert panel.herb_table.rowCount() == 2
assert panel.herb_table.item(0, 1).text() == ""
assert not panel.chart.bars_enabled
def test_explicit_missing_snapshot_and_nested_unknown_unit_override_flat_values(panel: PrescriptionComparisonPanel) -> None:
row = saved_row()
row["doctor"] = None
row["candidate"]["unit"] = None
panel.set_batch(saved_batch([row]))
assert panel.herb_table.item(0, 1).text() == ""
assert "单位未注明" in panel.herb_table.item(0, 2).text()
assert panel.chart.groups[0][0] is None
assert "未明确" in panel.chart.accessibleDescription()
def test_incomparable_report_retains_original_doses_without_bars(panel: PrescriptionComparisonPanel) -> None:
data = saved_batch()
data["models"]["qwen"]["comparison"].update(status="not_comparable", score=99, reason="dose_basis_mismatch")
panel.set_batch(data)
assert "剂量基准不同" in panel.status_label.text()
assert not panel.chart.bars_enabled
assert panel.herb_table.item(0, 1).text() == "30 克 / 每剂"
assert all(scale is None for scale, _rows, _maximum in panel.chart.groups)
assert "99" not in panel.chart.accessibleDescription()
def test_units_and_bases_have_independent_scales_and_mismatched_rows_are_excluded(panel: PrescriptionComparisonPanel) -> None:
rows = [saved_row("黄芪"), saved_row("白术", unit="mg", doctor=1000), saved_row("茯苓", basis="per_day", doctor=60), saved_row("甘草")]
rows[-1]["candidate"]["dose_basis"] = "per_day"
panel.set_batch(saved_batch(rows))
assert [scale for scale, _members, _maximum in panel.chart.groups] == [("", "每剂"), ("毫克", "每剂"), ("", "每日"), None]
assert panel.chart.groups[0][2] == 30
assert panel.chart.groups[1][2] == 1000
assert "单位或剂量基准不同" in panel.chart.accessibleDescription()
assert "每日" in panel.herb_table.item(3, 2).text()
def test_main_auxiliary_and_processing_rows_never_merge(panel: PrescriptionComparisonPanel) -> None:
rows = [saved_row("甘草", formula="主方"), saved_row("甘草", formula="辅方"), saved_row("甘草", processing="炙品")]
rows[2]["candidate"]["processing"] = "生品"
panel.set_batch(saved_batch(rows))
assert panel.herb_table.rowCount() == 3
assert "主方" in panel.herb_table.item(0, 0).text()
assert "辅方" in panel.herb_table.item(1, 0).text()
assert panel.chart.rows[2].scale is None
assert "炮制" in panel.chart.accessibleDescription()
@pytest.mark.parametrize("skipped_index", [0, 1])
def test_zero_based_trace_preserves_candidate_herbs_skipped_during_normalization(panel: PrescriptionComparisonPanel, skipped_index: int) -> None:
normalized = saved_row("黄芪")
normalized["candidate"]["source_rows"] = [1 - skipped_index]
data = saved_batch([normalized])
original = deepcopy(data["models"]["qwen"]["candidate"]["herbs"][0])
excluded = {**original, "name": "黄芪", "processing": "炮制待核对", "dosage": 8, "instructions": "先煎 30 分钟"}
herbs = [original]
herbs.insert(skipped_index, excluded)
data["models"]["qwen"]["candidate"]["herbs"] = herbs
# Even a contradictory comparable status must not grant the omitted raw herb a bar.
panel.set_batch(data)
assert panel.herb_table.rowCount() == 2
assert panel.herb_table.item(0, 2).text() == "15 克 / 每剂"
assert "未纳入对比" in panel.herb_table.item(1, 0).text()
assert "炮制待核对" in panel.herb_table.item(1, 0).text()
assert panel.herb_table.item(1, 1).text() == ""
assert panel.herb_table.item(1, 2).text() == "8 克 / 每剂"
assert panel.chart.rows[1].scale is None
assert "先煎 30 分钟" in panel.chart.accessibleDescription()
assert "原方 2 项" in panel.count_label.text() and "未纳入 1 项" in panel.count_label.text()
def test_merged_source_rows_cover_each_original_once_and_keep_original_doses(panel: PrescriptionComparisonPanel) -> None:
merged = saved_row("黄芪", candidate=10)
merged["candidate"]["source_rows"] = [0, 1]
data = saved_batch([merged])
original = data["models"]["qwen"]["candidate"]["herbs"][0]
data["models"]["qwen"]["candidate"]["herbs"] = [{**original, "name": "北芪", "dosage": 4}, {**original, "dosage": 6}, {**original, "name": "未识别药材", "dosage": 5}]
panel.set_batch(data)
assert panel.herb_table.rowCount() == 2
assert panel.herb_table.item(0, 2).text() == "10 克 / 每剂"
tooltip = panel.herb_table.item(0, 0).toolTip()
assert "第 1 项 北芪 4 克 / 每剂" in tooltip
assert "第 2 项 黄芪 6 克 / 每剂" in tooltip
assert "未识别药材" in panel.herb_table.item(1, 0).text()
assert "未纳入对比" in panel.herb_table.item(1, 0).text()
assert "原方 3 项" in panel.count_label.text() and "对比 1 项" in panel.count_label.text()
panel.search.setText("北芪")
assert panel.herb_table.rowCount() == 1
assert "黄芪" in panel.herb_table.item(0, 0).text()
def test_legacy_missing_trace_retains_full_original_separately_without_name_matching(panel: PrescriptionComparisonPanel) -> None:
data = saved_batch([saved_row("黄芪")])
model = data["models"]["qwen"]
del model["comparison"]["rows"][0]["candidate"]["source_rows"]
original = model["candidate"]["herbs"][0]
model["candidate"]["herbs"] = [{**original, "dosage": 4}, {**original, "name": "炮制待核对药材", "dosage": 6}]
panel.set_batch(data)
assert panel.herb_table.rowCount() == 3
assert panel.herb_table.item(0, 2).text() == "15 克 / 每剂"
for index, dose in ((1, "4 克 / 每剂"), (2, "6 克 / 每剂")):
assert "原方附列" in panel.herb_table.item(index, 0).text()
assert panel.herb_table.item(index, 1).text() == ""
assert panel.herb_table.item(index, 2).text() == dose
assert panel.chart.rows[index].scale is None
assert "对应关系未保存" in panel.status_label.text()
assert "原方附列 2 项" in panel.count_label.text()
@pytest.mark.parametrize("trace", [[True], [1], [-1], ["0"], []])
def test_invalid_source_trace_never_hides_an_original_herb(panel: PrescriptionComparisonPanel, trace: list[Any]) -> None:
data = saved_batch([saved_row("黄芪")])
data["models"]["qwen"]["comparison"]["rows"][0]["candidate"]["source_rows"] = trace
panel.set_batch(data)
assert panel.herb_table.rowCount() == 2
assert "原方附列" in panel.herb_table.item(1, 0).text()
assert panel.chart.rows[1].scale is None
def test_real_normalized_usage_keeps_all_special_instructions_visible_and_accessible(panel: PrescriptionComparisonPanel) -> None:
row = saved_row("石膏")
row["doctor"]["usage"] = {"decoction_instruction": "先煎 30 分钟", "special_usage": "布包煎", "usage_time": "饭后", "usage_way": "温服"}
row["candidate"]["instructions"] = "后下 5 分钟"
row["candidate"]["usage"] = {"instructions": "后下 5 分钟", "decoction_instruction": "另煎", "special_usage": "分次兑服", "usage_instruction": "服前摇匀", "usage_time": "睡前", "usage_way": "温服"}
panel.set_batch(saved_batch([row]))
name_item = panel.herb_table.item(0, 0)
for instruction in ("先煎 30 分钟", "布包煎", "饭后", "温服", "后下 5 分钟", "另煎", "分次兑服", "服前摇匀", "睡前"):
assert instruction in name_item.toolTip()
assert instruction in name_item.data(Qt.ItemDataRole.AccessibleDescriptionRole)
assert instruction in panel.chart.accessibleDescription()
assert "先煎 30 分钟" in name_item.text() and "后下 5 分钟" in name_item.text()
assert panel.chart.rows[0].candidate.instructions.count("后下 5 分钟") == 1
assert panel.herb_table.rowHeight(0) >= panel.herb_table.fontMetrics().height() * 3 + 12
def test_clicking_a_table_row_scrolls_to_its_chart_group(panel: PrescriptionComparisonPanel, application: QApplication) -> None:
rows = [saved_row(f"药材{index}", unit="g" if index % 2 == 0 else "mg") for index in range(20)]
panel.set_batch(saved_batch(rows))
application.processEvents()
item = panel.herb_table.item(11, 0)
panel.herb_table.scrollToItem(item)
application.processEvents()
panel.chart_scroll.verticalScrollBar().setValue(0)
QTest.mouseClick(panel.herb_table.viewport(), Qt.MouseButton.LeftButton, pos=panel.herb_table.visualItemRect(item).center())
application.processEvents()
assert panel.herb_table.currentRow() == 11
# Ten gram rows precede the milligram group; the clicked row is its sixth member.
expected_y = 8 + panel.chart.GROUP_HEIGHT * 2 + panel.chart.ROW_HEIGHT * 15
assert panel.chart_scroll.verticalScrollBar().value() == expected_y
@pytest.mark.parametrize("field", ["processing", "formula_type", "administration_route", "group"])
def test_unrecognized_identity_values_do_not_become_equal_after_localization(panel: PrescriptionComparisonPanel, field: str) -> None:
row = saved_row()
row["doctor"][field] = "unknown_first"
row["candidate"][field] = "unknown_second"
panel.set_batch(saved_batch([row]))
assert panel.chart.rows[0].scale is None
assert "unknown_first" not in panel.chart.accessibleDescription()
@pytest.mark.parametrize("value", [None, "适量", -3, float("nan"), float("inf"), True])
def test_invalid_or_missing_doses_never_produce_numeric_bars(panel: PrescriptionComparisonPanel, application: QApplication, value: Any) -> None:
panel.set_batch(saved_batch([saved_row(doctor=value)]))
assert panel.chart.rows[0].doctor.number is None
assert panel.chart.groups[0][2] == 15
application.processEvents()
assert not panel.chart.grab().isNull()
if value is None or value is True:
assert panel.herb_table.item(0, 1).text() == ""
@pytest.mark.parametrize(("status", "expected"), [("running", "正在生成"), ("failed", "未完成"), ("future_state", "状态未确认")])
def test_processing_failed_and_unknown_model_states_are_explicit(panel: PrescriptionComparisonPanel, status: str, expected: str) -> None:
data = saved_batch()
data["models"]["qwen"].update(status=status, candidate=None, comparison=None)
panel.set_batch(data)
assert expected in panel.status_label.text()
assert panel.empty_label.isVisible()
assert panel.herb_table.rowCount() == 0
assert not panel.chart.bars_enabled
@pytest.mark.parametrize("validity", ["stale", "source_updated", "future_validity"])
def test_outdated_and_unknown_validity_only_show_saved_original_values(panel: PrescriptionComparisonPanel, validity: str) -> None:
data = saved_batch()
data["validity"] = validity
panel.set_batch(data)
assert not panel.chart.bars_enabled
assert "历史原值" in panel.status_label.text()
assert panel.herb_table.rowCount() == 2
def test_refresh_preserves_search_model_selection_and_both_scroll_positions(panel: PrescriptionComparisonPanel, application: QApplication) -> None:
data = saved_batch([saved_row(f"黄芪{index}") for index in range(40)])
panel.set_batch(data)
panel.model_buttons["openai"].click()
panel.search.setText("黄芪")
panel.herb_table.selectRow(10)
application.processEvents()
panel.herb_table.verticalScrollBar().setValue(180)
panel.chart_scroll.verticalScrollBar().setValue(250)
before = (panel.herb_table.verticalScrollBar().value(), panel.chart_scroll.verticalScrollBar().value())
for _ in range(3):
panel.set_batch(deepcopy(data))
application.processEvents()
assert panel.search.text() == "黄芪"
assert panel.selected_model == "openai"
assert panel.herb_table.currentRow() == 10
assert before == (panel.herb_table.verticalScrollBar().value(), panel.chart_scroll.verticalScrollBar().value())
changed = deepcopy(data)
changed["models"]["qwen"]["progress"] = {"stage": "completed"}
panel.set_batch(changed)
application.processEvents()
assert panel.search.text() == "黄芪" and panel.selected_model == "openai"
assert before == (panel.herb_table.verticalScrollBar().value(), panel.chart_scroll.verticalScrollBar().value())
panel.search.setText("黄芪39")
assert panel.herb_table.rowCount() == 1
assert len(panel.chart.rows) == 1
panel.search.setText("未匹配")
assert "没有匹配" in panel.empty_label.text()
def test_parent_owned_controls_do_not_flash_windows_on_update(application: QApplication) -> None:
shown_windows = []
class WindowObserver(QObject):
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
if event.type() == QEvent.Type.Show and isinstance(watched, QWidget) and watched.isWindow():
shown_windows.append(watched)
return False
observer = WindowObserver()
application.installEventFilter(observer)
host = QWidget()
try:
layout = QVBoxLayout(host)
widget = PrescriptionComparisonPanel(host)
layout.addWidget(widget)
widget.set_batch(saved_batch())
host.show()
application.processEvents()
assert shown_windows == [host]
shown_windows.clear()
widget.set_batch(saved_batch([saved_row("当归")]))
widget.model_buttons["openai"].click()
widget.set_batch({})
application.processEvents()
assert shown_windows == []
assert all(child.parentWidget() is not None for child in widget.findChildren(QWidget))
assert widget.herb_table.rowCount() == 0
assert widget.chart.rows == []
finally:
application.removeEventFilter(observer)
host.close()
host.deleteLater()
application.processEvents()
@pytest.mark.parametrize("size", [(1120, 400), (940, 340)])
def test_compact_sizes_keep_table_and_chart_scrollable(panel: PrescriptionComparisonPanel, application: QApplication, size: tuple[int, int]) -> None:
panel.parentWidget().resize(*size)
panel.set_batch(saved_batch([saved_row(f"药材{index}") for index in range(60)]))
application.processEvents()
assert panel.width() <= size[0] and panel.height() <= size[1]
assert panel.herb_table.viewport().height() >= 65
assert panel.herb_table.viewport().width() >= 320
assert panel.chart_scroll.viewport().height() >= 90
assert panel.herb_table.verticalScrollBar().maximum() > 0
assert panel.chart_scroll.verticalScrollBar().maximum() > 0
assert not panel.grab().isNull()
assert panel.herb_table.item(0, 2).data(Qt.ItemDataRole.AccessibleTextRole) == "15 克 / 每剂"
assert "药材59" in panel.chart.accessibleDescription()
@@ -0,0 +1,478 @@
"""The composed pages must show the saved batch exactly, including what is missing from it."""
from __future__ import annotations
from typing import Any
import pytest
from PySide6.QtWidgets import QApplication, QLabel
from doctor_workstation.ui.dialogs import issued_prescription_ai_pages as pages
@pytest.fixture(scope="module")
def application() -> QApplication:
return QApplication.instance() or QApplication([])
def comparison_rows(model: str) -> list[dict[str, Any]]:
doses = {"qwen": {"生地黄": 15, "生麦冬": 12, "麸炒白术": 12}, "openai": {"生地黄": 12, "茯苓": 8}}[model]
contributions = {"qwen": {"生地黄": 0.94, "生麦冬": 1.0}, "openai": {"生地黄": 0.75}}[model]
doctor = {"生地黄": 16, "生麦冬": 12, "红参片": 6, "茯苓": 10}
rows = []
for name in sorted(set(doses) | set(doctor)):
rows.append({
"name": name, "unit": "g",
"doctor_dosage": doctor.get(name),
"candidate_dosage": doses.get(name),
"contribution": contributions.get(name),
})
return rows
def batch(**overrides: Any) -> dict[str, Any]:
value = {
"id": 13, "status": "success", "coverage_status": "partial", "comparison_type": "latest_context",
"source_summary": {"attachment_count": 4, "diagnoses_count": 1, "video_calls_count": 3,
"source_record_count": 9},
"missing": [{"code": "TRANSCRIPT_NOT_VERIFIED_COMPLETE", "critical": True},
{"code": "TRANSCRIPT_NOT_VERIFIED_COMPLETE", "critical": True},
{"code": "ARCHIVE_SYNC_WATERMARK_UNAVAILABLE", "critical": False}],
"models": {
"qwen": {"status": "success", "algorithm_version": "prescription-soft-dice-v1.1.0",
"prompt_version": "manual-prescription-required-candidate-v4",
"comparison": {"status": "comparable", "rows": comparison_rows("qwen")},
"coverage": {"files": [{"status": "processed"}, {"status": "processed"},
{"status": "processed"}, {"status": "restricted"}]},
"progress": {"stage_label": "处理完成", "elapsed_seconds": 135, "attempt": 1},
"usage": {"total_calls": 2, "calls": [
{"stage": "text:0", "ok": True, "latency_ms": 3400, "file_count": 0,
"usage": {"completion_tokens": 1020}, "error_code": ""},
{"stage": "final", "ok": False, "latency_ms": 14400, "file_count": 0,
"usage": {"completion_tokens": 5617}, "error_code": "INVALID_REPORT_OUTPUT"}]}},
"openai": {"status": "success", "comparison": {"status": "comparable", "rows": comparison_rows("openai")},
"progress": {"stage_label": "处理完成", "elapsed_seconds": 593, "attempt": 1},
"usage": {"calls": []}},
},
}
value.update(overrides)
return value
# ---------------------------------------------------------------- candidates page
@pytest.fixture
def per_herb(application: QApplication) -> pages.CandidatesPage:
widget = pages.CandidatesPage()
widget.resize(1200, 700)
widget.show()
application.processEvents()
yield widget
widget.close()
def _dose_cells(page: pages.CandidatesPage, row: int) -> tuple[str, str]:
return (page.table.cellWidget(row, 1).dose, page.table.cellWidget(row, 2).dose)
def _verdict(page: pages.CandidatesPage, row: int) -> str:
return page.table.cellWidget(row, 4).findChildren(QLabel)[0].text()
def test_per_herb_merges_both_models_into_one_row(per_herb: pages.CandidatesPage) -> None:
per_herb.set_batch(batch())
names = [per_herb.table.item(row, 0).text() for row in range(per_herb.table.rowCount())]
assert names.count("生地黄") == 1
row = names.index("生地黄")
assert _dose_cells(per_herb, row) == ("15 g", "12 g")
assert per_herb.table.item(row, 3).text() == "16 g"
assert per_herb.table.cellWidget(row, 1).contribution == 0.94
assert per_herb.table.cellWidget(row, 2).contribution == 0.75
def test_per_herb_marks_absence_without_inventing_a_dose(per_herb: pages.CandidatesPage) -> None:
per_herb.set_batch(batch())
names = [per_herb.table.item(row, 0).text() for row in range(per_herb.table.rowCount())]
only_doctor = names.index("红参片")
assert _dose_cells(per_herb, only_doctor) == ("", "")
assert per_herb.table.cellWidget(only_doctor, 1).contribution is None
assert _verdict(per_herb, only_doctor) == "仅医方使用"
added = names.index("麸炒白术")
assert per_herb.table.item(added, 3).text() == "未收录"
assert _verdict(per_herb, added) == "仅 千问 收录"
def test_per_herb_conclusion_states_a_dose_gap_over_the_threshold(per_herb: pages.CandidatesPage) -> None:
per_herb.set_batch(batch())
names = [per_herb.table.item(row, 0).text() for row in range(per_herb.table.rowCount())]
row = names.index("生地黄")
assert _verdict(per_herb, row) == "两模型均收录" # 医方 16,两侧差 1 与 4,未过 5 克阈值
source = batch()
for model in source["models"].values():
for entry in model["comparison"]["rows"]:
if entry["name"] == "生地黄":
entry["candidate_dosage"] = 9
per_herb.set_batch(source)
names = [per_herb.table.item(index, 0).text() for index in range(per_herb.table.rowCount())]
assert _verdict(per_herb, names.index("生地黄")) == "剂量分歧 7 g"
def test_per_herb_filters_count_and_narrow_the_table(
per_herb: pages.CandidatesPage, application: QApplication) -> None:
per_herb.set_batch(batch())
total = per_herb.table.rowCount()
assert per_herb.filter_buttons["all"].text() == f"全部 {total}"
per_herb.search.setText("生地黄")
application.processEvents()
assert per_herb.table.rowCount() == 1
per_herb.search.clear()
per_herb.filter_buttons["doctor"].click()
application.processEvents()
assert 0 < per_herb.table.rowCount() < total
assert all(_verdict(per_herb, row) in {"仅医方使用", "两模型均未收录"}
for row in range(per_herb.table.rowCount()))
per_herb.filter_buttons["single"].click()
application.processEvents()
assert all("" in _verdict(per_herb, row) for row in range(per_herb.table.rowCount()))
def test_per_herb_cards_show_each_saved_prescription(per_herb: pages.CandidatesPage) -> None:
per_herb.set_batch(batch(doctor_snapshot={"prescription": {
"herbs": [{"name": "生地黄", "dosage": 16, "unit": "g"}, {"name": "红参片", "dosage": 6, "unit": "g"}],
"prescription_type": "浓缩水丸", "dose_count": 1, "usage_instruction": "每日1剂,水煎分服。"}}))
doctor = per_herb.cards["doctor"]
assert doctor.summary.text() == "2 味 · 浓缩水丸"
assert "生地黄" in doctor.herbs.text() and "16 g" in doctor.herbs.text()
assert "每日1剂" in doctor.usage.text()
assert per_herb.cards["qwen"].summary.text() == "尚无候选方"
def test_per_herb_card_grows_for_a_long_usage_note(per_herb: pages.CandidatesPage,
application: QApplication) -> None:
"""A card must not cap itself and cut the herb list or the 方义 in half."""
note = "方中生黄芪益气固表,生地黄、生麦冬滋阴清热。" * 8
per_herb.set_batch(batch(doctor_snapshot={"prescription": {
"herbs": [{"name": f"{index}", "dosage": 15, "unit": "g"} for index in range(20)],
"prescription_type": "浓缩水丸", "dose_count": 1, "usage_instruction": note}}))
per_herb.resize(1100, 700)
application.processEvents()
card = per_herb.cards["doctor"]
assert "另有 15 味" in card.herbs.text()
assert card.usage.height() >= card.usage.heightForWidth(card.usage.width())
assert card.height() >= card.herbs.height() + card.usage.height()
def test_per_herb_says_when_no_prescription_was_saved(per_herb: pages.CandidatesPage) -> None:
per_herb.set_batch({})
assert per_herb.cards["doctor"].summary.text() == "未保存原方"
assert per_herb.cards["doctor"].herbs.text() == "尚未保存药味"
assert per_herb.table.rowCount() == 0
# ---------------------------------------------------------------- sources page
@pytest.fixture
def sources(application: QApplication) -> pages.SourcesPage:
widget = pages.SourcesPage()
widget.resize(900, 600)
widget.show()
application.processEvents()
yield widget
widget.close()
def test_sources_groups_gaps_by_type_and_keeps_criticality(sources: pages.SourcesPage) -> None:
sources.set_batch(batch())
rows = {sources.gaps.item(row, 0).text(): (sources.gaps.item(row, 1).text(), sources.gaps.item(row, 2).text())
for row in range(sources.gaps.rowCount())}
transcript = next(key for key in rows if "转写" in key)
assert rows[transcript] == ("关键", "2")
archive = next(key for key in rows if "归档" in key)
assert rows[archive][0] == "一般"
def _composition(sources: pages.SourcesPage) -> dict[str, str]:
rows = {}
for index in range(sources.composition_layout.count()):
widget = sources.composition_layout.itemAt(index).widget()
labels = widget.findChildren(QLabel)
rows[labels[0].text()] = labels[-1].text()
return rows
def test_sources_reports_attachment_reading_and_composition(sources: pages.SourcesPage) -> None:
sources.set_batch(batch())
assert "模型实际读取 3 个" in sources.attachment_note.text()
assert sources.waffle.accessibleDescription() == "附件 4 个:已读 3,受限或不支持 1"
assert _composition(sources)["问诊通话"] == "3"
assert "soft-dice" in sources.meta.text()
def test_sources_reports_what_each_model_managed_to_read(sources: pages.SourcesPage) -> None:
sources.set_batch(batch())
rows = {sources.per_model.item(row, 0).text(): (sources.per_model.item(row, 1).text(),
sources.per_model.item(row, 3).text())
for row in range(sources.per_model.rowCount())}
assert rows["千问"] == ("3", "75%")
assert "已读取 3" in sources.attachment_legend.text()
def test_sources_meta_states_the_cutoff_and_reads_codes_in_chinese(sources: pages.SourcesPage) -> None:
sources.set_batch(batch(cutoff_at="2026-09-10 15:29"))
text = sources.meta.text()
assert "资料截止:2026-09-10 15:29" in text
assert "覆盖状态:部分资料缺失" in text # partial 在覆盖语境里说的是资料,不是进度
assert "对照类型:最新资料对照" in text
sources.set_batch(batch())
assert "资料截止:—" in sources.meta.text()
def test_sources_stays_empty_without_a_batch(sources: pages.SourcesPage) -> None:
sources.set_batch({})
assert _composition(sources) == {}
assert sources.gaps.rowCount() == 0
assert sources.attachment_note.text() == "本次没有附件"
assert not sources.gap_note.isVisible()
def test_sources_puts_critical_gaps_first_and_counts_them(sources: pages.SourcesPage) -> None:
sources.set_batch(batch())
assert "关键" in sources.gaps.item(0, 1).text()
assert "3 项 · 关键 2" in sources.gap_title.findChildren(QLabel)[-1].text()
# ---------------------------------------------------------------- progress page
@pytest.fixture
def progress(application: QApplication) -> pages.ProgressPage:
widget = pages.ProgressPage()
widget.resize(900, 600)
widget.show()
application.processEvents()
yield widget
widget.close()
def test_progress_lists_every_call_with_its_outcome(progress: pages.ProgressPage) -> None:
progress.set_batch(batch())
assert progress.calls.rowCount() == 2
assert progress.calls.item(0, 0).text() == "千问"
assert progress.calls.item(0, 1).text() == "文字资料 1" # 阶段键不直接露出
assert progress.calls.item(1, 1).text() == "生成候选与报告"
assert progress.calls.item(0, 3).text() == "3.4 s"
assert progress.calls.item(0, 6).text() == "通过"
assert progress.calls.item(1, 5).text() == "5617"
# 最慢的一次调用占满耗时分布条,其余按比例
assert progress.calls.cellWidget(1, 2)._fraction == 1.0
assert progress.calls.cellWidget(0, 2)._fraction < 0.3
assert "INVALID_REPORT_OUTPUT" not in progress.calls.item(1, 5).text()
def test_progress_shows_each_model_stage_and_attempt(progress: pages.ProgressPage) -> None:
progress.set_batch(batch())
assert "处理完成" in progress.stage_labels["qwen"].text()
assert "第 1 次尝试" in progress.stage_labels["qwen"].text()
assert "已用时 2 分 15 秒" in progress.stage_labels["qwen"].text()
assert "已用时 9 分 53 秒" in progress.stage_labels["openai"].text()
# ---------------------------------------------------------------- history page
@pytest.fixture
def history(application: QApplication) -> pages.HistoryPage:
widget = pages.HistoryPage()
widget.resize(900, 600)
widget.show()
application.processEvents()
yield widget
widget.close()
def test_history_orders_by_time_and_keeps_uncomparable_slots_empty(history: pages.HistoryPage) -> None:
history.set_history([
{"id": 13, "created_at": 300, "status": "success", "comparison_type": "latest_context",
"models": {"qwen": {"score": 18.9, "comparison_status": "comparable",
"algorithm_version": "prescription-soft-dice-v1.1.0"},
"openai": {"score": 16.7, "comparison_status": "comparable"}}},
{"id": 11, "created_at": 100, "status": "failed",
"models": {"qwen": {"score": None, "comparison_status": "not_comparable"},
"openai": {"score": None, "comparison_status": "not_comparable"}}},
])
description = history.chart.accessibleDescription()
assert description.index("11") < description.index("13") # oldest first on the chart
assert "11 千问 — OpenAI —" in description
assert history.table.item(0, 0).text() == "#13" # newest first in the list
assert history.table.item(0, 3).text() == "18.9%"
assert history.table.item(1, 3).text() == ""
assert history.table.item(0, 5).text() == "v1.1.0"
assert history.chart.has_data()
def test_history_reads_the_score_from_either_payload_shape(history: pages.HistoryPage) -> None:
"""列表接口把分数摊平,详情接口留在 comparison 里,两种都要认。"""
history.set_history([
{"id": 40, "created_at": "2026-09-10 15:29:00", "status": "success", "comparison_type": "non_independent",
"models": {"qwen": {"comparison": {"status": "comparable", "score": 94.44}},
"openai": {"comparison": {"status": "not_comparable", "score": 93.3}}}},
{"id": 39, "created_at": "2026-09-09 09:00:00", "status": "success",
"models": {"qwen": {"score": 42.9, "comparison_status": "comparable"}}},
])
assert history.table.item(0, 0).text() == "#40"
assert history.table.item(0, 3).text() == "94.4%"
assert history.table.item(0, 4).text() == "" # 不可比就不给分,哪怕载荷里带着数字
assert "分层" in history.strata.text() or "同一套" in history.strata.text()
assert history.table.item(1, 3).text() == "42.9%"
description = history.chart.accessibleDescription()
assert description.index("39") < description.index("40")
def test_history_without_any_comparable_batch_draws_nothing(history: pages.HistoryPage) -> None:
history.set_history([{"id": 1, "created_at": 1, "models": {"qwen": {"comparison_status": "not_comparable"}}}])
assert not history.chart.has_data()
assert history.table.rowCount() == 1
# ---------------------------------------------------------------- statistics panel
@pytest.fixture
def statistics(application: QApplication) -> pages.StatisticsPanel:
widget = pages.StatisticsPanel()
widget.resize(1000, 600)
widget.show()
application.processEvents()
yield widget
widget.close()
def statistics_payload() -> dict[str, Any]:
return {
"total_count": 10, "patient_count": 8,
"doctors": [
{"doctor_id": 26, "doctor_name": "何医生", "total_count": 6, "patient_count": 5, "paired_count": 3,
"models": {"qwen": {"eligible_count": 4, "mean": 18.4, "median": 16.9,
"excluded_reasons": {"SOURCE_HISTORY_VERSIONS_UNAVAILABLE": 2}},
"openai": {"eligible_count": 3, "mean": 21.0, "median": 19.6,
"excluded_reasons": {"SOURCE_HISTORY_VERSIONS_UNAVAILABLE": 3}}},
"review": {"evaluated_count": 0, "qualified_count": 0, "qualified_rate": None}},
{"doctor_id": 31, "doctor_name": "李医生", "total_count": 4, "patient_count": 3, "paired_count": 1,
"models": {"qwen": {"eligible_count": 2, "mean": 25.0, "excluded_reasons": {"incomplete_coverage": 2}},
"openai": {"eligible_count": 0, "mean": None, "excluded_reasons": {"incomplete_coverage": 4}}},
"review": {"evaluated_count": 2, "qualified_count": 1, "qualified_rate": 50.0}},
],
}
def test_statistics_headline_counts_and_coverage(statistics: pages.StatisticsPanel) -> None:
statistics.set_statistics(statistics_payload())
assert statistics.kpi_values["events"].text() == "10"
assert "涉及患者 8 人" in statistics.kpi_notes["events"].text()
assert statistics.kpi_values["qwen"].text() == "6"
assert "覆盖率 60.0%" in statistics.kpi_notes["qwen"].text()
assert statistics.kpi_values["openai"].text() == "3"
def test_statistics_review_rate_needs_a_review_sample(statistics: pages.StatisticsPanel) -> None:
payload = statistics_payload()
for doctor in payload["doctors"]:
doctor["review"] = {"evaluated_count": 0, "qualified_count": 0, "qualified_rate": None}
statistics.set_statistics(payload)
assert statistics.kpi_values["review"].text() == ""
assert "尚未建立复核样本" in statistics.kpi_notes["review"].text()
statistics.set_statistics(statistics_payload())
assert statistics.kpi_values["review"].text() == "50.0%"
def _bars(layout) -> list[str]:
return [layout.itemAt(index).widget().accessibleDescription() for index in range(layout.count())]
def test_statistics_funnel_and_exclusions_are_aggregated(statistics: pages.StatisticsPanel) -> None:
statistics.set_statistics(statistics_payload())
funnel = _bars(statistics.funnel_layout)
assert "范围内开方事件 10" in funnel
assert "千问 有效基线比较 6" in funnel
assert "两模型配对共同样本 4" in funnel
reasons = _bars(statistics.exclusion_layout)
assert any(text.endswith(" 5") for text in reasons) # 来源历史版本无法重建 2 + 3
assert all("SOURCE_HISTORY" not in text for text in reasons)
def test_statistics_distribution_sums_the_saved_bins(statistics: pages.StatisticsPanel) -> None:
payload = statistics_payload()
payload["doctors"][0]["models"]["qwen"]["distribution"] = {"[0,20)": 3, "[20,40)": 1}
payload["doctors"][1]["models"]["qwen"]["distribution"] = {"[0,20)": 2}
statistics.set_statistics(payload)
assert statistics.distribution.has_data()
assert "千问 5/1/0/0/0" in statistics.distribution.accessibleDescription()
assert statistics.distribution.isVisible()
def test_statistics_draws_no_distribution_without_samples(statistics: pages.StatisticsPanel) -> None:
statistics.set_statistics(statistics_payload())
assert not statistics.distribution.has_data()
assert statistics.chart_empty.isVisible()
def test_statistics_summary_row_pairs_mean_with_median(statistics: pages.StatisticsPanel) -> None:
statistics.set_statistics(statistics_payload())
assert statistics.summary_values["qwen"].text() == "21.7% / 16.9%"
assert statistics.summary_values["paired"].text() == "4 例"
def test_statistics_lists_each_doctor_without_ranking(statistics: pages.StatisticsPanel) -> None:
statistics.set_statistics(statistics_payload())
assert statistics.doctors.rowCount() == 2
assert statistics.doctors.item(0, 0).text() == "何医生"
assert statistics.doctors.item(0, 3).text() == "4 / 18.4%"
assert statistics.doctors.item(1, 4).text() == "0 / —"
assert statistics.doctors.item(1, 6).text() == "50.0%"
assert "不是医生准确率" in statistics.footnote.text()
def test_progress_counts_the_batch_in_the_stat_row(progress: pages.ProgressPage) -> None:
progress.set_batch(batch())
assert progress.stat_values["calls"].text() == "2 次"
assert progress.stat_values["failures"].text() == "1 次"
assert progress.stat_values["repairs"].text() == "0 次"
assert progress.stat_values["elapsed"].text() == "9 分 53 秒"
def test_progress_derives_its_stages_from_the_saved_calls(progress: pages.ProgressPage) -> None:
progress.set_batch(batch())
names = []
layout = progress.stage_lists["qwen"]
for index in range(layout.count()):
widget = layout.itemAt(index).widget()
names.append(widget.findChildren(QLabel)[1].text())
assert names == ["文字资料分析", "生成候选与报告"]
assert progress.stage_lists["openai"].count() == 0 # 没有调用记录就不编造阶段
def test_history_names_the_version_change_between_two_batches(history: pages.HistoryPage) -> None:
history.set_history([
{"id": 5, "created_at": "2026-09-10 15:29:00", "status": "success",
"models": {"qwen": {"comparison": {"status": "comparable", "score": 15.3},
"algorithm_version": "prescription-soft-dice-v1.1.0",
"prompt_version": "v4"}}},
{"id": 4, "created_at": "2026-09-10 15:18:00", "status": "success",
"models": {"qwen": {"comparison": {"status": "comparable", "score": 18.9},
"algorithm_version": "prescription-soft-dice-v1.0.1",
"prompt_version": "v3"}}},
])
text = history.strata.text()
assert "比较算法 v1.0.1 → v1.1.0" in text
assert "提示词 v3 → v4" in text
assert "不能直接相减" in text
def test_history_says_when_every_batch_shares_one_version(history: pages.HistoryPage) -> None:
history.set_history([
{"id": 2, "created_at": "2026-09-10 15:29:00",
"models": {"qwen": {"algorithm_version": "prescription-soft-dice-v1.1.0"}}},
{"id": 1, "created_at": "2026-09-10 14:29:00",
"models": {"qwen": {"algorithm_version": "prescription-soft-dice-v1.1.0"}}},
])
assert "全部批次使用同一套算法" in history.strata.text()
def test_history_shows_why_a_batch_has_no_score(history: pages.HistoryPage) -> None:
history.set_history([{"id": 2, "created_at": "2026-09-10 13:25:00", "status": "failed",
"models": {"qwen": {"error_message": "模型返回未通过校验"}}}])
assert "模型返回未通过校验" in history.table.item(0, 2).text()
assert history.table.item(0, 3).text() == ""
@@ -0,0 +1,337 @@
"""Data provenance and native UI checks for the prescription overview page."""
from __future__ import annotations
import os
from copy import deepcopy
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
import pytest
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget
from doctor_workstation.ui.dialogs.issued_prescription_ai_workspace import (
PrescriptionReviewWorkspace,
checklist_items,
diagnosis_text,
dose_deltas,
gap_counts,
review_rows,
)
def batch(count: int = 3) -> dict:
rows = []
herbs = []
for index in range(count):
herb = {"name": f"药材{index}", "dosage": "15.00", "unit": "g", "dose_basis": "per_dose", "formula_type": "主方", "processing": "生品"}
rows.append({"key": f"saved-identity-{index}", "name": herb["name"], "doctor": {**herb, "dosage": "30.00"}, "candidate": {**herb, "source_rows": [index]}, "match_type": "matched"})
herbs.append(herb)
model = {"status": "succeeded", "report": {"diagnosis": "已保存的辨证意见", "summary": "不能当作诊断的摘要", "missing_information": ["缺少舌脉记录"]}, "candidate": {"status": "available_for_review", "herbs": herbs}, "comparison": {"status": "comparable", "rows": rows}}
return {"id": 4, "validity": "current", "models": {"qwen": deepcopy(model), "openai": deepcopy(model)}, "doctor_snapshot": {"patient": {"name": "测试患者", "gender": "male", "age": 50}, "diagnosis": {"western_diagnosis": "已记录西医诊断", "tcm_diagnosis": "已记录中医诊断", "syndrome": "已记录证候"}, "prescription": {"herbs": deepcopy(herbs), "usage_instruction": "水煎服", "usage_days": 7, "times_per_day": 2}}}
@pytest.fixture(scope="module")
def application():
return QApplication.instance() or QApplication([])
@pytest.fixture
def workspace(application):
host = QWidget()
host.resize(1024, 760)
layout = QVBoxLayout(host)
layout.setContentsMargins(0, 0, 0, 0)
widget = PrescriptionReviewWorkspace(host)
layout.addWidget(widget)
host.show()
application.processEvents()
yield widget
host.close()
host.deleteLater()
application.processEvents()
def test_three_series_need_saved_unique_identity_and_same_baseline():
source = batch()
before = deepcopy(source)
rows, _ = review_rows(source)
assert source == before
assert len(rows) == 3
assert all(set(row.doses) == {"doctor", "qwen", "openai"} for row in rows)
assert rows[0].scale == ("", "每剂")
assert rows[0].changed
assert "15.00 克 / 每剂" in rows[0].description
assert "候选原方记录:第 1 项 药材0" in rows[0].description
assert "saved-identity" not in rows[0].description
@pytest.mark.parametrize("mutation", ["no_keys", "different_baseline", "duplicate_keys", "different_units", "different_identity"])
def test_no_guessed_cross_model_join(mutation):
source = batch(1)
target = source["models"]["openai"]["comparison"]["rows"][0]
if mutation == "no_keys":
for model in source["models"].values():
model["comparison"]["rows"][0].pop("key")
elif mutation == "different_baseline":
target["doctor"]["dosage"] = 31
elif mutation == "duplicate_keys":
source["models"]["openai"]["comparison"]["rows"].append(deepcopy(target))
elif mutation == "different_units":
target["candidate"]["unit"] = "mg"
else:
target["candidate"]["processing"] = "炙品"
rows, _ = review_rows(source)
assert len(rows) >= 2
assert all(len(row.entries) == 1 for row in rows)
@pytest.mark.parametrize("value", ["nan", "-3", "1/2", "Infinity", None])
def test_invalid_numbers_and_missing_are_never_zero(value):
source = batch(1)
for model in source["models"].values():
model["comparison"]["rows"][0]["candidate"]["dosage"] = value
rows, _ = review_rows(source)
assert rows[0].scale is None
assert not rows[0].changed
if value is None:
assert rows[0].entries["qwen"].candidate.label == ""
@pytest.mark.parametrize("field,value", [("validity", "stale"), ("validity", "source_updated"), ("validity", None), ("status", "running"), ("status", "failed"), ("comparison", "not_comparable"), ("candidate", "withheld_for_risk")])
def test_pending_historical_and_error_are_text_only(field, value):
source = batch()
if field == "validity":
source[field] = value
else:
for model in source["models"].values():
if field == "status":
model[field] = value
else:
model[field]["status"] = value
rows, _ = review_rows(source)
assert rows
assert all(row.scale is None for row in rows)
assert all(not row.changed for row in rows)
def test_unaccounted_candidates_and_missing_trace_survive():
source = batch(1)
for model in source["models"].values():
model["candidate"]["herbs"].append({"name": "未规范炮制药", "dosage": "2.5", "unit": "g"})
model["comparison"]["rows"][0]["candidate"].pop("source_rows")
rows, _ = review_rows(source)
assert len(rows) == 5
originals = [row for row in rows if next(iter(row.entries.values())).origin != "comparison"]
assert len(originals) == 4
assert all(row.scale is None for row in originals)
assert sum(row.name == "未规范炮制药" for row in originals) == 2
def test_diagnosis_never_fabricated_from_summary():
assert diagnosis_text({"report": {"summary": "摘要疾病"}}) == "诊断意见未保存"
assert diagnosis_text({"report": {"diagnosis": "辨证原文"}}) == "辨证原文"
assert diagnosis_text({"report": {"diagnosis": {"western_diagnosis": "诊断原值"}}}) == "西医诊断:诊断原值"
def test_missing_side_is_not_a_zero_dose_difference():
source = batch(1)
for model in source["models"].values():
model["comparison"]["rows"][0]["doctor"] = None
rows, _ = review_rows(source)
assert rows[0].doctor is None
assert not rows[0].changed
def test_historical_report_states_itself_instead_of_reporting_zero_differences(workspace):
source = batch()
source["validity"] = "stale"
workspace.set_batch(source)
assert "历史或失效报告" in workspace.summary_text
assert workspace.doses.rows == [] # 不可比就不画,不是画成 0
assert workspace.doses.empty.isVisible()
def test_每味药与原方的距离按共同标尺画出(workspace):
workspace.set_batch(batch())
assert [delta.name for delta in workspace.doses.rows] == ["药材0", "药材1", "药材2"]
assert all(delta.deltas["qwen"] == -15 for delta in workspace.doses.rows)
assert "3 项剂量差异" in workspace.summary_text
axis = workspace.doses.body.findChildren(QWidget)
described = [widget.accessibleDescription() for widget in axis if widget.accessibleName() == "剂量差异条"]
assert "药材0 千问 15克 OpenAI 15克" in described
def test_a_herb_one_model_never_listed_is_marked_absent_not_zero(workspace):
source = batch(1)
source["models"]["openai"]["comparison"]["rows"][0]["candidate"] = None
source["models"]["openai"]["candidate"]["herbs"] = []
workspace.set_batch(source)
delta = workspace.doses.rows[0]
assert delta.deltas["openai"] is None
assert delta.badge == "OpenAI 未收录"
assert delta.deltas["qwen"] == -15
def test_an_unreadable_saved_dose_takes_the_whole_row_off_the_chart(workspace):
source = batch(1)
source["models"]["openai"]["comparison"]["rows"][0]["candidate"]["dosage"] = "nan"
workspace.set_batch(source)
assert workspace.doses.rows == []
def test_an_incomparable_basis_keeps_the_row_off_the_chart(workspace):
source = batch(1)
for model in source["models"].values():
model["comparison"]["rows"][0]["doctor"]["dose_basis"] = "per_day"
workspace.set_batch(source)
assert workspace.doses.rows == []
def test_a_pending_model_adds_no_row_and_no_difference(workspace):
source = batch(1)
source["models"]["openai"] = {"status": "running"}
workspace.set_batch(source)
assert len(workspace.doses.rows) == 1
assert workspace.doses.rows[0].deltas["openai"] is None
def test_new_herbs_carry_their_full_dose_and_say_which_model_added_them(workspace):
source = batch(1)
for model in source["models"].values():
model["comparison"]["rows"][0]["doctor"] = None
workspace.set_batch(source)
delta = workspace.doses.rows[0]
assert delta.doctor is None
assert delta.deltas["qwen"] == 15
def test_gap_counts_split_the_saved_gaps_into_three_buckets() -> None:
source = batch(1)
source["missing"] = [{"code": "TRANSCRIPT_NOT_VERIFIED_COMPLETE", "critical": True},
{"code": "ATTACHMENT_STORAGE_RESTRICTED"},
{"code": "ARCHIVE_SYNC_WATERMARK_UNAVAILABLE"}]
assert gap_counts(source) == {"critical": 1, "attachment": 1, "other": 1}
assert gap_counts(batch(1)) == {"critical": 0, "attachment": 0, "other": 0}
def _conclusion_texts(workspace) -> list[str]:
"""The numbered sentences, skipping the hairlines the design puts between them."""
texts = []
for index in range(workspace.conclusions.body_layout.count()):
widget = workspace.conclusions.body_layout.itemAt(index).widget()
labels = widget.findChildren(QLabel) if widget is not None else []
if labels:
texts.append(labels[-1].text())
return texts
def test_conclusions_state_the_counts_they_were_built_from(workspace) -> None:
workspace.set_batch(batch(3))
texts = _conclusion_texts(workspace)
assert any("两个模型都给出了候选方" in text for text in texts)
assert any("剂量偏差共" in text for text in texts)
assert any("没有记录资料缺口" in text for text in texts)
def test_conclusions_never_invent_a_score_comparison(workspace) -> None:
source = batch(1)
source["models"]["openai"]["comparison"]["status"] = "not_comparable"
workspace.set_batch(source)
texts = _conclusion_texts(workspace)
assert any("只有可比的一侧有分数" in text for text in texts)
def test_attribution_groups_every_herb_exactly_once(workspace) -> None:
source = batch(2)
source["models"]["openai"]["candidate"]["herbs"] = [{"name": "药材0", "dosage": "15.00", "unit": "g"}]
workspace.set_batch(source)
assert workspace.attribution.rows["all"]["count"].text() == "1 味"
assert workspace.attribution.rows["doctor_only"]["count"].text() == "0 味"
assert workspace.attribution.rows["openai_only"]["count"].text() == "0 味"
# 药材1 只有医方与千问共用,四个分组都不含它,标题必须说明这一点
assert "合计 2 味" in workspace.attribution.total_note.text()
assert "另 1 味为医方与单一模型共用" in workspace.attribution.total_note.text()
def test_risk_panel_flags_only_large_changes(workspace) -> None:
small = batch(1)
for model in small["models"].values():
model["comparison"]["rows"][0]["candidate"]["dosage"] = "29.00"
workspace.set_batch(small)
assert workspace.risks.empty.isVisible() # 差 1 克不进清单
large = batch(1)
for model in large["models"].values():
model["comparison"]["rows"][0]["candidate"]["dosage"] = "12.00"
workspace.set_batch(large)
assert workspace.risks.body.isVisible()
assert "2 项" in workspace.risks.hint.text()
def test_checklist_merges_the_models_and_keeps_the_severe_items_first():
source = batch(1)
source["models"]["qwen"]["report"]["risk_assessment"] = [{"level": "high", "label": "血压数据缺失"}]
source["missing"] = [{"code": "TRANSCRIPT_NOT_VERIFIED_COMPLETE", "critical": True}]
items = checklist_items(source)
assert items[0][0] == "血压数据缺失"
assert items[0][1] == "千问 关键"
shared = next(item for item in items if item[0] == "缺少舌脉记录")
assert shared[1] == "千问、OpenAI"
assert any("转写" in item[0] for item in items)
def test_checklist_shows_the_saved_items_and_says_so_when_empty(workspace):
workspace.set_batch(batch())
assert "1 条 · 按严重度排序" in workspace.checklist.count_note.text()
assert not workspace.checklist.empty.isVisible()
workspace.set_batch({"id": 9, "validity": "current", "models": {}})
assert workspace.checklist.empty.isVisible()
assert workspace.checklist.count_note.text() == "暂无待确认项"
def test_rerendering_leaves_no_stale_row_widgets(workspace, application):
workspace.set_batch(batch(20))
application.processEvents()
assert len(_axes(workspace)) == 20
workspace.set_batch(batch(3))
application.processEvents()
assert len(_axes(workspace)) == 3
def _axes(workspace):
return [widget for widget in workspace.doses.body.findChildren(QWidget)
if widget.accessibleName() == "剂量差异条" and widget.parentWidget() is not None]
def test_page_fits_a_1024_window_without_horizontal_scrolling(workspace, application):
workspace.set_batch(batch())
application.processEvents()
assert workspace.minimumSizeHint().width() <= 900
assert workspace.width() == 1024
assert workspace.doses.scroll.horizontalScrollBarPolicy() == Qt.ScrollBarPolicy.ScrollBarAlwaysOff
assert workspace.checklist.scroll.horizontalScrollBarPolicy() == Qt.ScrollBarPolicy.ScrollBarAlwaysOff
def test_dose_deltas_ignore_rows_without_a_shared_scale():
source = batch(1)
for model in source["models"].values():
model["comparison"]["rows"][0]["candidate"]["unit"] = "mg"
rows, _states = review_rows(source)
assert dose_deltas(rows) == []
def test_review_slot_has_no_visible_parentless_widget(workspace, application):
before = {widget for widget in application.topLevelWidgets() if widget.isVisible()}
first = QLabel("第一模型复核", workspace)
second = QLabel("双模型复核", workspace)
workspace.set_review_widget(first)
workspace.set_review_widget(second)
application.processEvents()
assert second.parentWidget() is workspace.checklist.review_slot
assert not first.isVisible()
assert first.parentWidget() is workspace.checklist.review_slot
assert {widget for widget in application.topLevelWidgets() if widget.isVisible()} == before
@@ -23,7 +23,6 @@ from doctor_workstation.ui.pages import prescription_library as library_module
from doctor_workstation.ui.pages import prescriptions as prescriptions_module from doctor_workstation.ui.pages import prescriptions as prescriptions_module
from doctor_workstation.ui.pages.prescription_library import PrescriptionLibraryPage from doctor_workstation.ui.pages.prescription_library import PrescriptionLibraryPage
from doctor_workstation.ui.pages.prescriptions import PrescriptionsPage from doctor_workstation.ui.pages.prescriptions import PrescriptionsPage
from doctor_workstation.ui.widgets import BusinessPager
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

@@ -0,0 +1,42 @@
........................................................................ [ 5%]
........................................................................ [ 11%]
.....................................................................F.. [ 16%]
........................................................................ [ 22%]
...................................Windows fatal exception: code 0xc0000374
Thread 0x00006580 (most recent call first):
File "D:\web\zyt\app\tests\test_diagnosis_drawer_visual.py", line 610 in _open_dialog
File "D:\web\zyt\app\tests\test_diagnosis_drawer_visual.py", line 643 in test_readonly_scroll_keeps_close_controls_reachable
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\python.py", line 157 in pytest_pyfunc_call
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_callers.py", line 121 in _multicall
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_manager.py", line 120 in _hookexec
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_hooks.py", line 512 in __call__
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\python.py", line 1671 in runtest
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\runner.py", line 178 in pytest_runtest_call
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_callers.py", line 121 in _multicall
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_manager.py", line 120 in _hookexec
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_hooks.py", line 512 in __call__
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\runner.py", line 246 in <lambda>
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\runner.py", line 344 in from_call
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\runner.py", line 245 in call_and_report
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\runner.py", line 136 in runtestprotocol
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\runner.py", line 117 in pytest_runtest_protocol
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_callers.py", line 121 in _multicall
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_manager.py", line 120 in _hookexec
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_hooks.py", line 512 in __call__
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\main.py", line 367 in pytest_runtestloop
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_callers.py", line 121 in _multicall
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_manager.py", line 120 in _hookexec
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_hooks.py", line 512 in __call__
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\main.py", line 343 in _main
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\main.py", line 289 in wrap_session
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\main.py", line 336 in pytest_cmdline_main
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_callers.py", line 121 in _multicall
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_manager.py", line 120 in _hookexec
File "D:\web\zyt\app\.venv\Lib\site-packages\pluggy\_hooks.py", line 512 in __call__
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\config\__init__.py", line 175 in main
File "D:\web\zyt\app\.venv\Lib\site-packages\_pytest\config\__init__.py", line 201 in console_main
File "D:\web\zyt\app\.venv\Lib\site-packages\pytest\__main__.py", line 9 in <module>
File "<frozen runpy>", line 88 in _run_code
File "<frozen runpy>", line 198 in _run_module_as_main
.
@@ -0,0 +1,173 @@
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[40001]
PRESCRIPTION_AI {"lane":"openai","enabled":true,"processed":true}
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
@@ -0,0 +1,172 @@
PRESCRIPTION_AI {"lane":"openai","enabled":true,"processed":true}
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
@@ -0,0 +1,172 @@
PRESCRIPTION_AI {"lane":"openai","enabled":true,"processed":true}
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
@@ -0,0 +1,172 @@
PRESCRIPTION_AI {"lane":"openai","enabled":true,"processed":true}
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
@@ -0,0 +1,37 @@
[
{
"lane": "prepare",
"instance": 1,
"pid": 1160,
"stdout": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\prepare-1-parallel-20260910-171432.stdout.log",
"stderr": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\prepare-1-parallel-20260910-171432.stderr.log"
},
{
"lane": "qwen",
"instance": 1,
"pid": 25112,
"stdout": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\qwen-1-parallel-20260910-171432.stdout.log",
"stderr": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\qwen-1-parallel-20260910-171432.stderr.log"
},
{
"lane": "qwen",
"instance": 2,
"pid": 25352,
"stdout": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\qwen-2-parallel-20260910-171432.stdout.log",
"stderr": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\qwen-2-parallel-20260910-171432.stderr.log"
},
{
"lane": "openai",
"instance": 1,
"pid": 25156,
"stdout": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\openai-1-parallel-20260910-171432.stdout.log",
"stderr": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\openai-1-parallel-20260910-171432.stderr.log"
},
{
"lane": "openai",
"instance": 2,
"pid": 25188,
"stdout": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\openai-2-parallel-20260910-171432.stdout.log",
"stderr": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\openai-2-parallel-20260910-171432.stderr.log"
}
]
@@ -0,0 +1,175 @@
PRESCRIPTION_AI {"lane":"prepare","enabled":true,"processed":true}
PRESCRIPTION_AI {"lane":"prepare","enabled":true,"processed":true}
PRESCRIPTION_AI {"lane":"prepare","enabled":true,"processed":true}
PRESCRIPTION_AI {"lane":"prepare","enabled":true,"processed":true}
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
@@ -0,0 +1,171 @@
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
@@ -0,0 +1 @@
29924
@@ -1 +1 @@
28108 20460
@@ -0,0 +1,171 @@
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
@@ -0,0 +1,173 @@
PRESCRIPTION_AI {"lane":"qwen","enabled":true,"processed":true}
PRESCRIPTION_AI {"lane":"qwen","enabled":true,"processed":true}
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
@@ -0,0 +1,172 @@
PRESCRIPTION_AI {"lane":"qwen","enabled":true,"processed":true}
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
@@ -0,0 +1,173 @@
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[40001]
PRESCRIPTION_AI {"lane":"qwen","enabled":true,"processed":true}
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
PRESCRIPTION_AI storage_or_configuration_error think\db\exception\PDOException@PDOConnection.php:836 SQLSTATE[HY000] [2002]
@@ -0,0 +1,45 @@
# Start prescription AI consumers.
# -Mode Restart : stop every existing consumer first (use only when no task is running)
# -Mode Add : leave running consumers alone and add missing instances
# Concurrency needs one process per concurrent task: keep the per-lane counts at or above
# prescription_analysis.max_parallel_per_model.
param(
[ValidateSet('Restart', 'Add')][string]$Mode = 'Add',
[int]$Prepare = 2,
[int]$Qwen = 4,
[int]$OpenAi = 4
)
$ErrorActionPreference = 'Stop'
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$art = 'D:\web\zyt\artifacts\prescription-ai-runtime'
$php = 'D:\phpstudy_pro\Extensions\php\php8.2.9nts\php.exe'
$server = 'D:\web\zyt\server'
$plan = @(
@{ lane = 'prepare'; count = $Prepare },
@{ lane = 'qwen'; count = $Qwen },
@{ lane = 'openai'; count = $OpenAi }
)
$records = @()
foreach ($item in $plan) {
$lane = $item.lane
$existing = @(Get-CimInstance Win32_Process -Filter "Name='php.exe'" |
Where-Object { $_.CommandLine -like "*prescription-ai:work*--lane=$lane*" })
if ($Mode -eq 'Restart') {
foreach ($p in $existing) { Stop-Process -Id $p.ProcessId -Force }
Start-Sleep -Milliseconds 400
$existing = @()
}
for ($i = $existing.Count + 1; $i -le $item.count; $i++) {
$out = Join-Path $art "$lane-$i-$stamp.stdout.log"
$err = Join-Path $art "$lane-$i-$stamp.stderr.log"
$proc = Start-Process -FilePath $php -ArgumentList @('think', 'prescription-ai:work', "--lane=$lane") `
-WorkingDirectory $server -WindowStyle Hidden -RedirectStandardOutput $out -RedirectStandardError $err -PassThru
$records += [pscustomobject]@{ lane = $lane; instance = $i; pid = $proc.Id; stdout = $out; stderr = $err }
}
}
$path = Join-Path $art "worker-start-$stamp.json"
$records | ConvertTo-Json -Depth 4 | Out-File -FilePath $path -Encoding utf8
Write-Output "mode=$Mode record=$path started=$($records.Count)"
Get-CimInstance Win32_Process -Filter "Name='php.exe'" |
Where-Object { $_.CommandLine -like '*prescription-ai:work*' } |
ForEach-Object { Write-Output ("{0} {1}" -f $_.ProcessId, ($_.CommandLine -replace '.*--lane=', 'lane=')) }
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
// Read-only concurrency probe: samples how many model tasks hold a live lease at the same time.
// Prints task metadata only; no clinical content.
$serverRoot = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'server' . DIRECTORY_SEPARATOR;
chdir($serverRoot);
require $serverRoot . 'vendor/autoload.php';
$app = new \think\App($serverRoot);
$app->initialize();
use think\facade\Db;
$seconds = (int) ($argv[1] ?? 120);
$deadline = time() + $seconds;
$peak = ['total' => 0, 'qwen' => 0, 'openai' => 0];
$samples = [];
while (time() <= $deadline) {
$now = time();
$rows = Db::name('prescription_ai_task')->where('status', 'running')->where('lock_until', '>', $now)
->field('id,batch_id,model_key')->select()->toArray();
$byModel = array_count_values(array_column($rows, 'model_key'));
$peak['total'] = max($peak['total'], count($rows));
foreach (['qwen', 'openai'] as $model) {
$peak[$model] = max($peak[$model], (int) ($byModel[$model] ?? 0));
}
$samples[] = ['at' => date('H:i:s', $now), 'running' => count($rows),
'qwen' => (int) ($byModel['qwen'] ?? 0), 'openai' => (int) ($byModel['openai'] ?? 0),
'batches' => array_values(array_unique(array_map('intval', array_column($rows, 'batch_id')))),
'queued' => Db::name('prescription_ai_task')->whereIn('status', ['queued', 'retry_wait'])->count()];
if (count($samples) > 1 && $samples[count($samples) - 1] === $samples[count($samples) - 2]) {
array_pop($samples);
}
if ($peak['total'] > 0 && count($rows) === 0 && (int) end($samples)['queued'] === 0) {
break;
}
sleep(5);
}
echo json_encode(['peak' => $peak, 'samples' => array_slice($samples, -40)],
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
@@ -0,0 +1,37 @@
[
{
"lane": "prepare",
"instance": 2,
"pid": 20680,
"stdout": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\prepare-2-20260910-172227.stdout.log",
"stderr": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\prepare-2-20260910-172227.stderr.log"
},
{
"lane": "qwen",
"instance": 3,
"pid": 25864,
"stdout": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\qwen-3-20260910-172227.stdout.log",
"stderr": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\qwen-3-20260910-172227.stderr.log"
},
{
"lane": "qwen",
"instance": 4,
"pid": 21684,
"stdout": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\qwen-4-20260910-172227.stdout.log",
"stderr": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\qwen-4-20260910-172227.stderr.log"
},
{
"lane": "openai",
"instance": 3,
"pid": 25824,
"stdout": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\openai-3-20260910-172227.stdout.log",
"stderr": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\openai-3-20260910-172227.stderr.log"
},
{
"lane": "openai",
"instance": 4,
"pid": 22168,
"stdout": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\openai-4-20260910-172227.stdout.log",
"stderr": "D:\\web\\zyt\\artifacts\\prescription-ai-runtime\\openai-4-20260910-172227.stderr.log"
}
]
+210
View File
@@ -278,3 +278,213 @@ php server/tests/PrescriptionAiPipelineTest.php
6. **复核条**:两个模型的复核状态、意见、保存与重试压缩到一行,原来纵向占用的约 200px 让给报告正文。免责声明降为脚注。 6. **复核条**:两个模型的复核状态、意见、保存与重试压缩到一行,原来纵向占用的约 200px 让给报告正文。免责声明降为脚注。
改动只在展示层:接口、轮询、权限、进度语义和落库数据都没变;`model_views` 的既有键全部保留,新增 `score``status_chip``card``model_label`。97 项桌面回归与 Ruff 通过,示意图见 `app/artifacts/issued-prescription-ai/redesign-running-20260910.png``redesign-finished-20260910.png`(合成数据,无网络)。 改动只在展示层:接口、轮询、权限、进度语义和落库数据都没变;`model_views` 的既有键全部保留,新增 `score``status_chip``card``model_label`。97 项桌面回归与 Ruff 通过,示意图见 `app/artifacts/issued-prescription-ai/redesign-running-20260910.png``redesign-finished-20260910.png`(合成数据,无网络)。
## 2026-09-10 三栏对照工作区与医生原方快照
上一版把两个模型的分数做成了重点,但医生仍要靠记忆比对自己开的方。本轮把"医生原方"接进窗口,改成一次看三列。
**服务端:报告详情新增 `doctor_snapshot`。** 由批次入队时冻结的处方密文投影而来(`PrescriptionAiDoctorSnapshot::project()`),因此模型还没开始就能显示,且选择历史批次时始终是当时那一版——线上处方后来被改动也不会回写。投影只保留展示所需的标量字段:患者姓名/性别/年龄、`clinical_diagnosis`、剂型与用法、逐味药名剂量单位炮制与煎服说明、独立的辅方用法;明确排除电话、签名、`case_record`、药材 ID 等。权限沿用 `loadBatch`(当前处方 + 历史来源双重校验),列表与状态摘要不带该字段。源数据没有的单位或中西医诊断拆分一律不补造。
**桌面端:`PrescriptionReviewWorkspace` 三栏工作区。**
- 左栏"医生原方":患者、医生诊断、逐味药方与用法,按保存顺序原样展示(含重复行)。
- 中栏"诊断对照 + 逐味剂量差异":两个模型的辨证结论并排,下面是逐味剂量点线图(医生原方 / 千问 / OpenAI 三点同轴),支持药名搜索与"仅看剂量差异"过滤,未保存值显示"—"。
- 右栏"复核笔记":待确认差异、已保存对比计数、复核状态与意见表单。
- 顶部导航收敛为"对比总览 / 完整报告 / 资料记录"三个主入口,方义与用法、逐味明细、处理进度、重新分析移入"更多内容"菜单;上方"本次关注"一行给出本批次最该看的一句话。
展示层之外没有改动:轮询、权限、进度语义、比较算法与落库数据均不变。
本轮验证:桌面 182 项(报告窗口 / 对照组件 / 工作区三个套件)通过,Ruff 通过(顺手修了本轮编辑引入的一处 import 排序);后端 12 个离线套件通过,独立 MySQL 上 Queue 78 / Pipeline 90,旧结构 Queue 76 / Pipeline 85 通过,其中新增了原方快照的隐私、权限、不可变与"不补造字段"检查。界面渲染脚本 `app/scripts/render_issued_prescription_ai.py` 用合成数据离线产出 1024/1280/1440 三种宽度及部分完成、历史、等待中状态的截图。
## 2026-09-10 并发生成:多张处方同时分析
此前每个模型最多只跑 1~2 个任务,多张处方只能排队等待,界面上就表现为"一次只能生成一个"。本轮把并发做成真正可调的能力:
- `prescription_analysis.max_parallel_per_model` 默认提到 **4**,并改为环境变量 `prescription_analysis.MAX_PARALLEL_PER_MODEL` 可调(最小 1)。该值只是上限,**每一个并发任务都需要一个消费者进程**:本地按 `prepare×2、qwen×4、openai×4` 启动,启动脚本 `artifacts/prescription-ai-runtime/start_workers.ps1` 支持 `-Mode Add`(保留在跑的进程,补齐缺的实例)与 `-Mode Restart`(全部重启,仅在没有任务运行时使用)。
- `daily_model_tasks``daily_patient_batches` 同样改为环境变量可调;单张处方每日分析上限由 10 提到 20——每次修复后重新分析是正常复核方式,这是预算护栏而不是正确性限制。超限时接口仍明确返回"今日分析次数已达预算"。
- **资料扫描不再挡住新处方。** 准备通道原来在同一轮里既领批次又跑来源/处方扫描,而扫描会重建完整上下文;现在扫描只在准备通道无事可做时执行,刚保存的处方永远优先被领取。
实测:同时对 3 张处方发起重新分析,加上原有 1 个在跑的任务,**7 个模型任务并行执行**(千问 3 + OpenAI 4),队列为空,无失败。批次 13(处方 7556,今天反复失败的那张)两个模型都完成:千问 18.88%、OpenAI 16.73%,均为可比。批次 14/15/16 的千问侧一致度分别为 19.29%、42.97%、17.36%OpenAI 侧三个任务同时在跑。
线上同步部署时,需要在进程守护器里为每个模型通道配置与 `max_parallel_per_model` 相同数量的实例,并确认上游应用与账号的并发额度;数据库连接数也要留出余量(每个消费者一条长连接)。
`PrescriptionAiWorkerResilienceTest` 增加 4 项契约检查(并发下限、三个预算/并发值可由环境变量覆盖、扫描仅在空闲时执行),共 11 项。
## 2026-09-10 科技蓝指标条与图表
报告窗口补上"一眼看懂"的一层:在三栏工作区之上增加科技蓝指标条(`issued_prescription_ai_metrics.py`),全部取自已保存的不可变报告,不新增任何接口调用。
- **两张模型卡**:模型名 + 状态药丸 + 覆盖情况;大号一致度数字与刻度条(千问 #1769E8、OpenAI #0E9384);下面是药味构成堆叠条与图例——医生独有 / 共同 / 该模型独有,并标出药味重合度。没有可比结果时数字为灰色"—"、刻度条留空,绝不显示 0%。
- **历史一致度折线**:来自历史批次列表(按时间从左到右),不可比的批次断线不补点;这是真正能看出"这次比上次接近还是更远"的图。
- **资料覆盖卡**:附件已读 / 受限不支持堆叠条、缺口总数与其中关键项数、资料构成(病历、医生备注、历史处方、问诊通话、监测记录、聊天记录、记录合计)。
- **自适应高度**:窗口高度小于 780 时指标条收起副图(折线、构成条、阶段行),只留数字与刻度条,把首屏让给处方工作区;`MAX_HEIGHT` / `COMPACT_HEIGHT` 有回归测试守住。
同时修掉一个真实显示缺陷:逐味剂量差异表的列宽按"尚未布局完成的视口宽度"按比例计算,结果所有列被压到二三十像素,药名一个字一行、剂量显示成省略号。改为药名列与模型列有最小宽度、图表列吸收剩余宽度,窗口过窄时优先保证药名和剂量可读,并在 `showEvent` 里再测一次。
新增 `app/tests/test_issued_prescription_ai_metrics.py` 19 项检查(空值不变 0、0% 是真实测量、共同药味不超过任一侧、覆盖与缺口计数、历史折线时间顺序、紧凑模式取舍、百分比格式边界)。四个报告窗口相关套件共 **201 项**通过,Ruff 通过。
需要注意:本地客户端连接的是线上 `admin.zhenyangtang.com.cn`,而**医生原方快照(`doctor_snapshot`)尚未部署到线上**,所以左栏会显示"原方快照未保存",图表只能回退到对比结果里记录的医生剂量。要看到完整左栏,需要把服务端这轮代码部署上线。
## 2026-09-11 报告窗口改版落地(第一批)
按确认的设计稿开始落地,配色全部取自工作站的 `reception_style.TECH_BLUE`(主色 #1769E8、深色 #124EA9、画布 #F3F7FD、线条 #DBE5F2),OpenAI 侧继续用 #0E9384 区分。设计稿 `app/artifacts/ui-mockups/v2/` 已同步换色。
**① 蓝色渐变头部条。** 面包屑、标题、版本与对照类型药丸、批次选择与刷新/重新分析/保存复核、六列信息带(处方·诊单 / 患者 / 临床诊断 / 剂型·剂数 / 资料截止 / 比较口径)以及主导航全部收进同一条渐变带,下方白色区域完整留给处方内容。信息带取自冻结快照,线上未部署 `doctor_snapshot` 时逐项显示"—",不猜测。窗口高度小于 780 时信息带与副标题自动收起,保证处方工作区仍占首屏一半以上(有回归测试守住)。
**② 模型失败可就地重试。** 失败原因从隐藏的进度页移到该模型卡片上:中文原因 + 「重试 千问 / 重试 OpenAI」按钮同行显示,只重试该模型并使用原资料快照;手动重试额度用尽时按钮隐藏,改为提示改用"重新分析"。无权限、批次过期、进度暂停时按钮不可用。
**③ 新图表进入程序。** 新增 `issued_prescription_ai_charts.py`QPainter 自绘):
- `VennChart` 三方用药交集——由两个模型的候选药味与对比行中的医生药味算出七个区域,无候选方时不画空图;
- `WaffleCoverage` 附件华夫图——一格一个附件,蓝=模型已读、橙=受限或不支持;
- `DivergingDoses` 剂量差异发散条——已实现待接入逐味页。
回归:报告窗口相关四个套件 **208 项通过**,Ruff 通过。后续按设计稿推进:逐味页发散条与贡献列、资料与缺口页、处理进度页、历史与趋势页、统计页。
## 2026-09-11 报告窗口改版落地(第二批,全部页面完成)
第一批只完成了头部条、失败重试与三个图表控件;这一批把设计稿里剩下的页面全部做进程序,并顺手修掉改版过程中暴露出来的几处旧问题。
**① 导航按目的地命名,不再按下标。** 新增页面后原来写死的 `setCurrentIndex(2/4/6)` 全部错位("资料记录"实际跳到了原文页,"处理进度"出现了两个同名标签)。改成 `tab_pages` 字典 + `_goto(key)`:主导航是 `对比总览 / 逐味明细 / 资料与缺口 / 历史与趋势``更多内容` 菜单收 `方义与用法 / 综合分析 / 处理进度 / 原方记录 / 逐味原文 / 来源原文`。工作区内的"查看依据"链接现在指向新的组合页而不是原始文本页。回归测试断言标签名不重复、每个链接落到正确的页面。
**② 逐味明细页。** 发散条形图(相对医生原方,只画两侧都有剂量的药味)+ 合并表:一行一味药,列出医生 / 千问 / OpenAI 三方剂量与两个模型的贡献值,说明列区分"仅医生使用""模型新增""剂量差 N g"。支持搜索药名与"仅看剂量差异"过滤,脚注写明贡献的算式。
**③ 资料与缺口页。** 左栏资料构成(病历 / 问诊通话 / 记录合计等,按行数自适应高度)与读取范围(资料截止、比较算法、提示词版本、覆盖状态、对照类型);右栏附件华夫图 + 一句话说明"读取 N 个、受限 N 个、未送达 N 个",下方缺口清单按类型合并并标注关键 / 一般。
**④ 处理进度页(与旧进度页合并)。** 批次流程与实时进度条保留在上方,下方是新的每模型阶段卡与调用记录表(模型 / 阶段 / 耗时 / 附件数 / 输出 tokens / 结果)。阶段键 `text:0``files:1``final` 一律翻成中文(文字资料 1、附件读取 2、开方与对比),错误码走词表。
**⑤ 历史与趋势页。** 一致度趋势柱(按时间从左到右,不可比的批次留空)+ 批次列表(批次号、时间、状态、两个模型的分数、算法版本、对照类型)。分数同时兼容列表接口的扁平 `score` 与详情接口的 `comparison.score`,并且只有 `comparable` 才给分——之前趋势图与列表全是"—"就是漏了这一层。排序也从"把时间戳当数字"改成按时间字符串排。
**⑥ 统计窗口改版。** 四张 KPI 卡(合格开方事件 / 两个模型的有效比较数与均值 / 专家复核合格率)、样本口径漏斗、排除原因合计、按医生列表,下方保留原有的分层明细 HTML。统计窗口现在也套用同一套样式表,卡片有边框有底色。脚注继续写明"一致度不是医生准确率",没有复核样本时合格率显示"—"而不是拿一致度顶替。
**⑦ 顺带修掉的问题。**
- `资料截止:` 一行因为写错表达式一直是空的,现在正常显示时间,没有就显示"—"。
- `覆盖状态:partial` 直接吐英文;覆盖语境下译为"部分资料缺失"(沿用 STATE_LABELS 的"部分完成"会误导成进度)。
- 表头默认居中,在被拉宽的最后一列里飘到中间;统一改为左对齐。
- 指标条在压缩模式下高度不够,华夫图与文字叠在一起;压缩时只留计数文字。
- 打开自带图表的页面(逐味 / 资料 / 历史 / 进度)时指标条自动收起,把高度让给正文。
- `QFrame#AiMetricCard` 此前根本没有样式,卡片是"白底白字";补上边框与圆角。
- 短表格(资料构成、样本口径、排除原因)按行数固定高度,不再出现四行内容配一个滚动条。
回归:
- 桌面端报告窗口五个套件 **226 项通过**Ruff 通过。
- 服务端 12 个离线套件全部通过(对比 266 项、统计 64 项、进度 38 项、Worker 韧性 11 项等)。
- 服务端两个落库套件在独立 MySQL127.0.0.1:13379)上通过:`PrescriptionAiQueueTest` 78 项、`PrescriptionAiPipelineTest` 90 项。
- `scripts/render_issued_prescription_ai.py` 现在额外产出 `per-herb / sources / history / pipeline / failed / statistics` 六张截图用于核对。
已知与本次无关的失败,均已用"把本次改动 stash 掉再跑"验证过是既有问题:
- `tests/test_busy_overlay.py::test_shell_construction_never_shows_orphan_business_controls`
- `tests/test_diagnosis_drawer_visual.py` 整个模块跑到第 8 项时进程级崩溃(access violation / 0xc0000374),stash 后同样在同一位置崩溃。
## 2026-09-11 报告窗口按设计稿重做(第三批)
上一批做出来的界面和确认过的设计稿 `app/artifacts/ui-mockups/v2/` 并不是一回事——指标条、图表形态、页面骨架都是旧的。这一批按设计稿逐页重做。
**导航。** 六个目的地全部平铺在蓝色带里,和设计稿一致:`对比总览 / 完整报告 / 候选与逐味 / 资料与缺口 / 处理进度 / 历史与趋势`。「更多内容」下拉去掉;「重新分析」从菜单里挪到band 上,和「刷新」「保存复核」并排。原始文本页(逐味原文、来源原文、原方记录)不再占导航位,由页面内的链接进入。跳转全部改成按名字寻址(`tab_pages` + `_goto`),不再用会随页面增减而错位的下标。
**① 对比总览(设计稿 01)。** 整页重写:
- KPI 行:两张模型卡(左侧色条 + 环形一致度 + 大号分数 + 药味重合/共同药味/候选药味三列),加一张「本次关注」卡(标题句 + 关键缺口/附件受限/共识药味三个药丸)。原来横在标题下的指标条取消,KPI 行现在属于总览页本身,其它页面不再被它占掉高度。
- 左卡:三方用药交集韦恩图(七个区域各自的药味数,三个集合各自标注「医生 N / 千问 N / OpenAI N」)+ 资料覆盖华夫图与图例。
- 中卡:剂量差异分布——一行一味药,左侧药名与原方剂量,中间以「与原方相同」为中轴的双色发散条,右侧两个模型各自的差值;某个模型没收录就画「OpenAI 未收录」的虚线徽标,两边都一致则画绿色「两模型一致 6 g」,模型新增的药味用浅色条画出全量。底部一条共用刻度尺。
- 右卡:复核清单——按严重度排序的圆点列表(模型风险 > 模型缺口 > 系统缺口),每条注明是哪个模型提出的;下方是复核意见表单。
**② 候选与逐味(设计稿 03)。** 顶部三张处方卡(医生原方 / 千问候选 / OpenAI 候选),各自列出药味与剂量、味数与剂型、服法与风险提示;中间是逐味对照表,贡献列改为「小条 + 数值」,说明列区分三方共有/仅医生使用/某模型未收录/剂量差 N g;筛选从一个复选框换成设计稿的四个计数筹码(全部 / 仅共同 / 仅差异 / 仅未收录)加搜索;底部四格用法对照(服法、疗程、剂型、辅方,医生与模型分行)。
**③ 资料与缺口(设计稿 04)。** 改成三栏:资料构成用按类型的比例条(问诊通话、监测记录、医生备注、历史处方、病历、聊天归档),读取范围列出资料截止、比较算法、提示词、药材字典与覆盖状态;中栏是附件华夫图、读取说明与「模型各自读取到的量」表;右栏是按严重度排序的缺口清单,配一段说明缺口含义的提示块。
**④ 处理进度(设计稿 05)。** 顶部四格统计(批次总耗时 / 模型调用 / 格式修复·追问 / 失败调用);两张模型泳道卡,阶段列表由保存的调用记录归并得出(文字资料分析、附件读取、生成候选与报告、格式修复、药名回问……),没有调用记录就不编造阶段;调用记录表增加「耗时分布」条,按本批次最慢的一次调用取比例。
**⑤ 历史与趋势(设计稿 06)。** 左右两栏:左边是趋势柱与一段「分层说明」——相邻两个批次之间只要比较算法或提示词版本变了,就写清楚 `v1.0.1 → v1.1.0``v3 → v4` 并注明两侧分数不能直接相减;右边是批次列表(批次号、时间、状态/原因、两个模型的分数、算法版本),失败批次直接显示模型给出的原因。
**⑥ 统计窗口(设计稿 07)。** 四张 KPI 卡之后是一致度分布直方图(按 20% 分箱,两个模型并排)与「均值 / 中位 · 配对样本」三格小结;右侧样本口径与排除原因改成条形(标签写在条内),下面是那段说明「一致度不是医生准确率」的琥珀色提示块;按医生列表保留在最下方。
为此在服务端把已经算好但没往外传的分布补上:`PrescriptionAiLogic::statistics()` 的每个模型多返回一个 `distribution``PrescriptionAiStatistics::summarize()` 早就在算 `[0,20) … [80,100]` 五个分箱,只是之前没进接口)。`PrescriptionAiQueueTest` 增加一条断言,保证分箱确实随统计接口返回。
**⑦ 完整报告(设计稿 02)。** 每一栏加上模型名 + 状态药丸 + 生成时间的抬头。设计稿左侧的章节目录(TOC)没有做——报告正文是服务端结构化字段渲染的,没有可跳转的锚点,硬做会是个假目录。
**顺带修掉的问题。** `比较口径` 改为从对比行真实的单位与剂量基准推导(全部一致才显示,否则回退到冻结处方的剂数单位);`QFrame#AiMetricCard` 之前完全没有样式;统计窗口现在也套用同一套样式表。指标条里已经用不到的堆叠条、迷你折线、覆盖卡与交集卡整体删除,不留死代码。
回归:报告窗口五个套件 **243 项通过**,Ruff 通过;服务端 12 个离线套件全部通过,落库套件 `PrescriptionAiQueueTest` 79 项、`PrescriptionAiPipelineTest` 90 项通过。`scripts/render_issued_prescription_ai.py` 的示例数据换成三份真正不同的处方(医生 8 味 / 千问 7 味 / OpenAI 6 味)与 5 个历史批次,六张页面截图逐张核对过。
## 2026-09-11 报告窗口改造为深色分析控制台(ai-redesign-v2
`C:/Users/pc/WorkBuddy/2026-09-11-16-46-03/ai-redesign-v2.html` 与同目录 `overview.md` 重做。注意:动手前发现这些文件正在被另一个进程同时修改(白色顶栏、可展开交集卡、"保存千问复核"都不是本会话写的),与用户确认另一会话已停止后才接手,本轮是在那个状态上继续改的。
**① 主题令牌独立成模块。** 新增 `issued_prescription_ai_theme.py`:海军蓝底 `#080D18`、卡片 `#0F1726`/`#131D2E`、线 `#22304A`;科技蓝 `#2E7BF6` 只做界面色(按钮、激活态、焦点),千问改天青 `#17BFDD`、OpenAI 改紫 `#9B7BFF`,风险保留琥珀 `#F5B942` 与玫红 `#F4697A`。窗口内六个模块统一从这里取色,不再各自写死浅色值;原先散落的 30 多个浅色硬编码全部换成令牌。
**② 顶栏。** 面包屑「甄养医生工作站 / AI 分析报告」+ 标题 + 版本药丸;右侧四组事实(处方/诊单、剂量口径、资料截止、批次)、版本选择、刷新、重新分析、登录医生。患者 / 主诉 / 临床诊断 / 剂型收在标题下一行——设计稿顶栏没有这几项,但这是医疗窗口,冻结快照的身份信息不能丢。
**③ 一致率对比条(新控件 `issued_prescription_ai_console.py`)。** 两侧各自:模型点 + 状态 + 资料覆盖 + 用时,大号一致率,0/25/50/75/100 刻度条,以及对手所在位置的灰线标注「OpenAI 在此」。中间给差值 `+7.2pt` 与领先方,并列出两侧药味重合。下面一行写明一致率的定义与可比条件。模型失败的原因与「重试 X」按钮也移到这里——那是整个窗口里唯一只属于单个模型的位置。
**④ 左侧编号步骤导航。** 01–06 六个目的地,各带数量徽标(剂量差异项数、报告数、药味数、缺口数、调用次数、批次数),下面是「本次关注」四项计数与「保存本次复核」。原来的横向标签条与顶部 KPI 卡整块删除,`MetricsPanel` 及其 21 项测试一并移除。
**⑤ 对比总览重排为设计稿的两行。**
- 本批结论:四条按规则生成的编号结论(候选方味数 / 一致率与差值 / 剂量偏差最大的三味 / 资料缺口分布),配「结论由规则生成,不代表医生判断」的说明条。
- 药味归属分布:三方共识 / 医方独有 / 千问独有 / OpenAI 独有,各自条形 + **直接列出药名**(韦恩图读不出结论,换成这个)。四组不含"医方与单一模型共用"的药味,标题会把这部分单独说明,避免四个数加不回总数。
- 复核清单:关键 / 一般徽标 + 归并后的条数 + 来源模型,复核意见与状态表单折进同一张卡。
- 剂量差异分解:以「与医方相同」为轴,右=加量、左=减量,刻度 −N/−N/2/0/+N/2/+N,右侧两列分别是千问 Δ 与 OpenAI Δ。
- 高风险提示:偏差 ≥10 克或幅度 ≥50% 自动进清单,标高/中并写明触发原因;阈值只做排序,不替代医生判断。
- 底部四个快捷入口。
**⑥ 其余五页。** 完整报告、候选与逐味、资料与缺口、处理进度、历史与趋势跟随新色板;四页包在滚动容器里,窗口不够高时滚动而不是把控件压在一起。图表的模型色统一换成天青/紫。
**图标。** 窗口不带图片资源,新增 `issued_prescription_ai_glyphs.py`,所有图标(含两个模型徽标、窗口标记)都用 QPainter 画线稿,颜色由调用方给。
**设计稿里剩下的四项也已补齐:**
- **深 / 浅主题切换(◐)**`issued_prescription_ai_theme.py` 里给出 `DARK` / `LIGHT` 两套令牌,`use_theme()` 就地改写 `CONSOLE` 并回调所有派生值(图表色、卡片样式表、窗口样式表)。窗口构建整段抽成 `_build_ui()`,切换时原地重建:当前页面、选中批次与**尚未保存的复核意见**都会带过去。
- 顺带查出一个真 bug`QPushButton#AiStep:checked QLabel#AiStepName` 这种"父级伪状态 + 后代"选择器在 Qt 里是按子控件的状态判定的,于是六个步骤的文字**全部**取了选中态的白色 —— 深色底看不出来,一换浅色就成了白底白字。改为在 `set_current()` 里按选中与否直接给标签上色。
- **附件缩略图网格**:资料与缺口页把华夫图换成一格一个附件的磁贴,标注类型与编号,蓝=已读、琥珀=受限/不支持,悬停给出完整编号、状态、原因与版本核验情况。为此服务端 `PrescriptionAiGenerator` 在覆盖清单的四条写入路径上都补了 `type` 字段(原来只有 `file_id`),并在 `PrescriptionAiGeneratorTest` 里加了断言守住。
- **图表悬停说明**:贡献条给出算式、来源构成条说明归一口径、调用耗时条给出占最慢一次的比例、趋势柱与分布柱逐批次/逐分箱列出数值、归属分布的条形与药名列出完整药味。
- **完整报告页章节目录**:报告按字段切成带锚点的章节,左栏列出章节可跳转(两栏同时滚动),并提供「并排对照 / 仅千问 / 仅 OpenAI / 只看不同段落」四种显示方式;"只看不同段落"按章节渲染结果是否逐字相同来判定,规则可解释。
- 另外把左栏「附件受限」的口径改成**模型确实没读到的附件数**(原先取的是缺口记录条数,与页面上的附件状态对不上)。
回归:
- 报告窗口四个套件(`test_issued_prescription_ai{,_pages,_workspace,_comparison}.py`**231 项通过**,Ruff 通过;服务端 `PrescriptionAiGeneratorTest` 通过。
- 全量桌面套件(排除早已进程级崩溃的 `test_diagnosis_drawer_visual.py`)有 18 项失败,全部落在本轮没有碰过的模块里:`busy_overlay``list_detail_geometry``patients_visual_blue``patient_orders_visual_blue``patient_progress_visual_blue``reception_completion_button_ui``reception_parity_ui``silent_list_loading``diagnosis_order_video_visual``patient_ai_report_desktop``diagnosis_index_visual`
- 与改造前的一次完整跑(同一工作区、深色化之前)逐条比对:失败集合完全一致,只多出 `diagnosis_index_visual` 一项,而该项单独跑通过 —— 是整轮跑到后段(进程占用 4.6 GB)时的顺序/内存相关抖动。
- 另外验证过:把 `DEBUG_MODE` 临时改回 `True` 后这些用例仍然失败,所以与工作区里那处版本/调试开关的改动无关;`git status` 显示本轮只动了 `issued_prescription_ai*` 这一组文件。
## 逐项对齐设计稿 `ai-redesign-v2.html`(字号 / 间距 / 描边)
上一轮把结构搭到位,但字号与间距是估的,医生反馈"好多细节都不匹配",并指出两处硬伤:一致率刻度的百分比标签被裁掉、逐味对照矩阵的列错位。这轮按设计稿的 CSS 逐条换算(`html{font-size:15px}`,所以 .68rem = 10.2px、.82rem = 12.3px、.9rem = 13.5px、1.8rem = 27px),把两边的数值对齐。
**取值方式。** 设计稿是本地 HTML,直接读它的 CSS 而不是量截图:`--radius:10px``--radius-sm:7px`、卡片描边用 `--line-soft` 而不是 `--line``.card-h{padding:15px 18px 0}``.card-body{padding:14px 18px 18px}``.grid{gap:14px}`。浅色令牌与 `rgba()` 色调(`--blue-dim` 等)按叠在卡片底色上的合成值写进调色板,Qt 这边没有 alpha 混合也能落到同一颜色。
**改到的地方:**
- **卡片头**。设计稿里 `.card-h` 只有标题 + 小字说明 + 右侧图例,**没有图标**;`_panel_head()` 去掉了 glyph 参数,标题 13.5px/600、说明 10.65px `--text-faint`。说明文字换成 `HintLabel`:宽度不够时省略号收尾并留 tooltip,绝不再把卡片顶宽(上一版为此把它设成 `Ignored`,结果整行说明直接消失)。
- **左侧步骤导航**`.stepnav` 的 26px|1fr|auto 栅格:序号片 22×22/圆角 6/10.2px,名称 12.6px,数量徽标做成 20px 起宽的胶囊(9.9px、圆角 8、上下 17px 固定高)——之前徽标会被拉满整行高度,六个连成一条竖条,看着像滚动条。选中态按设计稿画左侧 3px 圆角竖条(`QSS``border-left` 会把内容顶偏,改在 `_StepButton.paintEvent` 里画),徽标按严重度分色:待决项有关键项时取玫瑰,资料缺口取琥珀。
- **一致率对比条**。百分号按 `.val sup` 处理成小一号、上标、`--text-dim`,新增 `ScoreLabel` 自绘,`text()` 仍返回 "56.1%" 这一个字符串,调用方与测试不受影响。刻度条填充改成设计稿的 `rgba(hue,.45) → hue` 渐变,四分位分隔线换 `--grid-line`,对手模型的标记改成 2px 圆角竖条。卡片左缘补上蓝→紫渐变竖条(按卡片圆角裁剪),中间差值列的分隔线改虚线。两侧模型块顶部对齐,失败的一侧不再把整列往下推。
- **刻度标签**。设计稿里 `.axis``bottom:-1px` 让 0%/25%/… 压在轨道上、被填充盖掉一半 —— 这正是医生截图里"看不清"的那处。这里**不照抄**:标签仍排在轨道下方 31px 处,可读优先。
- **逐味对照矩阵**。列固定为 药味 / 千问(克/剂 · 贡献度)/ OpenAI(克/剂 · 贡献度)/ 医方原方 / 结论,后四列定宽、首列拉伸,每个模型一格自绘(剂量 + 贡献条 + 数值),不再靠自动列宽挤在一起。右上补回「千问贡献度 / OpenAI 贡献度」图例;「仅医方使用」按设计稿不套胶囊,用弱化文字。
- **列表行**。本批结论、复核清单按 `.finding li` / `.rv-item` 加 1px `--line-soft` 分隔线与 10/11px 的上下留白;结论正文 12.3px/160%,复核标题 12.3px、副题 10.5px、计数用等宽字 13px。
- **表格与统计卡**。所有表头 10px/`--text-faint`/`padding:10px 16px`,单元格 12px/`padding:8px 16px`,分隔线 `--line-soft`;处理进度页四个数字改 26px 等宽字,标签 10.65px 带字距。
- **完整报告正文**。文档样式表原先写死了浅色(`#EFF4F9` 表头、`#244C79` 标题),深色主题下表格是一块白。改成按当前调色板生成:正文 13px/195%、章节标题 11px `--blue-text`、表头 10px。Qt 的富文本引擎会自己决定 `<h3>` 的字号、无视样式表,所以章节标题改用 `<p class=sec>`
- **趋势图**。每根柱子按设计稿在顶部标出自己的数值(用该模型的颜色),两根柱子拉开到能容下各自标签的间距。
- **说明条**`ReadingNote`)。虚线描边、9/12 内边距、10.8px、圆角 7,并在 `resizeEvent` 里按换行后的高度设最小高——否则 QLabel 只申报一行的高度,第二行被卡片裁掉。
**窄窗口跟着设计稿的断点走。** 设计稿在 `@media (max-width:1240px)` 把卡片栅格收成一列、把左栏改成横向;照着这个思路补了三处,都是这轮出图时才暴露的:
- 对比总览的两行卡片改用 `QGridLayout`,工作区窄于 960px 时收成一列。原先三张卡各占 1/3,940 宽时「本批结论」只剩两个字一行。
- 左栏放进自己的滚动区。窗口不够高时它原来会被压到最小高度以下,「本次关注」四行直接塌成四条空条。
- 顶栏在窗口窄于 1400px 时把资料摘要(处方 / 诊单、剂量口径、资料截止、批次)换行到第二排;窄于 1240px 再收起翻页箭头、面包屑与读取状态。原先 1280 宽时标题会被裁成「诊断与药...」。
**原生标题栏跟着主题走。** 深色控制台原先只画到窗口边框为止,Windows 自己画的标题栏还是浅色,接缝很突兀。`apply_window_chrome()``DwmSetWindowAttribute` 把标题栏底色、文字色、边框色设成当前调色板(Windows 11 22000+ 生效;其他系统调用失败即保持原样,不报错),窗口显示时与主题切换时各调一次;统计窗口同样处理。
**候选卡片不再自己截断。** 三张处方卡原来写死 `setMaximumHeight(250)`,方义长一点(真实数据里常有五六行)就把药味表和「另有 N 味」一起切掉。去掉上限,药味表按内容申报高度,方义换成 `WrappedLabel` —— 普通 `QLabel` 开了自动换行也只申报一行的高度,布局照给一行,剩下的被裁;这个类在 `resizeEvent` 里按实际宽度回问一次所需高度并设为最小高度。`ReadingNote` 里原来那段同样的代码合并到这个类上。
**表格直接往下铺,不再挤在剩余空间里。** 调用记录原来带 stretch 塞在页面底部,实际只剩六十来像素,表头被切、一次只看得到一行。`_fit_rows()` 统一成按实际行高量一次(原先那版假定每行 31px),关掉表格自己的纵向滚动条,把高度设成表头 + 所有行;逐味对照矩阵、缺口清单同样处理。页面长了就由页面自己滚——这也是设计稿的做法,矩阵在稿子里就是十二行一次铺完。
顺带修掉两处连带问题:
- 处理进度页是唯一没进滚动容器的页,表格铺开后整页被压到 464px,两个模型的阶段行直接塌成零高。改成和其余五页一样走 `_add_scrolled_tab()`
- 顶栏换行阈值原来写死 1400px,但批次翻页箭头出现时横向要 1482px,1440 下资料摘要又被切了。改成按 `top_row.sizeHint()` 与顶栏可用宽度实测比较,换行后把收起的宽度加回去再比,不会来回抖。
回归:
- 报告窗口四个套件 **234 项通过**(新增一个跳过分隔线取结论文本的辅助函数),Ruff 通过。
- 六个页面在 1440 / 1280 / 1024 / 940 四档宽度重新出图,深浅两套主题各出一张,逐张与设计稿截图对照。
- 全量桌面套件(排除早已进程级崩溃的 `test_diagnosis_drawer_visual.py`)**17 项失败**,全部落在本轮没碰过的模块里,与改造前基线的 18 项相比只少了 `diagnosis_index_visual` 那条——该条单独跑通过,是整轮跑到后段的顺序/内存抖动,此前已记录。全仓只有那四个 AI 测试文件 import 本轮改动的 dialog 模块。
@@ -144,7 +144,7 @@ class PrescriptionOrderController extends BaseAdminController
} }
/** /**
* 修改承运商与快递单号,不受订单履约状态或远端药房快照锁限制。 * 处方与支付单双审核通过后可修改承运商与快递单号,不另设履约状态或远端药房快照锁限制。
*/ */
public function ddcode() public function ddcode()
{ {
@@ -7,6 +7,7 @@ namespace app\adminapi\logic\tcm;
use app\common\model\auth\Admin; use app\common\model\auth\Admin;
use app\common\service\prescriptionai\PrescriptionAiAccess as Access; use app\common\service\prescriptionai\PrescriptionAiAccess as Access;
use app\common\service\prescriptionai\PrescriptionAiCipher as Cipher; use app\common\service\prescriptionai\PrescriptionAiCipher as Cipher;
use app\common\service\prescriptionai\PrescriptionAiDoctorSnapshot as DoctorSnapshot;
use app\common\service\prescriptionai\PrescriptionAiPolicy as Policy; use app\common\service\prescriptionai\PrescriptionAiPolicy as Policy;
use app\common\service\prescriptionai\PrescriptionAiProgress as Progress; use app\common\service\prescriptionai\PrescriptionAiProgress as Progress;
use app\common\service\prescriptionai\PrescriptionAiStatistics; use app\common\service\prescriptionai\PrescriptionAiStatistics;
@@ -106,7 +107,13 @@ final class PrescriptionAiLogic
self::requirePermission('detail', $actor, $info); self::requirePermission('detail', $actor, $info);
$batch = self::loadBatch($batchId, $actor, $info); $batch = self::loadBatch($batchId, $actor, $info);
$models = self::models([$batchId], true); $models = self::models([$batchId], true);
return self::formatBatch($batch, $models[$batchId] ?? [], Access::prescription((int) $batch['prescription_id'], $actor, $info)); $detail = self::formatBatch($batch, $models[$batchId] ?? [], Access::prescription((int) $batch['prescription_id'], $actor, $info));
// loadBatch has checked both current prescription and historical source permissions.
// This cipher is saved at enqueue time, so it is available before models start and
// remains the correct original when the user selects an older report revision.
$detail['doctor_snapshot'] = empty($batch['prescription_cipher']) ? null
: DoctorSnapshot::project((new Cipher())->decrypt($batch['prescription_cipher'], 'prescription'));
return $detail;
} }
public static function regenerate(int $rxId, string $reason, int $actor, array $info): array public static function regenerate(int $rxId, string $reason, int $actor, array $info): array
@@ -244,9 +251,10 @@ final class PrescriptionAiLogic
$models = []; $models = [];
foreach (Policy::MODELS as $model) { foreach (Policy::MODELS as $model) {
$m = $summary['models'][$model] ?? []; $m = $summary['models'][$model] ?? [];
// 分布直方图与均值同源,前端据此画分箱;缺失时给空数组,不补零。
$models[$model] = ['eligible_count' => $m['valid_count'] ?? 0, 'coverage_rate' => $m['coverage_percent'] ?? 0, $models[$model] = ['eligible_count' => $m['valid_count'] ?? 0, 'coverage_rate' => $m['coverage_percent'] ?? 0,
'mean' => $m['mean'] ?? null, 'median' => $m['median'] ?? null, 'excluded_reasons' => $m['exclusion_reasons'] ?? [], 'mean' => $m['mean'] ?? null, 'median' => $m['median'] ?? null, 'excluded_reasons' => $m['exclusion_reasons'] ?? [],
'strata' => $m['strata'] ?? []]; 'distribution' => $m['distribution'] ?? [], 'strata' => $m['strata'] ?? []];
} }
$doctors[] = ['doctor_id' => $doctorId, 'doctor_name' => (string) ($names[$doctorId] ?? ''), $doctors[] = ['doctor_id' => $doctorId, 'doctor_name' => (string) ($names[$doctorId] ?? ''),
'total_count' => $summary['total_events'] ?? 0, 'patient_count' => $summary['patient_count'] ?? 0, 'total_count' => $summary['total_events'] ?? 0, 'patient_count' => $summary['patient_count'] ?? 0,
@@ -45,6 +45,8 @@ class PrescriptionOrderLogic
{ {
private static string $error = ''; private static string $error = '';
private const TRACKING_AUDIT_REQUIRED = '处方审核和支付单审核均通过后,才可填写或修改快递单号';
public static function setError(string $msg): void public static function setError(string $msg): void
{ {
self::$error = $msg; self::$error = $msg;
@@ -55,6 +57,17 @@ class PrescriptionOrderLogic
return self::$error; return self::$error;
} }
private static function assertTrackingAuditApproved(PrescriptionOrder $order): bool
{
if ((int) $order->prescription_audit_status !== 1 || (int) $order->payment_slip_audit_status !== 1) {
self::$error = self::TRACKING_AUDIT_REQUIRED;
return false;
}
return true;
}
private static function assertRemoteSnapshotMutable(PrescriptionOrder $order): bool private static function assertRemoteSnapshotMutable(PrescriptionOrder $order): bool
{ {
try { try {
@@ -990,6 +1003,12 @@ class PrescriptionOrderLogic
private static function createLocked(array $params, int $adminId, array $adminInfo) private static function createLocked(array $params, int $adminId, array $adminInfo)
{ {
self::$error = ''; self::$error = '';
// 新建业务订单的两项审核均为待审核,不能在创建时预填单号。
if (trim((string) ($params['tracking_number'] ?? '')) !== '') {
self::$error = self::TRACKING_AUDIT_REQUIRED;
return false;
}
$rxId = (int) $params['prescription_id']; $rxId = (int) $params['prescription_id'];
$diagId = (int) $params['diagnosis_id']; $diagId = (int) $params['diagnosis_id'];
$payOrderIds = self::normalizePayOrderIds($params['pay_order_ids'] ?? []); $payOrderIds = self::normalizePayOrderIds($params['pay_order_ids'] ?? []);
@@ -1086,7 +1105,7 @@ class PrescriptionOrderLogic
$order->prev_staff = (string) ($params['prev_staff'] ?? ''); $order->prev_staff = (string) ($params['prev_staff'] ?? '');
$order->service_channel = (string) ($params['service_channel'] ?? ''); $order->service_channel = (string) ($params['service_channel'] ?? '');
$order->service_package = (string) ($params['service_package'] ?? ''); $order->service_package = (string) ($params['service_package'] ?? '');
$order->tracking_number = (string) ($params['tracking_number'] ?? ''); $order->tracking_number = '';
$order->express_company = self::normalizeExpressCompany($params['express_company'] ?? 'auto'); $order->express_company = self::normalizeExpressCompany($params['express_company'] ?? 'auto');
$order->ship_mode = self::canSelectShipMode($adminInfo) $order->ship_mode = self::canSelectShipMode($adminInfo)
? self::normalizeShipMode((string) ($params['ship_mode'] ?? 'gancao')) ? self::normalizeShipMode((string) ($params['ship_mode'] ?? 'gancao'))
@@ -1867,6 +1886,15 @@ class PrescriptionOrderLogic
return false; return false;
} }
// 未提交单号时保留原值;仅实际填写、修改或清空单号需要双审核通过。
$trackingNumber = array_key_exists('tracking_number', $params)
? (string) $params['tracking_number']
: (string) ($order->tracking_number ?? '');
$trackingChanged = $trackingNumber !== (string) ($order->tracking_number ?? '');
if ($trackingChanged && !self::assertTrackingAuditApproved($order)) {
return false;
}
$medDays = $params['medication_days'] ?? null; $medDays = $params['medication_days'] ?? null;
$medDays = $medDays === '' || $medDays === null ? null : (int) $medDays; $medDays = $medDays === '' || $medDays === null ? null : (int) $medDays;
@@ -1892,7 +1920,7 @@ class PrescriptionOrderLogic
$order->prev_staff = (string) ($params['prev_staff'] ?? ''); $order->prev_staff = (string) ($params['prev_staff'] ?? '');
$order->service_channel = (string) ($params['service_channel'] ?? ''); $order->service_channel = (string) ($params['service_channel'] ?? '');
$order->service_package = (string) ($params['service_package'] ?? ''); $order->service_package = (string) ($params['service_package'] ?? '');
$order->tracking_number = (string) ($params['tracking_number'] ?? ''); $order->tracking_number = $trackingNumber;
if (array_key_exists('express_company', $params)) { if (array_key_exists('express_company', $params)) {
$order->express_company = self::normalizeExpressCompany($params['express_company']); $order->express_company = self::normalizeExpressCompany($params['express_company']);
} }
@@ -1978,6 +2006,10 @@ class PrescriptionOrderLogic
$order->payment_slip_audit_status = 0; $order->payment_slip_audit_status = 0;
$order->payment_slip_audit_remark = ''; $order->payment_slip_audit_remark = '';
} }
// 同次编辑若让支付单重新进入待审核,也不能同时写入新的单号。
if ($trackingChanged && !self::assertTrackingAuditApproved($order)) {
return false;
}
self::syncFulfillmentStatus($order); self::syncFulfillmentStatus($order);
try { try {
@@ -2001,7 +2033,7 @@ class PrescriptionOrderLogic
} }
/** /**
* 仅修改承运商与快递单号。物流信息不属于药房下单快照,因此允许在所有履约状态下修正 * 双审核通过后,仅修改承运商与快递单号。物流信息不属于药房下单快照,不另设履约状态限制
* *
* @return array<string,mixed>|false * @return array<string,mixed>|false
*/ */
@@ -2061,6 +2093,10 @@ class PrescriptionOrderLogic
return false; return false;
} }
if (!self::assertTrackingAuditApproved($order)) {
return false;
}
$oldTrackingNumber = trim((string) ($order->tracking_number ?? '')); $oldTrackingNumber = trim((string) ($order->tracking_number ?? ''));
$oldExpressCompany = self::normalizeExpressCompany((string) ($order->express_company ?? 'auto')); $oldExpressCompany = self::normalizeExpressCompany((string) ($order->express_company ?? 'auto'));
$newExpressCompany = self::normalizeExpressCompany($expressCompany); $newExpressCompany = self::normalizeExpressCompany($expressCompany);
@@ -2148,6 +2184,9 @@ class PrescriptionOrderLogic
return false; return false;
} }
if (!self::assertTrackingAuditApproved($order)) {
return false;
}
$shipMode = self::normalizeShipMode((string) ($order->ship_mode ?? 'gancao')); $shipMode = self::normalizeShipMode((string) ($order->ship_mode ?? 'gancao'));
$shipModeText = $shipMode === 'direct' ? '药房直发' : '甘草药方发'; $shipModeText = $shipMode === 'direct' ? '药房直发' : '甘草药方发';
+3 -1
View File
@@ -53,7 +53,9 @@ final class PrescriptionAiWork extends Command
try { try {
if (PrescriptionAiStore::enabled()) { if (PrescriptionAiStore::enabled()) {
$worked = $lane === 'prepare' ? $worker->prepareOne() : $worker->runOne($lane); $worked = $lane === 'prepare' ? $worker->prepareOne() : $worker->runOne($lane);
if ($lane === 'prepare' && time() >= $sweepAt) { // Source and prescription sweeps rebuild whole contexts, so they only run while
// nothing is waiting: a prescription saved right now must never queue behind them.
if ($lane === 'prepare' && !$worked && time() >= $sweepAt) {
$sweep = $worker->refreshSources($sourceCursor); $sweep = $worker->refreshSources($sourceCursor);
$sourceCursor = $sweep['selected'] > 0 ? $sweep['last_id'] : 0; $sourceCursor = $sweep['selected'] > 0 ? $sweep['last_id'] : 0;
$rx = $worker->reconcile($rxCursor); $rx = $worker->reconcile($rxCursor);
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace app\common\service\prescriptionai;
/** Minimal display projection of the immutable prescription; never reads live records. */
final class PrescriptionAiDoctorSnapshot
{
public static function project(array $prescription): array
{
$patient = self::scalars($prescription, ['gender', 'age']);
$patient['name'] = self::text($prescription['patient_name'] ?? null);
$gender = self::text($patient['gender'] ?? null);
$patient['gender_label'] = in_array($gender, ['1', '男'], true) ? '男'
: (in_array($gender, ['0', '2', '女'], true) ? '女' : '未知');
$rx = self::scalars($prescription, [
'prescription_name', 'prescription_type', 'dosage_unit', 'dose_unit', 'dose_basis',
'dose_count', 'usage_instruction', 'usage_days', 'times_per_day', 'usage_time',
'usage_way', 'usage_notes', 'dietary_taboo', 'dosage_amount', 'dosage_bag_count',
'need_decoction', 'bags_per_dose',
]);
$auxUsage = self::scalars(PrescriptionAiPolicy::decode($prescription['aux_usage'] ?? []), [
'prescription_name', 'dosage_amount', 'dosage_bag_count', 'need_decoction',
'bags_per_dose', 'times_per_day', 'usage_days',
]);
if ($auxUsage !== []) {
// Auxiliary formula instructions belong to that formula, never the main usage.
$rx['aux_usage'] = $auxUsage;
}
$rx['herbs'] = [];
foreach (PrescriptionAiPolicy::decode($prescription['herbs'] ?? []) as $herb) {
if (!is_array($herb)) {
continue;
}
// Preserve saved row order and duplicates, without costs, stock, IDs or attachments.
$rx['herbs'][] = self::scalars($herb, [
'name', 'dosage', 'unit', 'dose_basis', 'formula_type', 'processing',
'special_usage', 'instructions', 'usage', 'remark',
]);
}
return [
'patient' => $patient,
// The source has one combined clinical diagnosis. Do not invent a Western/TCM split
// or expose case_record, which may contain other encounters and contact details.
'diagnosis' => ['clinical_diagnosis' => self::text($prescription['clinical_diagnosis'] ?? null)],
'prescription' => $rx,
];
}
private static function scalars(array $row, array $fields): array
{
return array_filter(array_intersect_key($row, array_flip($fields)),
static fn ($value): bool => is_string($value) || is_int($value) || is_float($value));
}
private static function text($value): string
{
return is_string($value) || is_numeric($value) ? trim((string) $value) : '';
}
}
@@ -119,7 +119,9 @@ final class PrescriptionAiGenerator
} }
$status = (string) ($file['status'] ?? 'pending'); $status = (string) ($file['status'] ?? 'pending');
if ($status === 'restricted' || empty($file['url']) || !in_array($file['type'] ?? '', ['image', 'document'], true) || $batchSize === 0) { if ($status === 'restricted' || empty($file['url']) || !in_array($file['type'] ?? '', ['image', 'document'], true) || $batchSize === 0) {
$coverage['files'][$id] = ['file_id' => $id, 'status' => $status === 'restricted' ? 'restricted' : 'unsupported', 'transmitted' => false, // 附件类型随覆盖清单一起保留:界面按类型展示每个附件的读取结果,不再只有编号。
$coverage['files'][$id] = ['file_id' => $id, 'type' => (string) ($file['type'] ?? ''),
'status' => $status === 'restricted' ? 'restricted' : 'unsupported', 'transmitted' => false,
'version_verified' => false, 'reason' => $batchSize === 0 ? 'FILE_CAPABILITY_DISABLED' : 'FILE_UNAVAILABLE_OR_UNSUPPORTED']; 'version_verified' => false, 'reason' => $batchSize === 0 ? 'FILE_CAPABILITY_DISABLED' : 'FILE_UNAVAILABLE_OR_UNSUPPORTED'];
$criticalGap = true; $criticalGap = true;
$unavailableGroups++; $unavailableGroups++;
@@ -149,7 +151,8 @@ final class PrescriptionAiGenerator
throw $e; throw $e;
} }
foreach ($batch as $file) { foreach ($batch as $file) {
$coverage['files'][$file['file_id']] = ['file_id' => $file['file_id'], 'status' => 'unsupported', 'transmitted' => false, $coverage['files'][$file['file_id']] = ['file_id' => $file['file_id'], 'type' => (string) ($file['type'] ?? ''),
'status' => 'unsupported', 'transmitted' => false,
'version_verified' => false, 'reason' => $e->getMessage()]; 'version_verified' => false, 'reason' => $e->getMessage()];
} }
$criticalGap = true; $criticalGap = true;
@@ -162,7 +165,8 @@ final class PrescriptionAiGenerator
// Delivery was confirmed and one format repair was already spent, so no finding // Delivery was confirmed and one format repair was already spent, so no finding
// in this malformed group is usable evidence. // in this malformed group is usable evidence.
foreach ($batch as $file) { foreach ($batch as $file) {
$coverage['files'][$file['file_id']] = ['file_id' => $file['file_id'], 'status' => 'unreadable', 'transmitted' => true, $coverage['files'][$file['file_id']] = ['file_id' => $file['file_id'], 'type' => (string) ($file['type'] ?? ''),
'status' => 'unreadable', 'transmitted' => true,
'version_verified' => false, 'reason' => 'MODEL_FILE_OUTPUT_INVALID']; 'version_verified' => false, 'reason' => 'MODEL_FILE_OUTPUT_INVALID'];
} }
$criticalGap = true; $criticalGap = true;
@@ -171,7 +175,8 @@ final class PrescriptionAiGenerator
} }
foreach ($result as $fileResult) { foreach ($result as $fileResult) {
$file = $batch[array_search($fileResult['file_id'], array_column($batch, 'file_id'), true)]; $file = $batch[array_search($fileResult['file_id'], array_column($batch, 'file_id'), true)];
$coverage['files'][$fileResult['file_id']] = ['file_id' => $fileResult['file_id'], 'status' => $fileResult['status'], 'transmitted' => true, $coverage['files'][$fileResult['file_id']] = ['file_id' => $fileResult['file_id'], 'type' => (string) ($file['type'] ?? ''),
'status' => $fileResult['status'], 'transmitted' => true,
'version_verified' => !empty($file['version_verified']), 'reason' => $fileResult['status'] === 'processed' ? '' : 'MODEL_REPORTED_' . strtoupper($fileResult['status'])]; 'version_verified' => !empty($file['version_verified']), 'reason' => $fileResult['status'] === 'processed' ? '' : 'MODEL_REPORTED_' . strtoupper($fileResult['status'])];
if ($fileResult['status'] !== 'processed') { if ($fileResult['status'] !== 'processed') {
$criticalGap = true; $criticalGap = true;
+8 -5
View File
@@ -10,11 +10,14 @@ return [
'lease_seconds' => 600, 'lease_seconds' => 600,
'max_attempts' => 3, 'max_attempts' => 3,
'max_manual_retries' => 2, 'max_manual_retries' => 2,
'daily_model_tasks' => 200, 'daily_model_tasks' => (int) env('prescription_analysis.DAILY_MODEL_TASKS', 200),
'daily_patient_batches' => 10, // Analyses of one prescription per day. Regenerating after each change is the normal way to
// Concurrent tasks allowed per model. It only takes effect when that many consumer // review a case, so this is a budget guard, not a correctness limit.
// processes run for the lane (php think prescription-ai:work --lane=<model>). 'daily_patient_batches' => (int) env('prescription_analysis.DAILY_PATIENT_BATCHES', 20),
'max_parallel_per_model' => 2, // Tasks that may run at the same time for one model, so several prescriptions are analysed
// concurrently instead of queueing behind each other. Each running task needs its own consumer
// process: start this many `php think prescription-ai:work --lane=<model>` instances.
'max_parallel_per_model' => max(1, (int) env('prescription_analysis.MAX_PARALLEL_PER_MODEL', 4)),
'refresh_recent_days' => 7, 'refresh_recent_days' => 7,
'auto_refresh_sources' => true, 'auto_refresh_sources' => true,
'auto_refresh_prescription' => true, 'auto_refresh_prescription' => true,
@@ -422,6 +422,10 @@ foreach (array_slice($badFiles['coverage']['files'], 0, 3) as $fileCoverage) {
rxGeneratorExpect($badFiles['coverage']['files'][3]['status'] === 'processed' rxGeneratorExpect($badFiles['coverage']['files'][3]['status'] === 'processed'
&& !isset($badFileProgress['steps']['files:0']) && isset($badFileProgress['steps']['files:1']), && !isset($badFileProgress['steps']['files:0']) && isset($badFileProgress['steps']['files:1']),
'invalid group cache is cleared without discarding the next valid group'); 'invalid group cache is cleared without discarding the next valid group');
// 界面要按类型展示每个附件,所以覆盖清单必须保留附件类型。
rxGeneratorExpect(array_key_exists('type', $badFiles['coverage']['files'][3])
&& array_key_exists('type', $badFiles['coverage']['files'][1]),
'coverage keeps every attachment type, delivered or not');
// The pharmacy's dispensing convention and medicine names must reach the candidate stage so both // The pharmacy's dispensing convention and medicine names must reach the candidate stage so both
// models express one comparable plan; neither may carry the doctor's own herbs or dosages. // models express one comparable plan; neither may carry the doctor's own herbs or dosages.
$conventionContext = $noncriticalContext; $conventionContext = $noncriticalContext;
+44 -2
View File
@@ -115,12 +115,42 @@ try {
'source_diagnosis_ids' => [1], 'source_summary' => ['diagnosis_count' => 1], 'missing' => [], 'source_diagnosis_ids' => [1], 'source_summary' => ['diagnosis_count' => 1], 'missing' => [],
'baseline_eligible' => false, 'baseline_exclusion_reasons' => ['SOURCE_HISTORY_VERSIONS_UNAVAILABLE'], 'baseline_eligible' => false, 'baseline_exclusion_reasons' => ['SOURCE_HISTORY_VERSIONS_UNAVAILABLE'],
'comparison_type' => 'latest_context', 'cutoff_at' => time(), 'wait_for_transcript' => false]; 'comparison_type' => 'latest_context', 'cutoff_at' => time(), 'wait_for_transcript' => false];
$rx = $fixture(); $rx = $fixture(['patient_name' => 'Snapshot patient', 'gender' => 0, 'age' => 52,
'clinical_diagnosis' => 'Saved clinical diagnosis', 'usage_instruction' => 'Saved usage',
'times_per_day' => 2, 'usage_days' => 7,
'aux_usage' => '{"prescription_name":"Saved auxiliary formula","dosage_amount":1.5,"dosage_bag_count":2,"need_decoction":0,"bags_per_dose":1,"times_per_day":1,"usage_days":3,"phone":"private-aux-contact","nested":{"phone":"private-aux-nested"}}',
'phone' => 'private-contact', 'doctor_signature' => 'private-signature',
'case_record' => '{"phone":"private-nested-contact","clinical_diagnosis":"other diagnosis"}']);
$context['source_access_manifest'] = ['schema_version' => 'prescription-source-access-v1', 'patient_id' => 100, $context['source_access_manifest'] = ['schema_version' => 'prescription-source-access-v1', 'patient_id' => 100,
'target' => ['prescription_id' => (int) $rx['id'], 'diagnosis_id' => 1], 'target' => ['prescription_id' => (int) $rx['id'], 'diagnosis_id' => 1],
'records' => [['source_kind' => 'diagnoses', 'id' => 1, 'source_id' => 'diagnoses:1', 'diagnosis_id' => 1, 'patient_id' => 100, 'staff' => []]]]; 'records' => [['source_kind' => 'diagnoses', 'id' => 1, 'source_id' => 'diagnoses:1', 'diagnosis_id' => 1, 'patient_id' => 100, 'staff' => []]]];
$batchId = $save($rx); $batchId = $save($rx);
$expect($batchId > 0 && $save($rx) === $batchId, 'same clinical content enqueues once'); $expect($batchId > 0 && $save($rx) === $batchId, 'same clinical content enqueues once');
$pendingDetail = Api::detail($batchId, 1, $root);
$snapshot = $pendingDetail['doctor_snapshot'];
$expect($pendingDetail['status'] === 'preparing' && $pendingDetail['models'] === []
&& $snapshot['prescription']['herbs'][0]['name'] === '测试药材',
'original prescription available before model preparation and results');
$expect($snapshot['patient']['name'] === 'Snapshot patient' && $snapshot['patient']['gender_label'] === '女'
&& (int) $snapshot['patient']['age'] === 52
&& $snapshot['diagnosis']['clinical_diagnosis'] === 'Saved clinical diagnosis'
&& $snapshot['prescription']['usage_instruction'] === 'Saved usage', 'saved patient diagnosis and usage projected');
$expect((int) $snapshot['prescription']['times_per_day'] === 2 && (int) $snapshot['prescription']['usage_days'] === 7
&& $snapshot['prescription']['aux_usage'] === ['prescription_name' => 'Saved auxiliary formula',
'dosage_amount' => 1.5, 'dosage_bag_count' => 2, 'need_decoction' => 0, 'bags_per_dose' => 1,
'times_per_day' => 1, 'usage_days' => 3],
'saved main and auxiliary usage remain distinct and auxiliary private fields are excluded');
$expect(!str_contains(json_encode($snapshot), 'private-') && !isset($snapshot['prescription']['herbs'][0]['medicine_id']),
'detail original excludes contacts signatures nested records and internal medicine IDs');
try { Api::detail($batchId, 0, []); $expect(false, 'unauthenticated caller cannot read original'); }
catch (DomainException $e) { $checks++; }
Db::name('tcm_diagnosis')->where('id', 1)->update(['delete_time' => time()]);
try { Api::detail($batchId, 1, $root); $expect(false, 'revoked source access cannot read original'); }
catch (DomainException $e) { $checks++; }
Db::name('tcm_diagnosis')->where('id', 1)->update(['delete_time' => null]);
$expect(!isset($snapshot['prescription']['herbs'][0]['unit'])
&& !isset($snapshot['prescription']['herbs'][0]['dose_basis'])
&& !isset($snapshot['diagnosis']['western_diagnosis']), 'missing source units and diagnosis split are not invented');
$blank = $fixture(['is_system_auto' => 1, 'herbs' => '[]']); $blank = $fixture(['is_system_auto' => 1, 'herbs' => '[]']);
$expect($save($blank) === null, 'blank prescriptions do not enqueue'); $expect($save($blank) === null, 'blank prescriptions do not enqueue');
$expect((int) Db::name('prescription_ai_task')->count() === 0, 'no model call or task before snapshot'); $expect((int) Db::name('prescription_ai_task')->count() === 0, 'no model call or task before snapshot');
@@ -158,6 +188,8 @@ try {
$liveStatus = Api::statuses([$rx['id']], 1, $root)['items'][0]; $liveStatus = Api::statuses([$rx['id']], 1, $root)['items'][0];
$liveDetail = Api::detail($batchId, 1, $root); $liveDetail = Api::detail($batchId, 1, $root);
$liveReports = Api::reports(['prescription_id' => $rx['id']], 1, $root); $liveReports = Api::reports(['prescription_id' => $rx['id']], 1, $root);
$expect(!array_key_exists('doctor_snapshot', $liveStatus)
&& !array_key_exists('doctor_snapshot', $liveReports['lists'][0]), 'summaries do not expose original clinical content');
$captureModels = false; $captureModels = false;
$expect($modelQueries !== [], 'list/detail/history task select queries observed'); $expect($modelQueries !== [], 'list/detail/history task select queries observed');
foreach ($modelQueries as $sql) { foreach ($modelQueries as $sql) {
@@ -203,6 +235,10 @@ try {
Db::name('prescription_ai_batch')->where('id', $batchId)->update(['baseline_eligible' => 1, 'baseline_exclusions_json' => '[]']); Db::name('prescription_ai_batch')->where('id', $batchId)->update(['baseline_eligible' => 1, 'baseline_exclusions_json' => '[]']);
$eligibleStats = Api::statistics([], 1, $root); $eligibleStats = Api::statistics([], 1, $root);
$expect($eligibleStats['doctors'][0]['models']['qwen']['mean'] === 80.0, 'synthetic qualified baseline has empty exclusion reason'); $expect($eligibleStats['doctors'][0]['models']['qwen']['mean'] === 80.0, 'synthetic qualified baseline has empty exclusion reason');
// 分布直方图必须随统计一起返回,否则界面只能凭均值猜形状。
$bins = $eligibleStats['doctors'][0]['models']['qwen']['distribution'] ?? null;
$expect(is_array($bins) && array_sum($bins) === 1 && ($bins['[80,100]'] ?? 0) === 1,
'statistics expose the agreement distribution bins');
Db::name('prescription_ai_batch')->where('id', $batchId)->update(['baseline_eligible' => 0, 'baseline_exclusions_json' => '["SOURCE_HISTORY_VERSIONS_UNAVAILABLE"]']); Db::name('prescription_ai_batch')->where('id', $batchId)->update(['baseline_eligible' => 0, 'baseline_exclusions_json' => '["SOURCE_HISTORY_VERSIONS_UNAVAILABLE"]']);
$expect((int) Db::name('prescription_ai_attempt')->count() === 3, 'attempt history retained'); $expect((int) Db::name('prescription_ai_attempt')->count() === 3, 'attempt history retained');
$payload = ['request_key' => '12345678-abcd-1234-abcd-123456789012', 'herbs' => [['name' => 'fixture']]]; $payload = ['request_key' => '12345678-abcd-1234-abcd-123456789012', 'herbs' => [['name' => 'fixture']]];
@@ -221,11 +257,17 @@ try {
}); });
} catch (RuntimeException $e) {} } catch (RuntimeException $e) {}
$expect((int) Db::name('prescription_ai_batch')->count() === $countBefore, 'prescription and outbox roll back together'); $expect((int) Db::name('prescription_ai_batch')->count() === $countBefore, 'prescription and outbox roll back together');
Db::name('tcm_prescription')->where('id', $rx['id'])->update(['herbs' => '[{"name":"changed","dosage":20}]']); Db::name('tcm_prescription')->where('id', $rx['id'])->update(['herbs' => '[{"name":"changed","dosage":20}]',
'clinical_diagnosis' => 'Changed clinical diagnosis', 'patient_name' => 'Changed patient',
'aux_usage' => '{"times_per_day":3,"usage_days":9}']);
$newBatch = $save($rx); $newBatch = $save($rx);
$expect($newBatch !== $batchId, 'clinical change creates immutable new batch'); $expect($newBatch !== $batchId, 'clinical change creates immutable new batch');
$expect(Db::name('prescription_ai_batch')->where('id', $batchId)->value('validity') === 'prescription_changed', 'previous version invalidated'); $expect(Db::name('prescription_ai_batch')->where('id', $batchId)->value('validity') === 'prescription_changed', 'previous version invalidated');
$expect((int) Db::name('prescription_ai_result')->count() === 2, 'historic model results immutable'); $expect((int) Db::name('prescription_ai_result')->count() === 2, 'historic model results immutable');
$expect(Api::detail($batchId, 1, $root)['doctor_snapshot'] === $snapshot,
'historic report retains saved original after live prescription patient and diagnosis change');
$expect(Api::detail($newBatch, 1, $root)['doctor_snapshot']['diagnosis']['clinical_diagnosis'] === 'Changed clinical diagnosis',
'new pending revision carries its own updated original');
$newClaim = Store::claimBatch(); $newClaim = Store::claimBatch();
Store::finishPreparation($newClaim, $context); Store::finishPreparation($newClaim, $context);
$lease = Store::claimTask('qwen'); $lease = Store::claimTask('qwen');
@@ -27,6 +27,7 @@ $expect = static function (bool $condition, string $message) use (&$checks): voi
}; };
$command = (string) file_get_contents(dirname(__DIR__) . '/app/command/PrescriptionAiWork.php'); $command = (string) file_get_contents(dirname(__DIR__) . '/app/command/PrescriptionAiWork.php');
$configSource = (string) file_get_contents(dirname(__DIR__) . '/config/prescription_analysis.php');
$config = require dirname(__DIR__) . '/config/prescription_analysis.php'; $config = require dirname(__DIR__) . '/config/prescription_analysis.php';
// A model task keeps its database connection idle for the whole upstream call. MySQL's // A model task keeps its database connection idle for the whole upstream call. MySQL's
@@ -50,4 +51,17 @@ $expect($requestTimeout > 0 && $requestTimeout < (int) $config['lease_seconds'],
$expect((int) $config['max_attempts'] >= 1 && (int) $config['lease_seconds'] >= 60, $expect((int) $config['max_attempts'] >= 1 && (int) $config['lease_seconds'] >= 60,
'lease and attempt limits stay within a recoverable range'); 'lease and attempt limits stay within a recoverable range');
// Several prescriptions must be analysed at the same time instead of queueing one by one.
$expect((int) $config['max_parallel_per_model'] >= 2,
'more than one task may run per model');
$expect(str_contains($configSource, "env('prescription_analysis.MAX_PARALLEL_PER_MODEL'"),
'the concurrency limit is tunable without a code edit');
$expect(str_contains($configSource, "env('prescription_analysis.DAILY_PATIENT_BATCHES'")
&& str_contains($configSource, "env('prescription_analysis.DAILY_MODEL_TASKS'"),
'the daily budgets are tunable without a code edit');
// A context rebuild sweep must never delay a prescription that is waiting to be prepared.
$expect(str_contains($command, '!$worked && time() >= $sweepAt'),
'source sweeps only run while the prepare lane has nothing to claim');
echo 'Prescription AI worker resilience: ' . $checks . " checks passed\n"; echo 'Prescription AI worker resilience: ' . $checks . " checks passed\n";
@@ -0,0 +1,220 @@
<?php
declare(strict_types=1);
/**
* Exercises the real ORM and public mutation entry points in a disposable MySQL database.
* Run with ZYT_TRACKING_TEST_MYSQL_PORT pointing at an isolated local root/no-password instance.
* Never initializes the application or loads business database configuration.
*/
require dirname(__DIR__) . '/vendor/autoload.php';
use app\adminapi\logic\tcm\PrescriptionOrderLogic;
use think\Container;
use think\DbManager;
use think\facade\Db;
$port = (int) getenv('ZYT_TRACKING_TEST_MYSQL_PORT');
if ($port <= 0) {
fwrite(STDERR, "Set ZYT_TRACKING_TEST_MYSQL_PORT to an isolated local MySQL instance.\n");
exit(1);
}
$database = 'tracking_audit_test_' . bin2hex(random_bytes(6));
$pdo = new PDO("mysql:host=127.0.0.1;port={$port};charset=utf8mb4", 'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$pdo->exec("CREATE DATABASE `{$database}` CHARACTER SET utf8mb4");
$pdo->exec("USE `{$database}`");
$testApp = new think\App();
$manager = new DbManager();
$manager->setConfig([
'default' => 'mysql', 'auto_timestamp' => true, 'datetime_format' => false,
'connections' => ['mysql' => [
'type' => 'mysql', 'hostname' => '127.0.0.1', 'hostport' => $port,
'database' => $database, 'username' => 'root', 'password' => '',
'charset' => 'utf8mb4', 'prefix' => 'zyt_', 'fields_strict' => true,
]],
]);
Container::getInstance()->instance('think\DbManager', $manager);
Container::getInstance()->instance('config', new think\Config());
$admin = ['root' => 1, 'admin_id' => 1, 'name' => '隔离测试管理员'];
$checks = 0;
$expect = static function (bool $ok, string $message) use (&$checks): void {
if (!$ok) {
throw new RuntimeException($message . ' | ' . PrescriptionOrderLogic::getError());
}
$checks++;
};
try {
$pdo->exec(file_get_contents(dirname(__DIR__) . '/database/migrations/2026_04_07_create_tcm_prescription_order.sql'));
$pdo->exec('ALTER TABLE zyt_tcm_prescription_order
ADD agency_collect_amount DECIMAL(10,2) NULL, ADD paid DECIMAL(10,2) DEFAULT 0,
ADD express_company VARCHAR(20) DEFAULT "auto", ADD ship_mode VARCHAR(20) DEFAULT "gancao",
ADD remark_assistant VARCHAR(500) DEFAULT "", ADD dose_unit VARCHAR(10) DEFAULT "",
ADD dose_count INT DEFAULT 1, ADD gancao_reciperl_order_no VARCHAR(100) DEFAULT ""');
$pdo->exec(file_get_contents(dirname(__DIR__) . '/database/migrations/2026_04_09_prescription_order_pay_links.sql'));
$pdo->exec('CREATE TABLE zyt_tcm_prescription_order_log (
id INT PRIMARY KEY AUTO_INCREMENT, prescription_order_id INT, admin_id INT,
admin_name VARCHAR(64), action VARCHAR(32), summary VARCHAR(500), create_time INT
) ENGINE=InnoDB');
$pdo->exec('CREATE TABLE zyt_tcm_prescription (
id INT PRIMARY KEY AUTO_INCREMENT, diagnosis_id INT DEFAULT 1, gender INT DEFAULT 0,
creator_id INT DEFAULT 1, assistant_id INT DEFAULT 0, is_shared INT DEFAULT 0,
herbs TEXT, audit_status INT DEFAULT 1, void_status INT DEFAULT 0, visible_role_ids VARCHAR(100) DEFAULT "",
create_time INT DEFAULT 0, update_time INT DEFAULT 0, delete_time INT NULL
) ENGINE=InnoDB');
$pdo->exec('CREATE TABLE zyt_tcm_diagnosis (id INT PRIMARY KEY, assistant_id INT DEFAULT 0, delete_time INT NULL)');
$pdo->exec('INSERT INTO zyt_tcm_diagnosis (id) VALUES (1)');
$pdo->exec('CREATE TABLE zyt_pharmacy_submission_claim (
id INT PRIMARY KEY AUTO_INCREMENT, prescription_order_id INT, source_revision INT, status VARCHAR(50)
) ENGINE=InnoDB');
$pdo->exec('CREATE TABLE zyt_admin (id INT PRIMARY KEY, name VARCHAR(100), delete_time INT NULL)');
$pdo->exec('CREATE TABLE zyt_admin_role (admin_id INT, role_id INT)');
$pdo->exec('CREATE TABLE zyt_system_role_menu (role_id INT, menu_id INT)');
$pdo->exec('CREATE TABLE zyt_system_menu (id INT PRIMARY KEY, perms VARCHAR(100), is_disable INT DEFAULT 0)');
$newRx = static fn (): int => (int) Db::name('tcm_prescription')->insertGetId(['herbs' => '[]']);
$fixture = static fn (array $fields = []): int => (int) Db::name('tcm_prescription_order')->insertGetId(array_merge([
'prescription_id' => $newRx(), 'diagnosis_id' => 1, 'creator_id' => 1,
'order_no' => 'TEST-' . bin2hex(random_bytes(6)), 'amount' => 100,
'prescription_audit_status' => 1, 'payment_slip_audit_status' => 1,
'fulfillment_status' => 2, 'tracking_number' => 'SF-OLD', 'express_company' => 'sf',
'create_time' => time(),
], $fields));
$row = static fn (int $id): array => Db::name('tcm_prescription_order')->where('id', $id)->find();
$logs = static fn (int $id): array => Db::name('tcm_prescription_order_log')
->where('prescription_order_id', $id)->order('id')->select()->toArray();
$snapshot = static fn (int $id): array => [$row($id), $logs($id)];
$editParams = static fn (int $id): array => [
'id' => $id, 'recipient_name' => '更新收货人', 'recipient_phone' => '13000000000',
'shipping_address' => '更新地址', 'fee_type' => 3, 'amount' => 100, 'remark_assistant' => '其他信息已保存',
];
$auditError = '处方审核和支付单审核均通过后,才可填写或修改快递单号';
// Full audit-status matrix, covering first entry, corrections and shipped orders.
foreach ([0, 1, 2] as $rxStatus) {
foreach ([0, 1, 2] as $payStatus) {
$approved = $rxStatus === 1 && $payStatus === 1;
foreach (['', 'SF-OLD'] as $oldTracking) {
$fields = ['prescription_audit_status' => $rxStatus, 'payment_slip_audit_status' => $payStatus,
'tracking_number' => $oldTracking];
$id = $fixture($fields);
$before = $snapshot($id);
$out = PrescriptionOrderLogic::ddcode($id, 'jd', ' JD-NEW ', 1, $admin);
$expect(is_array($out) === $approved, "ddcode audit {$rxStatus}/{$payStatus}");
if ($approved) {
$expect($row($id)['tracking_number'] === 'JD-NEW' && $row($id)['express_company'] === 'jd',
'Approved correction must persist normalized logistics');
$expect(count($logs($id)) === 1 && $logs($id)[0]['action'] === 'fill_tracking',
'Approved correction must record its audit trail');
} else {
$expect(PrescriptionOrderLogic::getError() === $auditError && $snapshot($id) === $before,
'Unapproved correction must reject even root without changing row or log');
}
foreach ([2, 5] as $status) {
$id = $fixture($fields + ['fulfillment_status' => $status]);
$before = $snapshot($id);
$out = PrescriptionOrderLogic::ship($id, 'jd', 'JD-SHIP', 'gancao', 1, $admin);
$expect(is_array($out) === $approved, "ship audit {$rxStatus}/{$payStatus}, status {$status}");
if ($approved) {
$expect($row($id)['tracking_number'] === 'JD-SHIP' && (int) $row($id)['fulfillment_status'] === 5,
'Approved shipping must persist logistics and shipped status');
} else {
$expect(PrescriptionOrderLogic::getError() === $auditError && $snapshot($id) === $before,
'Shipping status alone must never bypass audit requirements');
}
}
$id = $fixture($fields + ['fulfillment_status' => 5]);
$before = $snapshot($id);
$out = PrescriptionOrderLogic::edit($editParams($id) + ['tracking_number' => 'JD-EDIT'], 1, $admin);
$expect(is_array($out) === $approved, "edit audit {$rxStatus}/{$payStatus}");
if ($approved) {
$expect($row($id)['tracking_number'] === 'JD-EDIT', 'Approved edit must persist changed tracking');
} else {
$expect(PrescriptionOrderLogic::getError() === $auditError && $snapshot($id) === $before,
'General edit cannot smuggle unapproved tracking changes or partially persist other fields');
}
}
}
}
// Omitted or unchanged tracking must never block normal editing or clear an existing number.
foreach ([[0, 0], [1, 0], [2, 0], [1, 2], [1, 1]] as [$rxStatus, $payStatus]) {
foreach ([[], ['tracking_number' => 'SF-OLD']] as $trackingParams) {
$id = $fixture(['prescription_audit_status' => $rxStatus, 'payment_slip_audit_status' => $payStatus]);
$out = PrescriptionOrderLogic::edit($editParams($id) + $trackingParams, 1, $admin);
$expect(is_array($out) && $row($id)['tracking_number'] === 'SF-OLD', 'Unchanged or absent tracking must preserve old number');
$expect($row($id)['remark_assistant'] === '其他信息已保存' && $row($id)['recipient_name'] === '更新收货人',
'Ordinary edits must continue saving while audits are pending or rejected');
}
}
foreach ([false, true] as $approved) {
$id = $fixture(['payment_slip_audit_status' => $approved ? 1 : 0, 'fulfillment_status' => 5]);
$before = $snapshot($id);
$out = PrescriptionOrderLogic::edit($editParams($id) + ['tracking_number' => ''], 1, $admin);
$expect(is_array($out) === $approved, 'Clearing a tracking number must also require both approvals');
$expect($approved ? $row($id)['tracking_number'] === '' : $snapshot($id) === $before,
'Rejected clearing must preserve existing tracking and all order data');
}
// A form edit that resets payment audit must not simultaneously save new tracking.
foreach ([[], ['pay_order_ids' => []], ['linked_pay_order_id' => null]] as $paymentParams) {
$id = $fixture();
$before = $snapshot($id);
$out = PrescriptionOrderLogic::edit($editParams($id) + $paymentParams + ['tracking_number' => 'JD-PENDING'], 1, $admin);
$expect($out === false && PrescriptionOrderLogic::getError() === $auditError && $snapshot($id) === $before,
'Final pending audit must reject changed tracking and roll back all form changes');
}
// New orders always start pending, even if clients submit fake audit flags or the source prescription is approved.
$createParams = static fn (int $rxId): array => [
'prescription_id' => $rxId, 'diagnosis_id' => 1, 'recipient_name' => '测试患者',
'recipient_phone' => '13000000000', 'shipping_address' => '测试地址', 'fee_type' => 3, 'amount' => 100,
'prescription_audit_status' => 1, 'payment_slip_audit_status' => 1,
];
$rxId = $newRx();
$out = PrescriptionOrderLogic::create($createParams($rxId) + ['tracking_number' => 'SF-CREATE'], 1, $admin);
$expect($out === false && PrescriptionOrderLogic::getError() === $auditError, 'Creation must reject prefilled tracking');
$expect(Db::name('tcm_prescription_order')->where('prescription_id', $rxId)->count() === 0,
'Rejected creation must not leave an order');
foreach ([[], ['tracking_number' => ''], ['tracking_number' => ' ']] as $trackingParams) {
$out = PrescriptionOrderLogic::create($createParams($newRx()) + $trackingParams, 1, $admin);
$expect(is_array($out) && $out['tracking_number'] === ''
&& (int) $out['prescription_audit_status'] === 0 && (int) $out['payment_slip_audit_status'] === 0,
'Normal creation must store empty tracking and pending audits');
}
// Audit revocation must be observed on the next request; historical fulfillment/snapshot data grants no exemption.
foreach ([1, 2, 3, 4, 5, 6, 9, 10] as $status) {
$id = $fixture(['fulfillment_status' => $status, 'gancao_reciperl_order_no' => 'REMOTE-HISTORY']);
$out = PrescriptionOrderLogic::ddcode($id, 'jd', 'JD-APPROVED', 1, $admin);
$expect(is_array($out) && (int) $row($id)['fulfillment_status'] === $status,
'Approved correction must preserve existing fulfillment and remote snapshot behavior');
Db::name('tcm_prescription_order')->where('id', $id)->update(['payment_slip_audit_status' => 0]);
$before = $snapshot($id);
$expect(PrescriptionOrderLogic::ddcode($id, 'sf', 'SF-REVOKED', 1, $admin) === false && $snapshot($id) === $before,
'Revoked audit must immediately block further corrections');
}
$id = $fixture();
$before = $snapshot($id);
$outsider = ['root' => 0, 'admin_id' => 2, 'role_id' => [], 'name' => '无权限账号'];
$expect(PrescriptionOrderLogic::ddcode($id, 'jd', 'JD-NO-ACCESS', 2, $outsider) === false
&& PrescriptionOrderLogic::getError() === '无权限操作' && $snapshot($id) === $before,
'Audit approval must not grant order access');
// The mandatory tracking audit trail remains atomic with the logistics update.
$pdo->exec("CREATE TRIGGER reject_tracking_log BEFORE INSERT ON zyt_tcm_prescription_order_log
FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'forced tracking log failure'");
$expect(PrescriptionOrderLogic::ddcode($id, 'jd', 'JD-ROLLBACK', 1, $admin) === false && $snapshot($id) === $before,
'Log failure must roll back the approved correction');
$pdo->exec('DROP TRIGGER reject_tracking_log');
echo "PrescriptionOrderTrackingAuditTest: {$checks} assertions passed\n";
} finally {
$manager->connect()->close();
$pdo->exec("DROP DATABASE `{$database}`");
}