sksameermujahid commited on
Commit
9860c76
·
verified ·
1 Parent(s): 0e5c14c

Upload 34 files

Browse files
app.py CHANGED
@@ -404,23 +404,23 @@ def calculate_final_verdict(results):
404
  elif any([trust_score > 0, address_score > 0, location_score > 0, price_score > 0]):
405
  weighted_score = max(0.15, weighted_score) # Minimum 15% for any valid data
406
 
407
- # Much stricter verdict determination
408
- if fake_data_detected or fraudulent_issues > 0:
409
  verdict = 'HIGH RISK LISTING'
410
  risk_level = 'high'
411
- elif weighted_score >= 0.75 and fraud_score < 0.2 and high_severity_issues == 0:
412
  verdict = 'VERIFIED REAL ESTATE LISTING'
413
  risk_level = 'low'
414
- elif weighted_score >= 0.60 and fraud_score < 0.3 and high_severity_issues <= 1:
415
  verdict = 'LIKELY LEGITIMATE'
416
  risk_level = 'low'
417
- elif weighted_score >= 0.40 and fraud_score < 0.5 and high_severity_issues <= 2:
418
  verdict = 'SUSPICIOUS LISTING'
419
  risk_level = 'medium'
420
- elif fraud_score >= 0.5 or weighted_score < 0.20 or high_severity_issues >= 3:
421
  verdict = 'HIGH RISK LISTING'
422
  risk_level = 'high'
423
- elif weighted_score >= 0.20:
424
  verdict = 'VERIFICATION REQUIRED'
425
  risk_level = 'medium'
426
  else:
@@ -471,21 +471,21 @@ def calculate_final_verdict(results):
471
  # Ensure score is between 0 and 100
472
  overall_score = max(0, min(100, overall_score))
473
 
474
- # CRITICAL: Much stricter minimum score for fake data
475
  if fake_data_detected:
476
- overall_score = max(5, min(15, overall_score)) # 5-15% range for fake data
477
  elif overall_score == 0 and any([trust_score > 0, address_score > 0, location_score > 0]):
478
- overall_score = 15 # Minimum 15% score if any component is valid
479
 
480
- # Final score adjustment based on data quality - Much stricter
481
  if fake_data_detected or fraudulent_issues > 0:
482
- overall_score = max(5, min(15, overall_score)) # 5-15% for fake/fraudulent data
483
  elif high_severity_issues >= 3:
484
- overall_score = max(10, overall_score) # Minimum 10% for high risk
485
  elif high_severity_issues >= 1:
486
- overall_score = max(15, overall_score) # Minimum 15% for medium risk
487
  else:
488
- overall_score = max(20, overall_score) # Minimum 20% for low risk
489
 
