- Set correct table ID: mx149yctebfwvys (ai_data_Memory) - Updated type column with SingleSelect options - Added all required columns for corrections, preferences, episodes, decisions, validation - Fixed column options for type, severity, status fields
789 lines
22 KiB
Python
789 lines
22 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
IT Site Survey AI Flask Application
|
|
Creates surveys for client sites and uses AI to recommend network setups.
|
|
Enhanced with PDF export and quote generation.
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import uuid
|
|
import requests
|
|
from datetime import datetime
|
|
from flask import Flask, jsonify, request, send_from_directory, Response, stream_with_context, send_file
|
|
from flask_cors import CORS
|
|
from reportlab.lib import colors
|
|
from reportlab.lib.pagesizes import letter
|
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, ListFlowable, ListItem
|
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
from reportlab.lib.units import inch
|
|
import io
|
|
|
|
app = Flask(__name__, static_url_path='', static_folder='.')
|
|
CORS(app)
|
|
|
|
OLLAMA_BASE = os.environ.get('OLLAMA_URL', 'http://192.168.19.25:11434')
|
|
|
|
# In-memory storage for surveys
|
|
surveys_db = {}
|
|
survey_responses_db = {}
|
|
|
|
# Default survey template
|
|
DEFAULT_SURVEY_TEMPLATE = {
|
|
"id": "concise-network-survey-template",
|
|
"name": "Concise Network Site Survey",
|
|
"description": "Streamlined one-page network infrastructure survey with dropdown selections",
|
|
"questions": [
|
|
{
|
|
"id": "company_name",
|
|
"category": "Client Information",
|
|
"question": "Company Name",
|
|
"type": "text"
|
|
},
|
|
{
|
|
"id": "industry",
|
|
"category": "Client Information",
|
|
"question": "Industry/Vertical",
|
|
"type": "select",
|
|
"options": [
|
|
"Healthcare",
|
|
"Education",
|
|
"Financial Services",
|
|
"Manufacturing",
|
|
"Retail",
|
|
"Technology",
|
|
"Government",
|
|
"Hospitality",
|
|
"Other"
|
|
]
|
|
},
|
|
{
|
|
"id": "site_size",
|
|
"category": "Site Information",
|
|
"question": "Site Size",
|
|
"type": "select",
|
|
"options": [
|
|
"Small (1-10 employees)",
|
|
"Medium (11-50 employees)",
|
|
"Large (51-250 employees)",
|
|
"Enterprise (250+ employees)"
|
|
]
|
|
},
|
|
{
|
|
"id": "building_type",
|
|
"category": "Site Information",
|
|
"question": "Building Type",
|
|
"type": "select",
|
|
"options": [
|
|
"Single Office",
|
|
"Multi-tenant",
|
|
"Campus",
|
|
"Warehouse",
|
|
"Retail",
|
|
"Industrial",
|
|
"Data Center",
|
|
"Other"
|
|
]
|
|
},
|
|
{
|
|
"id": "square_footage",
|
|
"category": "Site Information",
|
|
"question": "Total Square Footage",
|
|
"type": "select",
|
|
"options": [
|
|
"Under 5,000 sq ft",
|
|
"5,000 - 10,000 sq ft",
|
|
"10,000 - 25,000 sq ft",
|
|
"25,000 - 50,000 sq ft",
|
|
"Over 50,000 sq ft"
|
|
]
|
|
},
|
|
{
|
|
"id": "network_topology",
|
|
"category": "Existing Infrastructure",
|
|
"question": "Current Network Topology",
|
|
"type": "select",
|
|
"options": [
|
|
"None - New Installation",
|
|
"Flat Network",
|
|
"Two-tier (Access/Distribution)",
|
|
"Three-tier (Access/Distribution/Core)",
|
|
"Spine-Leaf",
|
|
"Other/Not Sure"
|
|
]
|
|
},
|
|
{
|
|
"id": "network_size",
|
|
"category": "Existing Infrastructure",
|
|
"question": "Current Network Size",
|
|
"type": "select",
|
|
"options": [
|
|
"Small (<50 devices)",
|
|
"Medium (50-250 devices)",
|
|
"Large (250-1000 devices)",
|
|
"Enterprise (1000+ devices)"
|
|
]
|
|
},
|
|
{
|
|
"id": "internet_bandwidth",
|
|
"category": "Connectivity",
|
|
"question": "Current Internet Bandwidth",
|
|
"type": "select",
|
|
"options": [
|
|
"Under 50 Mbps",
|
|
"50-100 Mbps",
|
|
"100-500 Mbps",
|
|
"500 Mbps-1 Gbps",
|
|
"1-10 Gbps",
|
|
"Over 10 Gbps",
|
|
"Not Sure"
|
|
]
|
|
},
|
|
{
|
|
"id": "isp_type",
|
|
"category": "Connectivity",
|
|
"question": "ISP Connection Type",
|
|
"type": "multiselect",
|
|
"options": [
|
|
"Fiber",
|
|
"Cable/DSL",
|
|
"Fixed Wireless",
|
|
"5G/LTE",
|
|
"Multiple ISPs",
|
|
"Satellite",
|
|
"Not Sure"
|
|
]
|
|
},
|
|
{
|
|
"id": "wireless_standard",
|
|
"category": "Wireless Infrastructure",
|
|
"question": "Current Wireless Standard",
|
|
"type": "select",
|
|
"options": [
|
|
"None",
|
|
"802.11n (Wi-Fi 4)",
|
|
"802.11ac (Wi-Fi 5)",
|
|
"802.11ax (Wi-Fi 6)",
|
|
"802.11be (Wi-Fi 7)",
|
|
"Mixed/Not Sure"
|
|
]
|
|
},
|
|
{
|
|
"id": "wireless_coverage",
|
|
"category": "Wireless Infrastructure",
|
|
"question": "Wireless Coverage Quality",
|
|
"type": "select",
|
|
"options": [
|
|
"Excellent - No dead zones",
|
|
"Good - Minor coverage issues",
|
|
"Fair - Several dead zones",
|
|
"Poor - Major coverage issues",
|
|
"No wireless network"
|
|
]
|
|
},
|
|
{
|
|
"id": "critical_applications",
|
|
"category": "Business Requirements",
|
|
"question": "Critical Business Applications",
|
|
"type": "multiselect",
|
|
"options": [
|
|
"Email/Collaboration (Office 365, Google Workspace)",
|
|
"VoIP/Video Conferencing",
|
|
"Cloud Applications",
|
|
"File Sharing/Storage",
|
|
"Database Applications",
|
|
"Remote Desktop/VDI",
|
|
"Video Surveillance",
|
|
"Point of Sale (POS)",
|
|
"Industrial Systems",
|
|
"Other"
|
|
]
|
|
},
|
|
{
|
|
"id": "device_types",
|
|
"category": "Device Requirements",
|
|
"question": "Primary Device Types",
|
|
"type": "multiselect",
|
|
"options": [
|
|
"Desktop Computers",
|
|
"Laptops",
|
|
"Mobile Devices (Phones/Tablets)",
|
|
"IoT Devices",
|
|
"Printers/MFPs",
|
|
"Servers",
|
|
"Video Equipment",
|
|
"Specialized Equipment"
|
|
]
|
|
},
|
|
{
|
|
"id": "user_density",
|
|
"category": "Usage Requirements",
|
|
"question": "User Density",
|
|
"type": "select",
|
|
"options": [
|
|
"Low (<10 devices per AP)",
|
|
"Medium (10-30 devices per AP)",
|
|
"High (30-50 devices per AP)",
|
|
"Very High (50+ devices per AP)"
|
|
]
|
|
},
|
|
{
|
|
"id": "performance_requirements",
|
|
"category": "Performance Requirements",
|
|
"question": "Network Performance Requirements",
|
|
"type": "multiselect",
|
|
"options": [
|
|
"High Speed (1+ Gbps)",
|
|
"Low Latency (<10ms)",
|
|
"High Availability (99.9%+)",
|
|
"Guest Network Access",
|
|
"BYOD Support",
|
|
"Outdoor Coverage",
|
|
"No Special Requirements"
|
|
]
|
|
},
|
|
{
|
|
"id": "security_requirements",
|
|
"category": "Security Requirements",
|
|
"question": "Security Requirements",
|
|
"type": "multiselect",
|
|
"options": [
|
|
"Basic Firewall",
|
|
"Content Filtering",
|
|
"Network Segmentation",
|
|
"802.1X Authentication",
|
|
"Guest Network Isolation",
|
|
"Compliance (HIPAA, PCI, etc.)",
|
|
"No Special Security Requirements"
|
|
]
|
|
},
|
|
{
|
|
"id": "compliance_needs",
|
|
"category": "Compliance Requirements",
|
|
"question": "Compliance Requirements",
|
|
"type": "select",
|
|
"options": [
|
|
"None",
|
|
"HIPAA (Healthcare)",
|
|
"PCI-DSS (Payment Card)",
|
|
"SOX (Financial)",
|
|
"FERPA (Educational)",
|
|
"GDPR (Privacy)",
|
|
"Industry Specific",
|
|
"Multiple Compliance Requirements"
|
|
]
|
|
},
|
|
{
|
|
"id": "budget_range",
|
|
"category": "Project Information",
|
|
"question": "Estimated Budget Range",
|
|
"type": "select",
|
|
"options": [
|
|
"Under $5,000",
|
|
"$5,000 - $15,000",
|
|
"$15,000 - $50,000",
|
|
"$50,000 - $100,000",
|
|
"$100,000+",
|
|
"Not Established"
|
|
]
|
|
},
|
|
{
|
|
"id": "timeline",
|
|
"category": "Project Information",
|
|
"question": "Project Timeline",
|
|
"type": "select",
|
|
"options": [
|
|
"ASAP - Emergency",
|
|
"Within 2 weeks",
|
|
"Within 1 month",
|
|
"Within 3 months",
|
|
"Within 6 months",
|
|
"Flexible/Planning Phase"
|
|
]
|
|
},
|
|
{
|
|
"id": "additional_notes",
|
|
"category": "Additional Information",
|
|
"question": "Additional Notes or Special Requirements",
|
|
"type": "textarea",
|
|
"placeholder": "Please share any other important details about your network requirements..."
|
|
}
|
|
]
|
|
}
|
|
|
|
def call_ollama(prompt, model='llama3.2:3b', stream=False):
|
|
"""Call Ollama API with the given prompt."""
|
|
try:
|
|
if stream:
|
|
resp = requests.post(
|
|
f'{OLLAMA_BASE}/api/generate',
|
|
json={'model': model, 'prompt': prompt, 'stream': True},
|
|
stream=True,
|
|
timeout=120
|
|
)
|
|
return resp
|
|
else:
|
|
resp = requests.post(
|
|
f'{OLLAMA_BASE}/api/generate',
|
|
json={'model': model, 'prompt': prompt, 'stream': False},
|
|
timeout=120
|
|
)
|
|
return resp.json().get('response', '')
|
|
except Exception as e:
|
|
return f'Error: {str(e)}'
|
|
|
|
|
|
@app.route('/')
|
|
def index():
|
|
return send_from_directory('.', 'index.html')
|
|
|
|
|
|
@app.route('/<path:path>')
|
|
def serve_static(path):
|
|
return send_from_directory('.', path)
|
|
|
|
|
|
@app.route('/api/health')
|
|
def health_check():
|
|
try:
|
|
r = requests.get(f'{OLLAMA_BASE}/', timeout=3)
|
|
ollama_online = r.status_code == 200
|
|
except:
|
|
ollama_online = False
|
|
|
|
return jsonify({
|
|
'status': 'ok',
|
|
'service': 'site-survey-ai',
|
|
'ollama_connected': ollama_online,
|
|
'timestamp': datetime.utcnow().isoformat()
|
|
})
|
|
|
|
|
|
@app.route('/api/ollama-status')
|
|
def ollama_status():
|
|
try:
|
|
r = requests.get(f'{OLLAMA_BASE}/', timeout=3)
|
|
return jsonify({'online': r.status_code == 200})
|
|
except:
|
|
return jsonify({'online': False})
|
|
|
|
|
|
@app.route('/api/surveys/template')
|
|
def get_template():
|
|
return jsonify({'template': DEFAULT_SURVEY_TEMPLATE})
|
|
|
|
|
|
@app.route('/api/surveys', methods=['GET'])
|
|
def get_surveys():
|
|
return jsonify({
|
|
'surveys': list(surveys_db.values())
|
|
})
|
|
|
|
|
|
@app.route('/api/surveys', methods=['POST'])
|
|
def create_survey():
|
|
data = request.get_json()
|
|
|
|
survey_id = str(uuid.uuid4())
|
|
survey = {
|
|
'id': survey_id,
|
|
'name': data.get('name', 'New Survey'),
|
|
'description': data.get('description', ''),
|
|
'client_name': data.get('client_name', ''),
|
|
'site_name': data.get('site_name', ''),
|
|
'questions': data.get('questions', DEFAULT_SURVEY_TEMPLATE['questions']),
|
|
'created_at': datetime.utcnow().isoformat(),
|
|
'status': 'active',
|
|
'responses': []
|
|
}
|
|
|
|
surveys_db[survey_id] = survey
|
|
|
|
return jsonify({'survey': survey}), 201
|
|
|
|
|
|
@app.route('/api/surveys/<survey_id>', methods=['GET'])
|
|
def get_survey(survey_id):
|
|
survey = surveys_db.get(survey_id)
|
|
if not survey:
|
|
return jsonify({'error': 'Survey not found'}), 404
|
|
return jsonify({'survey': survey})
|
|
|
|
|
|
@app.route('/api/surveys/<survey_id>/responses', methods=['POST'])
|
|
def submit_response(survey_id):
|
|
survey = surveys_db.get(survey_id)
|
|
if not survey:
|
|
return jsonify({'error': 'Survey not found'}), 404
|
|
|
|
data = request.get_json()
|
|
|
|
response_id = str(uuid.uuid4())
|
|
response = {
|
|
'id': response_id,
|
|
'survey_id': survey_id,
|
|
'submitted_by': data.get('submitted_by', 'Anonymous'),
|
|
'responses': data.get('responses', {}),
|
|
'submitted_at': datetime.utcnow().isoformat()
|
|
}
|
|
|
|
survey['responses'].append(response)
|
|
survey_responses_db[response_id] = response
|
|
|
|
return jsonify({'response': response}), 201
|
|
|
|
|
|
@app.route('/api/surveys/responses', methods=['GET'])
|
|
def get_all_responses():
|
|
"""Get all survey responses across all surveys."""
|
|
all_responses = list(survey_responses_db.values())
|
|
return jsonify({'responses': all_responses})
|
|
|
|
|
|
@app.route('/api/surveys/<survey_id>/analyze', methods=['POST'])
|
|
def analyze_survey(survey_id):
|
|
"""Analyze survey responses with AI to generate recommendations."""
|
|
survey = surveys_db.get(survey_id)
|
|
if not survey:
|
|
return jsonify({'error': 'Survey not found'}), 404
|
|
|
|
if not survey['responses']:
|
|
return jsonify({'error': 'No responses to analyze'}), 400
|
|
|
|
latest_response = survey['responses'][-1]
|
|
responses = latest_response['responses']
|
|
|
|
context = f"""
|
|
IT Infrastructure Site Survey Results:
|
|
|
|
Client: {survey.get('client_name', 'Unknown')}
|
|
Site: {survey.get('site_name', 'Unknown')}
|
|
|
|
Survey Responses:
|
|
"""
|
|
|
|
for q in survey['questions']:
|
|
qid = q['id']
|
|
answer = responses.get(qid, 'Not answered')
|
|
context += f"\n{q['category']} - {q['question']}\nAnswer: {answer}\n"
|
|
|
|
prompt = f"""You are a senior network architect and IT infrastructure consultant. Based on the following site survey, provide a comprehensive network infrastructure recommendation.
|
|
|
|
{context}
|
|
|
|
Generate a detailed IT infrastructure recommendation report including:
|
|
|
|
1. **EXECUTIVE SUMMARY**
|
|
- Overview of the site and requirements
|
|
- Recommended approach (high-level)
|
|
|
|
2. **NETWORK TOPOLOGY RECOMMENDATION**
|
|
- Recommended network architecture (star, mesh, hybrid)
|
|
- VLAN structure if applicable
|
|
- Network segmentation strategy
|
|
|
|
3. **HARDWARE RECOMMENDATIONS**
|
|
- Firewall/router specifications
|
|
- Switch recommendations (managed/unmanaged, PoE needs)
|
|
- Wireless access point placement and quantity
|
|
- Cabling requirements
|
|
|
|
4. **INTERNET CONNECTIVITY**
|
|
- Recommended ISP and bandwidth
|
|
- Backup connectivity options
|
|
- WAN configuration if multi-site
|
|
|
|
5. **SECURITY RECOMMENDATIONS**
|
|
- Security appliances/services needed
|
|
- Compliance considerations
|
|
- Access control policies
|
|
|
|
6. **ESTIMATED COSTS**
|
|
- Equipment costs
|
|
- Installation costs
|
|
- Ongoing service costs
|
|
|
|
7. **IMPLEMENTATION TIMELINE**
|
|
- Phase 1: Planning and procurement
|
|
- Phase 2: Installation
|
|
- Phase 3: Testing and cutover
|
|
|
|
8. **PRIORITY ACTIONS**
|
|
- Top 3 immediate actions needed
|
|
|
|
Format as a professional proposal suitable for client presentation.
|
|
"""
|
|
|
|
def generate():
|
|
try:
|
|
resp = requests.post(
|
|
f'{OLLAMA_BASE}/api/generate',
|
|
json={'model': 'llama3.2:3b', 'prompt': prompt, 'stream': True},
|
|
stream=True,
|
|
timeout=180
|
|
)
|
|
for line in resp.iter_lines():
|
|
if line:
|
|
yield line.decode('utf-8') + '\n'
|
|
except Exception as e:
|
|
error_chunk = json.dumps({'error': str(e), 'done': True})
|
|
yield error_chunk + '\n'
|
|
|
|
return Response(stream_with_context(generate()), mimetype='application/x-ndjson')
|
|
|
|
|
|
@app.route('/api/surveys/<survey_id>/generate-quote', methods=['POST'])
|
|
def generate_quote(survey_id):
|
|
"""Generate a service quote based on survey responses."""
|
|
survey = surveys_db.get(survey_id)
|
|
if not survey or not survey['responses']:
|
|
return jsonify({'error': 'No survey data available'}), 400
|
|
|
|
latest_response = survey['responses'][-1]
|
|
responses = latest_response['responses']
|
|
|
|
context = f"""
|
|
Site Survey Data for Quote Generation:
|
|
|
|
Client: {survey.get('client_name', 'Unknown')}
|
|
Site: {survey.get('site_name', 'Unknown')}
|
|
"""
|
|
|
|
for q in survey['questions']:
|
|
qid = q['id']
|
|
answer = responses.get(qid, 'Not answered')
|
|
context += f"\n{q['category']}: {answer}"
|
|
|
|
prompt = f"""You are an IT services sales consultant. Based on the following site survey, create a professional service quote for network infrastructure setup.
|
|
|
|
{context}
|
|
|
|
Generate a professional service quote in the following format:
|
|
|
|
**IT INFRASTRUCTURE SERVICES QUOTE**
|
|
**For: {survey.get('client_name', 'Client')}**
|
|
**Site: {survey.get('site_name', 'Site')}**
|
|
**Date: {datetime.now().strftime('%B %d, %Y')}**
|
|
|
|
**SCOPE OF WORK:**
|
|
[List the specific services needed based on the survey - network design, equipment procurement, installation, configuration, testing]
|
|
|
|
**RECOMMENDED SOLUTION:**
|
|
1. [Category - e.g., Network Design & Planning] - $[Price]
|
|
- [Detailed description of what's included]
|
|
2. [Category - e.g., Equipment & Hardware] - $[Price]
|
|
- [List of equipment and costs]
|
|
3. [Category - e.g., Installation & Configuration] - $[Price]
|
|
- [Labor hours and rates]
|
|
4. [Category - e.g., Security Implementation] - $[Price]
|
|
- [Security services included]
|
|
5. [Category - e.g., Testing & Documentation] - $[Price]
|
|
- [Testing procedures and documentation]
|
|
|
|
**INVESTMENT SUMMARY:**
|
|
- Initial Assessment & Design: $[Amount]
|
|
- Equipment/Hardware: $[Amount]
|
|
- Labor/Installation: $[Amount]
|
|
- Training & Documentation: $[Amount]
|
|
- **Total Project Investment: $[Amount]**
|
|
|
|
**OPTIONAL ONGOING SERVICES:**
|
|
- Monthly Management & Monitoring: $[Amount]/month
|
|
- Annual Maintenance: $[Amount]/year
|
|
- Support Contract: $[Amount]/month
|
|
|
|
**TIMELINE:** [Timeframe]
|
|
- Week 1-2: Design & Procurement
|
|
- Week 3-4: Installation
|
|
- Week 5: Testing & Training
|
|
|
|
**TERMS:**
|
|
- 50% deposit required to begin
|
|
- Net 15 on completion
|
|
- Quote valid for 30 days
|
|
|
|
This is an estimate based on the initial site survey. Final pricing will be confirmed after detailed site assessment.
|
|
"""
|
|
|
|
response = call_ollama(prompt, stream=False)
|
|
return jsonify({'quote': response})
|
|
|
|
|
|
@app.route('/api/surveys/<survey_id>/export/pdf', methods=['POST'])
|
|
def export_pdf(survey_id):
|
|
"""Export survey report to PDF."""
|
|
survey = surveys_db.get(survey_id)
|
|
if not survey or not survey['responses']:
|
|
return jsonify({'error': 'No survey data available'}), 400
|
|
|
|
latest_response = survey['responses'][-1]
|
|
responses = latest_response['responses']
|
|
|
|
buffer = io.BytesIO()
|
|
doc = SimpleDocTemplate(buffer, pagesize=letter,
|
|
rightMargin=72, leftMargin=72,
|
|
topMargin=72, bottomMargin=18)
|
|
|
|
elements = []
|
|
styles = getSampleStyleSheet()
|
|
|
|
title_style = ParagraphStyle(
|
|
'CustomTitle',
|
|
parent=styles['Heading1'],
|
|
fontSize=24,
|
|
textColor=colors.HexColor('#1f6feb'),
|
|
spaceAfter=30
|
|
)
|
|
|
|
heading_style = ParagraphStyle(
|
|
'CustomHeading',
|
|
parent=styles['Heading2'],
|
|
fontSize=16,
|
|
textColor=colors.HexColor('#58a6ff'),
|
|
spaceAfter=12,
|
|
spaceBefore=12
|
|
)
|
|
|
|
# Title
|
|
elements.append(Paragraph(f"IT Site Survey Report", title_style))
|
|
elements.append(Paragraph(f"Client: {survey.get('client_name', 'Unknown')}", styles['Normal']))
|
|
elements.append(Paragraph(f"Site: {survey.get('site_name', 'Unknown')}", styles['Normal']))
|
|
elements.append(Paragraph(f"Date: {datetime.now().strftime('%B %d, %Y')}", styles['Normal']))
|
|
elements.append(Spacer(1, 0.3*inch))
|
|
|
|
# Survey Responses
|
|
elements.append(Paragraph("Survey Responses", heading_style))
|
|
|
|
for q in survey['questions']:
|
|
qid = q['id']
|
|
answer = responses.get(qid, 'Not answered')
|
|
if isinstance(answer, list):
|
|
answer = ', '.join(answer)
|
|
|
|
elements.append(Paragraph(f"<b>{q['category']}</b>", styles['Heading3']))
|
|
elements.append(Paragraph(q['question'], styles['Normal']))
|
|
elements.append(Paragraph(f"Answer: {answer}", styles['Normal']))
|
|
elements.append(Spacer(1, 0.1*inch))
|
|
|
|
doc.build(elements)
|
|
|
|
buffer.seek(0)
|
|
return send_file(
|
|
buffer,
|
|
as_attachment=True,
|
|
download_name=f'Site_Survey_{survey.get("client_name", "Client").replace(" ", "_")}_{datetime.now().strftime("%Y%m%d")}.pdf',
|
|
mimetype='application/pdf'
|
|
)
|
|
|
|
|
|
@app.route('/api/surveys/<survey_id>/export/text', methods=['POST'])
|
|
def export_text(survey_id):
|
|
"""Export survey report to formatted text."""
|
|
survey = surveys_db.get(survey_id)
|
|
if not survey or not survey['responses']:
|
|
return jsonify({'error': 'No survey data available'}), 400
|
|
|
|
latest_response = survey['responses'][-1]
|
|
responses = latest_response['responses']
|
|
|
|
text_report = f"""IT SITE SURVEY REPORT
|
|
{'=' * 60}
|
|
|
|
Client: {survey.get('client_name', 'Unknown')}
|
|
Site: {survey.get('site_name', 'Unknown')}
|
|
Date: {datetime.now().strftime('%B %d, %Y')}
|
|
|
|
{'=' * 60}
|
|
SURVEY RESPONSES:
|
|
{'=' * 60}
|
|
|
|
"""
|
|
|
|
for q in survey['questions']:
|
|
qid = q['id']
|
|
answer = responses.get(qid, 'Not answered')
|
|
if isinstance(answer, list):
|
|
answer = ', '.join(answer)
|
|
|
|
text_report += f"""
|
|
{q['category']}:
|
|
Question: {q['question']}
|
|
Answer: {answer}
|
|
|
|
"""
|
|
|
|
text_report += f"""
|
|
{'=' * 60}
|
|
Report generated by IT Site Survey AI
|
|
{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
"""
|
|
|
|
buffer = io.BytesIO(text_report.encode('utf-8'))
|
|
return send_file(
|
|
buffer,
|
|
as_attachment=True,
|
|
download_name=f'Site_Survey_{survey.get("client_name", "Client").replace(" ", "_")}_{datetime.now().strftime("%Y%m%d")}.txt',
|
|
mimetype='text/plain'
|
|
)
|
|
|
|
|
|
@app.route('/api/surveys/<survey_id>/export/email', methods=['POST'])
|
|
def export_email(survey_id):
|
|
"""Export survey report formatted for email."""
|
|
survey = surveys_db.get(survey_id)
|
|
if not survey or not survey['responses']:
|
|
return jsonify({'error': 'No survey data available'}), 400
|
|
|
|
latest_response = survey['responses'][-1]
|
|
responses = latest_response['responses']
|
|
|
|
email_body = f"""
|
|
<h2>IT Site Survey Report - {survey.get('client_name', 'Client')}</h2>
|
|
<p><strong>Site:</strong> {survey.get('site_name', 'Unknown')}</p>
|
|
<p><strong>Date:</strong> {datetime.now().strftime('%B %d, %Y')}</p>
|
|
|
|
<hr>
|
|
|
|
<h3>Survey Responses:</h3>
|
|
|
|
<table border="1" cellpadding="5" cellspacing="0" style="border-collapse: collapse;">
|
|
<tr style="background-color: #f0f0f0;">
|
|
<th>Category</th>
|
|
<th>Question</th>
|
|
<th>Answer</th>
|
|
</tr>
|
|
"""
|
|
|
|
for q in survey['questions']:
|
|
qid = q['id']
|
|
answer = responses.get(qid, 'Not answered')
|
|
if isinstance(answer, list):
|
|
answer = ', '.join(answer)
|
|
|
|
email_body += f"""
|
|
<tr>
|
|
<td>{q['category']}</td>
|
|
<td>{q['question']}</td>
|
|
<td>{answer}</td>
|
|
</tr>
|
|
"""
|
|
|
|
email_body += """
|
|
</table>
|
|
|
|
<p>---<br>
|
|
Report generated by IT Site Survey AI<br>
|
|
""" + datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "</p>
|
|
"""
|
|
|
|
return jsonify({'email_body': email_body})
|
|
|
|
|
|
if __name__ == '__main__':
|
|
port = int(os.environ.get('PORT', 3003))
|
|
print("───────────────────────────────────────")
|
|
print(" 🏢 IT Site Survey AI")
|
|
print(f" Running on http://0.0.0.0:{port}")
|
|
print(f" Ollama: {OLLAMA_BASE}")
|
|
print("───────────────────────────────────────")
|
|
app.run(host='0.0.0.0', port=port, debug=False, use_reloader=False) |