飞道的博客

电网知识图谱项目总结(1)python代码实现RDF三元组自动化标注

484人阅读  评论(0)

电网知识图谱项目总结(1)python代码实现RDF三元组自动化标注

简介

本次项目是电网知识图谱相关的,我们的主要任务是三元组的标注知识图谱的构建,本篇讲述Python代码标注三元组的具体实现,相关知识图谱的构建在后期的内容中再进行总结。

总共有几百个文档(包括几百个预案),刚开始时手动标注一个文档需要两三个小时,长一点的文档得三个多小时,加上三元组关系之多,标注一两个文档头脑一片混乱,盯都盯不住了,心态直接炸裂!在手动标注了一个文档后,我尝试用代码标注,写代码的过程也是特别煎熬,毕竟每一种类型的三元组都得用代码实现,不过写多了就觉得简单了,都是一样的套路。花了一天半时间后,写完了近500行代码,若是足够规范的文档,可保证100%的标注准确率,大部分文档的标注准确率也达到了90%以上。在写代码时为某些不好处理的三元组特意做了模糊标注,所以代码标注后的三元组只需要对照文档内容核对检查修改即可,特别方便,几乎不需要新增。原来一个文档两三个小时的任务最后只需几分钟即可完成,极大地提高了效率!

文档内容

最开始的数据格式是.docx文档,每个word文档中包含了针对特定千伏变电站停电时的具体调度,下面以一个预案的具体结构和内容来作为示例。

侯家塘变35千伏其侯线停电风险预案为例,具体内容组织结构如下


  • 侯家塘变35千伏其侯线停电风险预案

    • 一、事故情况及影响

      • 得地调告:侯家塘变全所失电。
      • 侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。
      • 两回全停重要用户:江苏省金陵监狱
      • 一回停电的双电源用户:南京自来水公司汤山增压站
    • 二、处理过程

      1. 得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源

      2. 配调:

        • (1)恢复所用电

          侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关

          汤山变:侯家塘线233开关定切保护停用过流时间由1”改为1.5”

          侯家塘变:侯家塘线135开关保护全部停用合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)

          悦民变:百镇#1线118开关定切保护停用过流时间由1”改为1.5”

          雨花急修班:合上#6260开关

          侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)

        • 恢复重要用户供电

          侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)

      3. 汇报地调。

      4. 得地调通知,侯家塘变1号、2号主变已恢复供电,事故方式自行恢复。


其中标黄的内容为每个文档中的固定大纲,部分内容(如变电站等)可能不同,但模式相同,标红的是一些需要标注的部分三元组关键字。

文档内容截图如下

RDF规范

RDF三元组规范如下,共有二十多种类型,要求是将这些三元组标注出并保存在Excel表格中。

侯家塘变35千伏其侯线停电风险预案为例,该预案中包含的所有三元组如下:

35千伏侯家塘变全停事故预案  事故情况    得地调告:侯家塘变全所失电。
35千伏侯家塘变全停事故预案  事故原因    侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。
35千伏侯家塘变全停事故预案  事故影响    两回全停的重要用户有:江苏省金陵监狱
35千伏侯家塘变全停事故预案  事故影响    一回停电的双电源用户有:南京自来水公司汤山增压站
35千伏侯家塘变全停事故预案  处理过程    得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源


得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源  下一步骤    侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关
侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关    下一步骤    汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”
汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”   下一步骤    侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)     下一步骤    悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”
悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”  下一步骤    雨花急修班:合上#6260开关
雨花急修班:合上#6260开关   下一步骤    侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)    下一步骤    侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)
侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)     下一步骤    汇报地调
回报地调    下一步骤    得地调通知,侯家塘变1号、2号主变已恢复供电,事故方式自行恢复。


侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变1号接地变
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变2号接地变
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变1号所用电源
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变2号所用电源


得地调告:侯家塘变全所失电。    两回全停    江苏省金陵监狱
江苏省金陵监狱    属性    重要用户
得地调告:侯家塘变全所失电。    一回停电    南京自来水公司汤山增压站
南京自来水公司汤山增压站    属性    双电源用户
南京自来水公司汤山增压站    属性    重要用户


得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源    移出    侯家塘变10千伏负荷
得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源    保证    悦民变所用变电源


侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关    属性    恢复所供电
汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”    属性    恢复所供电
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)    属性    恢复所供电
悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”    属性    恢复所供电
雨花急修班:合上#6260开关    属性    恢复所供电
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)     属性    恢复所供电


侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关    恢复所供电    侯家塘变
汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”    恢复所供电    侯家塘变
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)    恢复所供电    侯家塘变
悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”    恢复所供电    侯家塘变
雨花急修班:合上#6260开关    恢复所供电    侯家塘变
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)     恢复所供电    侯家塘变


侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)    操作分支    侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)    操作分支    汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关    等价操作    汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变

  
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)    操作分支    侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)    操作分支    悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关    等价操作    悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电


侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关    拉开    侯家塘变1号主变101开关
侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关    拉开    侯家塘变2号主变102开关
侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关    拉开    侯家塘变10千伏出线开关
汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”   定切保护停用    汤山变侯家塘线233开关
汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”   过流时间由1”改为1.5”    汤山变侯家塘线233开关
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)     保护全部停用    侯家塘变侯家塘线135开关
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)     合上    侯家塘变侯家塘线135开关
悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”   定切保护停用    悦民变百镇#1线118开关
悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”   过流时间由1”改为1.5”    悦民变百镇#1线118开关
雨花急修班:合上#6260开关   合上    #6260开关
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)    保护全部停用    侯家塘变官塘线235开关
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)    合上    侯家塘变官塘线235开关


侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)     合上    龙尚线134开关
侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)    属性    恢复重要用户供电
侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)     恢复重要用户供电    江苏省金陵监狱
侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)     恢复重要用户供电    南京自来水公司汤山增压站供电


侯家塘变1号主变101开关    属于    侯家塘变
侯家塘变2号主变102开关    属于    侯家塘变
汤山变侯家塘线233开关     属于    汤山变
侯家塘变侯家塘线135开关    属于    侯家塘变
侯家塘变10千伏Ⅰ段母线    属于    侯家塘变
侯家塘变1号接地变    属于    侯家塘变
悦民变百镇#1线118开关    属于    悦民变
侯家塘变官塘线235开关    属于    侯家塘变
侯家塘变龙尚线134开关    属于    侯家塘变
雨花急修班#6260开关    属于    雨花急修班

 

标注思路