490
  return {
491
  'verdict': verdict,
 
404
  elif any([trust_score > 0, address_score > 0, location_score > 0, price_score > 0]):
405
  weighted_score = max(0.15, weighted_score) # Minimum 15% for any valid data
406
 
407
+ # Less strict verdict determination
408
+ if fake_data_detected or fraudulent_issues > 2:
409
  verdict = 'HIGH RISK LISTING'
410
  risk_level = 'high'
411
+ elif weighted_score >= 0.70 and fraud_score < 0.3 and high_severity_issues == 0:
412
  verdict = 'VERIFIED REAL ESTATE LISTING'
413
  risk_level = 'low'
414
+ elif weighted_score >= 0.50 and fraud_score < 0.4 and high_severity_issues <= 1:
415
  verdict = 'LIKELY LEGITIMATE'
416
  risk_level = 'low'
417
+ elif weighted_score >= 0.30 and fraud_score < 0.6 and high_severity_issues <= 2:
418
  verdict = 'SUSPICIOUS LISTING'
419
  risk_level = 'medium'
420
+ elif fraud_score >= 0.7 or weighted_score < 0.15 or high_severity_issues >= 4:
421
  verdict = 'HIGH RISK LISTING'
422
  risk_level = 'high'
423
+ elif weighted_score >= 0.15:
424
  verdict = 'VERIFICATION REQUIRED'
425
  risk_level = 'medium'
426
  else:
 
471
  # Ensure score is between 0 and 100
472
  overall_score = max(0, min(100, overall_score))
473
 
474
+ # CRITICAL: Less punitive minimum score for fake data
475
  if fake_data_detected:
476
+ overall_score = max(10, min(25, overall_score)) # 10-25% range for fake data
477
  elif overall_score == 0 and any([trust_score > 0, address_score > 0, location_score > 0]):
478
+ overall_score = 20 # Minimum 20% score if any component is valid
479
 
480
+ # Final score adjustment based on data quality - Less punitive
481
  if fake_data_detected or fraudulent_issues > 0:
482
+ overall_score = max(10, min(25, overall_score)) # 10-25% for fake/fraudulent data
483
  elif high_severity_issues >= 3:
484
+ overall_score = max(15, overall_score) # Minimum 15% for high risk
485
  elif high_severity_issues >= 1:
486
+ overall_score = max(20, overall_score) # Minimum 20% for medium risk
487
  else:
488
+ overall_score = max(25, overall_score) # Minimum 25% for low risk
489
 
490
  return {
491
  'verdict': verdict,
models/address_verification.py CHANGED
@@ -28,6 +28,20 @@ def verify_address(data):
28
  latitude = data.get('latitude', None)
29
  longitude = data.get('longitude', None)
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  if zip_code:
32
  try:
33
  response = requests.get(f"https://api.postalpincode.in/pincode/{zip_code}", timeout=5)
@@ -48,15 +62,25 @@ def verify_address(data):
48
  address_results['issues'].append("Pincode API error")
49
  except Exception as e:
50
  logger.error(f"Pincode API error: {str(e)}")
51
- address_results['issues'].append("Pincode validation failed")
 
 
 
 
 
52
 
 
53
  full_address = ', '.join(filter(None, [address, city, state, country, zip_code]))
 
 
54
  for attempt in range(3):
55
  try:
56
  location = geocoder.geocode(full_address)
57
  if location:
58
  address_results['address_exists'] = True
59
  address_results['confidence'] = 0.9
 
 
60
  if latitude and longitude:
61
  try:
62
  provided_coords = (float(latitude), float(longitude))
@@ -74,9 +98,17 @@ def verify_address(data):
74
  except Exception as e:
75
  logger.error(f"Geocoding error on attempt {attempt + 1}: {str(e)}")
76
  time.sleep(1)
77
- else:
78
- address_results['issues'].append("Address geocoding failed")
 
 
 
 
 
 
 
79
 
 
80
  try:
81
  verification_points = (
82
  float(address_results['address_exists']) * 0.4 +
@@ -84,13 +116,31 @@ def verify_address(data):
84
  float(address_results['city_state_match']) * 0.2 +
85
  float(address_results['coordinates_match']) * 0.1
86
  )
 
 
 
 
 
87
  except Exception as e:
88
  logger.warning(f"Error calculating verification points: {str(e)}")
89
- verification_points = 0.0
90
- address_results['verification_score'] = verification_points
 
 
 
 
 
91
 
92
  return address_results
93
  except Exception as e:
94
  logger.error(f"Error verifying address: {str(e)}")
95
- address_results['issues'].append(str(e))
96
- return address_results
 
 
 
 
 
 
 
 
 
28
  latitude = data.get('latitude', None)
29
  longitude = data.get('longitude', None)
30
 
31
+ # Basic validation - give points for having required fields
32
+ basic_score = 0.0
33
+ if zip_code:
34
+ basic_score += 0.2
35
+ if city:
36
+ basic_score += 0.2
37
+ if state:
38
+ basic_score += 0.2
39
+ if address:
40
+ basic_score += 0.2
41
+ if latitude and longitude:
42
+ basic_score += 0.2
43
+
44
+ # Pincode validation with fallback
45
  if zip_code:
46
  try:
47
  response = requests.get(f"https://api.postalpincode.in/pincode/{zip_code}", timeout=5)
 
62
  address_results['issues'].append("Pincode API error")
63
  except Exception as e:
64
  logger.error(f"Pincode API error: {str(e)}")
65
+ # Don't fail completely - give partial credit for having pincode
66
+ if zip_code and len(zip_code) == 6 and zip_code.isdigit():
67
+ address_results['pincode_valid'] = True
68
+ address_results['issues'].append("Pincode validation failed (API error)")
69
+ else:
70
+ address_results['issues'].append("Pincode validation failed")
71
 
72
+ # Geocoding with fallback
73
  full_address = ', '.join(filter(None, [address, city, state, country, zip_code]))
74
+ geocoding_success = False
75
+
76
  for attempt in range(3):
77
  try:
78
  location = geocoder.geocode(full_address)
79
  if location:
80
  address_results['address_exists'] = True
81
  address_results['confidence'] = 0.9
82
+ geocoding_success = True
83
+
84
  if latitude and longitude:
85
  try:
86
  provided_coords = (float(latitude), float(longitude))
 
98
  except Exception as e:
99
  logger.error(f"Geocoding error on attempt {attempt + 1}: {str(e)}")
100
  time.sleep(1)
101
+
102
+ if not geocoding_success:
103
+ # Fallback: if we have basic address components, give partial credit
104
+ if address and city and state:
105
+ address_results['address_exists'] = True
106
+ address_results['confidence'] = 0.6
107
+ address_results['issues'].append("Address geocoding failed (using fallback validation)")
108
+ else:
109
+ address_results['issues'].append("Address geocoding failed")
110
 
111
+ # Calculate verification score with fallback
112
  try:
113
  verification_points = (
114
  float(address_results['address_exists']) * 0.4 +
 
116
  float(address_results['city_state_match']) * 0.2 +
117
  float(address_results['coordinates_match']) * 0.1
118
  )
119
+
120
+ # If external APIs failed but we have basic data, give minimum score
121
+ if verification_points == 0.0 and basic_score > 0.0:
122
+ verification_points = basic_score * 0.5 # 50% of basic score as fallback
123
+
124
  except Exception as e:
125
  logger.warning(f"Error calculating verification points: {str(e)}")
126
+ verification_points = basic_score * 0.5 # Fallback to basic score
127
+
128
+ # Ensure minimum score for valid data
129
+ if verification_points == 0.0 and (zip_code or city or state or address):
130
+ verification_points = 0.2 # Minimum 20% for having some address data
131
+
132
+ address_results['verification_score'] = verification_points * 100 # Convert to percentage
133
 
134
  return address_results
135
  except Exception as e:
136
  logger.error(f"Error verifying address: {str(e)}")
137
+ # Return minimum score instead of 0
138
+ return {
139
+ 'address_exists': False,
140
+ 'pincode_valid': False,
141
+ 'city_state_match': False,
142
+ 'coordinates_match': False,
143
+ 'confidence': 0.0,
144
+ 'issues': [f"Address verification error: {str(e)}"],
145
+ 'verification_score': 10.0 # Minimum 10% score instead of 0
146
+ }
models/legal_analysis.py CHANGED
@@ -11,11 +11,15 @@ def analyze_legal_details(legal_text: str) -> Dict[str, Any]:
11
  if not legal_text or len(str(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': {},
@@ -28,14 +32,25 @@ def analyze_legal_details(legal_text: str) -> Dict[str, Any]:
28
  classifier = load_model("zero-shot-classification")
29
  except Exception as e:
30
  logger.error(f"Error loading model in legal analysis: {str(e)}")
 
 
 
 
 
 
 
31
  return {
32
- 'assessment': 'error',
33
- 'confidence': 0.0,
34
- 'summary': f'Model loading error: {str(e)}',
35
- 'completeness_score': 0,
36
  'potential_issues': False,
37
- 'legal_metrics': {},
38
- 'reasoning': f'Model loading error: {str(e)}',
 
 
 
 
39
  'top_classifications': [],
40
  'document_verification': {},
41
  'compliance_status': {},
@@ -98,202 +113,111 @@ def analyze_legal_details(legal_text: str) -> Dict[str, Any]:
98
  # Create a more detailed context for analysis
99
  legal_context = f"""
100
  Legal Documentation Analysis:
101
- {legal_text[:1000]}
102
-
103
- Key aspects to verify:
104
- 1. Title and Ownership:
105
- - Clear title documentation
106
- - Ownership transfer history
107
- - Inheritance/gift documentation
108
- - Power of attorney status
109
-
110
- 2. Property Registration:
111
- - Sale deed validity
112
- - Registration status
113
- - Development agreements
114
- - Joint development status
115
-
116
- 3. Tax and Financial:
117
- - Property tax compliance
118
- - Tax clearance status
119
- - Encumbrance status
120
- - Mortgage/loan status
121
-
122
- 4. Approvals and Permits:
123
- - Building permit validity
124
- - Construction approvals
125
- - Occupation certificates
126
- - Environmental clearances
127
-
128
- 5. Land and Usage:
129
- - Land use compliance
130
- - Zoning regulations
131
- - Layout approvals
132
- - Master plan compliance
133
-
134
- 6. Compliance and Legal:
135
- - Legal compliance status
136
- - Safety certificates
137
- - Utility compliance
138
- - Regulatory approvals
139
-
140
- 7. Disputes and Litigation:
141
- - Dispute history
142
- - Court orders
143
- - Settlement status
144
- - Pending cases
145
  """
146
 
147
- # Analyze legal text with multiple aspects
148
  try:
149
- if hasattr(classifier, 'task_type') and classifier.task_type == "zero-shot-classification":
150
- # Using fallback classifier
151
- legal_result = classifier(legal_context, categories)
152
- else:
153
- # Using actual model
154
- legal_result = classifier(legal_context, categories, multi_label=True)
155
  except Exception as e:
156
  logger.error(f"Error in legal classification: {str(e)}")
157
- # Use simple keyword-based analysis
158
- legal_result = simple_legal_analysis(legal_text, categories)
159
-
160
- # Get top classifications with confidence scores
161
- top_classifications = []
162
- if isinstance(legal_result, dict) and 'scores' in legal_result:
163
- for label, score in zip(legal_result['labels'][:5], legal_result['scores'][:5]):
164
- if score > 0.3: # Only include if confidence is above 30%
165
- top_classifications.append({
166
- 'classification': label,
167
- 'confidence': float(score)
168
- })
169
 
170
- # Generate summary using BART or fallback
171
- try:
172
- summary = summarize_text(legal_text[:1000])
173
- except Exception as e:
174
- logger.warning(f"Error generating summary: {str(e)}")
175
- summary = "Legal document analysis completed with basic text processing."
176
-
177
- # Calculate detailed legal metrics
178
  legal_metrics = calculate_legal_metrics(legal_result, categories)
 
 
 
 
 
 
 
 
 
179
 
180
- # Calculate completeness score with weighted components
181
- weights = {
182
- 'title_and_ownership': 0.25,
183
- 'property_registration': 0.20,
184
- 'tax_and_financial': 0.15,
185
- 'approvals_and_permits': 0.15,
186
- 'land_and_usage': 0.10,
187
- 'compliance_and_legal': 0.10,
188
- 'disputes_and_litigation': 0.05
189
- }
190
-
191
- completeness_score = sum(
192
- legal_metrics[category] * weight * 100
193
- for category, weight in weights.items()
194
- )
195
-
196
- # Determine if there are potential issues
197
- potential_issues = legal_metrics['disputes_and_litigation'] > 0.3
198
-
199
- # Generate detailed reasoning
200
- reasoning_parts = []
201
-
202
- # Document verification status
203
- document_verification = {
204
- 'title_documents': {
205
- 'status': 'verified' if legal_metrics['title_and_ownership'] > 0.7 else 'partial' if legal_metrics['title_and_ownership'] > 0.4 else 'missing',
206
- 'score': legal_metrics['title_and_ownership'] * 100
207
- },
208
- 'registration_documents': {
209
- 'status': 'verified' if legal_metrics['property_registration'] > 0.7 else 'partial' if legal_metrics['property_registration'] > 0.4 else 'missing',
210
- 'score': legal_metrics['property_registration'] * 100
211
- },
212
- 'tax_documents': {
213
- 'status': 'verified' if legal_metrics['tax_and_financial'] > 0.7 else 'partial' if legal_metrics['tax_and_financial'] > 0.4 else 'missing',
214
- 'score': legal_metrics['tax_and_financial'] * 100
215
- },
216
- 'approval_documents': {
217
- 'status': 'verified' if legal_metrics['approvals_and_permits'] > 0.7 else 'partial' if legal_metrics['approvals_and_permits'] > 0.4 else 'missing',
218
- 'score': legal_metrics['approvals_and_permits'] * 100
219
- }
220
- }
221
-
222
- # Compliance status
223
- compliance_status = {
224
- 'land_use': {
225
- 'status': 'compliant' if legal_metrics['land_and_usage'] > 0.7 else 'partial' if legal_metrics['land_and_usage'] > 0.4 else 'non-compliant',
226
- 'score': legal_metrics['land_and_usage'] * 100
227
- },
228
- 'legal_compliance': {
229
- 'status': 'compliant' if legal_metrics['compliance_and_legal'] > 0.7 else 'partial' if legal_metrics['compliance_and_legal'] > 0.4 else 'non-compliant',
230
- 'score': legal_metrics['compliance_and_legal'] * 100
231
- }
232
- }
233
-
234
- # Risk assessment
235
- risk_assessment = {
236
- 'litigation_risk': {
237
- 'level': 'high' if legal_metrics['disputes_and_litigation'] > 0.6 else 'medium' if legal_metrics['disputes_and_litigation'] > 0.3 else 'low',
238
- 'score': legal_metrics['disputes_and_litigation'] * 100
239
- },
240
- 'documentation_risk': {
241
- 'level': 'high' if completeness_score < 50 else 'medium' if completeness_score < 70 else 'low',
242
- 'score': 100 - completeness_score
243
- }
244
- }
245
-
246
- # Generate reasoning based on all metrics
247
- if top_classifications:
248
- primary_class = top_classifications[0]['classification']
249
- confidence = top_classifications[0]['confidence']
250
- reasoning_parts.append(f"Primary assessment: {primary_class} (confidence: {confidence:.0%})")
251
-
252
- # Add document verification status
253
- for doc_type, status in document_verification.items():
254
- reasoning_parts.append(f"{doc_type.replace('_', ' ').title()}: {status['status']} (score: {status['score']:.0f}%)")
255
-
256
- # Add compliance status
257
- for compliance_type, status in compliance_status.items():
258
- reasoning_parts.append(f"{compliance_type.replace('_', ' ').title()}: {status['status']} (score: {status['score']:.0f}%)")
259
 
260
- # Add risk assessment
261
- for risk_type, assessment in risk_assessment.items():
262
- reasoning_parts.append(f"{risk_type.replace('_', ' ').title()}: {assessment['level']} risk (score: {assessment['score']:.0f}%)")
 
 
 
 
 
 
 
 
 
 
 
 
 
263
 
264
- # Calculate overall confidence
265
- overall_confidence = min(1.0, (
266
- legal_metrics['title_and_ownership'] * 0.3 +
267
- legal_metrics['property_registration'] * 0.2 +
268
- legal_metrics['tax_and_financial'] * 0.15 +
269
- legal_metrics['approvals_and_permits'] * 0.15 +
270
- legal_metrics['land_and_usage'] * 0.1 +
271
- legal_metrics['compliance_and_legal'] * 0.1
272
- ))
273
 
274
  return {
275
- 'assessment': top_classifications[0]['classification'] if top_classifications else 'could not assess',
276
- 'confidence': float(overall_confidence),
277
  'summary': summary,
278
- 'completeness_score': int(completeness_score),
279
- 'potential_issues': potential_issues,
280
  'legal_metrics': legal_metrics,
281
- 'reasoning': '. '.join(reasoning_parts),
282
  'top_classifications': top_classifications,
283
- 'document_verification': document_verification,
284
- 'compliance_status': compliance_status,
285
- 'risk_assessment': risk_assessment
 
 
 
 
 
 
 
 
 
 
 
286
  }
 
287
  except Exception as e:
288
- logger.error(f"Error analyzing legal details: {str(e)}")
 
289
  return {
290
- 'assessment': 'could not assess',
291
- 'confidence': 0.0,
292
- 'summary': 'Error analyzing legal details',
293
- 'completeness_score': 0,
294
  'potential_issues': False,
295
- 'legal_metrics': {},
296
- 'reasoning': 'Technical error occurred during analysis',
 
 
 
 
297
  'top_classifications': [],
298
  'document_verification': {},
299
  'compliance_status': {},
 
11
  if not legal_text or len(str(legal_text).strip()) < 5:
12
  return {
13
  'assessment': 'insufficient',
14
+ 'confidence': 0.1, # Small confidence instead of 0
15
  'summary': 'No legal details provided',
16
+ 'completeness_score': 5, # Minimum score instead of 0
17
  'potential_issues': False,
18
+ 'legal_metrics': {
19
+ 'text_length': 0,
20
+ 'word_count': 0,
21
+ 'legal_terms_found': 0
22
+ },
23
  'reasoning': 'No legal details provided for analysis',
24
  'top_classifications': [],
25
  'document_verification': {},
 
32
  classifier = load_model("zero-shot-classification")
33
  except Exception as e:
34
  logger.error(f"Error loading model in legal analysis: {str(e)}")
35
+ # Provide fallback scoring based on text content
36
+ legal_text_str = str(legal_text)
37
+ legal_terms = ['title', 'deed', 'registration', 'tax', 'permit', 'approval', 'certificate', 'compliance', 'legal']
38
+ legal_terms_found = sum(1 for term in legal_terms if term in legal_text_str.lower())
39
+
40
+ fallback_score = min(50, legal_terms_found * 10) # 10 points per legal term, max 50
41
+
42
  return {
43
+ 'assessment': 'basic',
44
+ 'confidence': 0.3, # Basic confidence
45
+ 'summary': f'Model loading error, using fallback analysis. Found {legal_terms_found} legal terms.',
46
+ 'completeness_score': fallback_score,
47
  'potential_issues': False,
48
+ 'legal_metrics': {
49
+ 'text_length': len(legal_text_str),
50
+ 'word_count': len(legal_text_str.split()),
51
+ 'legal_terms_found': legal_terms_found
52
+ },
53
+ 'reasoning': f'Model loading error: {str(e)}. Using fallback scoring based on legal terms found.',
54
  'top_classifications': [],
55
  'document_verification': {},
56
  'compliance_status': {},
 
113
  # Create a more detailed context for analysis
114
  legal_context = f"""
115
  Legal Documentation Analysis:
116
+ {legal_text}
117
+
118
+ Please analyze the above legal documentation for:
119
+ 1. Completeness of legal information
120
+ 2. Presence of required documents
121
+ 3. Compliance with regulations
122
+ 4. Potential legal issues
123
+ 5. Risk assessment
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  """
125
 
126
+ # Analyze with the classifier
127
  try:
128
+ legal_result = classifier(legal_context[:1000], categories, multi_label=True)
 
 
 
 
 
129
  except Exception as e:
130
  logger.error(f"Error in legal classification: {str(e)}")
131
+ # Fallback to simple analysis
132
+ return simple_legal_analysis(legal_text, categories)
 
 
 
 
 
 
 
 
 
 
133
 
134
+ # Calculate legal metrics
 
 
 
 
 
 
 
135
  legal_metrics = calculate_legal_metrics(legal_result, categories)
136
+
137
+ # Get top classifications
138
+ top_classifications = []
139
+ for label, score in zip(legal_result['labels'][:5], legal_result['scores'][:5]):
140
+ if score > 0.2: # Lower threshold for legal terms
141
+ top_classifications.append({
142
+ 'classification': label,
143
+ 'confidence': float(score)
144
+ })
145
 
146
+ # Calculate completeness score
147
+ positive_categories = [
148
+ "clear title documentation", "property registration documents", "sale deed documents",
149
+ "property tax records", "building permits", "occupation certificates",
150
+ "legal compliance certificates", "no objection certificates"
151
+ ]
152
+
153
+ positive_score = sum(score for label, score in zip(legal_result['labels'], legal_result['scores'])
154
+ if label in positive_categories)
155
+ completeness_score = min(100, int(positive_score * 100))
156
+
157
+ # Ensure minimum score for any legal content
158
+ if completeness_score < 10 and len(legal_text) > 20:
159
+ completeness_score = 10 # Minimum 10% for having some legal content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
+ # Determine assessment
162
+ if completeness_score >= 80:
163
+ assessment = 'excellent'
164
+ confidence = 0.9
165
+ elif completeness_score >= 60:
166
+ assessment = 'good'
167
+ confidence = 0.7
168
+ elif completeness_score >= 40:
169
+ assessment = 'adequate'
170
+ confidence = 0.5
171
+ elif completeness_score >= 20:
172
+ assessment = 'basic'
173
+ confidence = 0.3
174
+ else:
175
+ assessment = 'basic'
176
+ confidence = 0.2
177
 
178
+ # Generate summary
179
+ summary = summarize_text(legal_text)
 
 
 
 
 
 
 
180
 
181
  return {
182
+ 'assessment': assessment,
183
+ 'confidence': confidence,
184
  'summary': summary,
185
+ 'completeness_score': completeness_score,
186
+ 'potential_issues': legal_metrics.get('potential_issues', False),
187
  'legal_metrics': legal_metrics,
188
+ 'reasoning': f'Legal analysis completed with {completeness_score}% completeness score.',
189
  'top_classifications': top_classifications,
190
+ 'document_verification': {
191
+ 'title_docs': legal_metrics.get('title_docs', 0),
192
+ 'registration_docs': legal_metrics.get('registration_docs', 0),
193
+ 'tax_docs': legal_metrics.get('tax_docs', 0),
194
+ 'approval_docs': legal_metrics.get('approval_docs', 0)
195
+ },
196
+ 'compliance_status': {
197
+ 'overall_compliance': legal_metrics.get('compliance_score', 0),
198
+ 'missing_documents': legal_metrics.get('missing_docs', [])
199
+ },
200
+ 'risk_assessment': {
201
+ 'risk_level': legal_metrics.get('risk_level', 'low'),
202
+ 'risk_factors': legal_metrics.get('risk_factors', [])
203
+ }
204
  }
205
+
206
  except Exception as e:
207
+ logger.error(f"Error in legal analysis: {str(e)}")
208
+ # Return reasonable fallback instead of complete failure
209
  return {
210
+ 'assessment': 'basic',
211
+ 'confidence': 0.2,
212
+ 'summary': 'Legal analysis failed due to technical error',
213
+ 'completeness_score': 10, # Minimum score instead of 0
214
  'potential_issues': False,
215
+ 'legal_metrics': {
216
+ 'text_length': len(str(legal_text)) if legal_text else 0,
217
+ 'word_count': len(str(legal_text).split()) if legal_text else 0,
218
+ 'legal_terms_found': 0
219
+ },
220
+ 'reasoning': f'Legal analysis error: {str(e)}. Using fallback scoring.',
221
  'top_classifications': [],
222
  'document_verification': {},
223
  'compliance_status': {},
models/text_quality.py CHANGED
@@ -5,26 +5,61 @@ from .logging_config import logger
5
 
6
  def assess_text_quality(text):
7
  try:
8
- if not text or len(str(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
  'model_used': 'static_fallback'
16
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  try:
18
  classifier = load_model("zero-shot-classification") # Use standard model instead of typeform
19
  except Exception as e:
20
  logger.error(f"Error loading model in text quality: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  return {
22
- 'assessment': 'error',
23
- 'score': 0,
24
- 'reasoning': f'Model loading error: {str(e)}',
25
  'is_ai_generated': False,
26
- 'quality_metrics': {},
27
- 'top_classifications': [],
 
 
 
28
  'model_used': 'static_fallback'
29
  }
30
 
@@ -63,8 +98,9 @@ def assess_text_quality(text):
63
  negative_score = sum(score for label, score in zip(quality_result['labels'], quality_result['scores'])
64
  if label in negative_categories)
65
 
66
- # Calculate final score (0-100)
67
- quality_score = max(0, min(100, int((positive_score - negative_score + 1) * 50)))
 
68
 
69
  # Determine assessment
70
  if quality_score >= 80:
@@ -74,9 +110,9 @@ def assess_text_quality(text):
74
  elif quality_score >= 40:
75
  assessment = 'adequate'
76
  elif quality_score >= 20:
77
- assessment = 'poor'
78
  else:
79
- assessment = 'very poor'
80
 
81
  # Simple AI detection (basic heuristic)
82
  is_ai_generated = len(text) > 500 and (
@@ -99,14 +135,22 @@ def assess_text_quality(text):
99
  'top_classifications': top_classifications,
100
  'model_used': getattr(classifier, 'fallback_model', 'primary_model')
101
  }
 
102
  except Exception as e:
103
  logger.error(f"Error in text quality assessment: {str(e)}")
 
 
 
 
104
  return {
105
- 'assessment': 'error',
106
- 'score': 0,
107
- 'reasoning': f'Error: {str(e)}',
108
  'is_ai_generated': False,
109
- 'quality_metrics': {},
110
- 'top_classifications': [],
111
- 'model_used': 'static_fallback'
 
 
 
112
  }
 
5
 
6
  def assess_text_quality(text):
7
  try:
8
+ # Handle very short or empty text with more reasonable scoring
9
+ if not text or len(str(text).strip()) < 5:
10
  return {
11
  'assessment': 'insufficient',
12
+ 'score': 5, # Give minimum score instead of 0
13
+ 'reasoning': 'Text too short or empty.',
14
  'is_ai_generated': False,
15
  'quality_metrics': {},
16
  'model_used': 'static_fallback'
17
  }
18
+
19
+ # For very short text (5-20 characters), give basic score
20
+ if len(str(text).strip()) < 20:
21
+ return {
22
+ 'assessment': 'basic',
23
+ 'score': 15, # Basic score for minimal text
24
+ 'reasoning': 'Very short text provided.',
25
+ 'is_ai_generated': False,
26
+ 'quality_metrics': {
27
+ 'text_length': len(text),
28
+ 'word_count': len(text.split()),
29
+ 'sentence_count': text.count('.') + text.count('!') + text.count('?')
30
+ },
31
+ 'model_used': 'static_fallback'
32
+ }
33
+
34
  try:
35
  classifier = load_model("zero-shot-classification") # Use standard model instead of typeform
36
  except Exception as e:
37
  logger.error(f"Error loading model in text quality: {str(e)}")
38
+ # Fallback scoring for when model fails
39
+ text_length = len(text)
40
+ if text_length > 200:
41
+ fallback_score = 60
42
+ assessment = 'good'
43
+ elif text_length > 100:
44
+ fallback_score = 40
45
+ assessment = 'adequate'
46
+ elif text_length > 50:
47
+ fallback_score = 25
48
+ assessment = 'basic'
49
+ else:
50
+ fallback_score = 15
51
+ assessment = 'basic'
52
+
53
  return {
54
+ 'assessment': assessment,
55
+ 'score': fallback_score,
56
+ 'reasoning': f'Model loading error, using fallback scoring based on text length ({text_length} chars).',
57
  'is_ai_generated': False,
58
+ 'quality_metrics': {
59
+ 'text_length': text_length,
60
+ 'word_count': len(text.split()),
61
+ 'sentence_count': text.count('.') + text.count('!') + text.count('?')
62
+ },
63
  'model_used': 'static_fallback'
64
  }
65
 
 
98
  negative_score = sum(score for label, score in zip(quality_result['labels'], quality_result['scores'])
99
  if label in negative_categories)
100
 
101
+ # Calculate final score (0-100) with better handling of edge cases
102
+ base_score = (positive_score - negative_score + 1) * 50
103
+ quality_score = max(10, min(100, int(base_score))) # Ensure minimum 10% score
104
 
105
  # Determine assessment
106
  if quality_score >= 80:
 
110
  elif quality_score >= 40:
111
  assessment = 'adequate'
112
  elif quality_score >= 20:
113
+ assessment = 'basic'
114
  else:
115
+ assessment = 'basic' # Changed from 'very poor' to 'basic'
116
 
117
  # Simple AI detection (basic heuristic)
118
  is_ai_generated = len(text) > 500 and (
 
135
  'top_classifications': top_classifications,
136
  'model_used': getattr(classifier, 'fallback_model', 'primary_model')
137
  }
138
+
139
  except Exception as e:
140
  logger.error(f"Error in text quality assessment: {str(e)}")
141
+ # Return reasonable fallback instead of 0
142
+ text_length = len(str(text)) if text else 0
143
+ fallback_score = max(10, min(50, text_length // 2)) # Basic scoring based on length
144
+
145
  return {
146
+ 'assessment': 'basic',
147
+ 'score': fallback_score,
148
+ 'reasoning': f'Text quality assessment failed: {str(e)}. Using fallback scoring.',
149
  'is_ai_generated': False,
150
+ 'quality_metrics': {
151
+ 'text_length': text_length,
152
+ 'word_count': len(str(text).split()) if text else 0,
153
+ 'sentence_count': str(text).count('.') + str(text).count('!') + str(text).count('?') if text else 0
154
+ },
155
+ 'model_used': 'error_fallback'
156
  }
models/trust_score.py CHANGED
@@ -6,14 +6,14 @@ import re
6
 
7
  def generate_trust_score(text, image_analysis, pdf_analysis):
8
  try:
9
- # Start with a much lower base score and be very strict
10
- trust_score = 20.0 # Drastically reduced from 60.0
11
  reasoning_parts = []
12
 
13
  # Simple text-based trust indicators
14
  text_lower = str(text).lower()
15
 
16
- # CRITICAL: Check for obvious fake data patterns
17
  fake_patterns = [
18
  r'\b\d+\s*$', # Numbers at end of lines
19
  r'^\d+$', # Only numbers
@@ -28,136 +28,124 @@ def generate_trust_score(text, image_analysis, pdf_analysis):
28
  for pattern in fake_patterns:
29
  if re.search(pattern, text_lower):
30
  fake_detected = True
31
- trust_score -= 30 # Heavy penalty for fake patterns
32
  reasoning_parts.append("Detected suspicious number patterns")
33
  break
34
 
35
- # Check for repeated numbers (like "2, 2, 2, 2")
36
  numbers = re.findall(r'\b\d+\b', text_lower)
37
  if len(numbers) >= 3:
38
  unique_numbers = set(numbers)
39
  if len(unique_numbers) <= 2: # If most numbers are the same
40
  fake_detected = True
41
- trust_score -= 40 # Very heavy penalty
42
  reasoning_parts.append("Detected repeated number patterns (likely fake data)")
43
 
44
- # Check for extremely low values
45
  if any(word in text_lower for word in ['₹2', '₹1', '₹3', '₹4', '₹5']):
46
  fake_detected = True
47
- trust_score -= 50 # Extremely heavy penalty
48
  reasoning_parts.append("Detected suspiciously low pricing")
49
 
50
- # Check for very small property sizes
51
- if any(word in text_lower for word in ['2 sq ft', '1 sq ft', '3 sq ft', '4 sq ft', '5 sq ft']):
52
  fake_detected = True
53
- trust_score -= 40
54
- reasoning_parts.append("Detected unrealistic property size")
55
-
56
- # Check for generic property names
57
- if any(word in text_lower for word in ['2', '1', '3', '4', '5']) and len(text.strip()) < 50:
58
- fake_detected = True
59
- trust_score -= 30
60
- reasoning_parts.append("Detected generic/numeric property name")
61
-
62
- # Positive indicators - Much more strict
63
  positive_indicators = [
64
- 'verified', 'authentic', 'genuine', 'real', 'legitimate',
65
- 'complete', 'detailed', 'professional', 'official', 'certified',
66
- 'luxurious', 'modern', 'spacious', 'well-maintained', 'prime location',
67
- 'amenities', 'security', 'parking', 'garden', 'balcony',
68
- 'renovated', 'furnished', 'semi-furnished', 'ready to move',
69
- 'clear title', 'no litigation', 'approved', 'registered'
70
  ]
71
 
72
- # Negative indicators - More comprehensive
73
  negative_indicators = [
74
- 'fake', 'scam', 'fraud', 'suspicious', 'unverified',
75
- 'incomplete', 'missing', 'unclear', 'doubtful', 'questionable',
76
- 'urgent sale', 'quick sale', 'no documents needed', 'cash only',
77
- 'below market', 'distress sale', 'owner abroad', 'inheritance',
78
- 'unclear title', 'litigation', 'dispute', 'encroachment'
79
  ]
80
 
81
- # Count positive and negative indicators
82
  positive_count = sum(1 for indicator in positive_indicators if indicator in text_lower)
83
  negative_count = sum(1 for indicator in negative_indicators if indicator in text_lower)
84
 
85
- # Adjust score based on indicators - Much stricter
86
  if positive_count > 0 and not fake_detected:
87
- trust_score += min(15, positive_count * 2) # Reduced from 25 to 15
88
  reasoning_parts.append(f"Found {positive_count} positive trust indicators")
89
 
90
  if negative_count > 0:
91
- trust_score -= min(30, negative_count * 8) # Increased penalty from 20 to 30
92
  reasoning_parts.append(f"Found {negative_count} negative trust indicators")
93
 
94
- # Image analysis contribution - Much stricter
95
  if image_analysis:
96
  image_count = len(image_analysis) if isinstance(image_analysis, list) else 1
97
  if image_count > 0:
98
  # Check if images are actually property-related
99
  property_related_count = sum(1 for img in image_analysis if img.get('is_property_related', False))
100
  if property_related_count > 0:
101
- trust_score += min(10, property_related_count * 3) # Reduced from 20 to 10
102
  reasoning_parts.append(f"Property has {property_related_count} property-related images")
103
  else:
104
- trust_score -= 20 # Penalty for non-property images
105
  reasoning_parts.append("No property-related images detected")
106
 
107
  # Bonus for multiple high-quality images
108
  if property_related_count >= 3:
109
- trust_score += 5
110
  reasoning_parts.append("Multiple property images provided")
111
 
112
- # PDF analysis contribution - Much stricter
113
  if pdf_analysis:
114
  pdf_count = len(pdf_analysis) if isinstance(pdf_analysis, list) else 1
115
  if pdf_count > 0:
116
  # Check if documents are actually property-related
117
  property_related_docs = sum(1 for doc in pdf_analysis if doc.get('is_property_related', False))
118
  if property_related_docs > 0:
119
- trust_score += min(10, property_related_docs * 4) # Reduced from 20 to 10
120
  reasoning_parts.append(f"Property has {property_related_docs} property-related documents")
121
  else:
122
- trust_score -= 15 # Penalty for non-property documents
123
  reasoning_parts.append("No property-related documents detected")
124
 
125
  # Bonus for multiple documents
126
  if property_related_docs >= 2:
127
- trust_score += 3
128
  reasoning_parts.append("Multiple supporting documents provided")
129
 
130
- # Text quality assessment - Much stricter
131
  if text and len(text) > 200 and not fake_detected:
132
- trust_score += 8
133
  reasoning_parts.append("Detailed property description provided")
134
  elif text and len(text) > 100 and not fake_detected:
135
- trust_score += 4
136
  reasoning_parts.append("Adequate property description provided")
137
  elif len(text) < 50:
138
- trust_score -= 20 # Heavy penalty for very short descriptions
139
  reasoning_parts.append("Very short property description")
140
 
141
- # Location quality assessment - Much stricter
142
  if 'hyderabad' in text_lower or 'mumbai' in text_lower or 'delhi' in text_lower or 'bangalore' in text_lower:
143
  if not fake_detected:
144
- trust_score += 3
145
  reasoning_parts.append("Property in major city")
146
 
147
- # Property type assessment - Much stricter
148
  if any(prop_type in text_lower for prop_type in ['apartment', 'flat', 'house', 'villa', 'bungalow']):
149
  if not fake_detected:
150
- trust_score += 2
151
  reasoning_parts.append("Clear property type mentioned")
152
 
153
- # Amenities assessment - Much stricter
154
  amenities_count = sum(1 for amenity in ['pool', 'gym', 'garden', 'parking', 'security', 'lift', 'balcony']
155
  if amenity in text_lower)
156
  if amenities_count > 0 and not fake_detected:
157
- trust_score += min(5, amenities_count * 1) # Reduced from 10 to 5
158
  reasoning_parts.append(f"Property has {amenities_count} amenities mentioned")
159
 
160
- # CRITICAL: Additional fake data checks
161
  # Check if all major fields are just numbers
162
  numeric_fields = ['property_name', 'bedrooms', 'bathrooms', 'sq_ft', 'market_value']
163
  numeric_count = 0
@@ -167,9 +155,13 @@ def generate_trust_score(text, image_analysis, pdf_analysis):
167
 
168
  if numeric_count >= 3: # If 3+ fields are just numbers
169
  fake_detected = True
170
- trust_score -= 60 # Extremely heavy penalty
171
  reasoning_parts.append("Multiple fields contain only numbers (highly suspicious)")
172
 
 
 
 
 
173
  # Ensure score is within bounds
174
  trust_score = max(0, min(100, trust_score))
175
 
@@ -183,4 +175,4 @@ def generate_trust_score(text, image_analysis, pdf_analysis):
183
 
184
  except Exception as e:
185
  logger.error(f"Error in trust score generation: {str(e)}")
186
- return 10.0, f"Trust analysis failed: {str(e)}" # Reduced from 50.0 to 10.0
 
6
 
7
  def generate_trust_score(text, image_analysis, pdf_analysis):
8
  try:
9
+ # Start with a more reasonable base score
10
+ trust_score = 30.0 # Increased from 20.0 to give more reasonable starting point
11
  reasoning_parts = []
12
 
13
  # Simple text-based trust indicators
14
  text_lower = str(text).lower()
15
 
16
+ # CRITICAL: Check for obvious fake data patterns - but be less punitive
17
  fake_patterns = [
18
  r'\b\d+\s*$', # Numbers at end of lines
19
  r'^\d+$', # Only numbers
 
28
  for pattern in fake_patterns:
29
  if re.search(pattern, text_lower):
30
  fake_detected = True
31
+ trust_score -= 15 # Reduced penalty from 30 to 15
32
  reasoning_parts.append("Detected suspicious number patterns")
33
  break
34
 
35
+ # Check for repeated numbers (like "2, 2, 2, 2") - but be less punitive
36
  numbers = re.findall(r'\b\d+\b', text_lower)
37
  if len(numbers) >= 3:
38
  unique_numbers = set(numbers)
39
  if len(unique_numbers) <= 2: # If most numbers are the same
40
  fake_detected = True
41
+ trust_score -= 20 # Reduced penalty from 40 to 20
42
  reasoning_parts.append("Detected repeated number patterns (likely fake data)")
43
 
44
+ # Check for extremely low values - but be less punitive
45
  if any(word in text_lower for word in ['₹2', '₹1', '₹3', '₹4', '₹5']):
46
  fake_detected = True
47
+ trust_score -= 25 # Reduced penalty from 50 to 25
48
  reasoning_parts.append("Detected suspiciously low pricing")
49
 
50
+ # Check for very small property sizes - but be less punitive
51
+ if any(word in text_lower for word in ['2 sq', '1 sq', '3 sq', '4 sq', '5 sq']):
52
  fake_detected = True
53
+ trust_score -= 20 # Reduced penalty from 40 to 20
54
+ reasoning_parts.append("Detected suspiciously small property size")
55
+
56
+ # Positive trust indicators - More generous
 
 
 
 
 
 
57
  positive_indicators = [
58
+ 'apartment', 'flat', 'house', 'villa', 'bungalow', 'property', 'real estate',
59
+ 'bedroom', 'bathroom', 'kitchen', 'living', 'dining', 'balcony', 'parking',
60
+ 'amenities', 'facilities', 'security', 'lift', 'gym', 'pool', 'garden',
61
+ 'hyderabad', 'mumbai', 'delhi', 'bangalore', 'chennai', 'kolkata', 'pune',
62
+ 'verified', 'authentic', 'genuine', 'legitimate', 'original', 'certified'
 
63
  ]
64
 
 
65
  negative_indicators = [
66
+ 'fake', 'fraud', 'scam', 'suspicious', 'doubtful', 'unverified', 'unauthentic',
67
+ 'illegal', 'unauthorized', 'forged', 'counterfeit', 'bogus', 'phony'
 
 
 
68
  ]
69
 
 
70
  positive_count = sum(1 for indicator in positive_indicators if indicator in text_lower)
71
  negative_count = sum(1 for indicator in negative_indicators if indicator in text_lower)
72
 
73
+ # Adjust score based on indicators - More balanced
74
  if positive_count > 0 and not fake_detected:
75
+ trust_score += min(20, positive_count * 3) # Increased from 15 to 20
76
  reasoning_parts.append(f"Found {positive_count} positive trust indicators")
77
 
78
  if negative_count > 0:
79
+ trust_score -= min(25, negative_count * 5) # Reduced penalty from 30 to 25
80
  reasoning_parts.append(f"Found {negative_count} negative trust indicators")
81
 
82
+ # Image analysis contribution - More balanced
83
  if image_analysis:
84
  image_count = len(image_analysis) if isinstance(image_analysis, list) else 1
85
  if image_count > 0:
86
  # Check if images are actually property-related
87
  property_related_count = sum(1 for img in image_analysis if img.get('is_property_related', False))
88
  if property_related_count > 0:
89
+ trust_score += min(15, property_related_count * 4) # Increased from 10 to 15
90
  reasoning_parts.append(f"Property has {property_related_count} property-related images")
91
  else:
92
+ trust_score -= 15 # Reduced penalty from 20 to 15
93
  reasoning_parts.append("No property-related images detected")
94
 
95
  # Bonus for multiple high-quality images
96
  if property_related_count >= 3:
97
+ trust_score += 8 # Increased from 5 to 8
98
  reasoning_parts.append("Multiple property images provided")
99
 
100
+ # PDF analysis contribution - More balanced
101
  if pdf_analysis:
102
  pdf_count = len(pdf_analysis) if isinstance(pdf_analysis, list) else 1
103
  if pdf_count > 0:
104
  # Check if documents are actually property-related
105
  property_related_docs = sum(1 for doc in pdf_analysis if doc.get('is_property_related', False))
106
  if property_related_docs > 0:
107
+ trust_score += min(15, property_related_docs * 5) # Increased from 10 to 15
108
  reasoning_parts.append(f"Property has {property_related_docs} property-related documents")
109
  else:
110
+ trust_score -= 10 # Reduced penalty from 15 to 10
111
  reasoning_parts.append("No property-related documents detected")
112
 
113
  # Bonus for multiple documents
114
  if property_related_docs >= 2:
115
+ trust_score += 5 # Increased from 3 to 5
116
  reasoning_parts.append("Multiple supporting documents provided")
117
 
118
+ # Text quality assessment - More balanced
119
  if text and len(text) > 200 and not fake_detected:
120
+ trust_score += 12 # Increased from 8 to 12
121
  reasoning_parts.append("Detailed property description provided")
122
  elif text and len(text) > 100 and not fake_detected:
123
+ trust_score += 8 # Increased from 4 to 8
124
  reasoning_parts.append("Adequate property description provided")
125
  elif len(text) < 50:
126
+ trust_score -= 15 # Reduced penalty from 20 to 15
127
  reasoning_parts.append("Very short property description")
128
 
129
+ # Location quality assessment - More balanced
130
  if 'hyderabad' in text_lower or 'mumbai' in text_lower or 'delhi' in text_lower or 'bangalore' in text_lower:
131
  if not fake_detected:
132
+ trust_score += 5 # Increased from 3 to 5
133
  reasoning_parts.append("Property in major city")
134
 
135
+ # Property type assessment - More balanced
136
  if any(prop_type in text_lower for prop_type in ['apartment', 'flat', 'house', 'villa', 'bungalow']):
137
  if not fake_detected:
138
+ trust_score += 4 # Increased from 2 to 4
139
  reasoning_parts.append("Clear property type mentioned")
140
 
141
+ # Amenities assessment - More balanced
142
  amenities_count = sum(1 for amenity in ['pool', 'gym', 'garden', 'parking', 'security', 'lift', 'balcony']
143
  if amenity in text_lower)
144
  if amenities_count > 0 and not fake_detected:
145
+ trust_score += min(8, amenities_count * 2) # Increased from 5 to 8
146
  reasoning_parts.append(f"Property has {amenities_count} amenities mentioned")
147
 
148
+ # CRITICAL: Additional fake data checks - but be less punitive
149
  # Check if all major fields are just numbers
150
  numeric_fields = ['property_name', 'bedrooms', 'bathrooms', 'sq_ft', 'market_value']
151
  numeric_count = 0
 
155
 
156
  if numeric_count >= 3: # If 3+ fields are just numbers
157
  fake_detected = True
158
+ trust_score -= 30 # Reduced penalty from 60 to 30
159
  reasoning_parts.append("Multiple fields contain only numbers (highly suspicious)")
160
 
161
+ # Ensure minimum score for any valid data
162
+ if trust_score < 10 and (image_analysis or pdf_analysis):
163
+ trust_score = 10 # Minimum score if there are images or documents
164
+
165
  # Ensure score is within bounds
166
  trust_score = max(0, min(100, trust_score))
167
 
 
175
 
176
  except Exception as e:
177
  logger.error(f"Error in trust score generation: {str(e)}")
178
+ return 20.0, f"Trust analysis failed: {str(e)}" # Increased from 10.0 to 20.0
templates/3d.html ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Cloudinary 3D Viewer Test</title>
6
+ <script src="https://dimensions-3d-viewer.cloudinary.com/1.0.12/all.js"></script>
7
+ <script src="https://dimensions-tag.cloudinary.com/0.0.105/all.js"></script>
8
+ </head>
9
+ <body>
10
+
11
+ <label>Cloudinary 3D Viewer Example</label>
12
+ <div
13
+ id="three-d-viewer"
14
+ style="height: 500px; width: 100%;"
15
+ data-d8s-type="3d"
16
+ data-d8s-id="93f1cbde-738f-4507-b3ef-1ea62c128adb">
17
+ </div>
18
+
19
+ <script>
20
+ const d8sApi = initDimensions({
21
+ cloudName: "dn4rackei",
22
+ viewers: ["3D"]
23
+ });
24
+ </script>
25
+
26
+ </body>
27
+ </html>
templates/analyzeindex.html ADDED
@@ -0,0 +1,595 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Advanced AI Property Image Analyzer</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
9
+ <style>
10
+ body {
11
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
+ min-height: 100vh;
13
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
14
+ }
15
+ .main-container {
16
+ background: rgba(255, 255, 255, 0.95);
17
+ border-radius: 20px;
18
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
19
+ backdrop-filter: blur(10px);
20
+ margin: 20px auto;
21
+ max-width: 1400px;
22
+ }
23
+ .header {
24
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
25
+ color: white;
26
+ padding: 30px;
27
+ border-radius: 20px 20px 0 0;
28
+ text-align: center;
29
+ }
30
+ .upload-area {
31
+ border: 3px dashed #667eea;
32
+ border-radius: 15px;
33
+ padding: 40px;
34
+ text-align: center;
35
+ background: rgba(102, 126, 234, 0.05);
36
+ transition: all 0.3s ease;
37
+ cursor: pointer;
38
+ }
39
+ .upload-area:hover {
40
+ border-color: #764ba2;
41
+ background: rgba(118, 75, 162, 0.05);
42
+ }
43
+ .upload-area.dragover {
44
+ border-color: #764ba2;
45
+ background: rgba(118, 75, 162, 0.1);
46
+ }
47
+ .result-card {
48
+ background: white;
49
+ border-radius: 15px;
50
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
51
+ margin: 20px 0;
52
+ overflow: hidden;
53
+ transition: transform 0.3s ease;
54
+ }
55
+ .result-card:hover {
56
+ transform: translateY(-5px);
57
+ }
58
+ .result-header {
59
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
60
+ color: white;
61
+ padding: 20px;
62
+ font-weight: bold;
63
+ }
64
+ .result-body {
65
+ padding: 25px;
66
+ }
67
+ .metric-card {
68
+ background: #f8f9fa;
69
+ border-radius: 10px;
70
+ padding: 20px;
71
+ margin: 10px 0;
72
+ border-left: 4px solid #667eea;
73
+ }
74
+ .score-badge {
75
+ font-size: 1.2em;
76
+ font-weight: bold;
77
+ padding: 8px 16px;
78
+ border-radius: 20px;
79
+ }
80
+ .score-excellent { background: #28a745; color: white; }
81
+ .score-good { background: #17a2b8; color: white; }
82
+ .score-fair { background: #ffc107; color: black; }
83
+ .score-poor { background: #dc3545; color: white; }
84
+ .ai-model-badge {
85
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
86
+ color: white;
87
+ padding: 5px 12px;
88
+ border-radius: 15px;
89
+ font-size: 0.8em;
90
+ margin: 2px;
91
+ display: inline-block;
92
+ }
93
+ .loading {
94
+ text-align: center;
95
+ padding: 40px;
96
+ }
97
+ .spinner {
98
+ border: 4px solid #f3f3f3;
99
+ border-top: 4px solid #667eea;
100
+ border-radius: 50%;
101
+ width: 50px;
102
+ height: 50px;
103
+ animation: spin 1s linear infinite;
104
+ margin: 0 auto 20px;
105
+ }
106
+ @keyframes spin {
107
+ 0% { transform: rotate(0deg); }
108
+ 100% { transform: rotate(360deg); }
109
+ }
110
+ .progress-bar {
111
+ height: 8px;
112
+ border-radius: 4px;
113
+ background: #e9ecef;
114
+ overflow: hidden;
115
+ }
116
+ .progress-fill {
117
+ height: 100%;
118
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
119
+ transition: width 0.3s ease;
120
+ }
121
+ .insight-card {
122
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
123
+ border-radius: 10px;
124
+ padding: 15px;
125
+ margin: 10px 0;
126
+ border-left: 4px solid #28a745;
127
+ }
128
+ .warning-card {
129
+ background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
130
+ border-radius: 10px;
131
+ padding: 15px;
132
+ margin: 10px 0;
133
+ border-left: 4px solid #ffc107;
134
+ }
135
+ .error-card {
136
+ background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
137
+ border-radius: 10px;
138
+ padding: 15px;
139
+ margin: 10px 0;
140
+ border-left: 4px solid #dc3545;
141
+ }
142
+ .object-item {
143
+ background: white;
144
+ border: 1px solid #dee2e6;
145
+ border-radius: 8px;
146
+ padding: 10px;
147
+ margin: 5px 0;
148
+ display: flex;
149
+ justify-content: space-between;
150
+ align-items: center;
151
+ }
152
+ .confidence-bar {
153
+ width: 100px;
154
+ height: 6px;
155
+ background: #e9ecef;
156
+ border-radius: 3px;
157
+ overflow: hidden;
158
+ }
159
+ .confidence-fill {
160
+ height: 100%;
161
+ background: linear-gradient(90deg, #28a745 0%, #17a2b8 100%);
162
+ transition: width 0.3s ease;
163
+ }
164
+ .scene-concept {
165
+ background: rgba(102, 126, 234, 0.1);
166
+ border-radius: 8px;
167
+ padding: 8px 12px;
168
+ margin: 5px;
169
+ display: inline-block;
170
+ font-size: 0.9em;
171
+ }
172
+ .market-tier-badge {
173
+ font-size: 1.1em;
174
+ font-weight: bold;
175
+ padding: 10px 20px;
176
+ border-radius: 25px;
177
+ text-transform: uppercase;
178
+ letter-spacing: 1px;
179
+ }
180
+ .tier-luxury { background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%); color: #333; }
181
+ .tier-premium { background: linear-gradient(135deg, #c0c0c0 0%, #e5e5e5 100%); color: #333; }
182
+ .tier-standard { background: linear-gradient(135deg, #cd7f32 0%, #daa520 100%); color: white; }
183
+ .tier-value { background: linear-gradient(135deg, #6c757d 0%, #495057 100%); color: white; }
184
+ </style>
185
+ </head>
186
+ <body>
187
+ <div class="container-fluid">
188
+ <div class="main-container">
189
+ <div class="header">
190
+ <h1><i class="fas fa-robot"></i> Advanced AI Property Image Analyzer</h1>
191
+ <p class="mb-0">State-of-the-art AI-powered real estate image analysis with comprehensive insights</p>
192
+ </div>
193
+
194
+ <div class="container-fluid p-4">
195
+ <div class="row">
196
+ <div class="col-md-6">
197
+ <div class="upload-area" id="uploadArea">
198
+ <i class="fas fa-cloud-upload-alt fa-3x text-primary mb-3"></i>
199
+ <h4>Upload Property Image</h4>
200
+ <p>Drag and drop your image here or click to browse</p>
201
+ <input type="file" id="imageInput" accept="image/*" style="display: none;">
202
+ <button class="btn btn-primary btn-lg" onclick="document.getElementById('imageInput').click()">
203
+ <i class="fas fa-upload"></i> Choose Image
204
+ </button>
205
+ </div>
206
+
207
+ <div id="imagePreview" class="mt-3" style="display: none;">
208
+ <img id="previewImg" class="img-fluid rounded" style="max-height: 300px;">
209
+ </div>
210
+ </div>
211
+
212
+ <div class="col-md-6">
213
+ <div id="loading" class="loading" style="display: none;">
214
+ <div class="spinner"></div>
215
+ <h4>Advanced AI Analysis in Progress...</h4>
216
+ <p>Multiple AI models are analyzing your image</p>
217
+ <div class="progress-bar">
218
+ <div class="progress-fill" style="width: 0%"></div>
219
+ </div>
220
+ </div>
221
+
222
+ <div id="results" style="display: none;">
223
+ <!-- Results will be populated here -->
224
+ </div>
225
+ </div>
226
+ </div>
227
+ </div>
228
+ </div>
229
+ </div>
230
+
231
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
232
+ <script>
233
+ const uploadArea = document.getElementById('uploadArea');
234
+ const imageInput = document.getElementById('imageInput');
235
+ const imagePreview = document.getElementById('imagePreview');
236
+ const previewImg = document.getElementById('previewImg');
237
+ const loading = document.getElementById('loading');
238
+ const results = document.getElementById('results');
239
+
240
+ // Drag and drop functionality
241
+ uploadArea.addEventListener('dragover', (e) => {
242
+ e.preventDefault();
243
+ uploadArea.classList.add('dragover');
244
+ });
245
+
246
+ uploadArea.addEventListener('dragleave', () => {
247
+ uploadArea.classList.remove('dragover');
248
+ });
249
+
250
+ uploadArea.addEventListener('drop', (e) => {
251
+ e.preventDefault();
252
+ uploadArea.classList.remove('dragover');
253
+ const files = e.dataTransfer.files;
254
+ if (files.length > 0) {
255
+ handleImageUpload(files[0]);
256
+ }
257
+ });
258
+
259
+ uploadArea.addEventListener('click', () => {
260
+ imageInput.click();
261
+ });
262
+
263
+ imageInput.addEventListener('change', (e) => {
264
+ if (e.target.files.length > 0) {
265
+ handleImageUpload(e.target.files[0]);
266
+ }
267
+ });
268
+
269
+ function handleImageUpload(file) {
270
+ if (!file.type.startsWith('image/')) {
271
+ alert('Please select a valid image file.');
272
+ return;
273
+ }
274
+
275
+ // Show preview
276
+ const reader = new FileReader();
277
+ reader.onload = (e) => {
278
+ previewImg.src = e.target.result;
279
+ imagePreview.style.display = 'block';
280
+ };
281
+ reader.readAsDataURL(file);
282
+
283
+ // Start analysis
284
+ analyzeImage(file);
285
+ }
286
+
287
+ function analyzeImage(file) {
288
+ const formData = new FormData();
289
+ formData.append('image', file);
290
+
291
+ // Show loading
292
+ loading.style.display = 'block';
293
+ results.style.display = 'none';
294
+
295
+ // Simulate progress
296
+ let progress = 0;
297
+ const progressFill = document.querySelector('.progress-fill');
298
+ const progressInterval = setInterval(() => {
299
+ progress += Math.random() * 15;
300
+ if (progress > 90) progress = 90;
301
+ progressFill.style.width = progress + '%';
302
+ }, 500);
303
+
304
+ fetch('/analyze', {
305
+ method: 'POST',
306
+ body: formData
307
+ })
308
+ .then(response => response.json())
309
+ .then(data => {
310
+ clearInterval(progressInterval);
311
+ progressFill.style.width = '100%';
312
+
313
+ setTimeout(() => {
314
+ loading.style.display = 'none';
315
+ displayResults(data);
316
+ }, 1000);
317
+ })
318
+ .catch(error => {
319
+ clearInterval(progressInterval);
320
+ loading.style.display = 'none';
321
+ alert('Analysis failed: ' + error.message);
322
+ });
323
+ }
324
+
325
+ function displayResults(data) {
326
+ if (data.error) {
327
+ results.innerHTML = `
328
+ <div class="error-card">
329
+ <h5><i class="fas fa-exclamation-triangle"></i> Analysis Error</h5>
330
+ <p>${data.error}</p>
331
+ </div>
332
+ `;
333
+ results.style.display = 'block';
334
+ return;
335
+ }
336
+
337
+ const summary = data.analysis_summary;
338
+ const roomClass = data.room_classification;
339
+ const quality = data.quality_analysis;
340
+ const objects = data.object_detection;
341
+ const assessment = data.property_assessment;
342
+ const insights = data.property_insights;
343
+ const scene = data.scene_analysis;
344
+ const market = data.market_analysis;
345
+ const roomSize = data.room_size_estimation;
346
+
347
+ results.innerHTML = `
348
+ <!-- Analysis Summary -->
349
+ <div class="result-card">
350
+ <div class="result-header">
351
+ <h4><i class="fas fa-chart-line"></i> Analysis Summary</h4>
352
+ </div>
353
+ <div class="result-body">
354
+ <div class="row">
355
+ <div class="col-md-6">
356
+ <div class="metric-card">
357
+ <h6><i class="fas fa-home"></i> Room Type</h6>
358
+ <p class="mb-1"><strong>${roomClass.room_type}</strong></p>
359
+ <div class="confidence-bar">
360
+ <div class="confidence-fill" style="width: ${roomClass.confidence}%"></div>
361
+ </div>
362
+ <small>Confidence: ${roomClass.confidence}%</small>
363
+ </div>
364
+ </div>
365
+ <div class="col-md-6">
366
+ <div class="metric-card">
367
+ <h6><i class="fas fa-star"></i> Overall Score</h6>
368
+ <span class="score-badge ${getScoreClass(assessment.overall_score)}">${assessment.overall_score}/100</span>
369
+ <p class="mt-2 mb-0"><small>${assessment.professional_grade ? 'Professional Grade' : 'Standard Grade'}</small></p>
370
+ </div>
371
+ </div>
372
+ </div>
373
+
374
+ <div class="row mt-3">
375
+ <div class="col-md-4">
376
+ <div class="metric-card">
377
+ <h6><i class="fas fa-camera"></i> Quality Score</h6>
378
+ <span class="score-badge ${getScoreClass(quality.quality_score)}">${quality.quality_score}/100</span>
379
+ <p class="mt-2 mb-0"><small>${quality.quality_level}</small></p>
380
+ </div>
381
+ </div>
382
+ <div class="col-md-4">
383
+ <div class="metric-card">
384
+ <h6><i class="fas fa-cube"></i> Objects Detected</h6>
385
+ <span class="score-badge score-good">${objects.objects.length}</span>
386
+ <p class="mt-2 mb-0"><small>${objects.analysis.total_objects || 0} total</small></p>
387
+ </div>
388
+ </div>
389
+ <div class="col-md-4">
390
+ <div class="metric-card">
391
+ <h6><i class="fas fa-ruler-combined"></i> Room Size</h6>
392
+ <span class="score-badge score-good">${roomSize.estimated_size}</span>
393
+ <p class="mt-2 mb-0"><small>Confidence: ${roomSize.confidence}</small></p>
394
+ </div>
395
+ </div>
396
+ </div>
397
+ </div>
398
+ </div>
399
+
400
+ <!-- Market Analysis -->
401
+ <div class="result-card">
402
+ <div class="result-header">
403
+ <h4><i class="fas fa-chart-bar"></i> Market Analysis</h4>
404
+ </div>
405
+ <div class="result-body">
406
+ <div class="row">
407
+ <div class="col-md-6">
408
+ <div class="market-tier-badge tier-${market.market_tier.toLowerCase()}">${market.market_tier}</div>
409
+ <p class="mt-2"><strong>Target Market:</strong> ${market.target_market}</p>
410
+ <p><strong>Price Positioning:</strong> ${market.price_positioning}</p>
411
+ <p><strong>Competitive Position:</strong> ${market.competitive_position}</p>
412
+ </div>
413
+ <div class="col-md-6">
414
+ <h6>Competitive Advantages:</h6>
415
+ <ul>
416
+ ${market.competitive_advantages.map(adv => `<li>${adv}</li>`).join('')}
417
+ </ul>
418
+
419
+ <h6>Differentiation Factors:</h6>
420
+ <ul>
421
+ ${market.differentiation_factors.map(factor => `<li>${factor}</li>`).join('')}
422
+ </ul>
423
+ </div>
424
+ </div>
425
+ </div>
426
+ </div>
427
+
428
+ <!-- Property Insights -->
429
+ <div class="result-card">
430
+ <div class="result-header">
431
+ <h4><i class="fas fa-lightbulb"></i> Property Insights</h4>
432
+ </div>
433
+ <div class="result-body">
434
+ <div class="row">
435
+ <div class="col-md-6">
436
+ <h6><i class="fas fa-bullhorn"></i> Marketing Tips</h6>
437
+ <ul>
438
+ ${insights.marketing_tips.map(tip => `<li>${tip}</li>`).join('')}
439
+ </ul>
440
+
441
+ <h6><i class="fas fa-users"></i> Target Audience</h6>
442
+ <ul>
443
+ ${insights.target_audience.map(audience => `<li>${audience}</li>`).join('')}
444
+ </ul>
445
+ </div>
446
+ <div class="col-md-6">
447
+ <h6><i class="fas fa-dollar-sign"></i> Pricing Considerations</h6>
448
+ <ul>
449
+ ${insights.pricing_considerations.map(consideration => `<li>${consideration}</li>`).join('')}
450
+ </ul>
451
+
452
+ <h6><i class="fas fa-tools"></i> Improvement Suggestions</h6>
453
+ <ul>
454
+ ${insights.improvement_suggestions.map(suggestion => `<li>${suggestion}</li>`).join('')}
455
+ </ul>
456
+ </div>
457
+ </div>
458
+ </div>
459
+ </div>
460
+
461
+ <!-- Scene Analysis -->
462
+ <div class="result-card">
463
+ <div class="result-header">
464
+ <h4><i class="fas fa-palette"></i> Scene Analysis</h4>
465
+ </div>
466
+ <div class="result-body">
467
+ <div class="row">
468
+ <div class="col-md-6">
469
+ <h6>Dominant Style: ${scene.style_analysis.dominant_style}</h6>
470
+ <p><strong>Style Confidence:</strong> ${(scene.style_analysis.style_confidence * 100).toFixed(1)}%</p>
471
+
472
+ <h6>Scene Concepts:</h6>
473
+ <div>
474
+ ${scene.scene_concepts.map(concept =>
475
+ `<span class="scene-concept">${concept.concept} (${(concept.confidence * 100).toFixed(1)}%)</span>`
476
+ ).join('')}
477
+ </div>
478
+ </div>
479
+ <div class="col-md-6">
480
+ <h6>Quality Indicators:</h6>
481
+ <ul>
482
+ ${scene.style_analysis.quality_indicators.map(indicator =>
483
+ `<li>${indicator.concept} (${(indicator.confidence * 100).toFixed(1)}%)</li>`
484
+ ).join('')}
485
+ </ul>
486
+
487
+ <h6>Functionality Indicators:</h6>
488
+ <ul>
489
+ ${scene.style_analysis.functionality_indicators.map(indicator =>
490
+ `<li>${indicator.concept} (${(indicator.confidence * 100).toFixed(1)}%)</li>`
491
+ ).join('')}
492
+ </ul>
493
+ </div>
494
+ </div>
495
+ </div>
496
+ </div>
497
+
498
+ <!-- Detailed Analysis -->
499
+ <div class="result-card">
500
+ <div class="result-header">
501
+ <h4><i class="fas fa-microscope"></i> Detailed Analysis</h4>
502
+ </div>
503
+ <div class="result-body">
504
+ <div class="row">
505
+ <div class="col-md-6">
506
+ <h6>Strengths:</h6>
507
+ <ul>
508
+ ${assessment.strengths.map(strength => `<li class="insight-card">${strength}</li>`).join('')}
509
+ </ul>
510
+
511
+ <h6>Weaknesses:</h6>
512
+ <ul>
513
+ ${assessment.weaknesses.map(weakness => `<li class="warning-card">${weakness}</li>`).join('')}
514
+ </ul>
515
+ </div>
516
+ <div class="col-md-6">
517
+ <h6>Recommendations:</h6>
518
+ <ul>
519
+ ${assessment.recommendations.map(rec => `<li class="insight-card">${rec}</li>`).join('')}
520
+ </ul>
521
+
522
+ <h6>Market Opportunities:</h6>
523
+ <ul>
524
+ ${market.market_opportunities.map(opp => `<li class="insight-card">${opp}</li>`).join('')}
525
+ </ul>
526
+ </div>
527
+ </div>
528
+ </div>
529
+ </div>
530
+
531
+ <!-- Object Detection Results -->
532
+ <div class="result-card">
533
+ <div class="result-header">
534
+ <h4><i class="fas fa-cube"></i> Object Detection</h4>
535
+ </div>
536
+ <div class="result-body">
537
+ <div class="row">
538
+ <div class="col-md-6">
539
+ <h6>Detected Objects:</h6>
540
+ ${objects.objects.map(obj => `
541
+ <div class="object-item">
542
+ <span><i class="fas fa-tag"></i> ${obj.label}</span>
543
+ <div class="confidence-bar">
544
+ <div class="confidence-fill" style="width: ${obj.confidence * 100}%"></div>
545
+ </div>
546
+ <small>${(obj.confidence * 100).toFixed(1)}%</small>
547
+ </div>
548
+ `).join('')}
549
+ </div>
550
+ <div class="col-md-6">
551
+ <h6>Object Categories:</h6>
552
+ <p><strong>Furniture:</strong> ${objects.analysis.object_categories?.furniture?.length || 0} items</p>
553
+ <p><strong>Appliances:</strong> ${objects.analysis.object_categories?.appliances?.length || 0} items</p>
554
+ <p><strong>Fixtures:</strong> ${objects.analysis.object_categories?.fixtures?.length || 0} items</p>
555
+ <p><strong>Other:</strong> ${objects.analysis.object_categories?.other?.length || 0} items</p>
556
+
557
+ <h6>Composition Analysis:</h6>
558
+ <p><strong>Object Density:</strong> ${objects.analysis.object_density?.toFixed(2) || 0}</p>
559
+ <p><strong>Composition Balance:</strong> ${(objects.analysis.composition_balance * 100).toFixed(1)}%</p>
560
+ </div>
561
+ </div>
562
+ </div>
563
+ </div>
564
+
565
+ <!-- AI Models Used -->
566
+ <div class="result-card">
567
+ <div class="result-header">
568
+ <h4><i class="fas fa-robot"></i> AI Models Used</h4>
569
+ </div>
570
+ <div class="result-body">
571
+ <div>
572
+ <span class="ai-model-badge">Room Classification</span>
573
+ <span class="ai-model-badge">BLIP Captioning</span>
574
+ <span class="ai-model-badge">DETR Detection</span>
575
+ <span class="ai-model-badge">Quality Analysis</span>
576
+ <span class="ai-model-badge">CLIP Scene Understanding</span>
577
+ <span class="ai-model-badge">Market Analysis</span>
578
+ </div>
579
+ <p class="mt-3 mb-0"><small><i class="fas fa-clock"></i> Analysis completed at ${new Date().toLocaleString()}</small></p>
580
+ </div>
581
+ </div>
582
+ `;
583
+
584
+ results.style.display = 'block';
585
+ }
586
+
587
+ function getScoreClass(score) {
588
+ if (score >= 85) return 'score-excellent';
589
+ if (score >= 70) return 'score-good';
590
+ if (score >= 50) return 'score-fair';
591
+ return 'score-poor';
592
+ }
593
+ </script>
594
+ </body>
595
+ </html>
templates/form.html ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Select Property</title>
6
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
7
+ </head>
8
+ <body class="bg-light">
9
+ <div class="container py-5">
10
+ <h2 class="mb-4">Tell us what you're looking for</h2>
11
+ <form method="POST">
12
+ <div class="mb-3">
13
+ <label class="form-label">Property Type</label>
14
+ <input type="text" class="form-control" name="propertyType" required placeholder="Villa, Flat, PG...">
15
+ </div>
16
+ <div class="mb-3">
17
+ <label class="form-label">Price Range (min-max)</label>
18
+ <input type="text" class="form-control" name="priceRange" required placeholder="50000-20000000">
19
+ </div>
20
+ <button class="btn btn-primary">Show Recommendations</button>
21
+ </form>
22
+ </div>
23
+ </body>
24
+ </html>
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/index2.html ADDED
The diff for this file is too large to render. See raw diff
 
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>
templates/prop.html ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Property Recommender</title>
6
+ <style>
7
+ body {
8
+ font-family: Arial, sans-serif;
9
+ background: #f8f9fa;
10
+ padding: 20px;
11
+ }
12
+ .container {
13
+ max-width: 960px;
14
+ margin: auto;
15
+ }
16
+ form {
17
+ background: #fff;
18
+ padding: 20px;
19
+ border-radius: 8px;
20
+ box-shadow: 0 0 8px rgba(0,0,0,0.1);
21
+ margin-bottom: 30px;
22
+ }
23
+ input[type="text"], button {
24
+ width: 100%;
25
+ padding: 12px;
26
+ margin: 8px 0;
27
+ border-radius: 4px;
28
+ border: 1px solid #ccc;
29
+ }
30
+ button {
31
+ background-color: #273469;
32
+ color: white;
33
+ cursor: pointer;
34
+ font-weight: bold;
35
+ }
36
+ button:hover {
37
+ background-color: #1e2749;
38
+ }
39
+ .property-card {
40
+ background: #fff;
41
+ padding: 16px;
42
+ margin-bottom: 20px;
43
+ border-radius: 8px;
44
+ border-left: 6px solid #273469;
45
+ box-shadow: 0 0 5px rgba(0,0,0,0.1);
46
+ }
47
+ .property-card img {
48
+ width: 200px;
49
+ height: auto;
50
+ margin-right: 10px;
51
+ border-radius: 6px;
52
+ }
53
+ .images {
54
+ display: flex;
55
+ gap: 10px;
56
+ margin-top: 10px;
57
+ }
58
+ </style>
59
+ </head>
60
+ <body>
61
+ <div class="container">
62
+ <h1>Find Your Dream Property</h1>
63
+
64
+ <form method="POST">
65
+ <label>What type of property are you looking for?</label>
66
+ <input type="text" name="propertyType" placeholder="e.g., Villa, Flat" required>
67
+
68
+ <label>What price range are you looking for? (e.g., 50000-20000000)</label>
69
+ <input type="text" name="priceRange" placeholder="e.g., 50000-20000000" required>
70
+
71
+ <button type="submit">Get Recommendations</button>
72
+ </form>
73
+
74
+ {% if error %}
75
+ <p style="color: red;"><strong>Error:</strong> {{ error }}</p>
76
+ {% endif %}
77
+
78
+ {% if recommended %}
79
+ <h2>Recommended Properties</h2>
80
+ {% for prop in recommended %}
81
+ <div class="property-card">
82
+ <h3>{{ prop.propertyName }}</h3>
83
+ <p><strong>Type:</strong> {{ prop.typeName }}</p>
84
+ <p><strong>Location:</strong> {{ prop.address }}</p>
85
+ <p><strong>Price:</strong> ₹{{ "{:,}".format(prop.marketValue) }}</p>
86
+ <p><strong>Description:</strong> {{ prop.description }}</p>
87
+ <div class="images">
88
+ {% for img in prop.propertyImages[:3] %}
89
+ <img src="{{ img }}" alt="Property Image">
90
+ {% endfor %}
91
+ </div>
92
+ </div>
93
+ {% endfor %}
94
+ {% endif %}
95
+ </div>
96
+ </body>
97
+ </html>
templates/properties.html ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Recommended Properties</title>
7
+ <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <h1 class="text-center my-4">Recommended Properties</h1>
12
+ <div class="properties">
13
+ {% for property in properties %}
14
+ <div class="property card mb-3">
15
+ <div class="card-body">
16
+ <h5 class="card-title">{{ property.propertyName }}</h5>
17
+ <p class="card-text"><strong>Type:</strong> {{ property.typeName }}</p>
18
+ <p class="card-text"><strong>Address:</strong> {{ property.address }}</p>
19
+ <p class="card-text"><strong>Market Value:</strong> {{ property.marketValue }}</p>
20
+ <p class="card-text">{{ property.description }}</p>
21
+ </div>
22
+ </div>
23
+ {% endfor %}
24
+ </div>
25
+ </div>
26
+ </body>
27
+ </html>
templates/results.html ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Recommended Properties</title>
6
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
7
+ </head>
8
+ <body class="bg-white">
9
+ <div class="container py-5">
10
+ <h2 class="mb-4">Recommended Properties</h2>
11
+ <a href="/" class="btn btn-secondary mb-4">🔙 Go Back</a>
12
+ <div class="row">
13
+ {% for prop in recommended %}
14
+ <div class="col-md-6 mb-4">
15
+ <div class="card h-100">
16
+ <img src="{{ prop.propertyImages[0] if prop.propertyImages else '' }}" class="card-img-top" alt="Image">
17
+ <div class="card-body">
18
+ <h5 class="card-title">{{ prop.propertyName }}</h5>
19
+ <p class="card-text">{{ prop.description[:150] }}...</p>
20
+ <p><strong>₹ {{ "{:,}".format(prop.marketValue) }}</strong> | {{ prop.address }}</p>
21
+ <p><small>Type: {{ prop.typeName }}</small></p>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ {% endfor %}
26
+ </div>
27
+ </div>
28
+ </body>
29
+ </html>
templates/visualindex.html ADDED
@@ -0,0 +1,623 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Property Visual Search - AI-Powered Image Recognition</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
+ <style>
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ body {
17
+ font-family: 'Inter', sans-serif;
18
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
19
+ min-height: 100vh;
20
+ color: #333;
21
+ }
22
+
23
+ .container {
24
+ max-width: 1200px;
25
+ margin: 0 auto;
26
+ padding: 20px;
27
+ }
28
+
29
+ .header {
30
+ text-align: center;
31
+ margin-bottom: 40px;
32
+ color: white;
33
+ }
34
+
35
+ .header h1 {
36
+ font-size: 2.5rem;
37
+ font-weight: 700;
38
+ margin-bottom: 10px;
39
+ text-shadow: 0 2px 4px rgba(0,0,0,0.3);
40
+ }
41
+
42
+ .header p {
43
+ font-size: 1.1rem;
44
+ opacity: 0.9;
45
+ font-weight: 300;
46
+ }
47
+
48
+ .main-content {
49
+ background: white;
50
+ border-radius: 20px;
51
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
52
+ overflow: hidden;
53
+ margin-bottom: 30px;
54
+ }
55
+
56
+ .upload-section {
57
+ padding: 40px;
58
+ text-align: center;
59
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
60
+ }
61
+
62
+ .upload-area {
63
+ border: 3px dashed #667eea;
64
+ border-radius: 15px;
65
+ padding: 60px 20px;
66
+ background: white;
67
+ cursor: pointer;
68
+ transition: all 0.3s ease;
69
+ margin-bottom: 30px;
70
+ }
71
+
72
+ .upload-area:hover {
73
+ border-color: #764ba2;
74
+ background: #f8f9ff;
75
+ transform: translateY(-2px);
76
+ }
77
+
78
+ .upload-area.dragover {
79
+ border-color: #764ba2;
80
+ background: #f0f2ff;
81
+ transform: scale(1.02);
82
+ }
83
+
84
+ .upload-icon {
85
+ font-size: 4rem;
86
+ color: #667eea;
87
+ margin-bottom: 20px;
88
+ }
89
+
90
+ .upload-text {
91
+ font-size: 1.2rem;
92
+ color: #666;
93
+ margin-bottom: 10px;
94
+ }
95
+
96
+ .upload-subtext {
97
+ font-size: 0.9rem;
98
+ color: #999;
99
+ }
100
+
101
+ .file-input {
102
+ display: none;
103
+ }
104
+
105
+ .upload-btn {
106
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
107
+ color: white;
108
+ border: none;
109
+ padding: 15px 30px;
110
+ border-radius: 50px;
111
+ font-size: 1rem;
112
+ font-weight: 600;
113
+ cursor: pointer;
114
+ transition: all 0.3s ease;
115
+ margin-top: 20px;
116
+ }
117
+
118
+ .upload-btn:hover {
119
+ transform: translateY(-2px);
120
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
121
+ }
122
+
123
+ .upload-btn:disabled {
124
+ opacity: 0.6;
125
+ cursor: not-allowed;
126
+ transform: none;
127
+ }
128
+
129
+ .status-section {
130
+ padding: 20px 40px;
131
+ background: #f8f9fa;
132
+ border-top: 1px solid #e9ecef;
133
+ }
134
+
135
+ .status-indicator {
136
+ display: flex;
137
+ align-items: center;
138
+ gap: 10px;
139
+ font-size: 0.9rem;
140
+ }
141
+
142
+ .status-dot {
143
+ width: 10px;
144
+ height: 10px;
145
+ border-radius: 50%;
146
+ background: #dc3545;
147
+ }
148
+
149
+ .status-dot.ready {
150
+ background: #28a745;
151
+ }
152
+
153
+ .status-dot.loading {
154
+ background: #ffc107;
155
+ animation: pulse 1.5s infinite;
156
+ }
157
+
158
+ @keyframes pulse {
159
+ 0%, 100% { opacity: 1; }
160
+ 50% { opacity: 0.5; }
161
+ }
162
+
163
+ .results-section {
164
+ padding: 40px;
165
+ display: none;
166
+ }
167
+
168
+ .results-header {
169
+ text-align: center;
170
+ margin-bottom: 30px;
171
+ }
172
+
173
+ .results-header h2 {
174
+ color: #333;
175
+ font-size: 1.8rem;
176
+ margin-bottom: 10px;
177
+ }
178
+
179
+ .results-grid {
180
+ display: grid;
181
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
182
+ gap: 20px;
183
+ margin-top: 30px;
184
+ }
185
+
186
+ .result-card {
187
+ background: white;
188
+ border-radius: 15px;
189
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
190
+ overflow: hidden;
191
+ transition: all 0.3s ease;
192
+ }
193
+
194
+ .result-card:hover {
195
+ transform: translateY(-5px);
196
+ box-shadow: 0 15px 30px rgba(0,0,0,0.15);
197
+ }
198
+
199
+ .result-image {
200
+ width: 100%;
201
+ height: 200px;
202
+ background: #f8f9fa;
203
+ display: flex;
204
+ align-items: center;
205
+ justify-content: center;
206
+ color: #999;
207
+ font-size: 0.9rem;
208
+ }
209
+
210
+ .result-image img {
211
+ width: 100%;
212
+ height: 100%;
213
+ object-fit: cover;
214
+ border-radius: 0;
215
+ }
216
+
217
+ .result-card img {
218
+ width: 100%;
219
+ height: 200px;
220
+ object-fit: cover;
221
+ border-radius: 0;
222
+ }
223
+
224
+ .result-info {
225
+ padding: 20px;
226
+ }
227
+
228
+ .result-title {
229
+ font-weight: 600;
230
+ color: #333;
231
+ margin-bottom: 10px;
232
+ }
233
+
234
+ .result-details {
235
+ display: flex;
236
+ justify-content: space-between;
237
+ align-items: center;
238
+ margin-bottom: 15px;
239
+ }
240
+
241
+ .similarity-score {
242
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
243
+ color: white;
244
+ padding: 5px 12px;
245
+ border-radius: 20px;
246
+ font-size: 0.8rem;
247
+ font-weight: 600;
248
+ }
249
+
250
+ .property-id {
251
+ color: #666;
252
+ font-size: 0.9rem;
253
+ }
254
+
255
+ .loading-spinner {
256
+ display: none;
257
+ text-align: center;
258
+ padding: 40px;
259
+ }
260
+
261
+ .spinner {
262
+ border: 4px solid #f3f3f3;
263
+ border-top: 4px solid #667eea;
264
+ border-radius: 50%;
265
+ width: 40px;
266
+ height: 40px;
267
+ animation: spin 1s linear infinite;
268
+ margin: 0 auto 20px;
269
+ }
270
+
271
+ @keyframes spin {
272
+ 0% { transform: rotate(0deg); }
273
+ 100% { transform: rotate(360deg); }
274
+ }
275
+
276
+ .error-message {
277
+ background: #f8d7da;
278
+ color: #721c24;
279
+ padding: 15px;
280
+ border-radius: 10px;
281
+ margin: 20px 0;
282
+ display: none;
283
+ }
284
+
285
+ .success-message {
286
+ background: #d4edda;
287
+ color: #155724;
288
+ padding: 15px;
289
+ border-radius: 10px;
290
+ margin: 20px 0;
291
+ display: none;
292
+ }
293
+
294
+ .preview-image {
295
+ max-width: 100%;
296
+ max-height: 300px;
297
+ border-radius: 10px;
298
+ margin: 20px 0;
299
+ display: none;
300
+ }
301
+
302
+ .features {
303
+ display: grid;
304
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
305
+ gap: 20px;
306
+ margin-top: 30px;
307
+ }
308
+
309
+ .feature-card {
310
+ background: white;
311
+ padding: 30px;
312
+ border-radius: 15px;
313
+ text-align: center;
314
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
315
+ }
316
+
317
+ .feature-icon {
318
+ font-size: 2.5rem;
319
+ color: #667eea;
320
+ margin-bottom: 20px;
321
+ }
322
+
323
+ .feature-title {
324
+ font-size: 1.2rem;
325
+ font-weight: 600;
326
+ margin-bottom: 10px;
327
+ color: #333;
328
+ }
329
+
330
+ .feature-description {
331
+ color: #666;
332
+ line-height: 1.6;
333
+ }
334
+
335
+ @media (max-width: 768px) {
336
+ .header h1 {
337
+ font-size: 2rem;
338
+ }
339
+
340
+ .upload-section {
341
+ padding: 20px;
342
+ }
343
+
344
+ .upload-area {
345
+ padding: 40px 20px;
346
+ }
347
+
348
+ .results-grid {
349
+ grid-template-columns: 1fr;
350
+ }
351
+ }
352
+ </style>
353
+ </head>
354
+ <body>
355
+ <div class="container">
356
+ <div class="header">
357
+ <h1><i class="fas fa-search"></i> Property Visual Search</h1>
358
+ <p>AI-Powered Image Recognition for Real Estate</p>
359
+ </div>
360
+
361
+ <div class="main-content">
362
+ <div class="upload-section">
363
+ <div class="upload-area" id="uploadArea">
364
+ <div class="upload-icon">
365
+ <i class="fas fa-cloud-upload-alt"></i>
366
+ </div>
367
+ <div class="upload-text">Drag & Drop your property image here</div>
368
+ <div class="upload-subtext">or click to browse files (JPG, PNG, GIF)</div>
369
+ <input type="file" id="fileInput" class="file-input" accept="image/*">
370
+ <button class="upload-btn" id="uploadBtn" onclick="document.getElementById('fileInput').click()">
371
+ <i class="fas fa-upload"></i> Choose Image
372
+ </button>
373
+ </div>
374
+
375
+ <img id="previewImage" class="preview-image" alt="Preview">
376
+
377
+ <div class="error-message" id="errorMessage"></div>
378
+ <div class="success-message" id="successMessage"></div>
379
+ </div>
380
+
381
+ <div class="status-section">
382
+ <div class="status-indicator">
383
+ <div class="status-dot" id="statusDot"></div>
384
+ <span id="statusText">Initializing visual search system...</span>
385
+ </div>
386
+ </div>
387
+
388
+ <div class="loading-spinner" id="loadingSpinner">
389
+ <div class="spinner"></div>
390
+ <p>Analyzing your image and finding similar properties...</p>
391
+ </div>
392
+
393
+ <div class="results-section" id="resultsSection">
394
+ <div class="results-header">
395
+ <h2>Similar Properties Found</h2>
396
+ <p>Based on visual similarity analysis</p>
397
+ </div>
398
+ <div class="results-grid" id="resultsGrid"></div>
399
+ </div>
400
+ </div>
401
+
402
+ <div class="features">
403
+ <div class="feature-card">
404
+ <div class="feature-icon">
405
+ <i class="fas fa-brain"></i>
406
+ </div>
407
+ <div class="feature-title">AI-Powered</div>
408
+ <div class="feature-description">
409
+ Advanced machine learning algorithms analyze visual patterns and architectural features
410
+ </div>
411
+ </div>
412
+
413
+ <div class="feature-card">
414
+ <div class="feature-icon">
415
+ <i class="fas fa-bolt"></i>
416
+ </div>
417
+ <div class="feature-title">Lightning Fast</div>
418
+ <div class="feature-description">
419
+ Get instant results with our optimized image processing and similarity matching
420
+ </div>
421
+ </div>
422
+
423
+ <div class="feature-card">
424
+ <div class="feature-icon">
425
+ <i class="fas fa-shield-alt"></i>
426
+ </div>
427
+ <div class="feature-title">Secure & Private</div>
428
+ <div class="feature-description">
429
+ Your uploaded images are processed securely and never stored permanently
430
+ </div>
431
+ </div>
432
+
433
+ <div class="feature-card">
434
+ <div class="feature-icon">
435
+ <i class="fas fa-chart-line"></i>
436
+ </div>
437
+ <div class="feature-title">Smart Matching</div>
438
+ <div class="feature-description">
439
+ Find properties with similar architectural styles, layouts, and visual characteristics
440
+ </div>
441
+ </div>
442
+ </div>
443
+ </div>
444
+
445
+ <script>
446
+ let isSystemReady = false;
447
+
448
+ // Check system status on page load
449
+ checkSystemStatus();
450
+
451
+ // Upload area functionality
452
+ const uploadArea = document.getElementById('uploadArea');
453
+ const fileInput = document.getElementById('fileInput');
454
+ const previewImage = document.getElementById('previewImage');
455
+ const uploadBtn = document.getElementById('uploadBtn');
456
+ const loadingSpinner = document.getElementById('loadingSpinner');
457
+ const resultsSection = document.getElementById('resultsSection');
458
+ const resultsGrid = document.getElementById('resultsGrid');
459
+ const errorMessage = document.getElementById('errorMessage');
460
+ const successMessage = document.getElementById('successMessage');
461
+
462
+ // Drag and drop functionality
463
+ uploadArea.addEventListener('dragover', (e) => {
464
+ e.preventDefault();
465
+ uploadArea.classList.add('dragover');
466
+ });
467
+
468
+ uploadArea.addEventListener('dragleave', () => {
469
+ uploadArea.classList.remove('dragover');
470
+ });
471
+
472
+ uploadArea.addEventListener('drop', (e) => {
473
+ e.preventDefault();
474
+ uploadArea.classList.remove('dragover');
475
+ const files = e.dataTransfer.files;
476
+ if (files.length > 0) {
477
+ handleFile(files[0]);
478
+ }
479
+ });
480
+
481
+ fileInput.addEventListener('change', (e) => {
482
+ if (e.target.files.length > 0) {
483
+ handleFile(e.target.files[0]);
484
+ }
485
+ });
486
+
487
+ function handleFile(file) {
488
+ if (!isSystemReady) {
489
+ showError('Visual search system is still initializing. Please wait a moment and try again.');
490
+ return;
491
+ }
492
+
493
+ if (!file.type.startsWith('image/')) {
494
+ showError('Please select a valid image file.');
495
+ return;
496
+ }
497
+
498
+ // Show preview
499
+ const reader = new FileReader();
500
+ reader.onload = (e) => {
501
+ previewImage.src = e.target.result;
502
+ previewImage.style.display = 'block';
503
+ };
504
+ reader.readAsDataURL(file);
505
+
506
+ // Upload and search
507
+ uploadAndSearch(file);
508
+ }
509
+
510
+ function uploadAndSearch(file) {
511
+ const formData = new FormData();
512
+ formData.append('file', file);
513
+
514
+ // Show loading
515
+ loadingSpinner.style.display = 'block';
516
+ resultsSection.style.display = 'none';
517
+ hideMessages();
518
+
519
+ fetch('/search', {
520
+ method: 'POST',
521
+ body: formData
522
+ })
523
+ .then(response => response.json())
524
+ .then(data => {
525
+ loadingSpinner.style.display = 'none';
526
+
527
+ if (data.error) {
528
+ showError(data.error);
529
+ } else if (data.message) {
530
+ showSuccess(data.message);
531
+ } else if (data.results) {
532
+ displayResults(data.results);
533
+ }
534
+ })
535
+ .catch(error => {
536
+ loadingSpinner.style.display = 'none';
537
+ showError('An error occurred while processing your image. Please try again.');
538
+ console.error('Error:', error);
539
+ });
540
+ }
541
+
542
+ function displayResults(results) {
543
+ resultsSection.style.display = 'block';
544
+ resultsGrid.innerHTML = '';
545
+
546
+ results.forEach((result, index) => {
547
+ const resultCard = document.createElement('div');
548
+ resultCard.className = 'result-card';
549
+
550
+ // Create image element with actual property image or fallback
551
+ let imageHtml = '';
552
+ if (result.image_path) {
553
+ imageHtml = `<img src="${result.image_path}" alt="Property ${index + 1}" style="width: 100%; height: 200px; object-fit: cover;" onerror="this.parentElement.innerHTML='<div class=\\'result-image\\'><i class=\\'fas fa-home\\' style=\\'font-size: 3rem; color: #ddd;\\'></i></div>'">`;
554
+ } else {
555
+ imageHtml = `<div class="result-image">
556
+ <i class="fas fa-home" style="font-size: 3rem; color: #ddd;"></i>
557
+ </div>`;
558
+ }
559
+
560
+ resultCard.innerHTML = `
561
+ ${imageHtml}
562
+ <div class="result-info">
563
+ <div class="result-title">Property ${index + 1}</div>
564
+ <div class="result-details">
565
+ <span class="similarity-score">${result.similarity_score}</span>
566
+ <span class="property-id">ID: ${result.property_id}</span>
567
+ </div>
568
+ <div style="font-size: 0.8rem; color: #999;">
569
+ Distance: ${result.distance}
570
+ </div>
571
+ </div>
572
+ `;
573
+ resultsGrid.appendChild(resultCard);
574
+ });
575
+
576
+ showSuccess(`Found ${results.length} similar properties!`);
577
+ }
578
+
579
+ function checkSystemStatus() {
580
+ fetch('/status')
581
+ .then(response => response.json())
582
+ .then(data => {
583
+ const statusDot = document.getElementById('statusDot');
584
+ const statusText = document.getElementById('statusText');
585
+
586
+ if (data.model_loaded && data.collection_ready) {
587
+ statusDot.className = 'status-dot ready';
588
+ statusText.textContent = `System ready! ${data.total_images} properties indexed`;
589
+ isSystemReady = true;
590
+ } else {
591
+ statusDot.className = 'status-dot loading';
592
+ statusText.textContent = 'Initializing system... Please wait';
593
+ isSystemReady = false;
594
+
595
+ // Check again in 5 seconds
596
+ setTimeout(checkSystemStatus, 5000);
597
+ }
598
+ })
599
+ .catch(error => {
600
+ console.error('Error checking status:', error);
601
+ setTimeout(checkSystemStatus, 5000);
602
+ });
603
+ }
604
+
605
+ function showError(message) {
606
+ errorMessage.textContent = message;
607
+ errorMessage.style.display = 'block';
608
+ successMessage.style.display = 'none';
609
+ }
610
+
611
+ function showSuccess(message) {
612
+ successMessage.textContent = message;
613
+ successMessage.style.display = 'block';
614
+ errorMessage.style.display = 'none';
615
+ }
616
+
617
+ function hideMessages() {
618
+ errorMessage.style.display = 'none';
619
+ successMessage.style.display = 'none';
620
+ }
621
+ </script>
622
+ </body>
623
+ </html>
templates/working.html ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Property Recommendation</title>
7
+ <style>
8
+ body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 0; }
9
+ .container { max-width: 400px; margin: 60px auto; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
10
+ h2 { text-align: center; }
11
+ label { display: block; margin-top: 15px; }
12
+ input, select { width: 100%; padding: 8px; margin-top: 5px; border-radius: 4px; border: 1px solid #ccc; }
13
+ button { width: 100%; padding: 10px; margin-top: 20px; background: #007bff; color: #fff; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; }
14
+ button:hover { background: #0056b3; }
15
+ .recommendations { margin-top: 30px; }
16
+ .property { background: #f9f9f9; padding: 15px; border-radius: 6px; margin-bottom: 15px; }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <div class="container">
21
+ <h2>Find Your Property</h2>
22
+ <form id="propertyForm">
23
+ <label for="propertyType">Property Type</label>
24
+ <select id="propertyType" required>
25
+ <option value="">Select type</option>
26
+ <option value="Villa">Villa</option>
27
+ <option value="Townhouse">Townhouse</option>
28
+ <option value="Apartment">Apartment</option>
29
+ <option value="Flat">Flat</option>
30
+ <option value="Office Space">Office Space</option>
31
+ </select>
32
+ <label for="price">Price</label>
33
+ <input type="number" id="price" required placeholder="Enter price">
34
+ <button type="submit">Next</button>
35
+ </form>
36
+ <div class="recommendations" id="recommendations"></div>
37
+ </div>
38
+ <script>
39
+ document.getElementById('propertyForm').addEventListener('submit', async function(e) {
40
+ e.preventDefault();
41
+ const propertyType = document.getElementById('propertyType').value;
42
+ const price = document.getElementById('price').value;
43
+ const resDiv = document.getElementById('recommendations');
44
+ resDiv.innerHTML = '<p>Loading recommendations...</p>';
45
+ try {
46
+ const response = await fetch('/recommend', {
47
+ method: 'POST',
48
+ headers: { 'Content-Type': 'application/json' },
49
+ body: JSON.stringify({ propertyType, price })
50
+ });
51
+ const data = await response.json();
52
+ if (data.recommendations && data.recommendations.length > 0) {
53
+ resDiv.innerHTML = data.recommendations.map(p => `
54
+ <div class="property">
55
+ <strong>Type:</strong> ${p.propertyType}<br>
56
+ <strong>Price:</strong> ${p.price}<br>
57
+ <strong>Description:</strong> ${p.description}<br>
58
+ </div>
59
+ `).join('');
60
+ } else {
61
+ resDiv.innerHTML = '<p>No recommendations found.</p>';
62
+ }
63
+ } catch (err) {
64
+ resDiv.innerHTML = '<p>Error fetching recommendations.</p>';
65
+ }
66
+ });
67
+ </script>
68
+ </body>
69
+ </html>