data_models.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. # Copyright (c) Opendatalab. All rights reserved.
  2. """
  3. 数据模型定义
  4. """
  5. from typing import List
  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. else:
  380. return result
  381. @staticmethod
  382. def _parse_number(value: str) -> str:
  383. """将数字字符串格式化,保留原始精度"""
  384. if not value or not value.strip():
  385. return "0"
  386. return value.strip()
  387. @staticmethod
  388. def _parse_no(value: str) -> int:
  389. if not value or not value.strip():
  390. return 0
  391. try:
  392. return int(value.strip())
  393. except ValueError:
  394. return 0
  395. class FeasibilityReviewInvestment:
  396. """可研评审投资估算数据模型
  397. 返回结构与 designReview 保持一致,不包含建设规模字段
  398. 三层嵌套结构:
  399. - Level 0: 顶层大类
  400. - Level 1: 二级分类,有自己的 items
  401. - Level 2: 具体项目
  402. """
  403. def __init__(self):
  404. self.items: List[InvestmentItem] = []
  405. def to_dict(self):
  406. """转换为嵌套结构,与 designReview 保持一致
  407. Level="1" 的项目作为顶层大类(Level: 0)
  408. Level="2" 的项目作为二级分类(Level: 1),有自己的 items
  409. Level="3" 的项目作为具体项目(Level: 2),放入二级分类的 items
  410. Level="0" 的项目(合计)跳过
  411. """
  412. if not self.items:
  413. return []
  414. result = []
  415. current_top_category = None
  416. current_sub_category = None
  417. for item in self.items:
  418. if item.level == "1":
  419. # 顶层大类
  420. if current_sub_category is not None and current_top_category is not None:
  421. current_top_category["items"].append(current_sub_category)
  422. current_sub_category = None
  423. if current_top_category is not None:
  424. result.append(current_top_category)
  425. current_top_category = {
  426. "name": item.name,
  427. "Level": 0,
  428. "staticInvestment": self._parse_number(item.staticInvestment),
  429. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  430. "items": []
  431. }
  432. elif item.level == "2" and current_top_category is not None:
  433. # 二级分类
  434. if current_sub_category is not None:
  435. current_top_category["items"].append(current_sub_category)
  436. current_sub_category = {
  437. "No": self._parse_no(item.no),
  438. "name": item.name,
  439. "Level": 1,
  440. "staticInvestment": self._parse_number(item.staticInvestment),
  441. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  442. "items": []
  443. }
  444. elif item.level == "3" and current_sub_category is not None:
  445. # 具体项目
  446. current_sub_category["items"].append({
  447. "No": self._parse_no(item.no),
  448. "name": item.name,
  449. "Level": 2,
  450. "staticInvestment": self._parse_number(item.staticInvestment),
  451. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  452. })
  453. elif item.level == "0":
  454. # 合计行 - 跳过
  455. if current_sub_category is not None and current_top_category is not None:
  456. current_top_category["items"].append(current_sub_category)
  457. current_sub_category = None
  458. if current_top_category is not None:
  459. result.append(current_top_category)
  460. current_top_category = None
  461. # 添加最后的分类
  462. if current_sub_category is not None and current_top_category is not None:
  463. current_top_category["items"].append(current_sub_category)
  464. if current_top_category is not None:
  465. result.append(current_top_category)
  466. return result
  467. @staticmethod
  468. def _parse_number(value: str) -> str:
  469. """将数字字符串格式化,保留原始精度"""
  470. if not value or not value.strip():
  471. return "0"
  472. return value.strip()
  473. @staticmethod
  474. def _parse_no(value: str) -> int:
  475. if not value or not value.strip():
  476. return 0
  477. try:
  478. return int(value.strip())
  479. except ValueError:
  480. return 0
  481. class PreliminaryApprovalInvestment:
  482. """初设批复概算投资数据模型
  483. 返回结构与 designReview 保持一致:
  484. [{
  485. "name": str, # 大类名称(如"变电工程"、"线路工程")
  486. "Level": 0, # 大类层级
  487. "staticInvestment": float, # 静态投资总计
  488. "dynamicInvestment": float, # 动态投资总计
  489. "items": [ # 子项列表
  490. {
  491. "No": int, # 序号
  492. "name": str, # 工程名称
  493. "Level": 1, # 子项层级
  494. "staticInvestment": float, # 静态投资
  495. "dynamicInvestment": float, # 动态投资
  496. },
  497. ...
  498. ]
  499. }, ...]
  500. """
  501. def __init__(self):
  502. self.items: List[InvestmentItem] = []
  503. def to_dict(self):
  504. """转换为嵌套结构,与 designReview 保持一致
  505. Level="1" 的项目作为大类(变电工程、线路工程等)
  506. Level="2" 的项目作为子项
  507. Level="0" 的项目(合计)跳过,不包含在输出中
  508. """
  509. if not self.items:
  510. return []
  511. result = []
  512. current_category = None
  513. for item in self.items:
  514. if item.level == "1":
  515. # 大类项目 - 创建新的类别
  516. if current_category is not None:
  517. result.append(current_category)
  518. current_category = {
  519. "name": item.name,
  520. "Level": 0,
  521. "staticInvestment": self._parse_number(item.staticInvestment),
  522. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  523. "items": []
  524. }
  525. elif item.level == "2" and current_category is not None:
  526. # 子项目 - 添加到当前类别的 items 中
  527. current_category["items"].append({
  528. "No": self._parse_no(item.no),
  529. "name": item.name,
  530. "Level": 1,
  531. "staticInvestment": self._parse_number(item.staticInvestment),
  532. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  533. })
  534. elif item.level == "0":
  535. # 合计行 - 跳过,不包含在输出中
  536. # 先保存当前类别
  537. if current_category is not None:
  538. result.append(current_category)
  539. current_category = None
  540. # 添加最后一个类别
  541. if current_category is not None:
  542. result.append(current_category)
  543. return result
  544. @staticmethod
  545. def _parse_number(value: str) -> str:
  546. """将数字字符串格式化,保留原始精度"""
  547. if not value or not value.strip():
  548. return "0"
  549. return value.strip()
  550. @staticmethod
  551. def _parse_no(value: str) -> int:
  552. """将序号转换为整数"""
  553. if not value or not value.strip():
  554. return 0
  555. try:
  556. return int(value.strip())
  557. except ValueError:
  558. return 0
  559. class FinalAccountItem:
  560. """决算报告单项工程投资完成情况数据模型"""
  561. def __init__(self):
  562. self.no: int = 0 # 序号(项目序号)
  563. self.name: str = "" # 项目名称(审计内容)
  564. self.feeName: str = "" # 费用项目(建筑安装工程、设备购置、其他费用)
  565. self.estimatedCost: str = "" # 概算金额
  566. self.approvedFinalAccountExcludingVat: str = "" # 决算金额审定不含税
  567. self.vatAmount: str = "" # 增值税额
  568. self.costVariance: str = "" # 超节支金额
  569. self.varianceRate: str = "" # 超节支率
  570. def to_dict(self, include_project_info: bool = True):
  571. """转换为字典
  572. Args:
  573. include_project_info: 是否包含项目序号和名称(分组后不需要)
  574. """
  575. result = {
  576. "feeName": self.feeName,
  577. "estimatedCost": self.estimatedCost,
  578. "approvedFinalAccountExcludingVat": self.approvedFinalAccountExcludingVat,
  579. "vatAmount": self.vatAmount,
  580. "costVariance": self.costVariance,
  581. "varianceRate": self.varianceRate
  582. }
  583. if include_project_info:
  584. result["No"] = self.no
  585. result["name"] = self.name
  586. return result
  587. class FinalAccountRecord:
  588. """决算报告记录数据模型
  589. 返回结构:按项目分组的嵌套数组
  590. [{
  591. "No": int, # 序号(项目序号,如1、2、3、4)
  592. "name": str, # 项目名称(如"周村220kV输变电工程变电站新建工程")
  593. "items": [{ # 费用明细列表
  594. "feeName": str, # 费用项目(如"建筑安装工程"、"设备购置"、"其他费用")
  595. "estimatedCost": str, # 概算金额
  596. "approvedFinalAccountExcludingVat": str, # 决算金额审定不含税
  597. "vatAmount": str, # 增值税额
  598. "costVariance": str, # 超节支金额
  599. "varianceRate": str, # 超节支率
  600. }, ...]
  601. }, ...]
  602. """
  603. def __init__(self):
  604. self.items: List[FinalAccountItem] = []
  605. def to_dict(self):
  606. """转换为按项目分组的嵌套结构"""
  607. from collections import OrderedDict
  608. # 按项目序号分组
  609. grouped: OrderedDict[int, dict] = OrderedDict()
  610. for item in self.items:
  611. if item.no not in grouped:
  612. grouped[item.no] = {
  613. "No": item.no,
  614. "name": item.name,
  615. "items": []
  616. }
  617. # 添加费用明细(不包含项目序号和名称)
  618. grouped[item.no]["items"].append(item.to_dict(include_project_info=False))
  619. return list(grouped.values())