更新
This commit is contained in:
@@ -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()
|
||||||
@@ -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> <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 0–100 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
@@ -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"
|
||||||
@@ -681,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")
|
||||||
@@ -971,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)
|
||||||
@@ -1099,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
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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 项等)。
|
||||||
|
- 服务端两个落库套件在独立 MySQL(127.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' ? '药房直发' : '甘草药方发';
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}`");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user