内容导读 疑点数据很多,能自动生成报告吗?
python-docx 库的介绍
“伪现金”交易批量核查单生成实例
本文只代表个人观点,不代表所在单位意见。
引子 很多审计同仁对数字化审计的梦想就是:点一个功能按钮,后台数据跑一下,分析报告就自动跑出来了 。
梦想还是要有的,万一实现了呢!
类似的问题被问过多次,一直想整理出来,拖更太久了!非常抱歉!
在数字化审计过程中,虽然直接将数据分析结果直接作为问题写成工作底稿不太可能,但将分析出来的疑点根据特定的维度(比如,分公司)分发下去核实,是经常会遇到的场景。
通常的流程就是:思路梳理 -> 数据分析 -> 结果整理 -> 撰写核查单。
在需要分发的核查单较多的情况下,“结果整理 -> 撰写核查单”会耗费大量的时间,进行很多典型的低价值密度工作 。
如何自动批量生成核查单呢?
python 的 python-docx 库可以生成docx文档,插入文字、表格、图片等文档要素,可以通过 python-docx 实现核查单批量生成。
python-docx python-docx 可以创建、修改、编辑 docx 格式(不支持 doc 格式)的 word 文档,提供提供全套的 Word 操作。
python-docx 有三个基本的对象:
Document:docx 文档对象,打开或创建文件后建立。 Paragraph:文档中的段落,每个docx文档都是有多个段落组成的,通过document.add_paragraph()生成。 Run:段落中的节段,每个段落由多个节段组成。这是python-docx对文档进行各种操作的直接对象,通过Paragraph对象的 add_run() 生成。 对象之间的关系如下图:
(图源:python-docx documentation)
python-docx 库可以通过 pip install python-docx 安装,但需要注意的是: python-docx 在python引入时的库名是 docx 而不是安装时的 python-docx 。
python-docx 的官方文档地址:https://python-docx./en/latest/index.html
案例背景 在反洗钱领域,存在一种“伪现金”交易,通过该方式可以通过现金过桥的方式,割裂大额现金转账交易,改变交易性质。
《“伪现金”交易的洗钱风险及其防范》[1] 一文中的定义:
“伪现金”交易指未发生真实现金存取,而是通过利用现金业务方式和渠道提供资金划转和转账服务,实现资金在不同账户间流动的交易。该类交易人为割裂交易链条,改变交易性质,造成大额交易错报和漏报,影响可疑交易的分析和甄别,存在洗钱风险。
但由于客户个人偏好、经营需要等,客户的“伪现金”交易并非一定是洗钱行为,对此类数据进行分析后,往往需要下发核查单由交易机构进行核实。
本案例通过对根据“伪现金”交易提取的交易流水分析后,挖掘高风险的交易网络,根据不同机构生成“现金过账核查单”,核查单包括网络的节点、关键节点,以及可视化的关系网络,方便核查机构快速定位问题和关键点。
演示用的疑点清单中,每条记录包括机构号、交易日期、前一笔现金取款的客户号、交易金额、后一笔现金存款的客户号、交易金额等字段。(测试数据随机生成,不表示真实的交易情况!)
生成“现金过账核查单”如下图示意。
实现代码 本文运行环境:Python 3.8 / Pandas 1.1.3 / networkx 2.5
1.初始化库 import matplotlib.pyplot as pltimport networkx as nximport pandas as pd# 编辑docx文件需要的各种库 from docx import Documentfrom docx.enum.text import WD_ALIGN_PARAGRAPHfrom docx.enum.style import WD_STYLE_TYPEfrom docx.shared import Ptfrom docx.oxml.ns import qnfrom docx.shared import RGBColor
2.通用功能封装成函数 #定义函数 #设置中文字体函数 def setRun (run,size=9 ) : run.font.name='微软雅黑' run.font.size = Pt(size) run._element.rPr.rFonts.set(qn('w:eastAsia' ), '微软雅黑' )#根据子图生成关系网络图函数 def genImg (subg) : plt.clf() node_size = [subg.degree(i)*150 for i in subg.nodes()] node_colors = [subg.degree(i) for i in subg.nodes()] g_options = { 'pos' : nx.spring_layout(subg), 'node_size' : node_size, 'node_color' : node_colors, 'cmap' : plt.cm.hsv, 'edge_color' : 'gray' , 'with_labels' : True , 'node_shape' : 'o' , } nx.draw(subg, **g_options) plt.savefig('tmp.jpg' )
3.疑点数据自动生成报告 #主程序开始 if __name__=='__main__' : #读取机构的交易数据 并将数字格式的字符型字段指定为字符 ttdata = pd.read_excel('现金过账交易清单(测试).xlsx' , sheet_name='疑点清单' , converters={'交易日期' : str,'取款客户号' : str,'存款客户号' : str} ) #对同一对客户的交易明细进行汇总 生成关系数据 ttdata_grp = ttdata.groupby(['交易机构名称' ,'取款客户号' ,'存款客户号' ],as_index=False ).size() ttdata_grp = ttdata_grp.rename(columns={'size' :u'笔数' }) #建立一个无向图(最大连通子图函数只支持无向图) G = nx.Graph() #关系数据添加到图中 有更简单的函数 这样写更易理解 for i in range(len(ttdata_grp)): item = ttdata_grp.iloc[i,:] G.add_edge(item['取款客户号' ],item['存款客户号' ],weight = item['笔数' ]) #当前机构名称 brname = ttdata_grp.iloc[0 ,0 ] #生成空白docx文档 document = Document() #设置文档的属性 document.core_properties.author = 'SmartAudit' document.core_properties.comments = 'Confidential' #定义样式 ##定义标题样式 MainTitleStyle = document.styles.add_style('MainTitle' , WD_STYLE_TYPE.PARAGRAPH) MainTitleStyle.font.name = 'SimSun' MainTitleStyle.font.size = Pt(22 ) ContentStyle = document.styles.add_style('Content' , WD_STYLE_TYPE.PARAGRAPH) ContentStyle.font.name = u'宋体' ContentStyle.font.size = Pt(9 ) #添加标题 p = document.add_paragraph(u'' ,style=MainTitleStyle) p.alignment = WD_ALIGN_PARAGRAPH.CENTER #设置中文字体 run = p.add_run(u'现金过账核查单(编号001)' ) setRun(run,18 ) #添加机构的基本情况 h = document.add_heading(u'' ,level=1 ) run = h.add_run(brname) setRun(run,14 ) #建立一个空表格 table = document.add_table(rows=1 , cols=1 ) table.style = 'TableGrid' curCell = table.rows[0 ].cells[0 ] sText = u'全图涉及客户数:%d 客户往来关系数:%d' %(G.number_of_nodes(),G.number_of_edges()) curCell.paragraphs[0 ].style = ContentStyle run = curCell.paragraphs[0 ].add_run(sText) setRun(run,9 ) #提取所有的连通子图 sub_graphs = list(nx.connected_components(G)) #只处理客户数大于5的子图 #有多个就在表格中生成多行 for index,subgnodes in enumerate(sub_graphs): if len(subgnodes)>5 : #根据子图的客户节点列表 从全图中提取出无向图 subg = G.subgraph(subgnodes) rows = table.add_row() rows.height_rule = 0 cells = rows.cells nodestr = ', ' .join(subg.nodes()) #节点重要性排序 根据客户中心度找出网络中的关键客户 nodes = nx.degree_centrality(subg) nodeslist = sorted(nodes.items(), key=lambda x: x[1 ], reverse=True )[0 :4 ] #列出排序前4的客户及其中心度 keynodestr = '\r关键客户(中心度Degree Centrality):' for node in nodeslist: keynodestr = keynodestr + '\r%s (%f)' %(node[0 ],node[1 ]) #拼接成段落文本内容 run = cells[0 ].paragraphs[0 ].add_run('重点集群索引:%d 包含客户数:%d\r%s\r%s' %(index,subg.number_of_nodes(),nodestr,keynodestr)) setRun(run,8 ) #根据当前子图生成关系网络图片 genImg(subg) #将图片插入docx文档当前单元格 cells[0 ].paragraphs[0 ].add_run().add_picture('tmp.jpg' ) #核查要求 红字显示 rows = table.add_row() run = rows.cells[0 ].paragraphs[0 ].add_run('核查要求:\r请在2022年X月X日前返回核查结果!' ) run.font.color.rgb = RGBColor (255 ,0 ,0 ) setRun(run,18 ) #保存docx文件 localfilename = '现金过账核查单(%s).docx' %(brname) document.save(localfilename)
参考文献
“伪现金”交易的洗钱风险及其防范,[E/OL],https://finance.sina.com.cn/money/fund/jjzl/2021-11-11/doc-iktzqtyu6681354.shtml python-docx 0.8.11 documentation,[E/OL],https://python-docx./en/latest/index.html