Files
deep_research/generate_report.py
T
2026-04-21 12:31:58 +08:00

764 lines
45 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Generate formal meeting minutes PDF for Yugong Biotech audit remediation.
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.lib.styles import ParagraphStyle
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
import os
# ── 字体注册 ──────────────────────────────────────────────────────────────────
pdfmetrics.registerFont(TTFont('STHeiti', '/System/Library/Fonts/STHeiti Medium.ttc', subfontIndex=0))
pdfmetrics.registerFont(TTFont('STHeitiLight', '/System/Library/Fonts/STHeiti Light.ttc', subfontIndex=0))
# ── 颜色定义 ──────────────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor('#1B3A6B')
MID_BLUE = colors.HexColor('#2E6DA4')
LIGHT_BLUE = colors.HexColor('#D6E4F0')
RED_CRIT = colors.HexColor('#C0392B')
ORANGE_MAJ = colors.HexColor('#E67E22')
YELLOW_MIN = colors.HexColor('#F1C40F')
BG_CRIT = colors.HexColor('#FADBD8')
BG_MAJ = colors.HexColor('#FDEBD0')
BG_MIN = colors.HexColor('#FEF9E7')
BG_HEAD = colors.HexColor('#EBF5FB')
GRAY_LINE = colors.HexColor('#BDC3C7')
WHITE = colors.white
DARK_GRAY = colors.HexColor('#2C3E50')
LIGHT_GRAY = colors.HexColor('#F2F3F4')
# ── 段落样式 ──────────────────────────────────────────────────────────────────
def make_styles():
base = {'fontName': 'STHeiti', 'leading': 16}
return {
'title': ParagraphStyle('title',
fontName='STHeiti', fontSize=16, leading=22,
alignment=TA_CENTER, textColor=WHITE, spaceAfter=4),
'subtitle': ParagraphStyle('subtitle',
fontName='STHeitiLight', fontSize=8.5, leading=13,
alignment=TA_CENTER, textColor=colors.HexColor('#D6EAF8'), spaceAfter=2),
'section': ParagraphStyle('section',
fontName='STHeiti', fontSize=12, leading=18,
textColor=WHITE, spaceBefore=6, spaceAfter=4),
'subsection': ParagraphStyle('subsection',
fontName='STHeiti', fontSize=10.5, leading=16,
textColor=DARK_BLUE, spaceBefore=8, spaceAfter=4),
'body': ParagraphStyle('body',
fontName='STHeitiLight', fontSize=9.5, leading=15,
textColor=DARK_GRAY, spaceAfter=3, alignment=TA_LEFT),
'body_bold': ParagraphStyle('body_bold',
fontName='STHeiti', fontSize=9.5, leading=15,
textColor=DARK_GRAY, spaceAfter=3),
'cell': ParagraphStyle('cell',
fontName='STHeitiLight', fontSize=8.5, leading=13,
textColor=DARK_GRAY),
'cell_bold': ParagraphStyle('cell_bold',
fontName='STHeiti', fontSize=8.5, leading=13,
textColor=DARK_GRAY),
'cell_crit': ParagraphStyle('cell_crit',
fontName='STHeiti', fontSize=8.5, leading=13,
textColor=RED_CRIT),
'cell_maj': ParagraphStyle('cell_maj',
fontName='STHeiti', fontSize=8.5, leading=13,
textColor=ORANGE_MAJ),
'cell_min': ParagraphStyle('cell_min',
fontName='STHeiti', fontSize=8.5, leading=13,
textColor=colors.HexColor('#9B7D11')),
'footer': ParagraphStyle('footer',
fontName='STHeitiLight', fontSize=7.5, leading=11,
textColor=colors.HexColor('#7F8C8D'), alignment=TA_CENTER),
'cell_header': ParagraphStyle('cell_header',
fontName='STHeiti', fontSize=8.5, leading=13,
textColor=WHITE),
'reg_title': ParagraphStyle('reg_title',
fontName='STHeiti', fontSize=9.5, leading=14,
textColor=MID_BLUE, spaceAfter=2),
'bullet': ParagraphStyle('bullet',
fontName='STHeitiLight', fontSize=9, leading=14,
textColor=DARK_GRAY, leftIndent=12, spaceAfter=2),
}
S = make_styles()
# ── 辅助:带背景色的节标题 ────────────────────────────────────────────────────
def section_header(text, color=MID_BLUE, text_style='section'):
data = [[Paragraph(text, S[text_style])]]
t = Table(data, colWidths=[17.5*cm])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), color),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 10),
('RIGHTPADDING', (0,0), (-1,-1), 10),
('ROWBACKGROUNDS', (0,0), (-1,-1), [color]),
]))
return t
def info_table(rows):
"""Two-column key-value table for meeting info."""
data = [[Paragraph(k, S['cell_bold']), Paragraph(v, S['cell'])]
for k, v in rows]
t = Table(data, colWidths=[3.8*cm, 13.7*cm])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (0,-1), BG_HEAD),
('BACKGROUND', (1,0), (1,-1), WHITE),
('GRID', (0,0), (-1,-1), 0.4, GRAY_LINE),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 6),
('RIGHTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
return t
# ── 主文档构建 ────────────────────────────────────────────────────────────────
def build_pdf(output_path):
doc = SimpleDocTemplate(
output_path,
pagesize=A4,
leftMargin=2.0*cm, rightMargin=2.0*cm,
topMargin=2.2*cm, bottomMargin=2.2*cm,
title='愚公生物(百时美)供应商审计整改会议纪要',
author='愚公生物科技有限公司 质量管理部',
)
story = []
W = 17.5 * cm # 可用宽度
# ═══════════════════════════════════════════════════════════════════════════
# 封面标题区
# ═══════════════════════════════════════════════════════════════════════════
cover_data = [
[Paragraph('愚公生物科技有限公司(百时美)', S['subtitle'])],
[Paragraph('供应商审计问题复盘与质量整改专项会议纪要', S['title'])],
[Paragraph('SUPPLIER AUDIT REMEDIATION — INTERNAL MEETING MINUTES', S['subtitle'])],
[Paragraph('文件编号:YG-QA-MOM-2026-001  版本:V1.0  密级:内部', S['subtitle'])],
]
cover_t = Table(cover_data, colWidths=[W])
cover_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), DARK_BLUE),
('TOPPADDING', (0,0), (-1,-1), 12),
('BOTTOMPADDING', (0,0), (-1,-1), 12),
('LEFTPADDING', (0,0), (-1,-1), 14),
('RIGHTPADDING', (0,0), (-1,-1), 14),
]))
story.append(cover_t)
story.append(Spacer(1, 0.4*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 一、会议基本信息
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header('一、会议基本信息'))
story.append(Spacer(1, 0.2*cm))
story.append(info_table([
('会议主题', '申基供应商审计问题复盘、质量体系整改及 4000 万订单配合工作'),
('会议日期', '2026 年 4 月 10 日'),
('会议地点', '愚公生物科技有限公司 会议室'),
('主持人', '任宏杰(说话人 A'),
('参会人员', '任宏杰(A,主持)、二袁(B)、说话人 C、毕海(D)、说话人 E'),
('记录人', '质量管理部'),
('核心背景', '申基承接制药企业 CDMO 业务,委托愚公生物生产关键工具酶原料。\n'
'申基依其客户要求对愚公生物开展现场供应商审计,发现若干缺陷。\n'
'本次会议为愚公生物内部复盘,制定整改方案与行动计划。\n'
'4000 万元订单执行不受本次审计直接影响,各方共担提考连带责任。'),
]))
story.append(Spacer(1, 0.5*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 二、适用质量法规体系综述
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header('二、酶类原料供应商适用质量法规体系综述'))
story.append(Spacer(1, 0.25*cm))
story.append(Paragraph('2.1 原料定位判断', S['subsection']))
story.append(Paragraph(
'愚公生物生产的工具酶(含 RI 核糖核酸酶抑制剂等)在申基 CDMO 场景中,以工艺辅助或关键'
'原料身份介入药物生产,<b>不作为最终药品活性成分(API)存在于制成品中</b>。依据 ICH Q7、'
'IPEC-PQG GMP 指南及 FDA/EMA 相关指导原则,其定位层级如下:',
S['body']))
pos_data = [
[Paragraph('定位层级', S['cell_header']),
Paragraph('判断依据', S['cell_header']),
Paragraph('适用情景', S['cell_header']),
Paragraph('适用标准', S['cell_header'])],
[Paragraph('API 起始物料\nAPI Starting Material', S['cell']),
Paragraph('酶直接掺入 API 结构,或残留物影响 API 关键质量属性(CQAs)', S['cell']),
Paragraph('酶参与关键化学反应步骤,残留显著影响杂质谱', S['cell']),
Paragraph('ICH Q7 全文、供应商审计、DMF 备案', S['cell'])],
[Paragraph('关键原料 / 工艺助剂\nCritical Reagent\n★ 当前最可能定位', S['cell_bold']),
Paragraph('酶仅参与上游工艺步骤,最终产品中不含酶活或酶蛋白;\n但其质量直接影响产品 CQA(如活性、纯度、内毒素)', S['cell']),
Paragraph('RI 酶用于 mRNA/蛋白合成工艺,\n纯化后被去除', S['cell']),
Paragraph('ICH Q7(原材料章节)、IPEC-PQG GMP(基础)、\nISO 9001:2015、质量协议(QA', S['cell'])],
[Paragraph('辅料 / 工艺助剂\nExcipient/Processing Aid', S['cell']),
Paragraph('酶以非治疗性功能加入制剂,如增稠、稳定等', S['cell']),
Paragraph('较少见于工具酶场景', S['cell']),
Paragraph('IPEC-PQG GMP、EXCiPACT 认证、ISO 9001', S['cell'])],
]
pos_t = Table(pos_data, colWidths=[3.5*cm, 5*cm, 4*cm, 5*cm])
pos_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'STHeiti'),
('BACKGROUND', (0,2), (-1,2), LIGHT_BLUE),
('GRID', (0,0), (-1,-1), 0.4, GRAY_LINE),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, LIGHT_BLUE, WHITE]),
]))
story.append(pos_t)
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph('2.2 核心适用法规框架', S['subsection']))
reg_rows = [
('ICH Q7\nAPI GMP', 'GMP 原材料章节要求供应商评估与批准;关键原材料须执行三批次全检基准测试;质量协议中须明确检验方法、CoA 要求、供应商变更通知条款。'),
('IPEC-PQG GMP 指南\n(辅料/关键原料)', '适用于药物辅料及工艺助剂生产商;要求在工艺中"有理可循"的节点执行 GMP;覆盖文件管理、变更控制、偏差管理、清洁验证、稳定性研究。与 ISO 9001 互补。'),
('ISO 9001:2015\n(质量管理体系)', '作为最低基线的质量管理体系;涵盖记录控制、不合格品管理、持续改进、内审等。不能替代药品 GMP,但为药企客户审计的基础期望。'),
('中国 NMPA\n相关法规', '《原辅料及直接接触药品包装材料供应商审计指南》(2020年):药企须对关键原辅料供应商定期审计;供应商须具备与产品风险相称的质量体系;原辅料变更须履行评估与报告程序。'),
('质量协议(QA\n与供应商资质', '申基作为 CDMO 依托其下游客户要求,须与愚公生物签订正式质量协议,约定:CoA 内容与放行标准、审计权利、变更通知时限、可溯源性要求、稳定性和储存条件、内毒素/生物负载限度。'),
('关键检测要求\n(酶原料特有)', '活性(RI 活性等效力测定)、纯度(SEC-HPLC)、内毒素(LAL)、生物负载(TAMC/TYMC)、宿主细胞蛋白(HCP)、残余 DNA、重金属;稳定性数据(支持储存条件)。'),
]
for title, content in reg_rows:
data = [[Paragraph(title, S['reg_title']), Paragraph(content, S['cell'])]]
t = Table(data, colWidths=[3.8*cm, 13.7*cm])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (0,-1), BG_HEAD),
('GRID', (0,0), (-1,-1), 0.4, GRAY_LINE),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('RIGHTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(t)
story.append(Spacer(1, 0.15*cm))
story.append(Paragraph(
'结论:愚公生物作为药物关键工具酶原料供应商,不必全面执行药品 GMP(21 CFR Part 211 或《药品生产质量管理规范》),'
'但须建立并有效运行<b>以 ISO 9001 为基础、参照 IPEC-PQG GMP 指南核心要求的质量体系</b>,并满足申基及其客户在'
'质量协议中约定的特定要求,包括:数据完整性、验证体系、关键参数检测(尤其 RI 活性)及稳定性研究。',
S['body']))
story.append(Spacer(1, 0.5*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 三、审计缺陷项分级分析
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header('三、审计缺陷项严重等级分析'))
story.append(Spacer(1, 0.2*cm))
# 分级说明
legend_data = [
[Paragraph('严重(Critical', S['cell_crit']),
Paragraph('重大(Major', S['cell_maj']),
Paragraph('一般(Minor', S['cell_min'])],
[Paragraph('直接危及产品质量/安全性,或存在系统性数据诚信问题,不可接受,须立即整改', S['cell']),
Paragraph('显著违反质量体系要求,可能导致产品质量问题,须限期整改', S['cell']),
Paragraph('程序性缺陷,当前影响有限,须计划整改', S['cell'])],
]
legend_t = Table(legend_data, colWidths=[W/3, W/3, W/3])
legend_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (0,-1), BG_CRIT),
('BACKGROUND', (1,0), (1,-1), BG_MAJ),
('BACKGROUND', (2,0), (2,-1), BG_MIN),
('GRID', (0,0), (-1,-1), 0.4, GRAY_LINE),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('RIGHTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('ALIGN', (0,0), (-1,0), 'CENTER'),
]))
story.append(legend_t)
story.append(Spacer(1, 0.35*cm))
# ── 3.1 严重缺陷 ──────────────────────────────────────────────────────────
story.append(section_header('3.1 严重缺陷(Critical', color=RED_CRIT))
story.append(Spacer(1, 0.2*cm))
crit_header = [
Paragraph('编号', S['cell_header']),
Paragraph('缺陷描述', S['cell_header']),
Paragraph('法规依据', S['cell_header']),
Paragraph('风险说明', S['cell_header']),
]
crit_items = [
('C-01', '系统性记录造假与数据完整性严重缺失',
'记录普遍事后补写;一批生产对应三批数据;温湿度/压差辅助记录迎审时统一补填,笔迹一眼可辨,与主批记录时间矛盾;批记录存在明显造假痕迹。',
'ICH Q7 第 6 章;IPEC-PQG GMP 4.5ISO 9001 7.5FDA Data Integrity Guidance2018',
'数据诚信(Data Integrity)是 GMP/质量体系的核心基石。系统性造假导致审计方对所有数据完全不信任,无法证明产品质量受控,直接危及下游客户的药品申报。'),
('C-02', 'RI 活性检测能力缺失,未纳入质量标准',
'生产端荧光仪无法检测 RI 活性;RI 活性效力测定未纳入原酶及成品质量标准;无法证明每批产品活性符合要求。',
'ICH Q7 11.1IPEC-PQG GMP 8.1FDA Potency Guidance;质量协议约定检测项目',
'RI 活性是该产品的核心关键质量属性(CQA)。无活性检测即无法保证产品功能一致性,直接影响客户药物工艺结果,属最高级别产品质量风险。'),
('C-03', '清洁验证严重缺失(交叉污染风险)',
'100+ 品种共线生产,无任何残留限度设定、无棉签回收率验证、无中转容器清洁验证;无法证明产品间无交叉污染。',
'IPEC-PQG GMP 6.4ICH Q7 第 12 章;EU GMP Annex 15',
'多品种共线而无清洁验证,存在产品间交叉污染的实质性风险,可能导致客户产品出现工艺外源污染物,危及药品安全性与有效性。'),
]
for code, title, desc, ref, risk in crit_items:
header_row = [
Paragraph(f'<b>{code}</b>', S['cell_crit']),
Paragraph(f'<b>{title}</b>', S['cell_crit']),
Paragraph('', S['cell']),
Paragraph('', S['cell']),
]
desc_row = [
Paragraph('缺陷描述', S['cell_bold']),
Paragraph(desc, S['cell']),
Paragraph('法规依据', S['cell_bold']),
Paragraph(ref, S['cell']),
]
risk_row = [
Paragraph('风险说明', S['cell_bold']),
Paragraph(risk, S['cell']),
Paragraph('', S['cell']),
Paragraph('', S['cell']),
]
t = Table([header_row, desc_row, risk_row],
colWidths=[1.8*cm, 6.8*cm, 2*cm, 6.9*cm])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), BG_CRIT),
('BACKGROUND', (0,1), (0,-1), LIGHT_GRAY),
('BACKGROUND', (2,1), (2,-1), LIGHT_GRAY),
('GRID', (0,0), (-1,-1), 0.4, GRAY_LINE),
('SPAN', (1,0), (3,0)),
('SPAN', (1,2), (3,2)),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(KeepTogether([t, Spacer(1, 0.2*cm)]))
# ── 3.2 重大缺陷 ──────────────────────────────────────────────────────────
story.append(Spacer(1, 0.15*cm))
story.append(section_header('3.2 重大缺陷(Major', color=ORANGE_MAJ))
story.append(Spacer(1, 0.2*cm))
maj_items = [
('M-01', '文件体系不具可执行性,照搬药企模板',
'文件大量照搬恒瑞/天晴模板,与公司实际人力、产能、工艺严重不符;存在替换页、纸张颜色不一致等低级错误;文件被审计方整体质疑。',
'IPEC-PQG GMP 4.1ISO 9001 7.5ICH Q7 第 6 章'),
('M-02', '文件发放、接收、销毁流程缺失,虚拟执行',
'无接收人记录、文件名错误、发放流程形同虚设;存在虚拟执行痕迹(文件从未真实流转)。',
'IPEC-PQG GMP 4.2ICH Q7 6.5ISO 9001 7.5.3'),
('M-03', '偏差/OOS/OOT 管理体系完全缺失',
'无任何偏差、超标(OOS)、超趋势(OOT)记录与处理程序;审计方判定"产品是检出来的,不是生产出来的"。',
'ICH Q7 第 15 章;IPEC-PQG GMP 5.2FDA OOS Guidance2006'),
('M-04', '工艺验证报告不可信,关键参数未覆盖',
'工艺验证报告未覆盖关键工艺参数、收率及内毒素去除效率;批记录与工艺规程描述不一致;纯化多峰即收,无固定标准。',
'ICH Q7 第 12 章;IPEC-PQG GMP 7.1EU GMP Annex 15'),
('M-05', '稳定性研究缺失,产品全生命周期数据空白',
'原酶未开展稳定性研究;现有方案设计不合理(25℃放置 12 个月,不符合酶类特性);无法为储存条件和效期提供数据支撑。',
'ICH Q1A(R2)IPEC-PQG GMP 8.2ICH Q7 11.6'),
('M-06', 'QA/QC 人员专业能力严重不足,QA 负责人不称职',
'QA/QC 团队多为外行,不了解酶行业特性;QA 负责人言语轻浮、以第三方心态应对客户,现场被当场质疑;人员一人多岗,审计方直接判定数据不可信。',
'ICH Q7 第 2 章;IPEC-PQG GMP 3.2ISO 9001 7.2'),
('M-07', '关键设备缺失,设备标识不全',
'缺少灭菌锅、干燥箱等必要设备;砝码、设备标识不全;荧光仪无法满足 RI 活性检测要求。',
'ICH Q7 第 5 章;IPEC-PQG GMP 6.1ISO 9001 7.1'),
('M-08', '酶与 buffer 未分离管理,不符合规范',
'酶与 buffer 混合管理,无独立稳定性和质量控制;不符合 GMP/IPEC 产品分类管理规范。',
'IPEC-PQG GMP 5.1ICH Q7 11.2'),
]
maj_header = [
Paragraph('编号', S['cell_header']),
Paragraph('缺陷名称', S['cell_header']),
Paragraph('缺陷描述', S['cell_header']),
Paragraph('法规依据', S['cell_header']),
]
maj_data = [maj_header] + [
[Paragraph(f'<b>{c}</b>', S['cell_maj']),
Paragraph(f'<b>{t}</b>', S['cell_bold']),
Paragraph(d, S['cell']),
Paragraph(r, S['cell'])]
for c, t, d, r in maj_items
]
maj_t = Table(maj_data, colWidths=[1.5*cm, 3.5*cm, 7.8*cm, 4.7*cm])
maj_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), ORANGE_MAJ),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'STHeiti'),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, BG_MAJ]),
('GRID', (0,0), (-1,-1), 0.4, GRAY_LINE),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(maj_t)
story.append(Spacer(1, 0.35*cm))
# ── 3.3 一般缺陷 ──────────────────────────────────────────────────────────
story.append(section_header('3.3 一般缺陷(Minor', color=colors.HexColor('#9B7D11')))
story.append(Spacer(1, 0.2*cm))
min_items = [
('N-01', '现场卫生问题',
'下水管道锈蚀严重,现场整洁度不达标,影响客户观感与检查结论。',
'IPEC-PQG GMP 6.3ISO 9001 7.1'),
('N-02', '文件低级错误(替换页、颜色不一致)',
'文件中存在替换页、纸张颜色不一致等低级错误,直接导致文件整体可信度下降。',
'ICH Q7 6.2IPEC-PQG GMP 4.2'),
('N-03', '批记录数据项不完整(缺少计算过程)',
'批记录中关键数据缺少计算过程记录,数据可追溯性不足(此项与 C-01 系统造假有别,属记录习惯问题)。',
'ICH Q7 6.4IPEC-PQG GMP 4.5'),
]
min_header = [
Paragraph('编号', S['cell_header']),
Paragraph('缺陷名称', S['cell_header']),
Paragraph('缺陷描述', S['cell_header']),
Paragraph('法规依据', S['cell_header']),
]
min_data = [min_header] + [
[Paragraph(f'<b>{c}</b>', S['cell_min']),
Paragraph(f'<b>{t}</b>', S['cell_bold']),
Paragraph(d, S['cell']),
Paragraph(r, S['cell'])]
for c, t, d, r in min_items
]
min_t = Table(min_data, colWidths=[1.5*cm, 3.5*cm, 7.8*cm, 4.7*cm])
min_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#9B7D11')),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'STHeiti'),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, BG_MIN]),
('GRID', (0,0), (-1,-1), 0.4, GRAY_LINE),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(min_t)
story.append(Spacer(1, 0.5*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 四、整改行动计划
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header('四、整改行动计划(CAPA'))
story.append(Spacer(1, 0.15*cm))
story.append(Paragraph(
'以下行动计划以缺陷严重等级为优先序,明确责任部门/人及预估完成日期。'
'所有整改措施须由 QA 部门完成审核后方可关闭。',
S['body']))
story.append(Spacer(1, 0.25*cm))
# ── 4.1 严重缺陷行动项 ────────────────────────────────────────────────────
story.append(Paragraph('4.1 严重缺陷整改行动项(须立即启动)', S['subsection']))
action_header = [
Paragraph('关联\n缺陷', S['cell_header']),
Paragraph('行动项', S['cell_header']),
Paragraph('具体措施', S['cell_header']),
Paragraph('责任人', S['cell_header']),
Paragraph('预估完成\n日期', S['cell_header']),
]
crit_actions = [
('C-01', 'A-01\n禁止事后\n补录并建立\n数据诚信\n管理制度',
'① 即日起发布内部令:禁止任何形式的事后补录,违者追责\n'
'② 制定《数据完整性管理程序》,涵盖原始记录、电子数据、审计追踪\n'
'③ 所有批记录和辅助记录须当日填写,QA 每日抽查\n'
'④ 对现有库存批记录完整性进行回顾,提出整改方案\n'
'⑤ 开展全员数据诚信培训(1 次/月)',
'毕海(QA\n全员配合', '2026-05-01\n(①②即日起执行)'),
('C-02', 'A-02\n建立 RI 活\n性检测能力\n并纳入质量\n标准',
'① 立即协调研发部荧光仪,临时用于 RI 活性检测\n'
'② 任宏杰负责制定 RI 活性效力测定方案,完成方法验证\n'
'③ 采购/租赁正式 RI 活性检测设备(二袁负责对接采购)\n'
'④ 将 RI 活性纳入原酶及成品质量标准,修订 QS 文件\n'
'⑤ 对历史存档批次进行补充 RI 活性检测(留样检测)',
'任宏杰(研发)\n二袁(采购)\n新 QA 负责人', '2026-07-15\n(设备临时方案\n2026-05-15'),
('C-03', 'A-03\n建立清洁\n验证体系',
'① 梳理 100+ 品种的共线生产矩阵,按风险分级\n'
'② 制定《清洁验证主计划》(CVP),明确残留限度计算方法(基于 HBEL/PDE)\n'
'③ 优先完成高风险品种组合的棉签回收率验证和中转容器清洁验证\n'
'④ 建立清洁验证记录模板及日常监控程序',
'毕海(QA\n新 QA 负责人\n生产部', '2026-08-31\nCVP 文件\n2026-06-01'),
]
action_data = [action_header]
for defect, action, measures, owner, date in crit_actions:
action_data.append([
Paragraph(f'<b>{defect}</b>', S['cell_crit']),
Paragraph(action, S['cell_bold']),
Paragraph(measures, S['cell']),
Paragraph(owner, S['cell_bold']),
Paragraph(date, S['cell_bold']),
])
ca_t = Table(action_data, colWidths=[1.5*cm, 2.2*cm, 8.3*cm, 2.5*cm, 3.0*cm])
ca_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), RED_CRIT),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'STHeiti'),
('ROWBACKGROUNDS', (0,1), (-1,-1), [BG_CRIT, WHITE]),
('GRID', (0,0), (-1,-1), 0.4, GRAY_LINE),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(ca_t)
story.append(Spacer(1, 0.35*cm))
# ── 4.2 重大缺陷行动项 ────────────────────────────────────────────────────
story.append(Paragraph('4.2 重大缺陷整改行动项', S['subsection']))
maj_actions = [
('M-01\nM-02', 'A-04\n重建文件\n管理体系',
'① 全面梳理现有文件,废止不可执行的模板,重新编写贴合公司实际的 SOP\n'
'② 建立文件发放、接收、销毁的纸质/电子台账,保留完整签字痕迹\n'
'③ 统一文件格式、纸张规格,消除低级错误\n'
'④ 新 QA 负责人牵头,每季度开展文件适用性审查',
'新 QA 负责人\n毕海', '2026-06-30'),
('M-03', 'A-05\n建立偏差/\nOOS/OOT\n管理程序',
'① 编写《偏差管理规程》《OOS 调查程序》《OOT 趋势分析程序》\n'
'② 对历史生产数据进行回顾,识别并补充登记已发生的偏差/OOS\n'
'③ 对 QA/QC 人员开展专项培训',
'新 QA 负责人', '2026-06-15'),
('M-04', 'A-06\n完善工艺\n验证报告',
'① 补充工艺验证报告中关键工艺参数(KPP)、收率、内毒素去除效率数据\n'
'② 制定固定纯化标准(收峰标准),与工艺规程保持一致\n'
'③ 执行至少 3 批次连续工艺验证,确保批次间一致性',
'任宏杰(研发)\n生产部\n新 QA 负责人', '2026-08-31'),
('M-05', 'A-07\n启动稳定性\n研究',
'① 制定《原酶稳定性研究方案》:检测项目简化为酶活性 + 内毒素,避免过度冗余\n'
'② 参照 ICH Q1A 条件(2-8℃实时 + 25℃加速),启动长期稳定性研究\n'
'③ 酶与 buffer 分开单独开展稳定性研究\n'
'④ 稳定性数据支持质量标准中的效期和储存条件设定',
'任宏杰(研发)\n新 QA 负责人', '2026-07-31\n(方案批准)\n2026-08-01\n(入样启动)'),
('M-06', 'A-08\n更换 QA 负\n责人,优化\n质量团队',
'① 即日起免除 QA 负责人左胜职务,由毕海临时代管 QA 事务\n'
'② 毕海组织面试不少于 20 名 QA 候选人(林毅/董方辅助推荐)\n'
'③ 优先从具备酶行业知识的研发骨干中培养,要求懂酶 + 懂法规\n'
'④ 确定新 QA 负责人后,明确其职责边界(对接客户、向研发/生产提需求)\n'
'⑤ 加强 QA/QC 人员的持续培训与考核机制',
'毕海(牵头)\n任宏杰(配合)', '面试启动:即日\n2026-06-01\n(到岗目标)'),
('M-07', 'A-09\n补齐关键\n设备',
'① 二袁对接采购:灭菌锅、干燥箱、砝码、设备标识\n'
'② 研发部荧光仪优先调配至生产端用于 RI 活性检测\n'
'③ 所有设备需完成校准/验证后方可投入使用',
'二袁(采购)\n任宏杰(设备调配)', '2026-05-31'),
('M-08', 'A-10\n酶与 buffer\n分离管理',
'① 将酶与 buffer 分开存储、分开建立质量标准\n'
'② 分别开展稳定性研究(纳入 A-07 统筹)\n'
'③ 修订相关 SOP 和批记录模板',
'新 QA 负责人\n生产部', '2026-06-30'),
]
ma_header = [
Paragraph('关联\n缺陷', S['cell_header']),
Paragraph('行动项', S['cell_header']),
Paragraph('具体措施', S['cell_header']),
Paragraph('责任人', S['cell_header']),
Paragraph('预估完成\n日期', S['cell_header']),
]
ma_data = [ma_header]
for defect, action, measures, owner, date in maj_actions:
ma_data.append([
Paragraph(f'<b>{defect}</b>', S['cell_maj']),
Paragraph(action, S['cell_bold']),
Paragraph(measures, S['cell']),
Paragraph(owner, S['cell_bold']),
Paragraph(date, S['cell_bold']),
])
ma_t = Table(ma_data, colWidths=[1.5*cm, 2.2*cm, 8.3*cm, 2.5*cm, 3.0*cm])
ma_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), ORANGE_MAJ),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'STHeiti'),
('ROWBACKGROUNDS', (0,1), (-1,-1), [BG_MAJ, WHITE]),
('GRID', (0,0), (-1,-1), 0.4, GRAY_LINE),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(ma_t)
story.append(Spacer(1, 0.35*cm))
# ── 4.3 一般缺陷行动项 ────────────────────────────────────────────────────
story.append(Paragraph('4.3 一般缺陷整改行动项', S['subsection']))
min_actions = [
('N-01', 'A-11\n现场卫生\n整改',
'① 即日安排人员清理下水管道锈蚀,必要时更换管道\n② 建立日常现场卫生检查制度(每日巡检记录)',
'生产部\n毕海(督导)', '2026-05-15'),
('N-02', 'A-12\n消除文件\n低级错误',
'① 系统排查现有文件中的替换页、纸张颜色不一致问题,统一重印\n② 制定文件印制和替换标准操作程序',
'新 QA 负责人', '2026-06-30'),
('N-03', 'A-13\n完善批记\n录数据项',
'① 修订批记录模板,新增计算过程记录栏\n② 对现有操作人员开展批记录填写规范培训',
'新 QA 负责人\n生产部', '2026-06-30'),
]
mi_header = [
Paragraph('关联\n缺陷', S['cell_header']),
Paragraph('行动项', S['cell_header']),
Paragraph('具体措施', S['cell_header']),
Paragraph('责任人', S['cell_header']),
Paragraph('预估完成\n日期', S['cell_header']),
]
mi_data = [mi_header]
for defect, action, measures, owner, date in min_actions:
mi_data.append([
Paragraph(f'<b>{defect}</b>', S['cell_min']),
Paragraph(action, S['cell_bold']),
Paragraph(measures, S['cell']),
Paragraph(owner, S['cell_bold']),
Paragraph(date, S['cell_bold']),
])
mi_t = Table(mi_data, colWidths=[1.5*cm, 2.2*cm, 8.3*cm, 2.5*cm, 3.0*cm])
mi_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#9B7D11')),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'STHeiti'),
('ROWBACKGROUNDS', (0,1), (-1,-1), [BG_MIN, WHITE]),
('GRID', (0,0), (-1,-1), 0.4, GRAY_LINE),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(mi_t)
story.append(Spacer(1, 0.5*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 五、整改里程碑时间轴汇总
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header('五、整改里程碑时间轴汇总'))
story.append(Spacer(1, 0.2*cm))
timeline_header = [
Paragraph('里程碑节点', S['cell_header']),
Paragraph('主要行动项', S['cell_header']),
Paragraph('责任人', S['cell_header']),
Paragraph('目标日期', S['cell_header']),
]
timeline_data = [timeline_header,
[Paragraph('★ 立即启动\n(≤ 1 周)', S['cell_crit']),
Paragraph('A-01 发布数据诚信禁令;A-08 免除左胜职务、毕海代管QA;A-09 研发设备调配至生产;A-11 现场清洁启动', S['cell']),
Paragraph('毕海、任宏杰、二袁', S['cell_bold']),
Paragraph('2026-04-22', S['cell_bold'])],
[Paragraph('第一阶段\n1个月内)', S['cell_crit']),
Paragraph('A-01 数据诚信制度落地;A-08 启动QA招聘(面试≥20人);A-09 设备采购启动;A-11 现场卫生完成', S['cell']),
Paragraph('毕海(主)\n二袁(采购)', S['cell_bold']),
Paragraph('2026-05-15', S['cell_bold'])],
[Paragraph('第二阶段\n2个月内)', S['cell_maj']),
Paragraph('A-02 RI临时检测方案上线;A-05 偏差/OOS/OOT程序建立;A-08 新QA负责人到岗;A-03 清洁验证主计划发布', S['cell']),
Paragraph('新QA负责人\n任宏杰', S['cell_bold']),
Paragraph('2026-06-15', S['cell_bold'])],
[Paragraph('第三阶段\n3个月内)', S['cell_maj']),
Paragraph('A-04 文件体系重建完成;A-06 工艺验证报告补充完成;A-07 稳定性研究入样启动;A-10 酶/buffer分离管理完成;A-12/13 批记录规范化', S['cell']),
Paragraph('新QA负责人\n任宏杰\n生产部', S['cell_bold']),
Paragraph('2026-06-30', S['cell_bold'])],
[Paragraph('第四阶段\n4~5个月内)', S['cell_maj']),
Paragraph('A-02 RI活性正式纳入质量标准并完成方法验证;A-06 3批次工艺验证完成;A-09 正式设备到位并完成验证', S['cell']),
Paragraph('任宏杰\n新QA负责人', S['cell_bold']),
Paragraph('2026-07-31', S['cell_bold'])],
[Paragraph('第五阶段\n(≤6个月)', S['cell_bold']),
Paragraph('A-03 清洁验证关键品种完成;A-02 RI活性检测体系完全建立;准备接受申基/客户复查审计', S['cell']),
Paragraph('毕海\n新QA负责人\n全员', S['cell_bold']),
Paragraph('2026-08-31', S['cell_bold'])],
]
tl_t = Table(timeline_data, colWidths=[2.8*cm, 9.2*cm, 2.8*cm, 2.7*cm])
tl_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'STHeiti'),
('ROWBACKGROUNDS', (0,1), (-1,-1), [BG_CRIT, BG_CRIT, BG_MAJ, BG_MAJ, BG_MAJ, LIGHT_BLUE]),
('GRID', (0,0), (-1,-1), 0.4, GRAY_LINE),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('RIGHTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(tl_t)
story.append(Spacer(1, 0.5*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 六、决议与签字
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header('六、会议决议与签署'))
story.append(Spacer(1, 0.2*cm))
decisions = [
'本次会议就申基供应商审计发现的全部问题达成整改共识,与会人员承诺按照上述行动计划推进落实。',
'4000 万元订单执行不受本次审计直接影响,各方共担下游药企提考连带责任,应以主人翁心态推进整改。',
'整改优先级排序:RI 活性检测体系建立 → 数据诚信管理 → 清洁验证 → QA 人员优化 → 文件体系重建 → 稳定性研究。',
'所有整改措施的完成情况须由新任 QA 负责人进行验证和记录,整改完成后须准备向申基/客户提交整改报告并接受复查审计。',
'毕海为本次整改的首要协调人,任宏杰负责研发端支持,二袁负责客户沟通与采购事务。',
]
for i, d in enumerate(decisions, 1):
story.append(Paragraph(f'{i}. {d}', S['body']))
story.append(Spacer(1, 0.5*cm))
sign_data = [
[Paragraph('主持人', S['cell_bold']),
Paragraph('任宏杰    签名:_______________', S['cell']),
Paragraph('日期:_____________', S['cell'])],
[Paragraph('质量代表', S['cell_bold']),
Paragraph('毕海     签名:_______________', S['cell']),
Paragraph('日期:_____________', S['cell'])],
[Paragraph('商务/对外', S['cell_bold']),
Paragraph('二袁     签名:_______________', S['cell']),
Paragraph('日期:_____________', S['cell'])],
[Paragraph('参会确认', S['cell_bold']),
Paragraph('说话人 C   签名:_______________', S['cell']),
Paragraph('日期:_____________', S['cell'])],
[Paragraph('参会确认', S['cell_bold']),
Paragraph('说话人 E   签名:_______________', S['cell']),
Paragraph('日期:_____________', S['cell'])],
]
sign_t = Table(sign_data, colWidths=[2.8*cm, 9*cm, 5.7*cm])
sign_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (0,-1), BG_HEAD),
('GRID', (0,0), (-1,-1), 0.4, GRAY_LINE),
('TOPPADDING', (0,0), (-1,-1), 8),
('BOTTOMPADDING', (0,0), (-1,-1), 8),
('LEFTPADDING', (0,0), (-1,-1), 6),
('RIGHTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
story.append(sign_t)
story.append(Spacer(1, 0.4*cm))
# ── 页脚说明 ──────────────────────────────────────────────────────────────
story.append(HRFlowable(width=W, thickness=0.5, color=GRAY_LINE))
story.append(Spacer(1, 0.1*cm))
story.append(Paragraph(
'本文件为愚公生物科技有限公司(百时美)内部保密文件,未经授权不得对外披露。\n'
'文件编号:YG-QA-MOM-2026-001 版本:V1.0 生效日期:2026-04-15 制定部门:质量管理部',
S['footer']))
doc.build(story)
print(f'PDF generated: {output_path}')
if __name__ == '__main__':
output = '/Users/tankai/Documents/Projects/deep_research/愚公生物_申基审计整改会议纪要_2026-04-10.pdf'
build_pdf(output)