| |
| """ |
| Gradio MCP Server for VDI Heat Pump Data Parsing and Querying |
| Provides tools to download, parse VDI files and query specific heat pump information |
| """ |
|
|
| import asyncio |
| import json |
| import os |
| import sys |
| import pandas as pd |
| import numpy as np |
| from typing import Any, Dict, List, Optional |
| import zipfile |
| import tempfile |
| from pathlib import Path |
| import requests |
| import xml.etree.ElementTree as ET |
| import gradio as gr |
|
|
| |
| import logging |
| logging.basicConfig(level=logging.DEBUG) |
|
|
| |
| def parse_010(r): |
| """Parse 010:Vorlaufdaten""" |
| return { |
| 'blatt': r[1] if len(r) > 1 else None, |
| 'hersteller': r[3] if len(r) > 3 else None, |
| 'datum': r[4] if len(r) > 4 else None, |
| } |
|
|
| def parse_100(r): |
| """Parse 100:Einsatzbereich""" |
| return { |
| 'index_100': int(r[1]) if len(r) > 1 else None, |
| 'einsatzbereich': r[3] if len(r) > 3 else None, |
| } |
|
|
| def parse_110(r): |
| """Parse 110:Typ""" |
| return { |
| 'index_110': int(r[1]) if len(r) > 1 else None, |
| 'typ': r[3] if len(r) > 3 else None, |
| } |
|
|
| def parse_400(r): |
| """Parse 400:Bauart""" |
| return { |
| 'index_400': int(r[1]) if len(r) > 1 else None, |
| 'bauart': r[2] if len(r) > 2 else None, |
| } |
|
|
| def parse_450(r): |
| """Parse 450:Aufstellungsort""" |
| return { |
| 'index_450': int(r[1]) if len(r) > 1 else None, |
| 'aufstellungsort': r[2] if len(r) > 2 else None, |
| } |
|
|
| def parse_460(r): |
| """Parse 460:Leistungsregelung der Wärmepumpe""" |
| return { |
| 'index_460': int(r[1]) if len(r) > 1 else None, |
| 'leistungsregelung_der_wärmepumpe': r[2] if len(r) > 2 else None, |
| } |
|
|
| def parse_700(r): |
| """Parse 700:Produktelementdaten""" |
| return { |
| 'index_700': int(r[1]) if len(r) > 1 else None, |
| 'sortiernummer': r[2] if len(r) > 2 else None, |
| 'produktname': r[3] if len(r) > 3 else None, |
| 'heizleistung': r[4] if len(r) > 4 else None, |
| 'leistungszahl': r[5] if len(r) > 5 else None, |
| 'elektrische_aufnahmeleistung_wärmepumpe': r[6] if len(r) > 6 else None, |
| 'leistung_der_elektrischen_zusatzheizung': r[7] if len(r) > 7 else None, |
| 'elektroanschluss': r[8] if len(r) > 8 else None, |
| 'anlaufstrom': r[9] if len(r) > 9 else None, |
| 'index_auf_satzart_200_wärmequelle': r[10] if len(r) > 10 else None, |
| 'eingesetztes_kältemittel': r[11] if len(r) > 11 else None, |
| 'füllmenge_des_kältemittels': r[12] if len(r) > 12 else None, |
| 'schallleistungspegel': r[13] if len(r) > 13 else None, |
| 'schutzart_nach_din_en_60529': r[14] if len(r) > 14 else None, |
| 'maximale_vorlauftemperatur': r[15] if len(r) > 15 else None, |
| 'heizwassertemperaturspreizung': r[16] if len(r) > 16 else None, |
| 'trinkwasser_erwärmung_über_indirekt_beheizten_speicher': r[17] if len(r) > 17 else None, |
| 'kühlfunktion': r[19] if len(r) > 19 else None, |
| 'kühlleistung': r[20] if len(r) > 20 else None, |
| 'verdichteranzahl': r[21] if len(r) > 21 else None, |
| 'leistungsstufen': r[22] if len(r) > 22 else None, |
| 'produktserie': r[32] if len(r) > 32 else None, |
| 'treibhauspotential_gwp': r[33] if len(r) > 33 else None, |
| 'bauart_des_kältekreis': r[35] if len(r) > 35 else None, |
| 'sicherheitsklasse_nach_din_en_378_1': r[36] if len(r) > 36 else None, |
| 'hinweistext_zum_kältemittel': r[37] if len(r) > 37 else None, |
| 'sanftanlasser': r[38] if len(r) > 38 else None, |
| } |
|
|
| def parse_710_01(r): |
| """Parse 710.01:Heizungs-Wärmepumpe""" |
| return { |
| 'index_710_01': int(r[1]) if len(r) > 1 else None, |
| 'korrekturfaktor_7_k': r[2] if len(r) > 2 else None, |
| 'korrekturfaktor_10_k': r[3] if len(r) > 3 else None, |
| 'temperaturdifferenz_am_verflüssiger_bei_prüfstandmessung': r[4] if len(r) > 4 else None, |
| } |
|
|
| def parse_710_04(r): |
| """Parse 710.04:Wasser-Wasser-Wärmepumpe""" |
| return { |
| 'index_710_04': int(r[1]) if len(r) > 1 else None, |
| 'leistungszahl_bei_w10_w35': r[2] if len(r) > 2 else None, |
| 'elektrische_leistungsaufnahme_wärmequellenpumpe': r[3] if len(r) > 3 else None, |
| 'heizleistung': r[4] if len(r) > 4 else None, |
| 'einsatzgrenze_wärmequelle_von': r[5] if len(r) > 5 else None, |
| 'einsatzgrenze_wärmequelle_bis': r[6] if len(r) > 6 else None, |
| 'volumenstrom_heizungsseitig': r[7] if len(r) > 7 else None, |
| 'wärmequellenpumpe_intern': r[8] if len(r) > 8 else None, |
| 'heizkreispumpe_intern': r[9] if len(r) > 9 else None, |
| 'elektrische_leistungsaufnahme_heizkreispumpe': r[10] if len(r) > 10 else None, |
| 'leistungszahl_bei_w10_w45': r[11] if len(r) > 11 else None, |
| 'leistungszahl_bei_w10_w55': r[12] if len(r) > 12 else None, |
| 'leistungszahl_bei_w7_w35': r[13] if len(r) > 13 else None, |
| 'kühlleistung_bei_w15_w23': r[14] if len(r) > 14 else None, |
| 'kühlleistungszahl_bei_w15_w23': r[15] if len(r) > 15 else None, |
| 'minimaler_volumenstrom_wärmequelle': r[16] if len(r) > 16 else None, |
| 'maximaler_volumenstrom_wärmequelle': r[17] if len(r) > 17 else None, |
| } |
|
|
| def parse_710_05(r): |
| """Parse 710.05:Luft-Wasser-Wärmepumpe""" |
| return { |
| 'index_710_05': int(r[1]) if len(r) > 1 else None, |
| 'leistungszahl_bei_a_7_w35': r[2] if len(r) > 2 else None, |
| 'leistungszahl_bei_a2_w35': r[3] if len(r) > 3 else None, |
| 'leistungszahl_bei_a10_w35': r[4] if len(r) > 4 else None, |
| 'abtauart': r[5] if len(r) > 5 else None, |
| 'kühlfunktion_durch_kreislaufumkehr': r[6] if len(r) > 6 else None, |
| 'leistungszahl_bei_a7_w35': r[7] if len(r) > 7 else None, |
| 'leistungszahl_bei_a_15_w35': r[8] if len(r) > 8 else None, |
| 'leistungszahl_bei_a2_w45': r[9] if len(r) > 9 else None, |
| 'leistungszahl_bei_a7_w45': r[10] if len(r) > 10 else None, |
| 'leistungszahl_bei_a_7_w55': r[11] if len(r) > 11 else None, |
| 'leistungszahl_bei_a7_w55': r[12] if len(r) > 12 else None, |
| 'leistungszahl_bei_a10_w55': r[13] if len(r) > 13 else None, |
| 'kühlleistungszahl_bei_a35_w7': r[14] if len(r) > 14 else None, |
| 'kühlleistungszahl_bei_a35_w18': r[15] if len(r) > 15 else None, |
| 'kühlleistung_bei_a35_w7': r[16] if len(r) > 16 else None, |
| 'kühlleistung_bei_a35_w18': r[17] if len(r) > 17 else None, |
| 'minimale_einsatzgrenze_wärmequelle': r[18] if len(r) > 18 else None, |
| 'maximale_einsatzgrenze_wärmequelle': r[19] if len(r) > 19 else None, |
| 'leistungszahl_a20_w35': r[20] if len(r) > 20 else None, |
| 'leistungszahl_a20_w45': r[21] if len(r) > 21 else None, |
| 'leistungszahl_a20_w55': r[22] if len(r) > 22 else None, |
| 'leistungsaufnahme_luefter': r[25] if len(r) > 25 else None, |
| 'volumenstrom_heizungsseitig': r[26] if len(r) > 26 else None, |
| } |
|
|
| def parse_710_07(r): |
| """Parse 710.07:Einbringmaße""" |
| return { |
| 'index_710_07': int(r[1]) if len(r) > 1 else None, |
| 'art_der_maße': r[2] if len(r) > 2 else None, |
| 'länge': r[3] if len(r) > 3 else None, |
| 'breite': r[4] if len(r) > 4 else None, |
| 'höhe': r[5] if len(r) > 5 else None, |
| 'masse': r[6] if len(r) > 6 else None, |
| 'beschreibung': r[7] if len(r) > 7 else None, |
| } |
|
|
| def parse_800(r): |
| """Parse 800:TGA-Nummer""" |
| return { |
| 'index_800': int(r[1]) if len(r) > 1 else None, |
| 'tga_nummer': r[2] if len(r) > 2 else None, |
| } |
|
|
| def parse_810(r): |
| """Parse 810:Artikelnummern""" |
| return { |
| 'index_810': int(r[1]) if len(r) > 1 else None, |
| 'artikelnummer': r[2] if len(r) > 2 else None, |
| 'artikelname': r[9] if len(r) > 9 else None, |
| 'energieeffizienzklasse': r[10] if len(r) > 10 else None, |
| 'erp_richtlinie': r[11] if len(r) > 11 else None, |
| } |
|
|
| class VDIHeatPumpParser: |
| """Main parser class for VDI heat pump data""" |
| |
| def __init__(self): |
| self.data_cache = {} |
| self.parsed_files = {} |
| self.domain = "bim4hvac.com" |
| |
| def check_catalogs_for_part(self, part: int) -> str: |
| """Check if catalogs have been updated for a part""" |
| try: |
| url = f"http://catalogue.{self.domain}/bdh/ws/mcc.asmx" |
| payload = f"""<?xml version="1.0" encoding="utf-8"?> |
| <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> |
| <soap:Body> |
| <CheckCatalogsForPart xmlns="urn:bdhmcc"> |
| <part>{part}</part> |
| </CheckCatalogsForPart> |
| </soap:Body> |
| </soap:Envelope> |
| """ |
| headers = {'Content-Type': 'text/xml; charset=utf-8'} |
| response = requests.post(url, headers=headers, data=payload, timeout=30) |
| return response.text |
| except Exception as e: |
| raise Exception(f"Error checking catalogs for part {part}: {str(e)}") |
| |
| def download_vdi_files(self, part: int, download_dir: str = None) -> List[str]: |
| """Download all VDI files for a given part""" |
| try: |
| if download_dir is None: |
| download_dir = os.path.join(tempfile.gettempdir(), f"vdi_part{part:02d}") |
| |
| os.makedirs(download_dir, exist_ok=True) |
| |
| |
| catalog_xml = self.check_catalogs_for_part(part) |
| |
| |
| root = ET.fromstring(catalog_xml) |
| result = root.find('.//{urn:bdhmcc}CheckCatalogsForPartResult') |
| |
| if result is None: |
| raise Exception("No catalog results found") |
| |
| |
| catalogs_xml = ET.fromstring(result.text) |
| |
| |
| data = [] |
| for catalog in catalogs_xml.findall('Catalog'): |
| data.append({ |
| 'mfr': catalog.get('mfr'), |
| 'nick': catalog.get('nick').split('/')[0].strip(), |
| 'part': f"{int(catalog.get('part')):02d}", |
| }) |
|
|
| df = pd.DataFrame(data) |
| |
| |
| df['slug'] = df['nick'] + df['part'] |
| df['download_url'] = f"https://www.catalogue.{self.domain}/" + df['slug'] + "/Downloads/PART" + df['part'] + "_" + df['nick'] + ".zip" |
| |
| |
| downloaded_files = [] |
| for _, row in df.iterrows(): |
| download_url = row['download_url'] |
| file_name = os.path.join(download_dir, f"PART{row['part']}_{row['nick']}.zip") |
| |
| print(f"Downloading {download_url}...") |
| response = requests.get(download_url, timeout=30) |
| response.raise_for_status() |
| |
| with open(file_name, 'wb') as file: |
| file.write(response.content) |
| |
| downloaded_files.append(file_name) |
| print(f"Downloaded: {file_name}") |
| |
| return downloaded_files |
| |
| except Exception as e: |
| raise Exception(f"Error downloading VDI files for part {part}: {str(e)}") |
| |
| def parse_vdi_file(self, file_path: str, nick: str) -> pd.DataFrame: |
| """Parse a single VDI file using the full parsing logic""" |
| try: |
| with open(file_path, 'r', encoding="latin-1") as file: |
| text = file.read() |
| |
| |
| lines = np.array(text.split("\n")) |
| records = list(map(lambda x: x.split(";"), lines)) |
| |
| |
| r010 = parse_010([]) |
| r100 = parse_100([]) |
| r110 = parse_110([]) |
| r400 = parse_400([]) |
| r450 = parse_450([]) |
| r460 = parse_460([]) |
| r700 = parse_700([]) |
| r710_01 = parse_710_01([]) |
| r710_04 = parse_710_04([]) |
| r710_05 = parse_710_05([]) |
| r710_07 = parse_710_07([]) |
| r800 = parse_800([]) |
| r810 = parse_810([]) |
|
|
| |
| data = [] |
| for r in records: |
| if r[0] == '010': |
| r010 = parse_010(r) |
| if r[0] == '100': |
| r100 = parse_100(r) |
| elif r[0] == '110': |
| r110 = parse_110(r) |
| |
| r400s = [] |
| r450s = [] |
| r460s = [] |
| r700s = [] |
| r710_07s = {} |
| elif r[0] == '400': |
| r400 = parse_400(r) |
| r400s.append(r400) |
| elif r[0] == '450': |
| r450 = parse_450(r) |
| r450s.append(r450) |
| elif r[0] == '460': |
| r460 = parse_460(r) |
| r460s.append(r460) |
| elif r[0] == '700': |
| r700 = parse_700(r) |
| r700s.append(r700) |
| elif r[0] == '710.01': |
| r710_01 = parse_710_01(r) |
| if r700s: |
| r700s[-1].update(r710_01) |
| elif r[0] == '710.04': |
| r710_04 = parse_710_04(r) |
| if r700s: |
| r700s[-1].update(r710_04) |
| elif r[0] == '710.05': |
| r710_05 = parse_710_05(r) |
| if r700s: |
| r700s[-1].update(r710_05) |
| elif r[0] == '710.07': |
| r710_07 = parse_710_07(r) |
| if r700s: |
| r710_07s.setdefault(r700s[-1]['index_700'], []).append(r710_07) |
| elif r[0] == '800': |
| r800 = parse_800(r) |
| |
| |
| if r800['tga_nummer'] and len(r800['tga_nummer']) >= 50: |
| try: |
| index_400 = int(r800['tga_nummer'][27:30]) |
| index_450 = int(r800['tga_nummer'][30:33]) |
| index_460 = int(r800['tga_nummer'][33:36]) |
| index_700 = int(r800['tga_nummer'][45:50]) |
| |
| |
| r400 = next((r for r in r400s if r['index_400'] == index_400), {}) |
| r450 = next((r for r in r450s if r['index_450'] == index_450), {}) |
| r460 = next((r for r in r460s if r['index_460'] == index_460), {}) |
| r700 = next((r for r in r700s if r['index_700'] == index_700), {}) |
| |
| |
| filtered_r710_07s = [r for r in r710_07s.get(index_700, []) if r['art_der_maße'] == '2'] |
| if len(filtered_r710_07s) == 1: |
| r710_07 = filtered_r710_07s[0] |
| else: |
| aufstellungsort = r450.get('aufstellungsort', '').lower() |
| r710_07 = next((r for r in filtered_r710_07s if r.get('beschreibung', '').lower().startswith(aufstellungsort)), {}) |
| except (ValueError, IndexError): |
| |
| r400, r450, r460, r700, r710_07 = {}, {}, {}, {}, {} |
| |
| elif r[0] == '810': |
| r810 = parse_810(r) |
| if r700: |
| data.append({**r810, **r800, **r710_07, **r700, **r460, **r450, **r400, **r110, **r100, **r010}) |
|
|
| |
| df = pd.DataFrame(data) |
| |
| |
| df['nick'] = nick |
| |
| |
| df = df.replace({r'[\r\n]+': ' '}, regex=True) |
| |
| |
| df = df.replace({r'¶': ', '}, regex=True) |
| |
| |
| index_columns = [col for col in df.columns if col.startswith('index_')] |
| for col in index_columns: |
| if col in df.columns: |
| df[col] = df[col].replace('', pd.NA).astype('Int64') |
| |
| return df |
| |
| except Exception as e: |
| raise Exception(f"Error parsing VDI file {file_path}: {str(e)}") |
| |
| def extract_and_parse_zip(self, zip_path: str) -> pd.DataFrame: |
| """Extract ZIP file and parse VDI files""" |
| nick = Path(zip_path).stem.split('_')[-1] |
| |
| with tempfile.TemporaryDirectory() as temp_dir: |
| with zipfile.ZipFile(zip_path, 'r') as zip_ref: |
| zip_ref.extractall(temp_dir) |
| |
| |
| vdi_files = list(Path(temp_dir).rglob("*.vdi")) |
| if not vdi_files: |
| vdi_files = list(Path(temp_dir).rglob("*.VDI")) |
| |
| if not vdi_files: |
| raise Exception(f"No VDI files found in {zip_path}") |
| |
| all_data = [] |
| for vdi_file in vdi_files: |
| try: |
| df = self.parse_vdi_file(str(vdi_file), nick) |
| if not df.empty: |
| all_data.append(df) |
| except Exception as e: |
| print(f"Error parsing {vdi_file.name}: {e}") |
| continue |
| |
| if all_data: |
| combined_df = pd.concat(all_data, ignore_index=True) |
| self.parsed_files[zip_path] = combined_df |
| return combined_df |
| |
| return pd.DataFrame() |
|
|
| |
| parser = VDIHeatPumpParser() |
|
|
| |
| def download_and_parse_part(part: int) -> str: |
| """Download and parse VDI files for a specific part""" |
| try: |
| download_dir = os.path.join(tempfile.gettempdir(), f"vdi_part{part:02d}") |
| |
| |
| downloaded_files = parser.download_vdi_files(part, download_dir) |
| |
| |
| all_data = [] |
| for file_path in downloaded_files: |
| try: |
| df = parser.extract_and_parse_zip(file_path) |
| if not df.empty: |
| all_data.append(df) |
| except Exception as e: |
| print(f"Error parsing {file_path}: {e}") |
| |
| if all_data: |
| combined_df = pd.concat(all_data, ignore_index=True) |
| cache_key = f"part_{part}" |
| parser.parsed_files[cache_key] = combined_df |
| |
| result = { |
| "status": "success", |
| "message": f"Successfully downloaded and parsed {len(combined_df)} heat pump records for part {part}", |
| "part": part, |
| "record_count": len(combined_df), |
| "manufacturers": sorted(combined_df['hersteller'].dropna().unique().tolist()) |
| } |
| else: |
| result = { |
| "status": "warning", |
| "message": f"No heat pump data found for part {part}", |
| "part": part, |
| "record_count": 0 |
| } |
| |
| return json.dumps(result, indent=2) |
| |
| except Exception as e: |
| return json.dumps({"status": "error", "message": f"Error: {str(e)}"}, indent=2) |
|
|
| def search_heatpump(manufacturer: str = None, product_name: str = None, article_number: str = None, |
| heating_power_min: float = None, heating_power_max: float = None, heat_pump_type: str = None) -> str: |
| """Search for heat pumps based on criteria""" |
| try: |
| |
| all_data = [] |
| for df in parser.parsed_files.values(): |
| all_data.append(df) |
| |
| if not all_data: |
| return json.dumps({"status": "error", "message": "No data available. Please parse VDI files first."}) |
| |
| combined_df = pd.concat(all_data, ignore_index=True) |
| |
| |
| filtered_df = combined_df.copy() |
| |
| if manufacturer: |
| filtered_df = filtered_df[ |
| filtered_df['hersteller'].str.contains(manufacturer, case=False, na=False) |
| ] |
| |
| if product_name: |
| filtered_df = filtered_df[ |
| filtered_df['produktname'].str.contains(product_name, case=False, na=False) |
| ] |
| |
| if article_number: |
| filtered_df = filtered_df[ |
| filtered_df['artikelnummer'].str.contains(article_number, case=False, na=False) |
| ] |
| |
| if heat_pump_type: |
| filtered_df = filtered_df[ |
| filtered_df['typ'].str.contains(heat_pump_type, case=False, na=False) |
| ] |
| |
| |
| if heating_power_min is not None or heating_power_max is not None: |
| filtered_df['heizleistung_numeric'] = pd.to_numeric(filtered_df['heizleistung'], errors='coerce') |
| |
| if heating_power_min is not None: |
| filtered_df = filtered_df[filtered_df['heizleistung_numeric'] >= heating_power_min] |
| |
| if heating_power_max is not None: |
| filtered_df = filtered_df[filtered_df['heizleistung_numeric'] <= heating_power_max] |
| |
| |
| result_columns = ['hersteller', 'produktname', 'artikelnummer', 'heizleistung', 'typ', 'energieeffizienzklasse'] |
| available_columns = [col for col in result_columns if col in filtered_df.columns] |
| result_df = filtered_df[available_columns].head(20) |
| |
| return result_df.to_json(orient='records', indent=2) |
| |
| except Exception as e: |
| return json.dumps({"status": "error", "message": f"Error searching heat pumps: {str(e)}"}, indent=2) |
|
|
| def get_heatpump_details(article_number: str) -> str: |
| """Get detailed information about a specific heat pump""" |
| try: |
| |
| for df in parser.parsed_files.values(): |
| matching_rows = df[df['artikelnummer'] == article_number] |
| if not matching_rows.empty: |
| details = matching_rows.iloc[0].to_dict() |
| |
| details = {k: v for k, v in details.items() if v is not None and v != ''} |
| return json.dumps(details, indent=2, default=str) |
| |
| return json.dumps({"status": "error", "message": f"Heat pump with article number '{article_number}' not found"}) |
| |
| except Exception as e: |
| return json.dumps({"status": "error", "message": f"Error getting heat pump details: {str(e)}"}, indent=2) |
|
|
| def list_manufacturers() -> str: |
| """List all manufacturers in the parsed data""" |
| try: |
| all_manufacturers = set() |
| for df in parser.parsed_files.values(): |
| if 'hersteller' in df.columns: |
| manufacturers = df['hersteller'].dropna().unique() |
| all_manufacturers.update(manufacturers) |
| |
| if not all_manufacturers: |
| return json.dumps({ |
| "status": "warning", |
| "message": "No manufacturers available. Please parse VDI files first.", |
| "manufacturers": [], |
| "count": 0 |
| }) |
| |
| result = { |
| "status": "success", |
| "manufacturers": sorted(list(all_manufacturers)), |
| "count": len(all_manufacturers) |
| } |
| return json.dumps(result, indent=2) |
| |
| except Exception as e: |
| return json.dumps({"status": "error", "message": f"Error listing manufacturers: {str(e)}"}, indent=2) |
|
|
| def get_data_summary() -> str: |
| """Get summary statistics of all parsed data""" |
| try: |
| if not parser.parsed_files: |
| return json.dumps({ |
| "status": "warning", |
| "message": "No data available. Please parse VDI files first.", |
| "total_records": 0 |
| }) |
| |
| total_records = 0 |
| manufacturers = set() |
| heat_pump_types = set() |
| |
| for df in parser.parsed_files.values(): |
| total_records += len(df) |
| if 'hersteller' in df.columns: |
| manufacturers.update(df['hersteller'].dropna().unique()) |
| if 'typ' in df.columns: |
| heat_pump_types.update(df['typ'].dropna().unique()) |
| |
| result = { |
| "status": "success", |
| "total_records": total_records, |
| "manufacturer_count": len(manufacturers), |
| "heat_pump_types": sorted(list(heat_pump_types)), |
| "parsed_files": len(parser.parsed_files) |
| } |
| return json.dumps(result, indent=2) |
| |
| except Exception as e: |
| return json.dumps({"status": "error", "message": f"Error getting data summary: {str(e)}"}, indent=2) |
|
|
| |
| def create_interface(): |
| """Create Gradio interface with MCP server""" |
| |
| def test_download_and_parse(part_number): |
| """Test the download and parse functionality""" |
| if not part_number: |
| return "Please enter a part number" |
| |
| try: |
| part = int(part_number) |
| result = download_and_parse_part(part) |
| return result |
| except ValueError: |
| return "Please enter a valid integer for part number" |
| except Exception as e: |
| return f"Error: {str(e)}" |
| |
| def test_search(manufacturer, product_name, article_number, heating_power_min, heating_power_max, heat_pump_type): |
| """Test the search functionality""" |
| try: |
| |
| manufacturer = manufacturer if manufacturer.strip() else None |
| product_name = product_name if product_name.strip() else None |
| article_number = article_number if article_number.strip() else None |
| heat_pump_type = heat_pump_type if heat_pump_type.strip() else None |
| |
| |
| heating_power_min = float(heating_power_min) if heating_power_min else None |
| heating_power_max = float(heating_power_max) if heating_power_max else None |
| |
| result = search_heatpump( |
| manufacturer=manufacturer, |
| product_name=product_name, |
| article_number=article_number, |
| heating_power_min=heating_power_min, |
| heating_power_max=heating_power_max, |
| heat_pump_type=heat_pump_type |
| ) |
| return result |
| except Exception as e: |
| return f"Error: {str(e)}" |
| |
| def test_get_details(article_number): |
| """Test getting heat pump details""" |
| if not article_number.strip(): |
| return "Please enter an article number" |
| |
| result = get_heatpump_details(article_number.strip()) |
| return result |
| |
| def test_list_manufacturers(): |
| """Test listing manufacturers""" |
| result = list_manufacturers() |
| return result |
| |
| def test_get_summary(): |
| """Test getting data summary""" |
| result = get_data_summary() |
| return result |
| |
| |
| with gr.Blocks(title="VDI Heat Pump Parser - MCP Server") as demo: |
| gr.Markdown("# VDI Heat Pump Parser - MCP Server") |
| gr.Markdown("This interface allows you to test the MCP tools for parsing VDI heat pump data.") |
| |
| with gr.Tab("Download & Parse"): |
| with gr.Row(): |
| part_input = gr.Textbox(label="Part Number", placeholder="Enter part number (e.g., 22 for heat pumps)") |
| download_btn = gr.Button("Download & Parse") |
| download_output = gr.Textbox(label="Result", lines=10) |
| download_btn.click(test_download_and_parse, inputs=[part_input], outputs=[download_output]) |
| |
| with gr.Tab("Search Heat Pumps"): |
| with gr.Row(): |
| with gr.Column(): |
| search_manufacturer = gr.Textbox(label="Manufacturer", placeholder="e.g., Viessmann") |
| search_product = gr.Textbox(label="Product Name", placeholder="e.g., Vitocal") |
| search_article = gr.Textbox(label="Article Number", placeholder="e.g., 12345") |
| with gr.Column(): |
| search_power_min = gr.Textbox(label="Min Heating Power (kW)", placeholder="e.g., 5") |
| search_power_max = gr.Textbox(label="Max Heating Power (kW)", placeholder="e.g., 15") |
| search_type = gr.Textbox(label="Heat Pump Type", placeholder="e.g., Luft-Wasser") |
| |
| search_btn = gr.Button("Search") |
| search_output = gr.Textbox(label="Search Results", lines=15) |
| |
| search_btn.click( |
| test_search, |
| inputs=[search_manufacturer, search_product, search_article, search_power_min, search_power_max, search_type], |
| outputs=[search_output] |
| ) |
| |
| with gr.Tab("Get Details"): |
| with gr.Row(): |
| details_article = gr.Textbox(label="Article Number", placeholder="Enter exact article number") |
| details_btn = gr.Button("Get Details") |
| details_output = gr.Textbox(label="Heat Pump Details", lines=20) |
| details_btn.click(test_get_details, inputs=[details_article], outputs=[details_output]) |
| |
| with gr.Tab("Data Management"): |
| with gr.Row(): |
| with gr.Column(): |
| manufacturers_btn = gr.Button("List Manufacturers") |
| manufacturers_output = gr.Textbox(label="Manufacturers", lines=10) |
| manufacturers_btn.click(test_list_manufacturers, outputs=[manufacturers_output]) |
| |
| with gr.Column(): |
| summary_btn = gr.Button("Get Data Summary") |
| summary_output = gr.Textbox(label="Data Summary", lines=10) |
| summary_btn.click(test_get_summary, outputs=[summary_output]) |
| |
| |
| demo.mcp_functions = { |
| "download_and_parse_part": { |
| "function": download_and_parse_part, |
| "description": "Download and automatically parse all VDI files for a part", |
| "parameters": { |
| "type": "object", |
| "properties": { |
| "part": { |
| "type": "integer", |
| "description": "Part number (e.g., 22 for heat pumps)" |
| } |
| }, |
| "required": ["part"] |
| } |
| }, |
| "search_heatpump": { |
| "function": search_heatpump, |
| "description": "Search for specific heat pump by criteria", |
| "parameters": { |
| "type": "object", |
| "properties": { |
| "manufacturer": { |
| "type": "string", |
| "description": "Manufacturer name (optional)" |
| }, |
| "product_name": { |
| "type": "string", |
| "description": "Product name or partial match (optional)" |
| }, |
| "article_number": { |
| "type": "string", |
| "description": "Article number (optional)" |
| }, |
| "heating_power_min": { |
| "type": "number", |
| "description": "Minimum heating power in kW (optional)" |
| }, |
| "heating_power_max": { |
| "type": "number", |
| "description": "Maximum heating power in kW (optional)" |
| }, |
| "heat_pump_type": { |
| "type": "string", |
| "description": "Heat pump type (e.g., 'Luft-Wasser') (optional)" |
| } |
| } |
| } |
| }, |
| "get_heatpump_details": { |
| "function": get_heatpump_details, |
| "description": "Get detailed information about a specific heat pump", |
| "parameters": { |
| "type": "object", |
| "properties": { |
| "article_number": { |
| "type": "string", |
| "description": "Article number of the heat pump" |
| } |
| }, |
| "required": ["article_number"] |
| } |
| }, |
| "list_manufacturers": { |
| "function": list_manufacturers, |
| "description": "List all available manufacturers in parsed data", |
| "parameters": { |
| "type": "object", |
| "properties": {} |
| } |
| }, |
| "get_data_summary": { |
| "function": get_data_summary, |
| "description": "Get summary of all parsed heat pump data", |
| "parameters": { |
| "type": "object", |
| "properties": {} |
| } |
| } |
| } |
| |
| return demo |
|
|
| |
| if __name__ == "__main__": |
| |
| demo = create_interface() |
| |
| |
| demo.launch( |
| server_name="0.0.0.0", |
| server_port=7860, |
| share=True, |
| mcp_server=True |
| ) |
|
|