data_models.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  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. """
  251. def __init__(self):
  252. self.items: List[InvestmentItem] = []
  253. def to_dict(self):
  254. """转换为嵌套结构,与 designReview 保持一致
  255. Level="1" 的项目作为顶层大类(Level: 0)
  256. Level="2" 的项目作为二级分类(Level: 1),有自己的 items
  257. Level="3" 的项目作为具体项目(Level: 2),放入二级分类的 items
  258. Level="0" 的项目(合计)跳过
  259. """
  260. if not self.items:
  261. return []
  262. result = []
  263. current_top_category = None # Level 0 顶层大类
  264. current_sub_category = None # Level 1 二级分类
  265. for item in self.items:
  266. if item.level == "1":
  267. # 顶层大类(如"山西晋城周村220千伏输变电工程")
  268. # 保存之前的二级分类和顶层大类
  269. if current_sub_category is not None and current_top_category is not None:
  270. current_top_category["items"].append(current_sub_category)
  271. current_sub_category = None
  272. if current_top_category is not None:
  273. result.append(current_top_category)
  274. current_top_category = {
  275. "name": item.name,
  276. "Level": 0,
  277. "constructionScaleSubstation": item.constructionScaleSubstation or "",
  278. "constructionScaleBay": item.constructionScaleBay or "",
  279. "constructionScaleOverheadLine": item.constructionScaleOverheadLine or "",
  280. "constructionScaleOpticalCable": item.constructionScaleOpticalCable or "",
  281. "staticInvestment": self._parse_number(item.staticInvestment),
  282. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  283. "constructionProjectCost": self._parse_number(item.constructionProjectCost),
  284. "equipmentPurchaseCost": self._parse_number(item.equipmentPurchaseCost),
  285. "installationProjectCost": self._parse_number(item.installationProjectCost),
  286. "otherExpenses": self._parse_number(item.otherExpenses),
  287. "items": []
  288. }
  289. elif item.level == "2" and current_top_category is not None:
  290. # 二级分类(如"变电工程"、"线路工程")
  291. # 保存之前的二级分类
  292. if current_sub_category is not None:
  293. current_top_category["items"].append(current_sub_category)
  294. current_sub_category = {
  295. "No": self._parse_no(item.no),
  296. "name": item.name,
  297. "Level": 1,
  298. "constructionScaleSubstation": item.constructionScaleSubstation or "",
  299. "constructionScaleBay": item.constructionScaleBay or "",
  300. "constructionScaleOverheadLine": item.constructionScaleOverheadLine or "",
  301. "constructionScaleOpticalCable": item.constructionScaleOpticalCable or "",
  302. "staticInvestment": self._parse_number(item.staticInvestment),
  303. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  304. "constructionProjectCost": self._parse_number(item.constructionProjectCost),
  305. "equipmentPurchaseCost": self._parse_number(item.equipmentPurchaseCost),
  306. "installationProjectCost": self._parse_number(item.installationProjectCost),
  307. "otherExpenses": self._parse_number(item.otherExpenses),
  308. "items": []
  309. }
  310. elif item.level == "3" and current_sub_category is not None:
  311. # 具体项目(如"周村220千伏变电站新建工程")
  312. current_sub_category["items"].append({
  313. "No": self._parse_no(item.no),
  314. "name": item.name,
  315. "Level": 2,
  316. "constructionScaleSubstation": item.constructionScaleSubstation or "",
  317. "constructionScaleBay": item.constructionScaleBay or "",
  318. "constructionScaleOverheadLine": item.constructionScaleOverheadLine or "",
  319. "constructionScaleOpticalCable": item.constructionScaleOpticalCable or "",
  320. "staticInvestment": self._parse_number(item.staticInvestment),
  321. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  322. "constructionProjectCost": self._parse_number(item.constructionProjectCost),
  323. "equipmentPurchaseCost": self._parse_number(item.equipmentPurchaseCost),
  324. "installationProjectCost": self._parse_number(item.installationProjectCost),
  325. "otherExpenses": self._parse_number(item.otherExpenses),
  326. })
  327. elif item.level == "0":
  328. # 合计行 - 跳过
  329. if current_sub_category is not None and current_top_category is not None:
  330. current_top_category["items"].append(current_sub_category)
  331. current_sub_category = None
  332. if current_top_category is not None:
  333. result.append(current_top_category)
  334. current_top_category = None
  335. # 添加最后的分类
  336. if current_sub_category is not None and current_top_category is not None:
  337. current_top_category["items"].append(current_sub_category)
  338. if current_top_category is not None:
  339. result.append(current_top_category)
  340. return result
  341. @staticmethod
  342. def _parse_number(value: str) -> str:
  343. """将数字字符串格式化,保留原始精度"""
  344. if not value or not value.strip():
  345. return "0"
  346. return value.strip()
  347. @staticmethod
  348. def _parse_no(value: str) -> int:
  349. if not value or not value.strip():
  350. return 0
  351. try:
  352. return int(value.strip())
  353. except ValueError:
  354. return 0
  355. class FeasibilityReviewInvestment:
  356. """可研评审投资估算数据模型
  357. 返回结构与 designReview 保持一致,不包含建设规模字段
  358. 三层嵌套结构:
  359. - Level 0: 顶层大类
  360. - Level 1: 二级分类,有自己的 items
  361. - Level 2: 具体项目
  362. """
  363. def __init__(self):
  364. self.items: List[InvestmentItem] = []
  365. def to_dict(self):
  366. """转换为嵌套结构,与 designReview 保持一致
  367. Level="1" 的项目作为顶层大类(Level: 0)
  368. Level="2" 的项目作为二级分类(Level: 1),有自己的 items
  369. Level="3" 的项目作为具体项目(Level: 2),放入二级分类的 items
  370. Level="0" 的项目(合计)跳过
  371. """
  372. if not self.items:
  373. return []
  374. result = []
  375. current_top_category = None
  376. current_sub_category = None
  377. for item in self.items:
  378. if item.level == "1":
  379. # 顶层大类
  380. if current_sub_category is not None and current_top_category is not None:
  381. current_top_category["items"].append(current_sub_category)
  382. current_sub_category = None
  383. if current_top_category is not None:
  384. result.append(current_top_category)
  385. current_top_category = {
  386. "name": item.name,
  387. "Level": 0,
  388. "staticInvestment": self._parse_number(item.staticInvestment),
  389. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  390. "items": []
  391. }
  392. elif item.level == "2" and current_top_category is not None:
  393. # 二级分类
  394. if current_sub_category is not None:
  395. current_top_category["items"].append(current_sub_category)
  396. current_sub_category = {
  397. "No": self._parse_no(item.no),
  398. "name": item.name,
  399. "Level": 1,
  400. "staticInvestment": self._parse_number(item.staticInvestment),
  401. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  402. "items": []
  403. }
  404. elif item.level == "3" and current_sub_category is not None:
  405. # 具体项目
  406. current_sub_category["items"].append({
  407. "No": self._parse_no(item.no),
  408. "name": item.name,
  409. "Level": 2,
  410. "staticInvestment": self._parse_number(item.staticInvestment),
  411. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  412. })
  413. elif item.level == "0":
  414. # 合计行 - 跳过
  415. if current_sub_category is not None and current_top_category is not None:
  416. current_top_category["items"].append(current_sub_category)
  417. current_sub_category = None
  418. if current_top_category is not None:
  419. result.append(current_top_category)
  420. current_top_category = None
  421. # 添加最后的分类
  422. if current_sub_category is not None and current_top_category is not None:
  423. current_top_category["items"].append(current_sub_category)
  424. if current_top_category is not None:
  425. result.append(current_top_category)
  426. return result
  427. @staticmethod
  428. def _parse_number(value: str) -> str:
  429. """将数字字符串格式化,保留原始精度"""
  430. if not value or not value.strip():
  431. return "0"
  432. return value.strip()
  433. @staticmethod
  434. def _parse_no(value: str) -> int:
  435. if not value or not value.strip():
  436. return 0
  437. try:
  438. return int(value.strip())
  439. except ValueError:
  440. return 0
  441. class PreliminaryApprovalInvestment:
  442. """初设批复概算投资数据模型
  443. 返回结构与 designReview 保持一致:
  444. [{
  445. "name": str, # 大类名称(如"变电工程"、"线路工程")
  446. "Level": 0, # 大类层级
  447. "staticInvestment": float, # 静态投资总计
  448. "dynamicInvestment": float, # 动态投资总计
  449. "items": [ # 子项列表
  450. {
  451. "No": int, # 序号
  452. "name": str, # 工程名称
  453. "Level": 1, # 子项层级
  454. "staticInvestment": float, # 静态投资
  455. "dynamicInvestment": float, # 动态投资
  456. },
  457. ...
  458. ]
  459. }, ...]
  460. """
  461. def __init__(self):
  462. self.items: List[InvestmentItem] = []
  463. def to_dict(self):
  464. """转换为嵌套结构,与 designReview 保持一致
  465. Level="1" 的项目作为大类(变电工程、线路工程等)
  466. Level="2" 的项目作为子项
  467. Level="0" 的项目(合计)跳过,不包含在输出中
  468. """
  469. if not self.items:
  470. return []
  471. result = []
  472. current_category = None
  473. for item in self.items:
  474. if item.level == "1":
  475. # 大类项目 - 创建新的类别
  476. if current_category is not None:
  477. result.append(current_category)
  478. current_category = {
  479. "name": item.name,
  480. "Level": 0,
  481. "staticInvestment": self._parse_number(item.staticInvestment),
  482. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  483. "items": []
  484. }
  485. elif item.level == "2" and current_category is not None:
  486. # 子项目 - 添加到当前类别的 items 中
  487. current_category["items"].append({
  488. "No": self._parse_no(item.no),
  489. "name": item.name,
  490. "Level": 1,
  491. "staticInvestment": self._parse_number(item.staticInvestment),
  492. "dynamicInvestment": self._parse_number(item.dynamicInvestment),
  493. })
  494. elif item.level == "0":
  495. # 合计行 - 跳过,不包含在输出中
  496. # 先保存当前类别
  497. if current_category is not None:
  498. result.append(current_category)
  499. current_category = None
  500. # 添加最后一个类别
  501. if current_category is not None:
  502. result.append(current_category)
  503. return result
  504. @staticmethod
  505. def _parse_number(value: str) -> str:
  506. """将数字字符串格式化,保留原始精度"""
  507. if not value or not value.strip():
  508. return "0"
  509. return value.strip()
  510. @staticmethod
  511. def _parse_no(value: str) -> int:
  512. """将序号转换为整数"""
  513. if not value or not value.strip():
  514. return 0
  515. try:
  516. return int(value.strip())
  517. except ValueError:
  518. return 0
  519. class FinalAccountItem:
  520. """决算报告单项工程投资完成情况数据模型"""
  521. def __init__(self):
  522. self.no: int = 0 # 序号(项目序号)
  523. self.name: str = "" # 项目名称(审计内容)
  524. self.feeName: str = "" # 费用项目(建筑安装工程、设备购置、其他费用)
  525. self.estimatedCost: str = "" # 概算金额
  526. self.approvedFinalAccountExcludingVat: str = "" # 决算金额审定不含税
  527. self.vatAmount: str = "" # 增值税额
  528. self.costVariance: str = "" # 超节支金额
  529. self.varianceRate: str = "" # 超节支率
  530. def to_dict(self, include_project_info: bool = True):
  531. """转换为字典
  532. Args:
  533. include_project_info: 是否包含项目序号和名称(分组后不需要)
  534. """
  535. result = {
  536. "feeName": self.feeName,
  537. "estimatedCost": self.estimatedCost,
  538. "approvedFinalAccountExcludingVat": self.approvedFinalAccountExcludingVat,
  539. "vatAmount": self.vatAmount,
  540. "costVariance": self.costVariance,
  541. "varianceRate": self.varianceRate
  542. }
  543. if include_project_info:
  544. result["No"] = self.no
  545. result["name"] = self.name
  546. return result
  547. class FinalAccountRecord:
  548. """决算报告记录数据模型
  549. 返回结构:按项目分组的嵌套数组
  550. [{
  551. "No": int, # 序号(项目序号,如1、2、3、4)
  552. "name": str, # 项目名称(如"周村220kV输变电工程变电站新建工程")
  553. "items": [{ # 费用明细列表
  554. "feeName": str, # 费用项目(如"建筑安装工程"、"设备购置"、"其他费用")
  555. "estimatedCost": str, # 概算金额
  556. "approvedFinalAccountExcludingVat": str, # 决算金额审定不含税
  557. "vatAmount": str, # 增值税额
  558. "costVariance": str, # 超节支金额
  559. "varianceRate": str, # 超节支率
  560. }, ...]
  561. }, ...]
  562. """
  563. def __init__(self):
  564. self.items: List[FinalAccountItem] = []
  565. def to_dict(self):
  566. """转换为按项目分组的嵌套结构"""
  567. from collections import OrderedDict
  568. # 按项目序号分组
  569. grouped: OrderedDict[int, dict] = OrderedDict()
  570. for item in self.items:
  571. if item.no not in grouped:
  572. grouped[item.no] = {
  573. "No": item.no,
  574. "name": item.name,
  575. "items": []
  576. }
  577. # 添加费用明细(不包含项目序号和名称)
  578. grouped[item.no]["items"].append(item.to_dict(include_project_info=False))
  579. return list(grouped.values())