data_verification.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. import uvicorn
  2. import warnings
  3. import os
  4. from fastapi import FastAPI, UploadFile, File, BackgroundTasks, routing
  5. from openpyxl import load_workbook
  6. from openpyxl.utils.cell import coordinate_from_string
  7. from openpyxl.comments import Comment
  8. from openpyxl.styles import PatternFill
  9. from fastapi.middleware.cors import CORSMiddleware
  10. from datetime import datetime
  11. from fastapi.responses import FileResponse
  12. from fastapi.staticfiles import StaticFiles
  13. import asyncio
  14. from concurrent.futures.process import ProcessPoolExecutor
  15. from fastapi.responses import StreamingResponse
  16. import shutil
  17. import uuid
  18. import time
  19. warnings.filterwarnings("ignore")
  20. app = FastAPI()
  21. app.add_middleware(
  22. CORSMiddleware,
  23. allow_origins=["*"],
  24. allow_credentials=True,
  25. allow_methods=["*"],
  26. allow_headers=["*"],
  27. )
  28. shared_dir = "download_cache"
  29. app.mount(f"/{shared_dir}", StaticFiles(directory=shared_dir), name={shared_dir})
  30. diff_dir = "diff_cache"
  31. app.mount(f"/{diff_dir}", StaticFiles(directory=diff_dir), name={diff_dir})
  32. cur_cache_path = "cur_cache/"
  33. def get_title_row(sheet):
  34. title_row_num = -1
  35. row_range = sheet[1:5]
  36. for i, r in enumerate(row_range):
  37. for j, c in enumerate(r):
  38. print(f"第{i + 1 }行,第{j}列,值:{c.value}")
  39. if "证件号码" == c.value or "收入(元)" == c.value or "务工月收入" == c.value:
  40. title_row_num = c.row
  41. return title_row_num
  42. def get_all_numbers(sheet, start_row, cow):
  43. keys = {}
  44. for i in range(start_row, sheet.max_row * 2):
  45. id_number = sheet[f"{cow}{i}"].value
  46. if id_number is None:
  47. break
  48. keys[id_number] = i
  49. return keys
  50. def deal_diff_data(file: UploadFile = File(...), target_name: str = None):
  51. print("开始处理")
  52. def generate_diff_data(start_row, max_row, sheet, title_dict, keys, need_copy_data):
  53. for i in range(start_row, max_row):
  54. id_number = sheet[f"{title_dict['证件号码']}{i}"].value
  55. if id_number is None:
  56. # print(f"该行身份证为空{i}")
  57. continue
  58. if id_number not in keys:
  59. # print(f"该身份证不在省办列表中{id_number}")
  60. need_copy_data.append(i)
  61. new = None
  62. # 删除所需要的执行时间太久了,暂时废弃删除的分支
  63. # if sheet.max_row > len(need_copy_data) * 2 and 3 < 2:
  64. # """如果diff很少,那么创建一个新表,一条一条添加"""
  65. new = workbook.create_sheet("仅" + sheet.title + "有的数据")
  66. for i, row in enumerate(need_copy_data):
  67. print(f"开始写入{i}行")
  68. for j, c in enumerate(sheet[row]):
  69. new.cell(i + 1, j + 1, c.value)
  70. # else:
  71. # """如果diff数据比较多,直接copy旧表,删除不需要的数据"""
  72. # start = time.time()
  73. # new = workbook.copy_worksheet(sheet)
  74. # print(f" copy执行时间{time.time() - start}")
  75. # for i in range(max_row, start_row, -1):
  76. # id_number = sheet[f"{title_dict['证件号码']}{i}"].value
  77. # if id_number is None:
  78. # print(f"该行身份证为空{i}")
  79. # continue
  80. # if id_number in keys:
  81. # print(f"该身份证不在省办列表中{id_number}")
  82. # new.delete_rows(i)
  83. return need_copy_data
  84. dir_path = cur_cache_path
  85. savename = dir_path + file.filename
  86. contents = file.file.read()
  87. with open(savename, "wb") as f:
  88. f.write(contents)
  89. # 读取excel表
  90. workbook = load_workbook(savename)
  91. # 获取指定的sheet
  92. sheet_names = workbook.sheetnames
  93. first = None
  94. second = None
  95. for index, name in enumerate(sheet_names):
  96. print(f"表名为:{name}")
  97. if name == "省办务工":
  98. first = workbook[name]
  99. elif name == "国办务工":
  100. second = workbook[name]
  101. if first is None or second is None:
  102. return {
  103. "code": 202,
  104. "msg": "没有找到待处理的 省办务工 和 国办务工 两张表格",
  105. }
  106. first_title_row_num = get_title_row(first)
  107. if first_title_row_num == -1:
  108. return {"code": 202, "msg": "省办务工没有找到数据"}
  109. first_title_dict = {}
  110. first_title_rows = first[first_title_row_num]
  111. for title_cell in first_title_rows:
  112. x, y = coordinate_from_string(title_cell.coordinate)
  113. first_title_dict[title_cell.value] = x
  114. first_keys = get_all_numbers(first, first_title_row_num + 1, first_title_dict["证件号码"])
  115. second_title_row_num = get_title_row(second)
  116. if second_title_row_num == -1:
  117. return {"code": 202, "msg": "国办务工没有找到数据"}
  118. second_title_dict = {}
  119. second_title_rows = second[second_title_row_num]
  120. for title_cell in second_title_rows:
  121. x, y = coordinate_from_string(title_cell.coordinate)
  122. second_title_dict[title_cell.value] = x
  123. second_keys = get_all_numbers(second, second_title_row_num + 1, second_title_dict["证件号码"])
  124. generate_diff_data(
  125. first_title_row_num + 1, first.max_row, first, first_title_dict, second_keys, [first_title_row_num]
  126. )
  127. generate_diff_data(
  128. second_title_row_num + 1,
  129. second.max_row,
  130. second,
  131. second_title_dict,
  132. first_keys,
  133. [second_title_row_num],
  134. )
  135. workbook.save(savename)
  136. move_file(savename, target_name)
  137. print(f"处理完成,目标文件夹{diff_dir}, {target_name}")
  138. def clean_with_path(dir_path):
  139. for file in os.listdir(dir_path):
  140. # 遍历output_path文件夹下文件,删除后缀为woff的字体文件
  141. if file.endswith(".xlsx"):
  142. os.remove(f"{dir_path}/{file}")
  143. def move_dir(old_path, new_path):
  144. filelist = os.listdir(old_path) # 列出该目录下的所有文件,listdir返回的文件列表是不包含路径的。
  145. print(f"old path is {old_path}, new path is {new_path}")
  146. for file in filelist:
  147. src = os.path.join(old_path, file)
  148. dst = os.path.join(new_path, file)
  149. print("src:", src)
  150. print("dst:", dst)
  151. shutil.move(src, dst)
  152. def move_file(old_path, new_path):
  153. shutil.move(old_path, new_path)
  154. @app.get("/python_api/test")
  155. def test():
  156. # move_file(cur_cache_path + "123.xlsx", diff_dir + "/" + uuid.uuid4().hex + ".xlsx")
  157. print("准备睡眠")
  158. time.sleep(5)
  159. print("执行完成")
  160. return {"code": 200, "message": "成功"}
  161. @app.get("/python_api/is_exist")
  162. def is_exist(file_name: str):
  163. print(f"查询file{file_name}是否存在")
  164. for dir in [f"{diff_dir}/", f"{shared_dir}/"]:
  165. file_path = os.path.join(dir, file_name)
  166. if os.path.exists(file_path):
  167. return {"code": 200, "exists": True, "filePath": f"{dir}" + file_name}
  168. else:
  169. return {"code": 200, "exists": False}
  170. @app.post("/python_api/upload_diff_file")
  171. def diff_file(file: UploadFile, background_tasks: BackgroundTasks):
  172. # clean_with_path("cur_cache")
  173. # clean_with_path("diff_cache")
  174. file_name = diff_dir + "/" + uuid.uuid4().hex + ".xlsx"
  175. background_tasks.add_task(deal_diff_data, file, file_name)
  176. print(f"开始处理{file_name}")
  177. return {"code": 200, "msg": "开始处理", "filePath": file_name}
  178. @app.post("/python_api/uploadfile")
  179. def create_upload_file(file: UploadFile = File(...)):
  180. print(f"开始处理{file.filename}")
  181. # clean_with_path(f"{shared_dir}/")
  182. contents = file.file.read()
  183. savename = f"{shared_dir}/" + file.filename
  184. if file.filename.endswith("xlsx"):
  185. savename = f"{shared_dir}/" + uuid.uuid4().hex + ".xlsx"
  186. with open(savename, "wb") as f:
  187. f.write(contents)
  188. # 读取excel表
  189. workbook = load_workbook(savename)
  190. # 获取指定的sheet
  191. sheet_names = workbook.sheetnames
  192. # if "脱贫户信息查询" not in sheet_names:
  193. # print("读取不到指定的sheet页")
  194. # return {"code": 500, "msg": "读取不到指定的sheet页--脱贫户信息查询"}
  195. sheet = workbook[sheet_names[0]]
  196. title_row_num = 0
  197. # 读取前5行,正常应该有字段名了
  198. row_range = sheet[1:5]
  199. for i, r in enumerate(row_range):
  200. for j, c in enumerate(r):
  201. # print(f"第{i + 1 }行,第{j}列,值:{c.value}")
  202. if "户主编号" == c.value or "户编号" == c.value:
  203. title_row_num = c.row
  204. if title_row_num == 0:
  205. print(f"{file.filename}文件 内容不合格")
  206. return {"code": 202, "msg": "不是可以解析的格式"}
  207. # 获取字段名对应的列
  208. title_dict = {}
  209. title_rows = sheet[title_row_num]
  210. # 遍历字段名所在行的所有单元格
  211. for title_cell in title_rows:
  212. x, y = coordinate_from_string(title_cell.coordinate)
  213. title_dict[title_cell.value] = x
  214. # 开始读取表格内容
  215. read_data(sheet, title_row_num + 1, sheet.max_row, title_dict)
  216. # 保存文档
  217. workbook.save(savename)
  218. print(f"处理完了{file.filename}文件")
  219. return {"code": 200, "msg": "分析完成,请点击下载查看分析结果", "filePath": savename}
  220. def calculate_age_from_id_number(id_number):
  221. """根据传入的身份证号码,提取出出生年月日,结合代码运行时的当天时间,返回当前年龄(周岁)"""
  222. age = 0
  223. if len(id_number) != 18:
  224. return age
  225. birthday = id_number[6:14]
  226. birthday = datetime.strptime(birthday, "%Y%m%d")
  227. today = datetime.now()
  228. age = today.year - birthday.year
  229. if today.month < birthday.month or (today.month == birthday.month and today.day < birthday.day):
  230. age -= 1
  231. return age
  232. def verify_age_region_for_education(education, age):
  233. """根据传入的学历和年龄判断该年龄是否符合该学历"""
  234. # 义务教育年龄段
  235. # if education == "" and (age >= 6 and age <= 15):
  236. # return False
  237. education_for_age = {
  238. "学龄前儿童": (0, 6),
  239. "学前教育": (3, 6),
  240. "小学": (6, 12),
  241. "七年级": (12, 13),
  242. "八年级": (13, 14),
  243. "九年级": (14, 15),
  244. "初中": (12, 15),
  245. "高中": (15, 18),
  246. "中专": (15, 18),
  247. "中职": (15, 18),
  248. "高职": (18, 22),
  249. "高专": (18, 22),
  250. "大专": (18, 20),
  251. "本科": (18, 22),
  252. "硕士": (22, 25),
  253. }
  254. for education_key in education_for_age.keys():
  255. if education_key in education:
  256. age_range = education_for_age[education_key]
  257. if age < age_range[0] or age > age_range[1]:
  258. return False
  259. break
  260. return True
  261. def read_data(ws, start_row, end_row, title_dict):
  262. # 监测对象致(返)贫风险非最新设计的风险类型
  263. for i in range(start_row, end_row):
  264. check_poverty_causes(ws, i, title_dict)
  265. check_identitycard_length(ws, i, title_dict)
  266. check_deformity(ws, i, title_dict)
  267. check_education_level(ws, i, title_dict)
  268. check_risk_type(ws, i, title_dict)
  269. check_assistance(ws, i, title_dict)
  270. # 筛查方法
  271. def check_poverty_causes(ws, row_num, title_dict):
  272. """筛查主要致贫原因是否合规"""
  273. main_reason = "主要致贫原因"
  274. if main_reason not in title_dict:
  275. return
  276. poverty_causes = ws[f"{title_dict[main_reason]}{row_num}"].value
  277. # 致贫原因列表
  278. imageTypeList = ["因病", "因学", "因残", "因自然灾害", "因意外事故", "因产业项目失败", "因务工就业不稳", "缺劳动力", "因房", "因水", "其他(填写备注)"]
  279. if poverty_causes not in imageTypeList:
  280. target = ws[f"{title_dict[main_reason]}{row_num}"]
  281. comment_and_fill_yellow_for(target, "21.监测对象致(返)贫风险非最新设计的风险类型")
  282. def check_identitycard_length(ws, row_num, title_dict):
  283. """筛查身份证号码是否合规"""
  284. info_number = "户主证件号码"
  285. if info_number not in title_dict:
  286. return
  287. identitycard = ws[f"{title_dict[info_number]}{row_num}"].value
  288. if len(identitycard) not in [15, 18, 20, 22]:
  289. target = ws[f"{title_dict[info_number]}{row_num}"]
  290. comment_and_fill_yellow_for(target, "31.监测对象家庭成员证件号码位数异常(证件号码非15、18、20、22位)")
  291. def check_deformity(ws, row_num, title_dict):
  292. """筛查是否不符合残疾劳动标准"""
  293. condition = "健康状况"
  294. skill = "劳动技能"
  295. if condition not in title_dict or skill not in title_dict:
  296. return
  297. condition_value = ws[f"{title_dict[condition]}{row_num}"].value
  298. skill_value = ws[f"{title_dict[skill]}{row_num}"].value
  299. if "残疾" in condition_value and skill_value == "弱劳动力或半劳动力":
  300. target = ws[f"{title_dict[skill]}{row_num}"]
  301. comment_and_fill_yellow_for(target, "35.一、二级(重度)肢体残疾脱贫人口有普通劳动能力")
  302. def check_education_level(ws, row_num, title_dict):
  303. """筛查在校生状况填写是否不符合年龄"""
  304. info_number = "证件号码"
  305. if info_number not in title_dict:
  306. return
  307. identitycard = ws[f"{title_dict[info_number]}{row_num}"].value
  308. if len(identitycard) != 18:
  309. return
  310. education_key = "文化程度"
  311. is_student_key = "在校生状况"
  312. if education_key not in title_dict or is_student_key not in title_dict:
  313. return
  314. education = ws[f"{title_dict[education_key]}{row_num}"].value
  315. is_student = ws[f"{title_dict[is_student_key]}{row_num}"].value
  316. if len(education) == 0 and len(is_student) == 0:
  317. target = ws[f"{title_dict[education_key]}{row_num}"]
  318. comment_and_fill_yellow_for(target, "文化程度,在校生状况均未填写")
  319. target_is_student = ws[f"{title_dict[education_key]}{row_num}"]
  320. comment_and_fill_yellow_for(target_is_student, "文化程度,在校生状况均未填写")
  321. return
  322. if len(is_student) == 0:
  323. return
  324. age = calculate_age_from_id_number(identitycard)
  325. age_is_reliable = verify_age_region_for_education(is_student, age)
  326. if not age_is_reliable:
  327. target = ws[f"{title_dict[is_student_key]}{row_num}"]
  328. comment_and_fill_yellow_for(target, "在校生状况与年龄不符")
  329. def check_risk_type(ws, row_num, title_dict):
  330. """筛查风险类型,家庭信息是否合规"""
  331. def get_item_values_for(items):
  332. result = []
  333. for item in items:
  334. if item not in title_dict:
  335. continue
  336. value = ws[f"{title_dict[item]}{row_num}"].value
  337. if value is not None and len(value) > 0:
  338. result.append((item, value))
  339. return result
  340. def get_key_for(i):
  341. key = f"致贫风险{i}"
  342. return key
  343. risks = []
  344. # 最新修改,只需要筛查致贫风险1了
  345. # for i in range(1, 6):
  346. key = get_key_for(1)
  347. if key not in title_dict:
  348. return
  349. risk = ws[f"{title_dict[key]}{row_num}"].value
  350. if risk is not None and len(risk) > 0:
  351. risks.append((risk, 1))
  352. # 定义:健康帮扶,"综合保障,社会帮扶,义务教育保障, 教育帮扶, 住房安全保障, 搬迁, 饮水安全保障, 产业帮扶, 就业帮扶, 金融帮扶, 公益岗位帮扶等常量
  353. HEALTH_SUPPORT = "健康帮扶"
  354. COMPREHENSIVE_GUARANTEE = "综合保障"
  355. SOCIAL_SUPPORT = "社会帮扶"
  356. OBLIGATORY_EDUCATION_GUARANTEE = "义务教育保障"
  357. EDUCATION_SUPPORT = "教育帮扶"
  358. HOUSING_SECURITY_GUARANTEE = "住房安全保障"
  359. RELOCATION = "搬迁"
  360. DRINKING_WATER_SECURITY_GUARANTEE = "饮水安全保障"
  361. INDUSTRY_SUPPORT = "产业帮扶"
  362. EMPLOYMENT_SUPPORT = "就业帮扶"
  363. FINANCIAL_SUPPORT = "金融帮扶"
  364. PUBLIC_WELFARE_SUPPORT = "公益岗位帮扶"
  365. must_selected_option = []
  366. forbinddens_option = []
  367. for risk, i in risks:
  368. if risk == "因病":
  369. must_selected_option = [HEALTH_SUPPORT, COMPREHENSIVE_GUARANTEE, SOCIAL_SUPPORT]
  370. forbinddens_option = [
  371. HOUSING_SECURITY_GUARANTEE,
  372. DRINKING_WATER_SECURITY_GUARANTEE,
  373. OBLIGATORY_EDUCATION_GUARANTEE,
  374. EDUCATION_SUPPORT,
  375. ]
  376. elif risk == "因学":
  377. must_selected_option = [OBLIGATORY_EDUCATION_GUARANTEE, EDUCATION_SUPPORT, SOCIAL_SUPPORT]
  378. forbinddens_option = [
  379. HOUSING_SECURITY_GUARANTEE,
  380. DRINKING_WATER_SECURITY_GUARANTEE,
  381. HEALTH_SUPPORT,
  382. ]
  383. elif risk == "因残":
  384. must_selected_option = [COMPREHENSIVE_GUARANTEE, SOCIAL_SUPPORT]
  385. forbinddens_option = [
  386. HOUSING_SECURITY_GUARANTEE,
  387. DRINKING_WATER_SECURITY_GUARANTEE,
  388. OBLIGATORY_EDUCATION_GUARANTEE,
  389. EDUCATION_SUPPORT,
  390. ]
  391. elif risk == "因产业项目失败" or risk == "因务工就业不稳" or risk == "缺劳动力":
  392. must_selected_option = []
  393. forbinddens_option = [
  394. HOUSING_SECURITY_GUARANTEE,
  395. DRINKING_WATER_SECURITY_GUARANTEE,
  396. OBLIGATORY_EDUCATION_GUARANTEE,
  397. EDUCATION_SUPPORT,
  398. HEALTH_SUPPORT,
  399. ]
  400. elif risk == "住房出现安全问题":
  401. must_selected_option = [HOUSING_SECURITY_GUARANTEE, RELOCATION]
  402. forbinddens_option = [
  403. DRINKING_WATER_SECURITY_GUARANTEE,
  404. HEALTH_SUPPORT,
  405. OBLIGATORY_EDUCATION_GUARANTEE,
  406. EDUCATION_SUPPORT,
  407. COMPREHENSIVE_GUARANTEE,
  408. ]
  409. elif risk == "家庭成员中有义务教育阶段适龄儿童少年失学辍学":
  410. must_selected_option = [OBLIGATORY_EDUCATION_GUARANTEE]
  411. forbinddens_option = [
  412. INDUSTRY_SUPPORT,
  413. EMPLOYMENT_SUPPORT,
  414. FINANCIAL_SUPPORT,
  415. PUBLIC_WELFARE_SUPPORT,
  416. HOUSING_SECURITY_GUARANTEE,
  417. DRINKING_WATER_SECURITY_GUARANTEE,
  418. HEALTH_SUPPORT,
  419. EDUCATION_SUPPORT,
  420. COMPREHENSIVE_GUARANTEE,
  421. SOCIAL_SUPPORT,
  422. ]
  423. elif risk == "饮水安全问题":
  424. must_selected_option = [DRINKING_WATER_SECURITY_GUARANTEE]
  425. forbinddens_option = [
  426. INDUSTRY_SUPPORT,
  427. EMPLOYMENT_SUPPORT,
  428. FINANCIAL_SUPPORT,
  429. PUBLIC_WELFARE_SUPPORT,
  430. HOUSING_SECURITY_GUARANTEE,
  431. OBLIGATORY_EDUCATION_GUARANTEE,
  432. HEALTH_SUPPORT,
  433. EDUCATION_SUPPORT,
  434. COMPREHENSIVE_GUARANTEE,
  435. SOCIAL_SUPPORT,
  436. ]
  437. elif risk == "家庭成员中有未参加城乡居民(职工)基本医疗保险":
  438. must_selected_option = [HEALTH_SUPPORT, SOCIAL_SUPPORT]
  439. forbinddens_option = [
  440. INDUSTRY_SUPPORT,
  441. EMPLOYMENT_SUPPORT,
  442. FINANCIAL_SUPPORT,
  443. PUBLIC_WELFARE_SUPPORT,
  444. HOUSING_SECURITY_GUARANTEE,
  445. OBLIGATORY_EDUCATION_GUARANTEE,
  446. EDUCATION_SUPPORT,
  447. COMPREHENSIVE_GUARANTEE,
  448. DRINKING_WATER_SECURITY_GUARANTEE,
  449. ]
  450. must_selected = get_item_values_for(must_selected_option)
  451. forbiddens = get_item_values_for(forbinddens_option)
  452. # 如果option为空,代表无要求
  453. must_selected_ok = len(must_selected) > 0 if len(must_selected_option) > 0 else True
  454. forbiddens_ok = len(forbiddens) == 0 if len(forbinddens_option) > 0 else True
  455. # print(f"risk 为{risk}的必选项当前值:{must_selected},禁选项当前值:{forbiddens}")
  456. if not must_selected_ok or not forbiddens_ok:
  457. target = ws[f"{title_dict[get_key_for(i)]}{row_num}"]
  458. if not must_selected_ok:
  459. comment_and_fill_yellow_for(target, f"{get_key_for(i)} 未选择必选项,必选项为{must_selected_option}")
  460. else:
  461. forbiddens_keys = [x for x, y in forbiddens]
  462. forbiddens_values = [y for x, y in forbiddens]
  463. comment_and_fill_yellow_for(
  464. target,
  465. f"{get_key_for(i)} 填写了禁选项{forbiddens_keys},填写的内容为{forbiddens_values}",
  466. )
  467. def check_assistance(ws, row_num, title_dict):
  468. """检查实施开发式帮扶填写状态是否有问题,如果填写了开发式帮扶,以下四项:产业帮扶、就业帮扶、金融帮扶、公益岗位帮扶有一项填写的是其他或者技能培训就不合规"""
  469. assistance = "实施开发式帮扶措施情况"
  470. assistance_value = ws[f"{title_dict[assistance]}{row_num}"].value
  471. if assistance_value and len(assistance_value) > 0:
  472. for type in ["产业帮扶", "就业帮扶", "金融帮扶", "公益岗位帮扶"]:
  473. target = ws[f"{title_dict[type]}{row_num}"].value
  474. for key in ["其他", "技能培训"]:
  475. if key in target:
  476. comment_and_fill_yellow_for(
  477. ws[f"{title_dict[type]}{row_num}"], f"实施开发式帮扶填写状态下,{type} 不允许选择 {key}"
  478. )
  479. return
  480. def comment_and_fill_yellow_for(target, comment):
  481. target.comment = Comment(text=comment, author="system")
  482. yellow_fill = PatternFill(patternType="solid", fgColor="FFFF00")
  483. target.fill = yellow_fill
  484. if __name__ == "__main__":
  485. uvicorn.run("data_verification:app", host="0.0.0.0", port=8500, reload=True)