总体来说整个过程可分为三步:

  1. 读取docx文件(import docx
  2. 标注(对str字符的处理)
  3. 写入xlsx文件(import openpyxl

在读取内容后,为了处理字符串的方便,将每段内容句首和句末的空格、制表符、标点符号等去除。

其中最麻烦的是标注代码的编写,本质就是对字符串的一些处理技巧,这个只要掌握了字符串的分割和特定字符的位置查找其实也不难实现。

在三元组分类时要考虑以下两个问题:

  • 哪几种类型的三元组放在一起处理比较好?
  • 哪些内容段包含哪几种类型的三元组?

首先根据三元组的分布对内容进行大致划分,得到固定标题下的内容,这样方便后面特定三元组的处理,缩小查找范围。如layer1layer2层下的内容,datas是所有内容,datas1二、处理过程:下的内容。在处理特定三元组的时候,看它出现在哪些特定的内容中,或者某几条内容中包含几种类型的三元组。根据大致位置和三元组关键词找到包含三元组内容的具体位置,然后根据关键字、标点符号、以及三元组特征对内容进行拆分、拼接和添加,从而得到最后的结果。

下面举两个例子方便大家更好地了解:


内容一:

一、事故情况及影响
得地调告:侯家塘变全所失电
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电
两回全停重要用户有:江苏省金陵监狱。
一回停电的双电源用户有:南京自来水公司汤山增压站。

对于上面这段内容,由于它是连续的,先不考虑每个句子中包含的其他三元组,我们发现事故情况事故原因事故影响这几个三元组放在一起处理比较好,在得到标题后依次写入即可。对应代码中的函数为def step_1(datas, name, layer1, layer2)

35千伏侯家塘变全停事故预案  事故情况    得地调告:侯家塘变全所失电。
35千伏侯家塘变全停事故预案  事故原因    侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。
35千伏侯家塘变全停事故预案  事故影响    两回全停的重要用户有:江苏省金陵监狱
35千伏侯家塘变全停事故预案  事故影响    一回停电的双电源用户有:南京自来水公司汤山增压站

内容二:

侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。

首先是事故原因三元组,这个只要得到标题就可以构成三元组。(这一条我是按照内容一中程相关的三元组一起处理的)

35千伏侯家塘变全停事故预案	事故原因	侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。

其次是失电(主)失电(被),首先定位接地变对前段话中**“1号”“2号”进行检测根据顿号拆分,得到“失电(主)“三元组,之后定位所用电源对后半句话同样进行字符定位拆分,得到”失电(被)“**三元组。对应代码中的函数为def step_3(datas, layer1)

侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变1号接地变
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变2号接地变
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变1号所用电源
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。    失电()    侯家塘变2号所用电源

代码结构

其他的三元组处理方式与此类似,下面是整个代码的函数结构图,详解介绍了各个函数的功能和所要处理的三元组。

详细代码

import docx
import xlwt
import openpyxl
import os

switches_rdf = []

def readData(path):
    file = docx.Document(path)
    datas = []
    for p in file.paragraphs:
        cur = str(p.text.strip())
        cur.replace('\n', '').replace('\r', '').replace(' ', '')
        if cur != "":
            datas.append(cur)
    return datas

def printRDF(rdfs):
    if rdfs is None:
        return
    for rdf in rdfs:
        print(rdf)

# RDF 三元组使用 tuple 存储  (R, D, F)
# step_1  事故情况、事故原因、事故影响、处理过程
def step_1(datas, name, layer1, layer2):
    rdf1 = []
    for i in range(layer1 + 1, layer2):
        data = datas[i]
        if data.count("得地调告"):
            rdf1.append((name, "事故情况", data))
        elif data.count("导致"):
            rdf1.append((name, "事故原因", data))
        elif data.count("用户"):
            if data[-1]  != "无":
                rdf1.append((name, "事故影响", data))
    rdf1.append((name, "处理过程", datas[layer2 + 1][2:]))
    return rdf1


# setp_2  (?, 下一步骤, ?)
def step_2(datas, layer2):
    cycle_data = []
    for i in range(layer2 + 1, len(datas)):
        data = datas[i]
        if data.count("1、得地调要求:"):
            cycle_data.append(data[2:])
        elif data.count("2、配调:") or data.count("(1)恢复所用电") or data.count("(2)恢复重要用户供电"):
            pass
        elif data.count("3、汇报地调"):
            cycle_data.append(data[2:])
        elif data.count("4、得地调通知"):
            cycle_data.append(data[2:])
        else:
            cycle_data.append(data)
    # 使用循环加入三元组
    rdf2 = []
    for i in range(0, len(cycle_data) - 1):
        rdf2.append((cycle_data[i], "下一步骤", cycle_data[i + 1]))
    return rdf2


# step3  失电(主),失电(被)
def step_3(datas, layer1):
    data = datas[layer1 + 2]
    head_id = data.find("1号")
    head_name = data[:head_id]
    # 使用 , 划分前后部分
    data_sp = data.split(',')
    rdf3 = []
    for i in range(1, 5):
        if data_sp[0].count(str(i) + "号"):
            rdf3.append((data, "失电(主)", head_name + str(i) + "号接地变"))
            # add switches_rdf
            switches_rdf.append((head_name + str(i) + "号接地变", "属于", head_name))
    for i in range(1, 5):
        if data_sp[1].count(str(i) + "号"):
            rdf3.append((data, "失电(被)", head_name + str(i) + "号所用电源"))
    return rdf3


# step_4  两回全停、一回停电, 重要度,属性
def step_4(datas, layer1, layer2):
    s1 = datas[layer1 + 1]
    fst_s, sec_s = "", ""
    for i in range(layer1 + 1, layer2):
        if datas[i].find("两回全停") == 0:
            fst_s = datas[i]
        elif datas[i].find("一回停电") == 0:
            sec_s = datas[i]
    # 找出两回全停的地点
    fst_s1 = fst_s.split(":")
    fst_s_list = fst_s1[1].split("、")
    # 找出一回停电的地点
    sec_s1 = sec_s.split(":")
    sec_s_list = sec_s1[1].split("、")

    rdf4 = []
    # 加入 两回全停、一回停电 三元组
    for s in fst_s_list:
        if s != "无":
            rdf4.append((s1, "两回全停", s))
    for s in sec_s_list:
        if s != "无":
            rdf4.append((s1, "一回停电", s))

    # 处理属性问题 (双电源、 重要用户)
    if fst_s1[0].count("重要"):
        for s in fst_s_list:
            if s != "无":
                rdf4.append((s, "属性", "重要用户"))
    if sec_s1[0].count("重要"):
        for s in sec_s_list:
            if s != "无":
                rdf4.append((s, "属性", "重要用户"))
    if fst_s1[0].count("双电源"):
        for s in fst_s_list:
            if s != "无":
                rdf4.append((s, "属性", "双电源用户"))
    if sec_s1[0].count("双电源"):
        for s in sec_s_list:
            if s != "无":
                rdf4.append((s, "属性", "双电源用户"))

    return rdf4


# step_5 (?, 移出, ?)  , (?, 保证, ?)
def step_5(datas, layer2):
    data = datas[layer2 + 1][2:]
    parts = data[data.find(":") + 1:].split(",")
    id1 = parts[0].find("移出")
    id2 = parts[1].find("保证")
    rdf5 = []
    rdf5.append((data, "移出", parts[0][:id1]))
    rdf5.append((data, "保证", parts[1][id2 + 2:]))
    return rdf5


# step_6 (?, 属性, 恢复所供电)  (?, 恢复所供电, ?)
def step_6(datas, layer1, layer2):
    id1, id2 = 0, 0
    for i in range(layer2 + 1, len(datas)):
        data = datas[i]
        if data.count("(1)恢复所用电"):
            id1 = i
        elif data.count("(2)恢复重要用户供电"):
            id2 = i
            break
        else:
            pass
    # 得到 (1)恢复所用电 数据
    cycle_data = []
    for i in range(id1 + 1, id2):
        cycle_data.append(datas[i])

    head_name = datas[layer1 + 1].split(":")[1]
    head_name = head_name[:head_name.find("全所失电")]

    rdf6 = []
    # 添加  (?, 属性, 恢复所供电) 三元组
    for s in cycle_data:
        rdf6.append((s, "属性", "恢复所供电"))
    # 添加 (?, 恢复所供电, ?)
    for s in cycle_data:
        rdf6.append((s, "恢复所供电", head_name))

    return rdf6


# step_7 (?, 操作分支, ?)  (?, 等价操作 ,?)
def step_7(datas, layer2):
    id1, id2 = 0, 0
    for i in range(layer2 + 1, len(datas)):
        data = datas[i]
        if data.count("(1)恢复所用电"):
            id1 = i
        elif data.count("(2)恢复重要用户供电"):
            id2 = i
            break
        else:
            pass
    # 得到 含有操作分支的数据
    cycle_data = []
    for i in range(id1 + 1, id2):
        if datas[i][-1] == ")":
            cycle_data.append(datas[i])
    # 添加 操作分支 和 等价操作 三元组
    rdf7 = []
    for s in cycle_data:
        cur_s = s.replace(")", '')
        s_list = cur_s.split("(")
        rdf7.append((s, "操作分支", s_list[0]))
        rdf7.append((s, "操作分支", s_list[1]))
        rdf7.append((s_list[0], "等价操作", s_list[1]))
    return rdf7


# step_8 (?, 属性, 恢复重要用户供电)  (?, 恢复重要用户供电 ,?)
def step_8(datas, layer2):
    id1, id2 = 0, 0
    for i in range(layer2 + 1, len(datas)):
        data = datas[i]
        if data.count("(2)恢复重要用户供电"):
            id1 = i
        elif data.count("3、汇报地调"):
            id2 = i
            break
        else:
            pass
    # 得到 恢复重要用户供电 的数据
    cycle_data = []
    for i in range(id1 + 1, id2):
        cycle_data.append(datas[i])

    rdf8 = []
    # 添加 (?, 属性, 恢复重要用户供电) 三元组
    for s in cycle_data:
        rdf8.append((s, "属性", "恢复重要用户供电"))
    # 添加恢复用户供电的三元组   ?, 恢复重要用户供电 ,?)
    for s in cycle_data:
        if s.count("恢复"):
            id = s.find("恢复")
            cur_s = s[id + 2:]
            cur_s = cur_s.replace(")", '').replace("供电", "")
            cur_s_list = cur_s.split("、")
            for st in cur_s_list:
                rdf8.append((s, "恢复重要用户供电", st))
    return rdf8


# 得到 (1)恢复所用电 下的数据
def get_datas1(datas, layer2):
    id1, id2 = 0, 0
    for i in range(layer2 + 1, len(datas)):
        data = datas[i]
        if data.count("(1)恢复所用电"):
            id1 = i
        elif data.count("(2)恢复重要用户供电"):
            id2 = i
            break
        else:
            pass
    datas1 = []
    for i in range(id1 + 1, id2):
        datas1.append(datas[i])
    return datas1

# 得到 (2)恢复重要用户供电 下的数据
def get_datas2(datas, layer2):
    id1, id2 = 0, 0
    for i in range(layer2 + 1, len(datas)):
        data = datas[i]
        if data.count("(2)恢复重要用户供电"):
            id1 = i
        elif data.count("3、汇报地调"):
            id2 = i
            break
        else:
            pass
    datas2 = []
    for i in range(id1 + 1, id2):
        datas2.append(datas[i])
    return datas2

# step_9  (1)恢复所用电  (?, 拉开, ?)
def step_9(datas1):
    if datas1 is None:
        return
    rdf9 = []
    data = datas1[0]
    if data.count("拉开"):
        sp = data.split(":")
        cur_s = sp[1].replace("及所有", "、").replace("拉开", "").replace(",", "、")
        switch_list = cur_s.split("、")
        # print(switch_list)
        # add switches_rdf
        for i in range(0, len(switch_list) - 1):
            switches_rdf.append((sp[0] + switch_list[i], "属于", sp[0]))
            # print(switches_rdf)
        for switch in switch_list:
            rdf9.append((data, "拉开", sp[0] + switch))
    return rdf9

# step_10 (1)恢复所用电  (?, 定切保护停用, ?)  (?, 过流时间由1”改为1.5”  ,?)
def step_10(datas1):
    rdf10 = []
    cycle_data = []
    for data in datas1:
        if data.count("定切保护停用"):
            cycle_data.append(data)
    for d in cycle_data:
        index1 = d.find("定切保护停用")
        index2 = d.find(":")
        head_name = d[:index2]
        switch_name = d[:index1].replace(":", "")
        # add switches_rdf
        switches_rdf.append((switch_name, "属于", head_name))
        rdf10.append((d, "定切保护停用", switch_name))
        rdf10.append((d, "过流时间由1”改为1.5”", switch_name))
    return rdf10


# step11 (1)恢复所用电     (?, 保护全部停用 / 合上,)
def step_11(datas1):
    rdf11 = []
    cycle_data = []
    for data in datas1:
        if data.count("保护全部停用"):
            cycle_data.append(data)
    for d in cycle_data:
        index1 = d.find("保护全部停用")
        index2 = d.find(":")
        switch_name = d[:index1]
        head_name = d[:index2]
        switch_name = switch_name.replace(":", "")
        # add switches_rdf
        switches_rdf.append((switch_name, "属于", head_name))
        rdf11.append((d, "保护全部停用", switch_name))
        rdf11.append((d, "合上", switch_name))
    return rdf11

# step12  (1)恢复所用电 (2)恢复重要用户供电 直接的 合上 三元组 (?, 合上, ?)
def step_12(datas, layer1):
    cycle_data = []
    for data in datas:
        if data.count("保护全部停用") == 0 and data.count("合上"):
            cycle_data.append(data)
    rdf12 = []
    for d in cycle_data:
        index1 = d.find("开关")
        index2 = d.find(":")
        head_name = d[:index2]
        switch_name = d[:index1 + 2]
        switch_name = switch_name.replace(":合上", "")
        # add switches_rdf
        switches_rdf.append((switch_name, "属于", head_name))
        rdf12.append((d, "合上", switch_name))
    return rdf12


# step13  10KV 1/2/3段母线 (?母线, 属于, ?)
def step_13(datas1):
    head_name = datas1[0].split(":")[0]
    rdf13 = []
    s_nums1 = ["Ⅰ", "Ⅱ", "Ⅲ", "Ⅳ", "Ⅴ"]
    s_nums2 = ["I", "II", "Ⅲ", "Ⅳ", "Ⅴ"]
    nums_spot = [True for i in range(5)]
    for data in datas1:
        if data.count("段母线"):
            for i in range(0, len(s_nums1)):
                if data.count(s_nums1[i]) or data.count(s_nums2[i]):
                    if nums_spot[i] is False:
                        continue
                    nums_spot[i] = False
                    s1 = head_name + "10kV" + s_nums1[i] + "段母线"
                    rdf13.append((s1, "属于", head_name))
    return rdf13



def write_to_excel1(all_rdfs, name):
    savepath = name +  ".xls"
    workbook = xlwt.Workbook()
    worksheet = workbook.add_sheet('savepath')
    for i in range(0, len(all_rdfs)):
        cur = all_rdfs[i]
        worksheet.write(i, 0, cur[0])
        worksheet.write(i, 1, cur[1])
        worksheet.write(i, 2, cur[2])
    workbook.save(savepath)

def write_to_excel(all_rdfs, name):
    savepath = name +  ".xlsx"
    if os.path.exists(savepath):
        os.remove(savepath)
    workbook = openpyxl.Workbook()
    worksheet = workbook.active
    for rdf in all_rdfs:
        worksheet.append(rdf)
    workbook.save(savepath)

if __name__ == '__main__':
    path = "datasets/35千伏侯家塘变全停事故处理预案模板.docx"
    datas = readData(path)
    for i in range(len(datas)):
        # 此处数据 1" 没有读出,故替换
        if datas[i].count('过流时间由改为1.5”'):
            datas[i] = datas[i].replace('过流时间由改为1.5”', '过流时间由1”改为1.5”')
        print(datas[i])

    name = ""
    layer1, layer2 = 0, 0

    for i in range(0, len(datas)):
        if datas[i] == "一、事故情况及影响":
            name = datas[i - 1]
            layer1 = i
        elif datas[i] == "二、处理过程:":
            layer2 = i
            break

    print("------------------------ rdf1 ------------------------")
    rdf1 = step_1(datas, name, layer1, layer2)
    printRDF(rdf1)

    print("------------------------ rdf2 ------------------------")
    rdf2 = step_2(datas, layer2)
    printRDF(rdf2)

    print("------------------------ rdf3 ------------------------")
    rdf3 = step_3(datas, layer1)
    printRDF(rdf3)

    print("------------------------ rdf4 ------------------------")
    rdf4 = step_4(datas, layer1, layer2)
    printRDF(rdf4)

    print("------------------------ rdf5 ------------------------")
    rdf5 = step_5(datas, layer2)
    printRDF(rdf5)

    print("------------------------ rdf6 ------------------------")
    rdf6 = step_6(datas, layer1, layer2)
    printRDF(rdf6)

    print("------------------------ rdf7 ------------------------")
    rdf7 = step_7(datas, layer2)
    printRDF(rdf7)

    print("------------------------ rdf8 ------------------------")
    rdf8 = step_8(datas, layer2)
    printRDF(rdf8)

    print("#####################   datas1  #######################")
    datas1 = get_datas1(datas, layer2)
    for d in datas1:
        print(d)

    print("#####################   datas2  #######################")
    datas2 = get_datas2(datas, layer2)
    for d in datas2:
        print(d)

    print("------------------------ rdf9 ------------------------")
    rdf9 = step_9(datas1)
    printRDF(rdf9)

    print("------------------------ rdf10 ------------------------")
    rdf10 = step_10(datas1)
    printRDF(rdf10)

    print("------------------------ rdf11 ------------------------")
    rdf11 = step_11(datas1)
    printRDF(rdf11)

    print("------------------------ rdf12 ------------------------")
    rdf12 = step_12(datas, layer1)
    printRDF(rdf12)

    print("------------------------ rdf13 ------------------------")
    rdf13 = step_13(datas1)
    printRDF(rdf13)

    print("------------------------ switches_rdf ------------------------")
    printRDF(switches_rdf)

    # 汇总所有的 RDF
    all_rdfs = []
    all_rdfs += rdf1
    all_rdfs += rdf2
    all_rdfs += rdf3
    all_rdfs += rdf4
    all_rdfs += rdf5
    all_rdfs += rdf6
    all_rdfs += rdf7
    all_rdfs += rdf8
    all_rdfs += rdf9
    all_rdfs += rdf10
    all_rdfs += rdf11
    all_rdfs += rdf12
    all_rdfs += rdf13
    all_rdfs += switches_rdf

    # 保存数据
    my_path = path.replace(".docx", "")
    write_to_excel(all_rdfs, my_path)


 

标注结果

代码输出如下(读取的内容以及识别的三元组)

侯家塘变35千伏其侯线停电
风险预案
国网南京供电公司供电服务指挥中心(配网调控中心)
35千伏侯家塘变35千伏其侯线停电风险预案
一、事故情况及影响
得地调告:侯家塘变全所失电。
侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。
两回全停的重要用户有:江苏省金陵监狱
一回停电的双电源用户有:南京自来水公司汤山增压站
二、处理过程:
1、得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源
2、配调:
(1)恢复所用电
侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关
汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)
悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”
雨花急修班:合上#6260开关
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)
(2)恢复重要用户供电
侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)
3、汇报地调。
4、得地调通知,侯家塘变1号、2号主变已恢复供电,事故方式自行恢复。
------------------------ rdf1 ------------------------
('35千伏侯家塘变35千伏其侯线停电风险预案', '事故情况', '得地调告:侯家塘变全所失电。')
('35千伏侯家塘变35千伏其侯线停电风险预案', '事故原因', '侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。')
('35千伏侯家塘变35千伏其侯线停电风险预案', '事故影响', '两回全停的重要用户有:江苏省金陵监狱')
('35千伏侯家塘变35千伏其侯线停电风险预案', '事故影响', '一回停电的双电源用户有:南京自来水公司汤山增压站')
('35千伏侯家塘变35千伏其侯线停电风险预案', '处理过程', '得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源')
------------------------ rdf2 ------------------------
('得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源', '下一步骤', '侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关')
('侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关', '下一步骤', '汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”')
('汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5', '下一步骤', '侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)')
('侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)', '下一步骤', '悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”')
('悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5', '下一步骤', '雨花急修班:合上#6260开关')
('雨花急修班:合上#6260开关', '下一步骤', '侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)')
('侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)', '下一步骤', '侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)')
('侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)', '下一步骤', '汇报地调。')
('汇报地调。', '下一步骤', '得地调通知,侯家塘变1号、2号主变已恢复供电,事故方式自行恢复。')
------------------------ rdf3 ------------------------
('侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。', '失电()', '侯家塘变1号接地变')
('侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。', '失电()', '侯家塘变2号接地变')
('侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。', '失电()', '侯家塘变1号所用电源')
('侯家塘变1号、2号接地变失电,导致1号、2号所用电源失电。', '失电()', '侯家塘变2号所用电源')
------------------------ rdf4 ------------------------
('得地调告:侯家塘变全所失电。', '两回全停', '江苏省金陵监狱')
('得地调告:侯家塘变全所失电。', '一回停电', '南京自来水公司汤山增压站')
('江苏省金陵监狱', '属性', '重要用户')
('南京自来水公司汤山增压站', '属性', '双电源用户')
------------------------ rdf5 ------------------------
('得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源', '移出', '侯家塘变10千伏负荷')
('得地调要求:侯家塘变10千伏负荷移出,并保证悦民变所用变电源', '保证', '悦民变所用变电源')
------------------------ rdf6 ------------------------
('侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关', '属性', '恢复所供电')
('汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5', '属性', '恢复所供电')
('侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)', '属性', '恢复所供电')
('悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5', '属性', '恢复所供电')
('雨花急修班:合上#6260开关', '属性', '恢复所供电')
('侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)', '属性', '恢复所供电')
('侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关', '恢复所供电', '侯家塘变')
('汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5', '恢复所供电', '侯家塘变')
('侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)', '恢复所供电', '侯家塘变')
('悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5', '恢复所供电', '侯家塘变')
('雨花急修班:合上#6260开关', '恢复所供电', '侯家塘变')
('侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)', '恢复所供电', '侯家塘变')
------------------------ rdf7 ------------------------
('侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)', '操作分支', '侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关')
('侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)', '操作分支', '汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变')
('侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关', '等价操作', '汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变')
('侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)', '操作分支', '侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关')
('侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)', '操作分支', '悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电')
('侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关', '等价操作', '悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电')
------------------------ rdf8 ------------------------
('侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)', '属性', '恢复重要用户供电')
('侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)', '恢复重要用户供电', '江苏省金陵监狱')
('侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)', '恢复重要用户供电', '南京自来水公司汤山增压站')
#####################   datas1  #######################
侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关
汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5”
侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)
悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5”
雨花急修班:合上#6260开关
侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)
#####################   datas2  #######################
侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)
------------------------ rdf9 ------------------------
('侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关', '拉开', '侯家塘变1号主变101开关')
('侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关', '拉开', '侯家塘变2号主变102开关')
('侯家塘变:拉开1号主变101开关、2号主变102开关及所有10千伏出线开关', '拉开', '侯家塘变10千伏出线开关')
------------------------ rdf10 ------------------------
('汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5', '定切保护停用', '汤山变侯家塘线233开关')
('汤山变:侯家塘线233开关定切保护停用,过流时间由1”改为1.5', '过流时间由1”改为1.5', '汤山变侯家塘线233开关')
('悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5', '定切保护停用', '悦民变百镇#1线118开关')
('悦民变:百镇#1线118开关定切保护停用,过流时间由1”改为1.5', '过流时间由1”改为1.5', '悦民变百镇#1线118开关')
------------------------ rdf11 ------------------------
('侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)', '保护全部停用', '侯家塘变侯家塘线135开关')
('侯家塘变:侯家塘线135开关保护全部停用,合上侯家塘线135开关(汤山变侯家塘线233开关暂供进侯家塘变10千伏Ⅰ段母线转供1号接地变)', '合上', '侯家塘变侯家塘线135开关')
('侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)', '保护全部停用', '侯家塘变官塘线235开关')
('侯家塘变:官塘线235开关保护全部停用,合上官塘线235开关(悦民变百镇#1线118开关暂供进侯家塘变10千伏 Ⅱ段母线转供2号接地变并恢复江苏省金陵监狱供电)', '合上', '侯家塘变官塘线235开关')
------------------------ rdf12 ------------------------
('雨花急修班:合上#6260开关', '合上', '雨花急修班#6260开关')
('侯家塘变:合上龙尚线134开关(恢复江苏省金陵监狱、南京自来水公司汤山增压站供电)', '合上', '侯家塘变龙尚线134开关')
------------------------ rdf13 ------------------------
('侯家塘变10kVⅠ段母线', '属于', '侯家塘变')
('侯家塘变10kVⅡ段母线', '属于', '侯家塘变')
------------------------ switches_rdf ------------------------
('侯家塘变1号接地变', '属于', '侯家塘变')
('侯家塘变2号接地变', '属于', '侯家塘变')
('侯家塘变1号主变101开关', '属于', '侯家塘变')
('侯家塘变2号主变102开关', '属于', '侯家塘变')
('汤山变侯家塘线233开关', '属于', '汤山变')
('悦民变百镇#1线118开关', '属于', '悦民变')
('侯家塘变侯家塘线135开关', '属于', '侯家塘变')
('侯家塘变官塘线235开关', '属于', '侯家塘变')
('雨花急修班#6260开关', '属于', '雨花急修班')
('侯家塘变龙尚线134开关', '属于', '侯家塘变')

 

35千伏侯家塘变全停事故处理预案模板.docx文档为例,代码标注的RDF三元组保存在35千伏侯家塘变全停事故处理预案模板.xlsx表格中截图如下所示,结果与模版给出的三元组相差无几。

总结

三个多月过去再看那时候写的代码,发现还有很多方面需要提高。

  • 函数的命名不够简洁直观
  • 三元组的组织架构有几条比较混乱
  • 有几条三元组的标注还不够完整
  • 代码的容错性较低,遇到不规范的文档会报错(虽然可以将文档改为规范的文档,但对不熟悉代码的其他成员不太友好)

祝大家1024程序员节快乐!


转载:https://blog.csdn.net/weixin_44368437/article/details/127496062
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场