Spaces:
Sleeping
Sleeping
HVAC Blueprint Analyzer - Output Generator & GUI Guide
Date: June 19, 2026
Component: Output Generation & User Interface
π OUTPUT GENERATOR OVERVIEW
Purpose
Convert analyzed HVAC data into multiple formats:
- CSV files for spreadsheet analysis
- PDF reports with visual summaries
- JSON for API consumption
- HTML reports for web viewing
- Excel workbooks with multiple sheets
π― PHASE A: CSV OUTPUT GENERATION
CSV Output 1: Floor & Units Summary
File: hvac_analysis_floors_units.csv
Floor,Unit_ID,Unit_Type,Quantity,Capacity_BTU,Capacity_kW,Status,Notes
1,AC-A,Ceiling Concealed Ducted,1,36000,10.5,Detected,Located near kitchen
1,AC-B,Wall Mounted,1,24000,7.0,Detected,Bedroom unit
2,AC-C,Ceiling Concealed Ducted,1,36000,10.5,Detected,Master bedroom
2,AC-D,Wall Mounted,2,12000,3.5,Detected,Living areas
3,AC-E,Ceiling Concealed Ducted,1,48000,14.0,Detected,Commercial zone
3,AC-F,Wall Mounted,1,24000,7.0,Detected,Office area
Columns to Include:
- Floor number (1, 2, 3, B for basement)
- Unit ID/Tag (AC-A, AC-B, etc.)
- Unit Type (Ceiling Concealed Ducted, Wall Mounted, Ductless, etc.)
- Quantity (number of units)
- Capacity in BTU
- Capacity in kW
- Detection Status (Detected, Manual Entry, Inferred)
- Notes/Comments
- Location within floor (optional)
- Equipment Schedule reference (optional)
Implementation:
def export_floors_units_csv(analysis_data, output_path):
"""
Export floor-by-floor unit breakdown to CSV
Args:
analysis_data: Dict with floors and units
output_path: Path to save CSV
Returns:
Path to saved CSV file
"""
import pandas as pd
rows = []
for floor in analysis_data['floors']:
floor_num = floor['number']
for unit in floor['units']:
rows.append({
'Floor': floor_num,
'Unit_ID': unit['tag'],
'Unit_Type': unit['type'],
'Quantity': unit['quantity'],
'Capacity_BTU': unit.get('capacity_btu', ''),
'Capacity_kW': unit.get('capacity_kw', ''),
'Status': unit.get('detection_status', 'Detected'),
'Notes': unit.get('notes', ''),
'Location': unit.get('location', ''),
})
df = pd.DataFrame(rows)
df.to_csv(output_path, index=False)
return output_path
CSV Output 2: Equipment Schedule
File: hvac_equipment_schedule.csv
Equipment_ID,Manufacturer,Model,Type,Capacity_BTU,Voltage,Phase,Frequency_Hz,Quantity,Floor,Unit_Tag,Remarks
1,Carrier,25HNE024A03,Ceiling Cassette,24000,208-230,1,60,1,2,AC-B,Installed Jan 2023
2,Daikin,FCAG71B,Ceiling Concealed,36000,208-230,3,60,2,1,AC-A,New equipment
3,LG,ASNQ48LNZA0,Wall Mounted,48000,208-230,1,60,1,3,AC-E,High efficiency model
Columns to Include:
- Equipment ID (from schedule)
- Manufacturer
- Model number
- Equipment Type
- Capacity (BTU)
- Voltage
- Phase (1 or 3)
- Frequency (Hz)
- Quantity
- Floor number
- Associated Unit Tag (AC-A, AC-B, etc.)
- Remarks/Notes
CSV Output 3: Reconciliation Report
File: hvac_reconciliation_report.csv
Issue_Type,Severity,Floor,Unit_ID,Detected_in_Plan,Found_in_Schedule,Details,Recommended_Action
Missing_Unit,High,2,AC-B,No,Yes,Unit found in schedule but not detected in floor plan,Review floor plan markup
Extra_Unit,Medium,1,AC-A,Yes,No,Unit detected in plan but not listed in schedule,Add to equipment schedule
Capacity_Mismatch,Medium,3,AC-E,36000,48000,Plan shows 36kBTU but schedule lists 48kBTU,Verify actual installed capacity
Floor_Mismatch,High,NULL,AC-C,3,2,Unit AC-C listed on floor 2 but appears in floor 3 plan,Correct schedule or floor plan
Columns to Include:
- Issue Type (Missing Unit, Extra Unit, Capacity Mismatch, Floor Mismatch, etc.)
- Severity (Low, Medium, High)
- Floor number
- Unit ID
- Detected in floor plan (Yes/No)
- Found in schedule (Yes/No)
- Details/Description
- Recommended Action
- Confidence Score (0-100%)
π₯οΈ PHASE B: GUI - USER INTERFACE
Technology Stack (Recommended)
- Frontend Framework: Streamlit (easiest for quick demo) OR React
- Styling: Tailwind CSS (if React) OR Streamlit theme
- Charting: Plotly or Chart.js (for visualizations)
- Forms: Built-in Streamlit components OR React Hook Form
GUI Features Checklist
1. File Upload Section
# Streamlit example
import streamlit as st
st.title("π’ HVAC Blueprint Analyzer")
uploaded_file = st.file_uploader("Upload Mechanical Blueprint (PDF)", type="pdf")
if uploaded_file:
st.info(f"π File: {uploaded_file.name} ({uploaded_file.size / 1024:.2f} KB)")
col1, col2 = st.columns(2)
with col1:
model_choice = st.radio("Choose AI Model:", ["Gemini", "OpenAI"])
with col2:
schedule_mode = st.radio("Schedule Mode:",
["Auto-Detect", "Upload Schedule"])
if st.button("π Analyze Blueprint"):
with st.spinner("Analyzing..."):
results = analyze_blueprint(uploaded_file, model_choice, schedule_mode)
st.success("Analysis complete!")
Checklist:
- PDF upload widget
- File validation (size, format)
- Progress indicator during upload
- File preview/info display
- Multiple file upload support (batch processing)
- Drag-and-drop file upload
2. Configuration Section
- Model selection (Gemini vs OpenAI toggle)
- Schedule mode selection (Auto vs Manual)
- Output format selection (CSV, PDF, JSON)
- Advanced options (OCR threshold, confidence limits)
- Cost calculator (estimate before processing)
3. Analysis Results Display
Tab 1: Floors & Units Summary
st.header("π Analysis Results")
tab1, tab2, tab3, tab4 = st.tabs([
"Floors & Units",
"Equipment Schedule",
"Reconciliation",
"Downloads"
])
with tab1:
st.subheader("HVAC Units by Floor")
# Interactive table
st.dataframe(
floors_units_df,
use_container_width=True,
column_config={
"Floor": st.column_config.TextColumn("Floor"),
"Unit_ID": st.column_config.TextColumn("Unit ID", width="medium"),
"Unit_Type": st.column_config.TextColumn("Type", width="medium"),
"Quantity": st.column_config.NumberColumn("Qty"),
"Capacity_BTU": st.column_config.NumberColumn("BTU"),
}
)
# Summary statistics
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Total Floors", len(analysis_data['floors']))
with col2:
st.metric("Total Units", sum([u['quantity'] for f in analysis_data['floors'] for u in f['units']]))
with col3:
st.metric("Total Capacity", f"{total_btu:,.0f} BTU")
with col4:
st.metric("Cost (Gemini)", f"${cost:.2f}")
Tab 2: Equipment Schedule Table
- Sortable table with equipment details
- Filter by floor, manufacturer, type
- Search functionality
- Edit capability (for manual corrections)
- Export to CSV
Tab 3: Reconciliation Report
- Visual summary of issues (count by severity)
- Detailed issue list
- Severity indicators (color-coded)
- Recommended actions
- Mark issues as resolved
Tab 4: Downloads
- Download CSV files (Floors & Units, Schedule, Reconciliation)
- Download PDF report
- Download JSON data
- Zip all outputs together
4. Visualization Components
Floor Plan Overview
import plotly.graph_objects as go
fig = go.Figure()
# Add floor layers
for floor in analysis_data['floors']:
fig.add_trace(go.Scatter(
x=floor['units_x'],
y=floor['units_y'],
mode='markers+text',
text=floor['units_tags'],
marker=dict(size=15, color='blue'),
name=f"Floor {floor['number']}"
))
st.plotly_chart(fig, use_container_width=True)
Capacity Distribution Chart
- Stacked bar chart showing capacity by floor
- Unit type distribution pie chart
- Comparison chart (Detected vs Schedule)
Cost Breakdown
- API cost per model comparison
- Cost per unit analyzed
- Total project cost estimate
5. Schedule Upload Section (if auto-detect fails)
with st.expander("π Manual Schedule Upload"):
schedule_source = st.radio(
"How would you like to provide the schedule?",
["Upload CSV", "Upload Excel", "Paste Data", "Use Previous Schedule"]
)
if schedule_source == "Upload CSV":
schedule_file = st.file_uploader("Upload CSV with schedule", type="csv")
if schedule_file:
schedule_df = pd.read_csv(schedule_file)
st.dataframe(schedule_df)
elif schedule_source == "Paste Data":
schedule_text = st.text_area("Paste schedule data (CSV format)")
if schedule_text:
schedule_df = pd.read_csv(StringIO(schedule_text))
st.dataframe(schedule_df)
Checklist:
- CSV upload
- Excel upload (.xlsx, .xls)
- Copy/paste data
- Data validation
- Preview of uploaded data
- Confirm & save schedule
6. A/B Testing Comparison View
col1, col2 = st.columns(2)
with col1:
st.subheader("π΅ Gemini Results")
st.write(f"Time: {gemini_time:.2f}s")
st.write(f"Cost: ${gemini_cost:.4f}")
st.write(f"Units Found: {gemini_units}")
st.dataframe(gemini_results)
with col2:
st.subheader("π OpenAI Results")
st.write(f"Time: {openai_time:.2f}s")
st.write(f"Cost: ${openai_cost:.4f}")
st.write(f"Units Found: {openai_units}")
st.dataframe(openai_results)
# Comparison metrics
st.subheader("π Comparison")
comparison_data = {
'Metric': ['Speed (seconds)', 'Cost ($)', 'Units Detected', 'Accuracy (%)'],
'Gemini': [gemini_time, gemini_cost, gemini_units, gemini_accuracy],
'OpenAI': [openai_time, openai_cost, openai_units, openai_accuracy]
}
comparison_df = pd.DataFrame(comparison_data)
st.dataframe(comparison_df, use_container_width=True)
π PHASE C: PDF REPORT GENERATION
Report Sections
Cover Page
- Project name
- Analysis date
- Number of blueprints analyzed
- Model used (Gemini/OpenAI)
Executive Summary
- Total units found
- Total capacity
- Number of floors
- Key findings
Floor-by-Floor Summary
- Table of units per floor
- Visual breakdown
Equipment Schedule
- Full equipment table
- Specifications
Reconciliation Issues
- List of discrepancies
- Severity indicators
- Recommended actions
Appendix
- Analysis parameters
- Processing time
- Cost breakdown
PDF Generation Library
from reportlab.lib.pagesizes import letter, A4
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
import pandas as pd
def generate_pdf_report(analysis_data, output_path):
"""Generate comprehensive PDF report"""
from reportlab.lib import colors
from reportlab.lib.units import inch
doc = SimpleDocTemplate(output_path, pagesize=letter)
elements = []
# Title
title = Paragraph(
"<font size=24><b>HVAC Blueprint Analysis Report</b></font>",
style=ParagraphStyle(alignment=1) # Center aligned
)
elements.append(title)
elements.append(Spacer(1, 0.5*inch))
# Summary table
summary_data = [
['Total Floors', str(len(analysis_data['floors']))],
['Total Units', str(sum_units(analysis_data))],
['Total Capacity', f"{total_btu(analysis_data):,} BTU"],
]
summary_table = Table(summary_data)
summary_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 14),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('GRID', (0, 0), (-1, -1), 1, colors.black)
]))
elements.append(summary_table)
# Build PDF
doc.build(elements)
return output_path
Checklist:
- ReportLab setup
- Cover page generation
- Table formatting
- Chart embedding
- Multi-page support
- Table of contents
- Page numbering
π PHASE D: JSON OUTPUT
JSON Structure
{
"metadata": {
"analysis_date": "2026-06-19T10:30:00Z",
"blueprint_file": "353_E_86th_St.pdf",
"model_used": "gemini-2.5-pro",
"processing_time_seconds": 25.3,
"cost_usd": 0.08
},
"summary": {
"total_floors": 3,
"total_units": 8,
"total_capacity_btu": 256000,
"total_capacity_kw": 75.0
},
"floors": [
{
"floor_number": 1,
"floor_type": "residential",
"units": [
{
"id": "AC-A",
"type": "Ceiling Concealed Ducted",
"quantity": 1,
"capacity_btu": 36000,
"capacity_kw": 10.5,
"manufacturer": "Carrier",
"model": "25HNE024A03",
"location": "Living area",
"detection_confidence": 0.95
}
]
}
],
"equipment_schedule": [
{
"id": 1,
"manufacturer": "Carrier",
"model": "25HNE024A03",
"type": "Ceiling Cassette",
"capacity_btu": 24000,
"voltage": "208-230V",
"phase": 1,
"frequency_hz": 60,
"quantity": 1,
"floor": 2,
"unit_tag": "AC-B"
}
],
"reconciliation": {
"issues_found": 2,
"high_severity": 0,
"medium_severity": 2,
"low_severity": 0,
"issues": [
{
"type": "capacity_mismatch",
"floor": 3,
"unit_id": "AC-E",
"plan_capacity": 36000,
"schedule_capacity": 48000,
"severity": "medium",
"recommendation": "Verify actual installed capacity"
}
]
}
}
Checklist:
- Metadata section
- Summary statistics
- Detailed floor data
- Equipment schedule
- Reconciliation issues
- Confidence scores
- Timestamps
π₯οΈ PHASE E: GUI IMPLEMENTATION OPTIONS
Option 1: Streamlit (Recommended for Quick Demo)
Pros:
- Fastest to build
- No frontend experience needed
- Built-in data visualization
- Free hosting on Streamlit Cloud
Cons:
- Limited customization
- Not suitable for production UIs
- Slower for large datasets
Setup:
pip install streamlit plotly pandas openpyxl
streamlit run app.py
Option 2: React + FastAPI
Pros:
- Full customization
- Production-quality UI
- Better performance
- Responsive design
Cons:
- Takes longer to build
- Requires frontend/backend skills
- More complex deployment
Setup:
# Backend
pip install fastapi uvicorn pydantic
# Frontend
npm create vite@latest hvac-ui -- --template react
npm install axios react-query
Option 3: Simple HTML + jQuery
Pros:
- Minimal dependencies
- Fast to build
- Easy to deploy
- No build step needed
Cons:
- Limited functionality
- Not modern
- Harder to maintain
π CSV EXPORT IMPLEMENTATION CHECKLIST
Code Structure
# outputs/csv_exporter.py
import pandas as pd
from typing import Dict, List
from pathlib import Path
class HVACCSVExporter:
"""Export HVAC analysis data to CSV formats"""
def __init__(self, output_dir: str = "./outputs"):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(exist_ok=True)
def export_all(self, analysis_data: Dict) -> Dict[str, Path]:
"""Export all CSV files and return paths"""
return {
'floors_units': self.export_floors_units(analysis_data),
'equipment_schedule': self.export_equipment_schedule(analysis_data),
'reconciliation': self.export_reconciliation(analysis_data),
'summary': self.export_summary(analysis_data),
}
def export_floors_units(self, data: Dict) -> Path:
"""Export floor and unit breakdown"""
rows = []
for floor in data['floors']:
for unit in floor['units']:
rows.append({
'Floor': floor['number'],
'Unit_ID': unit['tag'],
'Unit_Type': unit['type'],
'Quantity': unit['quantity'],
'Capacity_BTU': unit.get('capacity_btu'),
'Capacity_kW': unit.get('capacity_kw'),
'Status': unit.get('detection_status', 'Detected'),
'Location': unit.get('location', ''),
'Notes': unit.get('notes', ''),
})
df = pd.DataFrame(rows)
path = self.output_dir / "hvac_floors_units.csv"
df.to_csv(path, index=False)
return path
def export_equipment_schedule(self, data: Dict) -> Path:
"""Export equipment schedule"""
df = pd.DataFrame(data.get('equipment_schedule', []))
path = self.output_dir / "hvac_equipment_schedule.csv"
df.to_csv(path, index=False)
return path
def export_reconciliation(self, data: Dict) -> Path:
"""Export reconciliation report"""
issues = data.get('reconciliation', {}).get('issues', [])
df = pd.DataFrame(issues)
path = self.output_dir / "hvac_reconciliation.csv"
df.to_csv(path, index=False)
return path
def export_summary(self, data: Dict) -> Path:
"""Export summary statistics"""
summary = data.get('summary', {})
summary_df = pd.DataFrame([summary])
path = self.output_dir / "hvac_summary.csv"
summary_df.to_csv(path, index=False)
return path
# Usage
exporter = HVACCSVExporter()
csv_files = exporter.export_all(analysis_data)
print(f"Exported to: {csv_files}")
Checklist:
- CSV exporter class created
- floors_units.csv export working
- equipment_schedule.csv export working
- reconciliation.csv export working
- summary.csv export working
- Error handling for missing data
- File encoding (UTF-8)
- Column ordering consistent
π¨ GUI MOCKUP LAYOUT
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β π’ HVAC Blueprint Analyzer β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β π€ Upload Blueprint β
β βββββββββββββββββββββββββββββββββββββββββββ β
β β Drag & drop PDF here or click to browse β β
β βββββββββββββββββββββββββββββββββββββββββββ β
β β
β βοΈ Configuration β
β Model: [Gemini β] [OpenAI β] β
β Schedule: [Auto-Detect β] [Upload β] β
β Output Format: [CSV β] [PDF β] [JSON β] β
β β
β [π Analyze Blueprint] [πΎ Save Config]β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β π Results (Tabs) β
β [Floors & Units] [Equipment] [Reconciliation] [Downloads] β
β β
β ββ Floors & Units βββββββββββββββββββββββββββ β
β β β β
β β Summary Statistics: β β
β β ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββ β β
β β β Floors β Units β Capacity β Cost β β β
β β β 3 β 8 β256kBTU β$0.08 β β β
β β ββββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββ β β
β β β β
β β Detailed Table: β β
β β ββββββββββββββββββββββββββββββββββββββββββββ β
β β βFloorβUnit IDβType βQtyβBTU ββ β
β β βββββββββββββββββββββββββββββββββββββββββββ€β β
β β β 1 β AC-A βCeiling Duct β 1 β36000 ββ β
β β β 1 β AC-B βWall Mount β 1 β24000 ββ β
β β β 2 β AC-C βCeiling Duct β 1 β36000 ββ β
β β β 2 β AC-D βWall Mount β 2 β24000 ββ β
β β ββββββββββββββββββββββββββββββββββββββββββββ β
β β β β
β β [π Chart View] [π Table View] [π Refresh]β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββ Downloads βββββββββββββββββββββββββββββββββ β
β β β β
β β Available Outputs: β β
β β β Floors & Units (CSV) [β¬ Download] β β
β β β Equipment Schedule (CSV) [β¬ Download] β β
β β β Reconciliation (CSV) [β¬ Download] β β
β β β Analysis Report (PDF) [β¬ Download] β β
β β β β
β β [π¦ Download All as ZIP] β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
π IMPLEMENTATION ORDER
Week 1: CSV Output & Basic GUI
- Create CSV exporter (all 3 CSV types)
- Test CSV output with sample data
- Build basic Streamlit app with file upload
- Add results display table
Week 2: Full GUI & Visualizations
- Add charts and visualizations
- Implement tabs (Floors, Schedule, Reconciliation)
- Add schedule upload functionality
- Create downloads section
Week 3: Polish & Deployment
- PDF report generation
- A/B testing comparison view
- Error handling and validation
- Deploy to Streamlit Cloud or cloud provider
π SUCCESS CRITERIA
CSV Output:
- β All three CSVs generated successfully
- β Data is accurate and complete
- β Columns are properly labeled
- β No missing values (or clearly marked)
GUI:
- β File upload works smoothly
- β Results display in <2 seconds
- β All tabs functional
- β Download buttons work
- β Mobile-responsive (if web)
Integration:
- β FastAPI endpoint returns CSVs
- β GUI calls API correctly
- β Error messages are clear
- β No hardcoded paths
π INTEGRATION WITH FASTAPI
# main.py - FastAPI app with CSV export
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import FileResponse, StreamingResponse
from outputs.csv_exporter import HVACCSVExporter
import zipfile
from io import BytesIO
app = FastAPI()
exporter = HVACCSVExporter()
@app.post("/analyze")
async def analyze_blueprint(file: UploadFile = File(...)):
"""Analyze blueprint and return results"""
analysis_data = await process_blueprint(file)
# Generate CSVs
csv_files = exporter.export_all(analysis_data)
return {
"status": "success",
"analysis": analysis_data,
"csv_files": {name: str(path) for name, path in csv_files.items()}
}
@app.get("/download/csv/{csv_type}")
async def download_csv(csv_type: str, task_id: str):
"""Download specific CSV file"""
file_path = f"./outputs/{task_id}_{csv_type}.csv"
return FileResponse(file_path, filename=f"hvac_{csv_type}.csv")
@app.get("/download/all/{task_id}")
async def download_all(task_id: str):
"""Download all outputs as ZIP"""
zip_buffer = BytesIO()
with zipfile.ZipFile(zip_buffer, 'w') as zf:
zf.write(f"./outputs/{task_id}_floors_units.csv")
zf.write(f"./outputs/{task_id}_equipment_schedule.csv")
zf.write(f"./outputs/{task_id}_reconciliation.csv")
zf.write(f"./outputs/{task_id}_report.pdf")
zip_buffer.seek(0)
return StreamingResponse(
iter([zip_buffer.getvalue()]),
media_type="application/zip",
headers={"Content-Disposition": "attachment; filename=hvac_analysis.zip"}
)
Checklist:
- /analyze endpoint returns CSV file paths
- /download/csv/{type} serves files
- /download/all/{task_id} creates ZIP
- Content-Type headers correct
- Filenames are descriptive
- Error handling for missing files