dev
This commit is contained in:
parent
51617cc0fc
commit
3e2499635a
|
@ -0,0 +1,3 @@
|
|||
.idea
|
||||
.venv
|
||||
*.pyc
|
|
@ -0,0 +1,49 @@
|
|||
from sqlalchemy.orm import Session
|
||||
|
||||
from App.Model.ReportModel import ReportGenerationFlow
|
||||
from App.Schemas import ReportSchemas
|
||||
from Utils.DataBase.MongoHelperUtils import MongoHelper
|
||||
from Utils.UniqueCoder.TimeSerialNumUtils import create_time_serial_num
|
||||
|
||||
|
||||
def create_report_generation_flow(db: Session, schemas: dict):
|
||||
item = ReportGenerationFlow(**schemas)
|
||||
item.id = create_time_serial_num(prefix="R")
|
||||
item.status = "进行"
|
||||
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return item
|
||||
|
||||
|
||||
def get_report_generation_flow(db: Session, _id: str):
|
||||
item = db.query(ReportGenerationFlow).filter_by(id=_id).first()
|
||||
return item
|
||||
|
||||
|
||||
def delete_report_generation_flow(db, _id: str):
|
||||
db.query(ReportGenerationFlow).filter_by(id=_id).delete()
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
def update_report_generation_flow(db, _id: str, update_data: dict):
|
||||
db.query(ReportGenerationFlow).filter_by(id=_id).update(update_data)
|
||||
db.commit()
|
||||
return db.query(ReportGenerationFlow).filter_by(id=_id).first()
|
||||
|
||||
|
||||
def save_report_data(db: Session, mongodb: MongoHelper, _id: str, data: dict):
|
||||
rf_item = db.query(ReportGenerationFlow).filter_by(id=_id).first()
|
||||
if not rf_item:
|
||||
return False
|
||||
|
||||
if rf_item.data_id:
|
||||
mongodb.delete_data_by_id(dbname="评级报告", sheet="填报数据", _id=rf_item.data_id)
|
||||
|
||||
obj_id = mongodb.insert_data(dbname="评级报告", sheet="填报数据", data=data)
|
||||
update_data = {"data_id": obj_id}
|
||||
db.query(ReportGenerationFlow).filter_by(id=_id).update(update_data)
|
||||
db.commit()
|
||||
return db.query(ReportGenerationFlow).filter_by(id=_id).first()
|
|
@ -0,0 +1,20 @@
|
|||
from sqlalchemy import Column, String, Date, Enum
|
||||
|
||||
from App.Schemas.ReportSchemas import GenerationFlowStatusEnum
|
||||
from Utils.DataBase.SqlAlchemyUtils import Base, engine
|
||||
|
||||
|
||||
class ReportGenerationFlow(Base):
|
||||
__tablename__ = "report_generation_flow"
|
||||
|
||||
id = Column(String(16), primary_key=True)
|
||||
company = Column(String(16))
|
||||
model = Column(String(32))
|
||||
generation_date = Column(Date)
|
||||
rating_process_id = Column(String(16), comment="评级流程ID")
|
||||
status = Column(Enum(GenerationFlowStatusEnum, values_callable=lambda x: [e.value for e in x]))
|
||||
data_id = Column(String(24), comment="填报数据ID")
|
||||
word_id = Column(String(24), comment="文件ID")
|
||||
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
|
@ -0,0 +1,385 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
from docx import Document
|
||||
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
||||
from docxtpl import DocxTemplate
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from interval import Interval
|
||||
from starlette.responses import FileResponse
|
||||
from docx.shared import Pt, RGBColor
|
||||
|
||||
from App.Crud import ReportCrud
|
||||
from App.Schemas import ReportSchemas
|
||||
from Utils.Common.RegisterUtils import request_to_post
|
||||
from Utils.DataBase.MongoHelperUtils import MongoHelper, get_mongodb
|
||||
from Utils.DataBase.SqlAlchemyUtils import get_db
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/report_generation"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/create", summary="新建报告生成流程", tags=["报告生成"])
|
||||
def func(schemas: ReportSchemas.CreateReportFlowReqBody, db: Session = Depends(get_db)):
|
||||
# 查询此条评级流程是否存在
|
||||
res = request_to_post(
|
||||
url="http://test.fecribd.com/api/rating_flow/search",
|
||||
data={"id": schemas.rating_process_id}
|
||||
)
|
||||
|
||||
if not res:
|
||||
raise HTTPException(status_code=404, detail="评级流程不存在")
|
||||
if res.get('status') != '完成':
|
||||
raise HTTPException(status_code=202, detail="评级流程未完成")
|
||||
|
||||
schema = schemas.dict()
|
||||
schema['company'] = res.get('company')
|
||||
schema['model'] = res.get('scorecard')
|
||||
|
||||
# 根据评级流程ID新建保存生成流程ID
|
||||
report_generation_obj = ReportCrud.create_report_generation_flow(db=db, schemas=schema)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "新建成功",
|
||||
"content": {
|
||||
"report_flow_id": report_generation_obj.id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.post("/delete", summary="删除此条流程", tags=["报告生成"])
|
||||
def func(schemas: ReportSchemas.SearchReportFlowReqBody, db: Session = Depends(get_db)):
|
||||
rf_item = ReportCrud.get_report_generation_flow(db, _id=schemas.report_generation_id)
|
||||
if not rf_item:
|
||||
raise HTTPException(status_code=404, detail="报告生成流程不存在")
|
||||
result = ReportCrud.delete_report_generation_flow(db, _id=schemas.report_generation_id)
|
||||
if not result:
|
||||
raise HTTPException(status_code=401, detail="删除失败")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "删除成功",
|
||||
"content": {}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/data_template", summary="导入填报数据模板", tags=["报告生成"])
|
||||
def func():
|
||||
with open(os.getcwd() + '\\Utils\\File\\ParamConfig.json', encoding='utf-8') as f:
|
||||
template = json.load(f)
|
||||
if not template:
|
||||
raise HTTPException(status_code=401, detail="数据填报模板获取失败")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "导入成功",
|
||||
"content": template
|
||||
}
|
||||
|
||||
|
||||
@router.post("/rating_result", summary="导入评级结果数据", tags=["报告生成"])
|
||||
def func(schemas: ReportSchemas.SearchReportFlowReqBody, db: Session = Depends(get_db)):
|
||||
# 判断报告生成流程ID是否存在
|
||||
rf_item = ReportCrud.get_report_generation_flow(db, _id=schemas.report_generation_id)
|
||||
if not rf_item:
|
||||
raise HTTPException(status_code=404, detail="报告生成流程不存在")
|
||||
if rf_item.status.value == '完成':
|
||||
raise HTTPException(status_code=202, detail="报告生成流程已完成")
|
||||
# 获取评级结果数据
|
||||
res = request_to_post(
|
||||
url="http://test.fecribd.com/api/rating_flow/get?_id={}".format(rf_item.rating_process_id),
|
||||
data={}
|
||||
)
|
||||
if not res:
|
||||
raise HTTPException(status_code=404, detail="评级流程不存在")
|
||||
|
||||
# 将评级结果数据整合到一个dict
|
||||
rating_dict = {"企业名称": rf_item.company, "本次评级适用评级方法与模型": rf_item.model}
|
||||
for node in res:
|
||||
rating_dict = dict(rating_dict, **node.get('content'))
|
||||
|
||||
finance_data = ["资产总额", "所有者权益", "总债务", "营业收入", "净利润", "EBITDA", "营业利润率(%)", "净资产收益率(%)",
|
||||
"资产负债率(%)", "总债务资本化比率(%)", "流动比率(%)", "EBITDA利息保障倍数(倍)", "总债务/EBITDA(倍)"]
|
||||
|
||||
# 主要财务数据
|
||||
|
||||
|
||||
# 映射评级结果key值
|
||||
rating_dict['主体信用等级'] = rating_dict.get('最终调整级别')
|
||||
rating_dict['基本信用状况得分'] = rating_dict.pop("bacp_score")
|
||||
rating_dict['基本信用状况评价(BACP)'] = rating_dict.pop("bacp_level")
|
||||
rating_dict['评级指标'] = rating_dict.pop("bacp_index_items")
|
||||
rating_dict['个体信用得分'] = rating_dict.pop("ascp_score")
|
||||
rating_dict['个体信用状况(ASCP)'] = rating_dict.pop("ascp_level")
|
||||
rating_dict['外部支持调整'] = rating_dict.pop("调整说明")
|
||||
rating_dict['评级模型级别'] = rating_dict.pop("最终调整级别")
|
||||
|
||||
# 清洗评级指标
|
||||
index_table = list()
|
||||
index_table.append(["评级要素", "指标名称", "权重", "档位"])
|
||||
for index in rating_dict["评级指标"]:
|
||||
index_item = list()
|
||||
index_item.append(index.get('一级指标'))
|
||||
index_item.append(index.get('二级指标'))
|
||||
index_item.append(str(index.get('权重')) + ".00%")
|
||||
if index.get('指标类型') == '定性指标':
|
||||
index_item.append(index.get('数值'))
|
||||
else:
|
||||
# 计算档位
|
||||
for gear_key, gear_value in index.get('档位').items():
|
||||
try:
|
||||
value = gear_value
|
||||
begin = value[0]
|
||||
end = value[-1]
|
||||
number = value[1:-1]
|
||||
|
||||
def judge_type(num):
|
||||
if num == '+inf':
|
||||
num = float('inf')
|
||||
elif num == '-inf':
|
||||
num = float('-inf')
|
||||
else:
|
||||
num = float(num)
|
||||
return num
|
||||
|
||||
begin_numeber = judge_type(number[0:number.rfind(',')])
|
||||
end_numeber = judge_type(number[number.rfind(','):].replace(",", "").strip())
|
||||
|
||||
# 区间各种情况
|
||||
zoom = None
|
||||
if begin == "(" and end == ")":
|
||||
zoom = Interval(begin_numeber, end_numeber, closed=False)
|
||||
elif begin == "(" and end == "]":
|
||||
zoom = Interval(begin_numeber, end_numeber, lower_closed=False)
|
||||
elif begin == "[" and end == "]":
|
||||
zoom = Interval(begin_numeber, end_numeber)
|
||||
elif begin == "[" and end == ")":
|
||||
zoom = Interval(begin_numeber, end_numeber, upper_closed=False)
|
||||
|
||||
if index.get('数值') in zoom:
|
||||
index_item.append(gear_key)
|
||||
break
|
||||
except Exception:
|
||||
index_item.append('第八档')
|
||||
index_table.append(index_item)
|
||||
rating_dict['评级指标'] = index_table
|
||||
|
||||
# 处理评价调整
|
||||
adjustment = 0
|
||||
for adj in rating_dict.get('adjustments'):
|
||||
adjustment += adj.get('调整分数')
|
||||
if adjustment == 0:
|
||||
adjustment = str(0)
|
||||
elif adjustment >= 1:
|
||||
adjustment = '+' + str(adjustment)
|
||||
else:
|
||||
adjustment = str(adjustment)
|
||||
rating_dict['评价调整(AM)'] = adjustment
|
||||
|
||||
# 获取数据填报模板
|
||||
with open(os.getcwd() + '\\Utils\\File\\ParamConfig.json', encoding='utf-8') as f:
|
||||
template = json.load(f)
|
||||
if not template:
|
||||
raise HTTPException(status_code=401, detail="数据填报模板获取失败")
|
||||
|
||||
# 将填报数据与结果数据进行匹配
|
||||
for key in template.keys():
|
||||
template[key] = rating_dict.get(key)
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "导入成功",
|
||||
"content": template
|
||||
}
|
||||
|
||||
|
||||
@router.post("/save_data", summary="保存填报数据", tags=["报告生成"])
|
||||
def func(schemas: ReportSchemas.SaveReportDataReqBody, db: Session = Depends(get_db),
|
||||
mongodb: MongoHelper = Depends(get_mongodb)):
|
||||
# 查询流程ID是否存在
|
||||
_id = schemas.report_generation_id
|
||||
data = schemas.report_data
|
||||
result = ReportCrud.save_report_data(db, mongodb, _id, data)
|
||||
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="报告生成流程不存在")
|
||||
|
||||
# 返回操作结果
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "保存成功",
|
||||
"content": result
|
||||
}
|
||||
|
||||
|
||||
@router.post("/generation", summary="生成word报告", tags=["报告生成"])
|
||||
def func(schemas: ReportSchemas.SearchReportFlowReqBody, db: Session = Depends(get_db),
|
||||
mongodb: MongoHelper = Depends(get_mongodb)):
|
||||
rf_item = ReportCrud.get_report_generation_flow(db, _id=schemas.report_generation_id)
|
||||
if not rf_item:
|
||||
raise HTTPException(status_code=404, detail="报告生成流程不存在")
|
||||
if rf_item.status.value == '完成':
|
||||
raise HTTPException(status_code=202, detail="报告生成流程已完成")
|
||||
# 获取填报数据
|
||||
|
||||
# 获取报告模板
|
||||
|
||||
# 生成报告
|
||||
|
||||
# 保存报告到mongodb,并返回obj_id
|
||||
with open(os.getcwd() + '\\Utils\\File\\template.docx', 'rb') as f:
|
||||
file_id = mongodb.insert_file(file=f.read())
|
||||
generation_time = time.strftime('%Y-%m-%d', time.localtime())
|
||||
result = ReportCrud.update_report_generation_flow(db, _id=rf_item.id, update_data={"word_id": str(file_id),
|
||||
"generation_date": generation_time})
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "生成成功",
|
||||
"content": result
|
||||
}
|
||||
|
||||
|
||||
@router.post("/preview", summary="预览word报告", tags=["报告生成"])
|
||||
def func(schemas: ReportSchemas.SearchReportFlowReqBody, db: Session = Depends(get_db)):
|
||||
rf_item = ReportCrud.get_report_generation_flow(db, _id=schemas.report_generation_id)
|
||||
if not rf_item:
|
||||
raise HTTPException(status_code=404, detail="报告生成流程不存在")
|
||||
if rf_item.status.value == '完成':
|
||||
raise HTTPException(status_code=202, detail="报告生成流程已完成")
|
||||
file = "{}信用评级报告初稿.docx".format(rf_item.company)
|
||||
file_path = os.getcwd() + "\\Utils\\File\\" + file
|
||||
return FileResponse(file_path, filename=file)
|
||||
|
||||
|
||||
@router.post("/download", summary="下载word报告", tags=["报告生成"])
|
||||
def func(schemas: ReportSchemas.SearchReportFlowReqBody, db: Session = Depends(get_db),
|
||||
mongodb: MongoHelper = Depends(get_mongodb)):
|
||||
rf_item = ReportCrud.get_report_generation_flow(db, _id=schemas.report_generation_id)
|
||||
if not rf_item:
|
||||
raise HTTPException(status_code=404, detail="报告生成流程不存在")
|
||||
if rf_item.status.value == '完成':
|
||||
raise HTTPException(status_code=202, detail="报告生成流程已完成")
|
||||
file = "{}信用评级报告初稿.docx".format(rf_item.company)
|
||||
file_path = os.getcwd() + "\\Utils\\File\\" + file
|
||||
result = mongodb.get_file(rf_item.word_id)
|
||||
myfile = open(file_path, mode='wb')
|
||||
myfile.write(result)
|
||||
return FileResponse(file_path, filename=file)
|
||||
|
||||
|
||||
@router.post("/confirm", summary="确认流程", tags=["报告生成"])
|
||||
def func(schemas: ReportSchemas.SearchReportFlowReqBody, db: Session = Depends(get_db)):
|
||||
rf_item = ReportCrud.get_report_generation_flow(db, _id=schemas.report_generation_id)
|
||||
if not rf_item:
|
||||
raise HTTPException(status_code=404, detail="报告生成流程不存在")
|
||||
if rf_item.status.value == '完成':
|
||||
raise HTTPException(status_code=202, detail="报告生成流程已完成")
|
||||
file = os.getcwd() + "\\Utils\\File\\{}信用评级报告初稿.docx".format(rf_item.company)
|
||||
os.remove(file)
|
||||
result = ReportCrud.update_report_generation_flow(db, _id=rf_item.id, update_data={"status": "完成"})
|
||||
if not result:
|
||||
raise HTTPException(status_code=202, detail="确认失败")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "确认成功",
|
||||
"content": result
|
||||
}
|
||||
|
||||
|
||||
@router.post("/generate", summary="生成报告", tags=["报告生成"])
|
||||
def func():
|
||||
doc = DocxTemplate(os.getcwd() + "\\Utils\\File\\" + "template.docx")
|
||||
with open(os.getcwd() + '\\Utils\\File\\TestConfig.json', encoding='utf-8') as f:
|
||||
replace_content = json.load(f)
|
||||
doc.render(replace_content)
|
||||
file_path = os.getcwd() + "\\Utils\\File\\" + "generated_doc.docx"
|
||||
doc.save(file_path)
|
||||
|
||||
file = Document(file_path)
|
||||
# 处理主要财务数据
|
||||
finance_table = file.tables[0]
|
||||
rows = len(finance_table.rows)
|
||||
cols = len(finance_table.columns)
|
||||
for row in range(rows):
|
||||
for col in range(cols):
|
||||
if col != 0:
|
||||
finance_table.cell(row, col).text = str(replace_content.get('主要财务数据')[row][col])
|
||||
for section in finance_table.cell(row, col).paragraphs:
|
||||
for block in section.runs:
|
||||
block.font.size = Pt(9)
|
||||
|
||||
# 处理分析师部分
|
||||
for n in range(len(file.paragraphs)):
|
||||
if file.paragraphs[n].text == "分析师":
|
||||
file.paragraphs[n].clear()
|
||||
analyst = replace_content.get('分析师信息')
|
||||
for i in range(len(analyst)):
|
||||
if i == len(analyst) - 1:
|
||||
file.paragraphs[n].insert_paragraph_before('分析师:{} {}'.format(analyst[i][0], analyst[i][1]))
|
||||
file.paragraphs[n].style.delete()
|
||||
else:
|
||||
file.paragraphs[n].insert_paragraph_before(' {} {}'.format(analyst[i][0], analyst[i][1]))
|
||||
break
|
||||
|
||||
# 处理评级模型部分
|
||||
index_table = file.tables[1]
|
||||
# 评级指标
|
||||
index_number = len(replace_content.get('评级指标'))
|
||||
for ind in range(0, index_number):
|
||||
index_table.add_row()
|
||||
# 当前行
|
||||
current_row = len(index_table.rows) - 1
|
||||
for r_i in range(len(replace_content.get('评级指标')[ind])):
|
||||
index_table.cell(current_row, r_i).text = str(replace_content.get('评级指标')[ind][r_i])
|
||||
for section in index_table.cell(current_row, r_i).paragraphs:
|
||||
for block in section.runs:
|
||||
block.font.size = Pt(9)
|
||||
index_table.cell(current_row, r_i).vertical_alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
||||
index_table.cell(current_row, r_i).paragraphs[0].alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
||||
|
||||
# 剩余部分
|
||||
result = ["基本信用状况评价(BACP)", "评价调整(AM)", "个体信用状况(ASCP)", "外部支持调整", "评级模型级别"]
|
||||
for item in result:
|
||||
index_table.add_row()
|
||||
# 当前行
|
||||
current_row = len(index_table.rows) - 1
|
||||
# 合并单元格
|
||||
a = index_table.cell(current_row, 0)
|
||||
b = index_table.cell(current_row, 2)
|
||||
a.merge(b)
|
||||
index_table.cell(current_row, 0).text = item
|
||||
index_table.cell(current_row, 3).text = replace_content.get(item)
|
||||
index_table.cell(current_row, 3).vertical_alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
||||
index_table.cell(current_row, 3).paragraphs[0].alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
||||
for section in index_table.cell(current_row, 0).paragraphs:
|
||||
for block in section.runs:
|
||||
block.font.size = Pt(9)
|
||||
for section in index_table.cell(current_row, 3).paragraphs:
|
||||
for block in section.runs:
|
||||
block.font.size = Pt(9)
|
||||
|
||||
# 模型评估与调整说明:
|
||||
index_table.add_row()
|
||||
current_row = len(index_table.rows) - 1
|
||||
# 合并单元格
|
||||
a = index_table.cell(current_row, 0)
|
||||
b = index_table.cell(current_row, 3)
|
||||
a.merge(b)
|
||||
index_table.cell(current_row, 0).text = "模型评估与调整说明:"
|
||||
for section in index_table.cell(current_row, 0).paragraphs:
|
||||
for block in section.runs:
|
||||
block.font.size = Pt(9)
|
||||
# 说明详情
|
||||
index_table.add_row()
|
||||
current_row = len(index_table.rows) - 1
|
||||
# 合并单元格
|
||||
a = index_table.cell(current_row, 0)
|
||||
b = index_table.cell(current_row, 3)
|
||||
a.merge(b)
|
||||
index_table.cell(current_row, 0).text = replace_content.get("模型评估与调整说明:")
|
||||
for section in index_table.cell(current_row, 0).paragraphs:
|
||||
for block in section.runs:
|
||||
block.font.size = Pt(9)
|
||||
file.save(file_path)
|
|
@ -0,0 +1,25 @@
|
|||
from datetime import date
|
||||
from enum import Enum, unique
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@unique
|
||||
class GenerationFlowStatusEnum(Enum):
|
||||
enum01 = "进行"
|
||||
enum02 = "完成"
|
||||
enum03 = "取消"
|
||||
|
||||
|
||||
class CreateReportFlowReqBody(BaseModel):
|
||||
rating_process_id = "评级流程ID"
|
||||
|
||||
|
||||
class SearchReportFlowReqBody(BaseModel):
|
||||
report_generation_id = "报告生成ID"
|
||||
|
||||
|
||||
class SaveReportDataReqBody(BaseModel):
|
||||
report_generation_id = "报告生成ID"
|
||||
report_data = {}
|
|
@ -0,0 +1,23 @@
|
|||
import json
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2luZm8iOnsidWlkIjoiVUlEMDAwMSIsImVtYWlsIjoiZmVjcmliZEBmZWNyLmNvbS5jbiIsIm5hbWUiOiJyb290Iiwicm9sZSI6Ilx1N2JhMVx1NzQwNlx1NTQ1OCIsImRlcGFydG1lbnQiOiJcdTY1NzBcdTViNTdcdTUzMTZcdTkwZTgiLCJyb2xlX2lkIjoiUk9MRTAxIiwiZGVwYXJ0bWVudF9pZCI6IkQwMDEifSwiZXhwIjoxNjgzNDc2OTA4fQ.8girdw3n0WDktRuK0aSgGor10eb11nIFvRJqUtPZum4"
|
||||
HEADERS = {"token": TOKEN}
|
||||
|
||||
|
||||
def request_to_get(url):
|
||||
res = requests.get(url=url, headers=HEADERS)
|
||||
if res.status_code == 200:
|
||||
return json.loads(res.text)
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def request_to_post(url, data):
|
||||
res = requests.post(url=url, headers=HEADERS, data=json.dumps(data))
|
||||
if res.status_code == 200:
|
||||
return json.loads(res.text)
|
||||
else:
|
||||
return False
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Mysql": {
|
||||
"wr_report_flow": "mysql+pymysql://root:123456@localhost/wr_report_flow?charset=utf8mb4",
|
||||
"wr_rating_flow": "mysql+pymysql://root:123456@localhost/wr_rating_flow?charset=utf8mb4"
|
||||
},
|
||||
"MongoDB": {
|
||||
"test": "root:123456@116.63.159.166:27017"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import re
|
||||
import os
|
||||
import json
|
||||
|
||||
import gridfs
|
||||
import pymongo
|
||||
|
||||
from urllib import parse
|
||||
from bson import ObjectId
|
||||
|
||||
|
||||
class MongoHelper:
|
||||
|
||||
def __init__(self, param):
|
||||
"""
|
||||
param:
|
||||
type:str
|
||||
desc: 选择连接哪个MongoDB数据库
|
||||
"""
|
||||
with open(os.path.abspath(os.path.dirname(__file__) + '/DBConfig.json')) as f:
|
||||
db_configs = json.load(f)
|
||||
this_mongo_cfg = db_configs['MongoDB'][param]
|
||||
m = re.match(r'([\s\S].*?):([\s\S].*)@([\s\S].*)', this_mongo_cfg)
|
||||
parsed_mongo_config = "{}:{}@{}".format(parse.quote_plus(m.group(1)), parse.quote_plus(m.group(2)),
|
||||
m.group(3))
|
||||
self.client = pymongo.MongoClient('mongodb://{}'.format(parsed_mongo_config))
|
||||
|
||||
def insert_data(self, dbname: str, sheet: str, data: dict):
|
||||
collection = self.client[dbname][sheet]
|
||||
item = collection.insert_one(data)
|
||||
return item.inserted_id.__str__()
|
||||
|
||||
def delete_data_by_id(self, dbname: str, sheet: str, _id: str):
|
||||
collection = self.client[dbname][sheet]
|
||||
collection.delete_one({'_id': ObjectId(_id)})
|
||||
return True
|
||||
|
||||
def find_data_by_id(self, dbname: str, sheet: str, _id: str):
|
||||
collection = self.client[dbname][sheet]
|
||||
return collection.find_one({'_id': ObjectId(_id)}, {"_id": False})
|
||||
|
||||
def update_data_by_id(self, dbname: str, sheet: str, _id: str, data: dict):
|
||||
collection = self.client[dbname][sheet]
|
||||
collection.update_one({'_id': ObjectId(_id)}, {"$set": data})
|
||||
return True
|
||||
|
||||
def insert_file(self, file: bytes):
|
||||
fs = gridfs.GridFS(self.client['评级报告'], 'docx')
|
||||
return fs.put(file)
|
||||
|
||||
def get_file(self, fid: str):
|
||||
fs = gridfs.GridFS(self.client['评级报告'], 'docx')
|
||||
gf = fs.get(ObjectId(fid))
|
||||
return gf.read()
|
||||
|
||||
|
||||
def get_mongodb():
|
||||
try:
|
||||
db = MongoHelper("test")
|
||||
yield db
|
||||
finally:
|
||||
db.client.close()
|
|
@ -0,0 +1,25 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
|
||||
# Import DBConfig
|
||||
with open(os.path.abspath(os.path.dirname(__file__) + '/DBConfig.json')) as f:
|
||||
db_configs = json.load(f)
|
||||
this_mysql_cfg = db_configs['Mysql']["wr_report_flow"]
|
||||
|
||||
# Sqlalchemy Export
|
||||
engine = create_engine(this_mysql_cfg)
|
||||
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
def get_db():
|
||||
try:
|
||||
db = Session()
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"企业名称": null,
|
||||
"信评编号": null,
|
||||
"报告年份": null,
|
||||
"报告月份": null,
|
||||
|
||||
"主体信用等级": null,
|
||||
"评级展望": null,
|
||||
"评级时间": null,
|
||||
|
||||
"主要财务数据": [],
|
||||
|
||||
"分析师信息": [],
|
||||
|
||||
"本次评级适用评级方法与模型": null,
|
||||
"评级指标": [],
|
||||
"基本信用状况评价(BACP)": null,
|
||||
"评价调整(AM)": null,
|
||||
"个体信用状况(ASCP)": null,
|
||||
"外部支持调整": null,
|
||||
"评级模型级别": null,
|
||||
"模型评估与调整说明": "受评企业的评级模型级别在个体信用状况的基础上考虑外部支持调整得到。其中,个体信用状况综合反映了基本信用状况评价以及个体相关的评价调整因素,外部支持主要考虑了股东/政府的外部支持因素。最终评级结果由信评委投票决定,可能与评级模型级别存在差异。",
|
||||
|
||||
"企业简称": null,
|
||||
"成立日期": null,
|
||||
"曾用名": null,
|
||||
"注册资本": null,
|
||||
|
||||
"当期年月": null,
|
||||
"当期资产总额": null,
|
||||
"当期资产总额变化趋势": null,
|
||||
"当期所有者权益": null,
|
||||
"当期所有者权益变化趋势": null,
|
||||
"当期月份": null,
|
||||
"当期营业收入": null,
|
||||
"当期营业收入变化趋势": null,
|
||||
"当期净利润变化趋势": null,
|
||||
"当期净利润": null,
|
||||
|
||||
"上期年份": null,
|
||||
"上期资产总额": null,
|
||||
"上期资产总额变化趋势": null,
|
||||
"上期所有者权益": null,
|
||||
"上期所有者权益变化趋势": null,
|
||||
"上期营业收入": null,
|
||||
"上期营业收入变化趋势": null,
|
||||
"上期净利润": null,
|
||||
"上期净利润变化趋势": null,
|
||||
|
||||
"财报日期区间": null,
|
||||
"未审计财报日期": null,
|
||||
"审计会计事务所": null
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
{
|
||||
"企业名称": "远东资信评估有限公司",
|
||||
"信评编号": "(2023)0001",
|
||||
"报告年份": "三",
|
||||
"报告月份": "二",
|
||||
"主体信用等级": "AAA",
|
||||
"评级展望": "正面",
|
||||
"评级时间": "2023年02月08日",
|
||||
"主要财务数据": [
|
||||
[
|
||||
"人民币:亿元",
|
||||
"2019",
|
||||
"2020",
|
||||
"2021",
|
||||
"2022.6"
|
||||
],
|
||||
[
|
||||
"资产总额",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
],
|
||||
[
|
||||
"所有者权益",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
],
|
||||
[
|
||||
"总债务",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
],
|
||||
[
|
||||
"营业收入",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
],
|
||||
[
|
||||
"净利润",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
],
|
||||
[
|
||||
"EBITDA",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
],
|
||||
[
|
||||
"营业利润率(%)",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
],
|
||||
[
|
||||
"净资产收益率(%)",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
],
|
||||
[
|
||||
"资产负债率(%)",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
],
|
||||
[
|
||||
"总债务资本化比率(%)",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
],
|
||||
[
|
||||
"流动比率(%)",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
],
|
||||
[
|
||||
"EBITDA利息保障倍数",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
],
|
||||
[
|
||||
"总债务/EBITDA(倍)",
|
||||
"1010",
|
||||
"1234",
|
||||
"1025",
|
||||
"1444"
|
||||
]
|
||||
],
|
||||
"分析师信息": [
|
||||
[
|
||||
"马文豪",
|
||||
"mawenhao@fecr.com.cn"
|
||||
],
|
||||
[
|
||||
"崔庆才",
|
||||
"cuiqingcai@fecr.com.cn"
|
||||
],
|
||||
[
|
||||
"胡毅",
|
||||
"huyi@fecr.com.cn"
|
||||
]
|
||||
],
|
||||
"本次评级适用评级方法与模型": "中国城投企业信用评级方法与模型",
|
||||
"评级指标": [
|
||||
[
|
||||
"规模",
|
||||
"营业收入(亿元)",
|
||||
"20.00",
|
||||
"第二档"
|
||||
],
|
||||
[
|
||||
"业务状况",
|
||||
"品牌地位",
|
||||
"12.00%",
|
||||
"第二档"
|
||||
],
|
||||
[
|
||||
"业务状况",
|
||||
"产品多样性",
|
||||
"12.00%",
|
||||
"第二档"
|
||||
],
|
||||
[
|
||||
"业务状况",
|
||||
"销售渠道多元化",
|
||||
"11.00%",
|
||||
"第二档"
|
||||
],
|
||||
[
|
||||
"盈利能力",
|
||||
"EBITDA利润率(%)",
|
||||
"8.00%",
|
||||
"第八档"
|
||||
],
|
||||
[
|
||||
"财务杠杆与偿债能力",
|
||||
"资产负债率(%)",
|
||||
"12.00%",
|
||||
"第一档"
|
||||
],
|
||||
[
|
||||
"财务杠杆与偿债能力",
|
||||
"总债务/EBITDA(倍)",
|
||||
"10.00%",
|
||||
"第一档"
|
||||
],
|
||||
[
|
||||
"财务杠杆与偿债能力",
|
||||
"EBITDA利息保障倍数",
|
||||
"15.00%",
|
||||
"第一档"
|
||||
]
|
||||
],
|
||||
"基本信用状况评价(BACP)": "AAA",
|
||||
"评价调整(AM)": "-1",
|
||||
"个体信用状况(ASCP)": "AAA",
|
||||
"外部支持调整": "-1",
|
||||
"评级模型级别": "AAA",
|
||||
"模型评估与调整说明:": "受评企业的评级模型级别在个体信用状况的基础上考虑外部支持调整得到。其中,个体信用状况综合反映了基本信用状况评价以及个体相关的评价调整因素,外部支持主要考虑了股东/政府的外部支持因素。最终评级结果由信评委投票决定,可能与评级模型级别存在差异。",
|
||||
"企业简称": "远东资信",
|
||||
"成立日期": "1988年2月",
|
||||
"曾用名": "上海远东资信评估有限公司",
|
||||
"注册资本": "5000万人民币",
|
||||
"当期年月": "2020年6月",
|
||||
"当期资产总额": "11236",
|
||||
"当期资产总额变化趋势": "增长2.5",
|
||||
"当期所有者权益": "12544",
|
||||
"当期所有者权益变化趋势": "减少10.2",
|
||||
"当期月份": "6",
|
||||
"当期营业收入": "15974",
|
||||
"当期营业收入变化趋势": "增长5.2",
|
||||
"当期净利润": "1021",
|
||||
"当期净利润变化趋势": "增长8.8",
|
||||
"上期年份": "2021",
|
||||
"上期资产总额": "14563",
|
||||
"上期资产总额变化趋势": "增长1.2",
|
||||
"上期所有者权益": "123510",
|
||||
"上期所有者权益变化趋势": "增长11.2",
|
||||
"上期营业收入": "12524",
|
||||
"上期营业收入变化趋势": "减少1.2",
|
||||
"上期净利润": "111214",
|
||||
"上期净利润变化趋势": "增长12.3",
|
||||
"财报日期区间": "2019~2022",
|
||||
"未审计财报日期": "2022年6月",
|
||||
"审计会计事务所": "远东审计会计事务所"
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"资产总额": {
|
||||
"参数": [
|
||||
"资产总计"
|
||||
],
|
||||
"计算公式": "{资产总计} / 100000000"
|
||||
},
|
||||
"所有者权益": {
|
||||
"参数": [
|
||||
"所有者权益合计"
|
||||
],
|
||||
"计算公式": "{所有者权益合计} / 100000000"
|
||||
},
|
||||
"总债务": {
|
||||
"参数": [
|
||||
"短期借款",
|
||||
"交易性金融负债",
|
||||
"应付票据",
|
||||
"一年内到期的非流动负债",
|
||||
"其他短期有息债务",
|
||||
"长期借款",
|
||||
"应付债券",
|
||||
"其他长期有息债务"
|
||||
],
|
||||
"计算公式": "({短期借款}+{交易性金融负债}+{应付票据}+{一年内到期的非流动负债}+{其他短期有息债务}+{长期借款}+{应付债券}+{其他长期有息债务}) / 100000000"
|
||||
},
|
||||
"营业收入": {
|
||||
"参数": [
|
||||
"营业收入"
|
||||
],
|
||||
"计算公式": "{营业收入} / 100000000"
|
||||
},
|
||||
"净利润": {
|
||||
"参数": [
|
||||
"利润总额",
|
||||
"减:所得税"
|
||||
],
|
||||
"计算公式": "({利润总额} - {减:所得税}) / 100000000"
|
||||
},
|
||||
"EBITDA": {
|
||||
"参数": [
|
||||
"利润总额",
|
||||
"计入财务费用的利息支出",
|
||||
"折旧",
|
||||
"无形资产及长期待摊费用摊销"
|
||||
],
|
||||
"计算公式": "{利润总额} + {计入财务费用的利息支出} + {折旧} + {无形资产及长期待摊费用摊销}"
|
||||
},
|
||||
"营业利润率(%)": {
|
||||
"参数": [
|
||||
"营业利润",
|
||||
"营业收入"
|
||||
],
|
||||
"计算公式": "{营业利润} / {营业收入} * 100"
|
||||
},
|
||||
"净资产收益率(%)": {
|
||||
"参数": [
|
||||
"利润总额",
|
||||
"减:所得税",
|
||||
"期初净资产余额",
|
||||
"期末净资产余额"
|
||||
],
|
||||
"计算公式": "({利润总额} - {减:所得税}) / [({期初净资产余额} + {期末净资产余额}) / 2] * 100"
|
||||
},
|
||||
"资产负债率(%)": {
|
||||
"参数": [
|
||||
"负债合计",
|
||||
"资产总计"
|
||||
],
|
||||
"计算公式": "{负债合计} / {资产总计} * 100"
|
||||
},
|
||||
"总债务资本化比率(%)": {
|
||||
"参数": [
|
||||
"所有者权益合计",
|
||||
"短期借款",
|
||||
"交易性金融负债",
|
||||
"应付票据",
|
||||
"一年内到期的非流动负债",
|
||||
"其他短期有息债务",
|
||||
"长期借款",
|
||||
"应付债券",
|
||||
"其他长期有息债务"
|
||||
],
|
||||
"计算公式": "({短期借款}+{交易性金融负债}+{应付票据}+{一年内到期的非流动负债}+{其他短期有息债务}+{长期借款}+{应付债券}+{其他长期有息债务})/ ({短期借款}+{交易性金融负债}+{应付票据}+{一年内到期的非流动负债}+{其他短期有息债务}+{长期借款}+{应付债券}+{其他长期有息债务}+{所有者权益合计}) × 100"
|
||||
},
|
||||
"流动比率(%)": {
|
||||
"参数": [
|
||||
"流动资产",
|
||||
"流动负债"
|
||||
],
|
||||
"计算公式": "{流动资产} / {流动负债} * 100"
|
||||
},
|
||||
"EBITDA利息保障倍数(倍)": {
|
||||
"参数": [
|
||||
"利润总额",
|
||||
"计入财务费用的利息支出",
|
||||
"折旧",
|
||||
"无形资产及长期待摊费用摊销",
|
||||
"资本化利息支出"
|
||||
],
|
||||
"计算公式": "({利润总额} + {计入财务费用的利息支出} + {折旧} + {无形资产及长期待摊费用摊销}) / ({计入财务费用的利息支出} + {资本化利息支出}) "
|
||||
},
|
||||
"总债务/EBITDA(倍)": {
|
||||
"参数": [
|
||||
"短期借款",
|
||||
"交易性金融负债",
|
||||
"应付票据",
|
||||
"一年内到期的非流动负债",
|
||||
"其他短期有息债务",
|
||||
"长期借款",
|
||||
"应付债券",
|
||||
"其他长期有息债务",
|
||||
"利润总额",
|
||||
"计入财务费用的利息支出",
|
||||
"折旧",
|
||||
"无形资产及长期待摊费用摊销"
|
||||
],
|
||||
"计算公式": "({短期借款}+{交易性金融负债}+{应付票据}+{一年内到期的非流动负债}+{其他短期有息债务}+{长期借款}+{应付债券}+{其他长期有息债务}) / ({利润总额} + {计入财务费用的利息支出} + {折旧} + {无形资产及长期待摊费用摊销})"
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,10 @@
|
|||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
def set_next_id(db: Session, num_len: int, prefix: str, model):
|
||||
data = db.query(model).with_entities(model.id).order_by(model.id.desc()).first()
|
||||
if data:
|
||||
num = str(int({**data}.get("id").split(prefix)[-1]) + 1)
|
||||
return prefix + "0" * (num_len - len(num)) + num
|
||||
else:
|
||||
return prefix + "0" * (num_len - 1) + "1"
|
|
@ -0,0 +1,9 @@
|
|||
import random
|
||||
|
||||
|
||||
def get_random_num_code(length: int):
|
||||
choices = '0123456789'
|
||||
num_code = ''
|
||||
for i in range(length):
|
||||
num_code += random.choice(choices)
|
||||
return num_code
|
|
@ -0,0 +1,12 @@
|
|||
import time
|
||||
|
||||
|
||||
def create_time_serial_num(prefix: str = "", suffix: str = ""):
|
||||
ct = time.time()
|
||||
local_time = time.localtime(ct)
|
||||
data_head = time.strftime("%Y-%m-%d %H:%M:%S", local_time)
|
||||
data_secs = (ct - int(ct)) * 1000
|
||||
time_stamp = "%s.%03d" % (data_head, data_secs)
|
||||
stamp = ("".join(time_stamp.split()[0].split("-")) + "".join(time_stamp.split()[1].split(":"))).replace('.', '')
|
||||
time_serial_num = "{}{}{}".format(prefix, stamp[2:], suffix)
|
||||
return time_serial_num
|
|
@ -0,0 +1,11 @@
|
|||
from fastapi import FastAPI
|
||||
|
||||
from App.Router.ReportRouter import router
|
||||
|
||||
app = FastAPI(
|
||||
title="评级报告自动生成",
|
||||
description="自动生成评级报告word",
|
||||
version="v1.0.0"
|
||||
)
|
||||
|
||||
app.include_router(router)
|
|
@ -0,0 +1,13 @@
|
|||
fastapi~=0.85.0
|
||||
pydantic~=1.10.2
|
||||
SQLAlchemy~=1.4.41
|
||||
PyMySQL~=1.0.2
|
||||
uvicorn~=0.19.0
|
||||
Werkzeug~=2.2.2
|
||||
PyJWT~=2.5.0
|
||||
requests~=2.28.1
|
||||
casbin~=1.17.1
|
||||
cryptography
|
||||
pycryptodome
|
||||
pymongo~=4.3.2
|
||||
pandas~=1.5.1
|
Loading…
Reference in New Issue