This commit is contained in:
P3ngSaM 2023-02-10 16:54:25 +08:00
parent 51617cc0fc
commit 3e2499635a
32 changed files with 1035 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea
.venv
*.pyc

49
App/Crud/ReportCrud.py Normal file
View File

@ -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
App/Crud/__init__.py Normal file
View File

20
App/Model/ReportModel.py Normal file
View File

@ -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
App/Model/__init__.py Normal file
View File

385
App/Router/ReportRouter.py Normal file
View File

@ -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
App/Router/__init__.py Normal file
View File

View File

@ -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
App/Schemas/__init__.py Normal file
View File

0
App/__init__.py Normal file
View File

View File

@ -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
Utils/Common/__init__.py Normal file
View File

View File

@ -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"
}
}

View File

@ -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:
typestr
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()

View File

@ -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()

View File

View File

@ -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
}

206
Utils/File/TestConfig.json Normal file
View File

@ -0,0 +1,206 @@
{
"企业名称": "远东资信评估有限公司",
"信评编号": "20230001",
"报告年份": "三",
"报告月份": "二",
"主体信用等级": "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
Utils/File/__init__.py Normal file
View File

120
Utils/File/calculation.json Normal file
View File

@ -0,0 +1,120 @@
{
"资产总额": {
"参数": [
"资产总计"
],
"计算公式": "{资产总计} / 100000000"
},
"所有者权益": {
"参数": [
"所有者权益合计"
],
"计算公式": "{所有者权益合计} / 100000000"
},
"总债务": {
"参数": [
"短期借款",
"交易性金融负债",
"应付票据",
"一年内到期的非流动负债",
"其他短期有息债务",
"长期借款",
"应付债券",
"其他长期有息债务"
],
"计算公式": "({短期借款}+{交易性金融负债}+{应付票据}+{一年内到期的非流动负债}+{其他短期有息债务}+{长期借款}+{应付债券}+{其他长期有息债务}) / 100000000"
},
"营业收入": {
"参数": [
"营业收入"
],
"计算公式": "{营业收入} / 100000000"
},
"净利润": {
"参数": [
"利润总额",
"减:所得税"
],
"计算公式": "({利润总额} - {减:所得税}) / 100000000"
},
"EBITDA": {
"参数": [
"利润总额",
"计入财务费用的利息支出",
"折旧",
"无形资产及长期待摊费用摊销"
],
"计算公式": "{利润总额} + {计入财务费用的利息支出} + {折旧} + {无形资产及长期待摊费用摊销}"
},
"营业利润率(%)": {
"参数": [
"营业利润",
"营业收入"
],
"计算公式": "{营业利润} / {营业收入} * 100"
},
"净资产收益率(%)": {
"参数": [
"利润总额",
"减:所得税",
"期初净资产余额",
"期末净资产余额"
],
"计算公式": "({利润总额} - {减:所得税}) / [({期初净资产余额} + {期末净资产余额}) / 2] * 100"
},
"资产负债率(%)": {
"参数": [
"负债合计",
"资产总计"
],
"计算公式": "{负债合计} / {资产总计} * 100"
},
"总债务资本化比率(%)": {
"参数": [
"所有者权益合计",
"短期借款",
"交易性金融负债",
"应付票据",
"一年内到期的非流动负债",
"其他短期有息债务",
"长期借款",
"应付债券",
"其他长期有息债务"
],
"计算公式": "({短期借款}+{交易性金融负债}+{应付票据}+{一年内到期的非流动负债}+{其他短期有息债务}+{长期借款}+{应付债券}+{其他长期有息债务})/ ({短期借款}+{交易性金融负债}+{应付票据}+{一年内到期的非流动负债}+{其他短期有息债务}+{长期借款}+{应付债券}+{其他长期有息债务}+{所有者权益合计}) × 100"
},
"流动比率(%)": {
"参数": [
"流动资产",
"流动负债"
],
"计算公式": "{流动资产} / {流动负债} * 100"
},
"EBITDA利息保障倍数(倍)": {
"参数": [
"利润总额",
"计入财务费用的利息支出",
"折旧",
"无形资产及长期待摊费用摊销",
"资本化利息支出"
],
"计算公式": "({利润总额} + {计入财务费用的利息支出} + {折旧} + {无形资产及长期待摊费用摊销}) / ({计入财务费用的利息支出} + {资本化利息支出}) "
},
"总债务/EBITDA(倍)": {
"参数": [
"短期借款",
"交易性金融负债",
"应付票据",
"一年内到期的非流动负债",
"其他短期有息债务",
"长期借款",
"应付债券",
"其他长期有息债务",
"利润总额",
"计入财务费用的利息支出",
"折旧",
"无形资产及长期待摊费用摊销"
],
"计算公式": "({短期借款}+{交易性金融负债}+{应付票据}+{一年内到期的非流动负债}+{其他短期有息债务}+{长期借款}+{应付债券}+{其他长期有息债务}) / ({利润总额} + {计入财务费用的利息支出} + {折旧} + {无形资产及长期待摊费用摊销})"
}
}

Binary file not shown.

BIN
Utils/File/template.docx Normal file

Binary file not shown.

BIN
Utils/File/~$mplate.docx Normal file

Binary file not shown.

Binary file not shown.

View File

@ -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"

View File

@ -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

View File

@ -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

View File

0
Utils/__init__.py Normal file
View File

11
main.py Normal file
View File

@ -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)

13
requirements.txt Normal file
View File

@ -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