snapshot before v0.5 refactor

This commit is contained in:
kai
2026-04-21 12:31:58 +08:00
commit 4a38f6bed1
82 changed files with 15230 additions and 0 deletions
+763
View File
@@ -0,0 +1,763 @@
#!/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)