From b352571e171cd08ede23e5a823421afe709bf4f0 Mon Sep 17 00:00:00 2001 From: paituo <330435863@qq.com> Date: Mon, 7 Jul 2025 11:25:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 2 +- main_streamlt.py | 852 +++++++++++++++++++++------------ requirements.txt | 3 +- src/code_executor.py | 4 +- src/project.py | 13 - src/prompt_manager.py | 24 +- tests/test_runcode.py | 2 +- tests/test_user_interaction.py | 184 ------- tests/zhibiao.json | 92 ++-- tools/xml_to_json.py | 10 +- 10 files changed, 634 insertions(+), 552 deletions(-) delete mode 100644 tests/test_user_interaction.py diff --git a/main.py b/main.py index 3012a3c..7386d6d 100644 --- a/main.py +++ b/main.py @@ -76,7 +76,7 @@ def main(): knowledge_retriever = Neo4jRawRetriever(neo4j_conf) - code_executor = CodeExecutor(prompt_manager.prompts, llm_client_coder) + code_executor = CodeExecutor(prompt_manager.prompts, llm_client_coder, config.max_retries) dialog_manager = DialogManager( llm_client, diff --git a/main_streamlt.py b/main_streamlt.py index 41c25e0..b556101 100644 --- a/main_streamlt.py +++ b/main_streamlt.py @@ -7,6 +7,7 @@ from typing import Dict, List, Optional, Any import logging import sys from datetime import datetime +from textcomplete import textcomplete, StrategyProps # 添加项目根目录到路径 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -350,6 +351,183 @@ def init_app_data(): } +# 自动补全策略相关函数 +def create_project_division_strategy(): + """创建项目划分自动补全策略""" + return StrategyProps( + id="projectDivision", + match="从项目划分【(.*)】", + search="""async (term, callback) => { + const divisions = [ + "架空输电线路本体工程/基础工程", + "架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立", + "架空输电线路本体工程/架线工程", + "架空输电线路本体工程/附件安装工程" + ]; + const matches = divisions.filter(div => div.toLowerCase().includes(term.toLowerCase())); + callback(matches); + }""", + replace="(division) => `从项目划分【${division}】`", + template="(division) => `📁 ${division}`", + ) + +def create_search_type_strategy(): + """创建搜索类型自动补全策略""" + return StrategyProps( + id="searchType", + match="中查找(名称|编码)(包含|等于)【(.*)】", + search="""async (term, callback) => { + const types = ["名称包含", "编码包含", "名称等于", "编码等于"]; + const matches = types.filter(type => type.toLowerCase().includes(term.toLowerCase())); + callback(matches); + }""", + replace="(type) => `中查找${type}【`", + template="(type) => `🔍 ${type}`", + ) + +def create_item_type_strategy(): + """创建项目类型自动补全策略""" + return StrategyProps( + id="itemType", + match="的所有【(.*)】", + search="""async (term, callback) => { + const types = ["定额", "主材", "取费名称"]; + const matches = types.filter(type => type.toLowerCase().includes(term.toLowerCase())); + callback(matches); + }""", + replace="(type) => `的所有【${type}】`", + template="(type) => `📋 ${type}`", + ) + +def create_attribute_strategy(): + """创建属性自动补全策略""" + return StrategyProps( + id="attribute", + match="的【(.*)】", + search="""async (term, callback) => { + const attributes = ["数量", "单价", "合计费", "属性"]; + const matches = attributes.filter(attr => attr.toLowerCase().includes(term.toLowerCase())); + callback(matches); + }""", + replace="(attr) => `的【${attr}】`", + template="(attr) => `📊 ${attr}`", + ) + +def create_fee_type_strategy(): + """创建费用类型自动补全策略""" + return StrategyProps( + id="feeType", + match="从【(.*)】中", + search="""async (term, callback) => { + const types = ["工程费用", "其他费用"]; + const matches = types.filter(type => type.toLowerCase().includes(term.toLowerCase())); + callback(matches); + }""", + replace="(type) => `从【${type}】中`", + template="(type) => `💰 ${type}`", + ) + +def create_project_name_strategy(): + """创建项目名称自动补全策略""" + return StrategyProps( + id="projectName", + match="获取【(.*)】的", + search="""async (term, callback) => { + const names = [ + "架空输电线路本体工程", + "建设场地征用及清理费" + ]; + const matches = names.filter(name => name.toLowerCase().includes(term.toLowerCase())); + callback(matches); + }""", + replace="(name) => `获取【${name}】的`", + template="(name) => `🏗️ ${name}`", + ) + +def create_code_value_strategy(): + """创建编码值自动补全策略""" + return StrategyProps( + id="codeValue", + match="编码包含【(.*)】", + search="""async (term, callback) => { + const codes = ["YX2-1~7", "YX5-9"]; + const matches = codes.filter(code => code.toLowerCase().includes(term.toLowerCase())); + callback(matches); + }""", + replace="(code) => `编码包含【${code}】`", + template="(code) => `🔢 ${code}`", + ) + +def create_name_value_strategy(): + """创建名称值自动补全策略""" + return StrategyProps( + id="nameValue", + match="名称包含【(.*)】", + search="""async (term, callback) => { + const names = ["角钢", "钢管杆"]; + const matches = names.filter(name => name.toLowerCase().includes(term.toLowerCase())); + callback(matches); + }""", + replace="(name) => `名称包含【${name}】`", + template="(name) => `📝 ${name}`", + ) + +def create_name_equal_strategy(): + """创建名称等于自动补全策略""" + return StrategyProps( + id="nameEqual", + match="取费名称等于【(.*)】", + search="""async (term, callback) => { + const names = ["合计"]; + const matches = names.filter(name => name.toLowerCase().includes(term.toLowerCase())); + callback(matches); + }""", + replace="(name) => `取费名称等于【${name}】`", + template="(name) => `📌 ${name}`", + ) + +def create_query_template_strategy(): + """创建查询模板自动补全策略""" + return StrategyProps( + id="queryTemplate", + match="^$", + search="""async (term, callback) => { + const templates = [ + "从项目划分【架空输电线路本体工程/基础工程】中查找编码包含【YX2-1~7】的所有【定额】的【数量】之和", + "从项目划分【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】中查找名称包含【角钢】的所有【主材】的【数量】之和", + "从项目划分【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】中查找名称包含【钢管杆】的所有【主材】的【单价】之和", + "从项目划分【架空输电线路本体工程/架线工程】中查找编码中包含【YX5-9】的所有【定额】的【数量】之和", + "从【工程费用】中获取【架空输电线路本体工程】的【合计费】属性", + "从【其他费用】中获取【建设场地征用及清理费】的属性", + "从项目划分【架空输电线路本体工程/附件安装工程】中获取取费名称等于【合计】的费用" + ]; + const matches = templates.filter(template => template.toLowerCase().includes(term.toLowerCase())); + callback(matches); + }""", + replace="(template) => template", + template="(template) => `📋 ${template}`", + ) + +def apply_autocomplete_to_query_input(area_label="输入查询语句", max_count=5): + """应用自动补全到查询输入框""" + # 初始化textcomplete组件 + textcomplete( + area_label=area_label, + strategies=[ + create_query_template_strategy(), + create_project_division_strategy(), + create_search_type_strategy(), + create_item_type_strategy(), + create_attribute_strategy(), + create_fee_type_strategy(), + create_project_name_strategy(), + create_code_value_strategy(), + create_name_value_strategy(), + create_name_equal_strategy() + ], + max_count=max_count + ) + # Streamlit 应用界面 def main(): st.set_page_config(page_title="造价工程指标管理器", layout="wide") @@ -364,6 +542,17 @@ def main(): st.session_state[key] = value # 初始化会话状态 + initialize_session_state() + + # 侧边栏 + render_sidebar() + + # 主区域 + render_main_area() + + +def initialize_session_state(): + """初始化会话状态""" if "indicator_manager" not in st.session_state: st.session_state.indicator_manager = IndicatorManager() @@ -372,314 +561,391 @@ def main(): # 加载保存的会话状态 load_session_state() - - # 侧边栏 + + +def render_sidebar(): + """渲染侧边栏内容""" with st.sidebar: - # 第一个expander:指标管理 - with st.expander("新建指标工具", expanded=True): - - # 显示清除指标链接 - col1, col2, col3 = st.columns([2, 1, 1]) - with col1: - st.subheader("指标列表") - with col2: - # 新建指标按钮 - if st.button("新建"): - try: - # 生成新指标名称 - indicator_count = len(st.session_state.indicator_manager.get_all_indicators()) - new_name = f"指标 {indicator_count + 1}" - - # 创建新指标 - new_indicator = st.session_state.indicator_manager.add_indicator(name=new_name) - st.session_state.current_indicator_id = new_indicator.id - # 清除已生成的代码 - if "generated_code" in st.session_state: - del st.session_state.generated_code - # 清除第二个expander的状态 - st.session_state.view_indicator_detail = False - save_session_state() - st.rerun() - except ValueError as e: - st.error(str(e)) - with col3: - if st.button("清空", help="删除所有指标", type="secondary"): - st.session_state.indicator_manager.clear_all_indicators() - st.session_state.current_indicator_id = None - # 清除已生成的代码 - if "generated_code" in st.session_state: - del st.session_state.generated_code - # 清除第二个expander的状态 - st.session_state.view_indicator_detail = False - save_session_state() - st.rerun() - - # 显示指标列表 - indicators = st.session_state.indicator_manager.get_all_indicators() - if not indicators: - st.info("暂无指标,请点击\"新建指标\"按钮创建") - else: - for indicator in sorted(indicators, key=lambda x: x.created_at): - if st.button( - indicator.name, - key=f"btn_{indicator.id}", - use_container_width=True, - type="primary" if st.session_state.current_indicator_id == indicator.id else "secondary" - ): - st.session_state.current_indicator_id = indicator.id - # 更新显示的代码 - if indicator.code: - st.session_state.generated_code = indicator.code - elif "generated_code" in st.session_state: - # 如果当前指标没有代码,清除session中的代码 - del st.session_state.generated_code - # 清除第二个expander的状态 - st.session_state.view_indicator_detail = False - save_session_state() - st.rerun() + render_indicator_management_expander() # 第二个expander:指标库浏览 - with st.expander("查看指标库", expanded=False): + render_indicator_library_expander() - # 设置默认文件路径 - file_path = os.path.join(DATA_DIR, "code.jsonl") - - # 文件路径输入 - #file_path = st.text_input("JSONL文件路径", value=default_file_path, key="jsonl_path") - - # 加载JSONL文件的函数 - @st.cache_data - def load_jsonl(file_path): - """加载JSONL文件并返回JSON指标列表""" - records = [] - try: - with open(file_path, "r", encoding="utf-8") as file: - for line in file: - if line.strip(): - records.append(json.loads(line)) - return records - except Exception as e: - st.error(f"加载文件失败: {str(e)}") - return [] - - # 检查文件是否存在 - if not os.path.exists(file_path): - st.warning(f"文件不存在: {file_path}") - else: - # 加载JSONL文件 - records = load_jsonl(file_path) - - if not records: - st.warning("没有找到有效的指标") - else: - # 初始化会话状态 - if "selected_index" not in st.session_state: - st.session_state.selected_index = 0 - - # 显示指标列表 - st.subheader("指标列表") - for i, record in enumerate(records): - if st.button(record.get("name", f"指标 {i+1}"), key=f"lib_btn_{i}", use_container_width=True): - st.session_state.selected_index = i - # 清除第一个expander的选中状态 - st.session_state.current_indicator_id = None - # 设置查看指标详情的逻辑 - st.session_state.view_indicator_detail = True - st.session_state.current_library_indicator = record - save_session_state() - st.rerun() - - # 主区域 - if "view_indicator_detail" in st.session_state and st.session_state.view_indicator_detail and "current_library_indicator" in st.session_state: - # 显示从指标库中选择的指标详细信息 - library_indicator = st.session_state.current_library_indicator - - # 指标名称和操作按钮 - col1, col2, col3 = st.columns([1, 4, 2]) + +def render_indicator_management_expander(): + """渲染指标管理扩展面板""" + with st.expander("新建指标工具", expanded=True): + # 显示清除指标链接 + col1, col2, col3 = st.columns([2, 1, 1]) with col1: - st.markdown('
指标名称:
', unsafe_allow_html=True) + st.subheader("指标列表") with col2: - st.markdown(f'
{library_indicator.get("name", "未命名指标")}
', unsafe_allow_html=True) + render_new_indicator_button() + with col3: + render_clear_indicators_button() + + # 显示指标列表 + render_indicator_list() - - # 查询语句部分 - title_col, run_btn_col, result_col = st.columns([1, 1, 4]) - with title_col: - st.subheader("查询语句") - with result_col: - title_query_result = st.subheader("") - - # 显示查询语句 - st.text_area( - "查询语句", - value=library_indicator.get('query', '无查询语句'), - height=70, - key="lib_query_display", - disabled=True, - label_visibility="collapsed" - ) - - # 指标代码部分 - title_col, run_btn_col, result_col = st.columns([1, 1, 4]) - with title_col: - st.subheader("指标代码") - with result_col: - title_code_result = st.subheader("") - with run_btn_col: - if st.button("执行代码", key="execute_lib_indicator", use_container_width=True): - with st.spinner('正在执行代码...'): - # 确保code_executor已初始化 - if "code_executor" in st.session_state: - result = st.session_state.code_executor.execute_code(library_indicator.get('code', '')) - if result and hasattr(result, 'get') and result.get('status'): - title_code_result.success("执行成功") - title_code_result.json(result.get('data', {})) - else: - title_code_result.error(f"执行失败: {result.get('message', '未知错误') if hasattr(result, 'get') else str(result)}") - else: - title_code_result.error("代码执行器未初始化") - - # 显示代码 - st.code(library_indicator.get('code', '无代码'), language='python') - - # 移除导入按钮部分 +def render_new_indicator_button(): + """渲染新建指标按钮""" + if st.button("新建"): + try: + # 生成新指标名称 + indicator_count = len(st.session_state.indicator_manager.get_all_indicators()) + new_name = f"指标 {indicator_count + 1}" - elif st.session_state.current_indicator_id is not None: - current_indicator = st.session_state.indicator_manager.get_indicator(st.session_state.current_indicator_id) + # 创建新指标 + new_indicator = st.session_state.indicator_manager.add_indicator(name=new_name) + st.session_state.current_indicator_id = new_indicator.id + # 清除已生成的代码 + if "generated_code" in st.session_state: + del st.session_state.generated_code + # 清除第二个expander的状态 + st.session_state.view_indicator_detail = False + save_session_state() + st.rerun() + except ValueError as e: + st.error(str(e)) + + +def render_clear_indicators_button(): + """渲染清空指标按钮""" + if st.button("清空", help="删除所有指标", type="secondary"): + st.session_state.indicator_manager.clear_all_indicators() + st.session_state.current_indicator_id = None + # 清除已生成的代码 + if "generated_code" in st.session_state: + del st.session_state.generated_code + # 清除第二个expander的状态 + st.session_state.view_indicator_detail = False + save_session_state() + st.rerun() + + +def render_indicator_list(): + """渲染指标列表""" + indicators = st.session_state.indicator_manager.get_all_indicators() + if not indicators: + st.info("暂无指标,请点击\"新建指标\"按钮创建") + else: + for indicator in sorted(indicators, key=lambda x: x.created_at): + if st.button( + indicator.name, + key=f"btn_{indicator.id}", + use_container_width=True, + type="primary" if st.session_state.current_indicator_id == indicator.id else "secondary" + ): + st.session_state.current_indicator_id = indicator.id + # 更新显示的代码 + if indicator.code: + st.session_state.generated_code = indicator.code + elif "generated_code" in st.session_state: + # 如果当前指标没有代码,清除session中的代码 + del st.session_state.generated_code + # 清除第二个expander的状态 + st.session_state.view_indicator_detail = False + save_session_state() + st.rerun() + + +def render_indicator_library_expander(): + """渲染指标库浏览扩展面板""" + with st.expander("查看指标库", expanded=False): + # 设置默认文件路径 + file_path = os.path.join(DATA_DIR, "code.jsonl") - if current_indicator: - # 指标名称和保存按钮 - col1, col2, col3 = st.columns([1, 4, 2]) - - with col1: - st.markdown('
指标名称:
', unsafe_allow_html=True) - - with col2: - new_name = st.text_input("指标名称", value=current_indicator.name, key="indicator_name", label_visibility="collapsed") - if new_name != current_indicator.name: - if not new_name.strip(): - st.error("指标名称不能为空") - else: - try: - st.session_state.indicator_manager.update_indicator( - current_indicator.id, name=new_name - ) - except ValueError as e: - st.error(str(e)) - - with col3: - if st.button("💾 保存指标", use_container_width=True): - if not new_name.strip(): - st.error("指标名称不能为空") - elif not current_indicator.query.strip(): - st.error("请先输入查询语句") - elif not current_indicator.code: - st.error("请先生成指标代码") - else: - try: - # 保存到指标管理器 - st.session_state.indicator_manager.save_indicators() - - # 同时保存到指标库 - library_path = os.path.join(DATA_DIR, "code.jsonl") - new_record = { - "name": current_indicator.name, - "query": current_indicator.query, - "code": current_indicator.code, - "created_at": time.time() - } - - # 追加到文件 - with open(library_path, "a", encoding="utf-8") as f: - f.write(json.dumps(new_record, ensure_ascii=False) + "\n") - - # 清除指标库的缓存,以便重新加载 - load_jsonl.clear() - - st.toast("指标已保存") - st.rerun() # 刷新页面以更新指标库列表 - except Exception as e: - st.error(f"保存失败: {str(e)}") - - # 查询语句 - title_col, test_btn_col, result_col = st.columns([1, 1, 4]) - with title_col: - st.subheader("查询语句") - with result_col: - title_query_result = st.subheader("") - - # 查询输入框 - query = st.text_area( - "输入查询语句", - value=current_indicator.query, - height=70, - key="query_input", - placeholder="在此输入查询语句...", - label_visibility="collapsed" - ) - - if query != current_indicator.query: - st.session_state.indicator_manager.update_indicator( - current_indicator.id, query=query - ) - - # 测试按钮改为生成指标代码按钮 - with test_btn_col: - if st.button("🔄 生成代码", key="generate_code_btn", use_container_width=True): - with st.spinner("正在生成指标代码..."): - # 使用缓存的测试函数 - result = generate_indicator_code(query) - - # 根据结果显示不同的状态 - if result.startswith("✅"): - title_query_result.success(result) - # 生成成功后,立即保存代码到指标对象中 - if "generated_code" in st.session_state: - st.session_state.indicator_manager.update_indicator( - current_indicator.id, - code=st.session_state.generated_code - ) - elif result.startswith("❌"): - title_query_result.error(result) - else: - # 显示实体识别结果或其他信息 - title_query_result.info(result) - - # 添加代码执行部分 - title_col, run_btn_col, result_col = st.columns([1, 1, 4]) - with title_col: - st.subheader("指标代码") - with result_col: - title_code_result = st.subheader("") - with run_btn_col: - if st.button("执行代码", key="execute_code_btn", use_container_width=True): - with st.spinner("正在执行代码..."): - if "code_executor" in st.session_state and "generated_code" in st.session_state: - result = st.session_state.code_executor.execute_code(st.session_state.generated_code) - if result and hasattr(result, 'get') and result.get('status'): - title_code_result.success("执行成功") - title_code_result.json(result.get('data', {})) - # 更新指标的代码 - st.session_state.indicator_manager.update_indicator( - current_indicator.id, - code=st.session_state.generated_code - ) - else: - title_code_result.error(f"执行失败: {result.get('message', '未知错误') if hasattr(result, 'get') else str(result)}") - else: - title_code_result.error("代码执行器未初始化或没有生成的代码") - - # 显示代码 - st.code(st.session_state.get("generated_code", "# 暂无代码,请输入有效的查询语句并点击测试按钮"), language="python") - - # 移除保存到指标库按钮部分 + # 检查文件是否存在 + if not os.path.exists(file_path): + st.warning(f"文件不存在: {file_path}") else: - st.warning("找不到当前指标,可能已被删除") + # 加载JSONL文件 + records = load_jsonl(file_path) + + if not records: + st.warning("没有找到有效的指标") + else: + # 初始化会话状态 + if "selected_index" not in st.session_state: + st.session_state.selected_index = 0 + + # 显示指标列表 + render_library_indicator_list(records) + + +@st.cache_data +def load_jsonl(file_path): + """加载JSONL文件并返回JSON指标列表""" + records = [] + try: + with open(file_path, "r", encoding="utf-8") as file: + for line in file: + if line.strip(): + records.append(json.loads(line)) + return records + except Exception as e: + st.error(f"加载文件失败: {str(e)}") + return [] + + +def render_library_indicator_list(records): + """渲染指标库列表""" + st.subheader("指标列表") + for i, record in enumerate(records): + if st.button(record.get("name", f"指标 {i+1}"), key=f"lib_btn_{i}", use_container_width=True): + st.session_state.selected_index = i + # 清除第一个expander的选中状态 + st.session_state.current_indicator_id = None + # 设置查看指标详情的逻辑 + st.session_state.view_indicator_detail = True + st.session_state.current_library_indicator = record + save_session_state() + st.rerun() + + +def render_main_area(): + """渲染主区域内容""" + if "view_indicator_detail" in st.session_state and st.session_state.view_indicator_detail and "current_library_indicator" in st.session_state: + render_library_indicator_detail() + elif st.session_state.current_indicator_id is not None: + render_current_indicator_detail() else: st.info("请在左侧创建或选择一个指标") +def render_library_indicator_detail(): + """渲染指标库详情""" + library_indicator = st.session_state.current_library_indicator + + # 指标名称和操作按钮 + col1, col2, col3 = st.columns([1, 4, 2]) + with col1: + st.markdown('
指标名称:
', unsafe_allow_html=True) + with col2: + st.markdown(f'
{library_indicator.get("name", "未命名指标")}
', unsafe_allow_html=True) + + + # 查询语句部分 + title_col, run_btn_col, result_col = st.columns([1, 1, 4]) + with title_col: + st.subheader("查询语句") + with result_col: + title_query_result = st.subheader("") + + # 显示查询语句 + st.text_area( + "查询语句", + value=library_indicator.get('query', '无查询语句'), + height=70, + key="lib_query_display", + disabled=True, + label_visibility="collapsed" + ) + + # 指标代码部分 + render_library_indicator_code(library_indicator) + + +def render_library_indicator_code(library_indicator): + """渲染指标库代码部分""" + title_col, run_btn_col, result_col = st.columns([1, 1, 4]) + with title_col: + st.subheader("指标代码") + with result_col: + title_code_result = st.subheader("") + with run_btn_col: + if st.button("执行代码", key="execute_lib_indicator", use_container_width=True): + execute_indicator_code(library_indicator.get('code', ''), title_code_result) + + # 显示代码 + st.code(library_indicator.get('code', '无代码'), language='python') + + +def render_current_indicator_detail(): + """渲染当前指标详情""" + current_indicator = st.session_state.indicator_manager.get_indicator(st.session_state.current_indicator_id) + + if current_indicator: + # 指标名称和保存按钮 + render_indicator_header(current_indicator) + + # 查询语句 + render_query_input_section(current_indicator) + + # 代码执行部分 + render_code_execution_section(current_indicator) + else: + st.warning("找不到当前指标,可能已被删除") + + +def render_indicator_header(current_indicator): + """渲染指标头部(名称和保存按钮)""" + col1, col2, col3 = st.columns([1, 4, 2]) + + with col1: + st.markdown('
指标名称:
', unsafe_allow_html=True) + + with col2: + new_name = st.text_input("指标名称", value=current_indicator.name, key="indicator_name", label_visibility="collapsed") + if new_name != current_indicator.name: + if new_name is None or not new_name.strip(): + st.error("指标名称不能为空") + else: + try: + st.session_state.indicator_manager.update_indicator( + current_indicator.id, name=new_name + ) + except ValueError as e: + st.error(str(e)) + + with col3: + render_save_indicator_button(current_indicator, new_name) + + +def render_save_indicator_button(current_indicator, new_name): + """渲染保存指标按钮""" + if st.button("💾 保存指标", use_container_width=True): + if new_name is None or not new_name.strip(): + st.error("指标名称不能为空") + elif not current_indicator.query.strip(): + st.error("请先输入查询语句") + elif not current_indicator.code: + st.error("请先生成指标代码") + else: + try: + save_indicator_to_library(current_indicator) + st.toast("指标已保存") + st.rerun() # 刷新页面以更新指标库列表 + except Exception as e: + st.error(f"保存失败: {str(e)}") + + +def save_indicator_to_library(current_indicator): + """保存指标到指标库""" + # 保存到指标管理器 + st.session_state.indicator_manager.save_indicators() + + # 同时保存到指标库 + library_path = os.path.join(DATA_DIR, "code.jsonl") + new_record = { + "name": current_indicator.name, + "query": current_indicator.query, + "code": current_indicator.code, + "created_at": time.time() + } + + # 追加到文件 + with open(library_path, "a", encoding="utf-8") as f: + f.write(json.dumps(new_record, ensure_ascii=False) + "\n") + + # 清除指标库的缓存,以便重新加载 + load_jsonl.clear() + + +def render_query_input_section(current_indicator): + """渲染查询输入部分""" + title_col, test_btn_col, result_col = st.columns([1, 1, 4]) + with title_col: + st.subheader("查询语句") + with result_col: + title_query_result = st.subheader("") + + # 查询输入框 + query = st.text_area( + "输入查询语句", + value=current_indicator.query, + height=70, + key="query_input", + placeholder="在此输入查询语句...", + label_visibility="collapsed" + ) + + # 应用自动补全 + apply_autocomplete_to_query_input() + + if query != current_indicator.query: + st.session_state.indicator_manager.update_indicator( + current_indicator.id, query=query + ) + + # 生成代码按钮 + with test_btn_col: + render_generate_code_button(query, title_query_result, current_indicator) + + +def render_generate_code_button(query, title_query_result, current_indicator): + """渲染生成代码按钮""" + if st.button("🔄 生成代码", key="generate_code_btn", use_container_width=True): + with st.spinner("正在生成指标代码..."): + # 使用缓存的测试函数 + result = generate_indicator_code(query) + + # 根据结果显示不同的状态 + if result.startswith("✅"): + title_query_result.success(result) + # 生成成功后,立即保存代码到指标对象中 + if "generated_code" in st.session_state: + st.session_state.indicator_manager.update_indicator( + current_indicator.id, + code=st.session_state.generated_code + ) + elif result.startswith("❌"): + title_query_result.error(result) + else: + # 显示实体识别结果或其他信息 + title_query_result.info(result) + + +def render_code_execution_section(current_indicator): + """渲染代码执行部分""" + title_col, run_btn_col, result_col = st.columns([1, 1, 4]) + with title_col: + st.subheader("指标代码") + with result_col: + title_code_result = st.subheader("") + with run_btn_col: + if st.button("执行代码", key="execute_code_btn", use_container_width=True): + with st.spinner("正在执行代码..."): + if "code_executor" in st.session_state and "generated_code" in st.session_state: + execute_and_update_code(current_indicator, title_code_result) + else: + title_code_result.error("代码执行器未初始化或没有生成的代码") + + # 显示代码 + st.code(st.session_state.get("generated_code", "# 暂无代码,请输入有效的查询语句并点击测试按钮"), language="python") + + +def execute_and_update_code(current_indicator, title_code_result): + """执行并更新代码""" + result = st.session_state.code_executor.execute_code(st.session_state.generated_code) + if result and hasattr(result, 'get') and result.get('status'): + title_code_result.success("执行成功") + title_code_result.json(result.get('data', {})) + # 更新指标的代码 + st.session_state.indicator_manager.update_indicator( + current_indicator.id, + code=st.session_state.generated_code + ) + else: + title_code_result.error(f"执行失败: {result.get('message', '未知错误') if hasattr(result, 'get') else str(result)}") + + +def execute_indicator_code(code, result_container): + """执行指标代码""" + with st.spinner('正在执行代码...'): + # 确保code_executor已初始化 + if "code_executor" in st.session_state: + result = st.session_state.code_executor.execute_code(code) + if result and hasattr(result, 'get') and result.get('status'): + result_container.success("执行成功") + result_container.json(result.get('data', {})) + else: + result_container.error(f"执行失败: {result.get('message', '未知错误') if hasattr(result, 'get') else str(result)}") + else: + result_container.error("代码执行器未初始化") + + if __name__ == "__main__": main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 78d697e..37fc6bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ pyyaml neo4j langchain-neo4j langgraph -chardet \ No newline at end of file +chardet +streamlit-textcomplete \ No newline at end of file diff --git a/src/code_executor.py b/src/code_executor.py index 533a4d2..8961c7f 100644 --- a/src/code_executor.py +++ b/src/code_executor.py @@ -123,8 +123,8 @@ class CodeExecutor: sys.stdout = old_stdout output = redirected_output.getvalue().strip() - if not isinstance(result_dict, dict) or len(result_dict) != 4: - raise ValueError("函数应返回包含4个元素的字典(status, data, error, helper_info)") + if not isinstance(result_dict, dict) or len(result_dict) < 3: + raise ValueError("函数应返回至少包含3个元素的字典(status, message, data)") logger.debug(f"执行结果: {result_dict}") diff --git a/src/project.py b/src/project.py index b759691..7b28cd0 100644 --- a/src/project.py +++ b/src/project.py @@ -17,8 +17,6 @@ class ProjectToolkit(ABC): def __init__(self, config: Any): pass - # 项目划分查询方法 - @abstractmethod def get_division_by_name(self, name_part): """ @@ -71,7 +69,6 @@ class ProjectToolkit(ABC): """ pass - # 工程量查询方法 @abstractmethod def get_quantities_by_paths(self, paths_str): """ @@ -128,7 +125,6 @@ class ProjectToolkit(ABC): """ pass - # 材机查询方法 @abstractmethod def get_material_equipment_by_path(self, paths_str): """ @@ -164,7 +160,6 @@ class ProjectToolkit(ABC): """ pass - # 取费查询方法 @abstractmethod def get_fee_template_by_path(self, paths_str): """ @@ -202,7 +197,6 @@ class ProjectToolkit(ABC): """ pass - # 费用表查询方法 @abstractmethod def get_fee_schedule_on_auxiliary_expense_table(self, table_name, fee_name, fee_attribute: str): """ @@ -298,7 +292,6 @@ class ProjectToolkit(ABC): """ pass - # 工程属性查询方法 @abstractmethod def get_project_property(self, property_name): """ @@ -316,7 +309,6 @@ class ProjectToolkit(ABC): """ pass - # 项目划分查找取费表 @abstractmethod def get_fee_table_by_project_division(self, project_division_path, fee_name): """ @@ -335,7 +327,6 @@ class ProjectToolkit(ABC): """ pass - # 清单查找取费表 @abstractmethod def get_fee_table_by_list(self, parent_path, list_code, list_unit, fee_name): """ @@ -356,7 +347,6 @@ class ProjectToolkit(ABC): """ pass - # 定额节点查找合价 @abstractmethod def get_fee_table_by_quoto_code(self, parent_path, quantity_type, code, fee_name): """ @@ -377,7 +367,6 @@ class ProjectToolkit(ABC): """ pass - # 工程量节点查找合价 @abstractmethod def get_fee_table_by_quantities_name(self, parent_path, quantity_type, quantity_name, fee_name): """ @@ -398,7 +387,6 @@ class ProjectToolkit(ABC): """ pass - # 材机节点查找市场价 @abstractmethod def get_fee_by_material_equipment_code(self, parent_path, material_equipment_code, fee_name): """ @@ -418,7 +406,6 @@ class ProjectToolkit(ABC): """ pass - # 查找清单节点 @abstractmethod def get_fee_table_by_list_name(self, parent_path, list_code, unit): """ diff --git a/src/prompt_manager.py b/src/prompt_manager.py index bbfd7bb..83381e0 100644 --- a/src/prompt_manager.py +++ b/src/prompt_manager.py @@ -66,19 +66,31 @@ def project_get_calculate_function(): result_dict = project.[SELECTED_METHOD]([PARAMETERS]) status = result_dict.get('status', False) message = result_dict.get('message', '') - code = result_dict.get('data', '') - data = result_dict.get('data', []) + quantity = 0.0 + if isinstance(result_dict, dict) and '数量' in result_dict: + quantity = result_dict['数量'] + if status: - return result_dict + return { + "code": 200, + "message": 'ok', + "status": True, + "data": quantity + } else: - return result_dict + return { + "code": 500, + "message": message, + "status": False, + "data": quantity + } # 执行规则 - 参数必须从用户问题或上下文信息中提取。 - 在代码函数内部生成功能说明,在函数外禁止生成任何注释或解释或非代码内容。 -- 输出代码中必须以def project_get_calculate_function() -> dict函数作为入口函数,该函数成功时返回的'data'通常是浮点或整型值,除非用户要求返回其他类型。 +- 输出代码中必须以def project_get_calculate_function() -> dict函数作为入口函数,该函数返回字典包含:'code'、'status'、'message'、'data'四个字段。 - 必须确保生成的代码可以直接执行,代码要注意进行各类容错检查。 -- 如果project_get_calculate_function函数需要返回浮点整形数,函数中间发生错误或找不到对象也必须返回成功,data为0,并在message说明错误原因。 +- 'data'字段通常要求是浮点或整型值,除非用户要求返回其他类型,同时函数执行过程中发生错误,'data'字段也必须为0,并在message说明错误原因。 - ProjectToolkit 类中涉及项目划分的函数已考虑在其及其子孙项目划分下查找,所以无需生成递归子项目划分的代码。 - 如果文本中包含范围编码格式则需要进行编码展开,如'YX2-1~7'展开为‘YX2-1/YX2-2/YX2-3/YX2-4/YX2-5/YX2-6/YX2-7’ """ diff --git a/tests/test_runcode.py b/tests/test_runcode.py index a476882..2129122 100644 --- a/tests/test_runcode.py +++ b/tests/test_runcode.py @@ -73,7 +73,7 @@ def project_get_calculate_function(): output = redirected_output.getvalue().strip() if not isinstance(result_dict, dict) or len(result_dict) != 4: - raise ValueError("函数应返回包含4个元素的字典(status, data, error, helper_info)") + raise ValueError("函数应返回包含4个元素的字典(code, status, message, data)") logger.debug(f"执行结果: {result_dict}") diff --git a/tests/test_user_interaction.py b/tests/test_user_interaction.py deleted file mode 100644 index 38fd1b1..0000000 --- a/tests/test_user_interaction.py +++ /dev/null @@ -1,184 +0,0 @@ -# tests/test_userinteraction.py - -import os -import sys -import json -import logging -import os -from datetime import datetime -sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -current_file = os.path.splitext(os.path.basename(__file__))[0] -now_str = datetime.now().strftime("%Y%m%d%H%M%S") -log_filename = f"{current_file}_{now_str}.log" - -logging.basicConfig( - level=logging.DEBUG, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - handlers=[ - logging.FileHandler(os.path.join("logs", log_filename), encoding="utf-8"), - logging.StreamHandler() - ], -) - -logger = logging.getLogger(current_file) - -def setup_logger(logger_name): - """ - 设置指定名称的logger,将其级别设置为WARNING并禁用传播 - :param logger_name: logger的名称 - """ - logger = logging.getLogger(logger_name) - logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别 - logger.propagate = False # 可选:禁用传播(防止被根logger处理) - return logger - - -logger_names = ["httpx", "openai", "langsmith.client", "neo4j", "urllib3", "httpcore"] -for name in logger_names: - setup_logger(name) - -from src.config import Config -from src.document_loader import load_file -from src.multi_llm_client import MultiAPIKeyChatOpenAI -from src.user_interaction import UserInteraction -import json -from src.dialog_manager import DialogManager -from src.multi_llm_client import MultiAPIKeyChatOpenAI -from src.code_executor import CodeExecutor -from src.neo4j_raw_retriever import Neo4jRawRetriever -from src.prompt_manager import PromptManager -import yaml -from src.config import Config -from src.document_loader import load_file -from src.embedding_client import EmbeddingClient - -from src.project import ProjectBuilder, ProjectToolkit -from src.project_implementation import ProjectToolkitNeo4j - -success_count = 0 -fail_count = 0 -questions = [] -error_list = [] -success_list = [] - -def main(): - global success_count, fail_count, questions, error_list, success_list - - config = Config() - - business_structure = load_file(config.business_object_structure_path) - bowei_api_docs = load_file(config.bowei_api_docs_path) - - llm_client = MultiAPIKeyChatOpenAI(config.openai) - user_interaction = UserInteraction(llm_client.llm, business_structure) - - llm_client_coder = MultiAPIKeyChatOpenAI(config.openai_coder) - - prompt_manager = PromptManager() - - neo4j_conf = config.neo4j_conf - embedding_conf = config.embedding - - embedding_client = EmbeddingClient(embedding_conf) - - # 创建Neo4j检索器 - knowledge_retriever = Neo4jRawRetriever(neo4j_conf) - - ProjectBuilder.register(ProjectToolkitNeo4j, knowledge_retriever.driver) - - code_executor = CodeExecutor(prompt_manager.prompts, llm_client_coder, config.max_retries) - - dialog_manager = DialogManager( - llm_client, - business_structure, - bowei_api_docs, - code_executor, - knowledge_retriever, - prompt_manager, - ) - - # 读取 zhibiao.json - zhibiao_path = os.path.join(os.path.dirname(__file__), "zhibiao.json") - with open(zhibiao_path, "r", encoding="utf-8") as f: - zhibiao_data = json.load(f) - - isTest = True - isTest = False - - if isTest: - zhibiao_data = [ - { - "指标名称": "杆塔总基数", - "指标描述": { - "指标映射": "从【架空输电线路本体工程/附件安装工程】项目划分中获取名称属于【'合计'】的费用", - "映射规则": "YX2-1~7" - }, - "code": "", - "单位": "基", - "单价类型": None, - "序号": "1", - "提取方式": None, - "指标类型": "工程量指标", - "数据来源": "定额数量" - } - ] - - for idx, item in enumerate(zhibiao_data): - name = item.get("指标名称", "") - datasource = item.get("数据来源", "") - if datasource in ("报表指标", "指标库"): - logger.info(f"跳过索引 {idx},数据来源为 {datasource}") - continue - - query = item.get("指标描述", {}).get("指标映射", "") - if not query: - logger.warning(f"索引 {idx} 缺少指标映射,跳过") - continue - - try: - # 调用用户交互理解接口(同步调用) - result = user_interaction.understand(query) - if not result: - logger.error(f"问题: {query} 没有找到符合要求的数据") - fail_count += 1 - error_list.append(f"指标名称 {name} 问题 {query} 调用 understand 返回空结果") - continue - - # 这里示例只打印理解结果,你可以根据业务逻辑替换为后续处理 - logger.info( - f"指标名称 {name} 问题: {query} 理解结果: " - f"{[{'name': r.get('name'), 'constraints': r.get('constraints')} for r in result]}" - ) - - success_list.append({ - "name": name, - "query": query, - "result": [{'name': r.get('name'), 'constraints': r.get('constraints')} for r in result] - }) - success_count += 1 - - except Exception as e: - logger.error(f"指标名称 {name} 问题: {query} 处理异常: {e}") - fail_count += 1 - error_list.append(f"指标名称 {name} 问题 {query} 异常: {e}") - - total = success_count + fail_count - success_rate = (success_count / total) * 100 if total > 0 else 0 - fail_rate = (fail_count / total) * 100 if total > 0 else 0 - - print(f"问题总数: {total}") - print(f"成功比例: {success_rate:.2f}%)") - print(f"失败比例: {fail_rate:.2f}%)") - print("错误列表:") - for error in error_list: - print(error) - - # 将成功内容保存为 jsonl 文件 - success_jsonl_path = os.path.join(os.path.dirname(__file__), f"zhibiao_{now_str}.jsonl") - with open(success_jsonl_path, "w", encoding="utf-8") as f: - for item in success_list: - f.write(json.dumps(item, ensure_ascii=False) + "\n") - -if __name__ == "__main__": - main() diff --git a/tests/zhibiao.json b/tests/zhibiao.json index 13cda14..201556a 100644 --- a/tests/zhibiao.json +++ b/tests/zhibiao.json @@ -169,7 +169,7 @@ "指标类型": "工程量指标", "数据来源": "定额数量", "指标描述": { - "指标映射": "从【架空输电线路本体工程/基础工程】及其子孙项目划分中查找编码中包含【YX2-1~7】的所有【定额】的【数量】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/基础工程】下编码包含【YX2-1~7】的所有【定额】的【数量】之和", "映射规则": "YX2-1~7" } }, @@ -199,7 +199,7 @@ "指标类型": "工程量指标", "数据来源": "主材数量", "指标描述": { - "指标映射": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【角钢】的所有【主材】的【数量】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及名称包含【角钢】的所有【主材】的【数量】之和", "映射规则": "角钢" } }, @@ -213,7 +213,7 @@ "指标类型": "工程量指标", "数据来源": "主材数量", "指标描述": { - "指标映射": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【角钢、高强】的所有【主材】的【数量】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及名称包含【角钢、高强】的所有【主材】的【数量】之和", "映射规则": "角钢、高强" } }, @@ -243,7 +243,7 @@ "指标类型": "单价指标", "数据来源": "主材单价", "指标描述": { - "指标映射": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【角钢】的所有【主材】的【单价】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及名称包含【角钢】的所有【主材】的【单价】之和", "映射规则": "角钢" } }, @@ -273,7 +273,7 @@ "指标类型": "单价指标", "数据来源": "主材单价", "指标描述": { - "指标映射": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【角钢、高强】的所有【主材】的【单价】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及名称包含【角钢、高强】的所有【主材】的【单价】之和", "映射规则": "角钢、高强" } }, @@ -303,7 +303,7 @@ "指标类型": "工程量指标", "数据来源": "主材数量", "指标描述": { - "指标映射": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【钢管塔】的所有【主材】的【数量】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及名称包含【钢管塔】的所有【主材】的【数量】之和", "映射规则": "钢管塔" } }, @@ -333,7 +333,7 @@ "指标类型": "单价指标", "数据来源": "主材单价", "指标描述": { - "指标映射": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【钢管塔】的所有【主材】的【单价】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及名称包含【钢管塔】的所有【主材】的【单价】之和", "映射规则": "钢管塔" } }, @@ -363,7 +363,7 @@ "指标类型": "工程量指标", "数据来源": "主材数量", "指标描述": { - "指标映射": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【钢管杆】的所有【主材】的【数量】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及名称包含【钢管杆】的所有【主材】的【数量】之和", "映射规则": "钢管杆" } }, @@ -393,7 +393,7 @@ "指标类型": "单价指标", "数据来源": "主材单价", "指标描述": { - "指标映射": "从【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及其子孙项目划分中查找名称中包含【钢管杆】的所有【主材】的【单价】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/杆塔工程/杆塔组立/铁塔、钢管杆组立】及名称包含【钢管杆】的所有【主材】的【单价】之和", "映射规则": "钢管杆" } }, @@ -473,7 +473,7 @@ "指标类型": "技术指标", "数据来源": "定额参数", "指标描述": { - "指标映射": "从【架空输电线路本体工程/架线工程】及其子孙项目划分中查找编码中包含【['YX5-67', 'YX5-68', 'YX5-69', 'YX5-78', 'YX5-79', 'YX5-59', 'YX5-60', 'YX5-61', 'YX5-62', 'YX5-63', 'YX5-64', 'YX5-65', 'YX5-66', 'YX5-74', 'YX5-75', 'YX5-76', 'YX5-77', 'YX5-52', 'YX5-53', 'YX5-54', 'YX5-55', 'YX5-56', 'YX5-57', 'YX5-58', 'YX5-70', 'YX5-71', 'YX5-72', 'YX5-73', 'YX5-14', 'YX5-15', 'YX5-16', 'YX5-17', 'YX5-43', 'YX5-44', 'YX5-45', 'YX5-46', 'YX5-47', 'YX5-48', 'YX5-49', 'YX5-50', 'YX5-51', 'YX5-10', 'YX5-11', 'YX5-12', 'YX5-13', 'YX5-38', 'YX5-39', 'YX5-40', 'YX5-41', 'YX5-42', 'YX5-8', 'YX5-9']】的所有【定额】的【参数】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/架线工程】下编码包含【['YX5-67', 'YX5-68', 'YX5-69', 'YX5-78', 'YX5-79', 'YX5-59', 'YX5-60', 'YX5-61', 'YX5-62', 'YX5-63', 'YX5-64', 'YX5-65', 'YX5-66', 'YX5-74', 'YX5-75', 'YX5-76', 'YX5-77', 'YX5-52', 'YX5-53', 'YX5-54', 'YX5-55', 'YX5-56', 'YX5-57', 'YX5-58', 'YX5-70', 'YX5-71', 'YX5-72', 'YX5-73', 'YX5-14', 'YX5-15', 'YX5-16', 'YX5-17', 'YX5-43', 'YX5-44', 'YX5-45', 'YX5-46', 'YX5-47', 'YX5-48', 'YX5-49', 'YX5-50', 'YX5-51', 'YX5-10', 'YX5-11', 'YX5-12', 'YX5-13', 'YX5-38', 'YX5-39', 'YX5-40', 'YX5-41', 'YX5-42', 'YX5-8', 'YX5-9']】的所有【定额】的【参数】之和", "映射规则": "| 资源识别规则 | 指标值 |\n|-------|-------|\n| YX5-67~69、YX5-78~79 | 8 |\n| YX5-59~66、YX5-74~77 | 6 |\n| YX5-52~58、YX5-70~73 | 4 |\n| YX5-14~17、YX5-43~51 | 2 |\n| YX5-10~13、YX5-38~42、YX5-8~9 | 1 |" } }, @@ -519,7 +519,7 @@ "指标类型": "工程量指标", "数据来源": "主材数量", "指标描述": { - "指标映射": "从【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设】及其子孙项目划分中查找名称中包含【高导电率】的所有【主材】的【数量】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设】及名称包含【高导电率】的所有【主材】的【数量】之和", "映射规则": "高导电率" } }, @@ -549,7 +549,7 @@ "指标类型": "单价指标", "数据来源": "主材单价", "指标描述": { - "指标映射": "从【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设】及其子孙项目划分中查找名称中包含【线】的所有【主材】的【单价】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设】及名称包含【线】的所有【主材】的【单价】之和", "映射规则": "线" } }, @@ -579,7 +579,7 @@ "指标类型": "单价指标", "数据来源": "主材单价", "指标描述": { - "指标映射": "从【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设】及其子孙项目划分中查找名称中包含【高导电率】的所有【主材】的【单价】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设】及名称包含【高导电率】的所有【主材】的【单价】之和", "映射规则": "高导电率" } }, @@ -593,7 +593,7 @@ "指标类型": "技术指标", "数据来源": "主材参数", "指标描述": { - "指标映射": "从【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设@@架空输电线路本体工程/架线工程/其他架线工程】及其子孙项目划分中查找名称中包含【['钢芯铝绞线', '铝包钢芯铝绞线', '中强度铝合金绞线', '铝合金芯铝绞线', '铝合金芯高导电率铝绞线', '钢芯高导电率铝绞线', '特高强度钢芯铝合金绞线', '扩径导线', '耐热导线', '碳纤维导线']】的所有【主材】的【参数】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/架线工程/导地线架设@@架空输电线路本体工程/架线工程/导地线跨越架设@@架空输电线路本体工程/架线工程/其他架线工程】及名称包含【['钢芯铝绞线', '铝包钢芯铝绞线', '中强度铝合金绞线', '铝合金芯铝绞线', '铝合金芯高导电率铝绞线', '钢芯高导电率铝绞线', '特高强度钢芯铝合金绞线', '扩径导线', '耐热导线', '碳纤维导线']】的所有【主材】的【参数】之和", "映射规则": "| 资源识别规则 | 指标值 |\n|-------|-------|\n| 钢芯铝绞线 | 钢芯铝绞线 |\n| 铝包钢芯铝绞线 | 铝包钢芯铝绞线 |\n| 中强度铝合金绞线 | 中强度铝合金绞线 |\n| 铝合金芯铝绞线 | 铝合金芯铝绞线 |\n| 铝合金芯高导电率铝绞线 | 铝合金芯高导电率铝绞线 |\n| 钢芯高导电率铝绞线 | 钢芯高导电率铝绞线 |\n| 特高强度钢芯铝合金绞线 | 特高强度钢芯铝合金绞线 |\n| 扩径导线 | 扩径导线 |\n| 耐热导线 | 耐热导线 |\n| 碳纤维导线 | 碳纤维导线 |" } }, @@ -1431,7 +1431,7 @@ "指标类型": "工程量指标", "数据来源": "主材数量", "指标描述": { - "指标映射": "从【架空输电线路本体工程/基础工程/基础砌筑】及其子孙项目划分中查找名称中包含【圆钢】的所有【主材】的【数量】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/基础工程/基础砌筑】及名称包含【圆钢】的所有【主材】的【数量】之和", "映射规则": "圆钢" } }, @@ -1445,7 +1445,7 @@ "指标类型": "单价指标", "数据来源": "主材单价", "指标描述": { - "指标映射": "从【架空输电线路本体工程/基础工程/基础砌筑】及其子孙项目划分中查找名称中包含【圆钢】的所有【主材】的【单价】之和", + "指标映射": "获取项目划分【架空输电线路本体工程/基础工程/基础砌筑】及名称包含【圆钢】的所有【主材】的【单价】之和", "映射规则": "圆钢" } }, @@ -1879,7 +1879,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【工程费用】中获取【架空输电线路本体工程】的【合计费】的属性", + "指标映射": "从【工程费用】中获取【架空输电线路本体工程】的【合计费】的值", "映射规则": "@工程费用.架空输电线路本体工程.合计费" } }, @@ -1893,7 +1893,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程】项目划分中获取取费费用名称属于【人工费】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程】下取费名称等于【人工费】的费用", "映射规则": "人工费" } }, @@ -1907,7 +1907,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程】项目划分中获取取费费用名称属于【人工费】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程】下取费名称等于【人工费】的费用", "映射规则": "人工费" } }, @@ -1921,7 +1921,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程】项目划分中获取取费费用名称属于【施工机械使用费】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程】下取费名称等于【施工机械使用费】的费用", "映射规则": "施工机械使用费" } }, @@ -1935,7 +1935,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程】项目划分中获取取费费用名称属于【施工机械使用费】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程】下取费名称等于【施工机械使用费】的费用", "映射规则": "施工机械使用费" } }, @@ -1949,7 +1949,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程/基础工程】项目划分中获取取费费用名称属于【合计】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程/基础工程】下取费名称等于【合计】的费用", "映射规则": "合计" } }, @@ -1963,7 +1963,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程/杆塔工程】项目划分中获取取费费用名称属于【合计】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程/杆塔工程】下取费名称等于【合计】的费用", "映射规则": "合计" } }, @@ -1977,7 +1977,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程/接地工程】项目划分中获取取费费用名称属于【合计】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程/接地工程】下取费名称等于【合计】的费用", "映射规则": "合计" } }, @@ -1991,7 +1991,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程/架线工程】项目划分中获取取费费用名称属于【合计】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程/架线工程】下取费名称等于【合计】的费用", "映射规则": "合计" } }, @@ -2005,7 +2005,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程/附件安装工程】项目划分中获取取费费用名称属于【合计】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程/附件安装工程】下取费名称等于【合计】的费用", "映射规则": "合计" } }, @@ -2019,7 +2019,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程/辅助工程】项目划分中获取取费费用名称属于【合计】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程/辅助工程】下取费名称等于【合计】的费用", "映射规则": "合计" } }, @@ -2033,7 +2033,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程/辅助工程】项目划分中获取取费费用名称属于【合计】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程/辅助工程】下取费名称等于【合计】的费用", "映射规则": "合计" } }, @@ -2047,7 +2047,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【工程费用】中获取【辅助设施工程】的【合计费】的属性", + "指标映射": "从【工程费用】中获取【辅助设施工程】的【合计费】的值", "映射规则": "@工程费用.辅助设施工程.合计费" } }, @@ -2061,7 +2061,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【工程费用】中获取【其他费用】的【合计费】的属性", + "指标映射": "从【工程费用】中获取【其他费用】的【合计费】的值", "映射规则": "@工程费用.其他费用.合计费" } }, @@ -2075,7 +2075,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【其他费用】中获取【建设场地征用及清理费】的属性", + "指标映射": "从【其他费用】中获取【建设场地征用及清理费】的值", "映射规则": "@其他费用.建设场地征用及清理费" } }, @@ -2089,7 +2089,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【其他费用】中获取【项目建设管理费】的属性", + "指标映射": "从【其他费用】中获取【项目建设管理费】的值", "映射规则": "@其他费用.项目建设管理费" } }, @@ -2103,7 +2103,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【其他费用】中获取【工程监理费】的属性", + "指标映射": "从【其他费用】中获取【工程监理费】的值", "映射规则": "@其他费用.工程监理费" } }, @@ -2117,7 +2117,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【其他费用】中获取【项目建设技术服务费】的属性", + "指标映射": "从【其他费用】中获取【项目建设技术服务费】的值", "映射规则": "@其他费用.项目建设技术服务费" } }, @@ -2131,7 +2131,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【其他费用】中获取【项目前期工作费】的属性", + "指标映射": "从【其他费用】中获取【项目前期工作费】的值", "映射规则": "@其他费用.项目前期工作费" } }, @@ -2145,7 +2145,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【其他费用】中获取【勘察费】的属性", + "指标映射": "从【其他费用】中获取【勘察费】的值", "映射规则": "@其他费用.勘察费" } }, @@ -2159,7 +2159,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【其他费用】中获取【设计费】的属性", + "指标映射": "从【其他费用】中获取【设计费】的值", "映射规则": "@其他费用.设计费" } }, @@ -2173,7 +2173,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【其他费用】中获取【工程建设检测费】的属性", + "指标映射": "从【其他费用】中获取【工程建设检测费】的值", "映射规则": "@其他费用.工程建设检测费" } }, @@ -2187,7 +2187,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【其他费用】中获取【生产准备费】的属性", + "指标映射": "从【其他费用】中获取【生产准备费】的值", "映射规则": "@其他费用.生产准备费" } }, @@ -2201,7 +2201,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程】项目划分中获取取费费用名称属于【安全文明施工费】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程】下取费名称等于【安全文明施工费】的费用", "映射规则": "安全文明施工费" } }, @@ -2215,7 +2215,7 @@ "指标类型": "造价指标", "数据来源": "项目划分费用", "指标描述": { - "指标映射": "从【架空输电线路本体工程】项目划分中获取取费费用名称属于【安全文明施工费】的费用", + "指标映射": "获取项目划分【架空输电线路本体工程】下取费名称等于【安全文明施工费】的费用", "映射规则": "安全文明施工费" } }, @@ -2229,7 +2229,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【工程费用】中获取【基本预备费】的【合计费】的属性", + "指标映射": "从【工程费用】中获取【基本预备费】的【合计费】的值", "映射规则": "@工程费用.基本预备费.合计费" } }, @@ -2243,7 +2243,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【工程费用】中获取【工程静态投资(一~七项合计)】的【合计费】的属性", + "指标映射": "从【工程费用】中获取【工程静态投资(一~七项合计)】的【合计费】的值", "映射规则": "@工程费用.工程静态投资(一~七项合计).合计费" } }, @@ -2257,7 +2257,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【工程费用】中获取【建设期贷款利息】的【合计费】的属性", + "指标映射": "从【工程费用】中获取【建设期贷款利息】的【合计费】的值", "映射规则": "@工程费用.建设期贷款利息.合计费" } }, @@ -2271,7 +2271,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【工程费用】中获取【工程动态投资(一~八项合计)】的【合计费】的属性", + "指标映射": "从【工程费用】中获取【工程动态投资(一~八项合计)】的【合计费】的值", "映射规则": "@工程费用.工程动态投资(一~八项合计).合计费" } }, @@ -2285,7 +2285,7 @@ "指标类型": "造价指标", "数据来源": "工程费用", "指标描述": { - "指标映射": "从【工程费用】中获取【其中:可抵扣增值税额】的【合计费】的属性", + "指标映射": "从【工程费用】中获取【其中:可抵扣增值税额】的【合计费】的值", "映射规则": "@工程费用.其中:可抵扣增值税额.合计费" } } diff --git a/tools/xml_to_json.py b/tools/xml_to_json.py index af37f19..03f7d59 100644 --- a/tools/xml_to_json.py +++ b/tools/xml_to_json.py @@ -145,7 +145,7 @@ def xml_to_json(xml_content, output_path): result.append(base_item) elif data_sources in project_division: - mapping_desc = f"从【{index_extraction_scope}】项目划分中获取取费费用名称属于【{keyword}】的费用" + mapping_desc = f"获取项目划分【{index_extraction_scope}】下取费名称等于【{keyword}】的费用" base_item["指标描述"] = { "指标映射": mapping_desc, "映射规则": parsed["映射规则"] @@ -162,11 +162,11 @@ def xml_to_json(xml_content, output_path): # 取数据来源的开头两个字(如"定额"、"清单"等) temp3 = data_sources[0:2] if temp3 in ["清单", "定额", "人材机"]: - mapping_desc = f"从【{index_extraction_scope}】及其子孙项目划分中查找编码中包含【{temp0}】的所有【{temp3}】的【{temp2}】之和" + mapping_desc = f"获取项目划分【{index_extraction_scope}】下编码包含【{temp0}】的所有【{temp3}】的【{temp2}】之和" elif temp3 in ["主材", "设备"]: - mapping_desc = f"从【{index_extraction_scope}】及其子孙项目划分中查找名称中包含【{temp0}】的所有【{temp3}】的【{temp2}】之和" + mapping_desc = f"获取项目划分【{index_extraction_scope}】及名称包含【{temp0}】的所有【{temp3}】的【{temp2}】之和" else: - mapping_desc = f"从【{data_sources}】中获取{temp0}的属性" + mapping_desc = f"从【{data_sources}】中获取{temp0}的值" base_item["指标描述"] = { "指标映射": mapping_desc, @@ -221,6 +221,6 @@ def xml_to_json(xml_content, output_path): return "结果已保存" xml_content = read_xml_as_string('dataset/主网架空线路造价分析指标.xml') -json_output = xml_to_json(xml_content, output_path= "./tests/zhibiao.json") +json_output = xml_to_json(xml_content, output_path= "./data/zhibiao.json") print("转换完毕!")