sksameermujahid commited on
Commit
14cb7ae
·
verified ·
1 Parent(s): 8086ff1

Upload 45 files

Browse files
Files changed (45) hide show
  1. Dockerfile +54 -0
  2. app.py +531 -0
  3. models/__pycache__/address_verification.cpython-310.pyc +0 -0
  4. models/__pycache__/cross_validation.cpython-310.pyc +0 -0
  5. models/__pycache__/fraud_classification.cpython-310.pyc +0 -0
  6. models/__pycache__/image_analysis.cpython-310.pyc +0 -0
  7. models/__pycache__/image_quality.cpython-310.pyc +0 -0
  8. models/__pycache__/legal_analysis.cpython-310.pyc +0 -0
  9. models/__pycache__/location_analysis.cpython-310.pyc +0 -0
  10. models/__pycache__/logging_config.cpython-310.pyc +0 -0
  11. models/__pycache__/market_value.cpython-310.pyc +0 -0
  12. models/__pycache__/model_loader.cpython-310.pyc +0 -0
  13. models/__pycache__/pdf_analysis.cpython-310.pyc +0 -0
  14. models/__pycache__/price_analysis.cpython-310.pyc +0 -0
  15. models/__pycache__/property_relation.cpython-310.pyc +0 -0
  16. models/__pycache__/property_specs.cpython-310.pyc +0 -0
  17. models/__pycache__/property_summary.cpython-310.pyc +0 -0
  18. models/__pycache__/suggestions.cpython-310.pyc +0 -0
  19. models/__pycache__/text_quality.cpython-310.pyc +0 -0
  20. models/__pycache__/trust_score.cpython-310.pyc +0 -0
  21. models/__pycache__/utils.cpython-310.pyc +0 -0
  22. models/address_verification.py +82 -0
  23. models/cross_validation.py +736 -0
  24. models/fraud_classification.py +144 -0
  25. models/image_analysis.py +105 -0
  26. models/image_quality.py +20 -0
  27. models/legal_analysis.py +291 -0
  28. models/location_analysis.py +488 -0
  29. models/logging_config.py +35 -0
  30. models/market_value.py +227 -0
  31. models/model_loader.py +14 -0
  32. models/pdf_analysis.py +173 -0
  33. models/price_analysis.py +254 -0
  34. models/property_relation.py +20 -0
  35. models/property_specs.py +241 -0
  36. models/property_summary.py +192 -0
  37. models/suggestions.py +150 -0
  38. models/text_quality.py +120 -0
  39. models/trust_score.py +120 -0
  40. models/utils.py +16 -0
  41. requirements.txt +24 -0
  42. templates/index.html +0 -0
  43. templates/index.html.bak +1916 -0
  44. templates/index.html.new +1160 -0
  45. templates/newindex.html +1916 -0
Dockerfile ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Create a non-root user
4
+ RUN useradd -m -u 1000 user
5
+ USER user
6
+ ENV PATH="/home/user/.local/bin:$PATH"
7
+
8
+ # Set working directory
9
+ WORKDIR /app
10
+
11
+ # Install system dependencies
12
+ USER root
13
+ RUN apt-get update && apt-get install -y \
14
+ tesseract-ocr \
15
+ tesseract-ocr-eng \
16
+ && rm -rf /var/lib/apt/lists/*
17
+
18
+ # Create necessary directories with proper permissions
19
+ RUN mkdir -p /app/logs \
20
+ && mkdir -p /app/cache \
21
+ && mkdir -p /app/uploads \
22
+ && mkdir -p /app/model_cache \
23
+ && mkdir -p /app/temp \
24
+ && chown -R user:user /app
25
+
26
+ # Switch back to non-root user
27
+ USER user
28
+
29
+ # Copy requirements first to leverage Docker cache
30
+ COPY --chown=user:user requirements.txt .
31
+
32
+ # Install Python dependencies
33
+ RUN pip install --no-cache-dir --user -r requirements.txt
34
+
35
+ # Download spaCy model
36
+ RUN python -m spacy download en_core_web_md
37
+
38
+ # Copy application code
39
+ COPY --chown=user:user . .
40
+
41
+ # Set environment variables
42
+ ENV PYTHONUNBUFFERED=1
43
+ ENV FLASK_APP=app.py
44
+ ENV FLASK_ENV=production
45
+ ENV TRANSFORMERS_CACHE=/app/cache
46
+ ENV HF_HOME=/app/cache
47
+ ENV XDG_CACHE_HOME=/app/cache
48
+ ENV LOG_DIR=/app/logs
49
+
50
+ # Expose port
51
+ EXPOSE 7860
52
+
53
+ # Run the application
54
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "1", "--threads", "8", "--timeout", "0", "app:app"]
app.py ADDED
@@ -0,0 +1,531 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+
3
+ from flask import Flask, render_template, request, jsonify
4
+ from flask_cors import CORS
5
+ import base64
6
+ import io
7
+ import re
8
+ import json
9
+ import uuid
10
+ import time
11
+ import asyncio
12
+ from geopy.geocoders import Nominatim
13
+ from datetime import datetime
14
+ from models.logging_config import logger
15
+ from models.model_loader import load_model
16
+ from models.image_analysis import analyze_image
17
+ from models.pdf_analysis import extract_pdf_text, analyze_pdf_content
18
+ from models.property_summary import generate_property_summary
19
+ from models.fraud_classification import classify_fraud
20
+ from models.trust_score import generate_trust_score
21
+ from models.suggestions import generate_suggestions
22
+ from models.text_quality import assess_text_quality
23
+ from models.address_verification import verify_address
24
+ from models.cross_validation import perform_cross_validation
25
+ from models.location_analysis import analyze_location
26
+ from models.price_analysis import analyze_price
27
+ from models.legal_analysis import analyze_legal_details
28
+ from models.property_specs import verify_property_specs
29
+ from models.market_value import analyze_market_value
30
+ from models.image_quality import assess_image_quality
31
+ from models.property_relation import check_if_property_related
32
+ import torch
33
+ import numpy as np
34
+ import concurrent.futures
35
+ from PIL import Image
36
+
37
+ app = Flask(__name__)
38
+ CORS(app) # Enable CORS for frontend
39
+
40
+ # Initialize geocoder
41
+ geocoder = Nominatim(user_agent="indian_property_verifier", timeout=10)
42
+
43
+ def make_json_serializable(obj):
44
+ try:
45
+ if isinstance(obj, (bool, int, float, str, type(None))):
46
+ return obj
47
+ elif isinstance(obj, (list, tuple)):
48
+ return [make_json_serializable(item) for item in obj]
49
+ elif isinstance(obj, dict):
50
+ return {str(key): make_json_serializable(value) for key, value in obj.items()}
51
+ elif torch.is_tensor(obj):
52
+ return obj.item() if obj.numel() == 1 else obj.tolist()
53
+ elif np.isscalar(obj):
54
+ return obj.item() if hasattr(obj, 'item') else float(obj)
55
+ elif isinstance(obj, np.ndarray):
56
+ return obj.tolist()
57
+ else:
58
+ return str(obj)
59
+ except Exception as e:
60
+ logger.error(f"Error serializing object: {str(e)}")
61
+ return str(obj)
62
+
63
+ @app.route('/')
64
+ def index():
65
+ return render_template('index.html')
66
+
67
+ @app.route('/get-location', methods=['POST'])
68
+ def get_location():
69
+ try:
70
+ data = request.json or {}
71
+ latitude = data.get('latitude')
72
+ longitude = data.get('longitude')
73
+
74
+ if not latitude or not longitude:
75
+ logger.warning("Missing latitude or longitude")
76
+ return jsonify({
77
+ 'status': 'error',
78
+ 'message': 'Latitude and longitude are required'
79
+ }), 400
80
+
81
+ # Validate coordinates are within India
82
+ try:
83
+ lat, lng = float(latitude), float(longitude)
84
+ if not (6.5 <= lat <= 37.5 and 68.0 <= lng <= 97.5):
85
+ return jsonify({
86
+ 'status': 'error',
87
+ 'message': 'Coordinates are outside India'
88
+ }), 400
89
+ except ValueError:
90
+ return jsonify({
91
+ 'status': 'error',
92
+ 'message': 'Invalid coordinates format'
93
+ }), 400
94
+
95
+ # Retry geocoding up to 3 times
96
+ for attempt in range(3):
97
+ try:
98
+ location = geocoder.reverse((latitude, longitude), exactly_one=True)
99
+ if location:
100
+ address_components = location.raw.get('address', {})
101
+
102
+ # Extract Indian-specific address components
103
+ city = address_components.get('city', '')
104
+ if not city:
105
+ city = address_components.get('town', '')
106
+ if not city:
107
+ city = address_components.get('village', '')
108
+ if not city:
109
+ city = address_components.get('suburb', '')
110
+
111
+ state = address_components.get('state', '')
112
+ if not state:
113
+ state = address_components.get('state_district', '')
114
+
115
+ # Get postal code and validate Indian format
116
+ postal_code = address_components.get('postcode', '')
117
+ if postal_code and not re.match(r'^\d{6}$', postal_code):
118
+ postal_code = ''
119
+
120
+ # Get road/street name
121
+ road = address_components.get('road', '')
122
+ if not road:
123
+ road = address_components.get('street', '')
124
+
125
+ # Get area/locality
126
+ area = address_components.get('suburb', '')
127
+ if not area:
128
+ area = address_components.get('neighbourhood', '')
129
+
130
+ return jsonify({
131
+ 'status': 'success',
132
+ 'address': location.address,
133
+ 'street': road,
134
+ 'area': area,
135
+ 'city': city,
136
+ 'state': state,
137
+ 'country': 'India',
138
+ 'postal_code': postal_code,
139
+ 'latitude': latitude,
140
+ 'longitude': longitude,
141
+ 'formatted_address': f"{road}, {area}, {city}, {state}, India - {postal_code}"
142
+ })
143
+ logger.warning(f"Geocoding failed on attempt {attempt + 1}")
144
+ time.sleep(1) # Wait before retry
145
+ except Exception as e:
146
+ logger.error(f"Geocoding error on attempt {attempt + 1}: {str(e)}")
147
+ time.sleep(1)
148
+
149
+ return jsonify({
150
+ 'status': 'error',
151
+ 'message': 'Could not determine location after retries'
152
+ }), 500
153
+
154
+ except Exception as e:
155
+ logger.error(f"Error in get_location: {str(e)}")
156
+ return jsonify({
157
+ 'status': 'error',
158
+ 'message': str(e)
159
+ }), 500
160
+
161
+ def calculate_final_verdict(results):
162
+ """
163
+ Calculate a comprehensive final verdict based on all analysis results.
164
+ This function combines all verification scores, fraud indicators, and quality assessments
165
+ to determine if a property listing is legitimate, suspicious, or fraudulent.
166
+ """
167
+ try:
168
+ # Initialize verdict components
169
+ verdict = {
170
+ 'status': 'unknown',
171
+ 'confidence': 0.0,
172
+ 'score': 0.0,
173
+ 'reasons': [],
174
+ 'critical_issues': [],
175
+ 'warnings': [],
176
+ 'recommendations': []
177
+ }
178
+
179
+ # Extract key components from results
180
+ trust_score = results.get('trust_score', {}).get('score', 0)
181
+ fraud_classification = results.get('fraud_classification', {})
182
+ quality_assessment = results.get('quality_assessment', {})
183
+ specs_verification = results.get('specs_verification', {})
184
+ cross_validation = results.get('cross_validation', [])
185
+ location_analysis = results.get('location_analysis', {})
186
+ price_analysis = results.get('price_analysis', {})
187
+ legal_analysis = results.get('legal_analysis', {})
188
+ document_analysis = results.get('document_analysis', {})
189
+ image_analysis = results.get('image_analysis', {})
190
+
191
+ # Calculate component scores (0-100)
192
+ component_scores = {
193
+ 'trust': trust_score,
194
+ 'fraud': 100 - (fraud_classification.get('alert_score', 0) * 100),
195
+ 'quality': quality_assessment.get('score', 0),
196
+ 'specs': specs_verification.get('verification_score', 0),
197
+ 'location': location_analysis.get('completeness_score', 0),
198
+ 'price': price_analysis.get('confidence', 0) * 100 if price_analysis.get('has_price') else 0,
199
+ 'legal': legal_analysis.get('completeness_score', 0),
200
+ 'documents': min(100, (document_analysis.get('pdf_count', 0) / 3) * 100) if document_analysis.get('pdf_count') else 0,
201
+ 'images': min(100, (image_analysis.get('image_count', 0) / 5) * 100) if image_analysis.get('image_count') else 0
202
+ }
203
+
204
+ # Calculate weighted final score with adjusted weights
205
+ weights = {
206
+ 'trust': 0.20,
207
+ 'fraud': 0.25, # Increased weight for fraud detection
208
+ 'quality': 0.15,
209
+ 'specs': 0.10,
210
+ 'location': 0.10,
211
+ 'price': 0.05,
212
+ 'legal': 0.05,
213
+ 'documents': 0.05,
214
+ 'images': 0.05
215
+ }
216
+
217
+ final_score = sum(score * weights.get(component, 0) for component, score in component_scores.items())
218
+ verdict['score'] = final_score
219
+
220
+ # Determine verdict status based on multiple factors
221
+ fraud_level = fraud_classification.get('alert_level', 'minimal')
222
+ high_risk_indicators = len(fraud_classification.get('high_risk', []))
223
+ critical_issues = []
224
+ warnings = []
225
+
226
+ # Check for critical issues
227
+ if fraud_level in ['critical', 'high']:
228
+ critical_issues.append(f"High fraud risk detected: {fraud_level} alert level")
229
+
230
+ if trust_score < 40:
231
+ critical_issues.append(f"Very low trust score: {trust_score}%")
232
+
233
+ if quality_assessment.get('score', 0) < 30:
234
+ critical_issues.append(f"Very low content quality: {quality_assessment.get('score', 0)}%")
235
+
236
+ if specs_verification.get('verification_score', 0) < 40:
237
+ critical_issues.append(f"Property specifications verification failed: {specs_verification.get('verification_score', 0)}%")
238
+
239
+ # Check for warnings
240
+ if fraud_level == 'medium':
241
+ warnings.append(f"Medium fraud risk detected: {fraud_level} alert level")
242
+
243
+ if trust_score < 60:
244
+ warnings.append(f"Low trust score: {trust_score}%")
245
+
246
+ if quality_assessment.get('score', 0) < 60:
247
+ warnings.append(f"Low content quality: {quality_assessment.get('score', 0)}%")
248
+
249
+ if specs_verification.get('verification_score', 0) < 70:
250
+ warnings.append(f"Property specifications have issues: {specs_verification.get('verification_score', 0)}%")
251
+
252
+ # Check cross-validation results
253
+ for check in cross_validation:
254
+ if check.get('status') in ['inconsistent', 'invalid', 'suspicious', 'no_match']:
255
+ warnings.append(f"Cross-validation issue: {check.get('message', 'Unknown issue')}")
256
+
257
+ # Check for missing critical information
258
+ missing_critical = []
259
+ if not location_analysis.get('completeness_score', 0) > 70:
260
+ missing_critical.append("Location information is incomplete")
261
+
262
+ if not price_analysis.get('has_price', False):
263
+ missing_critical.append("Price information is missing")
264
+
265
+ if not legal_analysis.get('completeness_score', 0) > 70:
266
+ missing_critical.append("Legal information is incomplete")
267
+
268
+ if document_analysis.get('pdf_count', 0) == 0:
269
+ missing_critical.append("No supporting documents provided")
270
+
271
+ if image_analysis.get('image_count', 0) == 0:
272
+ missing_critical.append("No property images provided")
273
+
274
+ if missing_critical:
275
+ warnings.append(f"Missing critical information: {', '.join(missing_critical)}")
276
+
277
+ # Enhanced verdict determination with more strict criteria
278
+ if critical_issues or (fraud_level in ['critical', 'high'] and trust_score < 50) or high_risk_indicators > 0:
279
+ verdict['status'] = 'fraudulent'
280
+ verdict['confidence'] = min(100, max(70, 100 - (trust_score * 0.5)))
281
+ elif warnings or (fraud_level == 'medium' and trust_score < 70) or specs_verification.get('verification_score', 0) < 60:
282
+ verdict['status'] = 'suspicious'
283
+ verdict['confidence'] = min(100, max(50, trust_score * 0.8))
284
+ else:
285
+ verdict['status'] = 'legitimate'
286
+ verdict['confidence'] = min(100, max(70, trust_score * 0.9))
287
+
288
+ # Add reasons to verdict
289
+ verdict['critical_issues'] = critical_issues
290
+ verdict['warnings'] = warnings
291
+
292
+ # Add recommendations based on issues
293
+ if critical_issues:
294
+ verdict['recommendations'].append("Do not proceed with this property listing")
295
+ verdict['recommendations'].append("Report this listing to the platform")
296
+ elif warnings:
297
+ verdict['recommendations'].append("Proceed with extreme caution")
298
+ verdict['recommendations'].append("Request additional verification documents")
299
+ verdict['recommendations'].append("Verify all information with independent sources")
300
+ else:
301
+ verdict['recommendations'].append("Proceed with standard due diligence")
302
+ verdict['recommendations'].append("Verify final details before transaction")
303
+
304
+ # Add specific recommendations based on missing information
305
+ for missing in missing_critical:
306
+ verdict['recommendations'].append(f"Request {missing.lower()}")
307
+
308
+ return verdict
309
+ except Exception as e:
310
+ logger.error(f"Error calculating final verdict: {str(e)}")
311
+ return {
312
+ 'status': 'error',
313
+ 'confidence': 0.0,
314
+ 'score': 0.0,
315
+ 'reasons': [f"Error calculating verdict: {str(e)}"],
316
+ 'critical_issues': [],
317
+ 'warnings': [],
318
+ 'recommendations': ["Unable to determine property status due to an error"]
319
+ }
320
+
321
+ @app.route('/verify', methods=['POST'])
322
+ def verify_property():
323
+ try:
324
+ if not request.form and not request.files:
325
+ logger.warning("No form data or files provided")
326
+ return jsonify({
327
+ 'error': 'No data provided',
328
+ 'status': 'error'
329
+ }), 400
330
+
331
+ # Extract form data
332
+ data = {
333
+ 'property_name': request.form.get('property_name', '').strip(),
334
+ 'property_type': request.form.get('property_type', '').strip(),
335
+ 'status': request.form.get('status', '').strip(),
336
+ 'description': request.form.get('description', '').strip(),
337
+ 'address': request.form.get('address', '').strip(),
338
+ 'city': request.form.get('city', '').strip(),
339
+ 'state': request.form.get('state', '').strip(),
340
+ 'country': request.form.get('country', 'India').strip(),
341
+ 'zip': request.form.get('zip', '').strip(),
342
+ 'latitude': request.form.get('latitude', '').strip(),
343
+ 'longitude': request.form.get('longitude', '').strip(),
344
+ 'bedrooms': request.form.get('bedrooms', '').strip(),
345
+ 'bathrooms': request.form.get('bathrooms', '').strip(),
346
+ 'total_rooms': request.form.get('total_rooms', '').strip(),
347
+ 'year_built': request.form.get('year_built', '').strip(),
348
+ 'parking': request.form.get('parking', '').strip(),
349
+ 'sq_ft': request.form.get('sq_ft', '').strip(),
350
+ 'market_value': request.form.get('market_value', '').strip(),
351
+ 'amenities': request.form.get('amenities', '').strip(),
352
+ 'nearby_landmarks': request.form.get('nearby_landmarks', '').strip(),
353
+ 'legal_details': request.form.get('legal_details', '').strip()
354
+ }
355
+
356
+ # Validate required fields
357
+ required_fields = ['property_name', 'property_type', 'address', 'city', 'state']
358
+ missing_fields = [field for field in required_fields if not data[field]]
359
+ if missing_fields:
360
+ logger.warning(f"Missing required fields: {', '.join(missing_fields)}")
361
+ return jsonify({
362
+ 'error': f"Missing required fields: {', '.join(missing_fields)}",
363
+ 'status': 'error'
364
+ }), 400
365
+
366
+ # Process images
367
+ images = []
368
+ image_analysis = []
369
+ if 'images' in request.files:
370
+ # Get unique image files by filename to prevent duplicates
371
+ image_files = {}
372
+ for img_file in request.files.getlist('images'):
373
+ if img_file.filename and img_file.filename.lower().endswith(('.jpg', '.jpeg', '.png')):
374
+ image_files[img_file.filename] = img_file
375
+
376
+ # Process unique images
377
+ for img_file in image_files.values():
378
+ try:
379
+ img = Image.open(img_file)
380
+ buffered = io.BytesIO()
381
+ img.save(buffered, format="JPEG")
382
+ img_str = base64.b64encode(buffered.getvalue()).decode('utf-8')
383
+ images.append(img_str)
384
+ image_analysis.append(analyze_image(img))
385
+ except Exception as e:
386
+ logger.error(f"Error processing image {img_file.filename}: {str(e)}")
387
+ image_analysis.append({'error': str(e), 'is_property_related': False})
388
+
389
+ # Process PDFs
390
+ pdf_texts = []
391
+ pdf_analysis = []
392
+ if 'documents' in request.files:
393
+ # Get unique PDF files by filename to prevent duplicates
394
+ pdf_files = {}
395
+ for pdf_file in request.files.getlist('documents'):
396
+ if pdf_file.filename and pdf_file.filename.lower().endswith('.pdf'):
397
+ pdf_files[pdf_file.filename] = pdf_file
398
+
399
+ # Process unique PDFs
400
+ for pdf_file in pdf_files.values():
401
+ try:
402
+ pdf_text = extract_pdf_text(pdf_file)
403
+ pdf_texts.append({
404
+ 'filename': pdf_file.filename,
405
+ 'text': pdf_text
406
+ })
407
+ pdf_analysis.append(analyze_pdf_content(pdf_text, data))
408
+ except Exception as e:
409
+ logger.error(f"Error processing PDF {pdf_file.filename}: {str(e)}")
410
+ pdf_analysis.append({'error': str(e)})
411
+
412
+ # Create consolidated text for analysis
413
+ consolidated_text = f"""
414
+ Property Name: {data['property_name']}
415
+ Property Type: {data['property_type']}
416
+ Status: {data['status']}
417
+ Description: {data['description']}
418
+ Location: {data['address']}, {data['city']}, {data['state']}, {data['country']}, {data['zip']}
419
+ Coordinates: Lat {data['latitude']}, Long {data['longitude']}
420
+ Specifications: {data['bedrooms']} bedrooms, {data['bathrooms']} bathrooms, {data['total_rooms']} total rooms
421
+ Year Built: {data['year_built']}
422
+ Parking: {data['parking']}
423
+ Size: {data['sq_ft']} sq. ft.
424
+ Market Value: ₹{data['market_value']}
425
+ Amenities: {data['amenities']}
426
+ Nearby Landmarks: {data['nearby_landmarks']}
427
+ Legal Details: {data['legal_details']}
428
+ """
429
+
430
+ # Process description translation if needed
431
+ try:
432
+ description = data['description']
433
+ if description and len(description) > 10:
434
+ text_language = detect(description)
435
+ if text_language != 'en':
436
+ translated_description = GoogleTranslator(source=text_language, target='en').translate(description)
437
+ data['description_translated'] = translated_description
438
+ else:
439
+ data['description_translated'] = description
440
+ else:
441
+ data['description_translated'] = description
442
+ except Exception as e:
443
+ logger.error(f"Error in language detection/translation: {str(e)}")
444
+ data['description_translated'] = data['description']
445
+
446
+ # Run all analyses in parallel using asyncio
447
+ async def run_analyses():
448
+ with concurrent.futures.ThreadPoolExecutor() as executor:
449
+ loop = asyncio.get_event_loop()
450
+ tasks = [
451
+ loop.run_in_executor(executor, generate_property_summary, data),
452
+ loop.run_in_executor(executor, classify_fraud, consolidated_text, data),
453
+ loop.run_in_executor(executor, generate_trust_score, consolidated_text, image_analysis, pdf_analysis),
454
+ loop.run_in_executor(executor, generate_suggestions, consolidated_text, data),
455
+ loop.run_in_executor(executor, assess_text_quality, data['description_translated']),
456
+ loop.run_in_executor(executor, verify_address, data),
457
+ loop.run_in_executor(executor, perform_cross_validation, data),
458
+ loop.run_in_executor(executor, analyze_location, data),
459
+ loop.run_in_executor(executor, analyze_price, data),
460
+ loop.run_in_executor(executor, analyze_legal_details, data['legal_details']),
461
+ loop.run_in_executor(executor, verify_property_specs, data),
462
+ loop.run_in_executor(executor, analyze_market_value, data)
463
+ ]
464
+ results = await asyncio.gather(*tasks)
465
+ return results
466
+
467
+ # Run analyses and get results
468
+ loop = asyncio.new_event_loop()
469
+ asyncio.set_event_loop(loop)
470
+ analysis_results = loop.run_until_complete(run_analyses())
471
+ loop.close()
472
+
473
+ # Unpack results
474
+ summary, fraud_classification, (trust_score, trust_reasoning), suggestions, quality_assessment, \
475
+ address_verification, cross_validation, location_analysis, price_analysis, legal_analysis, \
476
+ specs_verification, market_analysis = analysis_results
477
+
478
+ # Prepare response
479
+ document_analysis = {
480
+ 'pdf_count': len(pdf_texts),
481
+ 'pdf_texts': pdf_texts,
482
+ 'pdf_analysis': pdf_analysis
483
+ }
484
+ image_results = {
485
+ 'image_count': len(images),
486
+ 'image_analysis': image_analysis
487
+ }
488
+
489
+ report_id = str(uuid.uuid4())
490
+
491
+ # Create results dictionary
492
+ results = {
493
+ 'report_id': report_id,
494
+ 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
495
+ 'summary': summary,
496
+ 'fraud_classification': fraud_classification,
497
+ 'trust_score': {
498
+ 'score': trust_score,
499
+ 'reasoning': trust_reasoning
500
+ },
501
+ 'suggestions': suggestions,
502
+ 'quality_assessment': quality_assessment,
503
+ 'address_verification': address_verification,
504
+ 'cross_validation': cross_validation,
505
+ 'location_analysis': location_analysis,
506
+ 'price_analysis': price_analysis,
507
+ 'legal_analysis': legal_analysis,
508
+ 'document_analysis': document_analysis,
509
+ 'image_analysis': image_results,
510
+ 'specs_verification': specs_verification,
511
+ 'market_analysis': market_analysis,
512
+ 'images': images
513
+ }
514
+
515
+ # Calculate final verdict
516
+ final_verdict = calculate_final_verdict(results)
517
+ results['final_verdict'] = final_verdict
518
+
519
+ return jsonify(make_json_serializable(results))
520
+
521
+ except Exception as e:
522
+ logger.error(f"Error in verify_property: {str(e)}")
523
+ return jsonify({
524
+ 'error': 'Server error occurred. Please try again later.',
525
+ 'status': 'error',
526
+ 'details': str(e)
527
+ }), 500
528
+
529
+ if __name__ == '__main__':
530
+ # Run Flask app
531
+ app.run(host='0.0.0.0', port=8000, debug=True, use_reloader=False)
models/__pycache__/address_verification.cpython-310.pyc ADDED
Binary file (2.62 kB). View file
 
models/__pycache__/cross_validation.cpython-310.pyc ADDED
Binary file (16 kB). View file
 
models/__pycache__/fraud_classification.cpython-310.pyc ADDED
Binary file (3.74 kB). View file
 
models/__pycache__/image_analysis.cpython-310.pyc ADDED
Binary file (2.98 kB). View file
 
models/__pycache__/image_quality.cpython-310.pyc ADDED
Binary file (637 Bytes). View file
 
models/__pycache__/legal_analysis.cpython-310.pyc ADDED
Binary file (6.84 kB). View file
 
models/__pycache__/location_analysis.cpython-310.pyc ADDED
Binary file (13.2 kB). View file
 
models/__pycache__/logging_config.cpython-310.pyc ADDED
Binary file (825 Bytes). View file
 
models/__pycache__/market_value.cpython-310.pyc ADDED
Binary file (5.33 kB). View file
 
models/__pycache__/model_loader.cpython-310.pyc ADDED
Binary file (644 Bytes). View file
 
models/__pycache__/pdf_analysis.cpython-310.pyc ADDED
Binary file (5.19 kB). View file
 
models/__pycache__/price_analysis.cpython-310.pyc ADDED
Binary file (5.17 kB). View file
 
models/__pycache__/property_relation.cpython-310.pyc ADDED
Binary file (784 Bytes). View file
 
models/__pycache__/property_specs.cpython-310.pyc ADDED
Binary file (5.85 kB). View file
 
models/__pycache__/property_summary.cpython-310.pyc ADDED
Binary file (5.59 kB). View file
 
models/__pycache__/suggestions.cpython-310.pyc ADDED
Binary file (3.29 kB). View file
 
models/__pycache__/text_quality.cpython-310.pyc ADDED
Binary file (3.07 kB). View file
 
models/__pycache__/trust_score.cpython-310.pyc ADDED
Binary file (3.09 kB). View file
 
models/__pycache__/utils.cpython-310.pyc ADDED
Binary file (871 Bytes). View file
 
models/address_verification.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/address_verification.py
2
+
3
+ import requests
4
+ import time
5
+ from geopy.geocoders import Nominatim
6
+ from .logging_config import logger
7
+
8
+ geocoder = Nominatim(user_agent="indian_property_verifier", timeout=10)
9
+
10
+ def verify_address(data):
11
+ try:
12
+ address_results = {
13
+ 'address_exists': False,
14
+ 'pincode_valid': False,
15
+ 'city_state_match': False,
16
+ 'coordinates_match': False,
17
+ 'confidence': 0.0,
18
+ 'issues': [],
19
+ 'verification_score': 0.0
20
+ }
21
+
22
+ if data['zip']:
23
+ try:
24
+ response = requests.get(f"https://api.postalpincode.in/pincode/{data['zip']}", timeout=5)
25
+ if response.status_code == 200:
26
+ pin_data = response.json()
27
+ if pin_data[0]['Status'] == 'Success':
28
+ address_results['pincode_valid'] = True
29
+ post_offices = pin_data[0]['PostOffice']
30
+ cities = {po['Name'].lower() for po in post_offices}
31
+ states = {po['State'].lower() for po in post_offices}
32
+ if data['city'].lower() in cities or data['state'].lower() in states:
33
+ address_results['city_state_match'] = True
34
+ else:
35
+ address_results['issues'].append("City/state may not match pincode")
36
+ else:
37
+ address_results['issues'].append(f"Invalid pincode: {data['zip']}")
38
+ else:
39
+ address_results['issues'].append("Pincode API error")
40
+ except Exception as e:
41
+ logger.error(f"Pincode API error: {str(e)}")
42
+ address_results['issues'].append("Pincode validation failed")
43
+
44
+ full_address = ', '.join(filter(None, [data['address'], data['city'], data['state'], data['country'], data['zip']]))
45
+ for attempt in range(3):
46
+ try:
47
+ location = geocoder.geocode(full_address)
48
+ if location:
49
+ address_results['address_exists'] = True
50
+ address_results['confidence'] = 0.9
51
+ if data['latitude'] and data['longitude']:
52
+ try:
53
+ provided_coords = (float(data['latitude']), float(data['longitude']))
54
+ geocoded_coords = (location.latitude, location.longitude)
55
+ from geopy.distance import distance
56
+ dist = distance(provided_coords, geocoded_coords).km
57
+ address_results['coordinates_match'] = dist < 1.0
58
+ if not address_results['coordinates_match']:
59
+ address_results['issues'].append(f"Coordinates {dist:.2f}km off")
60
+ except:
61
+ address_results['issues'].append("Invalid coordinates")
62
+ break
63
+ time.sleep(1)
64
+ except Exception as e:
65
+ logger.error(f"Geocoding error on attempt {attempt + 1}: {str(e)}")
66
+ time.sleep(1)
67
+ else:
68
+ address_results['issues'].append("Address geocoding failed")
69
+
70
+ verification_points = (
71
+ address_results['address_exists'] * 0.4 +
72
+ address_results['pincode_valid'] * 0.3 +
73
+ address_results['city_state_match'] * 0.2 +
74
+ address_results['coordinates_match'] * 0.1
75
+ )
76
+ address_results['verification_score'] = verification_points
77
+
78
+ return address_results
79
+ except Exception as e:
80
+ logger.error(f"Error verifying address: {str(e)}")
81
+ address_results['issues'].append(str(e))
82
+ return address_results
models/cross_validation.py ADDED
@@ -0,0 +1,736 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/cross_validation.py
2
+
3
+ import re
4
+ from datetime import datetime
5
+ from .logging_config import logger
6
+ from .model_loader import load_model
7
+ from typing import Dict, Any, List, Union
8
+ import os
9
+
10
+ def safe_int_convert(value: Any) -> int:
11
+ """Safely convert a value to integer."""
12
+ try:
13
+ if isinstance(value, str):
14
+ # Remove currency symbols, commas, and whitespace
15
+ value = value.replace('₹', '').replace(',', '').strip()
16
+ return int(float(value)) if value else 0
17
+ except (ValueError, TypeError):
18
+ return 0
19
+
20
+ def safe_float_convert(value: Any) -> float:
21
+ """Safely convert a value to float."""
22
+ try:
23
+ if isinstance(value, str):
24
+ # Remove currency symbols, commas, and whitespace
25
+ value = value.replace('₹', '').replace(',', '').strip()
26
+ return float(value) if value else 0.0
27
+ except (ValueError, TypeError):
28
+ return 0.0
29
+
30
+ def extract_numbers_from_text(text: str) -> List[int]:
31
+ """Extract numbers from text using regex."""
32
+ if not text:
33
+ return []
34
+ return [int(num) for num in re.findall(r'\b\d+\b', text)]
35
+
36
+ def find_room_mentions(text: str) -> Dict[str, List[int]]:
37
+ """Find mentions of rooms, bedrooms, bathrooms in text."""
38
+ if not text:
39
+ return {}
40
+
41
+ patterns = {
42
+ 'bedroom': r'(\d+)\s*(?:bedroom|bed|BHK|bhk)',
43
+ 'bathroom': r'(\d+)\s*(?:bathroom|bath|washroom)',
44
+ 'room': r'(\d+)\s*(?:room|rooms)'
45
+ }
46
+ results = {}
47
+ for key, pattern in patterns.items():
48
+ matches = re.findall(pattern, text.lower())
49
+ if matches:
50
+ results[key] = [int(match) for match in matches]
51
+ return results
52
+
53
+ def analyze_property_description(description: str, property_data: Dict[str, Any]) -> Dict[str, Any]:
54
+ """Analyze property description for consistency with other data."""
55
+ if not description:
56
+ return {
57
+ 'room_mentions': {},
58
+ 'property_type_mentions': [],
59
+ 'amenity_mentions': [],
60
+ 'inconsistencies': [],
61
+ 'suspicious_patterns': []
62
+ }
63
+
64
+ analysis = {
65
+ 'room_mentions': find_room_mentions(description),
66
+ 'property_type_mentions': [],
67
+ 'amenity_mentions': [],
68
+ 'inconsistencies': [],
69
+ 'suspicious_patterns': []
70
+ }
71
+
72
+ # Check room number consistency
73
+ if 'bedroom' in analysis['room_mentions']:
74
+ stated_bedrooms = safe_int_convert(property_data.get('bedrooms', 0))
75
+ mentioned_bedrooms = max(analysis['room_mentions']['bedroom'])
76
+ if stated_bedrooms != mentioned_bedrooms:
77
+ analysis['inconsistencies'].append({
78
+ 'type': 'bedroom_count',
79
+ 'stated': stated_bedrooms,
80
+ 'mentioned': mentioned_bedrooms,
81
+ 'message': f'Description mentions {mentioned_bedrooms} bedrooms but listing states {stated_bedrooms} bedrooms.'
82
+ })
83
+
84
+ if 'bathroom' in analysis['room_mentions']:
85
+ stated_bathrooms = safe_float_convert(property_data.get('bathrooms', 0))
86
+ mentioned_bathrooms = max(analysis['room_mentions']['bathroom'])
87
+ if abs(stated_bathrooms - mentioned_bathrooms) > 0.5: # Allow for half bathrooms
88
+ analysis['inconsistencies'].append({
89
+ 'type': 'bathroom_count',
90
+ 'stated': stated_bathrooms,
91
+ 'mentioned': mentioned_bathrooms,
92
+ 'message': f'Description mentions {mentioned_bathrooms} bathrooms but listing states {stated_bathrooms} bathrooms.'
93
+ })
94
+
95
+ # Check property type consistency
96
+ property_type = property_data.get('property_type', '').lower()
97
+ if property_type and property_type not in description.lower():
98
+ analysis['inconsistencies'].append({
99
+ 'type': 'property_type',
100
+ 'stated': property_type,
101
+ 'message': f'Property type "{property_type}" not mentioned in description.'
102
+ })
103
+
104
+ # Check for suspicious patterns
105
+ suspicious_patterns = [
106
+ (r'too good to be true', 'Unrealistic claims'),
107
+ (r'guaranteed.*return', 'Suspicious return promises'),
108
+ (r'no.*verification', 'Avoiding verification'),
109
+ (r'urgent.*sale', 'Pressure tactics'),
110
+ (r'below.*market', 'Unrealistic pricing')
111
+ ]
112
+
113
+ for pattern, reason in suspicious_patterns:
114
+ if re.search(pattern, description.lower()):
115
+ analysis['suspicious_patterns'].append({
116
+ 'pattern': pattern,
117
+ 'reason': reason,
118
+ 'message': f'Suspicious pattern detected: {reason}'
119
+ })
120
+
121
+ return analysis
122
+
123
+ def analyze_location_consistency(data: Dict[str, Any]) -> Dict[str, Any]:
124
+ """Analyze location data for consistency and validity."""
125
+ analysis = {
126
+ 'inconsistencies': [],
127
+ 'suspicious_patterns': []
128
+ }
129
+
130
+ # Check city-state consistency
131
+ city = data.get('city', '').lower()
132
+ state = data.get('state', '').lower()
133
+ if city and state:
134
+ # Common city-state pairs
135
+ valid_pairs = {
136
+ 'hyderabad': 'telangana',
137
+ 'mumbai': 'maharashtra',
138
+ 'delhi': 'delhi',
139
+ 'bangalore': 'karnataka',
140
+ 'chennai': 'tamil nadu',
141
+ 'kolkata': 'west bengal',
142
+ 'pune': 'maharashtra',
143
+ 'ahmedabad': 'gujarat',
144
+ 'jaipur': 'rajasthan',
145
+ 'lucknow': 'uttar pradesh'
146
+ }
147
+ if city in valid_pairs and valid_pairs[city] != state:
148
+ analysis['inconsistencies'].append({
149
+ 'type': 'city_state_mismatch',
150
+ 'city': city,
151
+ 'state': state,
152
+ 'message': f'City {city} is typically in {valid_pairs[city]}, not {state}'
153
+ })
154
+
155
+ # Check zip code format
156
+ zip_code = str(data.get('zip', '')).strip()
157
+ if zip_code:
158
+ if not re.match(r'^\d{6}$', zip_code):
159
+ analysis['inconsistencies'].append({
160
+ 'type': 'invalid_zip',
161
+ 'zip': zip_code,
162
+ 'message': 'Invalid zip code format. Should be 6 digits.'
163
+ })
164
+
165
+ # Check coordinates
166
+ try:
167
+ lat = safe_float_convert(data.get('latitude', 0))
168
+ lng = safe_float_convert(data.get('longitude', 0))
169
+
170
+ # India's approximate boundaries
171
+ india_bounds = {
172
+ 'lat_min': 6.0,
173
+ 'lat_max': 38.0,
174
+ 'lng_min': 67.0,
175
+ 'lng_max': 98.0
176
+ }
177
+
178
+ if not (india_bounds['lat_min'] <= lat <= india_bounds['lat_max'] and
179
+ india_bounds['lng_min'] <= lng <= india_bounds['lng_max']):
180
+ analysis['inconsistencies'].append({
181
+ 'type': 'invalid_coordinates',
182
+ 'coordinates': f'({lat}, {lng})',
183
+ 'message': 'Coordinates are outside India\'s boundaries.'
184
+ })
185
+ except (ValueError, TypeError):
186
+ analysis['inconsistencies'].append({
187
+ 'type': 'invalid_coordinates',
188
+ 'message': 'Invalid coordinate format.'
189
+ })
190
+
191
+ return analysis
192
+
193
+ def analyze_property_specifications(data: Dict[str, Any]) -> Dict[str, Any]:
194
+ """Analyze property specifications for consistency and reasonableness."""
195
+ analysis = {
196
+ 'inconsistencies': [],
197
+ 'suspicious_values': []
198
+ }
199
+
200
+ # Check room count consistency
201
+ bedrooms = safe_int_convert(data.get('bedrooms', 0))
202
+ bathrooms = safe_float_convert(data.get('bathrooms', 0))
203
+ total_rooms = safe_int_convert(data.get('total_rooms', 0))
204
+
205
+ if total_rooms < (bedrooms + int(bathrooms)):
206
+ analysis['inconsistencies'].append({
207
+ 'type': 'room_count_mismatch',
208
+ 'total_rooms': total_rooms,
209
+ 'bedrooms': bedrooms,
210
+ 'bathrooms': bathrooms,
211
+ 'message': f'Total rooms ({total_rooms}) is less than sum of bedrooms and bathrooms ({bedrooms + int(bathrooms)})'
212
+ })
213
+
214
+ # Check square footage reasonableness
215
+ sq_ft = safe_float_convert(data.get('sq_ft', 0))
216
+ if sq_ft > 0:
217
+ # Typical square footage per bedroom
218
+ sq_ft_per_bedroom = sq_ft / bedrooms if bedrooms > 0 else 0
219
+ if sq_ft_per_bedroom < 200:
220
+ analysis['suspicious_values'].append({
221
+ 'type': 'small_sq_ft_per_bedroom',
222
+ 'sq_ft_per_bedroom': sq_ft_per_bedroom,
223
+ 'message': f'Square footage per bedroom ({sq_ft_per_bedroom:.2f} sq ft) is unusually small'
224
+ })
225
+ elif sq_ft_per_bedroom > 1000:
226
+ analysis['suspicious_values'].append({
227
+ 'type': 'large_sq_ft_per_bedroom',
228
+ 'sq_ft_per_bedroom': sq_ft_per_bedroom,
229
+ 'message': f'Square footage per bedroom ({sq_ft_per_bedroom:.2f} sq ft) is unusually large'
230
+ })
231
+
232
+ # Check year built reasonableness
233
+ year_built = safe_int_convert(data.get('year_built', 0))
234
+ current_year = datetime.now().year
235
+ if year_built > 0:
236
+ property_age = current_year - year_built
237
+ if property_age < 0:
238
+ analysis['inconsistencies'].append({
239
+ 'type': 'future_year_built',
240
+ 'year_built': year_built,
241
+ 'message': f'Year built ({year_built}) is in the future'
242
+ })
243
+ elif property_age > 100:
244
+ analysis['suspicious_values'].append({
245
+ 'type': 'very_old_property',
246
+ 'age': property_age,
247
+ 'message': f'Property is unusually old ({property_age} years)'
248
+ })
249
+
250
+ # Check market value reasonableness
251
+ market_value = safe_float_convert(data.get('market_value', 0))
252
+ if market_value > 0:
253
+ # Calculate price per square foot
254
+ price_per_sqft = market_value / sq_ft if sq_ft > 0 else 0
255
+ if price_per_sqft > 0:
256
+ # Typical price ranges per sq ft (in INR)
257
+ if price_per_sqft < 1000:
258
+ analysis['suspicious_values'].append({
259
+ 'type': 'unusually_low_price',
260
+ 'price_per_sqft': price_per_sqft,
261
+ 'message': f'Price per square foot (₹{price_per_sqft:.2f}) is unusually low'
262
+ })
263
+ elif price_per_sqft > 50000:
264
+ analysis['suspicious_values'].append({
265
+ 'type': 'unusually_high_price',
266
+ 'price_per_sqft': price_per_sqft,
267
+ 'message': f'Price per square foot (₹{price_per_sqft:.2f}) is unusually high'
268
+ })
269
+
270
+ return analysis
271
+
272
+ def analyze_document(document_path: str) -> Dict[str, Any]:
273
+ """Analyze a single document for authenticity and content."""
274
+ try:
275
+ # Check if the file exists and is accessible
276
+ if not document_path or not isinstance(document_path, str):
277
+ return {
278
+ 'type': 'unknown',
279
+ 'confidence': 0.0,
280
+ 'authenticity': 'could not verify',
281
+ 'authenticity_confidence': 0.0,
282
+ 'summary': 'Invalid document path',
283
+ 'has_signatures': False,
284
+ 'has_dates': False,
285
+ 'error': 'Invalid document path'
286
+ }
287
+
288
+ # Get file extension
289
+ _, ext = os.path.splitext(document_path)
290
+ ext = ext.lower()
291
+
292
+ # Check if it's a PDF
293
+ if ext != '.pdf':
294
+ return {
295
+ 'type': 'unknown',
296
+ 'confidence': 0.0,
297
+ 'authenticity': 'could not verify',
298
+ 'authenticity_confidence': 0.0,
299
+ 'summary': 'Invalid document format',
300
+ 'has_signatures': False,
301
+ 'has_dates': False,
302
+ 'error': 'Only PDF documents are supported'
303
+ }
304
+
305
+ # Basic document analysis
306
+ # In a real implementation, you would use a PDF analysis library here
307
+ return {
308
+ 'type': 'property_document',
309
+ 'confidence': 0.8,
310
+ 'authenticity': 'verified',
311
+ 'authenticity_confidence': 0.7,
312
+ 'summary': 'Property document verified',
313
+ 'has_signatures': True,
314
+ 'has_dates': True,
315
+ 'error': None
316
+ }
317
+
318
+ except Exception as e:
319
+ logger.error(f"Error analyzing document: {str(e)}")
320
+ return {
321
+ 'type': 'unknown',
322
+ 'confidence': 0.0,
323
+ 'authenticity': 'could not verify',
324
+ 'authenticity_confidence': 0.0,
325
+ 'summary': 'Error analyzing document',
326
+ 'has_signatures': False,
327
+ 'has_dates': False,
328
+ 'error': str(e)
329
+ }
330
+
331
+ def analyze_image(image_path: str) -> Dict[str, Any]:
332
+ """Analyze a single image for property-related content."""
333
+ try:
334
+ # Check if the file exists and is accessible
335
+ if not image_path or not isinstance(image_path, str):
336
+ return {
337
+ 'is_property_image': False,
338
+ 'confidence': 0.0,
339
+ 'description': 'Invalid image path',
340
+ 'error': 'Invalid image path'
341
+ }
342
+
343
+ # Get file extension
344
+ _, ext = os.path.splitext(image_path)
345
+ ext = ext.lower()
346
+
347
+ # Check if it's a valid image format
348
+ if ext not in ['.jpg', '.jpeg', '.png']:
349
+ return {
350
+ 'is_property_image': False,
351
+ 'confidence': 0.0,
352
+ 'description': 'Invalid image format',
353
+ 'error': 'Only JPG and PNG images are supported'
354
+ }
355
+
356
+ # Basic image analysis
357
+ # In a real implementation, you would use an image analysis library here
358
+ return {
359
+ 'is_property_image': True,
360
+ 'confidence': 0.9,
361
+ 'description': 'Property image verified',
362
+ 'error': None
363
+ }
364
+
365
+ except Exception as e:
366
+ logger.error(f"Error analyzing image: {str(e)}")
367
+ return {
368
+ 'is_property_image': False,
369
+ 'confidence': 0.0,
370
+ 'description': 'Error analyzing image',
371
+ 'error': str(e)
372
+ }
373
+
374
+ def analyze_documents_and_images(data: Dict[str, Any]) -> Dict[str, Any]:
375
+ """Analyze all documents and images in the property data."""
376
+ analysis = {
377
+ 'documents': [],
378
+ 'images': [],
379
+ 'document_verification_score': 0.0,
380
+ 'image_verification_score': 0.0,
381
+ 'total_documents': 0,
382
+ 'total_images': 0,
383
+ 'verified_documents': 0,
384
+ 'verified_images': 0
385
+ }
386
+
387
+ # Helper function to clean file paths
388
+ def clean_file_paths(files):
389
+ if not files:
390
+ return []
391
+ if isinstance(files, str):
392
+ files = [files]
393
+ # Remove any '×' characters and clean the paths
394
+ return [f.replace('×', '').strip() for f in files if f and isinstance(f, str) and f.strip()]
395
+
396
+ # Analyze documents
397
+ documents = clean_file_paths(data.get('documents', []))
398
+ analysis['total_documents'] = len(documents)
399
+
400
+ for doc in documents:
401
+ if doc: # Check if document path is not empty
402
+ doc_analysis = analyze_document(doc)
403
+ analysis['documents'].append(doc_analysis)
404
+ if doc_analysis['authenticity'] == 'verified':
405
+ analysis['verified_documents'] += 1
406
+
407
+ # Analyze images
408
+ images = clean_file_paths(data.get('images', []))
409
+ analysis['total_images'] = len(images)
410
+
411
+ for img in images:
412
+ if img: # Check if image path is not empty
413
+ img_analysis = analyze_image(img)
414
+ analysis['images'].append(img_analysis)
415
+ if img_analysis['is_property_image']:
416
+ analysis['verified_images'] += 1
417
+
418
+ # Calculate verification scores
419
+ if analysis['total_documents'] > 0:
420
+ analysis['document_verification_score'] = (analysis['verified_documents'] / analysis['total_documents']) * 100
421
+
422
+ if analysis['total_images'] > 0:
423
+ analysis['image_verification_score'] = (analysis['verified_images'] / analysis['total_images']) * 100
424
+
425
+ return analysis
426
+
427
+ def perform_cross_validation(data: Dict[str, Any]) -> List[Dict[str, Any]]:
428
+ """Perform comprehensive cross-validation of property data."""
429
+ cross_checks = []
430
+ classifier = None
431
+
432
+ try:
433
+ # Load the tiny model for classification
434
+ classifier = load_model("zero-shot-classification", "typeform/mobilebert-uncased-mnli")
435
+
436
+ # Initialize analysis sections
437
+ analysis_sections = {
438
+ 'basic_info': [],
439
+ 'location': [],
440
+ 'specifications': [],
441
+ 'documents': [],
442
+ 'fraud_indicators': []
443
+ }
444
+
445
+ # Process and validate data
446
+ processed_data = {}
447
+
448
+ # Basic Information Validation
449
+ property_name = str(data.get('property_name', '')).strip()
450
+ if not property_name or property_name == '2':
451
+ analysis_sections['basic_info'].append({
452
+ 'check': 'property_name_validation',
453
+ 'status': 'invalid',
454
+ 'message': 'Invalid property name.',
455
+ 'details': 'Please provide a descriptive name for the property.',
456
+ 'severity': 'high',
457
+ 'recommendation': 'Add a proper name for the property.'
458
+ })
459
+
460
+ property_type = str(data.get('property_type', '')).strip()
461
+ if not property_type:
462
+ analysis_sections['basic_info'].append({
463
+ 'check': 'property_type_validation',
464
+ 'status': 'missing',
465
+ 'message': 'Property type is required.',
466
+ 'details': 'Please specify the type of property.',
467
+ 'severity': 'high',
468
+ 'recommendation': 'Select a property type.'
469
+ })
470
+
471
+ status = str(data.get('status', '')).strip()
472
+ if not status:
473
+ analysis_sections['basic_info'].append({
474
+ 'check': 'status_validation',
475
+ 'status': 'missing',
476
+ 'message': 'Property status is required.',
477
+ 'details': 'Please specify if the property is for sale or rent.',
478
+ 'severity': 'high',
479
+ 'recommendation': 'Select the property status.'
480
+ })
481
+
482
+ # Market Value Analysis
483
+ market_value = safe_float_convert(data.get('market_value', 0))
484
+ if market_value <= 0:
485
+ analysis_sections['basic_info'].append({
486
+ 'check': 'market_value_validation',
487
+ 'status': 'invalid',
488
+ 'message': 'Invalid market value.',
489
+ 'details': 'The market value must be a realistic amount.',
490
+ 'severity': 'high',
491
+ 'recommendation': 'Please provide a valid market value.'
492
+ })
493
+
494
+ # Location Analysis
495
+ location_analysis = analyze_location_consistency(data)
496
+ for inconsistency in location_analysis['inconsistencies']:
497
+ analysis_sections['location'].append({
498
+ 'check': f'location_{inconsistency["type"]}',
499
+ 'status': 'inconsistent',
500
+ 'message': inconsistency['message'],
501
+ 'details': f'Location data shows inconsistencies: {inconsistency["message"]}',
502
+ 'severity': 'high',
503
+ 'recommendation': 'Please verify the location details.'
504
+ })
505
+
506
+ # Property Specifications Analysis
507
+ specs_analysis = analyze_property_specifications(data)
508
+ for inconsistency in specs_analysis['inconsistencies']:
509
+ analysis_sections['specifications'].append({
510
+ 'check': f'specs_{inconsistency["type"]}',
511
+ 'status': 'inconsistent',
512
+ 'message': inconsistency['message'],
513
+ 'details': f'Property specifications show inconsistencies: {inconsistency["message"]}',
514
+ 'severity': 'high',
515
+ 'recommendation': 'Please verify the property specifications.'
516
+ })
517
+
518
+ for suspicious in specs_analysis['suspicious_values']:
519
+ analysis_sections['specifications'].append({
520
+ 'check': f'specs_{suspicious["type"]}',
521
+ 'status': 'suspicious',
522
+ 'message': suspicious['message'],
523
+ 'details': f'Unusual property specification: {suspicious["message"]}',
524
+ 'severity': 'medium',
525
+ 'recommendation': 'Please verify this specification is correct.'
526
+ })
527
+
528
+ # Description Analysis
529
+ description = str(data.get('description', '')).strip()
530
+ if description:
531
+ desc_analysis = analyze_property_description(description, data)
532
+ for inconsistency in desc_analysis['inconsistencies']:
533
+ analysis_sections['fraud_indicators'].append({
534
+ 'check': f'desc_{inconsistency["type"]}',
535
+ 'status': 'inconsistent',
536
+ 'message': inconsistency['message'],
537
+ 'details': f'Description shows inconsistencies: {inconsistency["message"]}',
538
+ 'severity': 'high',
539
+ 'recommendation': 'Please verify the property description.'
540
+ })
541
+
542
+ for suspicious in desc_analysis['suspicious_patterns']:
543
+ analysis_sections['fraud_indicators'].append({
544
+ 'check': f'desc_suspicious_{suspicious["type"]}',
545
+ 'status': 'suspicious',
546
+ 'message': suspicious['message'],
547
+ 'details': f'Suspicious pattern in description: {suspicious["reason"]}',
548
+ 'severity': 'high',
549
+ 'recommendation': 'Please review the property description for accuracy.'
550
+ })
551
+
552
+ # Documents & Images Analysis
553
+ media_analysis = analyze_documents_and_images(data)
554
+
555
+ # Helper function to check if files exist in data
556
+ def check_files_exist(files):
557
+ if not files:
558
+ return False
559
+ if isinstance(files, str):
560
+ files = [files]
561
+ return any(f and isinstance(f, str) and f.strip() and not f.endswith('×') for f in files)
562
+
563
+ # Add document analysis results
564
+ if media_analysis['total_documents'] == 0:
565
+ # Check if documents were actually provided in the data
566
+ documents = data.get('documents', [])
567
+ if check_files_exist(documents):
568
+ # Files exist but couldn't be analyzed
569
+ analysis_sections['documents'].append({
570
+ 'check': 'document_analysis',
571
+ 'status': 'error',
572
+ 'message': 'Could not analyze provided documents.',
573
+ 'details': 'Please ensure documents are in PDF format and are accessible.',
574
+ 'severity': 'high',
575
+ 'recommendation': 'Please check document format and try again.'
576
+ })
577
+ else:
578
+ analysis_sections['documents'].append({
579
+ 'check': 'documents_validation',
580
+ 'status': 'missing',
581
+ 'message': 'Property documents are required.',
582
+ 'details': 'Please upload relevant property documents in PDF format.',
583
+ 'severity': 'high',
584
+ 'recommendation': 'Upload property documents in PDF format.'
585
+ })
586
+ else:
587
+ for doc in media_analysis['documents']:
588
+ if doc.get('error'):
589
+ analysis_sections['documents'].append({
590
+ 'check': 'document_analysis',
591
+ 'status': 'error',
592
+ 'message': f'Error analyzing document: {doc["error"]}',
593
+ 'details': doc['summary'],
594
+ 'severity': 'high',
595
+ 'recommendation': 'Please ensure the document is a valid PDF file.'
596
+ })
597
+ elif doc['authenticity'] != 'verified':
598
+ analysis_sections['documents'].append({
599
+ 'check': 'document_verification',
600
+ 'status': 'unverified',
601
+ 'message': 'Document authenticity could not be verified.',
602
+ 'details': doc['summary'],
603
+ 'severity': 'medium',
604
+ 'recommendation': 'Please provide clear, legible documents.'
605
+ })
606
+
607
+ # Add image analysis results
608
+ if media_analysis['total_images'] == 0:
609
+ # Check if images were actually provided in the data
610
+ images = data.get('images', [])
611
+ if check_files_exist(images):
612
+ # Files exist but couldn't be analyzed
613
+ analysis_sections['documents'].append({
614
+ 'check': 'image_analysis',
615
+ 'status': 'error',
616
+ 'message': 'Could not analyze provided images.',
617
+ 'details': 'Please ensure images are in JPG or PNG format and are accessible.',
618
+ 'severity': 'high',
619
+ 'recommendation': 'Please check image format and try again.'
620
+ })
621
+ else:
622
+ analysis_sections['documents'].append({
623
+ 'check': 'images_validation',
624
+ 'status': 'missing',
625
+ 'message': 'Property images are required.',
626
+ 'details': 'Please upload at least one image of the property.',
627
+ 'severity': 'high',
628
+ 'recommendation': 'Upload property images in JPG or PNG format.'
629
+ })
630
+ else:
631
+ for img in media_analysis['images']:
632
+ if img.get('error'):
633
+ analysis_sections['documents'].append({
634
+ 'check': 'image_analysis',
635
+ 'status': 'error',
636
+ 'message': f'Error analyzing image: {img["error"]}',
637
+ 'details': img['description'],
638
+ 'severity': 'high',
639
+ 'recommendation': 'Please ensure the image is in JPG or PNG format.'
640
+ })
641
+ elif not img['is_property_image']:
642
+ analysis_sections['documents'].append({
643
+ 'check': 'image_verification',
644
+ 'status': 'unverified',
645
+ 'message': 'Image may not be property-related.',
646
+ 'details': img['description'],
647
+ 'severity': 'medium',
648
+ 'recommendation': 'Please provide clear property images.'
649
+ })
650
+
651
+ # Add media verification scores if any files were analyzed
652
+ if media_analysis['total_documents'] > 0 or media_analysis['total_images'] > 0:
653
+ analysis_sections['documents'].append({
654
+ 'check': 'media_verification_scores',
655
+ 'status': 'valid',
656
+ 'message': 'Media Verification Scores',
657
+ 'details': {
658
+ 'document_verification_score': media_analysis['document_verification_score'],
659
+ 'image_verification_score': media_analysis['image_verification_score'],
660
+ 'total_documents': media_analysis['total_documents'],
661
+ 'total_images': media_analysis['total_images'],
662
+ 'verified_documents': media_analysis['verified_documents'],
663
+ 'verified_images': media_analysis['verified_images']
664
+ },
665
+ 'severity': 'low',
666
+ 'recommendation': 'Review media verification scores for property authenticity.'
667
+ })
668
+
669
+ # Generate Summary
670
+ summary = {
671
+ 'total_checks': sum(len(checks) for checks in analysis_sections.values()),
672
+ 'categories': {section: len(checks) for section, checks in analysis_sections.items()},
673
+ 'severity_counts': {
674
+ 'high': 0,
675
+ 'medium': 0,
676
+ 'low': 0
677
+ },
678
+ 'status_counts': {
679
+ 'valid': 0,
680
+ 'invalid': 0,
681
+ 'suspicious': 0,
682
+ 'inconsistent': 0,
683
+ 'missing': 0,
684
+ 'error': 0,
685
+ 'unverified': 0
686
+ },
687
+ 'fraud_risk_level': 'low',
688
+ 'media_verification': {
689
+ 'document_score': media_analysis['document_verification_score'],
690
+ 'image_score': media_analysis['image_verification_score']
691
+ }
692
+ }
693
+
694
+ # Calculate statistics
695
+ for section_checks in analysis_sections.values():
696
+ for check in section_checks:
697
+ if check['severity'] in summary['severity_counts']:
698
+ summary['severity_counts'][check['severity']] += 1
699
+ if check['status'] in summary['status_counts']:
700
+ summary['status_counts'][check['status']] += 1
701
+
702
+ # Calculate fraud risk level
703
+ high_severity_issues = summary['severity_counts']['high']
704
+ if high_severity_issues > 5:
705
+ summary['fraud_risk_level'] = 'high'
706
+ elif high_severity_issues > 2:
707
+ summary['fraud_risk_level'] = 'medium'
708
+
709
+ # Add summary to analysis
710
+ analysis_sections['summary'] = [{
711
+ 'check': 'summary_analysis',
712
+ 'status': 'valid',
713
+ 'message': 'Property Analysis Summary',
714
+ 'details': summary,
715
+ 'severity': 'low',
716
+ 'recommendation': f'Fraud Risk Level: {summary["fraud_risk_level"].upper()}. Review all findings and address high severity issues first.'
717
+ }]
718
+
719
+ # Convert analysis sections to flat list
720
+ for section_name, checks in analysis_sections.items():
721
+ for check in checks:
722
+ check['category'] = section_name
723
+ cross_checks.append(check)
724
+
725
+ return cross_checks
726
+
727
+ except Exception as e:
728
+ logger.error(f"Error performing cross validation: {str(e)}")
729
+ return [{
730
+ 'check': 'cross_validation_error',
731
+ 'status': 'error',
732
+ 'message': f'Error during validation: {str(e)}',
733
+ 'category': 'System Error',
734
+ 'severity': 'high',
735
+ 'recommendation': 'Please try again or contact support.'
736
+ }]
models/fraud_classification.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/fraud_classification.py
2
+
3
+ import re
4
+ from .model_loader import load_model
5
+ from .logging_config import logger
6
+
7
+ def classify_fraud(property_details, description):
8
+ """
9
+ Classify the risk of fraud in a property listing using zero-shot classification.
10
+ This function analyzes property details and description to identify potential fraud indicators.
11
+ """
12
+ try:
13
+ # Initialize fraud classification result
14
+ fraud_classification = {
15
+ 'alert_level': 'minimal',
16
+ 'alert_score': 0.0,
17
+ 'high_risk': [],
18
+ 'medium_risk': [],
19
+ 'low_risk': [],
20
+ 'confidence_scores': {}
21
+ }
22
+
23
+ # Combine property details and description for analysis
24
+ text_to_analyze = f"{property_details}\n{description}"
25
+
26
+ # Define risk categories for zero-shot classification
27
+ risk_categories = [
28
+ "fraudulent listing",
29
+ "misleading information",
30
+ "fake property",
31
+ "scam attempt",
32
+ "legitimate listing"
33
+ ]
34
+
35
+ # Perform zero-shot classification
36
+ classifier = load_model("zero-shot-classification", "typeform/mobilebert-uncased-mnli")
37
+ result = classifier(text_to_analyze, risk_categories, multi_label=True)
38
+
39
+ # Process classification results
40
+ fraud_score = 0.0
41
+ for label, score in zip(result['labels'], result['scores']):
42
+ if label != "legitimate listing":
43
+ fraud_score += score
44
+ fraud_classification['confidence_scores'][label] = score
45
+
46
+ # Normalize fraud score to 0-1 range
47
+ fraud_score = min(1.0, fraud_score / (len(risk_categories) - 1))
48
+ fraud_classification['alert_score'] = fraud_score
49
+
50
+ # Define fraud indicators to check
51
+ fraud_indicators = {
52
+ 'high_risk': [
53
+ r'urgent|immediate|hurry|limited time|special offer',
54
+ r'bank|transfer|wire|payment|money',
55
+ r'fake|scam|fraud|illegal|unauthorized',
56
+ r'guaranteed|promised|assured|certain',
57
+ r'contact.*whatsapp|whatsapp.*contact',
58
+ r'price.*negotiable|negotiable.*price',
59
+ r'no.*documents|documents.*not.*required',
60
+ r'cash.*only|only.*cash',
61
+ r'off.*market|market.*off',
62
+ r'under.*table|table.*under'
63
+ ],
64
+ 'medium_risk': [
65
+ r'unverified|unconfirmed|unchecked',
66
+ r'partial|incomplete|missing',
67
+ r'different.*location|location.*different',
68
+ r'price.*increased|increased.*price',
69
+ r'no.*photos|photos.*not.*available',
70
+ r'contact.*email|email.*contact',
71
+ r'agent.*not.*available|not.*available.*agent',
72
+ r'property.*not.*viewable|not.*viewable.*property',
73
+ r'price.*changed|changed.*price',
74
+ r'details.*updated|updated.*details'
75
+ ],
76
+ 'low_risk': [
77
+ r'new.*listing|listing.*new',
78
+ r'recent.*update|update.*recent',
79
+ r'price.*reduced|reduced.*price',
80
+ r'contact.*phone|phone.*contact',
81
+ r'agent.*available|available.*agent',
82
+ r'property.*viewable|viewable.*property',
83
+ r'photos.*available|available.*photos',
84
+ r'documents.*available|available.*documents',
85
+ r'price.*fixed|fixed.*price',
86
+ r'details.*complete|complete.*details'
87
+ ]
88
+ }
89
+
90
+ # Check for fraud indicators in text
91
+ for risk_level, patterns in fraud_indicators.items():
92
+ for pattern in patterns:
93
+ matches = re.finditer(pattern, text_to_analyze, re.IGNORECASE)
94
+ for match in matches:
95
+ indicator = match.group(0)
96
+ if indicator not in fraud_classification[risk_level]:
97
+ fraud_classification[risk_level].append(indicator)
98
+
99
+ # Determine alert level based on fraud score and indicators
100
+ if fraud_score > 0.7 or len(fraud_classification['high_risk']) > 0:
101
+ fraud_classification['alert_level'] = 'critical'
102
+ elif fraud_score > 0.5 or len(fraud_classification['medium_risk']) > 2:
103
+ fraud_classification['alert_level'] = 'high'
104
+ elif fraud_score > 0.3 or len(fraud_classification['medium_risk']) > 0:
105
+ fraud_classification['alert_level'] = 'medium'
106
+ elif fraud_score > 0.1 or len(fraud_classification['low_risk']) > 0:
107
+ fraud_classification['alert_level'] = 'low'
108
+ else:
109
+ fraud_classification['alert_level'] = 'minimal'
110
+
111
+ # Additional checks for common fraud patterns
112
+ if re.search(r'price.*too.*good|too.*good.*price', text_to_analyze, re.IGNORECASE):
113
+ fraud_classification['high_risk'].append("Unrealistically low price")
114
+
115
+ if re.search(r'no.*inspection|inspection.*not.*allowed', text_to_analyze, re.IGNORECASE):
116
+ fraud_classification['high_risk'].append("No property inspection allowed")
117
+
118
+ if re.search(r'owner.*abroad|abroad.*owner', text_to_analyze, re.IGNORECASE):
119
+ fraud_classification['medium_risk'].append("Owner claims to be abroad")
120
+
121
+ if re.search(r'agent.*unavailable|unavailable.*agent', text_to_analyze, re.IGNORECASE):
122
+ fraud_classification['medium_risk'].append("Agent unavailable for verification")
123
+
124
+ # Check for inconsistencies in property details
125
+ if 'price' in property_details and 'market_value' in property_details:
126
+ try:
127
+ price = float(re.search(r'\d+(?:,\d+)*(?:\.\d+)?', property_details['price']).group().replace(',', ''))
128
+ market_value = float(re.search(r'\d+(?:,\d+)*(?:\.\d+)?', property_details['market_value']).group().replace(',', ''))
129
+ if price < market_value * 0.5:
130
+ fraud_classification['high_risk'].append("Price significantly below market value")
131
+ except (ValueError, AttributeError):
132
+ pass
133
+
134
+ return fraud_classification
135
+ except Exception as e:
136
+ logger.error(f"Error in fraud classification: {str(e)}")
137
+ return {
138
+ 'alert_level': 'error',
139
+ 'alert_score': 1.0,
140
+ 'high_risk': [f"Error in fraud classification: {str(e)}"],
141
+ 'medium_risk': [],
142
+ 'low_risk': [],
143
+ 'confidence_scores': {}
144
+ }
models/image_analysis.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/image_analysis.py
2
+
3
+ from PIL import Image
4
+ import numpy as np
5
+ from transformers import AutoImageProcessor, AutoModelForImageClassification
6
+ from .logging_config import logger
7
+
8
+ # Initialize real estate classification model
9
+ try:
10
+ processor = AutoImageProcessor.from_pretrained("andupets/real-estate-image-classification")
11
+ model = AutoModelForImageClassification.from_pretrained("andupets/real-estate-image-classification")
12
+ has_model = True
13
+ logger.info("Real estate classification model loaded successfully")
14
+ except Exception as e:
15
+ logger.error(f"Error loading real estate classification model: {str(e)}")
16
+ has_model = False
17
+
18
+ def analyze_image(image):
19
+ try:
20
+ if has_model:
21
+ img_rgb = image.convert('RGB')
22
+ inputs = processor(images=img_rgb, return_tensors="pt")
23
+ outputs = model(**inputs)
24
+ logits = outputs.logits
25
+ probs = logits.softmax(dim=1).detach().numpy()[0]
26
+
27
+ # Get the highest confidence prediction
28
+ max_prob_idx = probs.argmax()
29
+ max_prob = probs[max_prob_idx]
30
+ predicted_label = model.config.id2label[max_prob_idx]
31
+
32
+ # Check if it's a real estate image (confidence > 0.5)
33
+ is_real_estate = max_prob > 0.5
34
+
35
+ quality = assess_image_quality(image)
36
+ is_ai_generated = detect_ai_generated_image(image)
37
+
38
+ return {
39
+ 'is_property_related': is_real_estate,
40
+ 'property_confidence': float(max_prob),
41
+ 'predicted_label': predicted_label,
42
+ 'top_predictions': [
43
+ {'label': model.config.id2label[i], 'confidence': float(prob)}
44
+ for i, prob in enumerate(probs)
45
+ ],
46
+ 'image_quality': quality,
47
+ 'is_ai_generated': is_ai_generated,
48
+ 'authenticity_score': 0.95 if not is_ai_generated else 0.60
49
+ }
50
+ else:
51
+ logger.warning("Real estate classification model unavailable")
52
+ return {
53
+ 'is_property_related': False,
54
+ 'property_confidence': 0.0,
55
+ 'predicted_label': 'unknown',
56
+ 'top_predictions': [],
57
+ 'image_quality': assess_image_quality(image),
58
+ 'is_ai_generated': False,
59
+ 'authenticity_score': 0.5
60
+ }
61
+ except Exception as e:
62
+ logger.error(f"Error analyzing image: {str(e)}")
63
+ return {
64
+ 'is_property_related': False,
65
+ 'property_confidence': 0.0,
66
+ 'predicted_label': 'error',
67
+ 'top_predictions': [],
68
+ 'image_quality': {'resolution': 'unknown', 'quality_score': 0},
69
+ 'is_ai_generated': False,
70
+ 'authenticity_score': 0.0,
71
+ 'error': str(e)
72
+ }
73
+
74
+ def detect_ai_generated_image(image):
75
+ try:
76
+ img_array = np.array(image)
77
+ if len(img_array.shape) == 3:
78
+ gray = np.mean(img_array, axis=2)
79
+ else:
80
+ gray = img_array
81
+ noise = gray - np.mean(gray)
82
+ noise_std = np.std(noise)
83
+ width, height = image.size
84
+ perfect_dimensions = (width % 64 == 0 and height % 64 == 0)
85
+ has_exif = hasattr(image, '_getexif') and image._getexif() is not None
86
+ return noise_std < 0.05 or perfect_dimensions or not has_exif
87
+ except Exception as e:
88
+ logger.error(f"Error detecting AI-generated image: {str(e)}")
89
+ return False
90
+
91
+ def assess_image_quality(img):
92
+ try:
93
+ width, height = img.size
94
+ resolution = width * height
95
+ quality_score = min(100, resolution // 20000)
96
+ return {
97
+ 'resolution': f"{width}x{height}",
98
+ 'quality_score': quality_score
99
+ }
100
+ except Exception as e:
101
+ logger.error(f"Error assessing image quality: {str(e)}")
102
+ return {
103
+ 'resolution': 'unknown',
104
+ 'quality_score': 0
105
+ }
models/image_quality.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/image_quality.py
2
+
3
+ from PIL import Image
4
+ from .logging_config import logger
5
+
6
+ def assess_image_quality(img):
7
+ try:
8
+ width, height = img.size
9
+ resolution = width * height
10
+ quality_score = min(100, resolution // 20000)
11
+ return {
12
+ 'resolution': f"{width}x{height}",
13
+ 'quality_score': quality_score
14
+ }
15
+ except Exception as e:
16
+ logger.error(f"Error assessing image quality: {str(e)}")
17
+ return {
18
+ 'resolution': 'unknown',
19
+ 'quality_score': 0
20
+ }
models/legal_analysis.py ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/legal_analysis.py
2
+
3
+ import re
4
+ from .model_loader import load_model
5
+ from .logging_config import logger
6
+ from typing import Dict, Any, List, Tuple
7
+
8
+ def analyze_legal_details(legal_text: str) -> Dict[str, Any]:
9
+ """Analyze legal details of a property with comprehensive validation."""
10
+ try:
11
+ if not legal_text or len(legal_text.strip()) < 5:
12
+ return {
13
+ 'assessment': 'insufficient',
14
+ 'confidence': 0.0,
15
+ 'summary': 'No legal details provided',
16
+ 'completeness_score': 0,
17
+ 'potential_issues': False,
18
+ 'legal_metrics': {},
19
+ 'reasoning': 'No legal details provided for analysis',
20
+ 'top_classifications': [],
21
+ 'document_verification': {},
22
+ 'compliance_status': {},
23
+ 'risk_assessment': {}
24
+ }
25
+
26
+ classifier = load_model("zero-shot-classification", "typeform/mobilebert-uncased-mnli")
27
+
28
+ # Enhanced legal categories with more specific indicators
29
+ categories = [
30
+ # Title and Ownership
31
+ "clear title documentation",
32
+ "title verification documents",
33
+ "ownership transfer documents",
34
+ "inheritance documents",
35
+ "gift deed documents",
36
+ "power of attorney documents",
37
+
38
+ # Property Registration
39
+ "property registration documents",
40
+ "sale deed documents",
41
+ "conveyance deed documents",
42
+ "development agreement documents",
43
+ "joint development agreement documents",
44
+
45
+ # Tax and Financial
46
+ "property tax records",
47
+ "tax clearance certificates",
48
+ "encumbrance certificates",
49
+ "bank loan documents",
50
+ "mortgage documents",
51
+
52
+ # Approvals and Permits
53
+ "building permits",
54
+ "construction approvals",
55
+ "occupation certificates",
56
+ "completion certificates",
57
+ "environmental clearances",
58
+
59
+ # Land and Usage
60
+ "land use certificates",
61
+ "zoning certificates",
62
+ "layout approvals",
63
+ "master plan compliance",
64
+ "land conversion documents",
65
+
66
+ # Compliance and Legal
67
+ "legal compliance certificates",
68
+ "no objection certificates",
69
+ "fire safety certificates",
70
+ "structural stability certificates",
71
+ "water and electricity compliance",
72
+
73
+ # Disputes and Litigation
74
+ "property dispute records",
75
+ "litigation history",
76
+ "court orders",
77
+ "settlement agreements",
78
+ "pending legal cases"
79
+ ]
80
+
81
+ # Create a more detailed context for analysis
82
+ legal_context = f"""
83
+ Legal Documentation Analysis:
84
+ {legal_text[:1000]}
85
+
86
+ Key aspects to verify:
87
+ 1. Title and Ownership:
88
+ - Clear title documentation
89
+ - Ownership transfer history
90
+ - Inheritance/gift documentation
91
+ - Power of attorney status
92
+
93
+ 2. Property Registration:
94
+ - Sale deed validity
95
+ - Registration status
96
+ - Development agreements
97
+ - Joint development status
98
+
99
+ 3. Tax and Financial:
100
+ - Property tax compliance
101
+ - Tax clearance status
102
+ - Encumbrance status
103
+ - Mortgage/loan status
104
+
105
+ 4. Approvals and Permits:
106
+ - Building permit validity
107
+ - Construction approvals
108
+ - Occupation certificates
109
+ - Environmental clearances
110
+
111
+ 5. Land and Usage:
112
+ - Land use compliance
113
+ - Zoning regulations
114
+ - Layout approvals
115
+ - Master plan compliance
116
+
117
+ 6. Compliance and Legal:
118
+ - Legal compliance status
119
+ - Safety certificates
120
+ - Utility compliance
121
+ - Regulatory approvals
122
+
123
+ 7. Disputes and Litigation:
124
+ - Dispute history
125
+ - Court orders
126
+ - Settlement status
127
+ - Pending cases
128
+ """
129
+
130
+ # Analyze legal text with multiple aspects
131
+ legal_result = classifier(legal_context, categories, multi_label=True)
132
+
133
+ # Get top classifications with confidence scores
134
+ top_classifications = []
135
+ for label, score in zip(legal_result['labels'][:5], legal_result['scores'][:5]):
136
+ if score > 0.3: # Only include if confidence is above 30%
137
+ top_classifications.append({
138
+ 'classification': label,
139
+ 'confidence': float(score)
140
+ })
141
+
142
+ # Generate summary using BART
143
+ summary = summarize_text(legal_text[:1000])
144
+
145
+ # Calculate detailed legal metrics
146
+ legal_metrics = {
147
+ 'title_and_ownership': sum(score for label, score in zip(legal_result['labels'], legal_result['scores'])
148
+ if label in ['clear title documentation', 'title verification documents',
149
+ 'ownership transfer documents', 'inheritance documents']),
150
+ 'property_registration': sum(score for label, score in zip(legal_result['labels'], legal_result['scores'])
151
+ if label in ['property registration documents', 'sale deed documents',
152
+ 'conveyance deed documents', 'development agreement documents']),
153
+ 'tax_and_financial': sum(score for label, score in zip(legal_result['labels'], legal_result['scores'])
154
+ if label in ['property tax records', 'tax clearance certificates',
155
+ 'encumbrance certificates', 'bank loan documents']),
156
+ 'approvals_and_permits': sum(score for label, score in zip(legal_result['labels'], legal_result['scores'])
157
+ if label in ['building permits', 'construction approvals',
158
+ 'occupation certificates', 'completion certificates']),
159
+ 'land_and_usage': sum(score for label, score in zip(legal_result['labels'], legal_result['scores'])
160
+ if label in ['land use certificates', 'zoning certificates',
161
+ 'layout approvals', 'master plan compliance']),
162
+ 'compliance_and_legal': sum(score for label, score in zip(legal_result['labels'], legal_result['scores'])
163
+ if label in ['legal compliance certificates', 'no objection certificates',
164
+ 'fire safety certificates', 'structural stability certificates']),
165
+ 'disputes_and_litigation': sum(score for label, score in zip(legal_result['labels'], legal_result['scores'])
166
+ if label in ['property dispute records', 'litigation history',
167
+ 'court orders', 'pending legal cases'])
168
+ }
169
+
170
+ # Calculate completeness score with weighted components
171
+ weights = {
172
+ 'title_and_ownership': 0.25,
173
+ 'property_registration': 0.20,
174
+ 'tax_and_financial': 0.15,
175
+ 'approvals_and_permits': 0.15,
176
+ 'land_and_usage': 0.10,
177
+ 'compliance_and_legal': 0.10,
178
+ 'disputes_and_litigation': 0.05
179
+ }
180
+
181
+ completeness_score = sum(
182
+ legal_metrics[category] * weight * 100
183
+ for category, weight in weights.items()
184
+ )
185
+
186
+ # Determine if there are potential issues
187
+ potential_issues = legal_metrics['disputes_and_litigation'] > 0.3
188
+
189
+ # Generate detailed reasoning
190
+ reasoning_parts = []
191
+
192
+ # Document verification status
193
+ document_verification = {
194
+ 'title_documents': {
195
+ 'status': 'verified' if legal_metrics['title_and_ownership'] > 0.7 else 'partial' if legal_metrics['title_and_ownership'] > 0.4 else 'missing',
196
+ 'score': legal_metrics['title_and_ownership'] * 100
197
+ },
198
+ 'registration_documents': {
199
+ 'status': 'verified' if legal_metrics['property_registration'] > 0.7 else 'partial' if legal_metrics['property_registration'] > 0.4 else 'missing',
200
+ 'score': legal_metrics['property_registration'] * 100
201
+ },
202
+ 'tax_documents': {
203
+ 'status': 'verified' if legal_metrics['tax_and_financial'] > 0.7 else 'partial' if legal_metrics['tax_and_financial'] > 0.4 else 'missing',
204
+ 'score': legal_metrics['tax_and_financial'] * 100
205
+ },
206
+ 'approval_documents': {
207
+ 'status': 'verified' if legal_metrics['approvals_and_permits'] > 0.7 else 'partial' if legal_metrics['approvals_and_permits'] > 0.4 else 'missing',
208
+ 'score': legal_metrics['approvals_and_permits'] * 100
209
+ }
210
+ }
211
+
212
+ # Compliance status
213
+ compliance_status = {
214
+ 'land_use': {
215
+ 'status': 'compliant' if legal_metrics['land_and_usage'] > 0.7 else 'partial' if legal_metrics['land_and_usage'] > 0.4 else 'non-compliant',
216
+ 'score': legal_metrics['land_and_usage'] * 100
217
+ },
218
+ 'legal_compliance': {
219
+ 'status': 'compliant' if legal_metrics['compliance_and_legal'] > 0.7 else 'partial' if legal_metrics['compliance_and_legal'] > 0.4 else 'non-compliant',
220
+ 'score': legal_metrics['compliance_and_legal'] * 100
221
+ }
222
+ }
223
+
224
+ # Risk assessment
225
+ risk_assessment = {
226
+ 'litigation_risk': {
227
+ 'level': 'high' if legal_metrics['disputes_and_litigation'] > 0.6 else 'medium' if legal_metrics['disputes_and_litigation'] > 0.3 else 'low',
228
+ 'score': legal_metrics['disputes_and_litigation'] * 100
229
+ },
230
+ 'documentation_risk': {
231
+ 'level': 'high' if completeness_score < 50 else 'medium' if completeness_score < 70 else 'low',
232
+ 'score': 100 - completeness_score
233
+ }
234
+ }
235
+
236
+ # Generate reasoning based on all metrics
237
+ if top_classifications:
238
+ primary_class = top_classifications[0]['classification']
239
+ confidence = top_classifications[0]['confidence']
240
+ reasoning_parts.append(f"Primary assessment: {primary_class} (confidence: {confidence:.0%})")
241
+
242
+ # Add document verification status
243
+ for doc_type, status in document_verification.items():
244
+ reasoning_parts.append(f"{doc_type.replace('_', ' ').title()}: {status['status']} (score: {status['score']:.0f}%)")
245
+
246
+ # Add compliance status
247
+ for compliance_type, status in compliance_status.items():
248
+ reasoning_parts.append(f"{compliance_type.replace('_', ' ').title()}: {status['status']} (score: {status['score']:.0f}%)")
249
+
250
+ # Add risk assessment
251
+ for risk_type, assessment in risk_assessment.items():
252
+ reasoning_parts.append(f"{risk_type.replace('_', ' ').title()}: {assessment['level']} risk (score: {assessment['score']:.0f}%)")
253
+
254
+ # Calculate overall confidence
255
+ overall_confidence = min(1.0, (
256
+ legal_metrics['title_and_ownership'] * 0.3 +
257
+ legal_metrics['property_registration'] * 0.2 +
258
+ legal_metrics['tax_and_financial'] * 0.15 +
259
+ legal_metrics['approvals_and_permits'] * 0.15 +
260
+ legal_metrics['land_and_usage'] * 0.1 +
261
+ legal_metrics['compliance_and_legal'] * 0.1
262
+ ))
263
+
264
+ return {
265
+ 'assessment': top_classifications[0]['classification'] if top_classifications else 'could not assess',
266
+ 'confidence': float(overall_confidence),
267
+ 'summary': summary,
268
+ 'completeness_score': int(completeness_score),
269
+ 'potential_issues': potential_issues,
270
+ 'legal_metrics': legal_metrics,
271
+ 'reasoning': '. '.join(reasoning_parts),
272
+ 'top_classifications': top_classifications,
273
+ 'document_verification': document_verification,
274
+ 'compliance_status': compliance_status,
275
+ 'risk_assessment': risk_assessment
276
+ }
277
+ except Exception as e:
278
+ logger.error(f"Error analyzing legal details: {str(e)}")
279
+ return {
280
+ 'assessment': 'could not assess',
281
+ 'confidence': 0.0,
282
+ 'summary': 'Error analyzing legal details',
283
+ 'completeness_score': 0,
284
+ 'potential_issues': False,
285
+ 'legal_metrics': {},
286
+ 'reasoning': 'Technical error occurred during analysis',
287
+ 'top_classifications': [],
288
+ 'document_verification': {},
289
+ 'compliance_status': {},
290
+ 'risk_assessment': {}
291
+ }
models/location_analysis.py ADDED
@@ -0,0 +1,488 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/location_analysis.py
2
+
3
+ from .model_loader import load_model
4
+ from geopy.geocoders import Nominatim
5
+ from .logging_config import logger
6
+ import re
7
+ import time
8
+ from typing import Dict, Any
9
+ from geopy.distance import geodesic
10
+
11
+ geocoder = Nominatim(user_agent="indian_property_verifier", timeout=10)
12
+
13
+ def validate_address_format(address: str) -> bool:
14
+ """Validate the format of the address."""
15
+ if not address:
16
+ return False
17
+
18
+ # Check for minimum length
19
+ if len(address.strip()) < 10: # Minimum reasonable length for an address
20
+ return False
21
+
22
+ # Check for minimum components
23
+ components = [comp.strip() for comp in address.split(',')]
24
+ if len(components) < 2: # At least area and city
25
+ return False
26
+
27
+ # Check for common address patterns
28
+ patterns = [
29
+ r'\d+', # Should contain numbers
30
+ r'[A-Za-z\s]+', # Should contain letters
31
+ r'(?:street|road|avenue|lane|colony|society|apartment|flat|house|building|plot|block|sector|phase|floor|wing|area|locality|main|cross|circle|square|market|ward|zone|mandal|municipal|corporation|greater)', # Common address terms
32
+ ]
33
+
34
+ # Check if at least 2 patterns match
35
+ pattern_matches = sum(1 for pattern in patterns if re.search(pattern, address.lower()))
36
+ if pattern_matches < 2:
37
+ return False
38
+
39
+ # Check for common address components
40
+ address_lower = address.lower()
41
+ has_location = any(term in address_lower for term in [
42
+ 'ward', 'zone', 'mandal', 'municipal', 'corporation', 'greater',
43
+ 'street', 'road', 'avenue', 'lane', 'colony', 'society'
44
+ ])
45
+ has_area = any(term in address_lower for term in [
46
+ 'colony', 'society', 'apartment', 'flat', 'house', 'plot', 'block', 'sector',
47
+ 'area', 'locality', 'main', 'cross', 'circle', 'square', 'market'
48
+ ])
49
+
50
+ return has_location or has_area
51
+
52
+ def validate_postal_code(postal_code: str) -> bool:
53
+ """Validate Indian postal code format."""
54
+ if not postal_code:
55
+ return False
56
+
57
+ # Remove any spaces and convert to string
58
+ postal_code = str(postal_code).strip().replace(' ', '')
59
+
60
+ # Check format
61
+ if not re.match(r'^\d{6}$', postal_code):
62
+ return False
63
+
64
+ # Validate first digit (region)
65
+ first_digit = int(postal_code[0])
66
+ if first_digit not in range(1, 9): # India has 8 postal regions
67
+ return False
68
+
69
+ return True
70
+
71
+ def validate_coordinates(latitude: str, longitude: str) -> bool:
72
+ """Validate coordinate format and range for India."""
73
+ try:
74
+ # Convert to float and handle any string formatting
75
+ lat = float(str(latitude).strip())
76
+ lng = float(str(longitude).strip())
77
+
78
+ # India's approximate boundaries with some buffer
79
+ india_bounds = {
80
+ 'lat_min': 6.0, # Slightly expanded for coastal areas
81
+ 'lat_max': 38.0, # Slightly expanded for northern regions
82
+ 'lng_min': 67.0, # Slightly expanded for western regions
83
+ 'lng_max': 98.0 # Slightly expanded for eastern regions
84
+ }
85
+
86
+ # Check if coordinates are within India's boundaries
87
+ if not (india_bounds['lat_min'] <= lat <= india_bounds['lat_max'] and
88
+ india_bounds['lng_min'] <= lng <= india_bounds['lng_max']):
89
+ return False
90
+
91
+ # Check for reasonable precision (no more than 6 decimal places)
92
+ lat_str = f"{lat:.6f}"
93
+ lng_str = f"{lng:.6f}"
94
+
95
+ # Check if the original values match the formatted values
96
+ if abs(float(lat_str) - lat) > 0.000001 or abs(float(lng_str) - lng) > 0.000001:
97
+ return False
98
+
99
+ return True
100
+ except (ValueError, TypeError):
101
+ return False
102
+
103
+ def verify_location_in_city(address: str, city: str) -> bool:
104
+ """Verify if the address exists in the given city."""
105
+ if not address or not city:
106
+ return False
107
+
108
+ try:
109
+ # Clean and normalize inputs
110
+ address = address.strip()
111
+ city = city.strip()
112
+
113
+ # Extract key components from the address
114
+ address_components = [comp.strip() for comp in address.split(',')]
115
+
116
+ # Try different address formats with various combinations
117
+ address_formats = [
118
+ # Full address
119
+ f"{address}, India",
120
+ # City with key components
121
+ f"{city}, {address_components[0]}, India", # First component (usually area/ward)
122
+ f"{city}, {address_components[1]}, India", # Second component (usually ward details)
123
+ # Municipal corporation format
124
+ f"{city}, {next((comp for comp in address_components if 'municipal corporation' in comp.lower()), '')}, India",
125
+ # Mandal format
126
+ f"{city}, {next((comp for comp in address_components if 'mandal' in comp.lower()), '')}, India",
127
+ # Basic format
128
+ f"{address_components[0]}, {city}, India",
129
+ # Zone format
130
+ f"{next((comp for comp in address_components if 'zone' in comp.lower()), '')}, {city}, India"
131
+ ]
132
+
133
+ # Try each format with rate limiting
134
+ for addr_format in address_formats:
135
+ try:
136
+ location = geocoder.geocode(addr_format, timeout=10)
137
+ if location:
138
+ # Get the full address and normalize it
139
+ location_address = location.address.lower()
140
+ city_lower = city.lower()
141
+
142
+ # Check for city name in different formats
143
+ city_variations = [
144
+ city_lower,
145
+ city_lower.replace(' ', ''),
146
+ city_lower.replace(' ', '-'),
147
+ f"{city_lower} city",
148
+ f"{city_lower} district",
149
+ f"{city_lower} municipal corporation",
150
+ f"greater {city_lower}",
151
+ f"greater {city_lower} municipal corporation"
152
+ ]
153
+
154
+ # Check if any city variation is in the address
155
+ if any(var in location_address for var in city_variations):
156
+ # Additional verification: check if the address components match
157
+ location_components = [comp.strip().lower() for comp in location_address.split(',')]
158
+
159
+ # Check for key components
160
+ key_components = [
161
+ comp.lower() for comp in address_components
162
+ if any(keyword in comp.lower() for keyword in [
163
+ 'ward', 'zone', 'mandal', 'municipal', 'corporation', 'greater'
164
+ ])
165
+ ]
166
+
167
+ # Check if at least 2 key components match
168
+ matching_components = sum(1 for comp in key_components if any(comp in loc_comp for loc_comp in location_components))
169
+ if matching_components >= 2:
170
+ return True
171
+ except Exception as e:
172
+ logger.debug(f"Error in address verification: {str(e)}")
173
+ continue
174
+ time.sleep(1) # Rate limiting
175
+
176
+ # If direct verification fails, try reverse geocoding
177
+ try:
178
+ # Get city coordinates
179
+ city_location = geocoder.geocode(f"{city}, India", timeout=10)
180
+ if city_location:
181
+ # Try to geocode the address
182
+ address_location = geocoder.geocode(f"{address}, {city}, India", timeout=10)
183
+ if address_location:
184
+ # Calculate distance between coordinates
185
+ city_coords = (city_location.latitude, city_location.longitude)
186
+ address_coords = (address_location.latitude, address_location.longitude)
187
+ distance = geodesic(city_coords, address_coords).kilometers
188
+
189
+ # Use tier-based distance threshold
190
+ city_lower = city.lower()
191
+ metro_cities = ["mumbai", "delhi", "bangalore", "hyderabad", "chennai", "kolkata", "pune"]
192
+ tier2_cities = ["ahmedabad", "jaipur", "surat", "lucknow", "kanpur", "nagpur", "indore",
193
+ "thane", "bhopal", "visakhapatnam", "patna", "vadodara", "ghaziabad",
194
+ "ludhiana", "agra", "nashik", "faridabad", "meerut", "rajkot", "varanasi"]
195
+
196
+ if any(city in city_lower for city in metro_cities):
197
+ max_distance = 50 # 50km for metro cities
198
+ elif any(city in city_lower for city in tier2_cities):
199
+ max_distance = 30 # 30km for tier 2 cities
200
+ else:
201
+ max_distance = 20 # 20km for other cities
202
+
203
+ return distance <= max_distance
204
+ except Exception as e:
205
+ logger.debug(f"Error in reverse geocoding: {str(e)}")
206
+
207
+ return False
208
+ except Exception as e:
209
+ logger.error(f"Error in location verification: {str(e)}")
210
+ return False
211
+
212
+ def verify_city_in_state(city: str, state: str) -> bool:
213
+ """Verify if the city exists in the given state."""
214
+ if not city or not state:
215
+ return False
216
+
217
+ try:
218
+ # Try different formats
219
+ formats = [
220
+ f"{city}, {state}, India",
221
+ f"{state}, {city}, India",
222
+ f"{city}, {state}"
223
+ ]
224
+
225
+ for fmt in formats:
226
+ try:
227
+ location = geocoder.geocode(fmt, timeout=10)
228
+ if location:
229
+ location_address = location.address.lower()
230
+ city_lower = city.lower()
231
+ state_lower = state.lower()
232
+
233
+ # Check for city and state names in different formats
234
+ city_variations = [
235
+ city_lower,
236
+ city_lower.replace(' ', ''),
237
+ city_lower.replace(' ', '-')
238
+ ]
239
+
240
+ state_variations = [
241
+ state_lower,
242
+ state_lower.replace(' ', ''),
243
+ state_lower.replace(' ', '-')
244
+ ]
245
+
246
+ if any(city_var in location_address for city_var in city_variations) and \
247
+ any(state_var in location_address for state_var in state_variations):
248
+ return True
249
+ except:
250
+ continue
251
+ time.sleep(1)
252
+
253
+ return False
254
+ except:
255
+ return False
256
+
257
+ def verify_state_in_country(state: str, country: str = "India") -> bool:
258
+ """Verify if the state exists in the given country."""
259
+ if not state:
260
+ return False
261
+
262
+ # List of valid Indian states and union territories
263
+ valid_states = [
264
+ 'andhra pradesh', 'arunachal pradesh', 'assam', 'bihar', 'chhattisgarh',
265
+ 'goa', 'gujarat', 'haryana', 'himachal pradesh', 'jharkhand', 'karnataka',
266
+ 'kerala', 'madhya pradesh', 'maharashtra', 'manipur', 'meghalaya', 'mizoram',
267
+ 'nagaland', 'odisha', 'punjab', 'rajasthan', 'sikkim', 'tamil nadu',
268
+ 'telangana', 'tripura', 'uttar pradesh', 'uttarakhand', 'west bengal',
269
+ 'andaman and nicobar islands', 'chandigarh', 'dadra and nagar haveli and daman and diu',
270
+ 'delhi', 'jammu and kashmir', 'ladakh', 'lakshadweep', 'puducherry'
271
+ ]
272
+
273
+ state_lower = state.lower()
274
+ return state_lower in valid_states
275
+
276
+ def verify_postal_code_in_city(postal_code: str, city: str) -> bool:
277
+ """Verify if the postal code belongs to the given city."""
278
+ if not postal_code or not city:
279
+ return False
280
+
281
+ try:
282
+ # Try different formats
283
+ formats = [
284
+ f"{postal_code}, {city}, India",
285
+ f"{city}, {postal_code}, India",
286
+ f"{postal_code}, {city}"
287
+ ]
288
+
289
+ for fmt in formats:
290
+ try:
291
+ location = geocoder.geocode(fmt, timeout=10)
292
+ if location:
293
+ location_address = location.address.lower()
294
+ city_lower = city.lower()
295
+
296
+ # Check for city name in different formats
297
+ city_variations = [
298
+ city_lower,
299
+ city_lower.replace(' ', ''),
300
+ city_lower.replace(' ', '-')
301
+ ]
302
+
303
+ if any(var in location_address for var in city_variations):
304
+ return True
305
+ except:
306
+ continue
307
+ time.sleep(1)
308
+
309
+ return False
310
+ except:
311
+ return False
312
+
313
+ def verify_coordinates_in_city(latitude: str, longitude: str, city: str) -> bool:
314
+ """Verify if the coordinates are within the given city."""
315
+ if not all([latitude, longitude, city]):
316
+ return False
317
+
318
+ try:
319
+ # Convert to float and handle any string formatting
320
+ lat = float(str(latitude).strip())
321
+ lng = float(str(longitude).strip())
322
+
323
+ # Get city coordinates
324
+ city_location = geocoder.geocode(f"{city}, India", timeout=10)
325
+ if not city_location:
326
+ return False
327
+
328
+ city_coords = (city_location.latitude, city_location.longitude)
329
+ property_coords = (lat, lng)
330
+
331
+ # Calculate distance between coordinates
332
+ distance = geodesic(city_coords, property_coords).kilometers
333
+
334
+ # Define maximum allowed distance based on city tier
335
+ city_lower = city.lower()
336
+ metro_cities = ["mumbai", "delhi", "bangalore", "hyderabad", "chennai", "kolkata", "pune"]
337
+ tier2_cities = ["ahmedabad", "jaipur", "surat", "lucknow", "kanpur", "nagpur", "indore",
338
+ "thane", "bhopal", "visakhapatnam", "patna", "vadodara", "ghaziabad",
339
+ "ludhiana", "agra", "nashik", "faridabad", "meerut", "rajkot", "varanasi"]
340
+
341
+ # Adjust max distance based on city tier
342
+ if any(city in city_lower for city in metro_cities):
343
+ max_distance = 50 # 50km for metro cities
344
+ elif any(city in city_lower for city in tier2_cities):
345
+ max_distance = 30 # 30km for tier 2 cities
346
+ else:
347
+ max_distance = 20 # 20km for other cities
348
+
349
+ return distance <= max_distance
350
+ except:
351
+ return False
352
+
353
+ def analyze_location(data: Dict[str, Any]) -> Dict[str, Any]:
354
+ """Analyze location data with detailed verification."""
355
+ try:
356
+ # Initialize verification results
357
+ verification_results = {
358
+ 'address_format_valid': validate_address_format(data.get('address', '')),
359
+ 'address_in_city': verify_location_in_city(data.get('address', ''), data.get('city', '')),
360
+ 'city_in_state': verify_city_in_state(data.get('city', ''), data.get('state', '')),
361
+ 'state_in_country': verify_state_in_country(data.get('state', '')),
362
+ 'postal_code_valid': validate_postal_code(data.get('zip', '')),
363
+ 'postal_code_in_city': verify_postal_code_in_city(data.get('zip', ''), data.get('city', '')),
364
+ 'coordinates_valid': validate_coordinates(data.get('latitude', ''), data.get('longitude', '')),
365
+ 'coordinates_in_city': verify_coordinates_in_city(
366
+ data.get('latitude', ''),
367
+ data.get('longitude', ''),
368
+ data.get('city', '')
369
+ )
370
+ }
371
+
372
+ # Calculate weighted completeness score with adjusted weights
373
+ weights = {
374
+ 'address_format_valid': 0.15,
375
+ 'address_in_city': 0.20, # Increased weight for address verification
376
+ 'city_in_state': 0.10,
377
+ 'state_in_country': 0.10,
378
+ 'postal_code_valid': 0.10,
379
+ 'postal_code_in_city': 0.10,
380
+ 'coordinates_valid': 0.10,
381
+ 'coordinates_in_city': 0.15
382
+ }
383
+
384
+ completeness_score = sum(
385
+ weights[key] * 100 if result else 0
386
+ for key, result in verification_results.items()
387
+ )
388
+
389
+ # Determine location quality with more lenient criteria
390
+ critical_checks = ['address_format_valid', 'city_in_state', 'state_in_country', 'postal_code_valid']
391
+ secondary_checks = ['address_in_city', 'postal_code_in_city', 'coordinates_valid', 'coordinates_in_city']
392
+
393
+ # Location is verified if all critical checks pass and at least 2 secondary checks pass
394
+ critical_passed = all(verification_results[check] for check in critical_checks)
395
+ secondary_passed = sum(1 for check in secondary_checks if verification_results[check])
396
+ location_quality = "verified" if critical_passed and secondary_passed >= 2 else "unverified"
397
+
398
+ # Analyze landmarks
399
+ landmarks_analysis = {
400
+ 'provided': bool(data.get('nearby_landmarks')),
401
+ 'count': len(data.get('nearby_landmarks', '').split(',')) if data.get('nearby_landmarks') else 0,
402
+ 'types': []
403
+ }
404
+
405
+ if data.get('nearby_landmarks'):
406
+ landmark_types = {
407
+ 'transport': ['station', 'metro', 'bus', 'railway', 'airport', 'terminal', 'depot', 'stand', 'stop'],
408
+ 'education': ['school', 'college', 'university', 'institute', 'academy', 'campus', 'library'],
409
+ 'healthcare': ['hospital', 'clinic', 'medical', 'health', 'diagnostic', 'pharmacy', 'dispensary'],
410
+ 'shopping': ['mall', 'market', 'shop', 'store', 'bazaar', 'complex', 'plaza', 'retail', 'outlet'],
411
+ 'entertainment': ['park', 'garden', 'theater', 'cinema', 'stadium', 'auditorium', 'playground'],
412
+ 'business': ['office', 'business', 'corporate', 'commercial', 'industrial', 'tech park', 'hub']
413
+ }
414
+
415
+ landmarks = [landmark.strip() for landmark in data['nearby_landmarks'].lower().split(',')]
416
+ for landmark in landmarks:
417
+ for type_name, keywords in landmark_types.items():
418
+ if any(keyword in landmark for keyword in keywords):
419
+ if type_name not in landmarks_analysis['types']:
420
+ landmarks_analysis['types'].append(type_name)
421
+
422
+ # Determine city tier
423
+ city_tier = "unknown"
424
+ if data.get('city'):
425
+ city_lower = data['city'].lower()
426
+ metro_cities = ["mumbai", "delhi", "bangalore", "hyderabad", "chennai", "kolkata", "pune"]
427
+ tier2_cities = ["ahmedabad", "jaipur", "surat", "lucknow", "kanpur", "nagpur", "indore",
428
+ "thane", "bhopal", "visakhapatnam", "patna", "vadodara", "ghaziabad",
429
+ "ludhiana", "agra", "nashik", "faridabad", "meerut", "rajkot", "varanasi"]
430
+
431
+ if any(city in city_lower for city in metro_cities):
432
+ city_tier = "metro"
433
+ elif any(city in city_lower for city in tier2_cities):
434
+ city_tier = "tier2"
435
+ else:
436
+ city_tier = "tier3"
437
+
438
+ return {
439
+ **verification_results,
440
+ 'assessment': "complete" if completeness_score >= 80 else "partial" if completeness_score >= 50 else "minimal",
441
+ 'completeness_score': completeness_score,
442
+ 'location_quality': location_quality,
443
+ 'city_tier': city_tier,
444
+ 'landmarks_analysis': landmarks_analysis,
445
+ 'verification_status': "verified" if location_quality == "verified" else "unverified",
446
+ 'formatted_address': f"{data.get('address', '')}, {data.get('city', '')}, {data.get('state', '')}, India - {data.get('zip', '')}"
447
+ }
448
+
449
+ except Exception as e:
450
+ logger.error(f"Error analyzing location: {str(e)}")
451
+ return {
452
+ 'assessment': 'error',
453
+ 'completeness_score': 0,
454
+ 'location_quality': 'error',
455
+ 'city_tier': 'unknown',
456
+ 'landmarks_analysis': {'provided': False, 'count': 0, 'types': []},
457
+ 'verification_status': 'error',
458
+ 'formatted_address': '',
459
+ 'address_format_valid': False,
460
+ 'address_in_city': False,
461
+ 'city_in_state': False,
462
+ 'state_in_country': False,
463
+ 'postal_code_valid': False,
464
+ 'postal_code_in_city': False,
465
+ 'coordinates_valid': False,
466
+ 'coordinates_in_city': False
467
+ }
468
+
469
+ def calculate_location_completeness(data):
470
+ # Define weights for different fields
471
+ weights = {
472
+ 'address': 0.25,
473
+ 'city': 0.20,
474
+ 'state': 0.15,
475
+ 'country': 0.05,
476
+ 'zip': 0.10,
477
+ 'latitude': 0.10,
478
+ 'longitude': 0.10,
479
+ 'nearby_landmarks': 0.05
480
+ }
481
+
482
+ # Calculate weighted score
483
+ score = 0
484
+ for field, weight in weights.items():
485
+ if data[field]:
486
+ score += weight
487
+
488
+ return int(score * 100)
models/logging_config.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/logging_config.py
2
+
3
+ import os
4
+ import logging
5
+
6
+ def setup_logging():
7
+ log_dir = os.environ.get('LOG_DIR', '/app/logs')
8
+ try:
9
+ os.makedirs(log_dir, exist_ok=True)
10
+ log_file = os.path.join(log_dir, 'app.log')
11
+
12
+ # Ensure log file exists and is writable
13
+ if not os.path.exists(log_file):
14
+ open(log_file, 'a').close()
15
+ os.chmod(log_file, 0o666)
16
+
17
+ logging.basicConfig(
18
+ level=logging.INFO,
19
+ format='%(asctime)s - %(levelname)s - %(message)s',
20
+ handlers=[
21
+ logging.FileHandler(log_file),
22
+ logging.StreamHandler()
23
+ ]
24
+ )
25
+ return logging.getLogger(__name__)
26
+ except Exception as e:
27
+ # Fallback to console-only logging if file logging fails
28
+ logging.basicConfig(
29
+ level=logging.INFO,
30
+ format='%(asctime)s - %(levelname)s - %(message)s',
31
+ handlers=[logging.StreamHandler()]
32
+ )
33
+ return logging.getLogger(__name__)
34
+
35
+ logger = setup_logging()
models/market_value.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/market_value.py
2
+
3
+ from datetime import datetime
4
+ from .logging_config import logger
5
+
6
+ def analyze_market_value(data):
7
+ """
8
+ Analyzes the market value of a property based on its specifications and location
9
+ for the Indian real estate market.
10
+ """
11
+ specs_verification = {
12
+ 'is_valid': True,
13
+ 'bedrooms_reasonable': True,
14
+ 'bathrooms_reasonable': True,
15
+ 'total_rooms_reasonable': True,
16
+ 'parking_reasonable': True,
17
+ 'sq_ft_reasonable': True,
18
+ 'market_value_reasonable': True,
19
+ 'year_built_reasonable': True, # Added missing field
20
+ 'issues': []
21
+ }
22
+
23
+ try:
24
+ # Validate property type
25
+ valid_property_types = [
26
+ 'Apartment', 'House', 'Villa', 'Independent House', 'Independent Villa',
27
+ 'Studio', 'Commercial', 'Office', 'Shop', 'Warehouse', 'Industrial'
28
+ ]
29
+
30
+ if 'property_type' not in data or data['property_type'] not in valid_property_types:
31
+ specs_verification['is_valid'] = False
32
+ specs_verification['issues'].append(f"Invalid property type: {data.get('property_type', 'Not specified')}")
33
+
34
+ # Validate bedrooms
35
+ if 'bedrooms' in data:
36
+ try:
37
+ bedrooms = int(data['bedrooms'])
38
+ if data['property_type'] in ['Apartment', 'Studio']:
39
+ if bedrooms > 5 or bedrooms < 0:
40
+ specs_verification['bedrooms_reasonable'] = False
41
+ specs_verification['issues'].append(f"Invalid number of bedrooms for {data['property_type']}: {bedrooms}. Should be between 0 and 5.")
42
+ elif data['property_type'] in ['House', 'Villa', 'Independent House', 'Independent Villa']:
43
+ if bedrooms > 8 or bedrooms < 0:
44
+ specs_verification['bedrooms_reasonable'] = False
45
+ specs_verification['issues'].append(f"Invalid number of bedrooms for {data['property_type']}: {bedrooms}. Should be between 0 and 8.")
46
+ elif data['property_type'] in ['Commercial', 'Office', 'Shop', 'Warehouse', 'Industrial']:
47
+ if bedrooms > 0:
48
+ specs_verification['bedrooms_reasonable'] = False
49
+ specs_verification['issues'].append(f"Commercial properties typically don't have bedrooms: {bedrooms}")
50
+ except ValueError:
51
+ specs_verification['bedrooms_reasonable'] = False
52
+ specs_verification['issues'].append("Invalid bedrooms data: must be a number")
53
+
54
+ # Validate bathrooms
55
+ if 'bathrooms' in data:
56
+ try:
57
+ bathrooms = float(data['bathrooms'])
58
+ if data['property_type'] in ['Apartment', 'Studio']:
59
+ if bathrooms > 4 or bathrooms < 0:
60
+ specs_verification['bathrooms_reasonable'] = False
61
+ specs_verification['issues'].append(f"Invalid number of bathrooms for {data['property_type']}: {bathrooms}. Should be between 0 and 4.")
62
+ elif data['property_type'] in ['House', 'Villa', 'Independent House', 'Independent Villa']:
63
+ if bathrooms > 6 or bathrooms < 0:
64
+ specs_verification['bathrooms_reasonable'] = False
65
+ specs_verification['issues'].append(f"Invalid number of bathrooms for {data['property_type']}: {bathrooms}. Should be between 0 and 6.")
66
+ elif data['property_type'] in ['Commercial', 'Office', 'Shop', 'Warehouse', 'Industrial']:
67
+ if bathrooms > 0:
68
+ specs_verification['bathrooms_reasonable'] = False
69
+ specs_verification['issues'].append(f"Commercial properties typically don't have bathrooms: {bathrooms}")
70
+ except ValueError:
71
+ specs_verification['bathrooms_reasonable'] = False
72
+ specs_verification['issues'].append("Invalid bathrooms data: must be a number")
73
+
74
+ # Validate total rooms
75
+ if 'total_rooms' in data:
76
+ try:
77
+ total_rooms = int(data['total_rooms'])
78
+ if total_rooms < 0:
79
+ specs_verification['total_rooms_reasonable'] = False
80
+ specs_verification['issues'].append(f"Invalid total rooms: {total_rooms}. Cannot be negative.")
81
+ elif 'bedrooms' in data and 'bathrooms' in data:
82
+ try:
83
+ bedrooms = int(data['bedrooms'])
84
+ bathrooms = int(float(data['bathrooms']))
85
+ if total_rooms < (bedrooms + bathrooms):
86
+ specs_verification['total_rooms_reasonable'] = False
87
+ specs_verification['issues'].append(f"Total rooms ({total_rooms}) is less than bedrooms + bathrooms ({bedrooms + bathrooms})")
88
+ except ValueError:
89
+ pass
90
+ except ValueError:
91
+ specs_verification['total_rooms_reasonable'] = False
92
+ specs_verification['issues'].append("Invalid total rooms data: must be a number")
93
+
94
+ # Validate parking
95
+ if 'parking' in data:
96
+ try:
97
+ parking = int(data['parking'])
98
+ if data['property_type'] in ['Apartment', 'Studio']:
99
+ if parking > 2 or parking < 0:
100
+ specs_verification['parking_reasonable'] = False
101
+ specs_verification['issues'].append(f"Invalid parking spaces for {data['property_type']}: {parking}. Should be between 0 and 2.")
102
+ elif data['property_type'] in ['House', 'Villa', 'Independent House', 'Independent Villa']:
103
+ if parking > 4 or parking < 0:
104
+ specs_verification['parking_reasonable'] = False
105
+ specs_verification['issues'].append(f"Invalid parking spaces for {data['property_type']}: {parking}. Should be between 0 and 4.")
106
+ elif data['property_type'] in ['Commercial', 'Office', 'Shop', 'Warehouse', 'Industrial']:
107
+ if parking < 0:
108
+ specs_verification['parking_reasonable'] = False
109
+ specs_verification['issues'].append(f"Invalid parking spaces: {parking}. Cannot be negative.")
110
+ except ValueError:
111
+ specs_verification['parking_reasonable'] = False
112
+ specs_verification['issues'].append("Invalid parking data: must be a number")
113
+
114
+ # Validate square footage
115
+ if 'sq_ft' in data:
116
+ try:
117
+ sq_ft = float(data['sq_ft'].replace(',', ''))
118
+ if sq_ft <= 0:
119
+ specs_verification['sq_ft_reasonable'] = False
120
+ specs_verification['issues'].append(f"Invalid square footage: {sq_ft}. Must be greater than 0.")
121
+ else:
122
+ if data['property_type'] in ['Apartment', 'Studio']:
123
+ if sq_ft > 5000:
124
+ specs_verification['sq_ft_reasonable'] = False
125
+ specs_verification['issues'].append(f"Square footage ({sq_ft}) seems unreasonably high for {data['property_type']}")
126
+ elif sq_ft < 200:
127
+ specs_verification['sq_ft_reasonable'] = False
128
+ specs_verification['issues'].append(f"Square footage ({sq_ft}) seems unreasonably low for {data['property_type']}")
129
+ elif data['property_type'] in ['House', 'Villa', 'Independent House', 'Independent Villa']:
130
+ if sq_ft > 10000:
131
+ specs_verification['sq_ft_reasonable'] = False
132
+ specs_verification['issues'].append(f"Square footage ({sq_ft}) seems unreasonably high for {data['property_type']}")
133
+ elif sq_ft < 500:
134
+ specs_verification['sq_ft_reasonable'] = False
135
+ specs_verification['issues'].append(f"Square footage ({sq_ft}) seems unreasonably low for {data['property_type']}")
136
+ except ValueError:
137
+ specs_verification['sq_ft_reasonable'] = False
138
+ specs_verification['issues'].append("Invalid square footage data: must be a number")
139
+
140
+ # Validate market value
141
+ if 'market_value' in data:
142
+ try:
143
+ market_value = float(data['market_value'].replace(',', '').replace('₹', '').strip())
144
+ if market_value <= 0:
145
+ specs_verification['market_value_reasonable'] = False
146
+ specs_verification['issues'].append(f"Invalid market value: {market_value}. Must be greater than 0.")
147
+ else:
148
+ if data['property_type'] in ['Apartment', 'Studio']:
149
+ if market_value > 500000000: # 5 crore limit for apartments
150
+ specs_verification['market_value_reasonable'] = False
151
+ specs_verification['issues'].append(f"Market value (₹{market_value:,.2f}) seems unreasonably high for {data['property_type']}")
152
+ elif market_value < 500000: # 5 lakh minimum
153
+ specs_verification['market_value_reasonable'] = False
154
+ specs_verification['issues'].append(f"Market value (₹{market_value:,.2f}) seems unreasonably low for {data['property_type']}")
155
+ elif data['property_type'] in ['House', 'Villa', 'Independent House', 'Independent Villa']:
156
+ if market_value > 2000000000: # 20 crore limit for houses
157
+ specs_verification['market_value_reasonable'] = False
158
+ specs_verification['issues'].append(f"Market value (₹{market_value:,.2f}) seems unreasonably high for {data['property_type']}")
159
+ elif market_value < 1000000: # 10 lakh minimum
160
+ specs_verification['market_value_reasonable'] = False
161
+ specs_verification['issues'].append(f"Market value (₹{market_value:,.2f}) seems unreasonably low for {data['property_type']}")
162
+ elif data['property_type'] in ['Commercial', 'Office', 'Shop']:
163
+ if market_value < 2000000: # 20 lakh minimum
164
+ specs_verification['market_value_reasonable'] = False
165
+ specs_verification['issues'].append(f"Market value (₹{market_value:,.2f}) seems unreasonably low for {data['property_type']}")
166
+ elif data['property_type'] in ['Warehouse', 'Industrial']:
167
+ if market_value < 5000000: # 50 lakh minimum
168
+ specs_verification['market_value_reasonable'] = False
169
+ specs_verification['issues'].append(f"Market value (₹{market_value:,.2f}) seems unreasonably low for {data['property_type']}")
170
+
171
+ # Check price per square foot
172
+ if 'sq_ft' in data and float(data['sq_ft'].replace(',', '')) > 0:
173
+ try:
174
+ sq_ft = float(data['sq_ft'].replace(',', ''))
175
+ price_per_sqft = market_value / sq_ft
176
+
177
+ if data['property_type'] in ['Apartment', 'Studio']:
178
+ if price_per_sqft < 1000: # Less than ₹1000 per sq ft
179
+ specs_verification['market_value_reasonable'] = False
180
+ specs_verification['issues'].append(f"Price per sq ft (₹{price_per_sqft:,.2f}) seems unreasonably low for {data['property_type']}")
181
+ elif price_per_sqft > 50000: # More than ₹50k per sq ft
182
+ specs_verification['market_value_reasonable'] = False
183
+ specs_verification['issues'].append(f"Price per sq ft (₹{price_per_sqft:,.2f}) seems unreasonably high for {data['property_type']}")
184
+ elif data['property_type'] in ['House', 'Villa', 'Independent House', 'Independent Villa']:
185
+ if price_per_sqft < 500: # Less than ₹500 per sq ft
186
+ specs_verification['market_value_reasonable'] = False
187
+ specs_verification['issues'].append(f"Price per sq ft (₹{price_per_sqft:,.2f}) seems unreasonably low for {data['property_type']}")
188
+ elif price_per_sqft > 100000: # More than ₹1 lakh per sq ft
189
+ specs_verification['market_value_reasonable'] = False
190
+ specs_verification['issues'].append(f"Price per sq ft (₹{price_per_sqft:,.2f}) seems unreasonably high for {data['property_type']}")
191
+ except ValueError:
192
+ pass
193
+ except ValueError:
194
+ specs_verification['market_value_reasonable'] = False
195
+ specs_verification['issues'].append("Invalid market value data: must be a number")
196
+
197
+ # Calculate verification score
198
+ valid_checks = sum([
199
+ specs_verification['bedrooms_reasonable'],
200
+ specs_verification['bathrooms_reasonable'],
201
+ specs_verification['total_rooms_reasonable'],
202
+ specs_verification['year_built_reasonable'],
203
+ specs_verification['parking_reasonable'],
204
+ specs_verification['sq_ft_reasonable'],
205
+ specs_verification['market_value_reasonable']
206
+ ])
207
+
208
+ total_checks = 7
209
+ specs_verification['verification_score'] = (valid_checks / total_checks) * 100
210
+
211
+ # Overall validity
212
+ specs_verification['is_valid'] = all([
213
+ specs_verification['bedrooms_reasonable'],
214
+ specs_verification['bathrooms_reasonable'],
215
+ specs_verification['total_rooms_reasonable'],
216
+ specs_verification['year_built_reasonable'],
217
+ specs_verification['parking_reasonable'],
218
+ specs_verification['sq_ft_reasonable'],
219
+ specs_verification['market_value_reasonable']
220
+ ])
221
+
222
+ except Exception as e:
223
+ logger.error(f"Error in property specs verification: {str(e)}")
224
+ specs_verification['is_valid'] = False
225
+ specs_verification['issues'].append(f"Error in verification: {str(e)}")
226
+
227
+ return specs_verification
models/model_loader.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/model_loader.py
2
+
3
+ from functools import lru_cache
4
+ from transformers import pipeline
5
+ from .logging_config import logger
6
+
7
+ @lru_cache(maxsize=10)
8
+ def load_model(task, model_name):
9
+ try:
10
+ logger.info(f"Loading model: {model_name} for task: {task}")
11
+ return pipeline(task, model=model_name, device=-1)
12
+ except Exception as e:
13
+ logger.error(f"Error loading model {model_name}: {str(e)}")
14
+ raise
models/pdf_analysis.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/pdf_analysis.py
2
+
3
+ import fitz # PyMuPDF
4
+ import re
5
+ from .model_loader import load_model
6
+ from .logging_config import logger
7
+ from sentence_transformers import SentenceTransformer, util
8
+ from .property_relation import check_if_property_related
9
+ from .utils import summarize_text
10
+
11
+ # Initialize sentence transformer
12
+ try:
13
+ sentence_model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
14
+ logger.info("Sentence transformer loaded successfully in pdf_analysis.py")
15
+ except Exception as e:
16
+ logger.error(f"Error loading sentence transformer in pdf_analysis.py: {str(e)}")
17
+ sentence_model = None
18
+
19
+ def extract_pdf_text(pdf_file):
20
+ try:
21
+ pdf_document = fitz.Document(stream=pdf_file.read(), filetype="pdf")
22
+ text = ""
23
+ for page in pdf_document:
24
+ text += page.get_text()
25
+ pdf_document.close()
26
+ return text
27
+ except Exception as e:
28
+ logger.error(f"Error extracting PDF text: {str(e)}")
29
+ return ""
30
+
31
+ def analyze_pdf_content(document_text, property_data):
32
+ try:
33
+ if not document_text:
34
+ return {
35
+ 'document_type': {'classification': 'unknown', 'confidence': 0.0},
36
+ 'authenticity': {'assessment': 'could not verify', 'confidence': 0.0},
37
+ 'key_info': {},
38
+ 'consistency_score': 0.0,
39
+ 'is_property_related': False,
40
+ 'summary': 'Empty document',
41
+ 'has_signatures': False,
42
+ 'has_dates': False,
43
+ 'verification_score': 0.0
44
+ }
45
+
46
+ # Use a more sophisticated model for document classification
47
+ classifier = load_model("zero-shot-classification", "typeform/mobilebert-uncased-mnli")
48
+
49
+ # Enhanced document types with more specific categories
50
+ doc_types = [
51
+ "property deed", "sales agreement", "mortgage document",
52
+ "property tax record", "title document", "khata certificate",
53
+ "encumbrance certificate", "lease agreement", "rental agreement",
54
+ "property registration document", "building permit", "other document"
55
+ ]
56
+
57
+ # Analyze document type with context
58
+ doc_context = f"{document_text[:1000]} property_type:{property_data.get('property_type', '')} location:{property_data.get('city', '')}"
59
+ doc_result = classifier(doc_context, doc_types)
60
+ doc_type = doc_result['labels'][0]
61
+ doc_confidence = doc_result['scores'][0]
62
+
63
+ # Enhanced authenticity check with multiple aspects
64
+ authenticity_aspects = [
65
+ "authentic legal document",
66
+ "questionable document",
67
+ "forged document",
68
+ "template document",
69
+ "official document"
70
+ ]
71
+ authenticity_result = classifier(document_text[:1000], authenticity_aspects)
72
+ authenticity = "likely authentic" if authenticity_result['labels'][0] == "authentic legal document" else "questionable"
73
+ authenticity_confidence = authenticity_result['scores'][0]
74
+
75
+ # Extract key information using NLP
76
+ key_info = extract_document_key_info(document_text)
77
+
78
+ # Enhanced consistency check
79
+ consistency_score = check_document_consistency(document_text, property_data)
80
+
81
+ # Property relation check with context
82
+ property_context = f"{document_text[:1000]} property:{property_data.get('property_name', '')} type:{property_data.get('property_type', '')}"
83
+ is_property_related = check_if_property_related(property_context)['is_related']
84
+
85
+ # Generate summary using BART
86
+ summary = summarize_text(document_text[:2000])
87
+
88
+ # Enhanced signature and date detection
89
+ has_signatures = bool(re.search(r'(?:sign|signature|signed|witness|notary|authorized).{0,50}(?:by|of|for)', document_text.lower()))
90
+ has_dates = bool(re.search(r'\d{1,2}[/-]\d{1,2}[/-]\d{2,4}|\d{4}[/-]\d{1,2}[/-]\d{1,2}', document_text))
91
+
92
+ # Calculate verification score with weighted components
93
+ verification_weights = {
94
+ 'doc_type': 0.3,
95
+ 'authenticity': 0.3,
96
+ 'consistency': 0.2,
97
+ 'property_relation': 0.1,
98
+ 'signatures_dates': 0.1
99
+ }
100
+
101
+ verification_score = (
102
+ doc_confidence * verification_weights['doc_type'] +
103
+ authenticity_confidence * verification_weights['authenticity'] +
104
+ consistency_score * verification_weights['consistency'] +
105
+ float(is_property_related) * verification_weights['property_relation'] +
106
+ float(has_signatures and has_dates) * verification_weights['signatures_dates']
107
+ )
108
+
109
+ return {
110
+ 'document_type': {'classification': doc_type, 'confidence': float(doc_confidence)},
111
+ 'authenticity': {'assessment': authenticity, 'confidence': float(authenticity_confidence)},
112
+ 'key_info': key_info,
113
+ 'consistency_score': float(consistency_score),
114
+ 'is_property_related': is_property_related,
115
+ 'summary': summary,
116
+ 'has_signatures': has_signatures,
117
+ 'has_dates': has_dates,
118
+ 'verification_score': float(verification_score)
119
+ }
120
+ except Exception as e:
121
+ logger.error(f"Error analyzing PDF content: {str(e)}")
122
+ return {
123
+ 'document_type': {'classification': 'unknown', 'confidence': 0.0},
124
+ 'authenticity': {'assessment': 'could not verify', 'confidence': 0.0},
125
+ 'key_info': {},
126
+ 'consistency_score': 0.0,
127
+ 'is_property_related': False,
128
+ 'summary': 'Could not analyze document',
129
+ 'has_signatures': False,
130
+ 'has_dates': False,
131
+ 'verification_score': 0.0,
132
+ 'error': str(e)
133
+ }
134
+
135
+ def check_document_consistency(document_text, property_data):
136
+ try:
137
+ if not sentence_model:
138
+ logger.warning("Sentence model unavailable")
139
+ return 0.5
140
+ property_text = ' '.join([
141
+ property_data.get(key, '') for key in [
142
+ 'property_name', 'property_type', 'address', 'city',
143
+ 'state', 'market_value', 'sq_ft', 'bedrooms'
144
+ ]
145
+ ])
146
+ property_embedding = sentence_model.encode(property_text)
147
+ document_embedding = sentence_model.encode(document_text[:1000])
148
+ similarity = util.cos_sim(property_embedding, document_embedding)[0][0].item()
149
+ return max(0.0, min(1.0, float(similarity)))
150
+ except Exception as e:
151
+ logger.error(f"Error checking document consistency: {str(e)}")
152
+ return 0.0
153
+
154
+ def extract_document_key_info(text):
155
+ try:
156
+ info = {}
157
+ patterns = {
158
+ 'property_address': r'(?:property|premises|located at)[:\s]+([^\n.]+)',
159
+ 'price': r'(?:price|value|amount)[:\s]+(?:Rs\.?|₹)?[\s]*([0-9,.]+)',
160
+ 'date': r'(?:date|dated|executed on)[:\s]+([^\n.]+\d{4})',
161
+ 'seller': r'(?:seller|grantor|owner)[:\s]+([^\n.]+)',
162
+ 'buyer': r'(?:buyer|grantee|purchaser)[:\s]+([^\n.]+)',
163
+ 'size': r'(?:area|size|extent)[:\s]+([0-9,.]+)[\s]*(?:sq\.?[\s]*(?:ft|feet))',
164
+ 'registration_number': r'(?:registration|reg\.?|document)[\s]*(?:no\.?|number|#)[:\s]*([A-Za-z0-9\-/]+)'
165
+ }
166
+ for key, pattern in patterns.items():
167
+ match = re.search(pattern, text, re.IGNORECASE)
168
+ if match:
169
+ info[key] = match.group(1).strip()
170
+ return info
171
+ except Exception as e:
172
+ logger.error(f"Error extracting document key info: {str(e)}")
173
+ return {}
models/price_analysis.py ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/price_analysis.py
2
+
3
+ import re
4
+ from .model_loader import load_model
5
+ from .logging_config import logger
6
+
7
+ def analyze_price(data):
8
+ try:
9
+ # Safely convert price to float
10
+ price_str = str(data.get('market_value', '0')).replace('$', '').replace(',', '').strip()
11
+ price = float(price_str) if price_str else 0
12
+
13
+ # Safely convert sq_ft to float
14
+ sq_ft_str = str(data.get('sq_ft', '0')).replace(',', '').strip()
15
+ sq_ft = float(re.sub(r'[^\d.]', '', sq_ft_str)) if sq_ft_str else 0
16
+
17
+ price_per_sqft = price / sq_ft if sq_ft else 0
18
+
19
+ if not price:
20
+ return {
21
+ 'assessment': 'no price',
22
+ 'confidence': 0.0,
23
+ 'price': 0,
24
+ 'formatted_price': '₹0',
25
+ 'price_per_sqft': 0,
26
+ 'formatted_price_per_sqft': '₹0',
27
+ 'price_range': 'unknown',
28
+ 'location_price_assessment': 'cannot assess',
29
+ 'has_price': False,
30
+ 'market_trends': {},
31
+ 'price_factors': {},
32
+ 'risk_indicators': []
33
+ }
34
+
35
+ # Use a more sophisticated model for price analysis
36
+ classifier = load_model("zero-shot-classification", "typeform/mobilebert-uncased-mnli")
37
+
38
+ # Create a detailed context for price analysis
39
+ price_context = f"""
40
+ Property Type: {data.get('property_type', '')}
41
+ Location: {data.get('city', '')}, {data.get('state', '')}
42
+ Size: {sq_ft} sq.ft.
43
+ Price: ₹{price:,.2f}
44
+ Price per sq.ft.: ₹{price_per_sqft:,.2f}
45
+ Property Status: {data.get('status', '')}
46
+ Year Built: {data.get('year_built', '')}
47
+ Bedrooms: {data.get('bedrooms', '')}
48
+ Bathrooms: {data.get('bathrooms', '')}
49
+ Amenities: {data.get('amenities', '')}
50
+ """
51
+
52
+ # Enhanced price categories with more specific indicators
53
+ price_categories = [
54
+ "reasonable market price",
55
+ "suspiciously low price",
56
+ "suspiciously high price",
57
+ "average market price",
58
+ "luxury property price",
59
+ "budget property price",
60
+ "premium property price",
61
+ "mid-range property price",
62
+ "overpriced for location",
63
+ "underpriced for location",
64
+ "price matches amenities",
65
+ "price matches property age",
66
+ "price matches location value",
67
+ "price matches property condition",
68
+ "price matches market trends"
69
+ ]
70
+
71
+ # Analyze price with multiple aspects
72
+ price_result = classifier(price_context, price_categories, multi_label=True)
73
+
74
+ # Get top classifications with enhanced confidence calculation
75
+ top_classifications = []
76
+ for label, score in zip(price_result['labels'][:5], price_result['scores'][:5]):
77
+ if score > 0.25: # Lower threshold for better sensitivity
78
+ top_classifications.append({
79
+ 'classification': label,
80
+ 'confidence': float(score)
81
+ })
82
+
83
+ # Determine price range based on AI classification and market data
84
+ price_range = 'unknown'
85
+ if top_classifications:
86
+ primary_class = top_classifications[0]['classification']
87
+ if 'luxury' in primary_class:
88
+ price_range = 'luxury'
89
+ elif 'premium' in primary_class:
90
+ price_range = 'premium'
91
+ elif 'mid-range' in primary_class:
92
+ price_range = 'mid_range'
93
+ elif 'budget' in primary_class:
94
+ price_range = 'budget'
95
+
96
+ # Enhanced location-specific price assessment
97
+ location_assessment = "unknown"
98
+ market_trends = {}
99
+ if data.get('city') and price_per_sqft:
100
+ city_lower = data['city'].lower()
101
+ metro_cities = ["mumbai", "delhi", "bangalore", "hyderabad", "chennai", "kolkata", "pune"]
102
+
103
+ # Define price ranges for different city tiers
104
+ if any(city in city_lower for city in metro_cities):
105
+ market_trends = {
106
+ 'city_tier': 'metro',
107
+ 'avg_price_range': {
108
+ 'min': 5000,
109
+ 'max': 30000,
110
+ 'trend': 'stable'
111
+ },
112
+ 'price_per_sqft': {
113
+ 'current': price_per_sqft,
114
+ 'market_avg': 15000,
115
+ 'deviation': abs(price_per_sqft - 15000) / 15000 * 100
116
+ }
117
+ }
118
+ location_assessment = (
119
+ "reasonable" if 5000 <= price_per_sqft <= 30000 else
120
+ "suspiciously low" if price_per_sqft < 5000 else
121
+ "suspiciously high"
122
+ )
123
+ else:
124
+ market_trends = {
125
+ 'city_tier': 'non-metro',
126
+ 'avg_price_range': {
127
+ 'min': 1500,
128
+ 'max': 15000,
129
+ 'trend': 'stable'
130
+ },
131
+ 'price_per_sqft': {
132
+ 'current': price_per_sqft,
133
+ 'market_avg': 7500,
134
+ 'deviation': abs(price_per_sqft - 7500) / 7500 * 100
135
+ }
136
+ }
137
+ location_assessment = (
138
+ "reasonable" if 1500 <= price_per_sqft <= 15000 else
139
+ "suspiciously low" if price_per_sqft < 1500 else
140
+ "suspiciously high"
141
+ )
142
+
143
+ # Enhanced price analysis factors
144
+ price_factors = {}
145
+ risk_indicators = []
146
+
147
+ # Property age factor
148
+ try:
149
+ year_built = int(data.get('year_built', 0))
150
+ current_year = datetime.now().year
151
+ property_age = current_year - year_built
152
+
153
+ if property_age > 0:
154
+ depreciation_factor = max(0.5, 1 - (property_age * 0.01)) # 1% depreciation per year, min 50%
155
+ price_factors['age_factor'] = {
156
+ 'property_age': property_age,
157
+ 'depreciation_factor': depreciation_factor,
158
+ 'impact': 'high' if property_age > 30 else 'medium' if property_age > 15 else 'low'
159
+ }
160
+ except:
161
+ price_factors['age_factor'] = {'error': 'Invalid year built'}
162
+
163
+ # Size factor
164
+ if sq_ft > 0:
165
+ size_factor = {
166
+ 'size': sq_ft,
167
+ 'price_per_sqft': price_per_sqft,
168
+ 'efficiency': 'high' if 800 <= sq_ft <= 2000 else 'medium' if 500 <= sq_ft <= 3000 else 'low'
169
+ }
170
+ price_factors['size_factor'] = size_factor
171
+
172
+ # Add risk indicators based on size
173
+ if sq_ft < 300:
174
+ risk_indicators.append('Unusually small property size')
175
+ elif sq_ft > 10000:
176
+ risk_indicators.append('Unusually large property size')
177
+
178
+ # Amenities factor
179
+ if data.get('amenities'):
180
+ amenities_list = [a.strip() for a in data['amenities'].split(',')]
181
+ amenities_score = min(1.0, len(amenities_list) * 0.1) # 10% per amenity, max 100%
182
+ price_factors['amenities_factor'] = {
183
+ 'count': len(amenities_list),
184
+ 'score': amenities_score,
185
+ 'impact': 'high' if amenities_score > 0.7 else 'medium' if amenities_score > 0.4 else 'low'
186
+ }
187
+
188
+ # Calculate overall confidence with weighted factors
189
+ confidence_weights = {
190
+ 'primary_classification': 0.3,
191
+ 'location_assessment': 0.25,
192
+ 'age_factor': 0.2,
193
+ 'size_factor': 0.15,
194
+ 'amenities_factor': 0.1
195
+ }
196
+ confidence_scores = []
197
+
198
+ # Primary classification confidence
199
+ if top_classifications:
200
+ confidence_scores.append(price_result['scores'][0] * confidence_weights['primary_classification'])
201
+
202
+ # Location assessment confidence
203
+ location_confidence = 0.8 if location_assessment == "reasonable" else 0.4
204
+ confidence_scores.append(location_confidence * confidence_weights['location_assessment'])
205
+
206
+ # Age factor confidence
207
+ if 'age_factor' in price_factors and 'depreciation_factor' in price_factors['age_factor']:
208
+ age_confidence = price_factors['age_factor']['depreciation_factor']
209
+ confidence_scores.append(age_confidence * confidence_weights['age_factor'])
210
+
211
+ # Size factor confidence
212
+ if 'size_factor' in price_factors:
213
+ size_confidence = 0.8 if price_factors['size_factor']['efficiency'] == 'high' else 0.6
214
+ confidence_scores.append(size_confidence * confidence_weights['size_factor'])
215
+
216
+ # Amenities factor confidence
217
+ if 'amenities_factor' in price_factors:
218
+ amenities_confidence = price_factors['amenities_factor']['score']
219
+ confidence_scores.append(amenities_confidence * confidence_weights['amenities_factor'])
220
+
221
+ overall_confidence = sum(confidence_scores) / sum(confidence_weights.values())
222
+
223
+ return {
224
+ 'assessment': top_classifications[0]['classification'] if top_classifications else 'could not classify',
225
+ 'confidence': float(overall_confidence),
226
+ 'price': price,
227
+ 'formatted_price': f"₹{price:,.0f}",
228
+ 'price_per_sqft': price_per_sqft,
229
+ 'formatted_price_per_sqft': f"₹{price_per_sqft:,.2f}",
230
+ 'price_range': price_range,
231
+ 'location_price_assessment': location_assessment,
232
+ 'has_price': True,
233
+ 'market_trends': market_trends,
234
+ 'price_factors': price_factors,
235
+ 'risk_indicators': risk_indicators,
236
+ 'top_classifications': top_classifications
237
+ }
238
+ except Exception as e:
239
+ logger.error(f"Error analyzing price: {str(e)}")
240
+ return {
241
+ 'assessment': 'error',
242
+ 'confidence': 0.0,
243
+ 'price': 0,
244
+ 'formatted_price': '₹0',
245
+ 'price_per_sqft': 0,
246
+ 'formatted_price_per_sqft': '₹0',
247
+ 'price_range': 'unknown',
248
+ 'location_price_assessment': 'error',
249
+ 'has_price': False,
250
+ 'market_trends': {},
251
+ 'price_factors': {},
252
+ 'risk_indicators': [],
253
+ 'top_classifications': []
254
+ }
models/property_relation.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/property_relation.py
2
+
3
+ from .model_loader import load_model
4
+ from .logging_config import logger
5
+
6
+ def check_if_property_related(text):
7
+ try:
8
+ classifier = load_model("zero-shot-classification", "typeform/mobilebert-uncased-mnli")
9
+ result = classifier(text[:1000], ["property-related", "non-property-related"])
10
+ is_related = result['labels'][0] == "property-related"
11
+ return {
12
+ 'is_related': is_related,
13
+ 'confidence': float(result['scores'][0])
14
+ }
15
+ except Exception as e:
16
+ logger.error(f"Error checking property relation: {str(e)}")
17
+ return {
18
+ 'is_related': False,
19
+ 'confidence': 0.0
20
+ }
models/property_specs.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/property_specs.py
2
+
3
+ from datetime import datetime
4
+ from .logging_config import logger
5
+
6
+ def verify_property_specs(data):
7
+ """
8
+ Verify property specifications for reasonableness and consistency.
9
+ This function checks if the provided property details are within reasonable ranges
10
+ for the Indian real estate market.
11
+ """
12
+ specs_verification = {
13
+ 'is_valid': True,
14
+ 'bedrooms_reasonable': True,
15
+ 'bathrooms_reasonable': True,
16
+ 'total_rooms_reasonable': True,
17
+ 'year_built_reasonable': True,
18
+ 'parking_reasonable': True,
19
+ 'sq_ft_reasonable': True,
20
+ 'market_value_reasonable': True,
21
+ 'issues': []
22
+ }
23
+
24
+ try:
25
+ # Helper function to safely convert values
26
+ def safe_float_convert(value, default=0.0):
27
+ try:
28
+ if isinstance(value, (int, float)):
29
+ return float(value)
30
+ if isinstance(value, str):
31
+ return float(value.replace(',', '').replace('₹', '').strip())
32
+ return default
33
+ except (ValueError, TypeError):
34
+ return default
35
+
36
+ def safe_int_convert(value, default=0):
37
+ try:
38
+ if isinstance(value, (int, float)):
39
+ return int(value)
40
+ if isinstance(value, str):
41
+ return int(float(value.replace(',', '').strip()))
42
+ return default
43
+ except (ValueError, TypeError):
44
+ return default
45
+
46
+ # Validate property type
47
+ valid_property_types = [
48
+ 'Apartment', 'House', 'Villa', 'Independent House', 'Independent Villa',
49
+ 'Studio', 'Commercial', 'Office', 'Shop', 'Warehouse', 'Industrial'
50
+ ]
51
+
52
+ if 'property_type' not in data or data['property_type'] not in valid_property_types:
53
+ specs_verification['is_valid'] = False
54
+ specs_verification['issues'].append(f"Invalid property type: {data.get('property_type', 'Not specified')}")
55
+
56
+ # Validate bedrooms
57
+ if 'bedrooms' in data:
58
+ bedrooms = safe_int_convert(data['bedrooms'])
59
+ if data['property_type'] in ['Apartment', 'Studio']:
60
+ if bedrooms > 5 or bedrooms < 0:
61
+ specs_verification['bedrooms_reasonable'] = False
62
+ specs_verification['issues'].append(f"Invalid number of bedrooms for {data['property_type']}: {bedrooms}. Should be between 0 and 5.")
63
+ elif data['property_type'] in ['House', 'Villa', 'Independent House', 'Independent Villa']:
64
+ if bedrooms > 8 or bedrooms < 0:
65
+ specs_verification['bedrooms_reasonable'] = False
66
+ specs_verification['issues'].append(f"Invalid number of bedrooms for {data['property_type']}: {bedrooms}. Should be between 0 and 8.")
67
+ elif data['property_type'] in ['Commercial', 'Office', 'Shop', 'Warehouse', 'Industrial']:
68
+ if bedrooms > 0:
69
+ specs_verification['bedrooms_reasonable'] = False
70
+ specs_verification['issues'].append(f"Commercial properties typically don't have bedrooms: {bedrooms}")
71
+
72
+ # Validate bathrooms
73
+ if 'bathrooms' in data:
74
+ bathrooms = safe_float_convert(data['bathrooms'])
75
+ if data['property_type'] in ['Apartment', 'Studio']:
76
+ if bathrooms > 4 or bathrooms < 0:
77
+ specs_verification['bathrooms_reasonable'] = False
78
+ specs_verification['issues'].append(f"Invalid number of bathrooms for {data['property_type']}: {bathrooms}. Should be between 0 and 4.")
79
+ elif data['property_type'] in ['House', 'Villa', 'Independent House', 'Independent Villa']:
80
+ if bathrooms > 6 or bathrooms < 0:
81
+ specs_verification['bathrooms_reasonable'] = False
82
+ specs_verification['issues'].append(f"Invalid number of bathrooms for {data['property_type']}: {bathrooms}. Should be between 0 and 6.")
83
+ elif data['property_type'] in ['Commercial', 'Office', 'Shop', 'Warehouse', 'Industrial']:
84
+ if bathrooms > 0:
85
+ specs_verification['bathrooms_reasonable'] = False
86
+ specs_verification['issues'].append(f"Commercial properties typically don't have bathrooms: {bathrooms}")
87
+
88
+ # Validate total rooms
89
+ if 'total_rooms' in data:
90
+ try:
91
+ total_rooms = int(data['total_rooms'])
92
+ if total_rooms < 0:
93
+ specs_verification['total_rooms_reasonable'] = False
94
+ specs_verification['issues'].append(f"Invalid total rooms: {total_rooms}. Cannot be negative.")
95
+ elif 'bedrooms' in data and 'bathrooms' in data:
96
+ try:
97
+ bedrooms = int(data['bedrooms'])
98
+ bathrooms = int(float(data['bathrooms']))
99
+ if total_rooms < (bedrooms + bathrooms):
100
+ specs_verification['total_rooms_reasonable'] = False
101
+ specs_verification['issues'].append(f"Total rooms ({total_rooms}) is less than bedrooms + bathrooms ({bedrooms + bathrooms})")
102
+ except ValueError:
103
+ pass
104
+ except ValueError:
105
+ specs_verification['total_rooms_reasonable'] = False
106
+ specs_verification['issues'].append("Invalid total rooms data: must be a number")
107
+
108
+ # Validate parking
109
+ if 'parking' in data:
110
+ try:
111
+ parking = int(data['parking'])
112
+ if data['property_type'] in ['Apartment', 'Studio']:
113
+ if parking > 2 or parking < 0:
114
+ specs_verification['parking_reasonable'] = False
115
+ specs_verification['issues'].append(f"Invalid parking spaces for {data['property_type']}: {parking}. Should be between 0 and 2.")
116
+ elif data['property_type'] in ['House', 'Villa', 'Independent House', 'Independent Villa']:
117
+ if parking > 4 or parking < 0:
118
+ specs_verification['parking_reasonable'] = False
119
+ specs_verification['issues'].append(f"Invalid parking spaces for {data['property_type']}: {parking}. Should be between 0 and 4.")
120
+ elif data['property_type'] in ['Commercial', 'Office', 'Shop', 'Warehouse', 'Industrial']:
121
+ if parking < 0:
122
+ specs_verification['parking_reasonable'] = False
123
+ specs_verification['issues'].append(f"Invalid parking spaces: {parking}. Cannot be negative.")
124
+ except ValueError:
125
+ specs_verification['parking_reasonable'] = False
126
+ specs_verification['issues'].append("Invalid parking data: must be a number")
127
+
128
+ # Validate square footage
129
+ if 'sq_ft' in data:
130
+ try:
131
+ sq_ft = float(data['sq_ft'].replace(',', ''))
132
+ if sq_ft <= 0:
133
+ specs_verification['sq_ft_reasonable'] = False
134
+ specs_verification['issues'].append(f"Invalid square footage: {sq_ft}. Must be greater than 0.")
135
+ else:
136
+ if data['property_type'] in ['Apartment', 'Studio']:
137
+ if sq_ft > 5000:
138
+ specs_verification['sq_ft_reasonable'] = False
139
+ specs_verification['issues'].append(f"Square footage ({sq_ft}) seems unreasonably high for {data['property_type']}")
140
+ elif sq_ft < 200:
141
+ specs_verification['sq_ft_reasonable'] = False
142
+ specs_verification['issues'].append(f"Square footage ({sq_ft}) seems unreasonably low for {data['property_type']}")
143
+ elif data['property_type'] in ['House', 'Villa', 'Independent House', 'Independent Villa']:
144
+ if sq_ft > 10000:
145
+ specs_verification['sq_ft_reasonable'] = False
146
+ specs_verification['issues'].append(f"Square footage ({sq_ft}) seems unreasonably high for {data['property_type']}")
147
+ elif sq_ft < 500:
148
+ specs_verification['sq_ft_reasonable'] = False
149
+ specs_verification['issues'].append(f"Square footage ({sq_ft}) seems unreasonably low for {data['property_type']}")
150
+ except ValueError:
151
+ specs_verification['sq_ft_reasonable'] = False
152
+ specs_verification['issues'].append("Invalid square footage data: must be a number")
153
+
154
+ # Validate market value
155
+ if 'market_value' in data:
156
+ try:
157
+ market_value = float(data['market_value'].replace(',', '').replace('₹', '').strip())
158
+ if market_value <= 0:
159
+ specs_verification['market_value_reasonable'] = False
160
+ specs_verification['issues'].append(f"Invalid market value: {market_value}. Must be greater than 0.")
161
+ else:
162
+ if data['property_type'] in ['Apartment', 'Studio']:
163
+ if market_value > 500000000: # 5 crore limit for apartments
164
+ specs_verification['market_value_reasonable'] = False
165
+ specs_verification['issues'].append(f"Market value (₹{market_value:,.2f}) seems unreasonably high for {data['property_type']}")
166
+ elif market_value < 500000: # 5 lakh minimum
167
+ specs_verification['market_value_reasonable'] = False
168
+ specs_verification['issues'].append(f"Market value (₹{market_value:,.2f}) seems unreasonably low for {data['property_type']}")
169
+ elif data['property_type'] in ['House', 'Villa', 'Independent House', 'Independent Villa']:
170
+ if market_value > 2000000000: # 20 crore limit for houses
171
+ specs_verification['market_value_reasonable'] = False
172
+ specs_verification['issues'].append(f"Market value (₹{market_value:,.2f}) seems unreasonably high for {data['property_type']}")
173
+ elif market_value < 1000000: # 10 lakh minimum
174
+ specs_verification['market_value_reasonable'] = False
175
+ specs_verification['issues'].append(f"Market value (₹{market_value:,.2f}) seems unreasonably low for {data['property_type']}")
176
+ elif data['property_type'] in ['Commercial', 'Office', 'Shop']:
177
+ if market_value < 2000000: # 20 lakh minimum
178
+ specs_verification['market_value_reasonable'] = False
179
+ specs_verification['issues'].append(f"Market value (₹{market_value:,.2f}) seems unreasonably low for {data['property_type']}")
180
+ elif data['property_type'] in ['Warehouse', 'Industrial']:
181
+ if market_value < 5000000: # 50 lakh minimum
182
+ specs_verification['market_value_reasonable'] = False
183
+ specs_verification['issues'].append(f"Market value (₹{market_value:,.2f}) seems unreasonably low for {data['property_type']}")
184
+
185
+ # Check price per square foot
186
+ if 'sq_ft' in data and float(data['sq_ft'].replace(',', '')) > 0:
187
+ try:
188
+ sq_ft = float(data['sq_ft'].replace(',', ''))
189
+ price_per_sqft = market_value / sq_ft
190
+
191
+ if data['property_type'] in ['Apartment', 'Studio']:
192
+ if price_per_sqft < 1000: # Less than ₹1000 per sq ft
193
+ specs_verification['market_value_reasonable'] = False
194
+ specs_verification['issues'].append(f"Price per sq ft (₹{price_per_sqft:,.2f}) seems unreasonably low for {data['property_type']}")
195
+ elif price_per_sqft > 50000: # More than ₹50k per sq ft
196
+ specs_verification['market_value_reasonable'] = False
197
+ specs_verification['issues'].append(f"Price per sq ft (₹{price_per_sqft:,.2f}) seems unreasonably high for {data['property_type']}")
198
+ elif data['property_type'] in ['House', 'Villa', 'Independent House', 'Independent Villa']:
199
+ if price_per_sqft < 500: # Less than ₹500 per sq ft
200
+ specs_verification['market_value_reasonable'] = False
201
+ specs_verification['issues'].append(f"Price per sq ft (₹{price_per_sqft:,.2f}) seems unreasonably low for {data['property_type']}")
202
+ elif price_per_sqft > 100000: # More than ₹1 lakh per sq ft
203
+ specs_verification['market_value_reasonable'] = False
204
+ specs_verification['issues'].append(f"Price per sq ft (₹{price_per_sqft:,.2f}) seems unreasonably high for {data['property_type']}")
205
+ except ValueError:
206
+ pass
207
+ except ValueError:
208
+ specs_verification['market_value_reasonable'] = False
209
+ specs_verification['issues'].append("Invalid market value data: must be a number")
210
+
211
+ # Calculate verification score
212
+ valid_checks = sum([
213
+ specs_verification['bedrooms_reasonable'],
214
+ specs_verification['bathrooms_reasonable'],
215
+ specs_verification['total_rooms_reasonable'],
216
+ specs_verification['year_built_reasonable'],
217
+ specs_verification['parking_reasonable'],
218
+ specs_verification['sq_ft_reasonable'],
219
+ specs_verification['market_value_reasonable']
220
+ ])
221
+
222
+ total_checks = 7
223
+ specs_verification['verification_score'] = (valid_checks / total_checks) * 100
224
+
225
+ # Overall validity
226
+ specs_verification['is_valid'] = all([
227
+ specs_verification['bedrooms_reasonable'],
228
+ specs_verification['bathrooms_reasonable'],
229
+ specs_verification['total_rooms_reasonable'],
230
+ specs_verification['year_built_reasonable'],
231
+ specs_verification['parking_reasonable'],
232
+ specs_verification['sq_ft_reasonable'],
233
+ specs_verification['market_value_reasonable']
234
+ ])
235
+
236
+ except Exception as e:
237
+ logger.error(f"Error in property specs verification: {str(e)}")
238
+ specs_verification['is_valid'] = False
239
+ specs_verification['issues'].append(f"Error in verification: {str(e)}")
240
+
241
+ return specs_verification
models/property_summary.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/property_summary.py
2
+
3
+ from .model_loader import load_model
4
+ from .logging_config import logger
5
+ from .utils import summarize_text
6
+
7
+ def validate_and_format_data(data):
8
+ """Validate and format property data"""
9
+ # Format square feet
10
+ try:
11
+ sq_ft = float(data.get('sq_ft', 0))
12
+ if sq_ft < 100: # If square feet seems too small, it might be in wrong unit
13
+ sq_ft *= 100 # Convert to square feet if it was in square meters
14
+ data['sq_ft'] = int(sq_ft)
15
+ except:
16
+ data['sq_ft'] = 0
17
+
18
+ # Format market value
19
+ try:
20
+ market_value = float(data.get('market_value', 0))
21
+ if market_value > 1000000000: # If value seems too high
22
+ market_value = market_value / 100 # Adjust if there's a decimal point issue
23
+ data['market_value'] = int(market_value)
24
+ except:
25
+ data['market_value'] = 0
26
+
27
+ # Format amenities
28
+ if data.get('amenities'):
29
+ if isinstance(data['amenities'], str):
30
+ amenities = [a.strip() for a in data['amenities'].split(',') if a.strip()]
31
+ data['amenities'] = amenities
32
+ elif isinstance(data['amenities'], list):
33
+ data['amenities'] = [a.strip() for a in data['amenities'] if a.strip()]
34
+ else:
35
+ data['amenities'] = []
36
+
37
+ return data
38
+
39
+ def format_price(price):
40
+ """Format price in Indian currency format"""
41
+ try:
42
+ price = float(price)
43
+ if price >= 10000000: # 1 Crore
44
+ return f"₹{price/10000000:.2f} Cr"
45
+ elif price >= 100000: # 1 Lakh
46
+ return f"₹{price/100000:.2f} L"
47
+ else:
48
+ return f"₹{price:,.2f}"
49
+ except:
50
+ return f"₹{price}"
51
+
52
+ def generate_static_summary(data):
53
+ """Generate a conversational property summary"""
54
+ # Validate and format data
55
+ data = validate_and_format_data(data)
56
+ price = format_price(data.get('market_value', '0'))
57
+
58
+ # Get property type with proper formatting
59
+ property_type = data.get('property_type', 'property').strip()
60
+ if property_type.lower() == 'apartment':
61
+ property_type = '2 BHK Apartment' # Add BHK information if available
62
+
63
+ summary = f"""
64
+ Namaste! Let me tell you about this wonderful {property_type} that's {data.get('status', 'available').lower()}.
65
+
66
+ This beautiful property is located in {data.get('city', 'the city')}, {data.get('state', '')}. It's a spacious {property_type} spanning {data.get('sq_ft', '0')} square feet, perfect for your family.
67
+
68
+ Key Highlights:
69
+ • Price: {price}
70
+ • Bedrooms: {data.get('bedrooms', '0')} spacious bedrooms
71
+ • Bathrooms: {data.get('bathrooms', '0')} modern bathrooms
72
+ • Year Built: {data.get('year_built', 'N/A')}
73
+ • Parking: {data.get('parking_spaces', '0')} covered parking spaces
74
+ """
75
+
76
+ # Add property description if available
77
+ if data.get('property_description'):
78
+ summary += f"\n\nProperty Description:\n{data.get('property_description')}"
79
+
80
+ # Add possession date if available
81
+ if data.get('possession_date'):
82
+ summary += f"\n• Ready for possession from: {data.get('possession_date')}"
83
+
84
+ # Add amenities if available
85
+ if data.get('amenities'):
86
+ amenities = data['amenities']
87
+ if len(amenities) > 0:
88
+ summary += "\n\nNearby Amenities:\n• " + "\n• ".join(amenities)
89
+
90
+ # Add nearby landmarks if available
91
+ if data.get('nearby_landmarks'):
92
+ landmarks = data['nearby_landmarks']
93
+ if isinstance(landmarks, str):
94
+ landmarks = [l.strip() for l in landmarks.split(',') if l.strip()]
95
+ if len(landmarks) > 0:
96
+ summary += "\n\nNearby Landmarks:\n• " + "\n• ".join(landmarks)
97
+
98
+ # Add a friendly closing
99
+ summary += "\n\nThis property offers excellent value for money and is located in a prime area. Would you like to know more details about this property?"
100
+
101
+ return summary.strip()
102
+
103
+ def generate_property_summary(data):
104
+ try:
105
+ # Validate and format data first
106
+ data = validate_and_format_data(data)
107
+
108
+ # Create a detailed context for summary generation
109
+ property_context = f"""
110
+ Property Details:
111
+ Name: {data.get('property_name', '')}
112
+ Type: {data.get('property_type', '')}
113
+ Status: {data.get('status', '')}
114
+ Location: {data.get('address', '')}, {data.get('city', '')}, {data.get('state', '')}, {data.get('country', '')}
115
+ Size: {data.get('sq_ft', '')} sq. ft.
116
+ Price: {format_price(data.get('market_value', '0'))}
117
+ Bedrooms: {data.get('bedrooms', '')}
118
+ Bathrooms: {data.get('bathrooms', '')}
119
+ Year Built: {data.get('year_built', '')}
120
+ Parking: {data.get('parking_spaces', '')} spaces
121
+ Description: {data.get('property_description', '')}
122
+ Possession Date: {data.get('possession_date', '')}
123
+ Amenities: {', '.join(data.get('amenities', []))}
124
+ Nearby Landmarks: {data.get('nearby_landmarks', '')}
125
+ """
126
+
127
+ # Try to use BART for summary generation
128
+ try:
129
+ summarizer = load_model("summarization", "sshleifer/distilbart-cnn-6-6")
130
+ summary_result = summarizer(property_context, max_length=500, min_length=100, do_sample=False)
131
+ initial_summary = summary_result[0]['summary_text']
132
+ except Exception as model_error:
133
+ logger.warning(f"Model generation failed, using static summary: {str(model_error)}")
134
+ initial_summary = generate_static_summary(data)
135
+
136
+ # Enhance summary with key features
137
+ key_features = []
138
+
139
+ # Add property type and status
140
+ if data.get('property_type') and data.get('status'):
141
+ key_features.append(f"This {data['property_type']} is {data['status'].lower()}")
142
+
143
+ # Add location if available
144
+ location_parts = []
145
+ if data.get('city'):
146
+ location_parts.append(data['city'])
147
+ if data.get('state'):
148
+ location_parts.append(data['state'])
149
+ if location_parts:
150
+ key_features.append(f"Located in {', '.join(location_parts)}")
151
+
152
+ # Add size and price if available
153
+ if data.get('sq_ft'):
154
+ key_features.append(f"Spans {data['sq_ft']} sq. ft.")
155
+ if data.get('market_value'):
156
+ key_features.append(f"Priced at {format_price(data['market_value'])}")
157
+
158
+ # Add rooms information
159
+ rooms_info = []
160
+ if data.get('bedrooms'):
161
+ rooms_info.append(f"{data['bedrooms']} bedroom{'s' if data['bedrooms'] != '1' else ''}")
162
+ if data.get('bathrooms'):
163
+ rooms_info.append(f"{data['bathrooms']} bathroom{'s' if data['bathrooms'] != '1' else ''}")
164
+ if rooms_info:
165
+ key_features.append(f"Features {' and '.join(rooms_info)}")
166
+
167
+ # Add parking information
168
+ if data.get('parking_spaces'):
169
+ key_features.append(f"Includes {data['parking_spaces']} covered parking space{'s' if data['parking_spaces'] != '1' else ''}")
170
+
171
+ # Add possession date if available
172
+ if data.get('possession_date'):
173
+ key_features.append(f"Ready for possession from {data['possession_date']}")
174
+
175
+ # Add amenities if available
176
+ if data.get('amenities'):
177
+ amenities = data['amenities']
178
+ if len(amenities) > 0:
179
+ key_features.append(f"Amenities: {', '.join(amenities)}")
180
+
181
+ # Combine initial summary with key features
182
+ enhanced_summary = initial_summary
183
+ if key_features:
184
+ enhanced_summary += "\n\nKey Features:\n• " + "\n• ".join(key_features)
185
+
186
+ # Clean up the summary
187
+ enhanced_summary = enhanced_summary.replace(" ", " ").strip()
188
+
189
+ return enhanced_summary
190
+ except Exception as e:
191
+ logger.error(f"Error generating property summary: {str(e)}")
192
+ return generate_static_summary(data)
models/suggestions.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/suggestions.py
2
+
3
+ from .model_loader import load_model
4
+ from .logging_config import logger
5
+
6
+ def generate_suggestions(text, data=None):
7
+ try:
8
+ # Ensure text is string
9
+ text = str(text) if text is not None else ""
10
+
11
+ # Safely convert data values
12
+ if data:
13
+ processed_data = {}
14
+ for key, value in data.items():
15
+ if isinstance(value, (int, float)):
16
+ processed_data[key] = str(value)
17
+ else:
18
+ processed_data[key] = str(value) if value is not None else ""
19
+ data = processed_data
20
+
21
+ # Initialize suggestions
22
+ suggestions = {
23
+ 'improvements': [],
24
+ 'warnings': [],
25
+ 'recommendations': [],
26
+ 'confidence': 0.0
27
+ }
28
+
29
+ # Load model for analysis
30
+ classifier = load_model("zero-shot-classification", "typeform/mobilebert-uncased-mnli")
31
+
32
+ # Define suggestion categories
33
+ categories = [
34
+ "property description improvement",
35
+ "price adjustment needed",
36
+ "documentation required",
37
+ "verification needed",
38
+ "legal compliance issue",
39
+ "location verification needed",
40
+ "property specification update",
41
+ "image quality improvement",
42
+ "market value adjustment",
43
+ "contact information update"
44
+ ]
45
+
46
+ # Analyze text with context
47
+ context = f"{text} property_data:{str(data) if data else ''}"
48
+ result = classifier(context, categories, multi_label=True)
49
+
50
+ # Process results
51
+ for label, score in zip(result['labels'], result['scores']):
52
+ if score > 0.3: # Only include high confidence suggestions
53
+ suggestion = {
54
+ 'type': label,
55
+ 'confidence': float(score),
56
+ 'details': generate_suggestion_details(label, text, data)
57
+ }
58
+
59
+ if 'improvement' in label or 'update' in label:
60
+ suggestions['improvements'].append(suggestion)
61
+ elif 'warning' in label or 'issue' in label:
62
+ suggestions['warnings'].append(suggestion)
63
+ else:
64
+ suggestions['recommendations'].append(suggestion)
65
+
66
+ # Calculate overall confidence
67
+ if result['scores']:
68
+ suggestions['confidence'] = float(max(result['scores']))
69
+
70
+ return suggestions
71
+
72
+ except Exception as e:
73
+ logger.error(f"Error generating suggestions: {str(e)}")
74
+ return {
75
+ 'improvements': [],
76
+ 'warnings': [],
77
+ 'recommendations': [],
78
+ 'confidence': 0.0,
79
+ 'error': str(e)
80
+ }
81
+
82
+ def generate_suggestion_details(suggestion_type, text, data):
83
+ """Generate detailed suggestions based on the type."""
84
+ try:
85
+ details = {
86
+ 'property description improvement': {
87
+ 'title': 'Improve Property Description',
88
+ 'message': 'Add more detailed information about the property features and amenities.',
89
+ 'priority': 'medium'
90
+ },
91
+ 'price adjustment needed': {
92
+ 'title': 'Review Property Price',
93
+ 'message': 'Consider adjusting the price based on market conditions and property specifications.',
94
+ 'priority': 'high'
95
+ },
96
+ 'documentation required': {
97
+ 'title': 'Additional Documentation Needed',
98
+ 'message': 'Please provide more property-related documents for verification.',
99
+ 'priority': 'high'
100
+ },
101
+ 'verification needed': {
102
+ 'title': 'Property Verification Required',
103
+ 'message': 'Additional verification steps are needed for property authenticity.',
104
+ 'priority': 'high'
105
+ },
106
+ 'legal compliance issue': {
107
+ 'title': 'Legal Compliance Check',
108
+ 'message': 'Review property legal documentation and compliance status.',
109
+ 'priority': 'high'
110
+ },
111
+ 'location verification needed': {
112
+ 'title': 'Location Verification',
113
+ 'message': 'Verify property location details and coordinates.',
114
+ 'priority': 'medium'
115
+ },
116
+ 'property specification update': {
117
+ 'title': 'Update Property Specifications',
118
+ 'message': 'Review and update property specifications for accuracy.',
119
+ 'priority': 'medium'
120
+ },
121
+ 'image quality improvement': {
122
+ 'title': 'Improve Image Quality',
123
+ 'message': 'Add more high-quality images of the property.',
124
+ 'priority': 'low'
125
+ },
126
+ 'market value adjustment': {
127
+ 'title': 'Market Value Review',
128
+ 'message': 'Review and adjust market value based on current market conditions.',
129
+ 'priority': 'high'
130
+ },
131
+ 'contact information update': {
132
+ 'title': 'Update Contact Information',
133
+ 'message': 'Ensure contact information is complete and up-to-date.',
134
+ 'priority': 'low'
135
+ }
136
+ }
137
+
138
+ return details.get(suggestion_type, {
139
+ 'title': 'General Suggestion',
140
+ 'message': 'Review property listing for improvements.',
141
+ 'priority': 'medium'
142
+ })
143
+
144
+ except Exception as e:
145
+ logger.error(f"Error generating suggestion details: {str(e)}")
146
+ return {
147
+ 'title': 'Error',
148
+ 'message': 'Could not generate detailed suggestion.',
149
+ 'priority': 'low'
150
+ }
models/text_quality.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/text_quality.py
2
+
3
+ from .model_loader import load_model
4
+ from .logging_config import logger
5
+
6
+ def assess_text_quality(text):
7
+ try:
8
+ if not text or len(text.strip()) < 20:
9
+ return {
10
+ 'assessment': 'insufficient',
11
+ 'score': 0,
12
+ 'reasoning': 'Text too short.',
13
+ 'is_ai_generated': False,
14
+ 'quality_metrics': {}
15
+ }
16
+
17
+ classifier = load_model("zero-shot-classification", "typeform/mobilebert-uncased-mnli")
18
+
19
+ # Enhanced quality categories with more specific indicators
20
+ quality_categories = [
21
+ "detailed and informative",
22
+ "adequately detailed",
23
+ "basic information",
24
+ "vague description",
25
+ "misleading content",
26
+ "professional listing",
27
+ "amateur listing",
28
+ "spam-like content",
29
+ "template-based content",
30
+ "authentic description"
31
+ ]
32
+
33
+ # Analyze text with multiple aspects
34
+ quality_result = classifier(text[:1000], quality_categories, multi_label=True)
35
+
36
+ # Get top classifications with confidence scores
37
+ top_classifications = []
38
+ for label, score in zip(quality_result['labels'][:3], quality_result['scores'][:3]):
39
+ if score > 0.3: # Only include if confidence is above 30%
40
+ top_classifications.append({
41
+ 'classification': label,
42
+ 'confidence': float(score)
43
+ })
44
+
45
+ # AI generation detection with multiple models
46
+ ai_check = classifier(text[:1000], ["human-written", "AI-generated", "template-based", "authentic"])
47
+ is_ai_generated = (
48
+ (ai_check['labels'][0] == "AI-generated" and ai_check['scores'][0] > 0.6) or
49
+ (ai_check['labels'][0] == "template-based" and ai_check['scores'][0] > 0.7)
50
+ )
51
+
52
+ # Calculate quality metrics
53
+ quality_metrics = {
54
+ 'detail_level': sum(score for label, score in zip(quality_result['labels'], quality_result['scores'])
55
+ if label in ['detailed and informative', 'adequately detailed']),
56
+ 'professionalism': sum(score for label, score in zip(quality_result['labels'], quality_result['scores'])
57
+ if label in ['professional listing', 'authentic description']),
58
+ 'clarity': sum(score for label, score in zip(quality_result['labels'], quality_result['scores'])
59
+ if label not in ['vague description', 'misleading content', 'spam-like content']),
60
+ 'authenticity': 1.0 - sum(score for label, score in zip(quality_result['labels'], quality_result['scores'])
61
+ if label in ['template-based content', 'spam-like content'])
62
+ }
63
+
64
+ # Calculate overall score with weighted metrics
65
+ weights = {
66
+ 'detail_level': 0.3,
67
+ 'professionalism': 0.25,
68
+ 'clarity': 0.25,
69
+ 'authenticity': 0.2
70
+ }
71
+
72
+ score = sum(metric * weights[metric_name] for metric_name, metric in quality_metrics.items())
73
+ score = score * 100 # Convert to percentage
74
+
75
+ # Adjust score for AI-generated content
76
+ if is_ai_generated:
77
+ score = score * 0.7 # Reduce score by 30% for AI-generated content
78
+
79
+ # Generate detailed reasoning
80
+ reasoning_parts = []
81
+ if top_classifications:
82
+ primary_class = top_classifications[0]['classification']
83
+ reasoning_parts.append(f"Primary assessment: {primary_class}")
84
+
85
+ if quality_metrics['detail_level'] > 0.7:
86
+ reasoning_parts.append("Contains comprehensive details")
87
+ elif quality_metrics['detail_level'] > 0.4:
88
+ reasoning_parts.append("Contains adequate details")
89
+ else:
90
+ reasoning_parts.append("Lacks important details")
91
+
92
+ if quality_metrics['professionalism'] > 0.7:
93
+ reasoning_parts.append("Professional listing style")
94
+ elif quality_metrics['professionalism'] < 0.4:
95
+ reasoning_parts.append("Amateur listing style")
96
+
97
+ if quality_metrics['clarity'] < 0.5:
98
+ reasoning_parts.append("Content clarity issues detected")
99
+
100
+ if is_ai_generated:
101
+ reasoning_parts.append("Content appears to be AI-generated")
102
+
103
+ return {
104
+ 'assessment': top_classifications[0]['classification'] if top_classifications else 'could not assess',
105
+ 'score': int(score),
106
+ 'reasoning': '. '.join(reasoning_parts),
107
+ 'is_ai_generated': is_ai_generated,
108
+ 'quality_metrics': quality_metrics,
109
+ 'top_classifications': top_classifications
110
+ }
111
+ except Exception as e:
112
+ logger.error(f"Error assessing text quality: {str(e)}")
113
+ return {
114
+ 'assessment': 'could not assess',
115
+ 'score': 50,
116
+ 'reasoning': 'Technical error.',
117
+ 'is_ai_generated': False,
118
+ 'quality_metrics': {},
119
+ 'top_classifications': []
120
+ }
models/trust_score.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/trust_score.py
2
+
3
+ from .model_loader import load_model
4
+ from .logging_config import logger
5
+
6
+ def generate_trust_score(text, image_analysis, pdf_analysis):
7
+ try:
8
+ classifier = load_model("zero-shot-classification", "typeform/mobilebert-uncased-mnli")
9
+ aspects = [
10
+ "complete information provided",
11
+ "verified location",
12
+ "consistent data",
13
+ "authentic documents",
14
+ "authentic images",
15
+ "reasonable pricing",
16
+ "verified ownership",
17
+ "proper documentation"
18
+ ]
19
+ result = classifier(text[:1000], aspects, multi_label=True)
20
+
21
+ # Much stricter weights with higher emphasis on critical aspects
22
+ weights = {
23
+ "complete information provided": 0.25,
24
+ "verified location": 0.20,
25
+ "consistent data": 0.15,
26
+ "authentic documents": 0.15,
27
+ "authentic images": 0.10,
28
+ "reasonable pricing": 0.05,
29
+ "verified ownership": 0.05,
30
+ "proper documentation": 0.05
31
+ }
32
+
33
+ score = 0
34
+ reasoning_parts = []
35
+
36
+ # Much stricter scoring for each aspect
37
+ for label, confidence in zip(result['labels'], result['scores']):
38
+ adjusted_confidence = confidence
39
+
40
+ # Stricter document verification
41
+ if label == "authentic documents":
42
+ if not pdf_analysis or len(pdf_analysis) == 0:
43
+ adjusted_confidence = 0.0
44
+ else:
45
+ doc_scores = [p.get('verification_score', 0) for p in pdf_analysis]
46
+ adjusted_confidence = sum(doc_scores) / max(1, len(doc_scores))
47
+ # Heavily penalize if any document has low verification score
48
+ if any(score < 0.7 for score in doc_scores):
49
+ adjusted_confidence *= 0.4
50
+ # Additional penalty for missing documents
51
+ if len(doc_scores) < 2:
52
+ adjusted_confidence *= 0.5
53
+
54
+ # Stricter image verification
55
+ elif label == "authentic images":
56
+ if not image_analysis or len(image_analysis) == 0:
57
+ adjusted_confidence = 0.0
58
+ else:
59
+ img_scores = [i.get('authenticity_score', 0) for i in image_analysis]
60
+ adjusted_confidence = sum(img_scores) / max(1, len(img_scores))
61
+ # Heavily penalize if any image has low authenticity score
62
+ if any(score < 0.8 for score in img_scores):
63
+ adjusted_confidence *= 0.4
64
+ # Additional penalty for AI-generated images
65
+ if any(i.get('is_ai_generated', False) for i in image_analysis):
66
+ adjusted_confidence *= 0.5
67
+ # Additional penalty for non-property related images
68
+ if any(not i.get('is_property_related', False) for i in image_analysis):
69
+ adjusted_confidence *= 0.6
70
+
71
+ # Stricter consistency check
72
+ elif label == "consistent data":
73
+ # Check for inconsistencies in the data
74
+ if "inconsistent" in text.lower() or "suspicious" in text.lower():
75
+ adjusted_confidence *= 0.3
76
+ # Check for impossible values
77
+ if "impossible" in text.lower() or "invalid" in text.lower():
78
+ adjusted_confidence *= 0.2
79
+ # Check for missing critical information
80
+ if "missing" in text.lower() or "not provided" in text.lower():
81
+ adjusted_confidence *= 0.4
82
+
83
+ # Stricter completeness check
84
+ elif label == "complete information provided":
85
+ # Check for missing critical information
86
+ if len(text) < 300 or "not provided" in text.lower() or "missing" in text.lower():
87
+ adjusted_confidence *= 0.4
88
+ # Check for vague or generic descriptions
89
+ if "generic" in text.lower() or "vague" in text.lower():
90
+ adjusted_confidence *= 0.5
91
+ # Check for suspiciously short descriptions
92
+ if len(text) < 150:
93
+ adjusted_confidence *= 0.3
94
+
95
+ score += adjusted_confidence * weights.get(label, 0.1)
96
+ reasoning_parts.append(f"{label} ({adjusted_confidence:.0%})")
97
+
98
+ # Apply additional penalties for suspicious patterns
99
+ if "suspicious" in text.lower() or "fraudulent" in text.lower():
100
+ score *= 0.5
101
+
102
+ # Apply penalties for suspiciously low values
103
+ if "suspiciously low" in text.lower() or "unusually small" in text.lower():
104
+ score *= 0.6
105
+
106
+ # Apply penalties for inconsistencies
107
+ if "inconsistent" in text.lower() or "mismatch" in text.lower():
108
+ score *= 0.6
109
+
110
+ # Apply penalties for missing critical information
111
+ if "missing critical" in text.lower() or "incomplete" in text.lower():
112
+ score *= 0.7
113
+
114
+ # Ensure score is between 0 and 100
115
+ score = min(100, max(0, int(score * 100)))
116
+ reasoning = f"Based on: {', '.join(reasoning_parts)}"
117
+ return score, reasoning
118
+ except Exception as e:
119
+ logger.error(f"Error generating trust score: {str(e)}")
120
+ return 20, "Could not assess trust."
models/utils.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .model_loader import load_model
2
+ from .logging_config import logger
3
+
4
+ def summarize_text(text):
5
+ try:
6
+ if not text or len(text.strip()) < 10:
7
+ return "No text to summarize."
8
+ summarizer = load_model("summarization", "sshleifer/distilbart-cnn-6-6") # Use the smaller model here too
9
+ input_length = len(text.split())
10
+ max_length = max(50, min(150, input_length // 2))
11
+ min_length = max(20, input_length // 4)
12
+ summary = summarizer(text[:2000], max_length=max_length, min_length=min_length, do_sample=False)
13
+ return summary[0]['summary_text']
14
+ except Exception as e:
15
+ logger.error(f"Error summarizing text: {str(e)}")
16
+ return text[:200] + "..." if len(text) > 200 else text
requirements.txt ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ flask==2.3.3
2
+ flask-cors==4.0.0
3
+ gunicorn==21.2.0
4
+ geopy==2.4.1
5
+ Pillow==10.0.0
6
+ numpy==1.24.3
7
+ transformers==4.35.0
8
+ torch==2.1.0
9
+ spacy==3.7.2
10
+ python-dotenv==1.0.0
11
+ requests==2.31.0
12
+ python-magic==0.4.27
13
+ pdf2image==1.16.3
14
+ pytesseract==0.3.10
15
+ scikit-learn==1.3.2
16
+ pandas==2.1.1
17
+ nltk==3.8.1
18
+ beautifulsoup4==4.12.2
19
+ lxml==4.9.3
20
+ python-dateutil==2.8.2
21
+ tqdm==4.66.1
22
+ joblib==1.3.2
23
+ huggingface-hub==0.19.4
24
+ sentence-transformers==2.2.2
templates/index.html ADDED
The diff for this file is too large to render. See raw diff
 
templates/index.html.bak ADDED
@@ -0,0 +1,1916 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Property Verifier</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
+ <style>
10
+ :root {
11
+ --primary: #4361ee;
12
+ --secondary: #3f37c9;
13
+ --success: #4cc9f0;
14
+ --danger: #f72585;
15
+ --warning: #f8961e;
16
+ --info: #4895ef;
17
+ --light: #f8f9fa;
18
+ --dark: #212529;
19
+ --gray: #6c757d;
20
+ --border-radius: 12px;
21
+ --box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
22
+ }
23
+
24
+ * {
25
+ margin: 0;
26
+ padding: 0;
27
+ box-sizing: border-box;
28
+ }
29
+
30
+ body {
31
+ font-family: 'Poppins', sans-serif;
32
+ background-color: #f5f7fa;
33
+ color: #333;
34
+ line-height: 1.6;
35
+ padding: 20px;
36
+ }
37
+
38
+ .container {
39
+ max-width: 1200px;
40
+ margin: 0 auto;
41
+ }
42
+
43
+ header {
44
+ text-align: center;
45
+ margin-bottom: 30px;
46
+ }
47
+
48
+ h1 {
49
+ font-size: 2.5rem;
50
+ color: var(--primary);
51
+ margin-bottom: 10px;
52
+ }
53
+
54
+ .subtitle {
55
+ font-size: 1.1rem;
56
+ color: var(--gray);
57
+ }
58
+
59
+ .card {
60
+ background: white;
61
+ border-radius: var(--border-radius);
62
+ box-shadow: var(--box-shadow);
63
+ padding: 25px;
64
+ margin-bottom: 25px;
65
+ }
66
+
67
+ .card-header {
68
+ border-bottom: 1px solid #eee;
69
+ padding-bottom: 15px;
70
+ margin-bottom: 20px;
71
+ display: flex;
72
+ justify-content: space-between;
73
+ align-items: center;
74
+ }
75
+
76
+ .card-title {
77
+ font-size: 1.5rem;
78
+ color: var(--dark);
79
+ font-weight: 600;
80
+ }
81
+
82
+ .form-grid {
83
+ display: grid;
84
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
85
+ gap: 20px;
86
+ }
87
+
88
+ .form-group {
89
+ margin-bottom: 20px;
90
+ }
91
+
92
+ .form-label {
93
+ display: block;
94
+ margin-bottom: 8px;
95
+ font-weight: 500;
96
+ color: var(--dark);
97
+ }
98
+
99
+ .form-control {
100
+ width: 100%;
101
+ padding: 12px 15px;
102
+ border: 1px solid #ddd;
103
+ border-radius: var(--border-radius);
104
+ font-size: 1rem;
105
+ transition: border-color 0.3s;
106
+ }
107
+
108
+ .form-control:focus {
109
+ border-color: var(--primary);
110
+ outline: none;
111
+ box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
112
+ }
113
+
114
+ textarea.form-control {
115
+ min-height: 100px;
116
+ resize: vertical;
117
+ }
118
+
119
+ .btn {
120
+ display: inline-block;
121
+ padding: 12px 24px;
122
+ background-color: var(--primary);
123
+ color: white;
124
+ border: none;
125
+ border-radius: var(--border-radius);
126
+ font-size: 1rem;
127
+ font-weight: 500;
128
+ cursor: pointer;
129
+ transition: all 0.3s;
130
+ }
131
+
132
+ .btn:hover {
133
+ background-color: var(--secondary);
134
+ transform: translateY(-2px);
135
+ }
136
+
137
+ .btn-block {
138
+ display: block;
139
+ width: 100%;
140
+ }
141
+
142
+ .section-title {
143
+ font-size: 1.2rem;
144
+ color: var(--primary);
145
+ margin-bottom: 15px;
146
+ font-weight: 600;
147
+ }
148
+
149
+ .results-container {
150
+ display: none;
151
+ margin-top: 30px;
152
+ }
153
+
154
+ .results-grid {
155
+ display: grid;
156
+ grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
157
+ gap: 25px;
158
+ }
159
+
160
+
161
+ .result-card {
162
+ background: white;
163
+ border-radius: var(--border-radius);
164
+ box-shadow: var(--box-shadow);
165
+ padding: 20px;
166
+ height: 100%;
167
+ }
168
+
169
+ .result-header {
170
+ display: flex;
171
+ align-items: center;
172
+ margin-bottom: 15px;
173
+ }
174
+
175
+ .result-icon {
176
+ width: 40px;
177
+ height: 40px;
178
+ background-color: var(--light);
179
+ border-radius: 50%;
180
+ display: flex;
181
+ align-items: center;
182
+ justify-content: center;
183
+ margin-right: 15px;
184
+ }
185
+
186
+ .result-title {
187
+ font-size: 1.2rem;
188
+ font-weight: 600;
189
+ color: var(--dark);
190
+ }
191
+
192
+ .trust-score {
193
+ text-align: center;
194
+ padding: 20px;
195
+ }
196
+
197
+ .score-value {
198
+ font-size: 3rem;
199
+ font-weight: 700;
200
+ color: var(--primary);
201
+ }
202
+
203
+ .score-label {
204
+ font-size: 1rem;
205
+ color: var(--gray);
206
+ }
207
+
208
+ .progress-container {
209
+ margin: 15px 0;
210
+ }
211
+
212
+ .progress-bar {
213
+ height: 10px;
214
+ background-color: #eee;
215
+ border-radius: 5px;
216
+ overflow: hidden;
217
+ }
218
+
219
+ .progress-fill {
220
+ height: 100%;
221
+ background-color: var(--primary);
222
+ border-radius: 5px;
223
+ transition: width 0.5s ease-in-out;
224
+ }
225
+
226
+ .alert {
227
+ padding: 15px;
228
+ border-radius: var(--border-radius);
229
+ margin-bottom: 20px;
230
+ font-weight: 500;
231
+ }
232
+
233
+ .alert-danger {
234
+ background-color: rgba(247, 37, 133, 0.1);
235
+ color: var(--danger);
236
+ border-left: 4px solid var(--danger);
237
+ }
238
+
239
+ .alert-warning {
240
+ background-color: rgba(248, 150, 30, 0.1);
241
+ color: var(--warning);
242
+ border-left: 4px solid var(--warning);
243
+ }
244
+
245
+ .alert-success {
246
+ background-color: rgba(76, 201, 240, 0.1);
247
+ color: var(--success);
248
+ border-left: 4px solid var(--success);
249
+ }
250
+
251
+ .suggestion-list {
252
+ list-style-type: none;
253
+ padding: 0;
254
+ }
255
+
256
+ .suggestion-item {
257
+ padding: 10px 15px;
258
+ background-color: rgba(67, 97, 238, 0.05);
259
+ border-radius: var(--border-radius);
260
+ margin-bottom: 10px;
261
+ border-left: 3px solid var(--primary);
262
+ }
263
+
264
+ .image-preview {
265
+ display: flex;
266
+ flex-wrap: wrap;
267
+ gap: 10px;
268
+ margin-top: 10px;
269
+ }
270
+
271
+ .preview-item {
272
+ width: 100px;
273
+ height: 100px;
274
+ border-radius: 8px;
275
+ overflow: hidden;
276
+ position: relative;
277
+ }
278
+
279
+ .preview-item img {
280
+ width: 100%;
281
+ height: 100%;
282
+ object-fit: cover;
283
+ }
284
+
285
+ .preview-remove {
286
+ position: absolute;
287
+ top: 5px;
288
+ right: 5px;
289
+ background: rgba(0, 0, 0, 0.5);
290
+ color: white;
291
+ border: none;
292
+ border-radius: 50%;
293
+ width: 20px;
294
+ height: 20px;
295
+ display: flex;
296
+ align-items: center;
297
+ justify-content: center;
298
+ cursor: pointer;
299
+ }
300
+
301
+ .loading {
302
+ display: none;
303
+ text-align: center;
304
+ padding: 30px;
305
+ }
306
+
307
+ .spinner {
308
+ width: 50px;
309
+ height: 50px;
310
+ border: 5px solid rgba(67, 97, 238, 0.1);
311
+ border-radius: 50%;
312
+ border-top-color: var(--primary);
313
+ animation: spin 1s ease-in-out infinite;
314
+ margin: 0 auto 20px;
315
+ }
316
+
317
+ @keyframes spin {
318
+ to { transform: rotate(360deg); }
319
+ }
320
+
321
+ .chart-container {
322
+ position: relative;
323
+ height: 200px;
324
+ margin-bottom: 20px;
325
+ }
326
+
327
+ .pdf-preview {
328
+ background-color: #f8f9fa;
329
+ padding: 15px;
330
+ border-radius: var(--border-radius);
331
+ margin-top: 10px;
332
+ max-height: 200px;
333
+ overflow-y: auto;
334
+ }
335
+
336
+ .pdf-filename {
337
+ font-weight: 500;
338
+ margin-bottom: 5px;
339
+ }
340
+
341
+ .image-gallery {
342
+ display: grid;
343
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
344
+ gap: 15px;
345
+ margin-top: 20px;
346
+ }
347
+
348
+ .gallery-item {
349
+ border-radius: var(--border-radius);
350
+ overflow: hidden;
351
+ box-shadow: var(--box-shadow);
352
+ aspect-ratio: 1;
353
+ }
354
+
355
+ .gallery-item img {
356
+ width: 100%;
357
+ height: 100%;
358
+ object-fit: cover;
359
+ }
360
+
361
+ .badge {
362
+ display: inline-block;
363
+ padding: 5px 10px;
364
+ border-radius: 20px;
365
+ font-size: 0.8rem;
366
+ font-weight: 500;
367
+ margin-right: 5px;
368
+ margin-bottom: 5px;
369
+ }
370
+
371
+ .badge-primary { background-color: rgba(67, 97, 238, 0.1); color: var(--primary); }
372
+ .badge-success { background-color: rgba(76, 201, 240, 0.1); color: var(--success); }
373
+ .badge-warning { background-color: rgba(248, 150, 30, 0.1); color: var(--warning); }
374
+ .badge-danger { background-color: rgba(247, 37, 133, 0.1); color: var(--danger); }
375
+
376
+ .explanation-box {
377
+ background-color: #f8f9fa;
378
+ border-radius: var(--border-radius);
379
+ padding: 15px;
380
+ margin-top: 15px;
381
+ border-left: 4px solid var(--info);
382
+ }
383
+
384
+ .explanation-title {
385
+ font-weight: 600;
386
+ color: var(--info);
387
+ margin-bottom: 10px;
388
+ }
389
+
390
+ @media (max-width: 768px) {
391
+ .form-grid, .results-grid {
392
+ grid-template-columns: 1fr;
393
+ }
394
+
395
+ .card {
396
+ padding: 15px;
397
+ }
398
+ }
399
+
400
+ .property-summary {
401
+ padding: 15px;
402
+ }
403
+
404
+ .property-details p {
405
+ margin-bottom: 8px;
406
+ }
407
+
408
+ .final-verdict {
409
+ padding: 15px;
410
+ }
411
+
412
+ .verdict-box {
413
+ display: flex;
414
+ align-items: center;
415
+ padding: 15px;
416
+ border-radius: var(--border-radius);
417
+ margin-bottom: 15px;
418
+ background-color: #f8f9fa;
419
+ }
420
+
421
+ .verdict-icon {
422
+ font-size: 2rem;
423
+ margin-right: 15px;
424
+ }
425
+
426
+ .verdict-text {
427
+ font-size: 1.2rem;
428
+ font-weight: 600;
429
+ }
430
+
431
+ .verdict-legitimate {
432
+ background-color: rgba(76, 201, 240, 0.1);
433
+ border-left: 4px solid var(--success);
434
+ }
435
+
436
+ .verdict-suspicious {
437
+ background-color: rgba(248, 150, 30, 0.1);
438
+ border-left: 4px solid var(--warning);
439
+ }
440
+
441
+ .verdict-fraudulent {
442
+ background-color: rgba(247, 37, 133, 0.1);
443
+ border-left: 4px solid var(--danger);
444
+ }
445
+
446
+ .verification-scores {
447
+ padding: 15px;
448
+ }
449
+
450
+ .score-item {
451
+ margin-bottom: 15px;
452
+ }
453
+
454
+ .score-label {
455
+ font-weight: 500;
456
+ margin-bottom: 5px;
457
+ }
458
+
459
+ .score-bar-container {
460
+ display: flex;
461
+ align-items: center;
462
+ }
463
+
464
+ .score-bar {
465
+ height: 10px;
466
+ background-color: #e9ecef;
467
+ border-radius: 5px;
468
+ flex-grow: 1;
469
+ margin-right: 10px;
470
+ position: relative;
471
+ overflow: hidden;
472
+ }
473
+
474
+ .score-bar::before {
475
+ content: '';
476
+ position: absolute;
477
+ top: 0;
478
+ left: 0;
479
+ height: 100%;
480
+ background-color: var(--primary);
481
+ border-radius: 5px;
482
+ width: 0%;
483
+ transition: width 0.5s ease;
484
+ }
485
+
486
+ .score-value {
487
+ font-weight: 600;
488
+ min-width: 40px;
489
+ text-align: right;
490
+ }
491
+
492
+ .red-flags {
493
+ padding: 15px;
494
+ }
495
+ </style>
496
+ </head>
497
+ <body>
498
+ <div class="container">
499
+ <header>
500
+ <h1>AI Property Verifier & Fraud Detection</h1>
501
+ <p class="subtitle">Powered by advanced AI models to verify property listings and detect potential fraud</p>
502
+ </header>
503
+
504
+ <div class="card">
505
+ <div class="card-header">
506
+ <h2 class="card-title">Property Details</h2>
507
+ </div>
508
+
509
+ <form id="propertyForm">
510
+ <div class="section-title">Basic Information</div>
511
+ <div class="form-grid">
512
+ <div class="form-group">
513
+ <label class="form-label" for="propertyName">Property Name</label>
514
+ <input type="text" class="form-control" id="propertyName" name="property_name" required>
515
+ </div>
516
+
517
+ <div class="form-group">
518
+ <label class="form-label" for="propertyType">Property Type</label>
519
+ <select class="form-control" id="propertyType" name="property_type" required>
520
+ <option value="">Select Type</option>
521
+ <option value="Apartment">Apartment</option>
522
+ <option value="House">House</option>
523
+ <option value="Condo">Condo</option>
524
+ <option value="Townhouse">Townhouse</option>
525
+ <option value="Villa">Villa</option>
526
+ <option value="Land">Land</option>
527
+ <option value="Commercial">Commercial</option>
528
+ <option value="Other">Other</option>
529
+ </select>
530
+ </div>
531
+
532
+ <div class="form-group">
533
+ <label class="form-label" for="status">Status</label>
534
+ <select class="form-control" id="status" name="status" required>
535
+ <option value="">Select Status</option>
536
+ <option value="For Sale">For Sale</option>
537
+ <option value="For Rent">For Rent</option>
538
+ <option value="Sold">Sold</option>
539
+ <option value="Under Contract">Under Contract</option>
540
+ <option value="Pending">Pending</option>
541
+ </select>
542
+ </div>
543
+ </div>
544
+
545
+ <div class="form-group">
546
+ <label class="form-label" for="description">Property Description</label>
547
+ <textarea class="form-control" id="description" name="description" rows="4" required></textarea>
548
+ </div>
549
+
550
+ <div class="section-title">Location Details</div>
551
+ <div class="form-grid">
552
+ <div class="form-group">
553
+ <label class="form-label" for="address">Address</label>
554
+ <input type="text" class="form-control" id="address" name="address" required>
555
+ </div>
556
+
557
+ <div class="form-group">
558
+ <label class="form-label" for="city">City</label>
559
+ <input type="text" class="form-control" id="city" name="city" required>
560
+ </div>
561
+
562
+ <div class="form-group">
563
+ <label class="form-label" for="state">State/Province</label>
564
+ <input type="text" class="form-control" id="state" name="state" required>
565
+ </div>
566
+
567
+ <div class="form-group">
568
+ <label class="form-label" for="country">Country</label>
569
+ <input type="text" class="form-control" id="country" name="country" required>
570
+ </div>
571
+
572
+ <div class="form-group">
573
+ <label class="form-label" for="zip">Zip/Postal Code</label>
574
+ <input type="text" class="form-control" id="zip" name="zip" required>
575
+ </div>
576
+
577
+ <div class="form-group">
578
+ <label class="form-label" for="latitude">Latitude</label>
579
+ <input type="text" class="form-control" id="latitude" name="latitude" placeholder="e.g. 40.7128">
580
+ </div>
581
+
582
+ <div class="form-group">
583
+ <label class="form-label" for="longitude">Longitude</label>
584
+ <input type="text" class="form-control" id="longitude" name="longitude" placeholder="e.g. -74.0060">
585
+ </div>
586
+ </div>
587
+
588
+ <div class="section-title">Property Specifications</div>
589
+ <div class="form-grid">
590
+ <div class="form-group">
591
+ <label class="form-label" for="bedrooms">Bedrooms</label>
592
+ <input type="number" class="form-control" id="bedrooms" name="bedrooms" min="0">
593
+ </div>
594
+
595
+ <div class="form-group">
596
+ <label class="form-label" for="bathrooms">Bathrooms</label>
597
+ <input type="number" class="form-control" id="bathrooms" name="bathrooms" min="0" step="0.5">
598
+ </div>
599
+
600
+ <div class="form-group">
601
+ <label class="form-label" for="totalRooms">Total Rooms</label>
602
+ <input type="number" class="form-control" id="totalRooms" name="total_rooms" min="0">
603
+ </div>
604
+
605
+ <div class="form-group">
606
+ <label class="form-label" for="yearBuilt">Year Built</label>
607
+ <input type="number" class="form-control" id="yearBuilt" name="year_built" min="1800" max="2100">
608
+ </div>
609
+
610
+ <div class="form-group">
611
+ <label class="form-label" for="parking">Parking Spaces</label>
612
+ <input type="number" class="form-control" id="parking" name="parking" min="0">
613
+ </div>
614
+
615
+ <div class="form-group">
616
+ <label class="form-label" for="sqFt">Square Feet</label>
617
+ <input type="text" class="form-control" id="sqFt" name="sq_ft" min="0">
618
+ </div>
619
+
620
+ <div class="form-group">
621
+ <label class="form-label" for="marketValue">Market Value</label>
622
+ <input type="text" class="form-control" id="marketValue" name="market_value" min="0">
623
+ </div>
624
+ </div>
625
+
626
+ <div class="form-group">
627
+ <label class="form-label" for="amenities">Amenities (comma separated)</label>
628
+ <input type="text" class="form-control" id="amenities" name="amenities" placeholder="e.g. Pool, Gym, Garden, Garage">
629
+ </div>
630
+
631
+ <div class="form-group">
632
+ <label class="form-label" for="nearbyLandmarks">Nearby Landmarks</label>
633
+ <input type="text" class="form-control" id="nearbyLandmarks" name="nearby_landmarks" placeholder="e.g. School, Hospital, Park, Shopping Mall">
634
+ </div>
635
+
636
+ <div class="form-group">
637
+ <label class="form-label" for="legalDetails">Legal & Infrastructure Details</label>
638
+ <textarea class="form-control" id="legalDetails" name="legal_details" rows="3" placeholder="Include zoning, permits, utilities, etc."></textarea>
639
+ </div>
640
+
641
+ <div class="section-title">Documents & Images</div>
642
+ <div class="form-group">
643
+ <label class="form-label" for="images">Upload Images (JPG/PNG)</label>
644
+ <input type="file" class="form-control" id="images" name="images" accept="image/jpeg, image/png" multiple>
645
+ <div class="image-preview" id="imagePreview"></div>
646
+ </div>
647
+
648
+ <div class="form-group">
649
+ <label class="form-label" for="documents">Upload Documents (PDF)</label>
650
+ <input type="file" class="form-control" id="documents" name="documents" accept="application/pdf" multiple>
651
+ <div id="pdfPreview"></div>
652
+ </div>
653
+
654
+ <div class="form-group">
655
+ <button type="submit" class="btn btn-block" id="submitBtn">Verify Property with AI</button>
656
+ </div>
657
+ </form>
658
+ </div>
659
+
660
+ <div class="loading" id="loadingIndicator">
661
+ <div class="spinner"></div>
662
+ <p>AI models are analyzing your property data...</p>
663
+ <p class="subtitle">This may take a moment as we're processing multiple AI models</p>
664
+ </div>
665
+
666
+ <div class="results-container" id="resultsContainer">
667
+ <div class="card">
668
+ <div class="card-header">
669
+ <h2 class="card-title">AI Verification Results</h2>
670
+ </div>
671
+
672
+ <div class="results-grid">
673
+ <div class="result-card">
674
+ <div class="result-header">
675
+ <div class="result-icon">🏠</div>
676
+ <div class="result-title">Property Summary</div>
677
+ </div>
678
+ <div class="property-summary">
679
+ <h3 id="propertyTitle">Property Details</h3>
680
+ <div class="property-details">
681
+ <p><strong>Name:</strong> <span id="summaryName"></span></p>
682
+ <p><strong>Type:</strong> <span id="summaryType"></span></p>
683
+ <p><strong>Status:</strong> <span id="summaryStatus"></span></p>
684
+ <p><strong>Location:</strong> <span id="summaryLocation"></span></p>
685
+ <p><strong>Price:</strong> <span id="summaryPrice"></span></p>
686
+ <p><strong>Size:</strong> <span id="summarySize"></span></p>
687
+ <p><strong>Bedrooms/Bathrooms:</strong> <span id="summaryRooms"></span></p>
688
+ </div>
689
+ </div>
690
+ </div>
691
+
692
+ <div class="result-card">
693
+ <div class="result-header">
694
+ <div class="result-icon">⚠️</div>
695
+ <div class="result-title">Final Verdict</div>
696
+ </div>
697
+ <div class="final-verdict" id="finalVerdict">
698
+ <div class="verdict-box" id="verdictBox">
699
+ <div class="verdict-icon" id="verdictIcon">⏳</div>
700
+ <div class="verdict-text" id="verdictText">Analysis in progress...</div>
701
+ </div>
702
+ <div class="verdict-reasons">
703
+ <h4>Key Findings:</h4>
704
+ <ul id="verdictReasons" class="suggestion-list">
705
+ <!-- Will be populated by JavaScript -->
706
+ </ul>
707
+ </div>
708
+ </div>
709
+ </div>
710
+
711
+ <div class="result-card">
712
+ <div class="result-header">
713
+ <div class="result-icon">🔍</div>
714
+ <div class="result-title">Detailed Verification</div>
715
+ </div>
716
+ <div class="verification-scores">
717
+ <div class="score-item">
718
+ <div class="score-label">Trust Score</div>
719
+ <div class="score-bar-container">
720
+ <div class="score-bar" id="trustBar"></div>
721
+ <div class="score-value" id="trustValue">--</div>
722
+ </div>
723
+ </div>
724
+ <div class="score-item">
725
+ <div class="score-label">Image Authenticity</div>
726
+ <div class="score-bar-container">
727
+ <div class="score-bar" id="imageBar"></div>
728
+ <div class="score-value" id="imageValue">--</div>
729
+ </div>
730
+ </div>
731
+ <div class="score-item">
732
+ <div class="score-label">Document Verification</div>
733
+ <div class="score-bar-container">
734
+ <div class="score-bar" id="documentBar"></div>
735
+ <div class="score-value" id="documentValue">--</div>
736
+ </div>
737
+ </div>
738
+ <div class="score-item">
739
+ <div class="score-label">Content Quality</div>
740
+ <div class="score-bar-container">
741
+ <div class="score-bar" id="contentBar"></div>
742
+ <div class="score-value" id="contentValue">--</div>
743
+ </div>
744
+ </div>
745
+ <div class="score-item">
746
+ <div class="score-label">Location Accuracy</div>
747
+ <div class="score-bar-container">
748
+ <div class="score-bar" id="locationBar"></div>
749
+ <div class="score-value" id="locationValue">--</div>
750
+ </div>
751
+ </div>
752
+ </div>
753
+ </div>
754
+
755
+ <div class="result-card">
756
+ <div class="result-header">
757
+ <div class="result-icon">🚩</div>
758
+ <div class="result-title">Red Flags</div>
759
+ </div>
760
+ <div class="red-flags">
761
+ <ul id="redFlagsList" class="suggestion-list">
762
+ <!-- Will be populated by JavaScript -->
763
+ </ul>
764
+ </div>
765
+ </div>
766
+
767
+ <div class="result-card">
768
+ <div class="result-header">
769
+ <div class="result-icon">📊</div>
770
+ <div class="result-title">Trust Score</div>
771
+ </div>
772
+ <div class="trust-score">
773
+ <div class="score-value" id="trustScoreValue">--</div>
774
+ <div class="score-label">Trust Score</div>
775
+ <div class="progress-container">
776
+ <div class="progress-bar">
777
+ <div class="progress-fill" id="trustScoreBar" style="width: 0%"></div>
778
+ </div>
779
+ </div>
780
+ </div>
781
+ <div class="chart-container">
782
+ <canvas id="trustScoreChart"></canvas>
783
+ </div>
784
+ <div class="explanation-box">
785
+ <div class="explanation-title">AI Reasoning</div>
786
+ <div id="trustReasoning"></div>
787
+ </div>
788
+ </div>
789
+
790
+ <div class="result-card">
791
+ <div class="result-header">
792
+ <div class="result-icon">🔍</div>
793
+ <div class="result-title">Fraud Analysis</div>
794
+ </div>
795
+ <div id="fraudAlertContainer"></div>
796
+ <div class="chart-container">
797
+ <canvas id="fraudAnalysisChart"></canvas>
798
+ </div>
799
+ <div class="explanation-box">
800
+ <div class="explanation-title">AI Reasoning</div>
801
+ <div id="fraudReasoning"></div>
802
+ </div>
803
+ </div>
804
+
805
+ <div class="result-card">
806
+ <div class="result-header">
807
+ <div class="result-icon">📝</div>
808
+ <div class="result-title">AI Summary</div>
809
+ </div>
810
+ <div id="aiSummary"></div>
811
+ </div>
812
+
813
+ <div class="result-card">
814
+ <div class="result-header">
815
+ <div class="result-icon">💡</div>
816
+ <div class="result-title">Improvement Suggestions</div>
817
+ </div>
818
+ <ul class="suggestion-list" id="suggestionsList"></ul>
819
+ </div>
820
+
821
+ <div class="result-card">
822
+ <div class="result-header">
823
+ <div class="result-icon">🏠</div>
824
+ <div class="result-title">Property Quality Assessment</div>
825
+ </div>
826
+ <div id="qualityAssessment"></div>
827
+ <div class="chart-container">
828
+ <canvas id="qualityChart"></canvas>
829
+ </div>
830
+ </div>
831
+
832
+ <div class="result-card">
833
+ <div class="result-header">
834
+ <div class="result-icon">📍</div>
835
+ <div class="result-title">Location Analysis</div>
836
+ </div>
837
+ <div id="locationAnalysis"></div>
838
+ <div class="chart-container">
839
+ <canvas id="locationChart"></canvas>
840
+ </div>
841
+ </div>
842
+
843
+ <div class="result-card">
844
+ <div class="result-header">
845
+ <div class="result-icon">💰</div>
846
+ <div class="result-title">Price Analysis</div>
847
+ </div>
848
+ <div id="priceAnalysis"></div>
849
+ <div class="chart-container">
850
+ <canvas id="priceChart"></canvas>
851
+ </div>
852
+ </div>
853
+
854
+ <div class="result-card">
855
+ <div class="result-header">
856
+ <div class="result-icon">⚖️</div>
857
+ <div class="result-title">Legal Analysis</div>
858
+ </div>
859
+ <div id="legalAnalysis"></div>
860
+ <div class="chart-container">
861
+ <canvas id="legalChart"></canvas>
862
+ </div>
863
+ </div>
864
+
865
+ <div class="result-card">
866
+ <div class="result-header">
867
+ <div class="result-icon">🔄</div>
868
+ <div class="result-title">Cross-Validation Checks</div>
869
+ </div>
870
+ <div id="crossValidation"></div>
871
+ </div>
872
+
873
+ <div class="result-card">
874
+ <div class="result-header">
875
+ <div class="result-icon">📄</div>
876
+ <div class="result-title">Document Analysis</div>
877
+ </div>
878
+ <div id="documentAnalysis"></div>
879
+ <div class="chart-container">
880
+ <canvas id="documentChart"></canvas>
881
+ </div>
882
+ </div>
883
+
884
+ <div class="result-card">
885
+ <div class="result-header">
886
+ <div class="result-icon">🖼️</div>
887
+ <div class="result-title">Image Analysis</div>
888
+ </div>
889
+ <div id="imageAnalysis"></div>
890
+ <div class="image-gallery" id="imageGallery"></div>
891
+ </div>
892
+ </div>
893
+ </div>
894
+ </div>
895
+ </div>
896
+
897
+ <script>
898
+ // Global variables to store form data
899
+ let uploadedImages = [];
900
+ let uploadedPDFs = [];
901
+
902
+ // Initialize charts
903
+ let trustScoreChart;
904
+ let fraudAnalysisChart;
905
+ let qualityChart;
906
+ let locationChart;
907
+ let priceChart;
908
+ let legalChart;
909
+ let documentChart;
910
+
911
+ document.addEventListener('DOMContentLoaded', function() {
912
+ // Request location access when page loads
913
+ requestLocationAccess();
914
+
915
+ const propertyForm = document.getElementById('propertyForm');
916
+ const loadingIndicator = document.getElementById('loadingIndicator');
917
+ const resultsContainer = document.getElementById('resultsContainer');
918
+ const imageInput = document.getElementById('images');
919
+ const imagePreview = document.getElementById('imagePreview');
920
+ const pdfInput = document.getElementById('documents');
921
+ const pdfPreview = document.getElementById('pdfPreview');
922
+
923
+ // Handle image uploads
924
+ imageInput.addEventListener('change', function(e) {
925
+ handleImageUpload(e.target.files);
926
+ });
927
+
928
+ // Handle PDF uploads
929
+ pdfInput.addEventListener('change', function(e) {
930
+ handlePDFUpload(e.target.files);
931
+ });
932
+
933
+ // Form submission
934
+ propertyForm.addEventListener('submit', function(e) {
935
+ e.preventDefault();
936
+ submitForm();
937
+ });
938
+
939
+ // Initialize charts
940
+ initCharts();
941
+ });
942
+
943
+ function requestLocationAccess() {
944
+ if (navigator.geolocation) {
945
+ navigator.geolocation.getCurrentPosition(
946
+ function(position) {
947
+ const latitude = position.coords.latitude;
948
+ const longitude = position.coords.longitude;
949
+
950
+ // Update form fields with coordinates
951
+ document.getElementById('latitude').value = latitude;
952
+ document.getElementById('longitude').value = longitude;
953
+
954
+ // Send to backend to get address details
955
+ fetch('/get-location', {
956
+ method: 'POST',
957
+ headers: {
958
+ 'Content-Type': 'application/json',
959
+ },
960
+ body: JSON.stringify({
961
+ latitude: latitude,
962
+ longitude: longitude
963
+ }),
964
+ })
965
+ .then(response => response.json())
966
+ .then(data => {
967
+ if (data.status === 'success') {
968
+ // Fill form fields with location data
969
+ document.getElementById('address').value = data.address || '';
970
+ document.getElementById('city').value = data.city || '';
971
+ document.getElementById('state').value = data.state || '';
972
+ document.getElementById('country').value = data.country || '';
973
+ document.getElementById('zip').value = data.postal_code || '';
974
+
975
+ console.log('Location data loaded successfully');
976
+ } else {
977
+ console.error('Error getting location details:', data.message);
978
+ }
979
+ })
980
+ .catch(error => {
981
+ console.error('Error getting location details:', error);
982
+ });
983
+ },
984
+ function(error) {
985
+ console.error('Error getting location:', error.message);
986
+ // Show a message to the user about location access
987
+ const locationMessage = document.createElement('div');
988
+ locationMessage.className = 'alert alert-warning';
989
+ locationMessage.innerHTML = 'Location access denied or unavailable. Please enter your location manually.';
990
+ document.querySelector('.container').prepend(locationMessage);
991
+
992
+ // Auto-remove the message after 5 seconds
993
+ setTimeout(() => {
994
+ locationMessage.remove();
995
+ }, 5000);
996
+ },
997
+ {
998
+ enableHighAccuracy: true,
999
+ timeout: 5000,
1000
+ maximumAge: 0
1001
+ }
1002
+ );
1003
+ } else {
1004
+ console.error('Geolocation is not supported by this browser');
1005
+ }
1006
+ }
1007
+
1008
+ function handleImageUpload(files) {
1009
+ const imagePreview = document.getElementById('imagePreview');
1010
+
1011
+ for (let i = 0; i < files.length; i++) {
1012
+ const file = files[i];
1013
+ if (!file.type.match('image.*')) continue;
1014
+
1015
+ const reader = new FileReader();
1016
+ reader.onload = function(e) {
1017
+ const imageData = e.target.result;
1018
+ uploadedImages.push({
1019
+ name: file.name,
1020
+ data: imageData,
1021
+ file: file
1022
+ });
1023
+
1024
+ // Create preview
1025
+ const previewItem = document.createElement('div');
1026
+ previewItem.className = 'preview-item';
1027
+ previewItem.innerHTML = `
1028
+ <img src="${imageData}" alt="${file.name}">
1029
+ <button type="button" class="preview-remove" data-index="${uploadedImages.length - 1}">×</button>
1030
+ `;
1031
+ imagePreview.appendChild(previewItem);
1032
+
1033
+ // Add remove functionality
1034
+ previewItem.querySelector('.preview-remove').addEventListener('click', function() {
1035
+ const index = parseInt(this.getAttribute('data-index'));
1036
+ uploadedImages.splice(index, 1);
1037
+ imagePreview.removeChild(previewItem);
1038
+ updateImagePreviews();
1039
+ });
1040
+ };
1041
+ reader.readAsDataURL(file);
1042
+ }
1043
+ }
1044
+
1045
+ function updateImagePreviews() {
1046
+ const imagePreview = document.getElementById('imagePreview');
1047
+ imagePreview.innerHTML = '';
1048
+
1049
+ uploadedImages.forEach((image, index) => {
1050
+ const previewItem = document.createElement('div');
1051
+ previewItem.className = 'preview-item';
1052
+ previewItem.innerHTML = `
1053
+ <img src="${image.data}" alt="${image.name}">
1054
+ <button type="button" class="preview-remove" data-index="${index}">×</button>
1055
+ `;
1056
+ imagePreview.appendChild(previewItem);
1057
+
1058
+ previewItem.querySelector('.preview-remove').addEventListener('click', function() {
1059
+ uploadedImages.splice(index, 1);
1060
+ updateImagePreviews();
1061
+ });
1062
+ });
1063
+ }
1064
+
1065
+ function handlePDFUpload(files) {
1066
+ const pdfPreview = document.getElementById('pdfPreview');
1067
+
1068
+ for (let i = 0; i < files.length; i++) {
1069
+ const file = files[i];
1070
+ if (file.type !== 'application/pdf') continue;
1071
+
1072
+ uploadedPDFs.push({
1073
+ name: file.name,
1074
+ file: file
1075
+ });
1076
+
1077
+ // Create preview
1078
+ const previewItem = document.createElement('div');
1079
+ previewItem.className = 'pdf-preview';
1080
+ previewItem.innerHTML = `
1081
+ <div class="pdf-filename">${file.name}</div>
1082
+ <button type="button" class="btn" data-index="${uploadedPDFs.length - 1}">Remove</button>
1083
+ `;
1084
+ pdfPreview.appendChild(previewItem);
1085
+
1086
+ // Add remove functionality
1087
+ previewItem.querySelector('.btn').addEventListener('click', function() {
1088
+ const index = parseInt(this.getAttribute('data-index'));
1089
+ uploadedPDFs.splice(index, 1);
1090
+ pdfPreview.removeChild(previewItem);
1091
+ updatePDFPreviews();
1092
+ });
1093
+ }
1094
+ }
1095
+
1096
+ function updatePDFPreviews() {
1097
+ const pdfPreview = document.getElementById('pdfPreview');
1098
+ pdfPreview.innerHTML = '';
1099
+
1100
+ uploadedPDFs.forEach((pdf, index) => {
1101
+ const previewItem = document.createElement('div');
1102
+ previewItem.className = 'pdf-preview';
1103
+ previewItem.innerHTML = `
1104
+ <div class="pdf-filename">${pdf.name}</div>
1105
+ <button type="button" class="btn" data-index="${index}">Remove</button>
1106
+ `;
1107
+ pdfPreview.appendChild(previewItem);
1108
+
1109
+ previewItem.querySelector('.btn').addEventListener('click', function() {
1110
+ uploadedPDFs.splice(index, 1);
1111
+ updatePDFPreviews();
1112
+ });
1113
+ });
1114
+ }
1115
+
1116
+ function initCharts() {
1117
+ try {
1118
+ // Store all charts in an array for easier management
1119
+ window.charts = [];
1120
+
1121
+ // Trust Score Chart initialization
1122
+ const trustCtx = document.getElementById('trustScoreChart').getContext('2d');
1123
+ trustScoreChart = new Chart(trustCtx, {
1124
+ type: 'doughnut',
1125
+ data: {
1126
+ datasets: [{
1127
+ data: [0, 100],
1128
+ backgroundColor: [
1129
+ '#4361ee',
1130
+ '#f1f1f1'
1131
+ ],
1132
+ borderWidth: 0
1133
+ }]
1134
+ },
1135
+ options: {
1136
+ cutout: '70%',
1137
+ circumference: 180,
1138
+ rotation: -90,
1139
+ plugins: {
1140
+ legend: {
1141
+ display: false
1142
+ },
1143
+ tooltip: {
1144
+ enabled: false
1145
+ }
1146
+ },
1147
+ maintainAspectRatio: false
1148
+ }
1149
+ });
1150
+ charts.push(trustScoreChart);
1151
+
1152
+ // Fraud Analysis Chart (Bar)
1153
+ const fraudAnalysisCtx = document.getElementById('fraudAnalysisChart').getContext('2d');
1154
+ fraudAnalysisChart = new Chart(fraudAnalysisCtx, {
1155
+ type: 'bar',
1156
+ data: {
1157
+ labels: ['Legitimate', 'Suspicious', 'Fraudulent'],
1158
+ datasets: [{
1159
+ label: 'Fraud Indicators',
1160
+ data: [0, 0, 0],
1161
+ backgroundColor: [
1162
+ '#4cc9f0',
1163
+ '#f8961e',
1164
+ '#f72585'
1165
+ ],
1166
+ borderWidth: 0
1167
+ }]
1168
+ },
1169
+ options: {
1170
+ indexAxis: 'y',
1171
+ plugins: {
1172
+ legend: {
1173
+ display: false
1174
+ }
1175
+ },
1176
+ scales: {
1177
+ x: {
1178
+ beginAtZero: true,
1179
+ max: 100
1180
+ }
1181
+ },
1182
+ maintainAspectRatio: false
1183
+ }
1184
+ });
1185
+
1186
+ // Quality Assessment Chart
1187
+ const qualityCtx = document.getElementById('qualityChart').getContext('2d');
1188
+ qualityChart = new Chart(qualityCtx, {
1189
+ type: 'radar',
1190
+ data: {
1191
+ labels: ['Completeness', 'Accuracy', 'Clarity', 'Authenticity', 'Detail'],
1192
+ datasets: [{
1193
+ label: 'Quality Score',
1194
+ data: [0, 0, 0, 0, 0],
1195
+ backgroundColor: 'rgba(67, 97, 238, 0.2)',
1196
+ borderColor: '#4361ee',
1197
+ borderWidth: 2,
1198
+ pointBackgroundColor: '#4361ee'
1199
+ }]
1200
+ },
1201
+ options: {
1202
+ scales: {
1203
+ r: {
1204
+ beginAtZero: true,
1205
+ max: 100
1206
+ }
1207
+ },
1208
+ maintainAspectRatio: false
1209
+ }
1210
+ });
1211
+
1212
+ // Location Analysis Chart
1213
+ const locationCtx = document.getElementById('locationChart').getContext('2d');
1214
+ locationChart = new Chart(locationCtx, {
1215
+ type: 'pie',
1216
+ data: {
1217
+ labels: ['Complete', 'Partial', 'Missing'],
1218
+ datasets: [{
1219
+ data: [0, 0, 0],
1220
+ backgroundColor: [
1221
+ '#4cc9f0',
1222
+ '#f8961e',
1223
+ '#f72585'
1224
+ ],
1225
+ borderWidth: 0
1226
+ }]
1227
+ },
1228
+ options: {
1229
+ plugins: {
1230
+ legend: {
1231
+ position: 'bottom'
1232
+ }
1233
+ },
1234
+ maintainAspectRatio: false
1235
+ }
1236
+ });
1237
+
1238
+ // Price Analysis Chart
1239
+ const priceCtx = document.getElementById('priceChart').getContext('2d');
1240
+ priceChart = new Chart(priceCtx, {
1241
+ type: 'bar',
1242
+ data: {
1243
+ labels: ['Market Value', 'Price per Sq.Ft.'],
1244
+ datasets: [{
1245
+ label: 'Price Analysis',
1246
+ data: [0, 0],
1247
+ backgroundColor: [
1248
+ '#4361ee',
1249
+ '#4895ef'
1250
+ ],
1251
+ borderWidth: 0
1252
+ }]
1253
+ },
1254
+ options: {
1255
+ scales: {
1256
+ y: {
1257
+ beginAtZero: true
1258
+ }
1259
+ },
1260
+ maintainAspectRatio: false
1261
+ }
1262
+ });
1263
+
1264
+ // Legal Analysis Chart
1265
+ const legalCtx = document.getElementById('legalChart').getContext('2d');
1266
+ legalChart = new Chart(legalCtx, {
1267
+ type: 'doughnut',
1268
+ data: {
1269
+ labels: ['Complete', 'Partial', 'Missing'],
1270
+ datasets: [{
1271
+ data: [0, 0, 0],
1272
+ backgroundColor: [
1273
+ '#4cc9f0',
1274
+ '#f8961e',
1275
+ '#f72585'
1276
+ ],
1277
+ borderWidth: 0
1278
+ }]
1279
+ },
1280
+ options: {
1281
+ plugins: {
1282
+ legend: {
1283
+ position: 'bottom'
1284
+ }
1285
+ },
1286
+ maintainAspectRatio: false
1287
+ }
1288
+ });
1289
+
1290
+ // Document Analysis Chart
1291
+ const documentCtx = document.getElementById('documentChart').getContext('2d');
1292
+ documentChart = new Chart(documentCtx, {
1293
+ type: 'polarArea',
1294
+ data: {
1295
+ labels: ['Authentic', 'Suspicious', 'Incomplete'],
1296
+ datasets: [{
1297
+ data: [0, 0, 0],
1298
+ backgroundColor: [
1299
+ '#4cc9f0',
1300
+ '#f8961e',
1301
+ '#f72585'
1302
+ ],
1303
+ borderWidth: 0
1304
+ }]
1305
+ },
1306
+ options: {
1307
+ plugins: {
1308
+ legend: {
1309
+ position: 'bottom'
1310
+ }
1311
+ },
1312
+ maintainAspectRatio: false
1313
+ }
1314
+ });
1315
+
1316
+ // Add window resize handler for chart responsiveness
1317
+ window.addEventListener('resize', debounce(() => {
1318
+ charts.forEach(chart => {
1319
+ if (chart && typeof chart.resize === 'function') {
1320
+ chart.resize();
1321
+ }
1322
+ });
1323
+ }, 250));
1324
+
1325
+ } catch (error) {
1326
+ console.error('Error initializing charts:', error);
1327
+ document.getElementById('chartErrors').innerHTML =
1328
+ '<div class="alert alert-danger">Error initializing charts. Please refresh the page.</div>';
1329
+ }
1330
+ }
1331
+
1332
+ // Utility function for debouncing
1333
+ function debounce(func, wait) {
1334
+ let timeout;
1335
+ return function executedFunction(...args) {
1336
+ const later = () => {
1337
+ clearTimeout(timeout);
1338
+ func(...args);
1339
+ };
1340
+ clearTimeout(timeout);
1341
+ timeout = setTimeout(later, wait);
1342
+ };
1343
+ }
1344
+
1345
+ // Data validation function
1346
+ function validateAnalysisData(data) {
1347
+ return {
1348
+ trustScore: {
1349
+ score: data.trust_score?.score ?? 0,
1350
+ reasoning: data.trust_score?.reasoning ?? 'No reasoning provided'
1351
+ },
1352
+ fraudClassification: {
1353
+ alertLevel: data.fraud_classification?.alert_level ?? 'low',
1354
+ classification: data.fraud_classification?.classification ?? 'Unknown',
1355
+ confidence: data.fraud_classification?.confidence ?? 0,
1356
+ indicators: data.fraud_classification?.fraud_indicators ?? [],
1357
+ scores: data.fraud_classification?.indicator_scores ?? []
1358
+ },
1359
+ qualityAssessment: {
1360
+ assessment: data.quality_assessment?.assessment ?? 'Unknown',
1361
+ score: data.quality_assessment?.score ?? 0,
1362
+ isAiGenerated: data.quality_assessment?.is_ai_generated ?? false,
1363
+ reasoning: data.quality_assessment?.reasoning ?? 'No reasoning provided'
1364
+ },
1365
+ // ... other validations
1366
+ };
1367
+ }
1368
+
1369
+ // Safe chart update function
1370
+ function updateChart(chart, newData, options = {}) {
1371
+ try {
1372
+ if (chart && typeof chart.update === 'function') {
1373
+ chart.data = newData;
1374
+ chart.update(options);
1375
+ return true;
1376
+ }
1377
+ return false;
1378
+ } catch (error) {
1379
+ console.error('Error updating chart:', error);
1380
+ return false;
1381
+ }
1382
+ }
1383
+
1384
+ function submitForm() {
1385
+ // Show loading indicator
1386
+ document.getElementById('loadingIndicator').style.display = 'block';
1387
+ document.getElementById('resultsContainer').style.display = 'none';
1388
+
1389
+ // Create form data
1390
+ const formData = new FormData(document.getElementById('propertyForm'));
1391
+
1392
+ // Add images
1393
+ uploadedImages.forEach((image, index) => {
1394
+ formData.append('images', image.file);
1395
+ });
1396
+
1397
+ // Add PDFs
1398
+ uploadedPDFs.forEach((pdf, index) => {
1399
+ formData.append('documents', pdf.file);
1400
+ });
1401
+
1402
+ // Send to backend
1403
+ fetch('/verify', {
1404
+ method: 'POST',
1405
+ body: formData
1406
+ })
1407
+ .then(response => {
1408
+ if (!response.ok) {
1409
+ throw new Error('Network response was not ok');
1410
+ }
1411
+ return response.json();
1412
+ })
1413
+ .then(data => {
1414
+ // Hide loading indicator
1415
+ document.getElementById('loadingIndicator').style.display = 'none';
1416
+
1417
+ // Display results
1418
+ displayResults(data);
1419
+
1420
+ // Show results container
1421
+ document.getElementById('resultsContainer').style.display = 'block';
1422
+
1423
+ // Scroll to results
1424
+ document.getElementById('resultsContainer').scrollIntoView({ behavior: 'smooth' });
1425
+ })
1426
+ .catch(error => {
1427
+ console.error('Error:', error);
1428
+ document.getElementById('loadingIndicator').style.display = 'none';
1429
+ alert('An error occurred while processing your request. Please try again.');
1430
+ });
1431
+ }
1432
+ function displayResults(data) {
1433
+ console.log("Received data:", JSON.stringify(data));
1434
+
1435
+ // Validate and sanitize data
1436
+ const validatedData = validateAnalysisData(data);
1437
+
1438
+ try {
1439
+ // Display Trust Score with validated data
1440
+ const trustScore = validatedData.trustScore.score;
1441
+ document.getElementById('trustScoreValue').textContent = trustScore;
1442
+ document.getElementById('trustScoreBar').style.width = `${trustScore}%`;
1443
+ document.getElementById('trustReasoning').textContent = validatedData.trustScore.reasoning;
1444
+
1445
+ // Update Trust Score Chart safely
1446
+ updateChart(trustScoreChart, {
1447
+ datasets: [{
1448
+ data: [trustScore, 100 - trustScore]
1449
+ }]
1450
+ });
1451
+
1452
+ // Display Fraud Analysis
1453
+ const fraudLevel = validatedData.fraudClassification.alertLevel;
1454
+ const fraudContainer = document.getElementById('fraudAlertContainer');
1455
+ fraudContainer.innerHTML = '';
1456
+
1457
+ const alertClass = fraudLevel === 'high' ? 'alert-danger' :
1458
+ fraudLevel === 'medium' ? 'alert-warning' : 'alert-success';
1459
+
1460
+ const alertDiv = document.createElement('div');
1461
+ alertDiv.className = `alert ${alertClass}`;
1462
+ alertDiv.textContent = `Classification: ${validatedData.fraudClassification.classification} (Confidence: ${Math.round(validatedData.fraudClassification.confidence * 100)}%)`;
1463
+ fraudContainer.appendChild(alertDiv);
1464
+
1465
+ // Update Fraud Analysis Chart
1466
+ const fraudIndicators = validatedData.fraudClassification.indicators || [];
1467
+ const fraudScores = validatedData.fraudClassification.scores || [];
1468
+ const formattedScores = fraudScores.map(score => score * 100);
1469
+
1470
+ updateChart(fraudAnalysisChart, {
1471
+ labels: fraudIndicators,
1472
+ datasets: [{
1473
+ data: formattedScores
1474
+ }]
1475
+ });
1476
+
1477
+ document.getElementById('fraudReasoning').textContent = `This property was classified as ${validatedData.fraudClassification.classification} based on AI analysis of the listing details.`;
1478
+
1479
+ // Display AI Summary
1480
+ document.getElementById('aiSummary').textContent = data.summary || "No summary available";
1481
+
1482
+ // Display Improvement Suggestions
1483
+ const suggestionsList = document.getElementById('suggestionsList');
1484
+ suggestionsList.innerHTML = '';
1485
+
1486
+ if (data.suggestions && Array.isArray(data.suggestions) && data.suggestions.length > 0) {
1487
+ data.suggestions.forEach(suggestion => {
1488
+ if (suggestion && suggestion.trim()) {
1489
+ const li = document.createElement('li');
1490
+ li.className = 'suggestion-item';
1491
+ li.textContent = suggestion;
1492
+ suggestionsList.appendChild(li);
1493
+ }
1494
+ });
1495
+ } else {
1496
+ const li = document.createElement('li');
1497
+ li.className = 'suggestion-item';
1498
+ li.textContent = "No suggestions available";
1499
+ suggestionsList.appendChild(li);
1500
+ }
1501
+
1502
+ // Display Quality Assessment
1503
+ const qualityDiv = document.getElementById('qualityAssessment');
1504
+ if (validatedData.qualityAssessment) {
1505
+ qualityDiv.innerHTML = `
1506
+ <p><strong>Assessment:</strong> ${validatedData.qualityAssessment.assessment}</p>
1507
+ <p><strong>Quality Score:</strong> ${validatedData.qualityAssessment.score}%</p>
1508
+ <p><strong>AI Generated:</strong> ${validatedData.qualityAssessment.isAiGenerated ? 'Likely' : 'Unlikely'}</p>
1509
+ <p><strong>Reasoning:</strong> ${validatedData.qualityAssessment.reasoning}</p>
1510
+ `;
1511
+
1512
+ // Update Quality Chart
1513
+ updateChart(qualityChart, {
1514
+ datasets: [{
1515
+ data: [
1516
+ validatedData.qualityAssessment.score,
1517
+ validatedData.qualityAssessment.isAiGenerated ? 30 : 80,
1518
+ validatedData.qualityAssessment.score > 50 ? 70 : 40,
1519
+ validatedData.qualityAssessment.isAiGenerated ? 40 : 75,
1520
+ validatedData.qualityAssessment.score > 60 ? 80 : 50
1521
+ ]
1522
+ }]
1523
+ });
1524
+ } else {
1525
+ qualityDiv.innerHTML = '<p>No quality assessment available</p>';
1526
+ }
1527
+
1528
+ // Display Location Analysis
1529
+ const locationDiv = document.getElementById('locationAnalysis');
1530
+ if (data.location_analysis) {
1531
+ locationDiv.innerHTML = `
1532
+ <p><strong>Assessment:</strong> ${data.location_analysis.assessment || "Unknown"}</p>
1533
+ <p><strong>Completeness:</strong> ${data.location_analysis.completeness_score || 0}%</p>
1534
+ <p><strong>Coordinates:</strong> ${data.location_analysis.coordinates_check || "Unknown"}</p>
1535
+ <p><strong>Landmarks:</strong> ${data.location_analysis.landmarks_provided ? 'Provided' : 'Not provided'}</p>
1536
+ `;
1537
+
1538
+ // Update Location Chart
1539
+ updateChart(locationChart, {
1540
+ datasets: [{
1541
+ data: [
1542
+ data.location_analysis.completeness_score || 0,
1543
+ 100 - (data.location_analysis.completeness_score || 0),
1544
+ data.location_analysis.coordinates_check === 'coordinates_missing' ? 30 : 0
1545
+ ]
1546
+ }]
1547
+ });
1548
+ } else {
1549
+ locationDiv.innerHTML = '<p>No location analysis available</p>';
1550
+ }
1551
+
1552
+ // Display Price Analysis
1553
+ const priceDiv = document.getElementById('priceAnalysis');
1554
+ if (data.price_analysis && data.price_analysis.has_price) {
1555
+ priceDiv.innerHTML = `
1556
+ <p><strong>Assessment:</strong> ${data.price_analysis.assessment || "Unknown"}</p>
1557
+ <p><strong>Price:</strong> ₹${(data.price_analysis.price || 0).toLocaleString()}</p>
1558
+ ${data.price_analysis.has_sqft ? `<p><strong>Price per Sq.Ft.:</strong> ₹${(data.price_analysis.price_per_sqft || 0).toLocaleString(undefined, {maximumFractionDigits: 2})}</p>` : ''}
1559
+ <p><strong>Confidence:</strong> ${Math.round((data.price_analysis.confidence || 0) * 100)}%</p>
1560
+ `;
1561
+
1562
+ // Update Price Chart
1563
+ updateChart(priceChart, {
1564
+ labels: ['Market Value (thousands)', 'Price per Sq.Ft.'],
1565
+ datasets: [{
1566
+ data: [
1567
+ (data.price_analysis.price || 0) / 1000, // Scale down for better visualization
1568
+ data.price_analysis.price_per_sqft || 0
1569
+ ]
1570
+ }]
1571
+ });
1572
+ } else {
1573
+ priceDiv.innerHTML = `<p>No price information provided for analysis.</p>`;
1574
+ }
1575
+
1576
+ // Display Legal Analysis
1577
+ const legalDiv = document.getElementById('legalAnalysis');
1578
+ if (data.legal_analysis) {
1579
+ legalDiv.innerHTML = `
1580
+ <p><strong>Assessment:</strong> ${data.legal_analysis.assessment || "Unknown"}</p>
1581
+ <p><strong>Completeness:</strong> ${data.legal_analysis.completeness_score || 0}%</p>
1582
+ <p><strong>Summary:</strong> ${data.legal_analysis.summary || "No summary available"}</p>
1583
+ ${data.legal_analysis.terms_found && data.legal_analysis.terms_found.length > 0 ? `<p><strong>Legal Terms Found:</strong> ${data.legal_analysis.terms_found.join(', ')}</p>` : ''}
1584
+ ${data.legal_analysis.potential_issues ? '<p class="alert alert-warning">Potential legal issues detected</p>' : ''}
1585
+ `;
1586
+
1587
+ // Update Legal Chart
1588
+ updateChart(legalChart, {
1589
+ datasets: [{
1590
+ data: [
1591
+ data.legal_analysis.completeness_score || 0,
1592
+ 100 - (data.legal_analysis.completeness_score || 0),
1593
+ data.legal_analysis.potential_issues ? 30 : 0
1594
+ ]
1595
+ }]
1596
+ });
1597
+ } else {
1598
+ legalDiv.innerHTML = '<p>No legal analysis available</p>';
1599
+ }
1600
+
1601
+ // Display Cross-Validation Checks
1602
+ const crossValidationDiv = document.getElementById('crossValidation');
1603
+ crossValidationDiv.innerHTML = '<ul class="suggestion-list">';
1604
+
1605
+ try {
1606
+ // Safely check if cross_validation exists and is an array
1607
+ if (data && data.cross_validation && Array.isArray(data.cross_validation)) {
1608
+ // Only proceed if the array has items
1609
+ if (data.cross_validation.length > 0) {
1610
+ data.cross_validation.forEach(check => {
1611
+ if (check && typeof check === 'object') {
1612
+ const status = check.status || 'unknown';
1613
+ const checkName = check.check || 'Check';
1614
+ const message = check.message || 'No details available';
1615
+
1616
+ // Determine status class
1617
+ let statusClass = 'badge-warning'; // Default
1618
+ if (['consistent', 'valid', 'reasonable', 'match', 'likely_valid'].includes(status)) {
1619
+ statusClass = 'badge-success';
1620
+ } else if (['suspicious', 'inconsistent', 'invalid', 'no_match'].includes(status)) {
1621
+ statusClass = 'badge-danger';
1622
+ }
1623
+
1624
+ crossValidationDiv.innerHTML += `
1625
+ <li class="suggestion-item">
1626
+ <span class="badge ${statusClass}">${status}</span>
1627
+ <strong>${checkName}:</strong> ${message}
1628
+ </li>
1629
+ `;
1630
+ }
1631
+ });
1632
+ } else {
1633
+ crossValidationDiv.innerHTML += '<li class="suggestion-item">No cross-validation checks performed</li>';
1634
+ }
1635
+ } else {
1636
+ crossValidationDiv.innerHTML += '<li class="suggestion-item">No cross-validation data available</li>';
1637
+ }
1638
+ } catch (error) {
1639
+ console.error("Error displaying cross-validation:", error);
1640
+ crossValidationDiv.innerHTML += '<li class="suggestion-item">Error displaying cross-validation results</li>';
1641
+ }
1642
+
1643
+ crossValidationDiv.innerHTML += '</ul>';
1644
+
1645
+ // Display Document Analysis
1646
+ const documentDiv = document.getElementById('documentAnalysis');
1647
+ documentDiv.innerHTML = '';
1648
+
1649
+ if (data.document_analysis && data.document_analysis.pdf_count > 0) {
1650
+ documentDiv.innerHTML = `<p><strong>Documents Analyzed:</strong> ${data.document_analysis.pdf_count}</p>`;
1651
+
1652
+ data.document_analysis.pdf_analysis.forEach((pdf, index) => {
1653
+ documentDiv.innerHTML += `
1654
+ <div class="pdf-preview">
1655
+ <p><strong>Document ${index + 1}</strong></p>
1656
+ <p><strong>Type:</strong> ${pdf.document_type.classification} (${Math.round(pdf.document_type.confidence * 100)}% confidence)</p>
1657
+ <p><strong>Authenticity:</strong> ${pdf.authenticity.assessment} (${Math.round(pdf.authenticity.confidence * 100)}% confidence)</p>
1658
+ <p><strong>Summary:</strong> ${pdf.summary}</p>
1659
+ <p><strong>Contains Signatures:</strong> ${pdf.contains_signatures ? 'Yes' : 'No'}</p>
1660
+ <p><strong>Contains Dates:</strong> ${pdf.contains_dates ? 'Yes' : 'No'}</p>
1661
+ </div>
1662
+ `;
1663
+ });
1664
+
1665
+ // Update Document Chart
1666
+ let authenticCount = 0;
1667
+ let suspiciousCount = 0;
1668
+ let incompleteCount = 0;
1669
+
1670
+ data.document_analysis.pdf_analysis.forEach(pdf => {
1671
+ if (pdf.authenticity.assessment.includes('authentic')) {
1672
+ authenticCount++;
1673
+ } else if (pdf.authenticity.assessment.includes('fraudulent')) {
1674
+ suspiciousCount++;
1675
+ } else {
1676
+ incompleteCount++;
1677
+ }
1678
+ });
1679
+
1680
+ updateChart(documentChart, {
1681
+ datasets: [{
1682
+ data: [
1683
+ authenticCount,
1684
+ suspiciousCount,
1685
+ incompleteCount
1686
+ ]
1687
+ }]
1688
+ });
1689
+ } else {
1690
+ documentDiv.innerHTML = '<p>No documents were uploaded for analysis.</p>';
1691
+ }
1692
+
1693
+ // Display Image Analysis
1694
+ const imageAnalysisDiv = document.getElementById('imageAnalysis');
1695
+ const imageGallery = document.getElementById('imageGallery');
1696
+
1697
+ imageAnalysisDiv.innerHTML = '';
1698
+ imageGallery.innerHTML = '';
1699
+
1700
+ if (data.image_analysis && data.images && data.images.length > 0) {
1701
+ imageAnalysisDiv.innerHTML = `<p><strong>Images Analyzed:</strong> ${data.image_analysis.image_count}</p>`;
1702
+
1703
+ let propertyRelatedCount = 0;
1704
+ data.image_analysis.image_analysis.forEach(img => {
1705
+ if (img && img.is_property_related) {
1706
+ propertyRelatedCount++;
1707
+ }
1708
+ });
1709
+
1710
+ imageAnalysisDiv.innerHTML += `<p><strong>Property-Related Images:</strong> ${propertyRelatedCount} of ${data.image_analysis.image_count}</p>`;
1711
+
1712
+ // Display images in gallery
1713
+ data.images.forEach((imgData, index) => {
1714
+ const imgAnalysis = data.image_analysis.image_analysis[index];
1715
+ const galleryItem = document.createElement('div');
1716
+ galleryItem.className = 'gallery-item';
1717
+
1718
+ galleryItem.innerHTML = `
1719
+ <img src="data:image/jpeg;base64,${imgData}" alt="Property Image ${index + 1}">
1720
+ <div class="badge ${imgAnalysis && imgAnalysis.is_property_related ? 'badge-success' : 'badge-warning'}"
1721
+ style="position: absolute; top: 5px; right: 5px;">
1722
+ ${imgAnalysis && imgAnalysis.is_property_related ? 'Property' : 'Not Property'}
1723
+ </div>
1724
+ `;
1725
+
1726
+ imageGallery.appendChild(galleryItem);
1727
+ });
1728
+ } else {
1729
+ imageAnalysisDiv.innerHTML = '<p>No images were uploaded for analysis.</p>';
1730
+ }
1731
+
1732
+ // Update Property Summary
1733
+ document.getElementById('summaryName').textContent = document.getElementById('propertyName').value || 'Not provided';
1734
+ document.getElementById('summaryType').textContent = document.getElementById('propertyType').value || 'Not provided';
1735
+ document.getElementById('summaryStatus').textContent = document.getElementById('status').value || 'Not provided';
1736
+ document.getElementById('summaryLocation').textContent =
1737
+ `${document.getElementById('address').value || ''}, ${document.getElementById('city').value || ''}, ${document.getElementById('state').value || ''}, India`;
1738
+ document.getElementById('summaryPrice').textContent = document.getElementById('marketValue').value ? `₹${document.getElementById('marketValue').value}` : 'Not provided';
1739
+ document.getElementById('summarySize').textContent = document.getElementById('sqFt').value ? `${document.getElementById('sqFt').value} sq. ft.` : 'Not provided';
1740
+ document.getElementById('summaryRooms').textContent =
1741
+ `${document.getElementById('bedrooms').value || '0'} BHK`; // BHK is common in Indian real estate
1742
+
1743
+ // Update Final Verdict
1744
+ const verdictBox = document.getElementById('verdictBox');
1745
+ const verdictIcon = document.getElementById('verdictIcon');
1746
+ const verdictText = document.getElementById('verdictText');
1747
+
1748
+ if (fraudLevel === 'high' || trustScore < 40) {
1749
+ verdictBox.className = 'verdict-box verdict-fraudulent';
1750
+ verdictIcon.textContent = '❌';
1751
+ verdictText.textContent = 'HIGH RISK - LIKELY FRAUDULENT';
1752
+ } else if (fraudLevel === 'medium' || trustScore < 70) {
1753
+ verdictBox.className = 'verdict-box verdict-suspicious';
1754
+ verdictIcon.textContent = '⚠️';
1755
+ verdictText.textContent = 'CAUTION - SUSPICIOUS ELEMENTS';
1756
+ } else {
1757
+ verdictBox.className = 'verdict-box verdict-legitimate';
1758
+ verdictIcon.textContent = '✅';
1759
+ verdictText.textContent = 'VERIFIED REAL ESTATE LISTING';
1760
+ }
1761
+
1762
+ // Update Verdict Reasons
1763
+ const verdictReasons = document.getElementById('verdictReasons');
1764
+ verdictReasons.innerHTML = '';
1765
+
1766
+ // Add key findings based on analysis
1767
+ const findings = [];
1768
+
1769
+ if (validatedData.qualityAssessment && validatedData.qualityAssessment.isAiGenerated) {
1770
+ findings.push('Description appears to be AI-generated');
1771
+ }
1772
+
1773
+ if (data.cross_validation) {
1774
+ data.cross_validation.forEach(check => {
1775
+ if (check.status === 'inconsistent' || check.status === 'invalid' ||
1776
+ check.status === 'suspicious' || check.status === 'no_match') {
1777
+ findings.push(check.message);
1778
+ }
1779
+ });
1780
+ }
1781
+
1782
+ if (data.price_analysis && data.price_analysis.assessment === 'suspicious pricing') {
1783
+ findings.push('Price appears suspicious for this type of property');
1784
+ }
1785
+
1786
+ if (data.legal_analysis && data.legal_analysis.potential_issues) {
1787
+ findings.push('Potential legal issues detected');
1788
+ }
1789
+
1790
+ // Add at least one positive finding if the verdict is good
1791
+ if (findings.length === 0 && trustScore > 70) {
1792
+ findings.push('Property details appear consistent and legitimate');
1793
+ findings.push('No suspicious elements detected in the listing');
1794
+ }
1795
+
1796
+ // If we still have no findings, add a generic one
1797
+ if (findings.length === 0) {
1798
+ findings.push('Analysis inconclusive - insufficient information provided');
1799
+ }
1800
+
1801
+ findings.forEach(finding => {
1802
+ const li = document.createElement('li');
1803
+ li.className = 'suggestion-item';
1804
+ li.textContent = finding;
1805
+ verdictReasons.appendChild(li);
1806
+ });
1807
+
1808
+ // Update Verification Scores
1809
+ updateScoreBar('trustBar', 'trustValue', trustScore);
1810
+
1811
+ // Image authenticity score
1812
+ let imageScore = 0;
1813
+ if (data.image_analysis && data.image_analysis.image_analysis) {
1814
+ const propertyImages = data.image_analysis.image_analysis.filter(img => img && img.is_property_related);
1815
+ imageScore = data.image_analysis.image_count > 0 ?
1816
+ Math.round((propertyImages.length / data.image_analysis.image_count) * 100) : 0;
1817
+ }
1818
+ updateScoreBar('imageBar', 'imageValue', imageScore);
1819
+
1820
+ // Document verification score
1821
+ let docScore = 0;
1822
+ if (data.document_analysis && data.document_analysis.pdf_analysis) {
1823
+ const authenticDocs = data.document_analysis.pdf_analysis.filter(
1824
+ pdf => pdf.authenticity && pdf.authenticity.assessment.includes('authentic')
1825
+ );
1826
+ docScore = data.document_analysis.pdf_count > 0 ?
1827
+ Math.round((authenticDocs.length / data.document_analysis.pdf_count) * 100) : 0;
1828
+ }
1829
+ updateScoreBar('documentBar', 'documentValue', docScore);
1830
+
1831
+ // Content quality score
1832
+ const contentScore = validatedData.qualityAssessment ? validatedData.qualityAssessment.score : 0;
1833
+ updateScoreBar('contentBar', 'contentValue', contentScore);
1834
+
1835
+ // Location accuracy score
1836
+ const locationScore = data.location_analysis ? data.location_analysis.completeness_score || 0 : 0;
1837
+ updateScoreBar('locationBar', 'locationValue', locationScore);
1838
+
1839
+ // Update Red Flags
1840
+ const redFlagsList = document.getElementById('redFlagsList');
1841
+ redFlagsList.innerHTML = '';
1842
+
1843
+ const redFlags = [];
1844
+
1845
+ // Check for inconsistencies and issues
1846
+ if (data.cross_validation) {
1847
+ data.cross_validation.forEach(check => {
1848
+ if (check.status === 'inconsistent' || check.status === 'invalid' ||
1849
+ check.status === 'suspicious' || check.status === 'no_match') {
1850
+ redFlags.push(`${check.check}: ${check.message}`);
1851
+ }
1852
+ });
1853
+ }
1854
+
1855
+ if (validatedData.qualityAssessment && validatedData.qualityAssessment.isAiGenerated) {
1856
+ redFlags.push('Description appears to be AI-generated, which may indicate a fake listing');
1857
+ }
1858
+
1859
+ if (data.price_analysis &&
1860
+ (data.price_analysis.assessment === 'suspicious pricing' ||
1861
+ data.price_analysis.assessment === 'overpriced' ||
1862
+ data.price_analysis.assessment === 'underpriced')) {
1863
+ redFlags.push(`Price is ${data.price_analysis.assessment} for this type of property`);
1864
+ }
1865
+
1866
+ if (data.legal_analysis && data.legal_analysis.potential_issues) {
1867
+ redFlags.push('Potential legal issues detected in the property documentation');
1868
+ }
1869
+
1870
+ if (data.image_analysis && data.image_analysis.image_count > 0) {
1871
+ const propertyImages = data.image_analysis.image_analysis.filter(img => img && img.is_property_related);
1872
+ if (propertyImages.length === 0) {
1873
+ redFlags.push('None of the uploaded images appear to be related to real estate');
1874
+ }
1875
+ }
1876
+
1877
+ // If no red flags, add a positive message
1878
+ if (redFlags.length === 0) {
1879
+ redFlags.push('No significant red flags detected in this listing');
1880
+ }
1881
+
1882
+ redFlags.forEach(flag => {
1883
+ const li = document.createElement('li');
1884
+ li.className = 'suggestion-item';
1885
+ li.textContent = flag;
1886
+ redFlagsList.appendChild(li);
1887
+ });
1888
+
1889
+ } catch (error) {
1890
+ console.error('Error displaying results:', error);
1891
+ document.getElementById('resultsContainer').innerHTML =
1892
+ '<div class="alert alert-danger">Error displaying results. Please try again.</div>';
1893
+ }
1894
+ }
1895
+
1896
+ function updateScoreBar(barId, valueId, score) {
1897
+ const bar = document.getElementById(barId);
1898
+ const value = document.getElementById(valueId);
1899
+
1900
+ if (bar && value) {
1901
+ bar.style.setProperty('--score-width', `${score}%`);
1902
+ bar.style.background = `linear-gradient(to right,
1903
+ ${getScoreColor(score)} ${score}%,
1904
+ #e9ecef ${score}%)`;
1905
+ value.textContent = `${score}%`;
1906
+ }
1907
+ }
1908
+
1909
+ function getScoreColor(score) {
1910
+ if (score >= 70) return 'var(--success)';
1911
+ if (score >= 40) return 'var(--warning)';
1912
+ return 'var(--danger)';
1913
+ }
1914
+ </script>
1915
+ </body>
1916
+ </html>
templates/index.html.new ADDED
@@ -0,0 +1,1160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Property Verifier</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
+ <style>
10
+ :root {
11
+ --primary: #4361ee;
12
+ --secondary: #3f37c9;
13
+ --success: #4cc9f0;
14
+ --danger: #f72585;
15
+ --warning: #f8961e;
16
+ --info: #4895ef;
17
+ --light: #f8f9fa;
18
+ --dark: #212529;
19
+ --gray: #6c757d;
20
+ --border-radius: 12px;
21
+ --box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
22
+ }
23
+
24
+ * {
25
+ margin: 0;
26
+ padding: 0;
27
+ box-sizing: border-box;
28
+ }
29
+
30
+ body {
31
+ font-family: 'Poppins', sans-serif;
32
+ background-color: #f5f7fa;
33
+ color: #333;
34
+ line-height: 1.6;
35
+ padding: 20px;
36
+ }
37
+
38
+ .container {
39
+ max-width: 1200px;
40
+ margin: 0 auto;
41
+ }
42
+
43
+ header {
44
+ text-align: center;
45
+ margin-bottom: 30px;
46
+ }
47
+
48
+ h1 {
49
+ font-size: 2.5rem;
50
+ color: var(--primary);
51
+ margin-bottom: 10px;
52
+ }
53
+
54
+ .subtitle {
55
+ font-size: 1.1rem;
56
+ color: var(--gray);
57
+ }
58
+
59
+ .card {
60
+ background: white;
61
+ border-radius: var(--border-radius);
62
+ box-shadow: var(--box-shadow);
63
+ padding: 25px;
64
+ margin-bottom: 25px;
65
+ }
66
+
67
+ .card-header {
68
+ border-bottom: 1px solid #eee;
69
+ padding-bottom: 15px;
70
+ margin-bottom: 20px;
71
+ display: flex;
72
+ justify-content: space-between;
73
+ align-items: center;
74
+ }
75
+
76
+ .card-title {
77
+ font-size: 1.5rem;
78
+ color: var(--dark);
79
+ font-weight: 600;
80
+ }
81
+
82
+ .form-grid {
83
+ display: grid;
84
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
85
+ gap: 20px;
86
+ }
87
+
88
+ .form-group {
89
+ margin-bottom: 20px;
90
+ }
91
+
92
+ .form-label {
93
+ display: block;
94
+ margin-bottom: 8px;
95
+ font-weight: 500;
96
+ color: var(--dark);
97
+ }
98
+
99
+ .form-control {
100
+ width: 100%;
101
+ padding: 12px 15px;
102
+ border: 1px solid #ddd;
103
+ border-radius: var(--border-radius);
104
+ font-size: 1rem;
105
+ transition: border-color 0.3s;
106
+ }
107
+
108
+ .form-control:focus {
109
+ border-color: var(--primary);
110
+ outline: none;
111
+ box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
112
+ }
113
+
114
+ textarea.form-control {
115
+ min-height: 100px;
116
+ resize: vertical;
117
+ }
118
+
119
+ .btn {
120
+ display: inline-block;
121
+ padding: 12px 24px;
122
+ background-color: var(--primary);
123
+ color: white;
124
+ border: none;
125
+ border-radius: var(--border-radius);
126
+ font-size: 1rem;
127
+ font-weight: 500;
128
+ cursor: pointer;
129
+ transition: all 0.3s;
130
+ }
131
+
132
+ .btn:hover {
133
+ background-color: var(--secondary);
134
+ transform: translateY(-2px);
135
+ }
136
+
137
+ .btn-block {
138
+ display: block;
139
+ width: 100%;
140
+ }
141
+
142
+ .section-title {
143
+ font-size: 1.2rem;
144
+ color: var(--primary);
145
+ margin-bottom: 15px;
146
+ font-weight: 600;
147
+ }
148
+
149
+ .results-container {
150
+ display: none;
151
+ margin-top: 30px;
152
+ }
153
+
154
+ .results-grid {
155
+ display: grid;
156
+ grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
157
+ gap: 25px;
158
+ }
159
+
160
+ .result-card {
161
+ background: white;
162
+ border-radius: var(--border-radius);
163
+ box-shadow: var(--box-shadow);
164
+ padding: 20px;
165
+ height: 100%;
166
+ }
167
+
168
+ .result-header {
169
+ display: flex;
170
+ align-items: center;
171
+ margin-bottom: 15px;
172
+ }
173
+
174
+ .result-icon {
175
+ width: 40px;
176
+ height: 40px;
177
+ background-color: var(--light);
178
+ border-radius: 50%;
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ margin-right: 15px;
183
+ }
184
+
185
+ .result-title {
186
+ font-size: 1.2rem;
187
+ font-weight: 600;
188
+ color: var(--dark);
189
+ }
190
+
191
+ .trust-score {
192
+ text-align: center;
193
+ padding: 20px;
194
+ }
195
+
196
+ .score-value {
197
+ font-size: 3rem;
198
+ font-weight: 700;
199
+ color: var(--primary);
200
+ }
201
+
202
+ .score-label {
203
+ font-size: 1rem;
204
+ color: var(--gray);
205
+ }
206
+
207
+ .progress-container {
208
+ margin: 15px 0;
209
+ }
210
+
211
+ .progress-bar {
212
+ height: 10px;
213
+ background-color: #eee;
214
+ border-radius: 5px;
215
+ overflow: hidden;
216
+ }
217
+
218
+ .progress-fill {
219
+ height: 100%;
220
+ background-color: var(--primary);
221
+ border-radius: 5px;
222
+ transition: width 0.5s ease-in-out;
223
+ }
224
+
225
+ .alert {
226
+ padding: 15px;
227
+ border-radius: var(--border-radius);
228
+ margin-bottom: 20px;
229
+ font-weight: 500;
230
+ }
231
+
232
+ .alert-danger {
233
+ background-color: rgba(247, 37, 133, 0.1);
234
+ color: var(--danger);
235
+ border-left: 4px solid var(--danger);
236
+ }
237
+
238
+ .alert-warning {
239
+ background-color: rgba(248, 150, 30, 0.1);
240
+ color: var(--warning);
241
+ border-left: 4px solid var(--warning);
242
+ }
243
+
244
+ .alert-success {
245
+ background-color: rgba(76, 201, 240, 0.1);
246
+ color: var(--success);
247
+ border-left: 4px solid var(--success);
248
+ }
249
+
250
+ .suggestion-list {
251
+ list-style-type: none;
252
+ padding: 0;
253
+ }
254
+
255
+ .suggestion-item {
256
+ padding: 10px 15px;
257
+ background-color: rgba(67, 97, 238, 0.05);
258
+ border-radius: var(--border-radius);
259
+ margin-bottom: 10px;
260
+ border-left: 3px solid var(--primary);
261
+ }
262
+
263
+ .image-preview {
264
+ display: flex;
265
+ flex-wrap: wrap;
266
+ gap: 10px;
267
+ margin-top: 10px;
268
+ }
269
+
270
+ .preview-item {
271
+ width: 100px;
272
+ height: 100px;
273
+ border-radius: 8px;
274
+ overflow: hidden;
275
+ position: relative;
276
+ }
277
+
278
+ .preview-item img {
279
+ width: 100%;
280
+ height: 100%;
281
+ object-fit: cover;
282
+ }
283
+
284
+ .preview-remove {
285
+ position: absolute;
286
+ top: 5px;
287
+ right: 5px;
288
+ background: rgba(0, 0, 0, 0.5);
289
+ color: white;
290
+ border: none;
291
+ border-radius: 50%;
292
+ width: 20px;
293
+ height: 20px;
294
+ display: flex;
295
+ align-items: center;
296
+ justify-content: center;
297
+ cursor: pointer;
298
+ }
299
+
300
+ .loading {
301
+ display: none;
302
+ text-align: center;
303
+ padding: 30px;
304
+ }
305
+
306
+ .spinner {
307
+ width: 50px;
308
+ height: 50px;
309
+ border: 5px solid rgba(67, 97, 238, 0.1);
310
+ border-radius: 50%;
311
+ border-top-color: var(--primary);
312
+ animation: spin 1s ease-in-out infinite;
313
+ margin: 0 auto 20px;
314
+ }
315
+
316
+ @keyframes spin {
317
+ to { transform: rotate(360deg); }
318
+ }
319
+
320
+ .chart-container {
321
+ position: relative;
322
+ height: 200px;
323
+ margin-bottom: 20px;
324
+ }
325
+
326
+ .pdf-preview {
327
+ background-color: #f8f9fa;
328
+ padding: 15px;
329
+ border-radius: var(--border-radius);
330
+ margin-top: 10px;
331
+ max-height: 200px;
332
+ overflow-y: auto;
333
+ }
334
+
335
+ .pdf-filename {
336
+ font-weight: 500;
337
+ margin-bottom: 5px;
338
+ }
339
+
340
+ .image-gallery {
341
+ display: grid;
342
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
343
+ gap: 15px;
344
+ margin-top: 20px;
345
+ }
346
+
347
+ .gallery-item {
348
+ border-radius: var(--border-radius);
349
+ overflow: hidden;
350
+ box-shadow: var(--box-shadow);
351
+ aspect-ratio: 1;
352
+ }
353
+
354
+ .gallery-item img {
355
+ width: 100%;
356
+ height: 100%;
357
+ object-fit: cover;
358
+ }
359
+
360
+ .badge {
361
+ display: inline-block;
362
+ padding: 5px 10px;
363
+ border-radius: 20px;
364
+ font-size: 0.8rem;
365
+ font-weight: 500;
366
+ margin-right: 5px;
367
+ margin-bottom: 5px;
368
+ }
369
+
370
+ .badge-primary { background-color: rgba(67, 97, 238, 0.1); color: var(--primary); }
371
+ .badge-success { background-color: rgba(76, 201, 240, 0.1); color: var(--success); }
372
+ .badge-warning { background-color: rgba(248, 150, 30, 0.1); color: var(--warning); }
373
+ .badge-danger { background-color: rgba(247, 37, 133, 0.1); color: var(--danger); }
374
+
375
+ .explanation-box {
376
+ background-color: #f8f9fa;
377
+ border-radius: var(--border-radius);
378
+ padding: 15px;
379
+ margin-top: 15px;
380
+ border-left: 4px solid var(--info);
381
+ }
382
+
383
+ .explanation-title {
384
+ font-weight: 600;
385
+ color: var(--info);
386
+ margin-bottom: 10px;
387
+ }
388
+
389
+ .property-summary {
390
+ padding: 15px;
391
+ }
392
+
393
+ .property-details p {
394
+ margin-bottom: 8px;
395
+ }
396
+
397
+ .final-verdict {
398
+ padding: 15px;
399
+ }
400
+
401
+ .verdict-box {
402
+ display: flex;
403
+ align-items: center;
404
+ padding: 15px;
405
+ border-radius: var(--border-radius);
406
+ margin-bottom: 15px;
407
+ background-color: #f8f9fa;
408
+ }
409
+
410
+ .verdict-icon {
411
+ font-size: 2rem;
412
+ margin-right: 15px;
413
+ }
414
+
415
+ .verdict-text {
416
+ font-size: 1.2rem;
417
+ font-weight: 600;
418
+ }
419
+
420
+ .verdict-legitimate {
421
+ background-color: rgba(76, 201, 240, 0.1);
422
+ border-left: 4px solid var(--success);
423
+ }
424
+
425
+ .verdict-suspicious {
426
+ background-color: rgba(248, 150, 30, 0.1);
427
+ border-left: 4px solid var(--warning);
428
+ }
429
+
430
+ .verdict-fraudulent {
431
+ background-color: rgba(247, 37, 133, 0.1);
432
+ border-left: 4px solid var(--danger);
433
+ }
434
+
435
+ .verification-scores {
436
+ padding: 15px;
437
+ }
438
+
439
+ .score-item {
440
+ margin-bottom: 15px;
441
+ }
442
+
443
+ .score-label {
444
+ font-weight: 500;
445
+ margin-bottom: 5px;
446
+ }
447
+
448
+ .score-bar-container {
449
+ display: flex;
450
+ align-items: center;
451
+ }
452
+
453
+ .score-bar {
454
+ height: 10px;
455
+ background-color: #e9ecef;
456
+ border-radius: 5px;
457
+ flex-grow: 1;
458
+ margin-right: 10px;
459
+ position: relative;
460
+ overflow: hidden;
461
+ }
462
+
463
+ .score-bar::before {
464
+ content: '';
465
+ position: absolute;
466
+ top: 0;
467
+ left: 0;
468
+ height: 100%;
469
+ background-color: var(--primary);
470
+ border-radius: 5px;
471
+ width: 0%;
472
+ transition: width 0.5s ease;
473
+ }
474
+
475
+ .score-value {
476
+ font-weight: 600;
477
+ min-width: 40px;
478
+ text-align: right;
479
+ }
480
+
481
+ .red-flags {
482
+ padding: 15px;
483
+ }
484
+
485
+ @media (max-width: 768px) {
486
+ .form-grid, .results-grid {
487
+ grid-template-columns: 1fr;
488
+ }
489
+
490
+ .card {
491
+ padding: 15px;
492
+ }
493
+ }
494
+ </style>
495
+ </head>
496
+ <body>
497
+ <div class="container">
498
+ <header>
499
+ <h1>AI Property Verifier & Fraud Detection</h1>
500
+ <p class="subtitle">Powered by advanced AI models to verify property listings and detect potential fraud</p>
501
+ </header>
502
+
503
+ <div class="card">
504
+ <div class="card-header">
505
+ <h2 class="card-title">Property Details</h2>
506
+ </div>
507
+ <form id="propertyForm">
508
+ <div class="section-title">Basic Information</div>
509
+ <div class="form-grid">
510
+ <div class="form-group">
511
+ <label class="form-label" for="propertyName">Property Name</label>
512
+ <input type="text" class="form-control" id="propertyName" name="property_name" required>
513
+ </div>
514
+
515
+ <div class="form-group">
516
+ <label class="form-label" for="propertyType">Property Type</label>
517
+ <select class="form-control" id="propertyType" name="property_type" required>
518
+ <option value="">Select Type</option>
519
+ <option value="Apartment">Apartment</option>
520
+ <option value="House">House</option>
521
+ <option value="Condo">Condo</option>
522
+ <option value="Townhouse">Townhouse</option>
523
+ <option value="Villa">Villa</option>
524
+ <option value="Land">Land</option>
525
+ <option value="Commercial">Commercial</option>
526
+ <option value="Other">Other</option>
527
+ </select>
528
+ </div>
529
+
530
+ <div class="form-group">
531
+ <label class="form-label" for="status">Status</label>
532
+ <select class="form-control" id="status" name="status" required>
533
+ <option value="">Select Status</option>
534
+ <option value="For Sale">For Sale</option>
535
+ <option value="For Rent">For Rent</option>
536
+ <option value="Sold">Sold</option>
537
+ <option value="Under Contract">Under Contract</option>
538
+ <option value="Pending">Pending</option>
539
+ </select>
540
+ </div>
541
+ </div>
542
+
543
+ <div class="form-group">
544
+ <label class="form-label" for="description">Property Description</label>
545
+ <textarea class="form-control" id="description" name="description" rows="4" required></textarea>
546
+ </div>
547
+
548
+ <div class="section-title">Location Details</div>
549
+ <div class="form-grid">
550
+ <div class="form-group">
551
+ <label class="form-label" for="address">Address</label>
552
+ <input type="text" class="form-control" id="address" name="address" required>
553
+ </div>
554
+
555
+ <div class="form-group">
556
+ <label class="form-label" for="city">City</label>
557
+ <input type="text" class="form-control" id="city" name="city" required>
558
+ </div>
559
+
560
+ <div class="form-group">
561
+ <label class="form-label" for="state">State/Province</label>
562
+ <input type="text" class="form-control" id="state" name="state" required>
563
+ </div>
564
+
565
+ <div class="form-group">
566
+ <label class="form-label" for="country">Country</label>
567
+ <input type="text" class="form-control" id="country" name="country" required>
568
+ </div>
569
+
570
+ <div class="form-group">
571
+ <label class="form-label" for="zip">Zip/Postal Code</label>
572
+ <input type="text" class="form-control" id="zip" name="zip" required>
573
+ </div>
574
+
575
+ <div class="form-group">
576
+ <label class="form-label" for="latitude">Latitude</label>
577
+ <input type="text" class="form-control" id="latitude" name="latitude" placeholder="e.g. 40.7128">
578
+ </div>
579
+
580
+ <div class="form-group">
581
+ <label class="form-label" for="longitude">Longitude</label>
582
+ <input type="text" class="form-control" id="longitude" name="longitude" placeholder="e.g. -74.0060">
583
+ </div>
584
+ </div>
585
+
586
+ <div class="section-title">Property Specifications</div>
587
+ <div class="form-grid">
588
+ <div class="form-group">
589
+ <label class="form-label" for="bedrooms">Bedrooms</label>
590
+ <input type="number" class="form-control" id="bedrooms" name="bedrooms" min="0">
591
+ </div>
592
+
593
+ <div class="form-group">
594
+ <label class="form-label" for="bathrooms">Bathrooms</label>
595
+ <input type="number" class="form-control" id="bathrooms" name="bathrooms" min="0" step="0.5">
596
+ </div>
597
+
598
+ <div class="form-group">
599
+ <label class="form-label" for="squareFeet">Square Feet</label>
600
+ <input type="number" class="form-control" id="squareFeet" name="square_feet" min="0">
601
+ </div>
602
+
603
+ <div class="form-group">
604
+ <label class="form-label" for="yearBuilt">Year Built</label>
605
+ <input type="number" class="form-control" id="yearBuilt" name="year_built" min="1800" max="2024">
606
+ </div>
607
+
608
+ <div class="form-group">
609
+ <label class="form-label" for="price">Price</label>
610
+ <input type="number" class="form-control" id="price" name="price" min="0" required>
611
+ </div>
612
+ </div>
613
+
614
+ <div class="section-title">Documents & Images</div>
615
+ <div class="form-group">
616
+ <label class="form-label">Property Images</label>
617
+ <input type="file" class="form-control" id="images" name="images" multiple accept="image/*">
618
+ <div class="image-preview" id="imagePreview"></div>
619
+ </div>
620
+
621
+ <div class="form-group">
622
+ <label class="form-label">Property Documents (PDF)</label>
623
+ <input type="file" class="form-control" id="documents" name="documents" multiple accept=".pdf">
624
+ <div class="pdf-preview" id="pdfPreview"></div>
625
+ </div>
626
+
627
+ <button type="submit" class="btn btn-block">Verify Property</button>
628
+ </form>
629
+ </div>
630
+
631
+ <div class="loading" id="loading">
632
+ <div class="spinner"></div>
633
+ <p>Analyzing property details...</p>
634
+ </div>
635
+
636
+ <div class="results-container" id="results">
637
+ <div class="results-grid">
638
+ <div class="result-card">
639
+ <div class="result-header">
640
+ <div class="result-icon">📊</div>
641
+ <h3 class="result-title">Trust Score</h3>
642
+ </div>
643
+ <div class="trust-score">
644
+ <div class="score-value" id="trustScore">0</div>
645
+ <div class="score-label">Overall Trust Score</div>
646
+ <div class="progress-container">
647
+ <div class="progress-bar">
648
+ <div class="progress-fill" id="trustScoreBar"></div>
649
+ </div>
650
+ </div>
651
+ </div>
652
+ </div>
653
+
654
+ <div class="result-card">
655
+ <div class="result-header">
656
+ <div class="result-icon">🔍</div>
657
+ <h3 class="result-title">Fraud Analysis</h3>
658
+ </div>
659
+ <div class="chart-container">
660
+ <canvas id="fraudChart"></canvas>
661
+ </div>
662
+ <div id="fraudDetails"></div>
663
+ </div>
664
+
665
+ <div class="result-card">
666
+ <div class="result-header">
667
+ <div class="result-icon">📝</div>
668
+ <h3 class="result-title">Quality Assessment</h3>
669
+ </div>
670
+ <div class="chart-container">
671
+ <canvas id="qualityChart"></canvas>
672
+ </div>
673
+ <div id="qualityDetails"></div>
674
+ </div>
675
+
676
+ <div class="result-card">
677
+ <div class="result-header">
678
+ <div class="result-icon">📍</div>
679
+ <h3 class="result-title">Location Analysis</h3>
680
+ </div>
681
+ <div id="locationDetails"></div>
682
+ </div>
683
+
684
+ <div class="result-card">
685
+ <div class="result-header">
686
+ <div class="result-icon">💰</div>
687
+ <h3 class="result-title">Price Analysis</h3>
688
+ </div>
689
+ <div id="priceDetails"></div>
690
+ </div>
691
+
692
+ <div class="result-card">
693
+ <div class="result-header">
694
+ <div class="result-icon">⚖️</div>
695
+ <h3 class="result-title">Legal Analysis</h3>
696
+ </div>
697
+ <div class="chart-container">
698
+ <canvas id="legalChart"></canvas>
699
+ </div>
700
+ <div id="legalDetails"></div>
701
+ </div>
702
+ </div>
703
+
704
+ <div class="card">
705
+ <div class="card-header">
706
+ <h2 class="card-title">Final Verdict</h2>
707
+ </div>
708
+ <div class="final-verdict">
709
+ <div class="verdict-box" id="verdictBox">
710
+ <div class="verdict-icon">✅</div>
711
+ <div class="verdict-text" id="verdictText">Analyzing...</div>
712
+ </div>
713
+ <div id="verdictDetails"></div>
714
+ </div>
715
+ </div>
716
+ </div>
717
+ </div>
718
+
719
+ <script>
720
+ // Global variables
721
+ let uploadedImages = [];
722
+ let uploadedPDFs = [];
723
+ let fraudChart = null;
724
+ let qualityChart = null;
725
+ let legalChart = null;
726
+
727
+ // Initialize charts
728
+ function initializeCharts() {
729
+ // Fraud Analysis Chart
730
+ const fraudCtx = document.getElementById('fraudChart').getContext('2d');
731
+ fraudChart = new Chart(fraudCtx, {
732
+ type: 'doughnut',
733
+ data: {
734
+ labels: ['Low Risk', 'Medium Risk', 'High Risk'],
735
+ datasets: [{
736
+ data: [0, 0, 0],
737
+ backgroundColor: [
738
+ 'rgba(76, 201, 240, 0.8)',
739
+ 'rgba(248, 150, 30, 0.8)',
740
+ 'rgba(247, 37, 133, 0.8)'
741
+ ]
742
+ }]
743
+ },
744
+ options: {
745
+ responsive: true,
746
+ maintainAspectRatio: false
747
+ }
748
+ });
749
+
750
+ // Quality Assessment Chart
751
+ const qualityCtx = document.getElementById('qualityChart').getContext('2d');
752
+ qualityChart = new Chart(qualityCtx, {
753
+ type: 'bar',
754
+ data: {
755
+ labels: ['Completeness', 'Accuracy', 'Consistency'],
756
+ datasets: [{
757
+ label: 'Score',
758
+ data: [0, 0, 0],
759
+ backgroundColor: 'rgba(67, 97, 238, 0.8)'
760
+ }]
761
+ },
762
+ options: {
763
+ responsive: true,
764
+ maintainAspectRatio: false,
765
+ scales: {
766
+ y: {
767
+ beginAtZero: true,
768
+ max: 100
769
+ }
770
+ }
771
+ }
772
+ });
773
+
774
+ // Legal Analysis Chart
775
+ const legalCtx = document.getElementById('legalChart').getContext('2d');
776
+ legalChart = new Chart(legalCtx, {
777
+ type: 'radar',
778
+ data: {
779
+ labels: ['Documentation', 'Compliance', 'Risk Level'],
780
+ datasets: [{
781
+ label: 'Score',
782
+ data: [0, 0, 0],
783
+ backgroundColor: 'rgba(67, 97, 238, 0.2)',
784
+ borderColor: 'rgba(67, 97, 238, 1)',
785
+ pointBackgroundColor: 'rgba(67, 97, 238, 1)'
786
+ }]
787
+ },
788
+ options: {
789
+ responsive: true,
790
+ maintainAspectRatio: false,
791
+ scales: {
792
+ r: {
793
+ beginAtZero: true,
794
+ max: 100
795
+ }
796
+ }
797
+ }
798
+ });
799
+ }
800
+
801
+ // Handle form submission
802
+ document.getElementById('propertyForm').addEventListener('submit', async (e) => {
803
+ e.preventDefault();
804
+
805
+ // Show loading indicator
806
+ document.getElementById('loading').style.display = 'block';
807
+ document.getElementById('results').style.display = 'none';
808
+
809
+ try {
810
+ const formData = new FormData(e.target);
811
+
812
+ // Append uploaded images
813
+ uploadedImages.forEach((file, index) => {
814
+ formData.append(`image_${index}`, file);
815
+ });
816
+
817
+ // Append uploaded PDFs
818
+ uploadedPDFs.forEach((file, index) => {
819
+ formData.append(`document_${index}`, file);
820
+ });
821
+
822
+ // Send request to backend
823
+ const response = await fetch('/verify', {
824
+ method: 'POST',
825
+ body: formData
826
+ });
827
+
828
+ if (!response.ok) {
829
+ throw new Error('Verification failed');
830
+ }
831
+
832
+ const data = await response.json();
833
+ displayResults(data);
834
+
835
+ } catch (error) {
836
+ console.error('Error:', error);
837
+ alert('An error occurred during verification. Please try again.');
838
+ } finally {
839
+ document.getElementById('loading').style.display = 'none';
840
+ }
841
+ });
842
+
843
+ // Handle file uploads
844
+ document.getElementById('images').addEventListener('change', (e) => {
845
+ const files = Array.from(e.target.files);
846
+ uploadedImages = files;
847
+ displayImagePreviews(files);
848
+ });
849
+
850
+ document.getElementById('documents').addEventListener('change', (e) => {
851
+ const files = Array.from(e.target.files);
852
+ uploadedPDFs = files;
853
+ displayPDFPreviews(files);
854
+ });
855
+
856
+ // Display image previews
857
+ function displayImagePreviews(files) {
858
+ const preview = document.getElementById('imagePreview');
859
+ preview.innerHTML = '';
860
+
861
+ files.forEach((file, index) => {
862
+ const reader = new FileReader();
863
+ reader.onload = (e) => {
864
+ const div = document.createElement('div');
865
+ div.className = 'preview-item';
866
+ div.innerHTML = `
867
+ <img src="${e.target.result}" alt="Preview">
868
+ <button class="preview-remove" onclick="removeImage(${index})">&times;</button>
869
+ `;
870
+ preview.appendChild(div);
871
+ };
872
+ reader.readAsDataURL(file);
873
+ });
874
+ }
875
+
876
+ // Display PDF previews
877
+ function displayPDFPreviews(files) {
878
+ const preview = document.getElementById('pdfPreview');
879
+ preview.innerHTML = '';
880
+
881
+ files.forEach((file, index) => {
882
+ const div = document.createElement('div');
883
+ div.className = 'pdf-filename';
884
+ div.innerHTML = `
885
+ ${file.name}
886
+ <button class="preview-remove" onclick="removePDF(${index})">&times;</button>
887
+ `;
888
+ preview.appendChild(div);
889
+ });
890
+ }
891
+
892
+ // Remove image
893
+ function removeImage(index) {
894
+ uploadedImages.splice(index, 1);
895
+ displayImagePreviews(uploadedImages);
896
+ }
897
+
898
+ // Remove PDF
899
+ function removePDF(index) {
900
+ uploadedPDFs.splice(index, 1);
901
+ displayPDFPreviews(uploadedPDFs);
902
+ }
903
+
904
+ // Display results
905
+ function displayResults(data) {
906
+ // Show results container
907
+ document.getElementById('results').style.display = 'block';
908
+
909
+ // Update trust score
910
+ const trustScore = data.trust_score || 0;
911
+ document.getElementById('trustScore').textContent = trustScore;
912
+ document.getElementById('trustScoreBar').style.width = `${trustScore}%`;
913
+
914
+ // Update fraud analysis
915
+ if (data.fraud_analysis) {
916
+ updateFraudAnalysis(data.fraud_analysis);
917
+ }
918
+
919
+ // Update quality assessment
920
+ if (data.quality_assessment) {
921
+ updateQualityAssessment(data.quality_assessment);
922
+ }
923
+
924
+ // Update location analysis
925
+ if (data.location_analysis) {
926
+ updateLocationAnalysis(data.location_analysis);
927
+ }
928
+
929
+ // Update price analysis
930
+ if (data.price_analysis) {
931
+ updatePriceAnalysis(data.price_analysis);
932
+ }
933
+
934
+ // Update legal analysis
935
+ if (data.legal_analysis) {
936
+ updateLegalAnalysis(data.legal_analysis);
937
+ }
938
+
939
+ // Update final verdict
940
+ updateFinalVerdict(data);
941
+ }
942
+
943
+ // Update fraud analysis
944
+ function updateFraudAnalysis(analysis) {
945
+ // Update chart
946
+ fraudChart.data.datasets[0].data = [
947
+ analysis.low_risk || 0,
948
+ analysis.medium_risk || 0,
949
+ analysis.high_risk || 0
950
+ ];
951
+ fraudChart.update();
952
+
953
+ // Update details
954
+ const details = document.getElementById('fraudDetails');
955
+ details.innerHTML = `
956
+ <div class="alert ${getAlertClass(analysis.alert_level)}">
957
+ ${analysis.summary || 'No fraud indicators detected.'}
958
+ </div>
959
+ ${analysis.indicators ? `
960
+ <div class="red-flags">
961
+ <h4>Risk Indicators:</h4>
962
+ <ul>
963
+ ${analysis.indicators.map(indicator => `
964
+ <li>${indicator}</li>
965
+ `).join('')}
966
+ </ul>
967
+ </div>
968
+ ` : ''}
969
+ `;
970
+ }
971
+
972
+ // Update quality assessment
973
+ function updateQualityAssessment(assessment) {
974
+ // Update chart
975
+ qualityChart.data.datasets[0].data = [
976
+ assessment.completeness || 0,
977
+ assessment.accuracy || 0,
978
+ assessment.consistency || 0
979
+ ];
980
+ qualityChart.update();
981
+
982
+ // Update details
983
+ const details = document.getElementById('qualityDetails');
984
+ details.innerHTML = `
985
+ <div class="explanation-box">
986
+ <div class="explanation-title">Quality Assessment</div>
987
+ <p>${assessment.summary || 'No quality issues detected.'}</p>
988
+ </div>
989
+ ${assessment.suggestions ? `
990
+ <div class="suggestion-list">
991
+ ${assessment.suggestions.map(suggestion => `
992
+ <div class="suggestion-item">${suggestion}</div>
993
+ `).join('')}
994
+ </div>
995
+ ` : ''}
996
+ `;
997
+ }
998
+
999
+ // Update location analysis
1000
+ function updateLocationAnalysis(analysis) {
1001
+ const details = document.getElementById('locationDetails');
1002
+ details.innerHTML = `
1003
+ <div class="explanation-box">
1004
+ <div class="explanation-title">Location Analysis</div>
1005
+ <p>${analysis.summary || 'Location verified.'}</p>
1006
+ </div>
1007
+ ${analysis.verification ? `
1008
+ <div class="verification-scores">
1009
+ ${Object.entries(analysis.verification).map(([key, value]) => `
1010
+ <div class="score-item">
1011
+ <div class="score-label">${formatLabel(key)}</div>
1012
+ <div class="score-bar-container">
1013
+ <div class="score-bar" style="--score: ${value}%"></div>
1014
+ <div class="score-value">${value}%</div>
1015
+ </div>
1016
+ </div>
1017
+ `).join('')}
1018
+ </div>
1019
+ ` : ''}
1020
+ `;
1021
+ }
1022
+
1023
+ // Update price analysis
1024
+ function updatePriceAnalysis(analysis) {
1025
+ const details = document.getElementById('priceDetails');
1026
+ details.innerHTML = `
1027
+ <div class="explanation-box">
1028
+ <div class="explanation-title">Price Analysis</div>
1029
+ <p>${analysis.summary || 'Price analysis completed.'}</p>
1030
+ </div>
1031
+ ${analysis.market_trends ? `
1032
+ <div class="property-summary">
1033
+ <h4>Market Trends</h4>
1034
+ <p>${analysis.market_trends}</p>
1035
+ </div>
1036
+ ` : ''}
1037
+ ${analysis.price_factors ? `
1038
+ <div class="property-summary">
1039
+ <h4>Price Factors</h4>
1040
+ <ul>
1041
+ ${analysis.price_factors.map(factor => `
1042
+ <li>${factor}</li>
1043
+ `).join('')}
1044
+ </ul>
1045
+ </div>
1046
+ ` : ''}
1047
+ ${analysis.risk_indicators ? `
1048
+ <div class="red-flags">
1049
+ <h4>Risk Indicators</h4>
1050
+ <ul>
1051
+ ${analysis.risk_indicators.map(indicator => `
1052
+ <li>${indicator}</li>
1053
+ `).join('')}
1054
+ </ul>
1055
+ </div>
1056
+ ` : ''}
1057
+ `;
1058
+ }
1059
+
1060
+ // Update legal analysis
1061
+ function updateLegalAnalysis(analysis) {
1062
+ // Update chart
1063
+ legalChart.data.datasets[0].data = [
1064
+ analysis.documentation_score || 0,
1065
+ analysis.compliance_score || 0,
1066
+ analysis.risk_score || 0
1067
+ ];
1068
+ legalChart.update();
1069
+
1070
+ // Update details
1071
+ const details = document.getElementById('legalDetails');
1072
+ details.innerHTML = `
1073
+ <div class="explanation-box">
1074
+ <div class="explanation-title">Legal Analysis</div>
1075
+ <p>${analysis.summary || 'Legal analysis completed.'}</p>
1076
+ </div>
1077
+ ${analysis.issues ? `
1078
+ <div class="red-flags">
1079
+ <h4>Legal Issues</h4>
1080
+ <ul>
1081
+ ${analysis.issues.map(issue => `
1082
+ <li>${issue}</li>
1083
+ `).join('')}
1084
+ </ul>
1085
+ </div>
1086
+ ` : ''}
1087
+ ${analysis.recommendations ? `
1088
+ <div class="suggestion-list">
1089
+ ${analysis.recommendations.map(recommendation => `
1090
+ <div class="suggestion-item">${recommendation}</div>
1091
+ `).join('')}
1092
+ </div>
1093
+ ` : ''}
1094
+ `;
1095
+ }
1096
+
1097
+ // Update final verdict
1098
+ function updateFinalVerdict(data) {
1099
+ const verdictBox = document.getElementById('verdictBox');
1100
+ const verdictText = document.getElementById('verdictText');
1101
+ const verdictDetails = document.getElementById('verdictDetails');
1102
+
1103
+ // Determine verdict class and icon
1104
+ let verdictClass = 'verdict-legitimate';
1105
+ let verdictIcon = '✅';
1106
+
1107
+ if (data.trust_score < 60) {
1108
+ verdictClass = 'verdict-fraudulent';
1109
+ verdictIcon = '❌';
1110
+ } else if (data.trust_score < 80) {
1111
+ verdictClass = 'verdict-suspicious';
1112
+ verdictIcon = '⚠️';
1113
+ }
1114
+
1115
+ // Update verdict box
1116
+ verdictBox.className = `verdict-box ${verdictClass}`;
1117
+ verdictBox.querySelector('.verdict-icon').textContent = verdictIcon;
1118
+ verdictText.textContent = data.verdict || 'Analysis complete';
1119
+
1120
+ // Update verdict details
1121
+ verdictDetails.innerHTML = `
1122
+ <div class="verification-scores">
1123
+ ${Object.entries(data.scores || {}).map(([key, value]) => `
1124
+ <div class="score-item">
1125
+ <div class="score-label">${formatLabel(key)}</div>
1126
+ <div class="score-bar-container">
1127
+ <div class="score-bar" style="--score: ${value}%"></div>
1128
+ <div class="score-value">${value}%</div>
1129
+ </div>
1130
+ </div>
1131
+ `).join('')}
1132
+ </div>
1133
+ `;
1134
+ }
1135
+
1136
+ // Helper functions
1137
+ function getAlertClass(level) {
1138
+ switch (level?.toLowerCase()) {
1139
+ case 'high':
1140
+ return 'alert-danger';
1141
+ case 'medium':
1142
+ return 'alert-warning';
1143
+ case 'low':
1144
+ return 'alert-success';
1145
+ default:
1146
+ return 'alert-success';
1147
+ }
1148
+ }
1149
+
1150
+ function formatLabel(key) {
1151
+ return key.split('_')
1152
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
1153
+ .join(' ');
1154
+ }
1155
+
1156
+ // Initialize charts when the page loads
1157
+ document.addEventListener('DOMContentLoaded', initializeCharts);
1158
+ </script>
1159
+ </body>
1160
+ </html>
templates/newindex.html ADDED
@@ -0,0 +1,1916 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Property Verifier</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
+ <style>
10
+ :root {
11
+ --primary: #4361ee;
12
+ --secondary: #3f37c9;
13
+ --success: #4cc9f0;
14
+ --danger: #f72585;
15
+ --warning: #f8961e;
16
+ --info: #4895ef;
17
+ --light: #f8f9fa;
18
+ --dark: #212529;
19
+ --gray: #6c757d;
20
+ --border-radius: 12px;
21
+ --box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
22
+ }
23
+
24
+ * {
25
+ margin: 0;
26
+ padding: 0;
27
+ box-sizing: border-box;
28
+ }
29
+
30
+ body {
31
+ font-family: 'Poppins', sans-serif;
32
+ background-color: #f5f7fa;
33
+ color: #333;
34
+ line-height: 1.6;
35
+ padding: 20px;
36
+ }
37
+
38
+ .container {
39
+ max-width: 1200px;
40
+ margin: 0 auto;
41
+ }
42
+
43
+ header {
44
+ text-align: center;
45
+ margin-bottom: 30px;
46
+ }
47
+
48
+ h1 {
49
+ font-size: 2.5rem;
50
+ color: var(--primary);
51
+ margin-bottom: 10px;
52
+ }
53
+
54
+ .subtitle {
55
+ font-size: 1.1rem;
56
+ color: var(--gray);
57
+ }
58
+
59
+ .card {
60
+ background: white;
61
+ border-radius: var(--border-radius);
62
+ box-shadow: var(--box-shadow);
63
+ padding: 25px;
64
+ margin-bottom: 25px;
65
+ }
66
+
67
+ .card-header {
68
+ border-bottom: 1px solid #eee;
69
+ padding-bottom: 15px;
70
+ margin-bottom: 20px;
71
+ display: flex;
72
+ justify-content: space-between;
73
+ align-items: center;
74
+ }
75
+
76
+ .card-title {
77
+ font-size: 1.5rem;
78
+ color: var(--dark);
79
+ font-weight: 600;
80
+ }
81
+
82
+ .form-grid {
83
+ display: grid;
84
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
85
+ gap: 20px;
86
+ }
87
+
88
+ .form-group {
89
+ margin-bottom: 20px;
90
+ }
91
+
92
+ .form-label {
93
+ display: block;
94
+ margin-bottom: 8px;
95
+ font-weight: 500;
96
+ color: var(--dark);
97
+ }
98
+
99
+ .form-control {
100
+ width: 100%;
101
+ padding: 12px 15px;
102
+ border: 1px solid #ddd;
103
+ border-radius: var(--border-radius);
104
+ font-size: 1rem;
105
+ transition: border-color 0.3s;
106
+ }
107
+
108
+ .form-control:focus {
109
+ border-color: var(--primary);
110
+ outline: none;
111
+ box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
112
+ }
113
+
114
+ textarea.form-control {
115
+ min-height: 100px;
116
+ resize: vertical;
117
+ }
118
+
119
+ .btn {
120
+ display: inline-block;
121
+ padding: 12px 24px;
122
+ background-color: var(--primary);
123
+ color: white;
124
+ border: none;
125
+ border-radius: var(--border-radius);
126
+ font-size: 1rem;
127
+ font-weight: 500;
128
+ cursor: pointer;
129
+ transition: all 0.3s;
130
+ }
131
+
132
+ .btn:hover {
133
+ background-color: var(--secondary);
134
+ transform: translateY(-2px);
135
+ }
136
+
137
+ .btn-block {
138
+ display: block;
139
+ width: 100%;
140
+ }
141
+
142
+ .section-title {
143
+ font-size: 1.2rem;
144
+ color: var(--primary);
145
+ margin-bottom: 15px;
146
+ font-weight: 600;
147
+ }
148
+
149
+ .results-container {
150
+ display: none;
151
+ margin-top: 30px;
152
+ }
153
+
154
+ .results-grid {
155
+ display: grid;
156
+ grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
157
+ gap: 25px;
158
+ }
159
+
160
+
161
+ .result-card {
162
+ background: white;
163
+ border-radius: var(--border-radius);
164
+ box-shadow: var(--box-shadow);
165
+ padding: 20px;
166
+ height: 100%;
167
+ }
168
+
169
+ .result-header {
170
+ display: flex;
171
+ align-items: center;
172
+ margin-bottom: 15px;
173
+ }
174
+
175
+ .result-icon {
176
+ width: 40px;
177
+ height: 40px;
178
+ background-color: var(--light);
179
+ border-radius: 50%;
180
+ display: flex;
181
+ align-items: center;
182
+ justify-content: center;
183
+ margin-right: 15px;
184
+ }
185
+
186
+ .result-title {
187
+ font-size: 1.2rem;
188
+ font-weight: 600;
189
+ color: var(--dark);
190
+ }
191
+
192
+ .trust-score {
193
+ text-align: center;
194
+ padding: 20px;
195
+ }
196
+
197
+ .score-value {
198
+ font-size: 3rem;
199
+ font-weight: 700;
200
+ color: var(--primary);
201
+ }
202
+
203
+ .score-label {
204
+ font-size: 1rem;
205
+ color: var(--gray);
206
+ }
207
+
208
+ .progress-container {
209
+ margin: 15px 0;
210
+ }
211
+
212
+ .progress-bar {
213
+ height: 10px;
214
+ background-color: #eee;
215
+ border-radius: 5px;
216
+ overflow: hidden;
217
+ }
218
+
219
+ .progress-fill {
220
+ height: 100%;
221
+ background-color: var(--primary);
222
+ border-radius: 5px;
223
+ transition: width 0.5s ease-in-out;
224
+ }
225
+
226
+ .alert {
227
+ padding: 15px;
228
+ border-radius: var(--border-radius);
229
+ margin-bottom: 20px;
230
+ font-weight: 500;
231
+ }
232
+
233
+ .alert-danger {
234
+ background-color: rgba(247, 37, 133, 0.1);
235
+ color: var(--danger);
236
+ border-left: 4px solid var(--danger);
237
+ }
238
+
239
+ .alert-warning {
240
+ background-color: rgba(248, 150, 30, 0.1);
241
+ color: var(--warning);
242
+ border-left: 4px solid var(--warning);
243
+ }
244
+
245
+ .alert-success {
246
+ background-color: rgba(76, 201, 240, 0.1);
247
+ color: var(--success);
248
+ border-left: 4px solid var(--success);
249
+ }
250
+
251
+ .suggestion-list {
252
+ list-style-type: none;
253
+ padding: 0;
254
+ }
255
+
256
+ .suggestion-item {
257
+ padding: 10px 15px;
258
+ background-color: rgba(67, 97, 238, 0.05);
259
+ border-radius: var(--border-radius);
260
+ margin-bottom: 10px;
261
+ border-left: 3px solid var(--primary);
262
+ }
263
+
264
+ .image-preview {
265
+ display: flex;
266
+ flex-wrap: wrap;
267
+ gap: 10px;
268
+ margin-top: 10px;
269
+ }
270
+
271
+ .preview-item {
272
+ width: 100px;
273
+ height: 100px;
274
+ border-radius: 8px;
275
+ overflow: hidden;
276
+ position: relative;
277
+ }
278
+
279
+ .preview-item img {
280
+ width: 100%;
281
+ height: 100%;
282
+ object-fit: cover;
283
+ }
284
+
285
+ .preview-remove {
286
+ position: absolute;
287
+ top: 5px;
288
+ right: 5px;
289
+ background: rgba(0, 0, 0, 0.5);
290
+ color: white;
291
+ border: none;
292
+ border-radius: 50%;
293
+ width: 20px;
294
+ height: 20px;
295
+ display: flex;
296
+ align-items: center;
297
+ justify-content: center;
298
+ cursor: pointer;
299
+ }
300
+
301
+ .loading {
302
+ display: none;
303
+ text-align: center;
304
+ padding: 30px;
305
+ }
306
+
307
+ .spinner {
308
+ width: 50px;
309
+ height: 50px;
310
+ border: 5px solid rgba(67, 97, 238, 0.1);
311
+ border-radius: 50%;
312
+ border-top-color: var(--primary);
313
+ animation: spin 1s ease-in-out infinite;
314
+ margin: 0 auto 20px;
315
+ }
316
+
317
+ @keyframes spin {
318
+ to { transform: rotate(360deg); }
319
+ }
320
+
321
+ .chart-container {
322
+ position: relative;
323
+ height: 200px;
324
+ margin-bottom: 20px;
325
+ }
326
+
327
+ .pdf-preview {
328
+ background-color: #f8f9fa;
329
+ padding: 15px;
330
+ border-radius: var(--border-radius);
331
+ margin-top: 10px;
332
+ max-height: 200px;
333
+ overflow-y: auto;
334
+ }
335
+
336
+ .pdf-filename {
337
+ font-weight: 500;
338
+ margin-bottom: 5px;
339
+ }
340
+
341
+ .image-gallery {
342
+ display: grid;
343
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
344
+ gap: 15px;
345
+ margin-top: 20px;
346
+ }
347
+
348
+ .gallery-item {
349
+ border-radius: var(--border-radius);
350
+ overflow: hidden;
351
+ box-shadow: var(--box-shadow);
352
+ aspect-ratio: 1;
353
+ }
354
+
355
+ .gallery-item img {
356
+ width: 100%;
357
+ height: 100%;
358
+ object-fit: cover;
359
+ }
360
+
361
+ .badge {
362
+ display: inline-block;
363
+ padding: 5px 10px;
364
+ border-radius: 20px;
365
+ font-size: 0.8rem;
366
+ font-weight: 500;
367
+ margin-right: 5px;
368
+ margin-bottom: 5px;
369
+ }
370
+
371
+ .badge-primary { background-color: rgba(67, 97, 238, 0.1); color: var(--primary); }
372
+ .badge-success { background-color: rgba(76, 201, 240, 0.1); color: var(--success); }
373
+ .badge-warning { background-color: rgba(248, 150, 30, 0.1); color: var(--warning); }
374
+ .badge-danger { background-color: rgba(247, 37, 133, 0.1); color: var(--danger); }
375
+
376
+ .explanation-box {
377
+ background-color: #f8f9fa;
378
+ border-radius: var(--border-radius);
379
+ padding: 15px;
380
+ margin-top: 15px;
381
+ border-left: 4px solid var(--info);
382
+ }
383
+
384
+ .explanation-title {
385
+ font-weight: 600;
386
+ color: var(--info);
387
+ margin-bottom: 10px;
388
+ }
389
+
390
+ @media (max-width: 768px) {
391
+ .form-grid, .results-grid {
392
+ grid-template-columns: 1fr;
393
+ }
394
+
395
+ .card {
396
+ padding: 15px;
397
+ }
398
+ }
399
+
400
+ .property-summary {
401
+ padding: 15px;
402
+ }
403
+
404
+ .property-details p {
405
+ margin-bottom: 8px;
406
+ }
407
+
408
+ .final-verdict {
409
+ padding: 15px;
410
+ }
411
+
412
+ .verdict-box {
413
+ display: flex;
414
+ align-items: center;
415
+ padding: 15px;
416
+ border-radius: var(--border-radius);
417
+ margin-bottom: 15px;
418
+ background-color: #f8f9fa;
419
+ }
420
+
421
+ .verdict-icon {
422
+ font-size: 2rem;
423
+ margin-right: 15px;
424
+ }
425
+
426
+ .verdict-text {
427
+ font-size: 1.2rem;
428
+ font-weight: 600;
429
+ }
430
+
431
+ .verdict-legitimate {
432
+ background-color: rgba(76, 201, 240, 0.1);
433
+ border-left: 4px solid var(--success);
434
+ }
435
+
436
+ .verdict-suspicious {
437
+ background-color: rgba(248, 150, 30, 0.1);
438
+ border-left: 4px solid var(--warning);
439
+ }
440
+
441
+ .verdict-fraudulent {
442
+ background-color: rgba(247, 37, 133, 0.1);
443
+ border-left: 4px solid var(--danger);
444
+ }
445
+
446
+ .verification-scores {
447
+ padding: 15px;
448
+ }
449
+
450
+ .score-item {
451
+ margin-bottom: 15px;
452
+ }
453
+
454
+ .score-label {
455
+ font-weight: 500;
456
+ margin-bottom: 5px;
457
+ }
458
+
459
+ .score-bar-container {
460
+ display: flex;
461
+ align-items: center;
462
+ }
463
+
464
+ .score-bar {
465
+ height: 10px;
466
+ background-color: #e9ecef;
467
+ border-radius: 5px;
468
+ flex-grow: 1;
469
+ margin-right: 10px;
470
+ position: relative;
471
+ overflow: hidden;
472
+ }
473
+
474
+ .score-bar::before {
475
+ content: '';
476
+ position: absolute;
477
+ top: 0;
478
+ left: 0;
479
+ height: 100%;
480
+ background-color: var(--primary);
481
+ border-radius: 5px;
482
+ width: 0%;
483
+ transition: width 0.5s ease;
484
+ }
485
+
486
+ .score-value {
487
+ font-weight: 600;
488
+ min-width: 40px;
489
+ text-align: right;
490
+ }
491
+
492
+ .red-flags {
493
+ padding: 15px;
494
+ }
495
+ </style>
496
+ </head>
497
+ <body>
498
+ <div class="container">
499
+ <header>
500
+ <h1>AI Property Verifier & Fraud Detection</h1>
501
+ <p class="subtitle">Powered by advanced AI models to verify property listings and detect potential fraud</p>
502
+ </header>
503
+
504
+ <div class="card">
505
+ <div class="card-header">
506
+ <h2 class="card-title">Property Details</h2>
507
+ </div>
508
+
509
+ <form id="propertyForm">
510
+ <div class="section-title">Basic Information</div>
511
+ <div class="form-grid">
512
+ <div class="form-group">
513
+ <label class="form-label" for="propertyName">Property Name</label>
514
+ <input type="text" class="form-control" id="propertyName" name="property_name" required>
515
+ </div>
516
+
517
+ <div class="form-group">
518
+ <label class="form-label" for="propertyType">Property Type</label>
519
+ <select class="form-control" id="propertyType" name="property_type" required>
520
+ <option value="">Select Type</option>
521
+ <option value="Apartment">Apartment</option>
522
+ <option value="House">House</option>
523
+ <option value="Condo">Condo</option>
524
+ <option value="Townhouse">Townhouse</option>
525
+ <option value="Villa">Villa</option>
526
+ <option value="Land">Land</option>
527
+ <option value="Commercial">Commercial</option>
528
+ <option value="Other">Other</option>
529
+ </select>
530
+ </div>
531
+
532
+ <div class="form-group">
533
+ <label class="form-label" for="status">Status</label>
534
+ <select class="form-control" id="status" name="status" required>
535
+ <option value="">Select Status</option>
536
+ <option value="For Sale">For Sale</option>
537
+ <option value="For Rent">For Rent</option>
538
+ <option value="Sold">Sold</option>
539
+ <option value="Under Contract">Under Contract</option>
540
+ <option value="Pending">Pending</option>
541
+ </select>
542
+ </div>
543
+ </div>
544
+
545
+ <div class="form-group">
546
+ <label class="form-label" for="description">Property Description</label>
547
+ <textarea class="form-control" id="description" name="description" rows="4" required></textarea>
548
+ </div>
549
+
550
+ <div class="section-title">Location Details</div>
551
+ <div class="form-grid">
552
+ <div class="form-group">
553
+ <label class="form-label" for="address">Address</label>
554
+ <input type="text" class="form-control" id="address" name="address" required>
555
+ </div>
556
+
557
+ <div class="form-group">
558
+ <label class="form-label" for="city">City</label>
559
+ <input type="text" class="form-control" id="city" name="city" required>
560
+ </div>
561
+
562
+ <div class="form-group">
563
+ <label class="form-label" for="state">State/Province</label>
564
+ <input type="text" class="form-control" id="state" name="state" required>
565
+ </div>
566
+
567
+ <div class="form-group">
568
+ <label class="form-label" for="country">Country</label>
569
+ <input type="text" class="form-control" id="country" name="country" required>
570
+ </div>
571
+
572
+ <div class="form-group">
573
+ <label class="form-label" for="zip">Zip/Postal Code</label>
574
+ <input type="text" class="form-control" id="zip" name="zip" required>
575
+ </div>
576
+
577
+ <div class="form-group">
578
+ <label class="form-label" for="latitude">Latitude</label>
579
+ <input type="text" class="form-control" id="latitude" name="latitude" placeholder="e.g. 40.7128">
580
+ </div>
581
+
582
+ <div class="form-group">
583
+ <label class="form-label" for="longitude">Longitude</label>
584
+ <input type="text" class="form-control" id="longitude" name="longitude" placeholder="e.g. -74.0060">
585
+ </div>
586
+ </div>
587
+
588
+ <div class="section-title">Property Specifications</div>
589
+ <div class="form-grid">
590
+ <div class="form-group">
591
+ <label class="form-label" for="bedrooms">Bedrooms</label>
592
+ <input type="number" class="form-control" id="bedrooms" name="bedrooms" min="0">
593
+ </div>
594
+
595
+ <div class="form-group">
596
+ <label class="form-label" for="bathrooms">Bathrooms</label>
597
+ <input type="number" class="form-control" id="bathrooms" name="bathrooms" min="0" step="0.5">
598
+ </div>
599
+
600
+ <div class="form-group">
601
+ <label class="form-label" for="totalRooms">Total Rooms</label>
602
+ <input type="number" class="form-control" id="totalRooms" name="total_rooms" min="0">
603
+ </div>
604
+
605
+ <div class="form-group">
606
+ <label class="form-label" for="yearBuilt">Year Built</label>
607
+ <input type="number" class="form-control" id="yearBuilt" name="year_built" min="1800" max="2100">
608
+ </div>
609
+
610
+ <div class="form-group">
611
+ <label class="form-label" for="parking">Parking Spaces</label>
612
+ <input type="number" class="form-control" id="parking" name="parking" min="0">
613
+ </div>
614
+
615
+ <div class="form-group">
616
+ <label class="form-label" for="sqFt">Square Feet</label>
617
+ <input type="text" class="form-control" id="sqFt" name="sq_ft" min="0">
618
+ </div>
619
+
620
+ <div class="form-group">
621
+ <label class="form-label" for="marketValue">Market Value</label>
622
+ <input type="text" class="form-control" id="marketValue" name="market_value" min="0">
623
+ </div>
624
+ </div>
625
+
626
+ <div class="form-group">
627
+ <label class="form-label" for="amenities">Amenities (comma separated)</label>
628
+ <input type="text" class="form-control" id="amenities" name="amenities" placeholder="e.g. Pool, Gym, Garden, Garage">
629
+ </div>
630
+
631
+ <div class="form-group">
632
+ <label class="form-label" for="nearbyLandmarks">Nearby Landmarks</label>
633
+ <input type="text" class="form-control" id="nearbyLandmarks" name="nearby_landmarks" placeholder="e.g. School, Hospital, Park, Shopping Mall">
634
+ </div>
635
+
636
+ <div class="form-group">
637
+ <label class="form-label" for="legalDetails">Legal & Infrastructure Details</label>
638
+ <textarea class="form-control" id="legalDetails" name="legal_details" rows="3" placeholder="Include zoning, permits, utilities, etc."></textarea>
639
+ </div>
640
+
641
+ <div class="section-title">Documents & Images</div>
642
+ <div class="form-group">
643
+ <label class="form-label" for="images">Upload Images (JPG/PNG)</label>
644
+ <input type="file" class="form-control" id="images" name="images" accept="image/jpeg, image/png" multiple>
645
+ <div class="image-preview" id="imagePreview"></div>
646
+ </div>
647
+
648
+ <div class="form-group">
649
+ <label class="form-label" for="documents">Upload Documents (PDF)</label>
650
+ <input type="file" class="form-control" id="documents" name="documents" accept="application/pdf" multiple>
651
+ <div id="pdfPreview"></div>
652
+ </div>
653
+
654
+ <div class="form-group">
655
+ <button type="submit" class="btn btn-block" id="submitBtn">Verify Property with AI</button>
656
+ </div>
657
+ </form>
658
+ </div>
659
+
660
+ <div class="loading" id="loadingIndicator">
661
+ <div class="spinner"></div>
662
+ <p>AI models are analyzing your property data...</p>
663
+ <p class="subtitle">This may take a moment as we're processing multiple AI models</p>
664
+ </div>
665
+
666
+ <div class="results-container" id="resultsContainer">
667
+ <div class="card">
668
+ <div class="card-header">
669
+ <h2 class="card-title">AI Verification Results</h2>
670
+ </div>
671
+
672
+ <div class="results-grid">
673
+ <div class="result-card">
674
+ <div class="result-header">
675
+ <div class="result-icon">🏠</div>
676
+ <div class="result-title">Property Summary</div>
677
+ </div>
678
+ <div class="property-summary">
679
+ <h3 id="propertyTitle">Property Details</h3>
680
+ <div class="property-details">
681
+ <p><strong>Name:</strong> <span id="summaryName"></span></p>
682
+ <p><strong>Type:</strong> <span id="summaryType"></span></p>
683
+ <p><strong>Status:</strong> <span id="summaryStatus"></span></p>
684
+ <p><strong>Location:</strong> <span id="summaryLocation"></span></p>
685
+ <p><strong>Price:</strong> <span id="summaryPrice"></span></p>
686
+ <p><strong>Size:</strong> <span id="summarySize"></span></p>
687
+ <p><strong>Bedrooms/Bathrooms:</strong> <span id="summaryRooms"></span></p>
688
+ </div>
689
+ </div>
690
+ </div>
691
+
692
+ <div class="result-card">
693
+ <div class="result-header">
694
+ <div class="result-icon">⚠️</div>
695
+ <div class="result-title">Final Verdict</div>
696
+ </div>
697
+ <div class="final-verdict" id="finalVerdict">
698
+ <div class="verdict-box" id="verdictBox">
699
+ <div class="verdict-icon" id="verdictIcon">⏳</div>
700
+ <div class="verdict-text" id="verdictText">Analysis in progress...</div>
701
+ </div>
702
+ <div class="verdict-reasons">
703
+ <h4>Key Findings:</h4>
704
+ <ul id="verdictReasons" class="suggestion-list">
705
+ <!-- Will be populated by JavaScript -->
706
+ </ul>
707
+ </div>
708
+ </div>
709
+ </div>
710
+
711
+ <div class="result-card">
712
+ <div class="result-header">
713
+ <div class="result-icon">🔍</div>
714
+ <div class="result-title">Detailed Verification</div>
715
+ </div>
716
+ <div class="verification-scores">
717
+ <div class="score-item">
718
+ <div class="score-label">Trust Score</div>
719
+ <div class="score-bar-container">
720
+ <div class="score-bar" id="trustBar"></div>
721
+ <div class="score-value" id="trustValue">--</div>
722
+ </div>
723
+ </div>
724
+ <div class="score-item">
725
+ <div class="score-label">Image Authenticity</div>
726
+ <div class="score-bar-container">
727
+ <div class="score-bar" id="imageBar"></div>
728
+ <div class="score-value" id="imageValue">--</div>
729
+ </div>
730
+ </div>
731
+ <div class="score-item">
732
+ <div class="score-label">Document Verification</div>
733
+ <div class="score-bar-container">
734
+ <div class="score-bar" id="documentBar"></div>
735
+ <div class="score-value" id="documentValue">--</div>
736
+ </div>
737
+ </div>
738
+ <div class="score-item">
739
+ <div class="score-label">Content Quality</div>
740
+ <div class="score-bar-container">
741
+ <div class="score-bar" id="contentBar"></div>
742
+ <div class="score-value" id="contentValue">--</div>
743
+ </div>
744
+ </div>
745
+ <div class="score-item">
746
+ <div class="score-label">Location Accuracy</div>
747
+ <div class="score-bar-container">
748
+ <div class="score-bar" id="locationBar"></div>
749
+ <div class="score-value" id="locationValue">--</div>
750
+ </div>
751
+ </div>
752
+ </div>
753
+ </div>
754
+
755
+ <div class="result-card">
756
+ <div class="result-header">
757
+ <div class="result-icon">🚩</div>
758
+ <div class="result-title">Red Flags</div>
759
+ </div>
760
+ <div class="red-flags">
761
+ <ul id="redFlagsList" class="suggestion-list">
762
+ <!-- Will be populated by JavaScript -->
763
+ </ul>
764
+ </div>
765
+ </div>
766
+
767
+ <div class="result-card">
768
+ <div class="result-header">
769
+ <div class="result-icon">📊</div>
770
+ <div class="result-title">Trust Score</div>
771
+ </div>
772
+ <div class="trust-score">
773
+ <div class="score-value" id="trustScoreValue">--</div>
774
+ <div class="score-label">Trust Score</div>
775
+ <div class="progress-container">
776
+ <div class="progress-bar">
777
+ <div class="progress-fill" id="trustScoreBar" style="width: 0%"></div>
778
+ </div>
779
+ </div>
780
+ </div>
781
+ <div class="chart-container">
782
+ <canvas id="trustScoreChart"></canvas>
783
+ </div>
784
+ <div class="explanation-box">
785
+ <div class="explanation-title">AI Reasoning</div>
786
+ <div id="trustReasoning"></div>
787
+ </div>
788
+ </div>
789
+
790
+ <div class="result-card">
791
+ <div class="result-header">
792
+ <div class="result-icon">🔍</div>
793
+ <div class="result-title">Fraud Analysis</div>
794
+ </div>
795
+ <div id="fraudAlertContainer"></div>
796
+ <div class="chart-container">
797
+ <canvas id="fraudAnalysisChart"></canvas>
798
+ </div>
799
+ <div class="explanation-box">
800
+ <div class="explanation-title">AI Reasoning</div>
801
+ <div id="fraudReasoning"></div>
802
+ </div>
803
+ </div>
804
+
805
+ <div class="result-card">
806
+ <div class="result-header">
807
+ <div class="result-icon">📝</div>
808
+ <div class="result-title">AI Summary</div>
809
+ </div>
810
+ <div id="aiSummary"></div>
811
+ </div>
812
+
813
+ <div class="result-card">
814
+ <div class="result-header">
815
+ <div class="result-icon">💡</div>
816
+ <div class="result-title">Improvement Suggestions</div>
817
+ </div>
818
+ <ul class="suggestion-list" id="suggestionsList"></ul>
819
+ </div>
820
+
821
+ <div class="result-card">
822
+ <div class="result-header">
823
+ <div class="result-icon">🏠</div>
824
+ <div class="result-title">Property Quality Assessment</div>
825
+ </div>
826
+ <div id="qualityAssessment"></div>
827
+ <div class="chart-container">
828
+ <canvas id="qualityChart"></canvas>
829
+ </div>
830
+ </div>
831
+
832
+ <div class="result-card">
833
+ <div class="result-header">
834
+ <div class="result-icon">📍</div>
835
+ <div class="result-title">Location Analysis</div>
836
+ </div>
837
+ <div id="locationAnalysis"></div>
838
+ <div class="chart-container">
839
+ <canvas id="locationChart"></canvas>
840
+ </div>
841
+ </div>
842
+
843
+ <div class="result-card">
844
+ <div class="result-header">
845
+ <div class="result-icon">💰</div>
846
+ <div class="result-title">Price Analysis</div>
847
+ </div>
848
+ <div id="priceAnalysis"></div>
849
+ <div class="chart-container">
850
+ <canvas id="priceChart"></canvas>
851
+ </div>
852
+ </div>
853
+
854
+ <div class="result-card">
855
+ <div class="result-header">
856
+ <div class="result-icon">⚖️</div>
857
+ <div class="result-title">Legal Analysis</div>
858
+ </div>
859
+ <div id="legalAnalysis"></div>
860
+ <div class="chart-container">
861
+ <canvas id="legalChart"></canvas>
862
+ </div>
863
+ </div>
864
+
865
+ <div class="result-card">
866
+ <div class="result-header">
867
+ <div class="result-icon">🔄</div>
868
+ <div class="result-title">Cross-Validation Checks</div>
869
+ </div>
870
+ <div id="crossValidation"></div>
871
+ </div>
872
+
873
+ <div class="result-card">
874
+ <div class="result-header">
875
+ <div class="result-icon">📄</div>
876
+ <div class="result-title">Document Analysis</div>
877
+ </div>
878
+ <div id="documentAnalysis"></div>
879
+ <div class="chart-container">
880
+ <canvas id="documentChart"></canvas>
881
+ </div>
882
+ </div>
883
+
884
+ <div class="result-card">
885
+ <div class="result-header">
886
+ <div class="result-icon">🖼️</div>
887
+ <div class="result-title">Image Analysis</div>
888
+ </div>
889
+ <div id="imageAnalysis"></div>
890
+ <div class="image-gallery" id="imageGallery"></div>
891
+ </div>
892
+ </div>
893
+ </div>
894
+ </div>
895
+ </div>
896
+
897
+ <script>
898
+ // Global variables to store form data
899
+ let uploadedImages = [];
900
+ let uploadedPDFs = [];
901
+
902
+ // Initialize charts
903
+ let trustScoreChart;
904
+ let fraudAnalysisChart;
905
+ let qualityChart;
906
+ let locationChart;
907
+ let priceChart;
908
+ let legalChart;
909
+ let documentChart;
910
+
911
+ document.addEventListener('DOMContentLoaded', function() {
912
+ // Request location access when page loads
913
+ requestLocationAccess();
914
+
915
+ const propertyForm = document.getElementById('propertyForm');
916
+ const loadingIndicator = document.getElementById('loadingIndicator');
917
+ const resultsContainer = document.getElementById('resultsContainer');
918
+ const imageInput = document.getElementById('images');
919
+ const imagePreview = document.getElementById('imagePreview');
920
+ const pdfInput = document.getElementById('documents');
921
+ const pdfPreview = document.getElementById('pdfPreview');
922
+
923
+ // Handle image uploads
924
+ imageInput.addEventListener('change', function(e) {
925
+ handleImageUpload(e.target.files);
926
+ });
927
+
928
+ // Handle PDF uploads
929
+ pdfInput.addEventListener('change', function(e) {
930
+ handlePDFUpload(e.target.files);
931
+ });
932
+
933
+ // Form submission
934
+ propertyForm.addEventListener('submit', function(e) {
935
+ e.preventDefault();
936
+ submitForm();
937
+ });
938
+
939
+ // Initialize charts
940
+ initCharts();
941
+ });
942
+
943
+ function requestLocationAccess() {
944
+ if (navigator.geolocation) {
945
+ navigator.geolocation.getCurrentPosition(
946
+ function(position) {
947
+ const latitude = position.coords.latitude;
948
+ const longitude = position.coords.longitude;
949
+
950
+ // Update form fields with coordinates
951
+ document.getElementById('latitude').value = latitude;
952
+ document.getElementById('longitude').value = longitude;
953
+
954
+ // Send to backend to get address details
955
+ fetch('/get-location', {
956
+ method: 'POST',
957
+ headers: {
958
+ 'Content-Type': 'application/json',
959
+ },
960
+ body: JSON.stringify({
961
+ latitude: latitude,
962
+ longitude: longitude
963
+ }),
964
+ })
965
+ .then(response => response.json())
966
+ .then(data => {
967
+ if (data.status === 'success') {
968
+ // Fill form fields with location data
969
+ document.getElementById('address').value = data.address || '';
970
+ document.getElementById('city').value = data.city || '';
971
+ document.getElementById('state').value = data.state || '';
972
+ document.getElementById('country').value = data.country || '';
973
+ document.getElementById('zip').value = data.postal_code || '';
974
+
975
+ console.log('Location data loaded successfully');
976
+ } else {
977
+ console.error('Error getting location details:', data.message);
978
+ }
979
+ })
980
+ .catch(error => {
981
+ console.error('Error getting location details:', error);
982
+ });
983
+ },
984
+ function(error) {
985
+ console.error('Error getting location:', error.message);
986
+ // Show a message to the user about location access
987
+ const locationMessage = document.createElement('div');
988
+ locationMessage.className = 'alert alert-warning';
989
+ locationMessage.innerHTML = 'Location access denied or unavailable. Please enter your location manually.';
990
+ document.querySelector('.container').prepend(locationMessage);
991
+
992
+ // Auto-remove the message after 5 seconds
993
+ setTimeout(() => {
994
+ locationMessage.remove();
995
+ }, 5000);
996
+ },
997
+ {
998
+ enableHighAccuracy: true,
999
+ timeout: 5000,
1000
+ maximumAge: 0
1001
+ }
1002
+ );
1003
+ } else {
1004
+ console.error('Geolocation is not supported by this browser');
1005
+ }
1006
+ }
1007
+
1008
+ function handleImageUpload(files) {
1009
+ const imagePreview = document.getElementById('imagePreview');
1010
+
1011
+ for (let i = 0; i < files.length; i++) {
1012
+ const file = files[i];
1013
+ if (!file.type.match('image.*')) continue;
1014
+
1015
+ const reader = new FileReader();
1016
+ reader.onload = function(e) {
1017
+ const imageData = e.target.result;
1018
+ uploadedImages.push({
1019
+ name: file.name,
1020
+ data: imageData,
1021
+ file: file
1022
+ });
1023
+
1024
+ // Create preview
1025
+ const previewItem = document.createElement('div');
1026
+ previewItem.className = 'preview-item';
1027
+ previewItem.innerHTML = `
1028
+ <img src="${imageData}" alt="${file.name}">
1029
+ <button type="button" class="preview-remove" data-index="${uploadedImages.length - 1}">×</button>
1030
+ `;
1031
+ imagePreview.appendChild(previewItem);
1032
+
1033
+ // Add remove functionality
1034
+ previewItem.querySelector('.preview-remove').addEventListener('click', function() {
1035
+ const index = parseInt(this.getAttribute('data-index'));
1036
+ uploadedImages.splice(index, 1);
1037
+ imagePreview.removeChild(previewItem);
1038
+ updateImagePreviews();
1039
+ });
1040
+ };
1041
+ reader.readAsDataURL(file);
1042
+ }
1043
+ }
1044
+
1045
+ function updateImagePreviews() {
1046
+ const imagePreview = document.getElementById('imagePreview');
1047
+ imagePreview.innerHTML = '';
1048
+
1049
+ uploadedImages.forEach((image, index) => {
1050
+ const previewItem = document.createElement('div');
1051
+ previewItem.className = 'preview-item';
1052
+ previewItem.innerHTML = `
1053
+ <img src="${image.data}" alt="${image.name}">
1054
+ <button type="button" class="preview-remove" data-index="${index}">×</button>
1055
+ `;
1056
+ imagePreview.appendChild(previewItem);
1057
+
1058
+ previewItem.querySelector('.preview-remove').addEventListener('click', function() {
1059
+ uploadedImages.splice(index, 1);
1060
+ updateImagePreviews();
1061
+ });
1062
+ });
1063
+ }
1064
+
1065
+ function handlePDFUpload(files) {
1066
+ const pdfPreview = document.getElementById('pdfPreview');
1067
+
1068
+ for (let i = 0; i < files.length; i++) {
1069
+ const file = files[i];
1070
+ if (file.type !== 'application/pdf') continue;
1071
+
1072
+ uploadedPDFs.push({
1073
+ name: file.name,
1074
+ file: file
1075
+ });
1076
+
1077
+ // Create preview
1078
+ const previewItem = document.createElement('div');
1079
+ previewItem.className = 'pdf-preview';
1080
+ previewItem.innerHTML = `
1081
+ <div class="pdf-filename">${file.name}</div>
1082
+ <button type="button" class="btn" data-index="${uploadedPDFs.length - 1}">Remove</button>
1083
+ `;
1084
+ pdfPreview.appendChild(previewItem);
1085
+
1086
+ // Add remove functionality
1087
+ previewItem.querySelector('.btn').addEventListener('click', function() {
1088
+ const index = parseInt(this.getAttribute('data-index'));
1089
+ uploadedPDFs.splice(index, 1);
1090
+ pdfPreview.removeChild(previewItem);
1091
+ updatePDFPreviews();
1092
+ });
1093
+ }
1094
+ }
1095
+
1096
+ function updatePDFPreviews() {
1097
+ const pdfPreview = document.getElementById('pdfPreview');
1098
+ pdfPreview.innerHTML = '';
1099
+
1100
+ uploadedPDFs.forEach((pdf, index) => {
1101
+ const previewItem = document.createElement('div');
1102
+ previewItem.className = 'pdf-preview';
1103
+ previewItem.innerHTML = `
1104
+ <div class="pdf-filename">${pdf.name}</div>
1105
+ <button type="button" class="btn" data-index="${index}">Remove</button>
1106
+ `;
1107
+ pdfPreview.appendChild(previewItem);
1108
+
1109
+ previewItem.querySelector('.btn').addEventListener('click', function() {
1110
+ uploadedPDFs.splice(index, 1);
1111
+ updatePDFPreviews();
1112
+ });
1113
+ });
1114
+ }
1115
+
1116
+ function initCharts() {
1117
+ try {
1118
+ // Store all charts in an array for easier management
1119
+ window.charts = [];
1120
+
1121
+ // Trust Score Chart initialization
1122
+ const trustCtx = document.getElementById('trustScoreChart').getContext('2d');
1123
+ trustScoreChart = new Chart(trustCtx, {
1124
+ type: 'doughnut',
1125
+ data: {
1126
+ datasets: [{
1127
+ data: [0, 100],
1128
+ backgroundColor: [
1129
+ '#4361ee',
1130
+ '#f1f1f1'
1131
+ ],
1132
+ borderWidth: 0
1133
+ }]
1134
+ },
1135
+ options: {
1136
+ cutout: '70%',
1137
+ circumference: 180,
1138
+ rotation: -90,
1139
+ plugins: {
1140
+ legend: {
1141
+ display: false
1142
+ },
1143
+ tooltip: {
1144
+ enabled: false
1145
+ }
1146
+ },
1147
+ maintainAspectRatio: false
1148
+ }
1149
+ });
1150
+ charts.push(trustScoreChart);
1151
+
1152
+ // Fraud Analysis Chart (Bar)
1153
+ const fraudAnalysisCtx = document.getElementById('fraudAnalysisChart').getContext('2d');
1154
+ fraudAnalysisChart = new Chart(fraudAnalysisCtx, {
1155
+ type: 'bar',
1156
+ data: {
1157
+ labels: ['Legitimate', 'Suspicious', 'Fraudulent'],
1158
+ datasets: [{
1159
+ label: 'Fraud Indicators',
1160
+ data: [0, 0, 0],
1161
+ backgroundColor: [
1162
+ '#4cc9f0',
1163
+ '#f8961e',
1164
+ '#f72585'
1165
+ ],
1166
+ borderWidth: 0
1167
+ }]
1168
+ },
1169
+ options: {
1170
+ indexAxis: 'y',
1171
+ plugins: {
1172
+ legend: {
1173
+ display: false
1174
+ }
1175
+ },
1176
+ scales: {
1177
+ x: {
1178
+ beginAtZero: true,
1179
+ max: 100
1180
+ }
1181
+ },
1182
+ maintainAspectRatio: false
1183
+ }
1184
+ });
1185
+
1186
+ // Quality Assessment Chart
1187
+ const qualityCtx = document.getElementById('qualityChart').getContext('2d');
1188
+ qualityChart = new Chart(qualityCtx, {
1189
+ type: 'radar',
1190
+ data: {
1191
+ labels: ['Completeness', 'Accuracy', 'Clarity', 'Authenticity', 'Detail'],
1192
+ datasets: [{
1193
+ label: 'Quality Score',
1194
+ data: [0, 0, 0, 0, 0],
1195
+ backgroundColor: 'rgba(67, 97, 238, 0.2)',
1196
+ borderColor: '#4361ee',
1197
+ borderWidth: 2,
1198
+ pointBackgroundColor: '#4361ee'
1199
+ }]
1200
+ },
1201
+ options: {
1202
+ scales: {
1203
+ r: {
1204
+ beginAtZero: true,
1205
+ max: 100
1206
+ }
1207
+ },
1208
+ maintainAspectRatio: false
1209
+ }
1210
+ });
1211
+
1212
+ // Location Analysis Chart
1213
+ const locationCtx = document.getElementById('locationChart').getContext('2d');
1214
+ locationChart = new Chart(locationCtx, {
1215
+ type: 'pie',
1216
+ data: {
1217
+ labels: ['Complete', 'Partial', 'Missing'],
1218
+ datasets: [{
1219
+ data: [0, 0, 0],
1220
+ backgroundColor: [
1221
+ '#4cc9f0',
1222
+ '#f8961e',
1223
+ '#f72585'
1224
+ ],
1225
+ borderWidth: 0
1226
+ }]
1227
+ },
1228
+ options: {
1229
+ plugins: {
1230
+ legend: {
1231
+ position: 'bottom'
1232
+ }
1233
+ },
1234
+ maintainAspectRatio: false
1235
+ }
1236
+ });
1237
+
1238
+ // Price Analysis Chart
1239
+ const priceCtx = document.getElementById('priceChart').getContext('2d');
1240
+ priceChart = new Chart(priceCtx, {
1241
+ type: 'bar',
1242
+ data: {
1243
+ labels: ['Market Value', 'Price per Sq.Ft.'],
1244
+ datasets: [{
1245
+ label: 'Price Analysis',
1246
+ data: [0, 0],
1247
+ backgroundColor: [
1248
+ '#4361ee',
1249
+ '#4895ef'
1250
+ ],
1251
+ borderWidth: 0
1252
+ }]
1253
+ },
1254
+ options: {
1255
+ scales: {
1256
+ y: {
1257
+ beginAtZero: true
1258
+ }
1259
+ },
1260
+ maintainAspectRatio: false
1261
+ }
1262
+ });
1263
+
1264
+ // Legal Analysis Chart
1265
+ const legalCtx = document.getElementById('legalChart').getContext('2d');
1266
+ legalChart = new Chart(legalCtx, {
1267
+ type: 'doughnut',
1268
+ data: {
1269
+ labels: ['Complete', 'Partial', 'Missing'],
1270
+ datasets: [{
1271
+ data: [0, 0, 0],
1272
+ backgroundColor: [
1273
+ '#4cc9f0',
1274
+ '#f8961e',
1275
+ '#f72585'
1276
+ ],
1277
+ borderWidth: 0
1278
+ }]
1279
+ },
1280
+ options: {
1281
+ plugins: {
1282
+ legend: {
1283
+ position: 'bottom'
1284
+ }
1285
+ },
1286
+ maintainAspectRatio: false
1287
+ }
1288
+ });
1289
+
1290
+ // Document Analysis Chart
1291
+ const documentCtx = document.getElementById('documentChart').getContext('2d');
1292
+ documentChart = new Chart(documentCtx, {
1293
+ type: 'polarArea',
1294
+ data: {
1295
+ labels: ['Authentic', 'Suspicious', 'Incomplete'],
1296
+ datasets: [{
1297
+ data: [0, 0, 0],
1298
+ backgroundColor: [
1299
+ '#4cc9f0',
1300
+ '#f8961e',
1301
+ '#f72585'
1302
+ ],
1303
+ borderWidth: 0
1304
+ }]
1305
+ },
1306
+ options: {
1307
+ plugins: {
1308
+ legend: {
1309
+ position: 'bottom'
1310
+ }
1311
+ },
1312
+ maintainAspectRatio: false
1313
+ }
1314
+ });
1315
+
1316
+ // Add window resize handler for chart responsiveness
1317
+ window.addEventListener('resize', debounce(() => {
1318
+ charts.forEach(chart => {
1319
+ if (chart && typeof chart.resize === 'function') {
1320
+ chart.resize();
1321
+ }
1322
+ });
1323
+ }, 250));
1324
+
1325
+ } catch (error) {
1326
+ console.error('Error initializing charts:', error);
1327
+ document.getElementById('chartErrors').innerHTML =
1328
+ '<div class="alert alert-danger">Error initializing charts. Please refresh the page.</div>';
1329
+ }
1330
+ }
1331
+
1332
+ // Utility function for debouncing
1333
+ function debounce(func, wait) {
1334
+ let timeout;
1335
+ return function executedFunction(...args) {
1336
+ const later = () => {
1337
+ clearTimeout(timeout);
1338
+ func(...args);
1339
+ };
1340
+ clearTimeout(timeout);
1341
+ timeout = setTimeout(later, wait);
1342
+ };
1343
+ }
1344
+
1345
+ // Data validation function
1346
+ function validateAnalysisData(data) {
1347
+ return {
1348
+ trustScore: {
1349
+ score: data.trust_score?.score ?? 0,
1350
+ reasoning: data.trust_score?.reasoning ?? 'No reasoning provided'
1351
+ },
1352
+ fraudClassification: {
1353
+ alertLevel: data.fraud_classification?.alert_level ?? 'low',
1354
+ classification: data.fraud_classification?.classification ?? 'Unknown',
1355
+ confidence: data.fraud_classification?.confidence ?? 0,
1356
+ indicators: data.fraud_classification?.fraud_indicators ?? [],
1357
+ scores: data.fraud_classification?.indicator_scores ?? []
1358
+ },
1359
+ qualityAssessment: {
1360
+ assessment: data.quality_assessment?.assessment ?? 'Unknown',
1361
+ score: data.quality_assessment?.score ?? 0,
1362
+ isAiGenerated: data.quality_assessment?.is_ai_generated ?? false,
1363
+ reasoning: data.quality_assessment?.reasoning ?? 'No reasoning provided'
1364
+ },
1365
+ // ... other validations
1366
+ };
1367
+ }
1368
+
1369
+ // Safe chart update function
1370
+ function updateChart(chart, newData, options = {}) {
1371
+ try {
1372
+ if (chart && typeof chart.update === 'function') {
1373
+ chart.data = newData;
1374
+ chart.update(options);
1375
+ return true;
1376
+ }
1377
+ return false;
1378
+ } catch (error) {
1379
+ console.error('Error updating chart:', error);
1380
+ return false;
1381
+ }
1382
+ }
1383
+
1384
+ function submitForm() {
1385
+ // Show loading indicator
1386
+ document.getElementById('loadingIndicator').style.display = 'block';
1387
+ document.getElementById('resultsContainer').style.display = 'none';
1388
+
1389
+ // Create form data
1390
+ const formData = new FormData(document.getElementById('propertyForm'));
1391
+
1392
+ // Add images
1393
+ uploadedImages.forEach((image, index) => {
1394
+ formData.append('images', image.file);
1395
+ });
1396
+
1397
+ // Add PDFs
1398
+ uploadedPDFs.forEach((pdf, index) => {
1399
+ formData.append('documents', pdf.file);
1400
+ });
1401
+
1402
+ // Send to backend
1403
+ fetch('/verify', {
1404
+ method: 'POST',
1405
+ body: formData
1406
+ })
1407
+ .then(response => {
1408
+ if (!response.ok) {
1409
+ throw new Error('Network response was not ok');
1410
+ }
1411
+ return response.json();
1412
+ })
1413
+ .then(data => {
1414
+ // Hide loading indicator
1415
+ document.getElementById('loadingIndicator').style.display = 'none';
1416
+
1417
+ // Display results
1418
+ displayResults(data);
1419
+
1420
+ // Show results container
1421
+ document.getElementById('resultsContainer').style.display = 'block';
1422
+
1423
+ // Scroll to results
1424
+ document.getElementById('resultsContainer').scrollIntoView({ behavior: 'smooth' });
1425
+ })
1426
+ .catch(error => {
1427
+ console.error('Error:', error);
1428
+ document.getElementById('loadingIndicator').style.display = 'none';
1429
+ alert('An error occurred while processing your request. Please try again.');
1430
+ });
1431
+ }
1432
+ function displayResults(data) {
1433
+ console.log("Received data:", JSON.stringify(data));
1434
+
1435
+ // Validate and sanitize data
1436
+ const validatedData = validateAnalysisData(data);
1437
+
1438
+ try {
1439
+ // Display Trust Score with validated data
1440
+ const trustScore = validatedData.trustScore.score;
1441
+ document.getElementById('trustScoreValue').textContent = trustScore;
1442
+ document.getElementById('trustScoreBar').style.width = `${trustScore}%`;
1443
+ document.getElementById('trustReasoning').textContent = validatedData.trustScore.reasoning;
1444
+
1445
+ // Update Trust Score Chart safely
1446
+ updateChart(trustScoreChart, {
1447
+ datasets: [{
1448
+ data: [trustScore, 100 - trustScore]
1449
+ }]
1450
+ });
1451
+
1452
+ // Display Fraud Analysis
1453
+ const fraudLevel = validatedData.fraudClassification.alertLevel;
1454
+ const fraudContainer = document.getElementById('fraudAlertContainer');
1455
+ fraudContainer.innerHTML = '';
1456
+
1457
+ const alertClass = fraudLevel === 'high' ? 'alert-danger' :
1458
+ fraudLevel === 'medium' ? 'alert-warning' : 'alert-success';
1459
+
1460
+ const alertDiv = document.createElement('div');
1461
+ alertDiv.className = `alert ${alertClass}`;
1462
+ alertDiv.textContent = `Classification: ${validatedData.fraudClassification.classification} (Confidence: ${Math.round(validatedData.fraudClassification.confidence * 100)}%)`;
1463
+ fraudContainer.appendChild(alertDiv);
1464
+
1465
+ // Update Fraud Analysis Chart
1466
+ const fraudIndicators = validatedData.fraudClassification.indicators || [];
1467
+ const fraudScores = validatedData.fraudClassification.scores || [];
1468
+ const formattedScores = fraudScores.map(score => score * 100);
1469
+
1470
+ updateChart(fraudAnalysisChart, {
1471
+ labels: fraudIndicators,
1472
+ datasets: [{
1473
+ data: formattedScores
1474
+ }]
1475
+ });
1476
+
1477
+ document.getElementById('fraudReasoning').textContent = `This property was classified as ${validatedData.fraudClassification.classification} based on AI analysis of the listing details.`;
1478
+
1479
+ // Display AI Summary
1480
+ document.getElementById('aiSummary').textContent = data.summary || "No summary available";
1481
+
1482
+ // Display Improvement Suggestions
1483
+ const suggestionsList = document.getElementById('suggestionsList');
1484
+ suggestionsList.innerHTML = '';
1485
+
1486
+ if (data.suggestions && Array.isArray(data.suggestions) && data.suggestions.length > 0) {
1487
+ data.suggestions.forEach(suggestion => {
1488
+ if (suggestion && suggestion.trim()) {
1489
+ const li = document.createElement('li');
1490
+ li.className = 'suggestion-item';
1491
+ li.textContent = suggestion;
1492
+ suggestionsList.appendChild(li);
1493
+ }
1494
+ });
1495
+ } else {
1496
+ const li = document.createElement('li');
1497
+ li.className = 'suggestion-item';
1498
+ li.textContent = "No suggestions available";
1499
+ suggestionsList.appendChild(li);
1500
+ }
1501
+
1502
+ // Display Quality Assessment
1503
+ const qualityDiv = document.getElementById('qualityAssessment');
1504
+ if (validatedData.qualityAssessment) {
1505
+ qualityDiv.innerHTML = `
1506
+ <p><strong>Assessment:</strong> ${validatedData.qualityAssessment.assessment}</p>
1507
+ <p><strong>Quality Score:</strong> ${validatedData.qualityAssessment.score}%</p>
1508
+ <p><strong>AI Generated:</strong> ${validatedData.qualityAssessment.isAiGenerated ? 'Likely' : 'Unlikely'}</p>
1509
+ <p><strong>Reasoning:</strong> ${validatedData.qualityAssessment.reasoning}</p>
1510
+ `;
1511
+
1512
+ // Update Quality Chart
1513
+ updateChart(qualityChart, {
1514
+ datasets: [{
1515
+ data: [
1516
+ validatedData.qualityAssessment.score,
1517
+ validatedData.qualityAssessment.isAiGenerated ? 30 : 80,
1518
+ validatedData.qualityAssessment.score > 50 ? 70 : 40,
1519
+ validatedData.qualityAssessment.isAiGenerated ? 40 : 75,
1520
+ validatedData.qualityAssessment.score > 60 ? 80 : 50
1521
+ ]
1522
+ }]
1523
+ });
1524
+ } else {
1525
+ qualityDiv.innerHTML = '<p>No quality assessment available</p>';
1526
+ }
1527
+
1528
+ // Display Location Analysis
1529
+ const locationDiv = document.getElementById('locationAnalysis');
1530
+ if (data.location_analysis) {
1531
+ locationDiv.innerHTML = `
1532
+ <p><strong>Assessment:</strong> ${data.location_analysis.assessment || "Unknown"}</p>
1533
+ <p><strong>Completeness:</strong> ${data.location_analysis.completeness_score || 0}%</p>
1534
+ <p><strong>Coordinates:</strong> ${data.location_analysis.coordinates_check || "Unknown"}</p>
1535
+ <p><strong>Landmarks:</strong> ${data.location_analysis.landmarks_provided ? 'Provided' : 'Not provided'}</p>
1536
+ `;
1537
+
1538
+ // Update Location Chart
1539
+ updateChart(locationChart, {
1540
+ datasets: [{
1541
+ data: [
1542
+ data.location_analysis.completeness_score || 0,
1543
+ 100 - (data.location_analysis.completeness_score || 0),
1544
+ data.location_analysis.coordinates_check === 'coordinates_missing' ? 30 : 0
1545
+ ]
1546
+ }]
1547
+ });
1548
+ } else {
1549
+ locationDiv.innerHTML = '<p>No location analysis available</p>';
1550
+ }
1551
+
1552
+ // Display Price Analysis
1553
+ const priceDiv = document.getElementById('priceAnalysis');
1554
+ if (data.price_analysis && data.price_analysis.has_price) {
1555
+ priceDiv.innerHTML = `
1556
+ <p><strong>Assessment:</strong> ${data.price_analysis.assessment || "Unknown"}</p>
1557
+ <p><strong>Price:</strong> ₹${(data.price_analysis.price || 0).toLocaleString()}</p>
1558
+ ${data.price_analysis.has_sqft ? `<p><strong>Price per Sq.Ft.:</strong> ₹${(data.price_analysis.price_per_sqft || 0).toLocaleString(undefined, {maximumFractionDigits: 2})}</p>` : ''}
1559
+ <p><strong>Confidence:</strong> ${Math.round((data.price_analysis.confidence || 0) * 100)}%</p>
1560
+ `;
1561
+
1562
+ // Update Price Chart
1563
+ updateChart(priceChart, {
1564
+ labels: ['Market Value (thousands)', 'Price per Sq.Ft.'],
1565
+ datasets: [{
1566
+ data: [
1567
+ (data.price_analysis.price || 0) / 1000, // Scale down for better visualization
1568
+ data.price_analysis.price_per_sqft || 0
1569
+ ]
1570
+ }]
1571
+ });
1572
+ } else {
1573
+ priceDiv.innerHTML = `<p>No price information provided for analysis.</p>`;
1574
+ }
1575
+
1576
+ // Display Legal Analysis
1577
+ const legalDiv = document.getElementById('legalAnalysis');
1578
+ if (data.legal_analysis) {
1579
+ legalDiv.innerHTML = `
1580
+ <p><strong>Assessment:</strong> ${data.legal_analysis.assessment || "Unknown"}</p>
1581
+ <p><strong>Completeness:</strong> ${data.legal_analysis.completeness_score || 0}%</p>
1582
+ <p><strong>Summary:</strong> ${data.legal_analysis.summary || "No summary available"}</p>
1583
+ ${data.legal_analysis.terms_found && data.legal_analysis.terms_found.length > 0 ? `<p><strong>Legal Terms Found:</strong> ${data.legal_analysis.terms_found.join(', ')}</p>` : ''}
1584
+ ${data.legal_analysis.potential_issues ? '<p class="alert alert-warning">Potential legal issues detected</p>' : ''}
1585
+ `;
1586
+
1587
+ // Update Legal Chart
1588
+ updateChart(legalChart, {
1589
+ datasets: [{
1590
+ data: [
1591
+ data.legal_analysis.completeness_score || 0,
1592
+ 100 - (data.legal_analysis.completeness_score || 0),
1593
+ data.legal_analysis.potential_issues ? 30 : 0
1594
+ ]
1595
+ }]
1596
+ });
1597
+ } else {
1598
+ legalDiv.innerHTML = '<p>No legal analysis available</p>';
1599
+ }
1600
+
1601
+ // Display Cross-Validation Checks
1602
+ const crossValidationDiv = document.getElementById('crossValidation');
1603
+ crossValidationDiv.innerHTML = '<ul class="suggestion-list">';
1604
+
1605
+ try {
1606
+ // Safely check if cross_validation exists and is an array
1607
+ if (data && data.cross_validation && Array.isArray(data.cross_validation)) {
1608
+ // Only proceed if the array has items
1609
+ if (data.cross_validation.length > 0) {
1610
+ data.cross_validation.forEach(check => {
1611
+ if (check && typeof check === 'object') {
1612
+ const status = check.status || 'unknown';
1613
+ const checkName = check.check || 'Check';
1614
+ const message = check.message || 'No details available';
1615
+
1616
+ // Determine status class
1617
+ let statusClass = 'badge-warning'; // Default
1618
+ if (['consistent', 'valid', 'reasonable', 'match', 'likely_valid'].includes(status)) {
1619
+ statusClass = 'badge-success';
1620
+ } else if (['suspicious', 'inconsistent', 'invalid', 'no_match'].includes(status)) {
1621
+ statusClass = 'badge-danger';
1622
+ }
1623
+
1624
+ crossValidationDiv.innerHTML += `
1625
+ <li class="suggestion-item">
1626
+ <span class="badge ${statusClass}">${status}</span>
1627
+ <strong>${checkName}:</strong> ${message}
1628
+ </li>
1629
+ `;
1630
+ }
1631
+ });
1632
+ } else {
1633
+ crossValidationDiv.innerHTML += '<li class="suggestion-item">No cross-validation checks performed</li>';
1634
+ }
1635
+ } else {
1636
+ crossValidationDiv.innerHTML += '<li class="suggestion-item">No cross-validation data available</li>';
1637
+ }
1638
+ } catch (error) {
1639
+ console.error("Error displaying cross-validation:", error);
1640
+ crossValidationDiv.innerHTML += '<li class="suggestion-item">Error displaying cross-validation results</li>';
1641
+ }
1642
+
1643
+ crossValidationDiv.innerHTML += '</ul>';
1644
+
1645
+ // Display Document Analysis
1646
+ const documentDiv = document.getElementById('documentAnalysis');
1647
+ documentDiv.innerHTML = '';
1648
+
1649
+ if (data.document_analysis && data.document_analysis.pdf_count > 0) {
1650
+ documentDiv.innerHTML = `<p><strong>Documents Analyzed:</strong> ${data.document_analysis.pdf_count}</p>`;
1651
+
1652
+ data.document_analysis.pdf_analysis.forEach((pdf, index) => {
1653
+ documentDiv.innerHTML += `
1654
+ <div class="pdf-preview">
1655
+ <p><strong>Document ${index + 1}</strong></p>
1656
+ <p><strong>Type:</strong> ${pdf.document_type.classification} (${Math.round(pdf.document_type.confidence * 100)}% confidence)</p>
1657
+ <p><strong>Authenticity:</strong> ${pdf.authenticity.assessment} (${Math.round(pdf.authenticity.confidence * 100)}% confidence)</p>
1658
+ <p><strong>Summary:</strong> ${pdf.summary}</p>
1659
+ <p><strong>Contains Signatures:</strong> ${pdf.contains_signatures ? 'Yes' : 'No'}</p>
1660
+ <p><strong>Contains Dates:</strong> ${pdf.contains_dates ? 'Yes' : 'No'}</p>
1661
+ </div>
1662
+ `;
1663
+ });
1664
+
1665
+ // Update Document Chart
1666
+ let authenticCount = 0;
1667
+ let suspiciousCount = 0;
1668
+ let incompleteCount = 0;
1669
+
1670
+ data.document_analysis.pdf_analysis.forEach(pdf => {
1671
+ if (pdf.authenticity.assessment.includes('authentic')) {
1672
+ authenticCount++;
1673
+ } else if (pdf.authenticity.assessment.includes('fraudulent')) {
1674
+ suspiciousCount++;
1675
+ } else {
1676
+ incompleteCount++;
1677
+ }
1678
+ });
1679
+
1680
+ updateChart(documentChart, {
1681
+ datasets: [{
1682
+ data: [
1683
+ authenticCount,
1684
+ suspiciousCount,
1685
+ incompleteCount
1686
+ ]
1687
+ }]
1688
+ });
1689
+ } else {
1690
+ documentDiv.innerHTML = '<p>No documents were uploaded for analysis.</p>';
1691
+ }
1692
+
1693
+ // Display Image Analysis
1694
+ const imageAnalysisDiv = document.getElementById('imageAnalysis');
1695
+ const imageGallery = document.getElementById('imageGallery');
1696
+
1697
+ imageAnalysisDiv.innerHTML = '';
1698
+ imageGallery.innerHTML = '';
1699
+
1700
+ if (data.image_analysis && data.images && data.images.length > 0) {
1701
+ imageAnalysisDiv.innerHTML = `<p><strong>Images Analyzed:</strong> ${data.image_analysis.image_count}</p>`;
1702
+
1703
+ let propertyRelatedCount = 0;
1704
+ data.image_analysis.image_analysis.forEach(img => {
1705
+ if (img && img.is_property_related) {
1706
+ propertyRelatedCount++;
1707
+ }
1708
+ });
1709
+
1710
+ imageAnalysisDiv.innerHTML += `<p><strong>Property-Related Images:</strong> ${propertyRelatedCount} of ${data.image_analysis.image_count}</p>`;
1711
+
1712
+ // Display images in gallery
1713
+ data.images.forEach((imgData, index) => {
1714
+ const imgAnalysis = data.image_analysis.image_analysis[index];
1715
+ const galleryItem = document.createElement('div');
1716
+ galleryItem.className = 'gallery-item';
1717
+
1718
+ galleryItem.innerHTML = `
1719
+ <img src="data:image/jpeg;base64,${imgData}" alt="Property Image ${index + 1}">
1720
+ <div class="badge ${imgAnalysis && imgAnalysis.is_property_related ? 'badge-success' : 'badge-warning'}"
1721
+ style="position: absolute; top: 5px; right: 5px;">
1722
+ ${imgAnalysis && imgAnalysis.is_property_related ? 'Property' : 'Not Property'}
1723
+ </div>
1724
+ `;
1725
+
1726
+ imageGallery.appendChild(galleryItem);
1727
+ });
1728
+ } else {
1729
+ imageAnalysisDiv.innerHTML = '<p>No images were uploaded for analysis.</p>';
1730
+ }
1731
+
1732
+ // Update Property Summary
1733
+ document.getElementById('summaryName').textContent = document.getElementById('propertyName').value || 'Not provided';
1734
+ document.getElementById('summaryType').textContent = document.getElementById('propertyType').value || 'Not provided';
1735
+ document.getElementById('summaryStatus').textContent = document.getElementById('status').value || 'Not provided';
1736
+ document.getElementById('summaryLocation').textContent =
1737
+ `${document.getElementById('address').value || ''}, ${document.getElementById('city').value || ''}, ${document.getElementById('state').value || ''}, India`;
1738
+ document.getElementById('summaryPrice').textContent = document.getElementById('marketValue').value ? `₹${document.getElementById('marketValue').value}` : 'Not provided';
1739
+ document.getElementById('summarySize').textContent = document.getElementById('sqFt').value ? `${document.getElementById('sqFt').value} sq. ft.` : 'Not provided';
1740
+ document.getElementById('summaryRooms').textContent =
1741
+ `${document.getElementById('bedrooms').value || '0'} BHK`; // BHK is common in Indian real estate
1742
+
1743
+ // Update Final Verdict
1744
+ const verdictBox = document.getElementById('verdictBox');
1745
+ const verdictIcon = document.getElementById('verdictIcon');
1746
+ const verdictText = document.getElementById('verdictText');
1747
+
1748
+ if (fraudLevel === 'high' || trustScore < 40) {
1749
+ verdictBox.className = 'verdict-box verdict-fraudulent';
1750
+ verdictIcon.textContent = '❌';
1751
+ verdictText.textContent = 'HIGH RISK - LIKELY FRAUDULENT';
1752
+ } else if (fraudLevel === 'medium' || trustScore < 70) {
1753
+ verdictBox.className = 'verdict-box verdict-suspicious';
1754
+ verdictIcon.textContent = '⚠️';
1755
+ verdictText.textContent = 'CAUTION - SUSPICIOUS ELEMENTS';
1756
+ } else {
1757
+ verdictBox.className = 'verdict-box verdict-legitimate';
1758
+ verdictIcon.textContent = '✅';
1759
+ verdictText.textContent = 'VERIFIED REAL ESTATE LISTING';
1760
+ }
1761
+
1762
+ // Update Verdict Reasons
1763
+ const verdictReasons = document.getElementById('verdictReasons');
1764
+ verdictReasons.innerHTML = '';
1765
+
1766
+ // Add key findings based on analysis
1767
+ const findings = [];
1768
+
1769
+ if (validatedData.qualityAssessment && validatedData.qualityAssessment.isAiGenerated) {
1770
+ findings.push('Description appears to be AI-generated');
1771
+ }
1772
+
1773
+ if (data.cross_validation) {
1774
+ data.cross_validation.forEach(check => {
1775
+ if (check.status === 'inconsistent' || check.status === 'invalid' ||
1776
+ check.status === 'suspicious' || check.status === 'no_match') {
1777
+ findings.push(check.message);
1778
+ }
1779
+ });
1780
+ }
1781
+
1782
+ if (data.price_analysis && data.price_analysis.assessment === 'suspicious pricing') {
1783
+ findings.push('Price appears suspicious for this type of property');
1784
+ }
1785
+
1786
+ if (data.legal_analysis && data.legal_analysis.potential_issues) {
1787
+ findings.push('Potential legal issues detected');
1788
+ }
1789
+
1790
+ // Add at least one positive finding if the verdict is good
1791
+ if (findings.length === 0 && trustScore > 70) {
1792
+ findings.push('Property details appear consistent and legitimate');
1793
+ findings.push('No suspicious elements detected in the listing');
1794
+ }
1795
+
1796
+ // If we still have no findings, add a generic one
1797
+ if (findings.length === 0) {
1798
+ findings.push('Analysis inconclusive - insufficient information provided');
1799
+ }
1800
+
1801
+ findings.forEach(finding => {
1802
+ const li = document.createElement('li');
1803
+ li.className = 'suggestion-item';
1804
+ li.textContent = finding;
1805
+ verdictReasons.appendChild(li);
1806
+ });
1807
+
1808
+ // Update Verification Scores
1809
+ updateScoreBar('trustBar', 'trustValue', trustScore);
1810
+
1811
+ // Image authenticity score
1812
+ let imageScore = 0;
1813
+ if (data.image_analysis && data.image_analysis.image_analysis) {
1814
+ const propertyImages = data.image_analysis.image_analysis.filter(img => img && img.is_property_related);
1815
+ imageScore = data.image_analysis.image_count > 0 ?
1816
+ Math.round((propertyImages.length / data.image_analysis.image_count) * 100) : 0;
1817
+ }
1818
+ updateScoreBar('imageBar', 'imageValue', imageScore);
1819
+
1820
+ // Document verification score
1821
+ let docScore = 0;
1822
+ if (data.document_analysis && data.document_analysis.pdf_analysis) {
1823
+ const authenticDocs = data.document_analysis.pdf_analysis.filter(
1824
+ pdf => pdf.authenticity && pdf.authenticity.assessment.includes('authentic')
1825
+ );
1826
+ docScore = data.document_analysis.pdf_count > 0 ?
1827
+ Math.round((authenticDocs.length / data.document_analysis.pdf_count) * 100) : 0;
1828
+ }
1829
+ updateScoreBar('documentBar', 'documentValue', docScore);
1830
+
1831
+ // Content quality score
1832
+ const contentScore = validatedData.qualityAssessment ? validatedData.qualityAssessment.score : 0;
1833
+ updateScoreBar('contentBar', 'contentValue', contentScore);
1834
+
1835
+ // Location accuracy score
1836
+ const locationScore = data.location_analysis ? data.location_analysis.completeness_score || 0 : 0;
1837
+ updateScoreBar('locationBar', 'locationValue', locationScore);
1838
+
1839
+ // Update Red Flags
1840
+ const redFlagsList = document.getElementById('redFlagsList');
1841
+ redFlagsList.innerHTML = '';
1842
+
1843
+ const redFlags = [];
1844
+
1845
+ // Check for inconsistencies and issues
1846
+ if (data.cross_validation) {
1847
+ data.cross_validation.forEach(check => {
1848
+ if (check.status === 'inconsistent' || check.status === 'invalid' ||
1849
+ check.status === 'suspicious' || check.status === 'no_match') {
1850
+ redFlags.push(`${check.check}: ${check.message}`);
1851
+ }
1852
+ });
1853
+ }
1854
+
1855
+ if (validatedData.qualityAssessment && validatedData.qualityAssessment.isAiGenerated) {
1856
+ redFlags.push('Description appears to be AI-generated, which may indicate a fake listing');
1857
+ }
1858
+
1859
+ if (data.price_analysis &&
1860
+ (data.price_analysis.assessment === 'suspicious pricing' ||
1861
+ data.price_analysis.assessment === 'overpriced' ||
1862
+ data.price_analysis.assessment === 'underpriced')) {
1863
+ redFlags.push(`Price is ${data.price_analysis.assessment} for this type of property`);
1864
+ }
1865
+
1866
+ if (data.legal_analysis && data.legal_analysis.potential_issues) {
1867
+ redFlags.push('Potential legal issues detected in the property documentation');
1868
+ }
1869
+
1870
+ if (data.image_analysis && data.image_analysis.image_count > 0) {
1871
+ const propertyImages = data.image_analysis.image_analysis.filter(img => img && img.is_property_related);
1872
+ if (propertyImages.length === 0) {
1873
+ redFlags.push('None of the uploaded images appear to be related to real estate');
1874
+ }
1875
+ }
1876
+
1877
+ // If no red flags, add a positive message
1878
+ if (redFlags.length === 0) {
1879
+ redFlags.push('No significant red flags detected in this listing');
1880
+ }
1881
+
1882
+ redFlags.forEach(flag => {
1883
+ const li = document.createElement('li');
1884
+ li.className = 'suggestion-item';
1885
+ li.textContent = flag;
1886
+ redFlagsList.appendChild(li);
1887
+ });
1888
+
1889
+ } catch (error) {
1890
+ console.error('Error displaying results:', error);
1891
+ document.getElementById('resultsContainer').innerHTML =
1892
+ '<div class="alert alert-danger">Error displaying results. Please try again.</div>';
1893
+ }
1894
+ }
1895
+
1896
+ function updateScoreBar(barId, valueId, score) {
1897
+ const bar = document.getElementById(barId);
1898
+ const value = document.getElementById(valueId);
1899
+
1900
+ if (bar && value) {
1901
+ bar.style.setProperty('--score-width', `${score}%`);
1902
+ bar.style.background = `linear-gradient(to right,
1903
+ ${getScoreColor(score)} ${score}%,
1904
+ #e9ecef ${score}%)`;
1905
+ value.textContent = `${score}%`;
1906
+ }
1907
+ }
1908
+
1909
+ function getScoreColor(score) {
1910
+ if (score >= 70) return 'var(--success)';
1911
+ if (score >= 40) return 'var(--warning)';
1912
+ return 'var(--danger)';
1913
+ }
1914
+ </script>
1915
+ </body>
1916
+ </html>