|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>AI Property Verifier</title> |
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet"> |
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
|
<style> |
|
:root { |
|
--primary: #4361ee; |
|
--secondary: #3f37c9; |
|
--success: #4cc9f0; |
|
--danger: #f72585; |
|
--warning: #f8961e; |
|
--info: #4895ef; |
|
--light: #f8f9fa; |
|
--dark: #212529; |
|
--gray: #6c757d; |
|
--border-radius: 12px; |
|
--box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
|
} |
|
.price-analysis-content { |
|
padding: 15px; |
|
} |
|
.price-overview { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-bottom: 20px; |
|
padding: 15px; |
|
background: #f8f9fa; |
|
border-radius: var(--border-radius); |
|
} |
|
.price-main { |
|
display: flex; |
|
gap: 20px; |
|
} |
|
.price-value, .price-assessment { |
|
display: flex; |
|
flex-direction: column; |
|
} |
|
.label { |
|
font-size: 0.9rem; |
|
color: var(--gray); |
|
} |
|
.value { |
|
font-size: 1.2rem; |
|
font-weight: 600; |
|
color: var(--dark); |
|
} |
|
.price-confidence { |
|
display: flex; |
|
align-items: center; |
|
gap: 10px; |
|
} |
|
.confidence-bar { |
|
width: 100px; |
|
height: 8px; |
|
background: #e9ecef; |
|
border-radius: 4px; |
|
overflow: hidden; |
|
} |
|
.confidence-fill { |
|
height: 100%; |
|
background: var(--primary); |
|
transition: width 0.3s ease; |
|
} |
|
.confidence-value { |
|
font-size: 0.9rem; |
|
color: var(--gray); |
|
} |
|
.price-details { |
|
display: grid; |
|
gap: 20px; |
|
} |
|
.price-ranges, .market-trends, .price-factors, .risk-indicators { |
|
background: white; |
|
padding: 15px; |
|
border-radius: var(--border-radius); |
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05); |
|
} |
|
.range-grid { |
|
display: grid; |
|
grid-template-columns: repeat(3, 1fr); |
|
gap: 15px; |
|
margin-top: 10px; |
|
} |
|
.range-item { |
|
padding: 10px; |
|
border-radius: var(--border-radius); |
|
text-align: center; |
|
} |
|
.range-item.budget { |
|
background: #e3f2fd; |
|
} |
|
.range-item.mid-range { |
|
background: #f3e5f5; |
|
} |
|
.range-item.premium { |
|
background: #fff3e0; |
|
} |
|
.range-label { |
|
display: block; |
|
font-size: 0.9rem; |
|
color: var(--gray); |
|
margin-bottom: 5px; |
|
} |
|
.range-value { |
|
font-weight: 600; |
|
color: var(--dark); |
|
} |
|
.trend-info { |
|
display: grid; |
|
grid-template-columns: repeat(2, 1fr); |
|
gap: 15px; |
|
margin-top: 10px; |
|
} |
|
.trend-item { |
|
display: flex; |
|
flex-direction: column; |
|
} |
|
.trend-label { |
|
font-size: 0.9rem; |
|
color: var(--gray); |
|
} |
|
.trend-value { |
|
font-weight: 600; |
|
color: var(--dark); |
|
} |
|
.factors-grid { |
|
display: grid; |
|
grid-template-columns: repeat(3, 1fr); |
|
gap: 15px; |
|
margin-top: 10px; |
|
} |
|
.factor-item { |
|
padding: 10px; |
|
background: #f8f9fa; |
|
border-radius: var(--border-radius); |
|
} |
|
.factor-label { |
|
display: block; |
|
font-size: 0.9rem; |
|
color: var(--gray); |
|
margin-bottom: 5px; |
|
} |
|
.factor-details { |
|
display: flex; |
|
flex-direction: column; |
|
gap: 5px; |
|
} |
|
.factor-value { |
|
font-weight: 600; |
|
color: var(--dark); |
|
} |
|
.factor-impact { |
|
font-size: 0.8rem; |
|
color: var(--gray); |
|
} |
|
|
|
|
|
.price-comparison-summary { |
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); |
|
padding: 20px; |
|
border-radius: var(--border-radius); |
|
margin: 20px 0; |
|
border-left: 4px solid var(--primary); |
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.price-comparison-grid { |
|
display: grid; |
|
grid-template-columns: 1fr 1fr; |
|
gap: 20px; |
|
margin-bottom: 15px; |
|
} |
|
|
|
.price-comparison-item { |
|
text-align: center; |
|
padding: 15px; |
|
background: white; |
|
border-radius: 8px; |
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.price-comparison-label { |
|
font-size: 0.9rem; |
|
color: var(--gray); |
|
margin-bottom: 8px; |
|
font-weight: 500; |
|
} |
|
|
|
.price-comparison-value { |
|
font-size: 1.5rem; |
|
font-weight: 700; |
|
margin-bottom: 5px; |
|
} |
|
|
|
.price-comparison-value.listing { |
|
color: var(--dark); |
|
} |
|
|
|
.price-comparison-value.market { |
|
color: var(--primary); |
|
} |
|
|
|
.price-assessment-summary { |
|
text-align: center; |
|
padding: 12px; |
|
background: white; |
|
border-radius: 8px; |
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.price-assessment-label { |
|
font-size: 0.9rem; |
|
color: var(--gray); |
|
margin-bottom: 5px; |
|
font-weight: 500; |
|
} |
|
|
|
.price-assessment-value { |
|
font-size: 1.2rem; |
|
font-weight: 600; |
|
} |
|
|
|
.price-assessment-value.above-market { |
|
color: #dc3545; |
|
} |
|
|
|
.price-assessment-value.below-market { |
|
color: #28a745; |
|
} |
|
|
|
.price-assessment-value.market-rate { |
|
color: #17a2b8; |
|
} |
|
|
|
|
|
.risk-indicators { |
|
background: white; |
|
padding: 15px; |
|
border-radius: var(--border-radius); |
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05); |
|
} |
|
|
|
.risk-list { |
|
list-style: none; |
|
padding: 0; |
|
margin: 0; |
|
} |
|
|
|
.risk-item { |
|
padding: 10px 15px; |
|
margin-bottom: 8px; |
|
border-radius: 6px; |
|
border-left: 4px solid; |
|
font-weight: 500; |
|
} |
|
|
|
.risk-item.warning { |
|
background: #fff3e0; |
|
color: #e65100; |
|
border-left-color: #ff9800; |
|
} |
|
|
|
.risk-item.success { |
|
background: #e8f5e9; |
|
color: #2e7d32; |
|
border-left-color: #4caf50; |
|
} |
|
|
|
|
|
@media (max-width: 768px) { |
|
.price-comparison-grid { |
|
grid-template-columns: 1fr; |
|
gap: 15px; |
|
} |
|
|
|
.price-overview { |
|
flex-direction: column; |
|
gap: 15px; |
|
align-items: stretch; |
|
} |
|
|
|
.price-main { |
|
justify-content: space-between; |
|
} |
|
|
|
.factors-grid { |
|
grid-template-columns: 1fr; |
|
} |
|
|
|
.trend-info { |
|
grid-template-columns: 1fr; |
|
} |
|
|
|
.range-grid { |
|
grid-template-columns: 1fr; |
|
} |
|
} |
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
|
|
body { |
|
font-family: 'Poppins', sans-serif; |
|
background-color: #f5f7fa; |
|
color: #333; |
|
line-height: 1.6; |
|
padding: 20px; |
|
} |
|
|
|
.container { |
|
max-width: 1200px; |
|
margin: 0 auto; |
|
} |
|
|
|
header { |
|
text-align: center; |
|
margin-bottom: 30px; |
|
} |
|
|
|
h1 { |
|
font-size: 2.5rem; |
|
color: var(--primary); |
|
margin-bottom: 10px; |
|
} |
|
|
|
.subtitle { |
|
font-size: 1.1rem; |
|
color: var(--gray); |
|
} |
|
|
|
.card { |
|
background: white; |
|
border-radius: var(--border-radius); |
|
box-shadow: var(--box-shadow); |
|
padding: 25px; |
|
margin-bottom: 25px; |
|
} |
|
|
|
.card-header { |
|
border-bottom: 1px solid #eee; |
|
padding-bottom: 15px; |
|
margin-bottom: 20px; |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
} |
|
|
|
.card-title { |
|
font-size: 1.5rem; |
|
color: var(--dark); |
|
font-weight: 600; |
|
} |
|
|
|
.form-grid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); |
|
gap: 20px; |
|
} |
|
|
|
.form-group { |
|
margin-bottom: 20px; |
|
} |
|
|
|
.form-label { |
|
display: block; |
|
margin-bottom: 8px; |
|
font-weight: 500; |
|
color: var(--dark); |
|
} |
|
|
|
.form-control { |
|
width: 100%; |
|
padding: 12px 15px; |
|
border: 1px solid #ddd; |
|
border-radius: var(--border-radius); |
|
font-size: 1rem; |
|
transition: border-color 0.3s; |
|
} |
|
|
|
.form-control:focus { |
|
border-color: var(--primary); |
|
outline: none; |
|
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1); |
|
} |
|
|
|
textarea.form-control { |
|
min-height: 100px; |
|
resize: vertical; |
|
} |
|
|
|
.btn { |
|
display: inline-block; |
|
padding: 12px 24px; |
|
background-color: var(--primary); |
|
color: white; |
|
border: none; |
|
border-radius: var(--border-radius); |
|
font-size: 1rem; |
|
font-weight: 500; |
|
cursor: pointer; |
|
transition: all 0.3s; |
|
} |
|
|
|
.btn:hover { |
|
background-color: var(--secondary); |
|
transform: translateY(-2px); |
|
} |
|
|
|
.btn-block { |
|
display: block; |
|
width: 100%; |
|
} |
|
|
|
.section-title { |
|
font-size: 1.2rem; |
|
color: var(--primary); |
|
margin-bottom: 15px; |
|
font-weight: 600; |
|
} |
|
|
|
.results-container { |
|
display: none; |
|
margin-top: 30px; |
|
} |
|
|
|
.results-grid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fill, minmax(450px, 1fr)); |
|
gap: 25px; |
|
} |
|
|
|
|
|
.result-card { |
|
background: white; |
|
border-radius: var(--border-radius); |
|
box-shadow: var(--box-shadow); |
|
padding: 20px; |
|
height: 100%; |
|
} |
|
|
|
.result-header { |
|
display: flex; |
|
align-items: center; |
|
margin-bottom: 15px; |
|
} |
|
|
|
.result-icon { |
|
width: 40px; |
|
height: 40px; |
|
background-color: var(--light); |
|
border-radius: 50%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
margin-right: 15px; |
|
} |
|
|
|
.result-title { |
|
font-size: 1.2rem; |
|
font-weight: 600; |
|
color: var(--dark); |
|
} |
|
|
|
.trust-score { |
|
text-align: center; |
|
padding: 20px; |
|
} |
|
|
|
.score-value { |
|
font-size: 3rem; |
|
font-weight: 700; |
|
color: var(--primary); |
|
} |
|
|
|
.score-label { |
|
font-size: 1rem; |
|
color: var(--gray); |
|
} |
|
|
|
.progress-container { |
|
margin: 15px 0; |
|
} |
|
|
|
.progress-bar { |
|
height: 10px; |
|
background-color: #eee; |
|
border-radius: 5px; |
|
overflow: hidden; |
|
} |
|
|
|
.progress-fill { |
|
height: 100%; |
|
background-color: var(--primary); |
|
border-radius: 5px; |
|
transition: width 0.5s ease-in-out; |
|
} |
|
|
|
.alert { |
|
padding: 15px; |
|
border-radius: var(--border-radius); |
|
margin-bottom: 20px; |
|
font-weight: 500; |
|
} |
|
|
|
.alert-danger { |
|
background-color: rgba(247, 37, 133, 0.1); |
|
color: var(--danger); |
|
border-left: 4px solid var(--danger); |
|
} |
|
|
|
.alert-warning { |
|
background-color: rgba(248, 150, 30, 0.1); |
|
color: var(--warning); |
|
border-left: 4px solid var(--warning); |
|
} |
|
|
|
.alert-success { |
|
background-color: rgba(76, 201, 240, 0.1); |
|
color: var(--success); |
|
border-left: 4px solid var(--success); |
|
} |
|
|
|
.suggestion-list { |
|
list-style-type: none; |
|
padding: 0; |
|
} |
|
|
|
.suggestion-item { |
|
padding: 10px 15px; |
|
background-color: rgba(67, 97, 238, 0.05); |
|
border-radius: var(--border-radius); |
|
margin-bottom: 10px; |
|
border-left: 3px solid var(--primary); |
|
} |
|
|
|
.image-preview { |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 10px; |
|
margin-top: 10px; |
|
} |
|
|
|
.preview-item { |
|
width: 100px; |
|
height: 100px; |
|
border-radius: 8px; |
|
overflow: hidden; |
|
position: relative; |
|
} |
|
|
|
.preview-item img { |
|
width: 100%; |
|
height: 100%; |
|
object-fit: cover; |
|
} |
|
|
|
.preview-remove { |
|
position: absolute; |
|
top: 5px; |
|
right: 5px; |
|
background: rgba(0, 0, 0, 0.5); |
|
color: white; |
|
border: none; |
|
border-radius: 50%; |
|
width: 20px; |
|
height: 20px; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
cursor: pointer; |
|
} |
|
|
|
.loading { |
|
display: none; |
|
text-align: center; |
|
padding: 30px; |
|
} |
|
|
|
.spinner { |
|
width: 50px; |
|
height: 50px; |
|
border: 5px solid rgba(67, 97, 238, 0.1); |
|
border-radius: 50%; |
|
border-top-color: var(--primary); |
|
animation: spin 1s ease-in-out infinite; |
|
margin: 0 auto 20px; |
|
} |
|
|
|
@keyframes spin { |
|
to { transform: rotate(360deg); } |
|
} |
|
|
|
.chart-container { |
|
position: relative; |
|
height: 200px; |
|
margin-bottom: 20px; |
|
} |
|
|
|
.pdf-preview { |
|
background-color: #f8f9fa; |
|
padding: 15px; |
|
border-radius: var(--border-radius); |
|
margin-top: 10px; |
|
max-height: 200px; |
|
overflow-y: auto; |
|
} |
|
|
|
.pdf-filename { |
|
font-weight: 500; |
|
margin-bottom: 5px; |
|
} |
|
|
|
.image-gallery { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); |
|
gap: 15px; |
|
margin-top: 20px; |
|
} |
|
|
|
.gallery-item { |
|
border-radius: var(--border-radius); |
|
overflow: hidden; |
|
box-shadow: var(--box-shadow); |
|
aspect-ratio: 1; |
|
} |
|
|
|
.gallery-item img { |
|
width: 100%; |
|
height: 100%; |
|
object-fit: cover; |
|
} |
|
|
|
.badge { |
|
display: inline-block; |
|
padding: 5px 10px; |
|
border-radius: 20px; |
|
font-size: 0.8rem; |
|
font-weight: 500; |
|
margin-right: 5px; |
|
margin-bottom: 5px; |
|
} |
|
|
|
.badge-primary { background-color: rgba(67, 97, 238, 0.1); color: var(--primary); } |
|
.badge-success { background-color: rgba(76, 201, 240, 0.1); color: var(--success); } |
|
.badge-warning { background-color: rgba(248, 150, 30, 0.1); color: var(--warning); } |
|
.badge-danger { background-color: rgba(247, 37, 133, 0.1); color: var(--danger); } |
|
|
|
.explanation-box { |
|
background-color: #f8f9fa; |
|
border-radius: var(--border-radius); |
|
padding: 15px; |
|
margin-top: 15px; |
|
border-left: 4px solid var(--info); |
|
} |
|
|
|
.explanation-title { |
|
font-weight: 600; |
|
color: var(--info); |
|
margin-bottom: 10px; |
|
} |
|
|
|
@media (max-width: 768px) { |
|
.form-grid, .results-grid { |
|
grid-template-columns: 1fr; |
|
} |
|
|
|
.card { |
|
padding: 15px; |
|
} |
|
} |
|
|
|
.property-summary { |
|
padding: 15px; |
|
} |
|
|
|
.property-details p { |
|
margin-bottom: 8px; |
|
} |
|
|
|
.final-verdict { |
|
padding: 15px; |
|
} |
|
|
|
.verdict-box { |
|
display: flex; |
|
align-items: center; |
|
padding: 15px; |
|
border-radius: var(--border-radius); |
|
margin-bottom: 15px; |
|
background-color: #f8f9fa; |
|
} |
|
|
|
.verdict-icon { |
|
font-size: 2rem; |
|
margin-right: 15px; |
|
} |
|
|
|
.verdict-text { |
|
font-size: 1.2rem; |
|
font-weight: 600; |
|
} |
|
|
|
.verdict-legitimate { |
|
background-color: rgba(76, 201, 240, 0.1); |
|
border-left: 4px solid var(--success); |
|
} |
|
|
|
.verdict-suspicious { |
|
background-color: rgba(248, 150, 30, 0.1); |
|
border-left: 4px solid var(--warning); |
|
} |
|
|
|
.verdict-fraudulent { |
|
background-color: rgba(247, 37, 133, 0.1); |
|
border-left: 4px solid var(--danger); |
|
} |
|
|
|
.verification-scores { |
|
padding: 15px; |
|
} |
|
|
|
.score-item { |
|
margin-bottom: 15px; |
|
} |
|
|
|
.score-label { |
|
font-weight: 500; |
|
margin-bottom: 5px; |
|
} |
|
|
|
.score-bar-container { |
|
display: flex; |
|
align-items: center; |
|
} |
|
|
|
.score-bar { |
|
height: 10px; |
|
background-color: #e9ecef; |
|
border-radius: 5px; |
|
flex-grow: 1; |
|
margin-right: 10px; |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
.score-bar::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
height: 100%; |
|
background-color: var(--primary); |
|
border-radius: 5px; |
|
width: 0%; |
|
transition: width 0.5s ease; |
|
} |
|
|
|
.score-value { |
|
font-weight: 600; |
|
min-width: 40px; |
|
text-align: right; |
|
} |
|
|
|
.red-flags { |
|
padding: 15px; |
|
} |
|
|
|
.verdict-score { |
|
text-align: center; |
|
margin: 15px 0; |
|
padding: 10px; |
|
background-color: rgba(67, 97, 238, 0.05); |
|
border-radius: var(--border-radius); |
|
} |
|
|
|
.verdict-reasoning { |
|
margin: 15px 0; |
|
padding: 15px; |
|
background-color: rgba(67, 97, 238, 0.05); |
|
border-radius: var(--border-radius); |
|
border-left: 4px solid var(--primary); |
|
} |
|
|
|
.reasoning-title { |
|
font-weight: 600; |
|
color: var(--primary); |
|
margin-bottom: 10px; |
|
font-size: 1rem; |
|
} |
|
|
|
.reasoning-text { |
|
color: var(--dark); |
|
line-height: 1.5; |
|
font-size: 0.9rem; |
|
} |
|
|
|
.verdict-details { |
|
margin-top: 20px; |
|
} |
|
|
|
.verdict-section { |
|
margin-bottom: 15px; |
|
padding: 10px; |
|
border-radius: var(--border-radius); |
|
} |
|
|
|
.verdict-section h4 { |
|
font-size: 1rem; |
|
margin-bottom: 10px; |
|
color: var(--dark); |
|
} |
|
|
|
#criticalIssuesSection { |
|
background-color: rgba(247, 37, 133, 0.05); |
|
border-left: 4px solid var(--danger); |
|
} |
|
|
|
#warningsSection { |
|
background-color: rgba(248, 150, 30, 0.05); |
|
border-left: 4px solid var(--warning); |
|
} |
|
|
|
#recommendationsSection { |
|
background-color: rgba(76, 201, 240, 0.05); |
|
border-left: 4px solid var(--success); |
|
} |
|
|
|
|
|
.location-verification { |
|
padding: 15px; |
|
} |
|
|
|
.verification-section { |
|
margin-bottom: 20px; |
|
padding: 15px; |
|
background-color: #f8f9fa; |
|
border-radius: var(--border-radius); |
|
} |
|
|
|
.verification-section h5 { |
|
color: var(--primary); |
|
margin-bottom: 15px; |
|
font-size: 1.1rem; |
|
} |
|
|
|
.verification-item { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-bottom: 10px; |
|
padding: 8px; |
|
background-color: white; |
|
border-radius: 8px; |
|
} |
|
|
|
.verification-label { |
|
font-weight: 500; |
|
color: var(--dark); |
|
} |
|
|
|
.verification-value { |
|
padding: 4px 8px; |
|
border-radius: 4px; |
|
font-weight: 500; |
|
} |
|
|
|
.verification-value.valid { |
|
background-color: rgba(76, 201, 240, 0.1); |
|
color: var(--success); |
|
} |
|
|
|
.verification-value.invalid { |
|
background-color: rgba(247, 37, 133, 0.1); |
|
color: var(--danger); |
|
} |
|
|
|
.verification-summary { |
|
margin-top: 20px; |
|
padding: 15px; |
|
background-color: #f8f9fa; |
|
border-radius: var(--border-radius); |
|
} |
|
|
|
.summary-item { |
|
margin-bottom: 15px; |
|
} |
|
|
|
.summary-label { |
|
display: block; |
|
margin-bottom: 5px; |
|
font-weight: 500; |
|
color: var(--dark); |
|
} |
|
|
|
.summary-value { |
|
display: inline-block; |
|
padding: 4px 8px; |
|
border-radius: 4px; |
|
font-weight: 500; |
|
} |
|
|
|
.summary-value.valid { |
|
background-color: rgba(76, 201, 240, 0.1); |
|
color: var(--success); |
|
} |
|
|
|
.summary-value.invalid { |
|
background-color: rgba(247, 37, 133, 0.1); |
|
color: var(--danger); |
|
} |
|
|
|
.progress { |
|
height: 10px; |
|
background-color: #e9ecef; |
|
border-radius: 5px; |
|
overflow: hidden; |
|
} |
|
|
|
.progress-bar { |
|
height: 100%; |
|
transition: width 0.5s ease; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<header> |
|
<h1>AI Property Verifier & Fraud Detection</h1> |
|
<p class="subtitle">Powered by advanced AI models to verify property listings and detect potential fraud</p> |
|
</header> |
|
|
|
<div id="errorContainer" class="alert alert-danger" style="display:none;"></div> |
|
|
|
<div class="card"> |
|
<div class="card-header"> |
|
<h2 class="card-title">Property Details</h2> |
|
</div> |
|
|
|
<form id="propertyForm"> |
|
<div class="section-title">Basic Information</div> |
|
<div class="form-grid"> |
|
<div class="form-group"> |
|
<label class="form-label" for="propertyName">Property Name</label> |
|
<input type="text" class="form-control" id="propertyName" name="property_name" required> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="propertyType">Property Type</label> |
|
<select class="form-control" id="propertyType" name="property_type" required> |
|
<option value="">Select Type</option> |
|
<option value="Apartment">Apartment</option> |
|
<option value="House">House</option> |
|
<option value="Condo">Condo</option> |
|
<option value="Townhouse">Townhouse</option> |
|
<option value="Villa">Villa</option> |
|
<option value="Land">Land</option> |
|
<option value="Commercial">Commercial</option> |
|
<option value="Other">Other</option> |
|
</select> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="status">Status</label> |
|
<select class="form-control" id="status" name="status" required> |
|
<option value="">Select Status</option> |
|
<option value="For Sale">For Sale</option> |
|
<option value="For Rent">For Rent</option> |
|
<option value="Sold">Sold</option> |
|
<option value="Under Contract">Under Contract</option> |
|
<option value="Pending">Pending</option> |
|
</select> |
|
</div> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="description">Property Description</label> |
|
<textarea class="form-control" id="description" name="description" rows="4" required></textarea> |
|
</div> |
|
|
|
<div class="section-title">Location Details</div> |
|
<div class="form-grid"> |
|
<div class="form-group"> |
|
<label class="form-label" for="address">Address</label> |
|
<input type="text" class="form-control" id="address" name="address" required> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="city">City</label> |
|
<input type="text" class="form-control" id="city" name="city" required> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="state">State/Province</label> |
|
<input type="text" class="form-control" id="state" name="state" required> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="country">Country</label> |
|
<input type="text" class="form-control" id="country" name="country" required> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="zip">Zip/Postal Code</label> |
|
<input type="text" class="form-control" id="zip" name="zip" required> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="latitude">Latitude</label> |
|
<input type="text" class="form-control" id="latitude" name="latitude" placeholder="e.g. 40.7128"> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="longitude">Longitude</label> |
|
<input type="text" class="form-control" id="longitude" name="longitude" placeholder="e.g. -74.0060"> |
|
</div> |
|
</div> |
|
|
|
<div class="section-title">Property Specifications</div> |
|
<div class="form-grid"> |
|
<div class="form-group"> |
|
<label class="form-label" for="bedrooms">Bedrooms</label> |
|
<input type="number" class="form-control" id="bedrooms" name="bedrooms" min="0"> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="bathrooms">Bathrooms</label> |
|
<input type="number" class="form-control" id="bathrooms" name="bathrooms" min="0" step="0.5"> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="totalRooms">Total Rooms</label> |
|
<input type="number" class="form-control" id="totalRooms" name="total_rooms" min="0"> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="yearBuilt">Year Built</label> |
|
<input type="number" class="form-control" id="yearBuilt" name="year_built" min="1800" max="2100"> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="parking">Parking Spaces</label> |
|
<input type="number" class="form-control" id="parking" name="parking" min="0"> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="sqFt">Square Feet</label> |
|
<input type="text" class="form-control" id="sqFt" name="sq_ft" min="0"> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="marketValue">Market Value</label> |
|
<input type="text" class="form-control" id="marketValue" name="market_value" min="0"> |
|
</div> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="amenities">Amenities (comma separated)</label> |
|
<input type="text" class="form-control" id="amenities" name="amenities" placeholder="e.g. Pool, Gym, Garden, Garage"> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="nearbyLandmarks">Nearby Landmarks</label> |
|
<input type="text" class="form-control" id="nearbyLandmarks" name="nearby_landmarks" placeholder="e.g. School, Hospital, Park, Shopping Mall"> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="legalDetails">Legal & Infrastructure Details</label> |
|
<textarea class="form-control" id="legalDetails" name="legal_details" rows="3" placeholder="Include zoning, permits, utilities, etc."></textarea> |
|
</div> |
|
|
|
<div class="section-title">Documents & Images</div> |
|
<div class="form-group"> |
|
<label class="form-label" for="images">Upload Images (JPG/PNG)</label> |
|
<input type="file" class="form-control" id="images" name="images" accept="image/jpeg, image/png" multiple> |
|
<div class="image-preview" id="imagePreview"></div> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label class="form-label" for="documents">Upload Documents (PDF)</label> |
|
<input type="file" class="form-control" id="documents" name="documents" accept="application/pdf" multiple> |
|
<div id="pdfPreview"></div> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<button type="submit" class="btn btn-block" id="submitBtn">Verify Property with AI</button> |
|
</div> |
|
</form> |
|
</div> |
|
|
|
<div class="loading" id="loadingIndicator"> |
|
<div class="spinner"></div> |
|
<p>AI models are analyzing your property data...</p> |
|
<p class="subtitle">This may take a moment as we're processing multiple AI models</p> |
|
</div> |
|
|
|
<div class="results-container" id="resultsContainer"> |
|
<div class="card"> |
|
<div class="card-header"> |
|
<h2 class="card-title">AI Verification Results</h2> |
|
</div> |
|
|
|
<div class="results-grid"> |
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">π </div> |
|
<div class="result-title">Property Summary</div> |
|
</div> |
|
<div class="property-summary"> |
|
<h3 id="propertyTitle">Property Details</h3> |
|
<div class="property-details"> |
|
<p><strong>Name:</strong> <span id="summaryName"></span></p> |
|
<p><strong>Type:</strong> <span id="summaryType"></span></p> |
|
<p><strong>Status:</strong> <span id="summaryStatus"></span></p> |
|
<p><strong>Location:</strong> <span id="summaryLocation"></span></p> |
|
<p><strong>Price:</strong> <span id="summaryPrice"></span></p> |
|
<p><strong>Size:</strong> <span id="summarySize"></span></p> |
|
<p><strong>Bedrooms/Bathrooms:</strong> <span id="summaryRooms"></span></p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">β οΈ</div> |
|
<div class="result-title">Final Verdict</div> |
|
</div> |
|
<div class="final-verdict" id="finalVerdict"> |
|
<div class="verdict-box" id="verdictBox"> |
|
<div class="verdict-icon" id="verdictIcon">β³</div> |
|
<div class="verdict-text" id="verdictText">Analysis in progress...</div> |
|
</div> |
|
<div class="verdict-score"> |
|
<div class="score-label">Overall Score</div> |
|
<div class="score-value" id="verdictScoreValue">--</div> |
|
<div class="progress-container"> |
|
<div class="progress-bar"> |
|
<div class="progress-fill" id="verdictScoreBar" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="verdict-reasoning"> |
|
<div class="reasoning-title">AI Reasoning</div> |
|
<div class="reasoning-text" id="verdictReasoning">Analysis in progress...</div> |
|
</div> |
|
<div class="verdict-details"> |
|
<div class="verdict-section" id="criticalIssuesSection"> |
|
<h4>Critical Issues</h4> |
|
<ul id="criticalIssuesList" class="suggestion-list"> |
|
|
|
</ul> |
|
</div> |
|
<div class="verdict-section" id="warningsSection"> |
|
<h4>Warnings</h4> |
|
<ul id="warningsList" class="suggestion-list"> |
|
|
|
</ul> |
|
</div> |
|
<div class="verdict-section" id="recommendationsSection"> |
|
<h4>Recommendations</h4> |
|
<ul id="recommendationsList" class="suggestion-list"> |
|
|
|
</ul> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">π</div> |
|
<div class="result-title">Detailed Verification</div> |
|
</div> |
|
<div class="verification-scores"> |
|
<div class="score-item"> |
|
<div class="score-label">Trust Score</div> |
|
<div class="score-bar-container"> |
|
<div class="score-bar" id="trustBar"></div> |
|
<div class="score-value" id="trustValue">--</div> |
|
</div> |
|
</div> |
|
<div class="score-item"> |
|
<div class="score-label">Image Authenticity</div> |
|
<div class="score-bar-container"> |
|
<div class="score-bar" id="imageBar"></div> |
|
<div class="score-value" id="imageValue">--</div> |
|
</div> |
|
</div> |
|
<div class="score-item"> |
|
<div class="score-label">Document Verification</div> |
|
<div class="score-bar-container"> |
|
<div class="score-bar" id="documentBar"></div> |
|
<div class="score-value" id="documentValue">--</div> |
|
</div> |
|
</div> |
|
<div class="score-item"> |
|
<div class="score-label">Content Quality</div> |
|
<div class="score-bar-container"> |
|
<div class="score-bar" id="contentBar"></div> |
|
<div class="score-value" id="contentValue">--</div> |
|
</div> |
|
</div> |
|
<div class="score-item"> |
|
<div class="score-label">Location Accuracy</div> |
|
<div class="score-bar-container"> |
|
<div class="score-bar" id="locationBar"></div> |
|
<div class="score-value" id="locationValue">--</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">π©</div> |
|
<div class="result-title">Red Flags</div> |
|
</div> |
|
<div class="red-flags"> |
|
<ul id="redFlagsList" class="suggestion-list"> |
|
|
|
</ul> |
|
</div> |
|
</div> |
|
|
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">π</div> |
|
<div class="result-title">Trust Score</div> |
|
</div> |
|
<div class="trust-score"> |
|
<div class="score-value" id="trustScoreValue">--</div> |
|
<div class="score-label">Trust Score</div> |
|
<div class="progress-container"> |
|
<div class="progress-bar"> |
|
<div class="progress-fill" id="trustScoreBar" style="width: 0%"></div> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="chart-container"> |
|
<canvas id="trustScoreChart"></canvas> |
|
</div> |
|
<div class="explanation-box"> |
|
<div class="explanation-title">AI Reasoning</div> |
|
<div id="trustReasoning"></div> |
|
</div> |
|
</div> |
|
|
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">π</div> |
|
<div class="result-title">Fraud Analysis</div> |
|
</div> |
|
<div id="fraudAlertContainer"></div> |
|
<div class="chart-container"> |
|
<canvas id="fraudAnalysisChart"></canvas> |
|
</div> |
|
<div class="explanation-box"> |
|
<div class="explanation-title">AI Reasoning</div> |
|
<div id="fraudReasoning"></div> |
|
</div> |
|
</div> |
|
|
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">π</div> |
|
<div class="result-title">AI Summary</div> |
|
</div> |
|
<div id="aiSummary"></div> |
|
</div> |
|
|
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">π‘</div> |
|
<div class="result-title">Improvement Suggestions</div> |
|
</div> |
|
<div id="improvementSuggestions"></div> |
|
</div> |
|
|
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">π </div> |
|
<div class="result-title">Property Quality Assessment</div> |
|
</div> |
|
<div id="qualityAssessment"></div> |
|
<div class="chart-container"> |
|
<canvas id="qualityChart"></canvas> |
|
</div> |
|
</div> |
|
|
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">π</div> |
|
<div class="result-title">Location Analysis</div> |
|
</div> |
|
<div id="locationAnalysis"></div> |
|
<div class="chart-container"> |
|
<canvas id="locationChart"></canvas> |
|
</div> |
|
</div> |
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">π°</div> |
|
<div class="result-title">Price Analysis</div> |
|
</div> |
|
<div id="priceAnalysis"></div> |
|
<div class="chart-container"> |
|
<canvas id="priceChart"></canvas> |
|
</div> |
|
</div> |
|
<div class="price-analysis-content"> |
|
<div class="price-overview"> |
|
<div class="price-main"> |
|
<div class="price-value"> |
|
<span class="label">Price per sq.ft.</span> |
|
<span class="value" id="price-per-sqft">βΉ0</span> |
|
</div> |
|
<div class="price-assessment"> |
|
<span class="label">Assessment</span> |
|
<span class="value" id="price-assessment">Unknown</span> |
|
</div> |
|
</div> |
|
<div class="price-confidence"> |
|
<div class="confidence-bar"> |
|
<div class="confidence-fill" id="price-confidence-bar"></div> |
|
</div> |
|
<span class="confidence-value" id="price-confidence-value">0%</span> |
|
</div> |
|
</div> |
|
<div class="price-details"> |
|
<div class="price-ranges"> |
|
<h4>Price Ranges</h4> |
|
<div class="range-grid"> |
|
<div class="range-item budget"> |
|
<span class="range-label">Budget</span> |
|
<span class="range-value" id="budget-range">βΉ0 - βΉ0</span> |
|
</div> |
|
<div class="range-item mid-range"> |
|
<span class="range-label">Mid-Range</span> |
|
<span class="range-value" id="mid-range">βΉ0 - βΉ0</span> |
|
</div> |
|
<div class="range-item premium"> |
|
<span class="range-label">Premium</span> |
|
<span class="range-value" id="premium-range">βΉ0 - βΉ0</span> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="market-trends"> |
|
<h4>Market Trends</h4> |
|
<div class="trend-info"> |
|
<div class="trend-item"> |
|
<span class="trend-label">City Tier</span> |
|
<span class="trend-value" id="city-tier">Unknown</span> |
|
</div> |
|
<div class="trend-item"> |
|
<span class="trend-label">Price Trend</span> |
|
<span class="trend-value" id="price-trend">Unknown</span> |
|
</div> |
|
<div class="trend-item"> |
|
<span class="trend-label">Market Average</span> |
|
<span class="trend-value" id="market-average">βΉ0</span> |
|
</div> |
|
<div class="trend-item"> |
|
<span class="trend-label">Deviation</span> |
|
<span class="trend-value" id="price-deviation">0%</span> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="price-factors"> |
|
<h4>Price Factors</h4> |
|
<div class="factors-grid"> |
|
<div class="factor-item"> |
|
<span class="factor-label">Property Age</span> |
|
<div class="factor-details" id="age-factor"> |
|
<span class="factor-value">Unknown</span> |
|
<span class="factor-impact">Impact: Unknown</span> |
|
</div> |
|
</div> |
|
<div class="factor-item"> |
|
<span class="factor-label">Size Efficiency</span> |
|
<div class="factor-details" id="size-factor"> |
|
<span class="factor-value">Unknown</span> |
|
<span class="factor-impact">Impact: Unknown</span> |
|
</div> |
|
</div> |
|
<div class="factor-item"> |
|
<span class="factor-label">Amenities</span> |
|
<div class="factor-details" id="amenities-factor"> |
|
<span class="factor-value">Unknown</span> |
|
<span class="factor-impact">Impact: Unknown</span> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="risk-indicators"> |
|
<h4>Risk Indicators</h4> |
|
<ul class="risk-list" id="risk-indicators"> |
|
|
|
</ul> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">βοΈ</div> |
|
<div class="result-title">Legal Analysis</div> |
|
</div> |
|
<div id="legalAnalysis"></div> |
|
<div class="chart-container"> |
|
<canvas id="legalChart"></canvas> |
|
</div> |
|
</div> |
|
|
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">π</div> |
|
<div class="result-title">Cross-Validation Checks</div> |
|
</div> |
|
<div id="crossValidation"></div> |
|
</div> |
|
|
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">π</div> |
|
<div class="result-title">Document Analysis</div> |
|
</div> |
|
<div id="documentAnalysis"></div> |
|
<div class="chart-container"> |
|
<canvas id="documentChart"></canvas> |
|
</div> |
|
</div> |
|
|
|
<div class="result-card"> |
|
<div class="result-header"> |
|
<div class="result-icon">πΌοΈ</div> |
|
<div class="result-title">Image Analysis</div> |
|
</div> |
|
<div id="imageAnalysis"></div> |
|
<div class="image-gallery" id="imageGallery"></div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
let uploadedImages = []; |
|
let uploadedPDFs = []; |
|
|
|
|
|
let trustScoreChart; |
|
let fraudAnalysisChart; |
|
let qualityChart; |
|
let locationChart; |
|
let priceChart; |
|
let legalChart; |
|
let documentChart; |
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
requestLocationAccess(); |
|
|
|
const propertyForm = document.getElementById('propertyForm'); |
|
const loadingIndicator = document.getElementById('loadingIndicator'); |
|
const resultsContainer = document.getElementById('resultsContainer'); |
|
const imageInput = document.getElementById('images'); |
|
const imagePreview = document.getElementById('imagePreview'); |
|
const pdfInput = document.getElementById('documents'); |
|
const pdfPreview = document.getElementById('pdfPreview'); |
|
|
|
|
|
imageInput.addEventListener('change', function(e) { |
|
handleImageUpload(e.target.files); |
|
}); |
|
|
|
|
|
pdfInput.addEventListener('change', function(e) { |
|
handlePDFUpload(e.target.files); |
|
}); |
|
|
|
|
|
propertyForm.addEventListener('submit', function(e) { |
|
e.preventDefault(); |
|
submitForm(); |
|
}); |
|
|
|
|
|
initCharts(); |
|
}); |
|
|
|
function requestLocationAccess() { |
|
if (navigator.geolocation) { |
|
navigator.geolocation.getCurrentPosition( |
|
function(position) { |
|
const latitude = position.coords.latitude; |
|
const longitude = position.coords.longitude; |
|
|
|
|
|
document.getElementById('latitude').value = latitude; |
|
document.getElementById('longitude').value = longitude; |
|
|
|
|
|
fetch('/get-location', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
body: JSON.stringify({ |
|
latitude: latitude, |
|
longitude: longitude |
|
}), |
|
}) |
|
.then(response => response.json()) |
|
.then(data => { |
|
if (data.status === 'success') { |
|
|
|
document.getElementById('address').value = data.address || ''; |
|
document.getElementById('city').value = data.city || ''; |
|
document.getElementById('state').value = data.state || ''; |
|
document.getElementById('country').value = data.country || ''; |
|
document.getElementById('zip').value = data.postal_code || ''; |
|
|
|
console.log('Location data loaded successfully'); |
|
} else { |
|
console.error('Error getting location details:', data.message); |
|
} |
|
}) |
|
.catch(error => { |
|
console.error('Error getting location details:', error); |
|
}); |
|
}, |
|
function(error) { |
|
console.error('Error getting location:', error.message); |
|
|
|
const locationMessage = document.createElement('div'); |
|
locationMessage.className = 'alert alert-warning'; |
|
locationMessage.innerHTML = 'Location access denied or unavailable. Please enter your location manually.'; |
|
document.querySelector('.container').prepend(locationMessage); |
|
|
|
|
|
setTimeout(() => { |
|
locationMessage.remove(); |
|
}, 5000); |
|
}, |
|
{ |
|
enableHighAccuracy: true, |
|
timeout: 5000, |
|
maximumAge: 0 |
|
} |
|
); |
|
} else { |
|
console.error('Geolocation is not supported by this browser'); |
|
} |
|
} |
|
|
|
function handleImageUpload(files) { |
|
const imagePreview = document.getElementById('imagePreview'); |
|
imagePreview.innerHTML = ''; |
|
uploadedImages = []; |
|
|
|
for (let i = 0; i < files.length; i++) { |
|
const file = files[i]; |
|
if (!file.type.match('image.*')) continue; |
|
|
|
const reader = new FileReader(); |
|
reader.onload = function(e) { |
|
const imageData = e.target.result; |
|
uploadedImages.push({ |
|
name: file.name, |
|
data: imageData, |
|
file: file |
|
}); |
|
|
|
|
|
const previewItem = document.createElement('div'); |
|
previewItem.className = 'preview-item'; |
|
previewItem.innerHTML = ` |
|
<img src="${imageData}" alt="${file.name}"> |
|
<button type="button" class="preview-remove" data-index="${uploadedImages.length - 1}">Γ</button> |
|
`; |
|
imagePreview.appendChild(previewItem); |
|
|
|
|
|
previewItem.querySelector('.preview-remove').addEventListener('click', function() { |
|
const index = parseInt(this.getAttribute('data-index')); |
|
uploadedImages.splice(index, 1); |
|
imagePreview.removeChild(previewItem); |
|
updateImagePreviews(); |
|
}); |
|
}; |
|
reader.readAsDataURL(file); |
|
} |
|
} |
|
|
|
function updateImagePreviews() { |
|
const imagePreview = document.getElementById('imagePreview'); |
|
imagePreview.innerHTML = ''; |
|
|
|
uploadedImages.forEach((image, index) => { |
|
const previewItem = document.createElement('div'); |
|
previewItem.className = 'preview-item'; |
|
previewItem.innerHTML = ` |
|
<img src="${image.data}" alt="${image.name}"> |
|
<button type="button" class="preview-remove" data-index="${index}">Γ</button> |
|
`; |
|
imagePreview.appendChild(previewItem); |
|
|
|
previewItem.querySelector('.preview-remove').addEventListener('click', function() { |
|
uploadedImages.splice(index, 1); |
|
updateImagePreviews(); |
|
}); |
|
}); |
|
} |
|
|
|
function handlePDFUpload(files) { |
|
const pdfPreview = document.getElementById('pdfPreview'); |
|
pdfPreview.innerHTML = ''; |
|
uploadedPDFs = []; |
|
|
|
for (let i = 0; i < files.length; i++) { |
|
const file = files[i]; |
|
if (file.type !== 'application/pdf') continue; |
|
|
|
uploadedPDFs.push({ |
|
name: file.name, |
|
file: file |
|
}); |
|
|
|
|
|
const previewItem = document.createElement('div'); |
|
previewItem.className = 'pdf-preview'; |
|
previewItem.innerHTML = ` |
|
<div class="pdf-filename">${file.name}</div> |
|
<button type="button" class="btn" data-index="${uploadedPDFs.length - 1}">Remove</button> |
|
`; |
|
pdfPreview.appendChild(previewItem); |
|
|
|
|
|
previewItem.querySelector('.btn').addEventListener('click', function() { |
|
const index = parseInt(this.getAttribute('data-index')); |
|
uploadedPDFs.splice(index, 1); |
|
pdfPreview.removeChild(previewItem); |
|
updatePDFPreviews(); |
|
}); |
|
} |
|
} |
|
|
|
function updatePDFPreviews() { |
|
const pdfPreview = document.getElementById('pdfPreview'); |
|
pdfPreview.innerHTML = ''; |
|
|
|
uploadedPDFs.forEach((pdf, index) => { |
|
const previewItem = document.createElement('div'); |
|
previewItem.className = 'pdf-preview'; |
|
previewItem.innerHTML = ` |
|
<div class="pdf-filename">${pdf.name}</div> |
|
<button type="button" class="btn" data-index="${index}">Remove</button> |
|
`; |
|
pdfPreview.appendChild(previewItem); |
|
|
|
previewItem.querySelector('.btn').addEventListener('click', function() { |
|
uploadedPDFs.splice(index, 1); |
|
updatePDFPreviews(); |
|
}); |
|
}); |
|
} |
|
|
|
function initCharts() { |
|
try { |
|
|
|
window.charts = []; |
|
|
|
|
|
const trustCtx = document.getElementById('trustScoreChart').getContext('2d'); |
|
trustScoreChart = new Chart(trustCtx, { |
|
type: 'doughnut', |
|
data: { |
|
datasets: [{ |
|
data: [0, 100], |
|
backgroundColor: [ |
|
'#4361ee', |
|
'#f1f1f1' |
|
], |
|
borderWidth: 0 |
|
}] |
|
}, |
|
options: { |
|
cutout: '70%', |
|
circumference: 180, |
|
rotation: -90, |
|
plugins: { |
|
legend: { |
|
display: false |
|
}, |
|
tooltip: { |
|
enabled: false |
|
} |
|
}, |
|
maintainAspectRatio: false |
|
} |
|
}); |
|
charts.push(trustScoreChart); |
|
|
|
|
|
const fraudAnalysisCtx = document.getElementById('fraudAnalysisChart').getContext('2d'); |
|
fraudAnalysisChart = new Chart(fraudAnalysisCtx, { |
|
type: 'bar', |
|
data: { |
|
labels: ['Legitimate', 'Suspicious', 'Fraudulent'], |
|
datasets: [{ |
|
label: 'Fraud Indicators', |
|
data: [0, 0, 0], |
|
backgroundColor: [ |
|
'#4cc9f0', |
|
'#f8961e', |
|
'#f72585' |
|
], |
|
borderWidth: 0 |
|
}] |
|
}, |
|
options: { |
|
indexAxis: 'y', |
|
plugins: { |
|
legend: { |
|
display: false |
|
} |
|
}, |
|
scales: { |
|
x: { |
|
beginAtZero: true, |
|
max: 100 |
|
} |
|
}, |
|
maintainAspectRatio: false |
|
} |
|
}); |
|
|
|
|
|
const qualityCtx = document.getElementById('qualityChart').getContext('2d'); |
|
qualityChart = new Chart(qualityCtx, { |
|
type: 'radar', |
|
data: { |
|
labels: ['Completeness', 'Accuracy', 'Clarity', 'Authenticity', 'Detail'], |
|
datasets: [{ |
|
label: 'Quality Score', |
|
data: [0, 0, 0, 0, 0], |
|
backgroundColor: 'rgba(67, 97, 238, 0.2)', |
|
borderColor: '#4361ee', |
|
borderWidth: 2, |
|
pointBackgroundColor: '#4361ee' |
|
}] |
|
}, |
|
options: { |
|
scales: { |
|
r: { |
|
beginAtZero: true, |
|
max: 100 |
|
} |
|
}, |
|
maintainAspectRatio: false |
|
} |
|
}); |
|
|
|
|
|
const locationCtx = document.getElementById('locationChart').getContext('2d'); |
|
locationChart = new Chart(locationCtx, { |
|
type: 'pie', |
|
data: { |
|
labels: ['Complete', 'Partial', 'Missing'], |
|
datasets: [{ |
|
data: [0, 0, 0], |
|
backgroundColor: [ |
|
'#4cc9f0', |
|
'#f8961e', |
|
'#f72585' |
|
], |
|
borderWidth: 0 |
|
}] |
|
}, |
|
options: { |
|
plugins: { |
|
legend: { |
|
position: 'bottom' |
|
} |
|
}, |
|
maintainAspectRatio: false |
|
} |
|
}); |
|
|
|
|
|
const priceCtx = document.getElementById('priceChart').getContext('2d'); |
|
priceChart = new Chart(priceCtx, { |
|
type: 'bar', |
|
data: { |
|
labels: ['Market Value', 'Price per Sq.Ft.'], |
|
datasets: [{ |
|
label: 'Price Analysis', |
|
data: [0, 0], |
|
backgroundColor: [ |
|
'#4361ee', |
|
'#4895ef' |
|
], |
|
borderWidth: 0 |
|
}] |
|
}, |
|
options: { |
|
scales: { |
|
y: { |
|
beginAtZero: true |
|
} |
|
}, |
|
maintainAspectRatio: false |
|
} |
|
}); |
|
|
|
|
|
const legalCtx = document.getElementById('legalChart').getContext('2d'); |
|
legalChart = new Chart(legalCtx, { |
|
type: 'doughnut', |
|
data: { |
|
labels: ['Complete', 'Partial', 'Missing'], |
|
datasets: [{ |
|
data: [0, 0, 0], |
|
backgroundColor: [ |
|
'#4cc9f0', |
|
'#f8961e', |
|
'#f72585' |
|
], |
|
borderWidth: 0 |
|
}] |
|
}, |
|
options: { |
|
plugins: { |
|
legend: { |
|
position: 'bottom' |
|
} |
|
}, |
|
maintainAspectRatio: false |
|
} |
|
}); |
|
|
|
|
|
const documentCtx = document.getElementById('documentChart').getContext('2d'); |
|
documentChart = new Chart(documentCtx, { |
|
type: 'polarArea', |
|
data: { |
|
labels: ['Authentic', 'Suspicious', 'Incomplete'], |
|
datasets: [{ |
|
data: [0, 0, 0], |
|
backgroundColor: [ |
|
'#4cc9f0', |
|
'#f8961e', |
|
'#f72585' |
|
], |
|
borderWidth: 0 |
|
}] |
|
}, |
|
options: { |
|
plugins: { |
|
legend: { |
|
position: 'bottom' |
|
} |
|
}, |
|
maintainAspectRatio: false |
|
} |
|
}); |
|
|
|
|
|
window.addEventListener('resize', debounce(() => { |
|
charts.forEach(chart => { |
|
if (chart && typeof chart.resize === 'function') { |
|
chart.resize(); |
|
} |
|
}); |
|
}, 250)); |
|
|
|
} catch (error) { |
|
console.error('Error initializing charts:', error); |
|
document.getElementById('chartErrors').innerHTML = |
|
'<div class="alert alert-danger">Error initializing charts. Please refresh the page.</div>'; |
|
} |
|
} |
|
|
|
|
|
function debounce(func, wait) { |
|
let timeout; |
|
return function executedFunction(...args) { |
|
const later = () => { |
|
clearTimeout(timeout); |
|
func(...args); |
|
}; |
|
clearTimeout(timeout); |
|
timeout = setTimeout(later, wait); |
|
}; |
|
} |
|
|
|
|
|
function validateAnalysisData(data) { |
|
return { |
|
trustScore: { |
|
score: data.trust_score?.score ?? 0, |
|
reasoning: data.trust_score?.reasoning ?? 'No reasoning provided' |
|
}, |
|
fraudClassification: { |
|
alertLevel: data.fraud_classification?.alert_level ?? 'low', |
|
classification: data.fraud_classification?.classification ?? 'Unknown', |
|
confidence: data.fraud_classification?.confidence ?? 0, |
|
indicators: data.fraud_classification?.fraud_indicators ?? [], |
|
scores: data.fraud_classification?.indicator_scores ?? [] |
|
}, |
|
qualityAssessment: { |
|
assessment: data.quality_assessment?.assessment ?? 'Unknown', |
|
score: data.quality_assessment?.score ?? 0, |
|
isAiGenerated: data.quality_assessment?.is_ai_generated ?? false, |
|
reasoning: data.quality_assessment?.reasoning ?? 'No reasoning provided' |
|
}, |
|
|
|
}; |
|
} |
|
|
|
|
|
function updateChart(chart, newData, options = {}) { |
|
try { |
|
if (chart && typeof chart.update === 'function') { |
|
chart.data = newData; |
|
chart.update(options); |
|
return true; |
|
} |
|
return false; |
|
} catch (error) { |
|
console.error('Error updating chart:', error); |
|
return false; |
|
} |
|
} |
|
|
|
function showError(message) { |
|
const errorContainer = document.getElementById('errorContainer'); |
|
errorContainer.textContent = message; |
|
errorContainer.style.display = 'block'; |
|
setTimeout(() => { errorContainer.style.display = 'none'; }, 8000); |
|
} |
|
|
|
|
|
function submitForm() { |
|
const propertyForm = document.getElementById('propertyForm'); |
|
const loadingIndicator = document.getElementById('loadingIndicator'); |
|
const resultsContainer = document.getElementById('resultsContainer'); |
|
|
|
console.log("π Starting form submission..."); |
|
|
|
loadingIndicator.style.display = 'block'; |
|
resultsContainer.style.display = 'none'; |
|
|
|
const formData = new FormData(propertyForm); |
|
|
|
|
|
console.log("π Form data being sent:"); |
|
for (let [key, value] of formData.entries()) { |
|
console.log(` ${key}: ${value}`); |
|
} |
|
|
|
console.log("π Making request to /verify endpoint..."); |
|
|
|
|
|
fetch('/verify', { |
|
method: 'POST', |
|
body: formData |
|
}) |
|
.then(response => { |
|
console.log("π‘ Response received:", response.status, response.statusText); |
|
return response.json(); |
|
}) |
|
.then(data => { |
|
console.log("β
Data received:", data); |
|
loadingIndicator.style.display = 'none'; |
|
if (data.status === 'error' || data.error) { |
|
console.error("β Server error:", data.error); |
|
showError(data.error || 'An error occurred. Please check your input and try again.'); |
|
return; |
|
} |
|
displayResults(data); |
|
resultsContainer.style.display = 'block'; |
|
}) |
|
.catch(error => { |
|
console.error("β Fetch error:", error); |
|
loadingIndicator.style.display = 'none'; |
|
showError('Server error: ' + (error.message || error)); |
|
}); |
|
} |
|
|
|
function displayResults(data) { |
|
console.log("Received data:", JSON.stringify(data)); |
|
|
|
|
|
if (data.price_analysis) { |
|
updatePriceAnalysis(data.price_analysis); |
|
} |
|
|
|
|
|
const validatedData = validateAnalysisData(data); |
|
|
|
try { |
|
|
|
const trustScore = validatedData.trustScore.score; |
|
document.getElementById('trustScoreValue').textContent = trustScore; |
|
document.getElementById('trustScoreBar').style.width = `${trustScore}%`; |
|
document.getElementById('trustReasoning').textContent = validatedData.trustScore.reasoning; |
|
|
|
|
|
updateChart(trustScoreChart, { |
|
datasets: [{ |
|
data: [trustScore, 100 - trustScore] |
|
}] |
|
}); |
|
|
|
|
|
const fraudLevel = validatedData.fraudClassification.alertLevel; |
|
const fraudContainer = document.getElementById('fraudAlertContainer'); |
|
fraudContainer.innerHTML = ''; |
|
|
|
const alertClass = fraudLevel === 'high' ? 'alert-danger' : |
|
fraudLevel === 'medium' ? 'alert-warning' : 'alert-success'; |
|
|
|
const alertDiv = document.createElement('div'); |
|
alertDiv.className = `alert ${alertClass}`; |
|
|
|
|
|
let fraudContent = `<h4>Fraud Classification</h4>`; |
|
fraudContent += `<p>Alert Level: ${fraudLevel.toUpperCase()}</p>`; |
|
|
|
if (data.fraud_classification.high_risk && data.fraud_classification.high_risk.length > 0) { |
|
fraudContent += `<div class="risk-section"> |
|
<h5>High Risk Indicators:</h5> |
|
<ul>`; |
|
data.fraud_classification.high_risk.forEach(([indicator, score]) => { |
|
fraudContent += `<li>${indicator} (${Math.round(score * 100)}% confidence)</li>`; |
|
}); |
|
fraudContent += `</ul></div>`; |
|
} |
|
|
|
if (data.fraud_classification.medium_risk && data.fraud_classification.medium_risk.length > 0) { |
|
fraudContent += `<div class="risk-section"> |
|
<h5>Medium Risk Indicators:</h5> |
|
<ul>`; |
|
data.fraud_classification.medium_risk.forEach(([indicator, score]) => { |
|
fraudContent += `<li>${indicator} (${Math.round(score * 100)}% confidence)</li>`; |
|
}); |
|
fraudContent += `</ul></div>`; |
|
} |
|
|
|
if (data.fraud_classification.low_risk && data.fraud_classification.low_risk.length > 0) { |
|
fraudContent += `<div class="risk-section"> |
|
<h5>Low Risk Indicators:</h5> |
|
<ul>`; |
|
data.fraud_classification.low_risk.forEach(([indicator, score]) => { |
|
fraudContent += `<li>${indicator} (${Math.round(score * 100)}% confidence)</li>`; |
|
}); |
|
fraudContent += `</ul></div>`; |
|
} |
|
|
|
alertDiv.innerHTML = fraudContent; |
|
fraudContainer.appendChild(alertDiv); |
|
|
|
|
|
const fraudChartData = { |
|
labels: ['High Risk', 'Medium Risk', 'Low Risk'], |
|
datasets: [{ |
|
data: [ |
|
data.fraud_classification.high_risk ? data.fraud_classification.high_risk.reduce((sum, [_, score]) => sum + score, 0) * 100 : 0, |
|
data.fraud_classification.medium_risk ? data.fraud_classification.medium_risk.reduce((sum, [_, score]) => sum + score, 0) * 100 : 0, |
|
data.fraud_classification.low_risk ? data.fraud_classification.low_risk.reduce((sum, [_, score]) => sum + score, 0) * 100 : 0 |
|
], |
|
backgroundColor: ['#dc3545', '#ffc107', '#28a745'] |
|
}] |
|
}; |
|
|
|
updateChart(fraudAnalysisChart, fraudChartData); |
|
|
|
document.getElementById('fraudReasoning').textContent = `This property was classified as ${validatedData.fraudClassification.classification} based on AI analysis of the listing details.`; |
|
|
|
|
|
document.getElementById('aiSummary').textContent = data.summary || "No summary available"; |
|
|
|
|
|
const improvementContainer = document.getElementById('improvementSuggestions'); |
|
improvementContainer.innerHTML = ''; |
|
|
|
|
|
const sections = { |
|
'Critical Issues': [], |
|
'Important Improvements': [], |
|
'Optional Enhancements': [] |
|
}; |
|
|
|
|
|
if (data && data.improvement_suggestions && Array.isArray(data.improvement_suggestions)) { |
|
|
|
data.improvement_suggestions.forEach(suggestion => { |
|
if (suggestion && typeof suggestion === 'string') { |
|
if (suggestion.toLowerCase().includes('critical') || |
|
suggestion.toLowerCase().includes('required') || |
|
suggestion.toLowerCase().includes('must')) { |
|
sections['Critical Issues'].push(suggestion); |
|
} else if (suggestion.toLowerCase().includes('should') || |
|
suggestion.toLowerCase().includes('recommended')) { |
|
sections['Important Improvements'].push(suggestion); |
|
} else { |
|
sections['Optional Enhancements'].push(suggestion); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
|
|
Object.entries(sections).forEach(([title, suggestions]) => { |
|
if (suggestions.length > 0) { |
|
const sectionDiv = document.createElement('div'); |
|
sectionDiv.className = 'improvement-section mb-4'; |
|
|
|
const sectionTitle = document.createElement('h5'); |
|
sectionTitle.className = title === 'Critical Issues' ? 'text-danger' : |
|
title === 'Important Improvements' ? 'text-warning' : 'text-info'; |
|
sectionTitle.textContent = title; |
|
|
|
const suggestionList = document.createElement('ul'); |
|
suggestionList.className = 'list-unstyled'; |
|
|
|
suggestions.forEach(suggestion => { |
|
const li = document.createElement('li'); |
|
li.className = 'mb-2'; |
|
li.innerHTML = `<i class="fas fa-arrow-right me-2"></i>${suggestion}`; |
|
suggestionList.appendChild(li); |
|
}); |
|
|
|
sectionDiv.appendChild(sectionTitle); |
|
sectionDiv.appendChild(suggestionList); |
|
improvementContainer.appendChild(sectionDiv); |
|
} |
|
}); |
|
|
|
|
|
if (!Object.values(sections).some(suggestions => suggestions.length > 0)) { |
|
improvementContainer.innerHTML = '<p class="text-muted">No improvement suggestions available at this time.</p>'; |
|
} |
|
|
|
|
|
const qualityDiv = document.getElementById('qualityAssessment'); |
|
qualityDiv.innerHTML = ''; |
|
|
|
if (data.quality_assessment) { |
|
const qualityScore = data.quality_assessment.score || 0; |
|
const assessment = data.quality_assessment.assessment || 'Not assessed'; |
|
const reasoning = data.quality_assessment.reasoning || 'No reasoning provided'; |
|
const isAiGenerated = data.quality_assessment.is_ai_generated || false; |
|
|
|
qualityDiv.innerHTML = ` |
|
<div class="quality-score mb-4"> |
|
<h5>Content Quality Score</h5> |
|
<div class="score-display"> |
|
<span class="score-value">${qualityScore}%</span> |
|
<div class="progress"> |
|
<div class="progress-bar ${getScoreColorClass(qualityScore)}" |
|
role="progressbar" |
|
style="width: ${qualityScore}%" |
|
aria-valuenow="${qualityScore}" |
|
aria-valuemin="0" |
|
aria-valuemax="100"> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="quality-details"> |
|
<div class="detail-item"> |
|
<strong>Assessment:</strong> ${assessment} |
|
</div> |
|
<div class="detail-item"> |
|
<strong>AI Generated:</strong> ${isAiGenerated ? 'Yes' : 'No'} |
|
</div> |
|
<div class="detail-item"> |
|
<strong>Analysis:</strong> |
|
<p class="reasoning-text">${reasoning}</p> |
|
</div> |
|
</div> |
|
`; |
|
|
|
|
|
const qualityChartData = { |
|
labels: ['Completeness', 'Accuracy', 'Clarity', 'Authenticity', 'Detail'], |
|
datasets: [{ |
|
label: 'Quality Metrics', |
|
data: [ |
|
data.quality_assessment.completeness_score || 0, |
|
data.quality_assessment.accuracy_score || 0, |
|
data.quality_assessment.clarity_score || 0, |
|
data.quality_assessment.authenticity_score || 0, |
|
data.quality_assessment.detail_score || 0 |
|
], |
|
backgroundColor: 'rgba(67, 97, 238, 0.2)', |
|
borderColor: '#4361ee', |
|
borderWidth: 2, |
|
pointBackgroundColor: '#4361ee' |
|
}] |
|
}; |
|
|
|
updateChart(qualityChart, qualityChartData); |
|
} else { |
|
qualityDiv.innerHTML = '<p class="text-muted">No quality assessment available at this time.</p>'; |
|
} |
|
|
|
|
|
function getScoreColorClass(score) { |
|
if (score >= 80) return 'bg-success'; |
|
if (score >= 60) return 'bg-info'; |
|
if (score >= 40) return 'bg-warning'; |
|
return 'bg-danger'; |
|
} |
|
|
|
|
|
const locationDiv = document.getElementById('locationAnalysis'); |
|
if (data.location_analysis) { |
|
locationDiv.innerHTML = ` |
|
<div class="location-verification"> |
|
<h4>Location Verification Results</h4> |
|
|
|
<div class="verification-section"> |
|
<h5>Address Verification</h5> |
|
<div class="verification-item"> |
|
<span class="verification-label">Address Format:</span> |
|
<span class="verification-value ${data.location_analysis.address_format_valid ? 'valid' : 'invalid'}"> |
|
${data.location_analysis.address_format_valid ? 'β Valid' : 'β Invalid'} |
|
</span> |
|
</div> |
|
<div class="verification-item"> |
|
<span class="verification-label">Address in City:</span> |
|
<span class="verification-value ${data.location_analysis.address_in_city ? 'valid' : 'invalid'}"> |
|
${data.location_analysis.address_in_city ? 'β Verified' : 'β Not Found'} |
|
</span> |
|
</div> |
|
</div> |
|
|
|
<div class="verification-section"> |
|
<h5>City & State Verification</h5> |
|
<div class="verification-item"> |
|
<span class="verification-label">City in State:</span> |
|
<span class="verification-value ${data.location_analysis.city_in_state ? 'valid' : 'invalid'}"> |
|
${data.location_analysis.city_in_state ? 'β Verified' : 'β Not Found'} |
|
</span> |
|
</div> |
|
<div class="verification-item"> |
|
<span class="verification-label">State in Country:</span> |
|
<span class="verification-value ${data.location_analysis.state_in_country ? 'valid' : 'invalid'}"> |
|
${data.location_analysis.state_in_country ? 'β Verified' : 'β Not Found'} |
|
</span> |
|
</div> |
|
</div> |
|
|
|
<div class="verification-section"> |
|
<h5>Postal Code Verification</h5> |
|
<div class="verification-item"> |
|
<span class="verification-label">Postal Code Format:</span> |
|
<span class="verification-value ${data.location_analysis.postal_code_valid ? 'valid' : 'invalid'}"> |
|
${data.location_analysis.postal_code_valid ? 'β Valid' : 'β Invalid'} |
|
</span> |
|
</div> |
|
<div class="verification-item"> |
|
<span class="verification-label">Postal Code in City:</span> |
|
<span class="verification-value ${data.location_analysis.postal_code_in_city ? 'valid' : 'invalid'}"> |
|
${data.location_analysis.postal_code_in_city ? 'β Verified' : 'β Not Found'} |
|
</span> |
|
</div> |
|
</div> |
|
|
|
<div class="verification-section"> |
|
<h5>Coordinates Verification</h5> |
|
<div class="verification-item"> |
|
<span class="verification-label">Coordinates Format:</span> |
|
<span class="verification-value ${data.location_analysis.coordinates_valid ? 'valid' : 'invalid'}"> |
|
${data.location_analysis.coordinates_valid ? 'β Valid' : 'β Invalid'} |
|
</span> |
|
</div> |
|
<div class="verification-item"> |
|
<span class="verification-label">Coordinates in City:</span> |
|
<span class="verification-value ${data.location_analysis.coordinates_in_city ? 'valid' : 'invalid'}"> |
|
${data.location_analysis.coordinates_in_city ? 'β Verified' : 'β Not Found'} |
|
</span> |
|
</div> |
|
</div> |
|
|
|
<div class="verification-section"> |
|
<h5>Location Quality</h5> |
|
<div class="verification-item"> |
|
<span class="verification-label">Overall Quality:</span> |
|
<span class="verification-value ${data.location_analysis.location_quality === 'verified' ? 'valid' : 'invalid'}"> |
|
${data.location_analysis.location_quality === 'verified' ? 'β High Quality' : 'β Low Quality'} |
|
</span> |
|
</div> |
|
<div class="verification-item"> |
|
<span class="verification-label">City Tier:</span> |
|
<span class="verification-value"> |
|
${data.location_analysis.city_tier.toUpperCase()} |
|
</span> |
|
</div> |
|
</div> |
|
|
|
<div class="verification-section"> |
|
<h5>Nearby Landmarks</h5> |
|
<div class="verification-item"> |
|
<span class="verification-label">Landmarks Provided:</span> |
|
<span class="verification-value ${data.location_analysis.landmarks_analysis.provided ? 'valid' : 'invalid'}"> |
|
${data.location_analysis.landmarks_analysis.provided ? 'β Yes' : 'β No'} |
|
</span> |
|
</div> |
|
${data.location_analysis.landmarks_analysis.types.length > 0 ? ` |
|
<div class="verification-item"> |
|
<span class="verification-label">Landmark Types:</span> |
|
<span class="verification-value"> |
|
${data.location_analysis.landmarks_analysis.types.join(', ')} |
|
</span> |
|
</div> |
|
` : ''} |
|
</div> |
|
|
|
<div class="verification-summary"> |
|
<h5>Verification Summary</h5> |
|
<div class="summary-item"> |
|
<span class="summary-label">Overall Score:</span> |
|
<div class="progress"> |
|
<div class="progress-bar ${getScoreColorClass(data.location_analysis.completeness_score)}" |
|
role="progressbar" |
|
style="width: ${data.location_analysis.completeness_score}%" |
|
aria-valuenow="${data.location_analysis.completeness_score}" |
|
aria-valuemin="0" |
|
aria-valuemax="100"> |
|
${data.location_analysis.completeness_score}% |
|
</div> |
|
</div> |
|
</div> |
|
<div class="summary-item"> |
|
<span class="summary-label">Verification Status:</span> |
|
<span class="summary-value ${data.location_analysis.verification_status === 'verified' ? 'valid' : 'invalid'}"> |
|
${data.location_analysis.verification_status === 'verified' ? 'β Verified' : 'β Unverified'} |
|
</span> |
|
</div> |
|
</div> |
|
</div> |
|
`; |
|
|
|
|
|
updateChart(locationChart, { |
|
datasets: [{ |
|
data: [ |
|
data.location_analysis.completeness_score || 0, |
|
100 - (data.location_analysis.completeness_score || 0), |
|
data.location_analysis.coordinates_check === 'coordinates_missing' ? 30 : 0 |
|
] |
|
}] |
|
}); |
|
} else { |
|
locationDiv.innerHTML = '<p>No location analysis available</p>'; |
|
} |
|
|
|
|
|
const priceDiv = document.getElementById('priceAnalysis'); |
|
if (data.price_analysis && data.price_analysis.has_price) { |
|
priceDiv.innerHTML = ` |
|
<p><strong>Assessment:</strong> ${data.price_analysis.assessment || "Unknown"}</p> |
|
<p><strong>Price:</strong> βΉ${(data.price_analysis.price || 0).toLocaleString()}</p> |
|
${data.price_analysis.has_sqft ? `<p><strong>Price per Sq.Ft.:</strong> βΉ${(data.price_analysis.price_per_sqft || 0).toLocaleString(undefined, {maximumFractionDigits: 2})}</p>` : ''} |
|
<p><strong>Confidence:</strong> ${Math.round((data.price_analysis.confidence || 0) * 100)}%</p> |
|
`; |
|
|
|
|
|
updateChart(priceChart, { |
|
labels: ['Market Value (thousands)', 'Price per Sq.Ft.'], |
|
datasets: [{ |
|
data: [ |
|
(data.price_analysis.price || 0) / 1000, |
|
data.price_analysis.price_per_sqft || 0 |
|
] |
|
}] |
|
}); |
|
} else { |
|
priceDiv.innerHTML = `<p>No price information provided for analysis.</p>`; |
|
} |
|
|
|
|
|
const legalDiv = document.getElementById('legalAnalysis'); |
|
if (data.legal_analysis) { |
|
legalDiv.innerHTML = ` |
|
<p><strong>Assessment:</strong> ${data.legal_analysis.assessment || "Unknown"}</p> |
|
<p><strong>Completeness:</strong> ${data.legal_analysis.completeness_score || 0}%</p> |
|
<p><strong>Summary:</strong> ${data.legal_analysis.summary || "No summary available"}</p> |
|
${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>` : ''} |
|
${data.legal_analysis.potential_issues ? '<p class="alert alert-warning">Potential legal issues detected</p>' : ''} |
|
`; |
|
|
|
|
|
updateChart(legalChart, { |
|
datasets: [{ |
|
data: [ |
|
data.legal_analysis.completeness_score || 0, |
|
100 - (data.legal_analysis.completeness_score || 0), |
|
data.legal_analysis.potential_issues ? 30 : 0 |
|
] |
|
}] |
|
}); |
|
} else { |
|
legalDiv.innerHTML = '<p>No legal analysis available</p>'; |
|
} |
|
|
|
|
|
const crossValidationDiv = document.getElementById('crossValidation'); |
|
crossValidationDiv.innerHTML = '<ul class="suggestion-list">'; |
|
|
|
try { |
|
|
|
if (data && data.cross_validation && Array.isArray(data.cross_validation)) { |
|
|
|
if (data.cross_validation.length > 0) { |
|
data.cross_validation.forEach(check => { |
|
if (check && typeof check === 'object') { |
|
const status = check.status || 'unknown'; |
|
const checkName = check.check || 'Check'; |
|
const message = check.message || 'No details available'; |
|
|
|
|
|
let statusClass = 'badge-warning'; |
|
if (['consistent', 'valid', 'reasonable', 'match', 'likely_valid'].includes(status)) { |
|
statusClass = 'badge-success'; |
|
} else if (['suspicious', 'inconsistent', 'invalid', 'no_match'].includes(status)) { |
|
statusClass = 'badge-danger'; |
|
} |
|
|
|
crossValidationDiv.innerHTML += ` |
|
<li class="suggestion-item"> |
|
<span class="badge ${statusClass}">${status}</span> |
|
<strong>${checkName}:</strong> ${message} |
|
</li> |
|
`; |
|
} |
|
}); |
|
} else { |
|
crossValidationDiv.innerHTML += '<li class="suggestion-item">No cross-validation checks performed</li>'; |
|
} |
|
} else { |
|
crossValidationDiv.innerHTML += '<li class="suggestion-item">No cross-validation data available</li>'; |
|
} |
|
} catch (error) { |
|
console.error("Error displaying cross-validation:", error); |
|
crossValidationDiv.innerHTML += '<li class="suggestion-item">Error displaying cross-validation results</li>'; |
|
} |
|
|
|
crossValidationDiv.innerHTML += '</ul>'; |
|
|
|
|
|
const documentDiv = document.getElementById('documentAnalysis'); |
|
documentDiv.innerHTML = ''; |
|
|
|
if (data.document_analysis && data.document_analysis.pdf_count > 0) { |
|
|
|
let totalVerificationScore = 0; |
|
let authenticCount = 0; |
|
let suspiciousCount = 0; |
|
let incompleteCount = 0; |
|
let totalConfidence = 0; |
|
let totalDocumentConfidence = 0; |
|
let totalAuthenticityConfidence = 0; |
|
let documentsWithSignatures = 0; |
|
let documentsWithDates = 0; |
|
let propertyRelatedCount = 0; |
|
|
|
data.document_analysis.pdf_analysis.forEach((pdf, index) => { |
|
if (pdf) { |
|
totalVerificationScore += pdf.verification_score || 0; |
|
totalConfidence += pdf.confidence || 0; |
|
totalDocumentConfidence += pdf.document_confidence || 0; |
|
totalAuthenticityConfidence += pdf.authenticity_confidence || 0; |
|
|
|
if (pdf.contains_signatures) documentsWithSignatures++; |
|
if (pdf.contains_dates) documentsWithDates++; |
|
if (pdf.is_property_related) propertyRelatedCount++; |
|
|
|
if (pdf.authenticity_assessment && pdf.authenticity_assessment.toLowerCase().includes('authentic')) { |
|
authenticCount++; |
|
} else if (pdf.authenticity_assessment && pdf.authenticity_assessment.toLowerCase().includes('suspicious')) { |
|
suspiciousCount++; |
|
} else { |
|
incompleteCount++; |
|
} |
|
} |
|
}); |
|
|
|
const avgVerificationScore = data.document_analysis.pdf_count > 0 ? (totalVerificationScore / data.document_analysis.pdf_count).toFixed(1) : 0; |
|
const avgConfidence = data.document_analysis.pdf_count > 0 ? (totalConfidence / data.document_analysis.pdf_count * 100).toFixed(1) : 0; |
|
const avgDocumentConfidence = data.document_analysis.pdf_count > 0 ? (totalDocumentConfidence / data.document_analysis.pdf_count * 100).toFixed(1) : 0; |
|
const avgAuthenticityConfidence = data.document_analysis.pdf_count > 0 ? (totalAuthenticityConfidence / data.document_analysis.pdf_count * 100).toFixed(1) : 0; |
|
|
|
|
|
documentDiv.innerHTML = ` |
|
<div class="document-summary"> |
|
<h4>Document Analysis Summary</h4> |
|
<div class="summary-grid"> |
|
<div class="summary-item"> |
|
<h5>Total Documents</h5> |
|
<p class="summary-value">${data.document_analysis.pdf_count}</p> |
|
</div> |
|
<div class="summary-item"> |
|
<h5>Property Related</h5> |
|
<p class="summary-value">${propertyRelatedCount} of ${data.document_analysis.pdf_count}</p> |
|
</div> |
|
<div class="summary-item"> |
|
<h5>Avg Verification Score</h5> |
|
<p class="summary-value">${avgVerificationScore}%</p> |
|
</div> |
|
<div class="summary-item"> |
|
<h5>Avg Confidence</h5> |
|
<p class="summary-value">${avgConfidence}%</p> |
|
</div> |
|
<div class="summary-item"> |
|
<h5>With Signatures</h5> |
|
<p class="summary-value">${documentsWithSignatures}</p> |
|
</div> |
|
<div class="summary-item"> |
|
<h5>With Dates</h5> |
|
<p class="summary-value">${documentsWithDates}</p> |
|
</div> |
|
</div> |
|
</div> |
|
`; |
|
|
|
|
|
const documentsContainer = document.createElement('div'); |
|
documentsContainer.className = 'documents-container'; |
|
documentsContainer.innerHTML = '<h4>Detailed Document Analysis</h4>'; |
|
|
|
data.document_analysis.pdf_analysis.forEach((pdf, index) => { |
|
if (pdf) { |
|
const documentCard = document.createElement('div'); |
|
documentCard.className = 'document-card'; |
|
|
|
|
|
let keyInfoHtml = ''; |
|
if (pdf.key_info && Object.keys(pdf.key_info).length > 0) { |
|
keyInfoHtml = '<div class="key-info-section"><h6>Key Information:</h6><ul>'; |
|
Object.entries(pdf.key_info).forEach(([key, value]) => { |
|
if (Array.isArray(value)) { |
|
keyInfoHtml += `<li><strong>${key}:</strong> ${value.slice(0, 3).join(', ')}${value.length > 3 ? '...' : ''}</li>`; |
|
} else { |
|
keyInfoHtml += `<li><strong>${key}:</strong> ${value}</li>`; |
|
} |
|
}); |
|
keyInfoHtml += '</ul></div>'; |
|
} |
|
|
|
|
|
let indicatorsHtml = ''; |
|
if (pdf.real_estate_indicators && pdf.real_estate_indicators.length > 0) { |
|
indicatorsHtml = '<div class="indicators-section"><h6>Real Estate Indicators:</h6><ul>'; |
|
pdf.real_estate_indicators.slice(0, 5).forEach(indicator => { |
|
indicatorsHtml += `<li>${indicator}</li>`; |
|
}); |
|
if (pdf.real_estate_indicators.length > 5) { |
|
indicatorsHtml += `<li>... and ${pdf.real_estate_indicators.length - 5} more</li>`; |
|
} |
|
indicatorsHtml += '</ul></div>'; |
|
} |
|
|
|
|
|
let legalTermsHtml = ''; |
|
if (pdf.legal_terms_found && pdf.legal_terms_found.length > 0) { |
|
legalTermsHtml = '<div class="legal-terms-section"><h6>Legal Terms Found:</h6><ul>'; |
|
pdf.legal_terms_found.slice(0, 5).forEach(term => { |
|
legalTermsHtml += `<li>${term}</li>`; |
|
}); |
|
if (pdf.legal_terms_found.length > 5) { |
|
legalTermsHtml += `<li>... and ${pdf.legal_terms_found.length - 5} more</li>`; |
|
} |
|
legalTermsHtml += '</ul></div>'; |
|
} |
|
|
|
|
|
let keywordAnalysisHtml = ''; |
|
if (pdf.keyword_analysis && Object.keys(pdf.keyword_analysis).length > 0) { |
|
keywordAnalysisHtml = '<div class="keyword-analysis-section"><h6>Keyword Analysis:</h6><ul>'; |
|
Object.entries(pdf.keyword_analysis).forEach(([category, count]) => { |
|
keywordAnalysisHtml += `<li><strong>${category}:</strong> ${count} matches</li>`; |
|
}); |
|
keywordAnalysisHtml += '</ul></div>'; |
|
} |
|
|
|
documentCard.innerHTML = ` |
|
<div class="document-header"> |
|
<h5>Document ${index + 1}</h5> |
|
<span class="status-badge ${pdf.is_property_related ? 'success' : 'warning'}"> |
|
${pdf.is_property_related ? 'Property Related' : 'Non-Property'} |
|
</span> |
|
</div> |
|
<div class="document-metrics"> |
|
<div class="metric"> |
|
<span class="metric-label">Document Type:</span> |
|
<span class="metric-value">${typeof pdf.document_type === 'object' ? (pdf.document_type.classification || 'Unknown') : (pdf.document_type || 'Unknown')}</span> |
|
</div> |
|
<div class="metric"> |
|
<span class="metric-label">Type Confidence:</span> |
|
<span class="metric-value">${Math.round((typeof pdf.document_type === 'object' ? (pdf.document_type.confidence || 0) : (pdf.document_confidence || 0)) * 100)}%</span> |
|
</div> |
|
<div class="metric"> |
|
<span class="metric-label">Authenticity:</span> |
|
<span class="metric-value">${typeof pdf.authenticity === 'object' ? (pdf.authenticity.assessment || 'Unknown') : (pdf.authenticity_assessment || 'Unknown')}</span> |
|
</div> |
|
<div class="metric"> |
|
<span class="metric-label">Auth Confidence:</span> |
|
<span class="metric-value">${Math.round((typeof pdf.authenticity === 'object' ? (pdf.authenticity.confidence || 0) : (pdf.authenticity_confidence || 0)) * 100)}%</span> |
|
</div> |
|
<div class="metric"> |
|
<span class="metric-label">Verification Score:</span> |
|
<span class="metric-value">${Math.round(pdf.verification_score || 0)}%</span> |
|
</div> |
|
<div class="metric"> |
|
<span class="metric-label">Overall Confidence:</span> |
|
<span class="metric-value">${Math.round((pdf.confidence || 0) * 100)}%</span> |
|
</div> |
|
</div> |
|
<div class="document-details"> |
|
<div class="detail-section"> |
|
<h6>Summary:</h6> |
|
<p>${pdf.summary || 'No summary available'}</p> |
|
</div> |
|
<div class="detail-section"> |
|
<h6>Document Features:</h6> |
|
<ul> |
|
<li><strong>Contains Signatures:</strong> ${pdf.contains_signatures ? 'Yes' : 'No'}</li> |
|
<li><strong>Contains Dates:</strong> ${pdf.contains_dates ? 'Yes' : 'No'}</li> |
|
</ul> |
|
</div> |
|
${keyInfoHtml} |
|
${indicatorsHtml} |
|
${legalTermsHtml} |
|
${keywordAnalysisHtml} |
|
</div> |
|
<div class="model-info"> |
|
<small>Model: ${pdf.model_used || 'Static Analysis'}</small> |
|
</div> |
|
`; |
|
|
|
documentsContainer.appendChild(documentCard); |
|
} |
|
}); |
|
|
|
documentDiv.appendChild(documentsContainer); |
|
|
|
|
|
updateChart(documentChart, { |
|
labels: ['Authentic', 'Suspicious', 'Incomplete'], |
|
datasets: [{ |
|
data: [authenticCount, suspiciousCount, incompleteCount], |
|
backgroundColor: ['#28a745', '#ffc107', '#dc3545'] |
|
}] |
|
}); |
|
|
|
|
|
const style = document.createElement('style'); |
|
style.textContent = ` |
|
.document-summary { |
|
margin-bottom: 30px; |
|
} |
|
.document-summary h4 { |
|
color: var(--primary); |
|
border-bottom: 2px solid var(--primary); |
|
padding-bottom: 5px; |
|
margin-bottom: 20px; |
|
} |
|
.documents-container { |
|
margin-top: 30px; |
|
} |
|
.documents-container h4 { |
|
color: var(--primary); |
|
margin-bottom: 20px; |
|
} |
|
.document-card { |
|
background: white; |
|
border-radius: var(--border-radius); |
|
box-shadow: var(--box-shadow); |
|
padding: 20px; |
|
margin-bottom: 20px; |
|
} |
|
.document-header { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-bottom: 15px; |
|
} |
|
.document-header h5 { |
|
margin: 0; |
|
color: var(--dark); |
|
} |
|
.document-metrics { |
|
display: grid; |
|
grid-template-columns: repeat(2, 1fr); |
|
gap: 10px; |
|
margin-bottom: 20px; |
|
} |
|
.document-details { |
|
margin-bottom: 15px; |
|
} |
|
.detail-section { |
|
margin-bottom: 15px; |
|
} |
|
.detail-section h6 { |
|
color: var(--dark); |
|
margin-bottom: 8px; |
|
} |
|
.detail-section p { |
|
color: var(--gray); |
|
margin: 0; |
|
line-height: 1.5; |
|
} |
|
.detail-section ul { |
|
list-style: none; |
|
padding: 0; |
|
margin: 0; |
|
} |
|
.detail-section li { |
|
padding: 4px 0; |
|
color: var(--gray); |
|
} |
|
.key-info-section, .indicators-section, .legal-terms-section, .keyword-analysis-section { |
|
margin-bottom: 15px; |
|
} |
|
.key-info-section h6, .indicators-section h6, .legal-terms-section h6, .keyword-analysis-section h6 { |
|
color: var(--dark); |
|
margin-bottom: 8px; |
|
} |
|
.key-info-section ul, .indicators-section ul, .legal-terms-section ul, .keyword-analysis-section ul { |
|
list-style: none; |
|
padding: 0; |
|
margin: 0; |
|
} |
|
.key-info-section li, .indicators-section li, .legal-terms-section li, .keyword-analysis-section li { |
|
padding: 4px 0; |
|
font-size: 0.85rem; |
|
color: var(--gray); |
|
} |
|
`; |
|
document.head.appendChild(style); |
|
} else { |
|
documentDiv.innerHTML = '<p>No documents were uploaded for analysis.</p>'; |
|
} |
|
|
|
|
|
const imageAnalysisDiv = document.getElementById('imageAnalysis'); |
|
const imageGallery = document.getElementById('imageGallery'); |
|
|
|
imageAnalysisDiv.innerHTML = ''; |
|
imageGallery.innerHTML = ''; |
|
|
|
if (data.image_analysis && data.images && data.images.length > 0) { |
|
|
|
const realEstateContainer = document.createElement('div'); |
|
realEstateContainer.className = 'image-section'; |
|
realEstateContainer.innerHTML = '<h4>Real Estate Images</h4>'; |
|
|
|
const nonRealEstateContainer = document.createElement('div'); |
|
nonRealEstateContainer.className = 'image-section'; |
|
nonRealEstateContainer.innerHTML = '<h4>Non-Real Estate Images</h4>'; |
|
|
|
let propertyRelatedCount = 0; |
|
let totalConfidence = 0; |
|
let totalRealEstateConfidence = 0; |
|
let totalAuthenticityScore = 0; |
|
let aiGeneratedCount = 0; |
|
|
|
data.image_analysis.image_analysis.forEach((img, index) => { |
|
if (img && img.is_property_related) { |
|
propertyRelatedCount++; |
|
} |
|
if (img) { |
|
totalConfidence += img.confidence || 0; |
|
totalRealEstateConfidence += img.real_estate_confidence || 0; |
|
totalAuthenticityScore += img.authenticity_score || 0; |
|
if (img.is_ai_generated) { |
|
aiGeneratedCount++; |
|
} |
|
} |
|
}); |
|
|
|
const avgConfidence = data.image_analysis.image_count > 0 ? (totalConfidence / data.image_analysis.image_count * 100).toFixed(1) : 0; |
|
const avgRealEstateConfidence = data.image_analysis.image_count > 0 ? (totalRealEstateConfidence / data.image_analysis.image_count * 100).toFixed(1) : 0; |
|
const avgAuthenticityScore = data.image_analysis.image_count > 0 ? (totalAuthenticityScore / data.image_analysis.image_count * 100).toFixed(1) : 0; |
|
|
|
imageAnalysisDiv.innerHTML = ` |
|
<div class="analysis-summary"> |
|
<div class="summary-grid"> |
|
<div class="summary-item"> |
|
<h5>Total Images Analyzed</h5> |
|
<p class="summary-value">${data.image_analysis.image_count}</p> |
|
</div> |
|
<div class="summary-item"> |
|
<h5>Property-Related Images</h5> |
|
<p class="summary-value">${propertyRelatedCount} of ${data.image_analysis.image_count}</p> |
|
</div> |
|
<div class="summary-item"> |
|
<h5>Average Confidence</h5> |
|
<p class="summary-value">${avgConfidence}%</p> |
|
</div> |
|
<div class="summary-item"> |
|
<h5>Real Estate Confidence</h5> |
|
<p class="summary-value">${avgRealEstateConfidence}%</p> |
|
</div> |
|
<div class="summary-item"> |
|
<h5>Authenticity Score</h5> |
|
<p class="summary-value">${avgAuthenticityScore}%</p> |
|
</div> |
|
<div class="summary-item"> |
|
<h5>AI Generated</h5> |
|
<p class="summary-value">${aiGeneratedCount} images</p> |
|
</div> |
|
</div> |
|
<div class="model-info"> |
|
<p><strong>Model Used:</strong> ${data.image_analysis.image_model_used ? data.image_analysis.image_model_used.join(', ') : 'Static Analysis'}</p> |
|
</div> |
|
</div> |
|
`; |
|
|
|
|
|
data.images.forEach((imgData, index) => { |
|
const imgAnalysis = data.image_analysis.image_analysis[index]; |
|
const galleryItem = document.createElement('div'); |
|
galleryItem.className = 'gallery-item'; |
|
|
|
|
|
const analysisCard = document.createElement('div'); |
|
analysisCard.className = 'analysis-card'; |
|
|
|
|
|
const imageContainer = document.createElement('div'); |
|
imageContainer.className = 'image-container'; |
|
imageContainer.innerHTML = ` |
|
<img src="data:image/jpeg;base64,${imgData}" alt="Property Image ${index + 1}"> |
|
<div class="image-overlay"> |
|
<div class="image-label">${imgAnalysis && imgAnalysis.predicted_label ? imgAnalysis.predicted_label : 'Unknown'}</div> |
|
<div class="confidence-badge">${imgAnalysis ? Math.round((imgAnalysis.confidence || 0) * 100) : 0}%</div> |
|
</div> |
|
`; |
|
|
|
|
|
const analysisDetails = document.createElement('div'); |
|
analysisDetails.className = 'analysis-details'; |
|
|
|
if (imgAnalysis) { |
|
const isPropertyRelated = imgAnalysis.is_property_related ? 'Yes' : 'No'; |
|
const isAiGenerated = imgAnalysis.is_ai_generated ? 'Yes' : 'No'; |
|
const authenticityScore = Math.round((imgAnalysis.authenticity_score || 0) * 100); |
|
const realEstateConfidence = Math.round((imgAnalysis.real_estate_confidence || 0) * 100); |
|
|
|
|
|
let topPredictionsHtml = ''; |
|
if (imgAnalysis.top_predictions && imgAnalysis.top_predictions.length > 0) { |
|
topPredictionsHtml = '<div class="top-predictions"><h6>Top Predictions:</h6><ul>'; |
|
imgAnalysis.top_predictions.slice(0, 3).forEach(pred => { |
|
const confidence = Math.round((pred.confidence || 0) * 100); |
|
topPredictionsHtml += `<li>${pred.label} (${confidence}%)</li>`; |
|
}); |
|
topPredictionsHtml += '</ul></div>'; |
|
} |
|
|
|
|
|
let qualityInfo = ''; |
|
if (imgAnalysis.image_quality) { |
|
qualityInfo = ` |
|
<div class="quality-info"> |
|
<h6>Image Quality:</h6> |
|
<p>Resolution: ${imgAnalysis.image_quality.resolution || 'Unknown'}</p> |
|
<p>Quality Score: ${Math.round((imgAnalysis.image_quality.quality_score || 0) * 100)}%</p> |
|
</div> |
|
`; |
|
} |
|
|
|
analysisDetails.innerHTML = ` |
|
<div class="analysis-header"> |
|
<h5>Image Analysis #${index + 1}</h5> |
|
<span class="status-badge ${imgAnalysis.is_property_related ? 'success' : 'warning'}"> |
|
${imgAnalysis.is_property_related ? 'Real Estate' : 'Non-Real Estate'} |
|
</span> |
|
</div> |
|
<div class="analysis-metrics"> |
|
<div class="metric"> |
|
<span class="metric-label">Confidence:</span> |
|
<span class="metric-value">${Math.round((imgAnalysis.confidence || 0) * 100)}%</span> |
|
</div> |
|
<div class="metric"> |
|
<span class="metric-label">Real Estate Confidence:</span> |
|
<span class="metric-value">${realEstateConfidence}%</span> |
|
</div> |
|
<div class="metric"> |
|
<span class="metric-label">Authenticity:</span> |
|
<span class="metric-value">${authenticityScore}%</span> |
|
</div> |
|
<div class="metric"> |
|
<span class="metric-label">AI Generated:</span> |
|
<span class="metric-value">${isAiGenerated}</span> |
|
</div> |
|
</div> |
|
${topPredictionsHtml} |
|
${qualityInfo} |
|
<div class="model-info"> |
|
<small>Model: ${imgAnalysis.model_used || 'Static Analysis'}</small> |
|
</div> |
|
`; |
|
} else { |
|
analysisDetails.innerHTML = '<p>Analysis not available</p>'; |
|
} |
|
|
|
analysisCard.appendChild(imageContainer); |
|
analysisCard.appendChild(analysisDetails); |
|
|
|
|
|
if (imgAnalysis && imgAnalysis.is_property_related) { |
|
realEstateContainer.appendChild(analysisCard); |
|
} else { |
|
nonRealEstateContainer.appendChild(analysisCard); |
|
} |
|
}); |
|
|
|
|
|
imageGallery.appendChild(realEstateContainer); |
|
imageGallery.appendChild(nonRealEstateContainer); |
|
|
|
|
|
const style = document.createElement('style'); |
|
style.textContent = ` |
|
.image-section { |
|
margin-bottom: 30px; |
|
} |
|
.image-section h4 { |
|
margin-bottom: 15px; |
|
color: var(--primary); |
|
border-bottom: 2px solid var(--primary); |
|
padding-bottom: 5px; |
|
} |
|
.summary-grid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
|
gap: 15px; |
|
margin-bottom: 20px; |
|
} |
|
.summary-item { |
|
background: #f8f9fa; |
|
padding: 15px; |
|
border-radius: var(--border-radius); |
|
text-align: center; |
|
} |
|
.summary-item h5 { |
|
font-size: 0.9rem; |
|
color: var(--gray); |
|
margin-bottom: 5px; |
|
} |
|
.summary-value { |
|
font-size: 1.2rem; |
|
font-weight: 600; |
|
color: var(--primary); |
|
margin: 0; |
|
} |
|
.analysis-card { |
|
background: white; |
|
border-radius: var(--border-radius); |
|
box-shadow: var(--box-shadow); |
|
overflow: hidden; |
|
margin-bottom: 20px; |
|
} |
|
.image-container { |
|
position: relative; |
|
width: 100%; |
|
height: 200px; |
|
overflow: hidden; |
|
} |
|
.image-container img { |
|
width: 100%; |
|
height: 100%; |
|
object-fit: cover; |
|
} |
|
.image-overlay { |
|
position: absolute; |
|
bottom: 0; |
|
left: 0; |
|
right: 0; |
|
background: rgba(0, 0, 0, 0.8); |
|
padding: 10px; |
|
color: white; |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
} |
|
.image-label { |
|
font-size: 0.9rem; |
|
font-weight: 500; |
|
} |
|
.confidence-badge { |
|
background: var(--primary); |
|
color: white; |
|
padding: 2px 8px; |
|
border-radius: 12px; |
|
font-size: 0.8rem; |
|
font-weight: 500; |
|
} |
|
.analysis-details { |
|
padding: 15px; |
|
} |
|
.analysis-header { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
margin-bottom: 15px; |
|
} |
|
.analysis-header h5 { |
|
margin: 0; |
|
color: var(--dark); |
|
} |
|
.status-badge { |
|
padding: 4px 12px; |
|
border-radius: 20px; |
|
font-size: 0.8rem; |
|
font-weight: 500; |
|
} |
|
.status-badge.success { |
|
background: #d4edda; |
|
color: #155724; |
|
} |
|
.status-badge.warning { |
|
background: #fff3cd; |
|
color: #856404; |
|
} |
|
.analysis-metrics { |
|
display: grid; |
|
grid-template-columns: repeat(2, 1fr); |
|
gap: 10px; |
|
margin-bottom: 15px; |
|
} |
|
.metric { |
|
display: flex; |
|
justify-content: space-between; |
|
padding: 8px; |
|
background: #f8f9fa; |
|
border-radius: 6px; |
|
} |
|
.metric-label { |
|
font-size: 0.9rem; |
|
color: var(--gray); |
|
} |
|
.metric-value { |
|
font-weight: 600; |
|
color: var(--dark); |
|
} |
|
.top-predictions { |
|
margin-bottom: 15px; |
|
} |
|
.top-predictions h6 { |
|
font-size: 0.9rem; |
|
color: var(--dark); |
|
margin-bottom: 8px; |
|
} |
|
.top-predictions ul { |
|
list-style: none; |
|
padding: 0; |
|
margin: 0; |
|
} |
|
.top-predictions li { |
|
padding: 4px 0; |
|
font-size: 0.85rem; |
|
color: var(--gray); |
|
} |
|
.quality-info { |
|
margin-bottom: 15px; |
|
} |
|
.quality-info h6 { |
|
font-size: 0.9rem; |
|
color: var(--dark); |
|
margin-bottom: 8px; |
|
} |
|
.quality-info p { |
|
font-size: 0.85rem; |
|
color: var(--gray); |
|
margin: 2px 0; |
|
} |
|
.model-info { |
|
text-align: right; |
|
} |
|
.model-info small { |
|
color: var(--gray); |
|
font-style: italic; |
|
} |
|
`; |
|
document.head.appendChild(style); |
|
} else { |
|
imageAnalysisDiv.innerHTML = '<p>No images were uploaded for analysis.</p>'; |
|
} |
|
|
|
|
|
document.getElementById('summaryName').textContent = document.getElementById('propertyName').value || 'Not provided'; |
|
document.getElementById('summaryType').textContent = document.getElementById('propertyType').value || 'Not provided'; |
|
document.getElementById('summaryStatus').textContent = document.getElementById('status').value || 'Not provided'; |
|
document.getElementById('summaryLocation').textContent = |
|
`${document.getElementById('address').value || ''}, ${document.getElementById('city').value || ''}, ${document.getElementById('state').value || ''}, India`; |
|
document.getElementById('summaryPrice').textContent = document.getElementById('marketValue').value ? `βΉ${document.getElementById('marketValue').value}` : 'Not provided'; |
|
document.getElementById('summarySize').textContent = document.getElementById('sqFt').value ? `${document.getElementById('sqFt').value} sq. ft.` : 'Not provided'; |
|
document.getElementById('summaryRooms').textContent = |
|
`${document.getElementById('bedrooms').value || '0'} BHK`; |
|
|
|
|
|
const verdictBox = document.getElementById('verdictBox'); |
|
const verdictIcon = document.getElementById('verdictIcon'); |
|
const verdictText = document.getElementById('verdictText'); |
|
const verdictScoreValue = document.getElementById('verdictScoreValue'); |
|
const verdictScoreBar = document.getElementById('verdictScoreBar'); |
|
|
|
|
|
const finalVerdict = data.final_verdict || {}; |
|
const verdictStatus = finalVerdict.verdict || 'unknown'; |
|
const verdictScore = finalVerdict.overall_score || 0; |
|
const verdictConfidence = finalVerdict.confidence || 'low'; |
|
|
|
|
|
if (verdictStatus.includes('HIGH RISK') || verdictStatus.includes('FRAUDULENT')) { |
|
verdictBox.className = 'verdict-box verdict-fraudulent'; |
|
verdictIcon.textContent = 'β'; |
|
verdictText.textContent = verdictStatus; |
|
} else if (verdictStatus.includes('SUSPICIOUS') || verdictStatus.includes('VERIFICATION REQUIRED')) { |
|
verdictBox.className = 'verdict-box verdict-suspicious'; |
|
verdictIcon.textContent = 'β οΈ'; |
|
verdictText.textContent = verdictStatus; |
|
} else if (verdictStatus.includes('VERIFIED') || verdictStatus.includes('LEGITIMATE')) { |
|
verdictBox.className = 'verdict-box verdict-legitimate'; |
|
verdictIcon.textContent = 'β
'; |
|
verdictText.textContent = verdictStatus; |
|
} else { |
|
verdictBox.className = 'verdict-box'; |
|
verdictIcon.textContent = 'β'; |
|
verdictText.textContent = verdictStatus || 'VERDICT UNKNOWN'; |
|
} |
|
|
|
|
|
verdictScoreValue.textContent = `${Math.round(verdictScore)}%`; |
|
verdictScoreBar.style.width = `${verdictScore}%`; |
|
verdictScoreBar.className = `progress-fill ${getScoreColorClass(verdictScore)}`; |
|
|
|
|
|
const verdictReasoning = document.getElementById('verdictReasoning'); |
|
if (verdictReasoning && finalVerdict.reasoning) { |
|
verdictReasoning.textContent = finalVerdict.reasoning; |
|
} |
|
|
|
|
|
const criticalIssuesList = document.getElementById('criticalIssuesList'); |
|
criticalIssuesList.innerHTML = ''; |
|
|
|
const criticalIssues = []; |
|
|
|
|
|
if (data.cross_validation) { |
|
data.cross_validation.forEach(check => { |
|
if (check.severity === 'high' || check.status === 'invalid' || check.status === 'suspicious') { |
|
criticalIssues.push(`${check.check}: ${check.message}`); |
|
} |
|
}); |
|
} |
|
|
|
if (criticalIssues.length > 0) { |
|
criticalIssues.forEach(issue => { |
|
const li = document.createElement('li'); |
|
li.className = 'suggestion-item'; |
|
li.innerHTML = `<i class="fas fa-exclamation-circle"></i> ${issue}`; |
|
criticalIssuesList.appendChild(li); |
|
}); |
|
document.getElementById('criticalIssuesSection').style.display = 'block'; |
|
} else { |
|
document.getElementById('criticalIssuesSection').style.display = 'none'; |
|
} |
|
|
|
|
|
const warningsList = document.getElementById('warningsList'); |
|
warningsList.innerHTML = ''; |
|
|
|
const warnings = []; |
|
|
|
|
|
if (data.cross_validation) { |
|
data.cross_validation.forEach(check => { |
|
if (check.severity === 'medium' || check.status === 'inconsistent') { |
|
warnings.push(`${check.check}: ${check.message}`); |
|
} |
|
}); |
|
} |
|
|
|
if (warnings.length > 0) { |
|
warnings.forEach(warning => { |
|
const li = document.createElement('li'); |
|
li.className = 'suggestion-item'; |
|
li.innerHTML = `<i class="fas fa-exclamation-triangle"></i> ${warning}`; |
|
warningsList.appendChild(li); |
|
}); |
|
document.getElementById('warningsSection').style.display = 'block'; |
|
} else { |
|
document.getElementById('warningsSection').style.display = 'none'; |
|
} |
|
|
|
|
|
const recommendationsList = document.getElementById('recommendationsList'); |
|
recommendationsList.innerHTML = ''; |
|
|
|
let hasRecommendations = false; |
|
|
|
|
|
if (data.suggestions && data.suggestions.suggestions && Array.isArray(data.suggestions.suggestions)) { |
|
data.suggestions.suggestions.forEach(suggestionObj => { |
|
if (suggestionObj && suggestionObj.suggestion) { |
|
const li = document.createElement('li'); |
|
li.className = 'suggestion-item'; |
|
li.innerHTML = `<i class="fas fa-check-circle"></i> ${suggestionObj.suggestion}`; |
|
recommendationsList.appendChild(li); |
|
hasRecommendations = true; |
|
} |
|
}); |
|
} else if (data.suggestions && data.suggestions.improvements && Array.isArray(data.suggestions.improvements)) { |
|
data.suggestions.improvements.forEach(recommendation => { |
|
if (recommendation && typeof recommendation === 'string') { |
|
const li = document.createElement('li'); |
|
li.className = 'suggestion-item'; |
|
li.innerHTML = `<i class="fas fa-check-circle"></i> ${recommendation}`; |
|
recommendationsList.appendChild(li); |
|
hasRecommendations = true; |
|
} |
|
}); |
|
} |
|
|
|
|
|
if (!hasRecommendations) { |
|
const basicRecommendations = [ |
|
"Provide a valid property name (not just numbers)", |
|
"Add more detailed property description", |
|
"Include property documents for verification", |
|
"Add high-quality property images", |
|
"Verify property specifications (bedrooms, bathrooms, size)", |
|
"Review and adjust property pricing to market rates", |
|
"Include legal documentation and compliance details" |
|
]; |
|
|
|
basicRecommendations.forEach(recommendation => { |
|
const li = document.createElement('li'); |
|
li.className = 'suggestion-item'; |
|
li.innerHTML = `<i class="fas fa-check-circle"></i> ${recommendation}`; |
|
recommendationsList.appendChild(li); |
|
hasRecommendations = true; |
|
}); |
|
} |
|
|
|
if (hasRecommendations) { |
|
document.getElementById('recommendationsSection').style.display = 'block'; |
|
} else { |
|
document.getElementById('recommendationsSection').style.display = 'none'; |
|
} |
|
|
|
|
|
updateScoreBar('trustBar', 'trustValue', trustScore); |
|
|
|
|
|
let imageScore = 0; |
|
if (data.image_analysis && data.image_analysis.image_analysis) { |
|
const propertyImages = data.image_analysis.image_analysis.filter(img => img && img.is_property_related); |
|
imageScore = data.image_analysis.image_count > 0 ? |
|
Math.round((propertyImages.length / data.image_analysis.image_count) * 100) : 0; |
|
} |
|
updateScoreBar('imageBar', 'imageValue', imageScore); |
|
|
|
|
|
let docScore = 0; |
|
if (data.document_analysis && data.document_analysis.pdf_analysis) { |
|
const verificationScores = data.document_analysis.pdf_analysis.map( |
|
pdf => pdf.verification_score || 0 |
|
); |
|
docScore = verificationScores.length > 0 ? |
|
Math.round(verificationScores.reduce((a, b) => a + b, 0) / verificationScores.length) : 0; |
|
} |
|
updateScoreBar('documentBar', 'documentValue', docScore); |
|
|
|
|
|
const contentScore = validatedData.qualityAssessment ? validatedData.qualityAssessment.score : 0; |
|
updateScoreBar('contentBar', 'contentValue', contentScore); |
|
|
|
|
|
const locationScore = data.location_analysis ? data.location_analysis.completeness_score || 0 : 0; |
|
updateScoreBar('locationBar', 'locationValue', locationScore); |
|
|
|
|
|
const redFlagsList = document.getElementById('redFlagsList'); |
|
redFlagsList.innerHTML = ''; |
|
|
|
const redFlags = []; |
|
|
|
|
|
if (data.cross_validation) { |
|
data.cross_validation.forEach(check => { |
|
if (check.status === 'inconsistent' || check.status === 'invalid' || |
|
check.status === 'suspicious' || check.status === 'no_match') { |
|
redFlags.push(`${check.check}: ${check.message}`); |
|
} |
|
}); |
|
} |
|
|
|
if (validatedData.qualityAssessment && validatedData.qualityAssessment.isAiGenerated) { |
|
redFlags.push('Description appears to be AI-generated, which may indicate a fake listing'); |
|
} |
|
|
|
if (data.price_analysis && |
|
(data.price_analysis.assessment === 'suspicious pricing' || |
|
data.price_analysis.assessment === 'overpriced' || |
|
data.price_analysis.assessment === 'underpriced')) { |
|
redFlags.push(`Price is ${data.price_analysis.assessment} for this type of property`); |
|
} |
|
|
|
if (data.legal_analysis && data.legal_analysis.potential_issues) { |
|
redFlags.push('Potential legal issues detected in the property documentation'); |
|
} |
|
|
|
if (data.image_analysis && data.image_analysis.image_count > 0) { |
|
const propertyImages = data.image_analysis.image_analysis.filter(img => img && img.is_property_related); |
|
if (propertyImages.length === 0) { |
|
redFlags.push('None of the uploaded images appear to be related to real estate'); |
|
} |
|
} |
|
|
|
|
|
if (redFlags.length === 0) { |
|
redFlags.push('No significant red flags detected in this listing'); |
|
} |
|
|
|
redFlags.forEach(flag => { |
|
const li = document.createElement('li'); |
|
li.className = 'suggestion-item'; |
|
li.textContent = flag; |
|
redFlagsList.appendChild(li); |
|
}); |
|
|
|
} catch (error) { |
|
console.error('Error displaying results:', error); |
|
document.getElementById('resultsContainer').innerHTML = |
|
'<div class="alert alert-danger">Error displaying results. Please try again.</div>'; |
|
} |
|
} |
|
|
|
function updateScoreBar(barId, valueId, score) { |
|
const bar = document.getElementById(barId); |
|
const value = document.getElementById(valueId); |
|
|
|
if (bar && value) { |
|
bar.style.setProperty('--score-width', `${score}%`); |
|
bar.style.background = `linear-gradient(to right, |
|
${getScoreColor(score)} ${score}%, |
|
#e9ecef ${score}%)`; |
|
value.textContent = `${score}%`; |
|
} |
|
} |
|
|
|
function getScoreColor(score) { |
|
if (score >= 70) return 'var(--success)'; |
|
if (score >= 40) return 'var(--warning)'; |
|
return 'var(--danger)'; |
|
} |
|
|
|
const priceCtx = document.getElementById('priceChart').getContext('2d'); |
|
priceChart = new Chart(priceCtx, { |
|
type: 'bar', |
|
data: { |
|
labels: ['Market Value', 'Price per Sq.Ft.'], |
|
datasets: [{ |
|
label: 'Price Analysis', |
|
data: [0, 0], |
|
backgroundColor: [ |
|
'#4361ee', |
|
'#4895ef' |
|
], |
|
borderWidth: 0 |
|
}] |
|
}, |
|
options: { |
|
scales: { |
|
y: { |
|
beginAtZero: true |
|
} |
|
}, |
|
maintainAspectRatio: false |
|
} |
|
}); |
|
|
|
|
|
function updatePriceAnalysis(data) { |
|
try { |
|
console.log('Price Analysis Data:', data); |
|
|
|
|
|
const listingPricePerSqft = data.formatted_price_per_sqft || 'βΉ0'; |
|
const marketAveragePerSqft = data.market_average ? `βΉ${data.market_average.toLocaleString(undefined, {maximumFractionDigits: 2})}` : 'βΉ0'; |
|
const deviation = data.deviation_percentage || 0; |
|
|
|
|
|
document.getElementById('price-per-sqft').innerHTML = ` |
|
<div style="margin-bottom: 8px;"> |
|
<span style="font-size: 0.9rem; color: #6c757d;">Listing Price:</span><br> |
|
<span style="font-size: 1.3rem; font-weight: 600; color: #212529;">${listingPricePerSqft}</span> |
|
</div> |
|
<div> |
|
<span style="font-size: 0.9rem; color: #6c757d;">Market Average:</span><br> |
|
<span style="font-size: 1.3rem; font-weight: 600; color: #4361ee;">${marketAveragePerSqft}</span> |
|
</div> |
|
`; |
|
|
|
|
|
let assessmentText = data.assessment || 'Unknown'; |
|
let assessmentColor = '#6c757d'; |
|
|
|
if (deviation > 20) { |
|
assessmentText = `${Math.abs(deviation).toFixed(0)}% above market average`; |
|
assessmentColor = '#dc3545'; |
|
} else if (deviation < -20) { |
|
assessmentText = `${Math.abs(deviation).toFixed(0)}% below market average`; |
|
assessmentColor = '#28a745'; |
|
} else if (Math.abs(deviation) <= 20) { |
|
assessmentText = 'Market rate'; |
|
assessmentColor = '#17a2b8'; |
|
} |
|
|
|
document.getElementById('price-assessment').textContent = assessmentText; |
|
document.getElementById('price-assessment').style.color = assessmentColor; |
|
|
|
|
|
const confidence = Math.round((data.confidence || 0) * 100); |
|
const confidenceBar = document.getElementById('price-confidence-bar'); |
|
const confidenceValue = document.getElementById('price-confidence-value'); |
|
confidenceValue.textContent = `${confidence}%`; |
|
confidenceBar.style.width = `${confidence}%`; |
|
confidenceBar.style.backgroundColor = getScoreColor(confidence); |
|
|
|
|
|
if (data.price_ranges) { |
|
const ranges = data.price_ranges; |
|
if (ranges.budget && ranges.budget.min !== undefined && ranges.budget.max !== undefined) { |
|
document.getElementById('budget-range').textContent = `βΉ${ranges.budget.min.toLocaleString()} - βΉ${ranges.budget.max.toLocaleString()}`; |
|
} else { |
|
document.getElementById('budget-range').textContent = 'βΉ0 - βΉ0'; |
|
} |
|
|
|
if (ranges.mid_range && ranges.mid_range.min !== undefined && ranges.mid_range.max !== undefined) { |
|
document.getElementById('mid-range').textContent = `βΉ${ranges.mid_range.min.toLocaleString()} - βΉ${ranges.mid_range.max.toLocaleString()}`; |
|
} else { |
|
document.getElementById('mid-range').textContent = 'βΉ0 - βΉ0'; |
|
} |
|
|
|
if (ranges.premium && ranges.premium.min !== undefined && ranges.premium.max !== undefined) { |
|
document.getElementById('premium-range').textContent = `βΉ${ranges.premium.min.toLocaleString()} - βΉ${ranges.premium.max.toLocaleString()}`; |
|
} else { |
|
document.getElementById('premium-range').textContent = 'βΉ0 - βΉ0'; |
|
} |
|
} else { |
|
document.getElementById('budget-range').textContent = 'βΉ0 - βΉ0'; |
|
document.getElementById('mid-range').textContent = 'βΉ0 - βΉ0'; |
|
document.getElementById('premium-range').textContent = 'βΉ0 - βΉ0'; |
|
} |
|
|
|
|
|
if (data.market_trends) { |
|
document.getElementById('city-tier').textContent = data.city_tier || 'Unknown'; |
|
document.getElementById('price-trend').textContent = data.market_trends.trend || 'Unknown'; |
|
document.getElementById('market-average').textContent = marketAveragePerSqft; |
|
document.getElementById('price-deviation').textContent = `${deviation.toFixed(1)}%`; |
|
|
|
|
|
const deviationElement = document.getElementById('price-deviation'); |
|
if (deviation > 20) { |
|
deviationElement.style.color = '#dc3545'; |
|
} else if (deviation < -20) { |
|
deviationElement.style.color = '#28a745'; |
|
} else { |
|
deviationElement.style.color = '#17a2b8'; |
|
} |
|
} else { |
|
document.getElementById('city-tier').textContent = 'Unknown'; |
|
document.getElementById('price-trend').textContent = 'Unknown'; |
|
document.getElementById('market-average').textContent = 'βΉ0'; |
|
document.getElementById('price-deviation').textContent = '0%'; |
|
} |
|
|
|
|
|
if (data.price_factors && data.price_factors.property_age) { |
|
const age = data.price_factors.property_age; |
|
document.getElementById('age-factor').innerHTML = |
|
`<span class="factor-value">${age.impact ? age.impact.toUpperCase() : 'UNKNOWN'}</span><span class="factor-impact">${age.description || 'Impact: Unknown'}</span>`; |
|
} else { |
|
document.getElementById('age-factor').innerHTML = '<span class="factor-value">Unknown</span><span class="factor-impact">Impact: Unknown</span>'; |
|
} |
|
|
|
if (data.price_factors && data.price_factors.size_efficiency) { |
|
const size = data.price_factors.size_efficiency; |
|
document.getElementById('size-factor').innerHTML = |
|
`<span class="factor-value">${size.impact ? size.impact.toUpperCase() : 'UNKNOWN'}</span><span class="factor-impact">${size.description || 'Impact: Unknown'}</span>`; |
|
} else { |
|
document.getElementById('size-factor').innerHTML = '<span class="factor-value">Unknown</span><span class="factor-impact">Impact: Unknown</span>'; |
|
} |
|
|
|
if (data.price_factors && data.price_factors.amenities) { |
|
const amenities = data.price_factors.amenities; |
|
document.getElementById('amenities-factor').innerHTML = |
|
`<span class="factor-value">${amenities.impact ? amenities.impact.toUpperCase() : 'UNKNOWN'}</span><span class="factor-impact">${amenities.description || 'Impact: Unknown'}</span>`; |
|
} else { |
|
document.getElementById('amenities-factor').innerHTML = '<span class="factor-value">Unknown</span><span class="factor-impact">Impact: Unknown</span>'; |
|
} |
|
|
|
|
|
const riskList = document.getElementById('risk-indicators'); |
|
riskList.innerHTML = ''; |
|
|
|
if (data.risk_indicators && data.risk_indicators.length > 0) { |
|
data.risk_indicators.forEach(risk => { |
|
const li = document.createElement('li'); |
|
li.className = 'risk-item warning'; |
|
li.innerHTML = `β οΈ ${risk}`; |
|
riskList.appendChild(li); |
|
}); |
|
} else { |
|
const li = document.createElement('li'); |
|
li.className = 'risk-item success'; |
|
li.innerHTML = 'β
No significant risks identified'; |
|
riskList.appendChild(li); |
|
} |
|
|
|
|
|
const priceComparisonDiv = document.createElement('div'); |
|
priceComparisonDiv.className = 'price-comparison-summary'; |
|
|
|
|
|
let assessmentClass = 'market-rate'; |
|
if (deviation > 20) { |
|
assessmentClass = 'above-market'; |
|
} else if (deviation < -20) { |
|
assessmentClass = 'below-market'; |
|
} |
|
|
|
const comparisonHTML = ` |
|
<h4 style="margin-bottom: 15px; color: #4361ee; font-size: 1.2rem;">Price Comparison Summary</h4> |
|
<div class="price-comparison-grid"> |
|
<div class="price-comparison-item"> |
|
<div class="price-comparison-label">Listing Price per sq.ft.</div> |
|
<div class="price-comparison-value listing">${listingPricePerSqft}</div> |
|
</div> |
|
<div class="price-comparison-item"> |
|
<div class="price-comparison-label">Market Average per sq.ft.</div> |
|
<div class="price-comparison-value market">${marketAveragePerSqft}</div> |
|
</div> |
|
</div> |
|
<div class="price-assessment-summary"> |
|
<div class="price-assessment-label">Price Assessment</div> |
|
<div class="price-assessment-value ${assessmentClass}">${assessmentText}</div> |
|
</div> |
|
`; |
|
|
|
priceComparisonDiv.innerHTML = comparisonHTML; |
|
|
|
|
|
const priceOverview = document.querySelector('.price-overview'); |
|
if (priceOverview && priceOverview.parentNode) { |
|
priceOverview.parentNode.insertBefore(priceComparisonDiv, priceOverview.nextSibling); |
|
} |
|
|
|
} catch (error) { |
|
console.error('Error updating price analysis:', error); |
|
} |
|
} |
|
|
|
function updatePriceRanges(ranges) { |
|
try { |
|
const elements = { |
|
'budget-range': ranges.budget, |
|
'mid-range': ranges.mid_range, |
|
'premium-range': ranges.premium |
|
}; |
|
|
|
Object.entries(elements).forEach(([id, range]) => { |
|
const element = document.getElementById(id); |
|
if (element) { |
|
element.innerHTML = ` |
|
<div class="range-value">βΉ${range.min.toLocaleString()} - βΉ${range.max.toLocaleString()}</div> |
|
<div class="range-description">${range.description}</div> |
|
`; |
|
} |
|
}); |
|
} catch (error) { |
|
console.error('Error updating price ranges:', error); |
|
} |
|
} |
|
|
|
function updateMarketTrends(trends) { |
|
try { |
|
|
|
const cityTier = document.getElementById('city-tier'); |
|
if (cityTier) { |
|
const tierIcon = trends.city_tier === 'metro' ? 'ποΈ' : |
|
trends.city_tier === 'tier-1' ? 'π' : |
|
trends.city_tier === 'tier-2' ? 'ποΈ' : 'π‘'; |
|
cityTier.innerHTML = `${tierIcon} ${trends.city_tier.toUpperCase()}`; |
|
} |
|
|
|
|
|
const priceTrend = document.getElementById('price-trend'); |
|
if (priceTrend) { |
|
const trendIcon = trends.avg_price_range.trend === 'increasing' ? 'π' : |
|
trends.avg_price_range.trend === 'decreasing' ? 'π' : 'β‘οΈ'; |
|
priceTrend.innerHTML = `${trendIcon} ${trends.avg_price_range.trend.toUpperCase()}`; |
|
} |
|
|
|
|
|
const marketAvg = document.getElementById('market-average'); |
|
if (marketAvg) { |
|
const avgPrice = trends.price_per_sqft.market_avg; |
|
marketAvg.innerHTML = ` |
|
<div class="trend-value">βΉ${avgPrice.toLocaleString()}</div> |
|
<div class="trend-description">Market Average</div> |
|
`; |
|
} |
|
|
|
|
|
const priceDeviation = document.getElementById('price-deviation'); |
|
if (priceDeviation) { |
|
const deviation = trends.price_per_sqft.deviation; |
|
const deviationColor = deviation > 20 ? '#f72585' : deviation > 10 ? '#f8961e' : '#4cc9f0'; |
|
priceDeviation.innerHTML = ` |
|
<div class="trend-value" style="color: ${deviationColor}">${deviation.toFixed(1)}%</div> |
|
<div class="trend-description">From Market Average</div> |
|
`; |
|
} |
|
} catch (error) { |
|
console.error('Error updating market trends:', error); |
|
} |
|
} |
|
|
|
function updatePriceFactors(factors) { |
|
try { |
|
|
|
if (factors.age_factor) { |
|
const ageFactor = document.getElementById('age-factor'); |
|
if (ageFactor) { |
|
const ageImpact = factors.age_factor.impact === 'high' ? 'π΄' : |
|
factors.age_factor.impact === 'medium' ? 'π‘' : 'π’'; |
|
ageFactor.innerHTML = ` |
|
<span class="factor-value">${factors.age_factor.property_age} years</span> |
|
<span class="factor-impact">${ageImpact} Impact: ${factors.age_factor.impact.toUpperCase()}</span> |
|
`; |
|
} |
|
} |
|
|
|
|
|
if (factors.size_factor) { |
|
const sizeFactor = document.getElementById('size-factor'); |
|
if (sizeFactor) { |
|
const sizeEfficiency = factors.size_factor.efficiency === 'high' ? 'π’' : |
|
factors.size_factor.efficiency === 'medium' ? 'π‘' : 'π΄'; |
|
sizeFactor.innerHTML = ` |
|
<span class="factor-value">${sizeEfficiency} ${factors.size_factor.efficiency.toUpperCase()}</span> |
|
<span class="factor-impact">Size: ${factors.size_factor.size.toLocaleString()} sq.ft.</span> |
|
`; |
|
} |
|
} |
|
|
|
|
|
if (factors.amenities_factor) { |
|
const amenitiesFactor = document.getElementById('amenities-factor'); |
|
if (amenitiesFactor) { |
|
const amenitiesImpact = factors.amenities_factor.impact === 'high' ? 'π’' : |
|
factors.amenities_factor.impact === 'medium' ? 'π‘' : 'π΄'; |
|
amenitiesFactor.innerHTML = ` |
|
<span class="factor-value">${amenitiesImpact} ${factors.amenities_factor.count} amenities</span> |
|
<span class="factor-impact">Impact: ${factors.amenities_factor.impact.toUpperCase()}</span> |
|
`; |
|
} |
|
} |
|
} catch (error) { |
|
console.error('Error updating price factors:', error); |
|
} |
|
} |
|
|
|
function updateRiskIndicators(indicators) { |
|
try { |
|
const riskList = document.getElementById('risk-indicators'); |
|
if (!riskList) return; |
|
|
|
riskList.innerHTML = ''; |
|
if (indicators.length > 0) { |
|
indicators.forEach(risk => { |
|
const li = document.createElement('li'); |
|
li.innerHTML = `β οΈ ${risk}`; |
|
li.style.background = '#fff3e0'; |
|
li.style.color = '#e65100'; |
|
riskList.appendChild(li); |
|
}); |
|
} else { |
|
const li = document.createElement('li'); |
|
li.innerHTML = 'β
No significant risks identified'; |
|
li.style.background = '#e8f5e9'; |
|
li.style.color = '#2e7d32'; |
|
riskList.appendChild(li); |
|
} |
|
} catch (error) { |
|
console.error('Error updating risk indicators:', error); |
|
} |
|
} |
|
</script> |
|
</body> |
|
</html> |