data_models.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. # Copyright (c) Opendatalab. All rights reserved.
  2. """
  3. 数据模型定义
  4. """
  5. from typing import List, Optional
  6. class WeatherData:
  7. """气象数据模型"""
  8. def __init__(self):
  9. self.monitorAt: str = ""
  10. self.weather: str = ""
  11. self.temp: str = ""
  12. self.humidity: str = ""
  13. self.windSpeed: str = ""
  14. self.windDirection: str = ""
  15. self._auto_filled_weather: bool = False
  16. def to_dict(self):
  17. return {
  18. "monitorAt": self.monitorAt,
  19. "weather": self.weather,
  20. "temp": self.temp,
  21. "humidity": self.humidity,
  22. "windSpeed": self.windSpeed,
  23. "windDirection": self.windDirection
  24. }
  25. class NoiseData:
  26. """噪声数据模型"""
  27. def __init__(self):
  28. self.code: str = ""
  29. self.address: str = ""
  30. self.source: str = ""
  31. self.dayMonitorAt: str = ""
  32. self.dayMonitorValue: str = ""
  33. self.dayMonitorBackgroundValue: str = ""
  34. self.nightMonitorAt: str = ""
  35. self.nightMonitorValue: str = ""
  36. self.nightMonitorBackgroundValue: str = ""
  37. self.remark: str = ""
  38. def to_dict(self):
  39. return {
  40. "code": self.code,
  41. "address": self.address,
  42. "source": self.source,
  43. "dayMonitorAt": self.dayMonitorAt,
  44. "dayMonitorValue": self.dayMonitorValue,
  45. "dayMonitorBackgroundValue": self.dayMonitorBackgroundValue,
  46. "nightMonitorAt": self.nightMonitorAt,
  47. "nightMonitorValue": self.nightMonitorValue,
  48. "nightMonitorBackgroundValue": self.nightMonitorBackgroundValue,
  49. "remark": self.remark
  50. }
  51. class OperationalCondition:
  52. """工况信息数据模型(旧格式)"""
  53. def __init__(self):
  54. self.monitorAt: str = "" # 检测时间
  55. self.project: str = "" # 项目名称
  56. self.name: str = "" # 名称,如1#主变
  57. self.voltage: str = "" # 电压范围
  58. self.current: str = "" # 电流范围
  59. self.activePower: str = "" # 有功功率
  60. self.reactivePower: str = "" # 无功功率
  61. def to_dict(self):
  62. return {
  63. "monitorAt": self.monitorAt,
  64. "project": self.project,
  65. "name": self.name,
  66. "voltage": self.voltage,
  67. "current": self.current,
  68. "activePower": self.activePower,
  69. "reactivePower": self.reactivePower
  70. }
  71. class OperationalConditionV2:
  72. """工况信息数据模型(新格式:表1检测工况)"""
  73. def __init__(self):
  74. self.monitorAt: str = "" # 检测时间
  75. self.project: str = "" # 项目名称
  76. self.name: str = "" # 名称,如500kV 江黄Ⅰ线
  77. self.maxVoltage: str = "" # 电压最大值
  78. self.minVoltage: str = "" # 电压最小值
  79. self.maxCurrent: str = "" # 电流最大值
  80. self.minCurrent: str = "" # 电流最小值
  81. self.maxActivePower: str = "" # 有功功率最大值
  82. self.minActivePower: str = "" # 有功功率最小值
  83. self.maxReactivePower: str = "" # 无功功率最大值
  84. self.minReactivePower: str = "" # 无功功率最小值
  85. def to_dict(self):
  86. return {
  87. "monitorAt": self.monitorAt,
  88. "project": self.project,
  89. "name": self.name,
  90. "maxVoltage": self.maxVoltage,
  91. "minVoltage": self.minVoltage,
  92. "maxCurrent": self.maxCurrent,
  93. "minCurrent": self.minCurrent,
  94. "maxActivePower": self.maxActivePower,
  95. "minActivePower": self.minActivePower,
  96. "maxReactivePower": self.maxReactivePower,
  97. "minReactivePower": self.minReactivePower
  98. }
  99. class NoiseDetectionRecord:
  100. """噪声检测记录数据模型"""
  101. def __init__(self):
  102. self.project: str = ""
  103. self.standardReferences: str = ""
  104. self.soundLevelMeterMode: str = ""
  105. self.soundCalibratorMode: str = ""
  106. self.calibrationValueBefore: str = ""
  107. self.calibrationValueAfter: str = ""
  108. self.weather: List[WeatherData] = []
  109. self.noise: List[NoiseData] = []
  110. self.operationalConditions: List[OperationalCondition] = []
  111. def to_dict(self):
  112. return {
  113. "project": self.project,
  114. "standardReferences": self.standardReferences,
  115. "soundLevelMeterMode": self.soundLevelMeterMode,
  116. "soundCalibratorMode": self.soundCalibratorMode,
  117. "calibrationValueBefore": self.calibrationValueBefore,
  118. "calibrationValueAfter": self.calibrationValueAfter,
  119. "weather": [w.to_dict() for w in self.weather],
  120. "noise": [n.to_dict() for n in self.noise],
  121. "operationalConditions": [oc.to_dict() for oc in self.operationalConditions]
  122. }
  123. class ElectromagneticWeatherData:
  124. """电磁检测气象数据模型"""
  125. def __init__(self):
  126. self.weather: str = ""
  127. self.temp: str = ""
  128. self.humidity: str = ""
  129. self.windSpeed: str = ""
  130. self.windDirection: str = ""
  131. def to_dict(self):
  132. return {
  133. "weather": self.weather,
  134. "temp": self.temp,
  135. "humidity": self.humidity,
  136. "windSpeed": self.windSpeed,
  137. "windDirection": self.windDirection
  138. }
  139. class ElectromagneticData:
  140. """电磁数据模型"""
  141. def __init__(self):
  142. self.code: str = ""
  143. self.address: str = ""
  144. self.height: str = ""
  145. self.monitorAt: str = ""
  146. self.powerFrequencyEFieldStrength1: str = ""
  147. self.powerFrequencyEFieldStrength2: str = ""
  148. self.powerFrequencyEFieldStrength3: str = ""
  149. self.powerFrequencyEFieldStrength4: str = ""
  150. self.powerFrequencyEFieldStrength5: str = ""
  151. self.avgPowerFrequencyEFieldStrength: str = ""
  152. self.powerFrequencyMagneticDensity1: str = ""
  153. self.powerFrequencyMagneticDensity2: str = ""
  154. self.powerFrequencyMagneticDensity3: str = ""
  155. self.powerFrequencyMagneticDensity4: str = ""
  156. self.powerFrequencyMagneticDensity5: str = ""
  157. self.avgPowerFrequencyMagneticDensity: str = ""
  158. def to_dict(self):
  159. return {
  160. "code": self.code,
  161. "address": self.address,
  162. "height": self.height,
  163. "monitorAt": self.monitorAt,
  164. "powerFrequencyEFieldStrength1": self.powerFrequencyEFieldStrength1,
  165. "powerFrequencyEFieldStrength2": self.powerFrequencyEFieldStrength2,
  166. "powerFrequencyEFieldStrength3": self.powerFrequencyEFieldStrength3,
  167. "powerFrequencyEFieldStrength4": self.powerFrequencyEFieldStrength4,
  168. "powerFrequencyEFieldStrength5": self.powerFrequencyEFieldStrength5,
  169. "avgPowerFrequencyEFieldStrength": self.avgPowerFrequencyEFieldStrength,
  170. "powerFrequencyMagneticDensity1": self.powerFrequencyMagneticDensity1,
  171. "powerFrequencyMagneticDensity2": self.powerFrequencyMagneticDensity2,
  172. "powerFrequencyMagneticDensity3": self.powerFrequencyMagneticDensity3,
  173. "powerFrequencyMagneticDensity4": self.powerFrequencyMagneticDensity4,
  174. "powerFrequencyMagneticDensity5": self.powerFrequencyMagneticDensity5,
  175. "avgPowerFrequencyMagneticDensity": self.avgPowerFrequencyMagneticDensity
  176. }
  177. class ElectromagneticDetectionRecord:
  178. """电磁检测记录数据模型"""
  179. def __init__(self):
  180. self.project: str = ""
  181. self.standardReferences: str = ""
  182. self.deviceName: str = ""
  183. self.deviceMode: str = ""
  184. self.deviceCode: str = ""
  185. self.monitorHeight: str = ""
  186. self.weather: ElectromagneticWeatherData = ElectromagneticWeatherData()
  187. self.electricMagnetic: List[ElectromagneticData] = []
  188. def to_dict(self):
  189. return {
  190. "project": self.project,
  191. "standardReferences": self.standardReferences,
  192. "deviceName": self.deviceName,
  193. "deviceMode": self.deviceMode,
  194. "deviceCode": self.deviceCode,
  195. "monitorHeight": self.monitorHeight,
  196. "weather": self.weather.to_dict(),
  197. "electricMagnetic": [em.to_dict() for em in self.electricMagnetic]
  198. }
  199. class InvestmentItem:
  200. """投资项目数据模型"""
  201. def __init__(self):
  202. self.no: str = "" # 序号
  203. self.name: str = "" # 工程或费用名称
  204. self.level: str = "" # 明细等级
  205. self.constructionScaleOverheadLine: str = "" # 建设规模-架空线(仅可研批复)
  206. self.constructionScaleBay: str = "" # 建设规模-间隔(仅可研批复)
  207. self.constructionScaleSubstation: str = "" # 建设规模-变电(仅可研批复)
  208. self.constructionScaleOpticalCable: str = "" # 建设规模-光缆(仅可研批复)
  209. self.staticInvestment: str = "" # 静态投资(元)
  210. self.dynamicInvestment: str = "" # 动态投资(元)
  211. # 新增费用字段(仅可研批复)
  212. self.constructionProjectCost: str = "" # 建筑工程费(元)
  213. self.equipmentPurchaseCost: str = "" # 设备购置费(元)
  214. self.installationProjectCost: str = "" # 安装工程费(元)
  215. self.otherExpenses: str = "" # 其他费用-合计(元)
  216. def to_dict(self, include_construction_scale: bool = False, include_cost_breakdown: bool = False):
  217. """
  218. 转换为字典
  219. Args:
  220. include_construction_scale: 是否包含建设规模字段(可研批复需要)
  221. include_cost_breakdown: 是否包含费用明细字段(可研批复需要)
  222. """
  223. result = {
  224. "No": self.no,
  225. "name": self.name,
  226. "Level": self.level,
  227. "staticInvestment": self.staticInvestment,
  228. "dynamicInvestment": self.dynamicInvestment
  229. }
  230. # 如果需要建设规模字段,添加到输出(用于可研批复)
  231. if include_construction_scale:
  232. result["constructionScaleOverheadLine"] = self.constructionScaleOverheadLine
  233. result["constructionScaleBay"] = self.constructionScaleBay
  234. result["constructionScaleSubstation"] = self.constructionScaleSubstation
  235. result["constructionScaleOpticalCable"] = self.constructionScaleOpticalCable
  236. # 如果需要费用明细字段,添加到输出(用于可研批复)
  237. if include_cost_breakdown:
  238. result["constructionProjectCost"] = self.constructionProjectCost
  239. result["equipmentPurchaseCost"] = self.equipmentPurchaseCost
  240. result["installationProjectCost"] = self.installationProjectCost
  241. result["otherExpenses"] = self.otherExpenses
  242. return result
  243. class FeasibilityApprovalInvestment:
  244. """可研批复投资估算数据模型
  245. 返回结构与 designReview 保持一致,包含建设规模字段
  246. 三层嵌套结构:
  247. - Level 0: 顶层大类(如"山西晋城周村220千伏输变电工程")
  248. - Level 1: 二级分类(如"变电工程"、"线路工程"),有自己的 items
  249. - Level 2: 具体项目(如"周村220千伏变电站新建工程")
  250. 项目信息(可选,用于 safetyFsApproval 类型):
  251. - projectName: 工程(项目)名称
  252. - projectUnit: 项目单位
  253. - designUnit: 设计单位
  254. """
  255. def __init__(self):
  256. self.items: List[InvestmentItem] = []
  257. # 项目基本信息(safetyFsApproval 专用)
  258. self.projectName: Optional[str] = None
  259. self.projectUnit: Optional[str] = None
  260. self.designUnit: Optional[str] = None
  261. def to_dict(self):
  262. """转换为嵌套结构,与 designReview 保持一致
  263. Level="1" 的项目作为顶层大类(Level: 0)
  264. Level="2" 的项目作为二级分类(Level: 1),有自己的 items
  265. Level="3" 的项目作为具体项目(Level: 2),放入二级分类的 items
  266. Level="0" 的项目(合计)跳过
  267. 特殊处理:如果表格没有 Level=1 的顶层大类(如湖北省格式),
  268. 自动创建一个虚拟顶层大类来包含所有 Level=2 的项目
  269. """
  270. if not self.items:
  271. return []
  272. # 检查是否有 Level=1 的顶层大类
  273. has_level_1 = any(item.level == "1" for item in self.items)
  274. result = []
  275. current_top_category = None # Level 0 顶层大类
  276. current_sub_category = None # Level 1 二级分类
  277. # 如果没有 Level=1 的顶层大类,创建一个虚拟的
  278. if not has_level_1:
  279. current_top_category = {
  280. "name": "项目总表",
  281. "Level": 0,
  282. "constructionScaleSubstation": "",
  283. "constructionScaleBay": "",
  284. "constructionScaleOverheadLine": "",
  285. "constructionScaleOpticalCable": "",
  286. "staticInvestment": "",
  287. "dynamicInvestment": "",
  288. "constructionProjectCost": "",
  289. "equipmentPurchaseCost": "",
  290. "installationProjectCost": "",
  291. "otherExpenses": "",
  292. "items": []
  293. }
  294. for item in self.items:
  295. if item.level == "1":
  296. # 顶层大类(如"山西晋城周村220千伏输变电工程")
  297. # 保存之前的二级分类和顶层大类
  298. if current_sub_category is not None and current_top_category is not None:
  299. current_top_category["items"].append(current_sub_category)
  300. current_sub_category = None
  301. if current_top_category is not None:
  302. result.append(current_top_category)
  303. current_top_category = {
  304. "name": item.name,
  305. "Level": 0,
  306. "constructionScaleSubstation": item.constructionScaleSubstation or "",
  307. "constructionScaleBay": item.constructionScaleBay or "",
  308. "constructionScaleOverheadLine": item.constructionScaleOverheadLine or "",
  309. "constructionScaleOpticalCable": item.constructionScaleOpticalCable or "",
  310. "staticInvestment": self._parse_number(item.staticInvestment),
  311. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  312. "constructionProjectCost": self._parse_number(item.constructionProjectCost),
  313. "equipmentPurchaseCost": self._parse_number(item.equipmentPurchaseCost),
  314. "installationProjectCost": self._parse_number(item.installationProjectCost),
  315. "otherExpenses": self._parse_number(item.otherExpenses),
  316. "items": []
  317. }
  318. elif item.level == "2" and current_top_category is not None:
  319. # 二级分类(如"变电工程"、"线路工程")
  320. # 保存之前的二级分类
  321. if current_sub_category is not None:
  322. current_top_category["items"].append(current_sub_category)
  323. current_sub_category = {
  324. "No": self._parse_no(item.no),
  325. "name": item.name,
  326. "Level": 1,
  327. "constructionScaleSubstation": item.constructionScaleSubstation or "",
  328. "constructionScaleBay": item.constructionScaleBay or "",
  329. "constructionScaleOverheadLine": item.constructionScaleOverheadLine or "",
  330. "constructionScaleOpticalCable": item.constructionScaleOpticalCable or "",
  331. "staticInvestment": self._parse_number(item.staticInvestment),
  332. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  333. "constructionProjectCost": self._parse_number(item.constructionProjectCost),
  334. "equipmentPurchaseCost": self._parse_number(item.equipmentPurchaseCost),
  335. "installationProjectCost": self._parse_number(item.installationProjectCost),
  336. "otherExpenses": self._parse_number(item.otherExpenses),
  337. "items": []
  338. }
  339. elif item.level == "3" and current_sub_category is not None:
  340. # 具体项目(如"周村220千伏变电站新建工程")
  341. current_sub_category["items"].append({
  342. "No": self._parse_no(item.no),
  343. "name": item.name,
  344. "Level": 2,
  345. "constructionScaleSubstation": item.constructionScaleSubstation or "",
  346. "constructionScaleBay": item.constructionScaleBay or "",
  347. "constructionScaleOverheadLine": item.constructionScaleOverheadLine or "",
  348. "constructionScaleOpticalCable": item.constructionScaleOpticalCable or "",
  349. "staticInvestment": self._parse_number(item.staticInvestment),
  350. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  351. "constructionProjectCost": self._parse_number(item.constructionProjectCost),
  352. "equipmentPurchaseCost": self._parse_number(item.equipmentPurchaseCost),
  353. "installationProjectCost": self._parse_number(item.installationProjectCost),
  354. "otherExpenses": self._parse_number(item.otherExpenses),
  355. })
  356. elif item.level == "0":
  357. # 合计行 - 跳过
  358. if current_sub_category is not None and current_top_category is not None:
  359. current_top_category["items"].append(current_sub_category)
  360. current_sub_category = None
  361. if current_top_category is not None:
  362. result.append(current_top_category)
  363. current_top_category = None
  364. # 添加最后的分类
  365. if current_sub_category is not None and current_top_category is not None:
  366. current_top_category["items"].append(current_sub_category)
  367. if current_top_category is not None:
  368. result.append(current_top_category)
  369. # 如果有项目信息,返回包含项目信息的字典;否则直接返回数据列表
  370. if self.projectName or self.projectUnit or self.designUnit:
  371. return {
  372. "projectInfo": {
  373. "projectName": self.projectName or "",
  374. "projectUnit": self.projectUnit or "",
  375. "designUnit": self.designUnit or ""
  376. },
  377. "data": result
  378. }
  379. return result
  380. @staticmethod
  381. def _parse_number(value: str) -> str:
  382. """将数字字符串格式化,保留原始精度"""
  383. if not value or not value.strip():
  384. return "0"
  385. return value.strip()
  386. @staticmethod
  387. def _parse_no(value: str) -> int:
  388. if not value or not value.strip():
  389. return 0
  390. try:
  391. return int(value.strip())
  392. except ValueError:
  393. return 0
  394. class FeasibilityReviewInvestment:
  395. """可研评审投资估算数据模型
  396. 返回结构与 designReview 保持一致,不包含建设规模字段
  397. 三层嵌套结构:
  398. - Level 0: 顶层大类
  399. - Level 1: 二级分类,有自己的 items
  400. - Level 2: 具体项目
  401. """
  402. def __init__(self):
  403. self.items: List[InvestmentItem] = []
  404. def to_dict(self):
  405. """转换为嵌套结构,与 designReview 保持一致
  406. Level="1" 的项目作为顶层大类(Level: 0)
  407. Level="2" 的项目作为二级分类(Level: 1),有自己的 items
  408. Level="3" 的项目作为具体项目(Level: 2),放入二级分类的 items
  409. Level="0" 的项目(合计)跳过
  410. """
  411. if not self.items:
  412. return []
  413. result = []
  414. current_top_category = None
  415. current_sub_category = None
  416. for item in self.items:
  417. if item.level == "1":
  418. # 顶层大类
  419. if current_sub_category is not None and current_top_category is not None:
  420. current_top_category["items"].append(current_sub_category)
  421. current_sub_category = None
  422. if current_top_category is not None:
  423. result.append(current_top_category)
  424. current_top_category = {
  425. "name": item.name,
  426. "Level": 0,
  427. "staticInvestment": self._parse_number(item.staticInvestment),
  428. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  429. "items": []
  430. }
  431. elif item.level == "2" and current_top_category is not None:
  432. # 二级分类
  433. if current_sub_category is not None:
  434. current_top_category["items"].append(current_sub_category)
  435. current_sub_category = {
  436. "No": self._parse_no(item.no),
  437. "name": item.name,
  438. "Level": 1,
  439. "staticInvestment": self._parse_number(item.staticInvestment),
  440. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  441. "items": []
  442. }
  443. elif item.level == "3" and current_sub_category is not None:
  444. # 具体项目
  445. current_sub_category["items"].append({
  446. "No": self._parse_no(item.no),
  447. "name": item.name,
  448. "Level": 2,
  449. "staticInvestment": self._parse_number(item.staticInvestment),
  450. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  451. })
  452. elif item.level == "0":
  453. # 合计行 - 跳过
  454. if current_sub_category is not None and current_top_category is not None:
  455. current_top_category["items"].append(current_sub_category)
  456. current_sub_category = None
  457. if current_top_category is not None:
  458. result.append(current_top_category)
  459. current_top_category = None
  460. # 添加最后的分类
  461. if current_sub_category is not None and current_top_category is not None:
  462. current_top_category["items"].append(current_sub_category)
  463. if current_top_category is not None:
  464. result.append(current_top_category)
  465. return result
  466. @staticmethod
  467. def _parse_number(value: str) -> str:
  468. """将数字字符串格式化,保留原始精度"""
  469. if not value or not value.strip():
  470. return "0"
  471. return value.strip()
  472. @staticmethod
  473. def _parse_no(value: str) -> int:
  474. if not value or not value.strip():
  475. return 0
  476. try:
  477. return int(value.strip())
  478. except ValueError:
  479. return 0
  480. class PreliminaryApprovalInvestment:
  481. """初设批复概算投资数据模型
  482. 返回结构与 designReview 保持一致:
  483. [{
  484. "name": str, # 大类名称(如"变电工程"、"线路工程")
  485. "Level": 0, # 大类层级
  486. "staticInvestment": float, # 静态投资总计
  487. "dynamicInvestment": float, # 动态投资总计
  488. "items": [ # 子项列表
  489. {
  490. "No": int, # 序号
  491. "name": str, # 工程名称
  492. "Level": 1, # 子项层级
  493. "staticInvestment": float, # 静态投资
  494. "dynamicInvestment": float, # 动态投资
  495. },
  496. ...
  497. ]
  498. }, ...]
  499. """
  500. def __init__(self):
  501. self.items: List[InvestmentItem] = []
  502. def to_dict(self):
  503. """转换为嵌套结构,与 designReview 保持一致
  504. Level="1" 的项目作为大类(变电工程、线路工程等)
  505. Level="2" 的项目作为子项
  506. Level="0" 的项目(合计)跳过,不包含在输出中
  507. """
  508. if not self.items:
  509. return []
  510. result = []
  511. current_category = None
  512. for item in self.items:
  513. if item.level == "1":
  514. # 大类项目 - 创建新的类别
  515. if current_category is not None:
  516. result.append(current_category)
  517. current_category = {
  518. "name": item.name,
  519. "Level": 0,
  520. "staticInvestment": self._parse_number(item.staticInvestment),
  521. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  522. "items": []
  523. }
  524. elif item.level == "2" and current_category is not None:
  525. # 子项目 - 添加到当前类别的 items 中
  526. current_category["items"].append({
  527. "No": self._parse_no(item.no),
  528. "name": item.name,
  529. "Level": 1,
  530. "staticInvestment": self._parse_number(item.staticInvestment),
  531. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  532. })
  533. elif item.level == "0":
  534. # 合计行 - 跳过,不包含在输出中
  535. # 先保存当前类别
  536. if current_category is not None:
  537. result.append(current_category)
  538. current_category = None
  539. # 添加最后一个类别
  540. if current_category is not None:
  541. result.append(current_category)
  542. return result
  543. @staticmethod
  544. def _parse_number(value: str) -> str:
  545. """将数字字符串格式化,保留原始精度"""
  546. if not value or not value.strip():
  547. return "0"
  548. return value.strip()
  549. @staticmethod
  550. def _parse_no(value: str) -> int:
  551. """将序号转换为整数"""
  552. if not value or not value.strip():
  553. return 0
  554. try:
  555. return int(value.strip())
  556. except ValueError:
  557. return 0
  558. class FinalAccountItem:
  559. """决算报告单项工程投资完成情况数据模型"""
  560. def __init__(self):
  561. self.no: int = 0 # 序号(项目序号)
  562. self.name: str = "" # 项目名称(审计内容)
  563. self.feeName: str = "" # 费用项目(建筑安装工程、设备购置、其他费用)
  564. self.estimatedCost: str = "" # 概算金额
  565. self.approvedFinalAccountExcludingVat: str = "" # 决算金额审定不含税
  566. self.vatAmount: str = "" # 增值税额
  567. self.costVariance: str = "" # 超节支金额
  568. self.varianceRate: str = "" # 超节支率
  569. def to_dict(self, include_project_info: bool = True):
  570. """转换为字典
  571. Args:
  572. include_project_info: 是否包含项目序号和名称(分组后不需要)
  573. """
  574. result = {
  575. "feeName": self.feeName,
  576. "estimatedCost": self.estimatedCost,
  577. "approvedFinalAccountExcludingVat": self.approvedFinalAccountExcludingVat,
  578. "vatAmount": self.vatAmount,
  579. "costVariance": self.costVariance,
  580. "varianceRate": self.varianceRate
  581. }
  582. if include_project_info:
  583. result["No"] = self.no
  584. result["name"] = self.name
  585. return result
  586. class FinalAccountRecord:
  587. """决算报告记录数据模型
  588. 返回结构:按项目分组的嵌套数组
  589. [{
  590. "No": int, # 序号(项目序号,如1、2、3、4)
  591. "name": str, # 项目名称(如"周村220kV输变电工程变电站新建工程")
  592. "items": [{ # 费用明细列表
  593. "feeName": str, # 费用项目(如"建筑安装工程"、"设备购置"、"其他费用")
  594. "estimatedCost": str, # 概算金额
  595. "approvedFinalAccountExcludingVat": str, # 决算金额审定不含税
  596. "vatAmount": str, # 增值税额
  597. "costVariance": str, # 超节支金额
  598. "varianceRate": str, # 超节支率
  599. }, ...]
  600. }, ...]
  601. """
  602. def __init__(self):
  603. self.items: List[FinalAccountItem] = []
  604. def to_dict(self):
  605. """转换为按项目分组的嵌套结构"""
  606. from collections import OrderedDict
  607. # 按项目序号分组
  608. grouped: OrderedDict[int, dict] = OrderedDict()
  609. for item in self.items:
  610. if item.no not in grouped:
  611. grouped[item.no] = {
  612. "No": item.no,
  613. "name": item.name,
  614. "items": []
  615. }
  616. # 添加费用明细(不包含项目序号和名称)
  617. grouped[item.no]["items"].append(item.to_dict(include_project_info=False))
  618. return list(grouped.values())