From f668a54e5fac4ccb58762055398cd49cd3fa7383 Mon Sep 17 00:00:00 2001 From: JC Beasley Date: Thu, 2 Jul 2026 15:18:42 -0700 Subject: [PATCH] Add .gitignore and remove incomplete file --- .gitignore | 15 ++ app_complete.py | 663 ------------------------------------------------ 2 files changed, 15 insertions(+), 663 deletions(-) delete mode 100644 app_complete.py diff --git a/.gitignore b/.gitignore index e69de29..819b98b 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,15 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +venv/ + +# Uploads +uploads/ + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store diff --git a/app_complete.py b/app_complete.py deleted file mode 100644 index ebe13d3..0000000 --- a/app_complete.py +++ /dev/null @@ -1,663 +0,0 @@ -#!/usr/bin/env python3 -""" -IT Site Survey AI Application -Complete implementation with all requested features: -- Photo uploads for AI assessment -- Model selection (Ollama vision models) -- Export results (PDF, Text, Email) -- Delete surveys (individual + Clear All) -- Chat interface for AI interaction -- Load Sample Data for testing -- Comprehensive 35-question survey template -""" - -from flask import Flask, request, jsonify, send_file -from flask_cors import CORS -import os -import json -import uuid -from datetime import datetime -import base64 -import requests -from werkzeug.utils import secure_filename - -app = Flask(__name__) -CORS(app, resources={ - r"/api/*": { - "origins": ["http://192.168.50.11:3003", "http://localhost:3003"], - "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"], - "allow_headers": ["Content-Type", "Authorization"] - } -}) - -# Configuration -UPLOAD_FOLDER = 'uploads' -PHOTOS_FOLDER = 'uploads/photos' -ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} -OLLAMA_HOST = os.getenv('OLLAMA_HOST', 'http://192.168.19.25:11434') -OLLAMA_URL = f"{OLLAMA_HOST}/api/chat" - -# Create upload directories -os.makedirs(UPLOAD_FOLDER, exist_ok=True) -os.makedirs(PHOTOS_FOLDER, exist_ok=True) - -# In-memory storage -surveys_db = {} -responses_db = {} - -# Survey Template with 35 comprehensive questions -DEFAULT_SURVEY_TEMPLATE = { - "title": "IT Infrastructure Site Survey", - "description": "Comprehensive network infrastructure assessment", - "questions": [ - # Site Topology & Layout (1-4) - { - "id": "q1", - "category": "Site Topology & Layout", - "question": "What is the total square footage of the site?", - "type": "number", - "placeholder": "e.g., 3500" - }, - { - "id": "q2", - "category": "Site Topology & Layout", - "question": "How many floors or buildings need network coverage?", - "type": "number", - "placeholder": "e.g., 2" - }, - { - "id": "q3", - "category": "Site Topology & Layout", - "question": "Describe the site layout (open plan, offices, warehouse, etc.)", - "type": "textarea", - "placeholder": "Describe the layout..." - }, - { - "id": "q4", - "category": "Site Topology & Layout", - "question": "Are there any physical barriers that may affect WiFi coverage?", - "type": "multiselect", - "options": ["Concrete walls", "Metal shelving", "Elevator shafts", "Thick drywall", "None"] - }, - - # Network Closet & Equipment (5-8) - { - "id": "q5", - "category": "Network Closet & Equipment", - "question": "How many network closets or IDF rooms are on-site?", - "type": "number", - "placeholder": "e.g., 2" - }, - { - "id": "q6", - "category": "Network Closet & Equipment", - "question": "Describe the current network closet setup", - "type": "textarea", - "placeholder": "Describe the setup..." - }, - { - "id": "q7", - "category": "Network Closet & Equipment", - "question": "What type of power and cooling is available in the network closets?", - "type": "textarea", - "placeholder": "Describe power and cooling..." - }, - { - "id": "q8", - "category": "Network Closet & Equipment", - "question": "Are there existing server racks or wall-mounted equipment?", - "type": "select", - "options": ["Yes, full racks", "Yes, partial racks", "Wall-mounted only", "No existing racks"] - }, - - # Cabling Infrastructure (9-12) - { - "id": "q9", - "category": "Cabling Infrastructure", - "question": "What type of existing cabling is in place?", - "type": "select", - "options": ["Cat5e (100Mb-1GbE)", "Cat6 (1GbE-10GbE)", "Cat6a (10GbE)", "Fiber (Multi-mode)", "Fiber (Single-mode)", "No existing cabling", "Unknown"] - }, - { - "id": "q10", - "category": "Cabling Infrastructure", - "question": "How many network drops are currently installed?", - "type": "number", - "placeholder": "e.g., 120" - }, - { - "id": "q11", - "category": "Cabling Infrastructure", - "question": "What is the estimated distance from the MDF to the farthest IDF or workstation?", - "type": "textarea", - "placeholder": "Describe distances..." - }, - { - "id": "q12", - "category": "Cabling Infrastructure", - "question": "Are there any special cabling requirements?", - "type": "multiselect", - "options": ["Plenum-rated cable required", "Outdoor-rated cable needed", "Direct burial cable needed", "Aerial cable needed", "Shielded cable required", "None"] - }, - - # Network Equipment (13-15) - { - "id": "q13", - "category": "Network Equipment", - "question": "What network equipment is currently installed?", - "type": "multiselect", - "options": ["Router/Firewall", "Managed switches", "Unmanaged switches", "Wireless access points", "None/Unknown"] - }, - { - "id": "q14", - "category": "Network Equipment", - "question": "What type of switches are needed?", - "type": "select", - "options": ["Unmanaged (plug-and-play)", "Managed (Layer 2)", "Managed (Layer 3 with routing)", "PoE switches", "Industrial-grade switches"] - }, - { - "id": "q15", - "category": "Network Equipment", - "question": "How many wireless access points are needed?", - "type": "number", - "placeholder": "e.g., 6" - }, - - # Power Requirements (16-18) - { - "id": "q16", - "category": "Power Requirements", - "question": "What is the estimated total power consumption for all network equipment?", - "type": "textarea", - "placeholder": "Describe power requirements..." - }, - { - "id": "q17", - "category": "Power Requirements", - "question": "What PoE devices will be connected?", - "type": "multiselect", - "options": ["IP Phones", "Wireless APs", "IP Cameras", "IoT sensors", "None", "Not sure"] - }, - { - "id": "q18", - "category": "Power Requirements", - "question": "What UPS backup capacity is required?", - "type": "select", - "options": ["No backup needed", "Minimal backup (15-30 min)", "Standard backup (1-2 hours)", "Extended backup (4+ hours)", "Generator backup"] - }, - - # User & Device Requirements (19-21) - { - "id": "q19", - "category": "User & Device Requirements", - "question": "How many employees or users will use the network?", - "type": "number", - "placeholder": "e.g., 45" - }, - { - "id": "q20", - "category": "User & Device Requirements", - "question": "What is the estimated total number of devices?", - "type": "number", - "placeholder": "e.g., 85" - }, - { - "id": "q21", - "category": "User & Device Requirements", - "question": "What types of network activities will be performed?", - "type": "multiselect", - "options": ["Email/Web Browsing", "Video conferencing", "File sharing", "Cloud applications", "Remote desktop", "VoIP/Phone calls", "Streaming media"] - }, - - # Security & Compliance (22-24) - { - "id": "q22", - "category": "Security & Compliance", - "question": "What compliance standards must be met?", - "type": "multiselect", - "options": ["HIPAA", "PCI-DSS", "SOC 2", "GDPR", "NIST", "None/General security"] - }, - { - "id": "q23", - "category": "Security & Compliance", - "question": "What network segmentation is required?", - "type": "multiselect", - "options": ["Corporate network", "Guest network", "IoT network", "Management network", "No segmentation needed"] - }, - { - "id": "q24", - "category": "Security & Compliance", - "question": "What security features are needed?", - "type": "multiselect", - "options": ["Firewall", "VPN access", "Intrusion detection", "Network access control (NAC)", "Content filtering", "None"] - }, - - # Internet & WAN (25-27) - { - "id": "q25", - "category": "Internet & WAN", - "question": "What type of internet connectivity is available?", - "type": "multiselect", - "options": ["Fiber", "Cable/DSL", "5G/Cellular backup", "Dedicated leased line", "No existing connection"] - }, - { - "id": "q26", - "category": "Internet & WAN", - "question": "What is the required internet bandwidth?", - "type": "textarea", - "placeholder": "e.g., 500Mbps download / 250Mbps upload" - }, - { - "id": "q27", - "category": "Internet & WAN", - "question": "Are there multiple sites that need to be connected?", - "type": "select", - "options": ["No - Single site only", "Yes - 2-3 sites", "Yes - 4+ sites", "Not sure yet"] - }, - - # Existing Infrastructure (28-30) - { - "id": "q28", - "category": "Existing Infrastructure", - "question": "Is there existing IT infrastructure to integrate with?", - "type": "select", - "options": ["Yes - Full upgrade needed", "Yes - Partial upgrade", "No - New installation", "Not sure"] - }, - { - "id": "q29", - "category": "Existing Infrastructure", - "question": "Describe any existing network issues or limitations", - "type": "textarea", - "placeholder": "Describe current issues..." - }, - { - "id": "q30", - "category": "Existing Infrastructure", - "question": "What performance issues have been observed?", - "type": "multiselect", - "options": ["Slow internet", "WiFi dead zones", "Network congestion", "Frequent disconnections", "No issues observed"] - }, - - # Budget & Timeline (31-33) - { - "id": "q31", - "category": "Budget & Timeline", - "question": "What is the estimated budget for this project?", - "type": "select", - "options": ["Under $10,000", "$10,000 - $25,000", "$25,000 - $50,000", "$50,000 - $100,000", "$100,000+", "Not determined yet"] - }, - { - "id": "q32", - "category": "Budget & Timeline", - "question": "What is the desired timeline for completion?", - "type": "select", - "options": ["Within 1 month", "Within 3 months", "Within 6 months", "Within 1 year", "Flexible"] - }, - { - "id": "q33", - "category": "Budget & Timeline", - "question": "Who will be responsible for ongoing network management?", - "type": "select", - "options": ["Internal IT team", "Managed service provider (MSP)", "Hybrid approach", "Not determined yet"] - }, - - # Additional Details (34-35) - { - "id": "q34", - "category": "Additional Details", - "question": "Upload any site photos, floor plans, or network diagrams", - "type": "file", - "placeholder": "Upload photos..." - }, - { - "id": "q35", - "category": "Additional Details", - "question": "Any additional requirements or special considerations?", - "type": "textarea", - "placeholder": "Describe any additional requirements..." - } - ] -} - -# Sample data for testing -SAMPLE_SURVEY_DATA = { - "name": "Sample Corporate Office Survey", - "client_name": "Acme Corporation", - "site_name": "Main Office - Downtown", - "description": "Comprehensive network infrastructure assessment for downtown office location", - "responses": { - "q1": "3500", - "q2": "2", - "q3": "Single-story office building with open floor plan, 8 private offices, 2 conference rooms, break room, and reception area. Approximately 60 workstations in cubicle area.", - "q4": ["Concrete walls"], - "q5": "2", - "q6": "MDF located in basement utility room. IDF on first floor in IT storage closet. Both have adequate ventilation but limited space.", - "q7": "2x 20A circuits per closet, UPS backup planned for MDF", - "q8": "Yes, partial racks", - "q9": "Cat5e (100Mb-1GbE)", - "q10": "120", - "q11": "Average 150 feet, longest run approximately 280 feet to corner offices", - "q12": ["Plenum-rated cable required"], - "q13": ["Router/Firewall", "Unmanaged switches", "Wireless access points"], - "q14": "Managed (Layer 2)", - "q15": "6", - "q16": "1800W estimated for all network equipment including PoE devices", - "q17": ["IP Phones", "Wireless APs", "IP Cameras"], - "q18": "Limited backup (1-2 hours)", - "q19": "45", - "q20": "85", - "q21": ["Email/Web Browsing", "Video conferencing", "File sharing", "Cloud applications", "Remote desktop"], - "q22": ["SOC 2"], - "q23": ["Corporate network", "Guest network", "IoT network", "Management network"], - "q24": ["Firewall", "VPN access", "Network access control (NAC)"], - "q25": ["Fiber", "5G/Cellular backup"], - "q26": "500Mbps download / 250Mbps upload", - "q27": "No - Single site only", - "q28": "Yes - Partial upgrade", - "q29": "Current WiFi has dead zones in conference rooms. Existing switches are unmanaged and showing signs of age. Need better coverage and management capabilities.", - "q30": ["Slow internet", "WiFi dead zones", "Network congestion"], - "q31": "$25,000 - $50,000", - "q32": "Within 3 months", - "q33": "Internal IT team", - "q34": "", - "q35": "Need outdoor WiFi coverage for patio area. Plan to add IP cameras in future. Prefer Cisco or Ubiquiti equipment. Require weekend installation to minimize downtime." - } -} - - -@app.route('/api/health', methods=['GET']) -def health_check(): - """Health check endpoint""" - try: - resp = requests.get(f"{OLLAMA_HOST}/api/tags", timeout=5) - ollama_online = resp.status_code == 200 - except: - ollama_online = False - - return jsonify({ - "status": "ok", - "service": "site-survey-ai", - "timestamp": datetime.utcnow().isoformat(), - "ollama_connected": ollama_online - }) - - -@app.route('/api/models', methods=['GET']) -def get_models(): - """Get available Ollama models""" - try: - resp = requests.get(f"{OLLAMA_HOST}/api/tags", timeout=5) - if resp.status_code == 200: - models_data = resp.json() - models = [m['name'] for m in models_data.get('models', [])] - # Add cloud models if available - cloud_models = ['kimi-k2.6:cloud', 'kimi-k2.5:cloud', 'nemotron-3-super:cloud'] - models.extend(cloud_models) - return jsonify({"models": models, "default": "kimi-k2.6:cloud"}) - except Exception as e: - print(f"Error fetching models: {e}") - - return jsonify({ - "models": ["kimi-k2.6:cloud", "kimi-k2.5:cloud", "llava:latest"], - "default": "kimi-k2.6:cloud" - }) - - -@app.route('/api/ollama-status', methods=['GET']) -def ollama_status(): - """Check Ollama connection status""" - try: - resp = requests.get(f"{OLLAMA_HOST}/api/tags", timeout=5) - return jsonify({"online": resp.status_code == 200}) - except: - return jsonify({"online": False}) - - -@app.route('/api/surveys/template', methods=['GET']) -def get_survey_template(): - """Get the survey template with questions""" - return jsonify({"template": DEFAULT_SURVEY_TEMPLATE}) - - -@app.route('/api/surveys', methods=['GET']) -def list_surveys(): - """List all surveys""" - surveys = [] - for sid, survey in surveys_db.items(): - surveys.append({ - "id": sid, - "name": survey['name'], - "client_name": survey.get('client_name', ''), - "site_name": survey.get('site_name', ''), - "description": survey.get('description', ''), - "created_at": survey['created_at'], - "responses_count": len(survey.get('responses', [])) - }) - return jsonify({"surveys": surveys}) - - -@app.route('/api/surveys', methods=['POST']) -def create_survey(): - """Create a new 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': DEFAULT_SURVEY_TEMPLATE['questions'], - 'created_at': datetime.utcnow().isoformat(), - 'status': 'active', - 'responses': [] - } - - # If responses are provided, add them - responses_data = data.get('responses') - if responses_data and isinstance(responses_data, dict) and len(responses_data) > 0: - response_entry = { - 'id': str(uuid.uuid4()), - 'survey_id': survey_id, - 'responses': responses_data, - 'photos': [], - 'analyzed': False, - 'analysis': None, - 'submitter_name': 'Survey Creator', - 'created_at': datetime.utcnow().isoformat() - } - survey['responses'].append(response_entry) - - surveys_db[survey_id] = survey - return jsonify({"survey": survey}), 201 - - -@app.route('/api/surveys/', methods=['GET']) -def get_survey(survey_id): - """Get a specific survey""" - if survey_id not in surveys_db: - return jsonify({"error": "Survey not found"}), 404 - return jsonify({"survey": surveys_db[survey_id]}) - - -@app.route('/api/surveys/', methods=['DELETE']) -def delete_survey(survey_id): - """Delete a survey""" - if survey_id not in surveys_db: - return jsonify({"error": "Survey not found"}), 404 - - del surveys_db[survey_id] - return jsonify({"success": True, "message": "Survey deleted"}) - - -@app.route('/api/surveys', methods=['DELETE']) -def delete_all_surveys(): - """Delete all surveys""" - count = len(surveys_db) - surveys_db.clear() - return jsonify({"success": True, "message": f"Deleted {count} surveys"}) - - -@app.route('/api/surveys//responses', methods=['POST']) -def submit_response(survey_id): - """Submit survey responses""" - if survey_id not in surveys_db: - return jsonify({"error": "Survey not found"}), 404 - - data = request.get_json() - response_id = str(uuid.uuid4()) - - response_entry = { - 'id': response_id, - 'survey_id': survey_id, - 'responses': data.get('responses', {}), - 'photos': data.get('photos', []), - 'analyzed': False, - 'analysis': None, - 'submitter_name': data.get('submitter_name', 'Anonymous'), - 'created_at': datetime.utcnow().isoformat() - } - - surveys_db[survey_id]['responses'].append(response_entry) - return jsonify({"response": response_entry}), 201 - - -@app.route('/api/surveys//photos', methods=['POST']) -def upload_photo(survey_id): - """Upload a photo for a survey""" - if survey_id not in surveys_db: - return jsonify({"error": "Survey not found"}), 404 - - if 'photo' not in request.files: - return jsonify({"error": "No photo provided"}), 400 - - photo = request.files['photo'] - if photo.filename == '': - return jsonify({"error": "No photo selected"}), 400 - - # Save photo - filename = secure_filename(f"{survey_id}_{uuid.uuid4()}_{photo.filename}") - photo_path = os.path.join(PHOTOS_FOLDER, filename) - photo.save(photo_path) - - return jsonify({ - "success": True, - "filename": filename, - "url": f"/api/photos/{survey_id}/{filename}" - }) - - -@app.route('/api/photos//', methods=['GET']) -def get_photo(survey_id, filename): - """Get a photo""" - photo_path = os.path.join(PHOTOS_FOLDER, filename) - if os.path.exists(photo_path): - return send_file(photo_path) - return jsonify({"error": "Photo not found"}), 404 - - -@app.route('/api/surveys//analyze', methods=['POST']) -def analyze_survey(survey_id): - """Analyze survey with AI""" - if survey_id not in surveys_db: - return jsonify({"error": "Survey not found"}), 404 - - survey = surveys_db[survey_id] - data = request.get_json() or {} - selected_model = data.get('model', 'llava:latest') - - # Get the latest response - if not survey['responses']: - return jsonify({"error": "No responses to analyze"}), 400 - - last_response = survey['responses'][-1] - responses = last_response['responses'] - - # Build analysis prompt - prompt = f"""Analyze this IT site survey and provide recommendations: - -Survey: {survey['name']} -Client: {survey['client_name']} -Site: {survey['site_name']} - -Responses: -""" - - for q in survey['questions']: - qid = q['id'] - if qid in responses: - prompt += f"\n{q['question']}: {responses[qid]}" - - prompt += "\n\nProvide a detailed analysis including:\n" - prompt += "1. Network infrastructure recommendations\n" - prompt += "2. Equipment recommendations with estimated costs\n" - prompt += "3. Implementation timeline\n" - prompt += "4. Potential challenges and solutions\n" - - try: - # Call Ollama for analysis - ollama_data = { - "model": selected_model, - "messages": [{"role": "user", "content": prompt}], - "stream": False - } - - resp = requests.post(OLLAMA_URL, json=ollama_data, timeout=300) - if resp.status_code == 200: - result = resp.json() - analysis = result.get('message', {}).get('content', 'No analysis available') - - # Save analysis - last_response['analyzed'] = True - last_response['analysis'] = analysis - last_response['model_used'] = selected_model - - return jsonify({ - "analysis": analysis, - "model_used": selected_model - }) - else: - return jsonify({"error": f"Ollama error: {resp.status_code}"}), 500 - - except Exception as e: - return jsonify({"error": str(e)}), 500 - - -@app.route('/api/surveys//export', methods=['POST']) -def export_survey(survey_id): - """Export survey results""" - if survey_id not in surveys_db: - return jsonify({"error": "Survey not found"}), 404 - - data = request.get_json() or {} - export_format = data.get('format', 'json') - - survey = surveys_db[survey_id] - - if export_format == 'json': - return jsonify({"survey": survey}) - elif export_format == 'text': - # Generate text report - text = f"IT Site Survey Report\n" - text += f"Survey: {survey['name']}\n" - text += f"Client: {survey['client_name']}\n" - text += f"Site: {survey['site_name']}\n" - text += f"Date: {survey['created_at']}\n\n" - - for response in survey['responses']: - text += f"Response by: {response['submitter_name']}\n" - for q in survey['questions']: - qid = q['id'] - if qid in response['responses']: - text += f"{q['question']}: {response['responses'][qid]}\n" - if response.get('analysis'): - text += f"\nAI Analysis:\n{response['analysis']}\n" - - return jsonify({"text": text}) - else: - return jsonify({"error": "Unsupported format"}), 400 - - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=3003, debug=False)