""" 批量对比多个 calculation_results.json 与 project_data.json 中的费用 """ import json import os import re from difflib import SequenceMatcher def extract_guid_from_filename(filename): """从文件名中提取 GUID""" pattern = r"([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})" match = re.search(pattern, filename, re.IGNORECASE) return match.group(1).upper() if match else None def normalize_cost_name(name): """标准化费用名称,便于匹配""" name = re.sub(r"_\w+$", "", name) # 去除后缀如 _GJJ name = re.sub(r"[^\w]", "", name) # 去除符号 return name.lower() def calculate_similarity(a, b): """计算两个字符串的相似度""" return SequenceMatcher(None, a, b).ratio() def load_calculation_results(json_file_path): """读取计算结果 JSON,返回费用总和字典""" with open(json_file_path, "r", encoding="utf-8") as f: data = json.load(f) total_costs = {} for node_name, cost_dict in data.items(): for cost_name, value in cost_dict.items(): total_costs[cost_name] = total_costs.get(cost_name, 0) + value return total_costs def find_node_by_guid(expense_preview, target_guid): """在 expensePreview 中递归查找 GUID 对应的节点""" def search_recursive(items): for item in items: current_guid = item.get("GUID", "").strip("{}").upper() if current_guid == target_guid: return item.get("children", []) if "children" in item: result = search_recursive(item["children"]) if result is not None: return result return None for category_name, items in expense_preview.items(): if isinstance(items, list): result = search_recursive(items) if result is not None: return result return None def load_project_data_and_find_costs(project_json_path, target_guid): """读取 project_data.json 并查找对应 GUID 的费用列表""" with open(project_json_path, "r", encoding="utf-8") as f: project_data = json.load(f) expense_preview = project_data.get("projectData", {}).get("expensePreview", {}) children = find_node_by_guid(expense_preview, target_guid) if not children: print(f"未找到 GUID 为 {target_guid} 的节点") return {} external_costs = {} for item in children: item_id = item.get("id", "") cost_str = item.get("cost", "0") try: cost_val = float(cost_str) except ValueError: cost_val = 0.0 if item_id: clean_id = re.sub(r"_\w+$", "", item_id) external_costs[clean_id] = cost_val return external_costs def match_and_compare_costs(calc_costs, ext_costs, similarity_threshold=0.6): """匹配两个费用列表并对比""" comparison = [] matched_ext = set() for calc_name, calc_value in calc_costs.items(): best_match = None best_score = 0 for ext_name in ext_costs: if ext_name in matched_ext: continue score = calculate_similarity(normalize_cost_name(calc_name), normalize_cost_name(ext_name)) if score > best_score: best_score = score best_match = ext_name if best_match and best_score >= similarity_threshold: ext_value = ext_costs[best_match] difference = calc_value - ext_value comparison.append( { "项目": calc_name, "参考值": ext_value, "计算值": calc_value, "差异": difference, "原数据项": best_match, "相似度": best_score, } ) matched_ext.add(best_match) else: comparison.append( { "项目": calc_name, "参考值": None, "计算值": calc_value, "差异": None, "原数据项": None, "相似度": best_score, } ) # 添加未匹配的参考项 for ext_name, ext_value in ext_costs.items(): if ext_name not in matched_ext: comparison.append( { "项目": None, "参考值": ext_value, "计算值": None, "差异": None, "原数据项": ext_name, "相似度": None, } ) return comparison def save_comparison_to_txt(comparison, output_txt_path): """保存对比结果到 TXT 文件""" with open(output_txt_path, "w", encoding="utf-8") as f: f.write(f"{'项目':<20} {'参考值':<25} {'计算值':<25} {'差异':<25} {'原数据项':<30}\n") f.write("-" * 120 + "\n") for item in comparison: project = (item["项目"] or "").ljust(20)[:20] ref = f"{item['参考值']:.2f}" if item["参考值"] is not None else "" ref = ref.ljust(25)[:25] calc = f"{item['计算值']:.2f}" if item["计算值"] is not None else "" calc = calc.ljust(25)[:25] diff = f"{item['差异']:.2f}" if item["差异"] is not None else "" diff = diff.ljust(25)[:25] original = (item["原数据项"] or "").ljust(30)[:30] f.write(f"{project}{ref}{calc}{diff}{original}\n") print(f"✅ 对比结果已保存至: {output_txt_path}") def main(): # ================== 配置路径 ================== # 存放所有 calculation_results.json 的文件夹 calc_results_folder = "project2json/outputs/bclresults/变电技改国网" # 主 project_data.json 路径(参考数据源) project_data_json_path = "project2json/outputs/json/变电技改国网.json" # 输出对比结果的文件夹 output_folder = "project2json/outputs/comparison_results" os.makedirs(output_folder, exist_ok=True) # 支持的文件名关键词(可根据实际命名调整) result_file_keyword = "_calculation_results.json" # ================================================== print(f"开始批量处理文件夹: {calc_results_folder}") processed_count = 0 for filename in os.listdir(calc_results_folder): if not filename.endswith(".json") or result_file_keyword not in filename: continue calc_json_path = os.path.join(calc_results_folder, filename) try: # 提取 GUID guid = extract_guid_from_filename(filename) if not guid: print(f"⚠️ 无法从文件名提取 GUID: {filename}") continue print(f"\n📄 处理文件: {filename}") print(f" 提取 GUID: {guid}") # 读取计算结果 calc_costs = load_calculation_results(calc_json_path) print(f" 加载 {len(calc_costs)} 个计算费用项") # 从主 JSON 获取参考费用 ext_costs = load_project_data_and_find_costs(project_data_json_path, guid) print(f" 找到 {len(ext_costs)} 个参考费用项") # 对比 comparison = match_and_compare_costs(calc_costs, ext_costs, similarity_threshold=0.6) # 生成输出文件名(与原 JSON 同名,但输出到指定文件夹) base_name = os.path.splitext(filename)[0] output_txt_path = os.path.join(output_folder, base_name + ".txt") # 保存结果 save_comparison_to_txt(comparison, output_txt_path) processed_count += 1 except Exception as e: print(f"❌ 处理文件 {filename} 时出错: {e}") print(f"\n✅ 批量处理完成!共处理 {processed_count} 个文件。") print(f"📊 所有对比结果已保存至: {output_folder}") if __name__ == "__main__": main()