We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/tbrennem-source/sf-permits-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<title>Property Report — {{ report.address }} — sfpermits.ai</title>
<script src="/static/htmx.min.js"></script>
<style>
:root {
--bg: #0f1117;
--surface: #1a1d27;
--surface-2: #252834;
--border: #333749;
--text: #e4e6eb;
--text-muted: #8b8fa3;
--accent: #4f8ff7;
--accent-hover: #3a7ae0;
--success: #34d399;
--warning: #fbbf24;
--error: #f87171;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
min-height: 100vh;
}
.container { max-width: 960px; margin: 0 auto; padding: 0 24px; }
/* Header */
header { border-bottom: 1px solid var(--border); padding: 20px 0; }
header .container { display: flex; align-items: center; justify-content: space-between; }
.logo { font-size: 1.4rem; font-weight: 700; color: var(--accent); text-decoration: none; }
.logo span { color: var(--text-muted); font-weight: 400; }
.header-right { display: flex; align-items: center; gap: 10px; }
.badge {
font-size: 0.7rem; background: var(--surface-2); color: var(--text-muted);
padding: 4px 10px; border-radius: 12px; border: 1px solid var(--border);
text-decoration: none; cursor: pointer; font-family: inherit;
}
.badge:hover { color: var(--text); border-color: var(--text-muted); }
/* Back link */
.back-link {
display: inline-block; margin-top: 24px; font-size: 0.85rem;
color: var(--text-muted); text-decoration: none;
}
.back-link:hover { color: var(--accent); }
/* Page title area */
main { padding: 0 0 80px; }
.page-title { margin-top: 8px; margin-bottom: 4px; font-size: 1.6rem; font-weight: 700; }
.page-subtitle { color: var(--text-muted); margin-bottom: 28px; font-size: 0.95rem; }
.page-subtitle a { color: var(--accent); text-decoration: none; }
.page-subtitle a:hover { text-decoration: underline; }
/* Sections */
.section {
background: var(--surface); border: 1px solid var(--border);
border-radius: 12px; padding: 24px; margin-bottom: 24px;
}
.section h2 {
font-size: 1.1rem; margin-bottom: 16px; color: var(--accent);
display: flex; align-items: center; gap: 8px;
}
.section-empty { color: var(--text-muted); font-style: italic; font-size: 0.9rem; }
/* Risk assessment */
.risk-card {
padding: 14px 16px; border-radius: 8px; margin-bottom: 12px;
border-left: 4px solid transparent;
}
.risk-card:last-child { margin-bottom: 0; }
.risk-card.risk-high { background: rgba(248, 113, 113, 0.08); border-left-color: var(--error); }
.risk-card.risk-moderate { background: rgba(251, 191, 36, 0.08); border-left-color: var(--warning); }
.risk-card.risk-low { background: rgba(79, 143, 247, 0.08); border-left-color: var(--accent); }
.risk-card.risk-none { background: rgba(52, 211, 153, 0.08); border-left-color: var(--success); }
.risk-header { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.severity-badge {
display: inline-block; padding: 2px 8px; border-radius: 4px;
font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
}
.severity-high { background: rgba(248, 113, 113, 0.2); color: var(--error); }
.severity-moderate { background: rgba(251, 191, 36, 0.2); color: var(--warning); }
.severity-low { background: rgba(79, 143, 247, 0.2); color: var(--accent); }
.risk-title { font-weight: 600; font-size: 0.95rem; }
.risk-title a { color: var(--text); text-decoration: none; }
.risk-title a:hover { color: var(--accent); text-decoration: underline; }
.risk-description { font-size: 0.85rem; color: var(--text-muted); }
.risk-xref { font-size: 0.8rem; color: var(--accent); text-decoration: none; margin-top: 4px; display: inline-block; }
.risk-xref:hover { text-decoration: underline; }
/* Property profile grid */
.profile-grid {
display: grid; grid-template-columns: 1fr 1fr; gap: 0;
}
.profile-item {
padding: 10px 0; border-bottom: 1px solid var(--border);
display: flex; justify-content: space-between; align-items: baseline;
}
.profile-item:nth-child(odd) { padding-right: 16px; }
.profile-item:nth-child(even) { padding-left: 16px; border-left: 1px solid var(--border); }
.profile-label { font-size: 0.8rem; color: var(--text-muted); text-transform: uppercase; font-weight: 600; }
.profile-value { font-size: 0.9rem; text-align: right; }
.profile-value a { color: var(--accent); text-decoration: none; }
.profile-value a:hover { text-decoration: underline; }
/* Tables */
table.data-table {
width: 100%; border-collapse: collapse; font-size: 0.85rem;
}
table.data-table th {
text-align: left; padding: 10px 12px; font-size: 0.75rem;
text-transform: uppercase; color: var(--text-muted); font-weight: 600;
border-bottom: 2px solid var(--border);
}
table.data-table td {
padding: 10px 12px; border-bottom: 1px solid var(--border);
vertical-align: top;
}
table.data-table tbody tr:nth-child(even) td { background: var(--surface-2); }
table.data-table tbody tr:hover td { background: rgba(79, 143, 247, 0.05); }
table.data-table a { color: var(--accent); text-decoration: none; }
table.data-table a:hover { text-decoration: underline; }
/* Status badges */
.status-badge {
display: inline-block; padding: 2px 8px; border-radius: 4px;
font-size: 0.7rem; font-weight: 600; text-transform: uppercase; white-space: nowrap;
}
.status-filed { background: rgba(79, 143, 247, 0.15); color: var(--accent); }
.status-approved { background: rgba(52, 211, 153, 0.15); color: var(--success); }
.status-issued { background: rgba(52, 211, 153, 0.2); color: var(--success); }
.status-complete, .status-completed { background: rgba(52, 211, 153, 0.3); color: var(--success); }
.status-expired, .status-cancelled, .status-revoked, .status-disapproved, .status-withdrawn {
background: rgba(248, 113, 113, 0.15); color: var(--error);
}
.status-open { background: rgba(251, 191, 36, 0.15); color: var(--warning); }
.status-closed { background: rgba(52, 211, 153, 0.15); color: var(--success); }
.status-suspended { background: rgba(248, 113, 113, 0.15); color: var(--error); }
/* Expandable permit details */
.permit-expand-btn {
background: none; border: none; color: var(--text-muted); cursor: pointer;
font-size: 0.75rem; font-family: inherit; padding: 2px 6px; border-radius: 4px;
}
.permit-expand-btn:hover { color: var(--accent); background: var(--surface-2); }
.permit-details {
display: none; padding: 12px 16px; background: var(--bg);
border-radius: 6px; margin-top: 8px;
}
.permit-details.open { display: block; }
.permit-details h4 {
font-size: 0.75rem; text-transform: uppercase; color: var(--text-muted);
margin-bottom: 6px; font-weight: 600;
}
.permit-details .detail-item {
font-size: 0.8rem; padding: 4px 0;
border-bottom: 1px solid var(--border);
}
.permit-details .detail-item:last-child { border-bottom: none; }
/* Complaint / violation cards */
.cv-card {
padding: 12px 16px; border-radius: 8px; margin-bottom: 10px;
background: var(--surface-2); border: 1px solid var(--border);
}
.cv-card:last-child { margin-bottom: 0; }
.cv-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
.cv-number { font-weight: 600; font-size: 0.9rem; }
.cv-number a { color: var(--text); text-decoration: none; }
.cv-number a:hover { color: var(--accent); text-decoration: underline; }
.cv-meta { font-size: 0.8rem; color: var(--text-muted); }
.cv-description { font-size: 0.85rem; margin-top: 4px; color: var(--text); }
/* Sub-section headers */
.sub-heading {
font-size: 0.9rem; font-weight: 600; color: var(--text);
margin-bottom: 12px; margin-top: 4px;
}
.sub-divider { border-top: 1px solid var(--border); margin: 20px 0; }
/* Consultant callout */
.consultant-callout {
padding: 16px 20px; border-radius: 8px; font-size: 0.9rem;
}
.consultant-warm {
background: rgba(79, 143, 247, 0.06); border: 1px solid rgba(79, 143, 247, 0.2);
color: var(--text-muted);
}
.consultant-recommended {
background: rgba(251, 191, 36, 0.08); border: 1px solid rgba(251, 191, 36, 0.3);
color: var(--warning);
}
.consultant-strongly_recommended {
background: rgba(248, 113, 113, 0.08); border: 1px solid rgba(248, 113, 113, 0.3);
color: var(--error);
}
.consultant-essential {
background: rgba(248, 113, 113, 0.12); border: 2px solid var(--error);
color: var(--error); font-weight: 600;
}
.consultant-callout a { color: var(--accent); text-decoration: none; font-weight: 600; }
.consultant-callout a:hover { text-decoration: underline; }
.consultant-factors { font-size: 0.8rem; color: var(--text-muted); margin-top: 8px; }
.consultant-factors li { margin-bottom: 2px; }
/* Owner Mode banner */
.owner-banner {
background: rgba(79, 143, 247, 0.08); border: 1px solid rgba(79, 143, 247, 0.25);
border-radius: 8px; padding: 12px 16px; margin-bottom: 24px;
font-size: 0.85rem; color: var(--accent); display: flex; align-items: center; gap: 8px;
}
.owner-banner-icon { font-size: 1.1rem; }
.badge-owner {
font-size: 0.7rem; background: rgba(79, 143, 247, 0.15); color: var(--accent);
padding: 4px 10px; border-radius: 12px; border: 1px solid rgba(79, 143, 247, 0.3);
text-decoration: none; cursor: pointer; font-family: inherit;
}
.badge-owner:hover { background: rgba(79, 143, 247, 0.25); }
/* Remediation roadmap cards */
.remediation-card {
padding: 20px; border-radius: 8px; margin-bottom: 16px;
border-left: 4px solid transparent; background: var(--surface-2);
}
.remediation-card:last-child { margin-bottom: 0; }
.remediation-card.risk-high { border-left-color: var(--error); }
.remediation-card.risk-moderate { border-left-color: var(--warning); }
.remediation-stakes {
font-size: 0.9rem; color: var(--text); margin-bottom: 16px;
padding: 10px 14px; background: var(--bg); border-radius: 6px;
border-left: 3px solid var(--warning);
}
.remediation-options { display: flex; flex-direction: column; gap: 12px; }
.remediation-option {
padding: 14px; border-radius: 8px; border: 1px solid var(--border);
background: var(--surface);
}
.remediation-option strong { font-size: 0.9rem; color: var(--text); }
.remediation-option p { font-size: 0.85rem; color: var(--text-muted); margin-top: 4px; }
.remediation-option .cost-timeline {
font-size: 0.8rem; color: var(--text-muted); margin-top: 6px;
}
.remediation-option ol {
margin-top: 8px; padding-left: 20px; font-size: 0.8rem; color: var(--text-muted);
}
.remediation-option ol li { margin-bottom: 3px; }
.effort-full_compliance { border-color: var(--success); }
.effort-least_effort { border-color: var(--accent); }
.effort-status_quo { border-color: var(--border); opacity: 0.8; }
.remediation-sources {
margin-top: 12px; display: flex; flex-wrap: wrap; gap: 8px;
}
.remediation-sources a {
font-size: 0.75rem; color: var(--accent); text-decoration: none;
background: rgba(79, 143, 247, 0.08); padding: 3px 8px; border-radius: 4px;
}
.remediation-sources a:hover { text-decoration: underline; background: rgba(79, 143, 247, 0.15); }
/* KB citations on risk cards */
.risk-citations {
margin-top: 6px; display: flex; flex-wrap: wrap; gap: 6px;
}
.citation-link {
font-size: 0.7rem; color: var(--accent); text-decoration: none;
background: rgba(79, 143, 247, 0.06); padding: 2px 6px; border-radius: 3px;
}
.citation-link:hover { text-decoration: underline; background: rgba(79, 143, 247, 0.12); }
/* What's Missing sub-section */
.whats-missing-header {
font-size: 0.9rem; font-weight: 600; color: var(--warning);
margin-bottom: 12px; display: flex; align-items: center; gap: 6px;
}
/* Zoning note */
.zoning-note { font-size: 0.85rem; color: var(--text-muted); margin-top: 8px; line-height: 1.5; }
/* Share modal */
.modal-overlay {
display: none; position: fixed; inset: 0;
background: rgba(0, 0, 0, 0.7); z-index: 100;
align-items: center; justify-content: center;
}
.modal-overlay.open { display: flex; }
.modal-box {
background: var(--surface); border: 1px solid var(--border);
border-radius: 12px; padding: 28px; max-width: 440px; width: 90%;
}
.modal-box h3 { font-size: 1.1rem; margin-bottom: 16px; }
.modal-box input[type="email"] {
width: 100%; background: var(--bg); border: 1px solid var(--border);
border-radius: 8px; color: var(--text); padding: 12px 16px;
font-size: 0.95rem; font-family: inherit; margin-bottom: 12px;
}
.modal-box input[type="email"]:focus { outline: none; border-color: var(--accent); }
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; }
.btn-primary {
background: var(--accent); color: #fff; border: none; border-radius: 8px;
padding: 10px 24px; font-size: 0.9rem; font-weight: 600;
cursor: pointer; font-family: inherit;
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-cancel {
background: none; color: var(--text-muted); border: 1px solid var(--border);
border-radius: 8px; padding: 10px 24px; font-size: 0.9rem;
cursor: pointer; font-family: inherit;
}
.btn-cancel:hover { color: var(--text); border-color: var(--text-muted); }
#share-result { font-size: 0.85rem; margin-top: 8px; }
/* Footer */
footer {
border-top: 1px solid var(--border); padding: 20px 0;
text-align: center; color: var(--text-muted); font-size: 0.8rem;
}
footer a { color: var(--accent); text-decoration: none; }
footer a:hover { text-decoration: underline; }
/* Print styles */
@media print {
:root {
--bg: #fff; --surface: #fff; --surface-2: #f5f5f5;
--border: #ddd; --text: #1a1a2e; --text-muted: #666;
--accent: #2563eb;
}
body { background: #fff; color: #1a1a2e; font-size: 11pt; }
header, footer, .back-link, .share-btn-wrap,
.permit-expand-btn, .modal-overlay { display: none !important; }
.section { break-inside: avoid; border: 1px solid #ddd; }
.permit-details { display: block !important; }
table.data-table tbody tr:nth-child(even) td { background: #f9f9f9; }
a { color: #2563eb; }
}
/* Responsive */
@media (max-width: 640px) {
.profile-grid { grid-template-columns: 1fr; }
.profile-item:nth-child(even) { padding-left: 0; border-left: none; }
.profile-item:nth-child(odd) { padding-right: 0; }
table.data-table { font-size: 0.8rem; }
table.data-table th, table.data-table td { padding: 8px 6px; }
.page-title { font-size: 1.3rem; }
.modal-box { padding: 20px; }
}
</style>
<link rel="stylesheet" href="/static/mobile.css">
</head>
<body>
<header>
<div class="container">
<a href="/" class="logo">sfpermits<span>.ai</span></a>
<div class="header-right">
<span class="share-btn-wrap">
{% if user and not is_owner %}
<a href="?owner=1" class="badge-owner">This is my property</a>
{% endif %}
{% if user %}
<button class="badge" onclick="openShareModal()">Share Report</button>
{% else %}
<a href="/auth/login" class="badge">Sign in to share</a>
{% endif %}
</span>
{% if user %}
<a href="/brief" class="badge">Morning Brief</a>
<a href="/account" class="badge">{{ user.display_name or user.email }}</a>
<form method="POST" action="/auth/logout" style="display:inline;">
<button class="badge" type="submit">Logout</button>
</form>
{% else %}
<a href="/auth/login" class="badge">Sign in</a>
{% endif %}
</div>
</div>
</header>
<main>
<div class="container">
<a href="/" class="back-link">← Back to search</a>
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;">
<h1 class="page-title" style="margin-bottom:0;">Property Report</h1>
{% if user %}
<button class="btn-primary" onclick="openShareModal()" style="font-size:0.85rem;padding:8px 18px;white-space:nowrap;">
✉ Share Report
</button>
{% else %}
<a href="/auth/login" class="badge" style="font-size:0.8rem;">Sign in to share</a>
{% endif %}
</div>
<p class="page-subtitle">
<strong>{{ report.address }}</strong>
·
Block {{ report.block }}, Lot {{ report.lot }}
{% if report.links and report.links.parcel %}
·
<a href="{{ report.links.parcel }}" target="_blank" rel="noopener">View parcel</a>
{% endif %}
</p>
{% if is_owner %}
<div class="owner-banner">
<span class="owner-banner-icon">🏠</span>
You are viewing this report as the property owner. Recommendations are tailored for your situation.
</div>
{% endif %}
{# ===== Section 1: Risk Assessment ===== #}
<div class="section" id="section-risks">
<h2>Risk Assessment</h2>
{% if report.risk_assessment %}
{% for risk in report.risk_assessment %}
<div class="risk-card risk-{{ risk.severity }}">
<div class="risk-header">
<span class="severity-badge severity-{{ risk.severity }}">{{ risk.severity }}</span>
<span class="risk-title">
{% if risk.link %}
<a href="{{ risk.link }}" target="_blank" rel="noopener">{{ risk.title }}</a>
{% else %}
{{ risk.title }}
{% endif %}
</span>
</div>
<div class="risk-description">{{ risk.description }}</div>
{% if risk.section_ref %}
<a href="#section-{{ risk.section_ref }}" class="risk-xref">See §{{ risk.section_ref }}</a>
{% endif %}
{% if risk.kb_citations %}
<div class="risk-citations">
{% for cite in risk.kb_citations %}
<a href="{{ cite.source_url }}" class="citation-link" target="_blank" rel="noopener">{{ cite.source_label }}</a>
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
{% else %}
<div class="risk-card risk-none">
<div class="risk-header">
<span class="severity-badge" style="background:rgba(52,211,153,0.2);color:var(--success);">clear</span>
<span class="risk-title">No known risks</span>
</div>
<div class="risk-description">No active complaints, violations, or anomalies were found for this property.</div>
</div>
{% endif %}
</div>
{# ===== Section 2: Complaints & Violations ===== #}
<div class="section" id="section-complaints">
<h2>Complaints & Violations</h2>
<div class="sub-heading">Complaints</div>
{% if report.complaints %}
{% for c in report.complaints %}
<div class="cv-card">
<div class="cv-header">
<span class="cv-number">
<a href="{{ links.complaint(c.complaint_number) }}" target="_blank" rel="noopener">#{{ c.complaint_number }}</a>
</span>
<span class="status-badge status-{{ (c.status or '')|lower }}">{{ c.status or 'Unknown' }}</span>
</div>
<div class="cv-meta">
Filed {{ c.date_filed or 'N/A' }}
{% if c.street_name %} · {{ c.street_name }}{% endif %}
</div>
{% if c.complaint_description %}
<div class="cv-description">{{ c.complaint_description }}</div>
{% endif %}
</div>
{% endfor %}
{% else %}
<p class="section-empty">No complaints on file.</p>
{% endif %}
<div class="sub-divider"></div>
<div class="sub-heading">Violations (Notices of Violation)</div>
{% if report.violations %}
{% for v in report.violations %}
<div class="cv-card">
<div class="cv-header">
<span class="cv-number">
{% if v.complaint_number %}
<a href="{{ links.complaint(v.complaint_number) }}" target="_blank" rel="noopener">#{{ v.complaint_number }}</a>
{% else %}
Violation
{% endif %}
</span>
<span class="status-badge status-{{ (v.status or '')|lower }}">{{ v.status or 'Unknown' }}</span>
</div>
<div class="cv-meta">
Filed {{ v.date_filed or 'N/A' }}
{% if v.nov_category_description %} · {{ v.nov_category_description }}{% endif %}
</div>
{% if v.description %}
<div class="cv-description">{{ v.description }}</div>
{% endif %}
</div>
{% endfor %}
{% else %}
<p class="section-empty">No violations on file.</p>
{% endif %}
</div>
{# ===== Section 3: Cross-Reference Analysis ===== #}
{% if report.whats_missing %}
<div class="section" id="section-cross-ref">
<h2>⚠ Cross-Reference Analysis</h2>
{% for item in report.whats_missing %}
<div class="risk-card risk-{{ item.severity }}">
<div class="risk-header">
<span class="severity-badge severity-{{ item.severity }}">{{ item.severity }}</span>
<span class="risk-title">{{ item.title }}</span>
</div>
<div class="risk-description">{{ item.description }}</div>
{% if item.section_ref %}
<a href="#section-{{ item.section_ref }}" class="risk-xref">See §{{ item.section_ref }}</a>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{# ===== Section 4: Remediation Roadmap (Owner Mode) ===== #}
{% if report.remediation_roadmap %}
<div class="section" id="section-remediation">
<h2>Remediation Roadmap</h2>
{% for card in report.remediation_roadmap %}
<div class="remediation-card risk-{{ card.severity }}">
<div class="risk-header">
<span class="severity-badge severity-{{ card.severity }}">{{ card.severity }}</span>
<span class="risk-title">{{ card.title }}</span>
</div>
{% if card.what_at_stake %}
<div class="remediation-stakes">{{ card.what_at_stake }}</div>
{% endif %}
<div class="remediation-options">
{% for opt in card.options %}
<div class="remediation-option effort-{{ opt.effort }}">
<strong>{{ opt.label }}</strong>
{% if opt.cost_range %}
<span class="cost-timeline">{{ opt.cost_range }}{% if opt.timeline %} · {{ opt.timeline }}{% endif %}</span>
{% endif %}
<p>{{ opt.description }}</p>
{% if opt.steps %}
<ol>{% for step in opt.steps %}<li>{{ step }}</li>{% endfor %}</ol>
{% endif %}
</div>
{% endfor %}
</div>
{% if card.sources %}
<div class="remediation-sources">
{% for src in card.sources %}
<a href="{{ src.url }}" target="_blank" rel="noopener">{{ src.label }}</a>
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{# ===== Section 5: Property Profile ===== #}
{% if report.property_profile %}
<div class="section" id="section-profile">
<h2>Property Profile</h2>
<div class="profile-grid">
<div class="profile-item">
<span class="profile-label">Address</span>
<span class="profile-value">{{ report.address }}</span>
</div>
<div class="profile-item">
<span class="profile-label">Parcel</span>
<span class="profile-value">
{% if report.links and report.links.parcel %}
<a href="{{ report.links.parcel }}" target="_blank" rel="noopener">{{ report.block }}/{{ report.lot }}</a>
{% else %}
{{ report.block }}/{{ report.lot }}
{% endif %}
</span>
</div>
<div class="profile-item">
<span class="profile-label">Zoning</span>
<span class="profile-value">
{% if report.property_profile.zoning %}
<a href="{{ links.planning_code(report.property_profile.zoning) }}" target="_blank" rel="noopener">{{ report.property_profile.zoning }}</a>
{% else %}
—
{% endif %}
</span>
</div>
<div class="profile-item">
<span class="profile-label">Assessed Value</span>
<span class="profile-value">{{ report.property_profile.assessed_value or '—' }}</span>
</div>
<div class="profile-item">
<span class="profile-label">Property Class</span>
<span class="profile-value">{{ report.property_profile.property_class or '—' }}</span>
</div>
<div class="profile-item">
<span class="profile-label">Year Built</span>
<span class="profile-value">{{ report.property_profile.year_built or '—' }}</span>
</div>
<div class="profile-item">
<span class="profile-label">Building Area</span>
<span class="profile-value">{{ report.property_profile.building_area or '—' }}</span>
</div>
<div class="profile-item">
<span class="profile-label">Lot Area</span>
<span class="profile-value">{{ report.property_profile.lot_area or '—' }}</span>
</div>
<div class="profile-item">
<span class="profile-label">Use Code</span>
<span class="profile-value">{{ report.property_profile.use_code or '—' }}</span>
</div>
<div class="profile-item">
<span class="profile-label">Tax Year</span>
<span class="profile-value">{{ report.property_profile.tax_year or '—' }}</span>
</div>
</div>
</div>
{% endif %}
{# ===== Section 6: Permit History ===== #}
<div class="section" id="section-permits">
<h2>Permit History</h2>
{% if report.permits %}
<div style="overflow-x:auto;">
<table class="data-table">
<thead>
<tr>
<th>Permit #</th>
<th>Type</th>
<th>Description</th>
<th>Cost</th>
<th>Status</th>
<th>Filed</th>
<th></th>
</tr>
</thead>
<tbody>
{% for p in report.permits %}
<tr>
<td>
<a href="{{ links.permit(p.permit_number) }}">{{ p.permit_number }}</a>
</td>
<td>{{ p.permit_type_definition or '—' }}</td>
<td>{{ p.description or '—' }}</td>
<td>
{% if p.estimated_cost %}
${{ '{:,.0f}'.format(p.estimated_cost) }}
{% else %}
—
{% endif %}
</td>
<td>
<span class="status-badge status-{{ (p.status or '')|lower }}">{{ p.status or 'Unknown' }}</span>
</td>
<td>{{ p.filed_date or '—' }}</td>
<td>
{% if p.contacts or p.inspections or (p.routing and p.routing.total > 0) %}
<button class="permit-expand-btn" onclick="togglePermitDetails('permit-{{ loop.index }}')">Details</button>
{% endif %}
</td>
</tr>
<tr>
<td colspan="7" style="padding:0;border:none;">
<div class="permit-details" id="permit-{{ loop.index }}">
{% if p.contacts %}
<h4>Contacts</h4>
{% for c in p.contacts %}
<div class="detail-item">
<strong>{{ c.role }}</strong>:
{% if c.canonical_name %}
<a href="{{ links.entity(c.canonical_name) }}">{{ c.name }}</a>
{% else %}
{{ c.name }}
{% endif %}
{% if c.firm_name %} — {{ c.firm_name }}{% endif %}
</div>
{% endfor %}
{% endif %}
{% if p.inspections %}
<h4 {% if p.contacts %}style="margin-top:12px;"{% endif %}>Recent Inspections</h4>
{% for insp in p.inspections %}
<div class="detail-item">
{{ insp.scheduled_date or '' }}
· {{ insp.description or 'Inspection' }}
{% if insp.inspector %} · {{ insp.inspector }}{% endif %}
·
<span style="font-weight:600;{% if insp.result == 'APPROVED' %}color:var(--success);{% elif insp.result in ('DISAPPROVED', 'NOT APPROVED') %}color:var(--error);{% else %}color:var(--text-muted);{% endif %}">
{{ insp.result or 'Pending' }}
</span>
</div>
{% endfor %}
{% endif %}
{% if p.routing and p.routing.total > 0 %}
<h4 style="margin-top:12px;">Plan Review Routing</h4>
<div style="margin-bottom:8px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:6px;">
<div style="flex:1;max-width:200px;height:8px;background:rgba(255,255,255,0.08);border-radius:4px;overflow:hidden;">
<div style="width:{{ p.routing.pct }}%;height:100%;background:{% if p.routing.pct == 100 %}var(--success, #34d399){% elif p.routing.pct >= 50 %}var(--accent, #4f8ff7){% else %}#f59e0b{% endif %};border-radius:4px;"></div>
</div>
<span style="font-size:0.85rem;font-weight:600;">
{{ p.routing.completed }}/{{ p.routing.total }} stations
{% if p.routing.is_all_clear %}
<span style="color:var(--success);">✓ All clear</span>
{% endif %}
</span>
</div>
{% if p.routing.approved %}<span class="detail-item" style="color:var(--success);">{{ p.routing.approved }} approved</span>{% endif %}
{% if p.routing.comments %}<span class="detail-item" style="color:#f59e0b;{% if p.routing.approved %}margin-left:12px;{% endif %}">{{ p.routing.comments }} with comments</span>{% endif %}
{% if p.routing.pending > 0 %}
<div class="detail-item" style="margin-top:4px;color:var(--text-muted);font-size:0.82rem;">
Pending: {{ p.routing.pending_stations | join(', ') }}
</div>
{% endif %}
{% if p.routing.stalled %}
<div style="margin-top:4px;">
{% for s in p.routing.stalled %}
<span class="detail-item" style="color:#f87171;">⚠ {{ s.station }} stalled {{ s.days }}d</span>
{% endfor %}
</div>
{% endif %}
{% if p.routing.latest %}
<div class="detail-item" style="margin-top:4px;font-size:0.82rem;color:var(--text-muted);">
Latest: {{ p.routing.latest.station }}
{% if p.routing.latest.result %} · {{ p.routing.latest.result }}{% endif %}
{% if p.routing.latest.date %} · {{ p.routing.latest.date }}{% endif %}
</div>
{% endif %}
</div>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="section-empty">No permits found for this property.</p>
{% endif %}
</div>
{# ===== Section 7: Zoning & Regulatory Context ===== #}
{% if report.property_profile and report.property_profile.zoning %}
<div class="section" id="section-zoning">
<h2>Zoning & Regulatory Context</h2>
<div style="margin-bottom:12px;">
<span class="profile-label" style="margin-right:8px;">Zoning District</span>
<a href="{{ links.planning_code(report.property_profile.zoning) }}" target="_blank" rel="noopener"
style="color:var(--accent);text-decoration:none;font-weight:600;">
{{ report.property_profile.zoning }}
</a>
</div>
{% if report.property_profile.property_class %}
<div style="margin-bottom:12px;">
<span class="profile-label" style="margin-right:8px;">Property Class</span>
<span>{{ report.property_profile.property_class }}</span>
</div>
{% endif %}
<div class="zoning-note">
{% set zoning = report.property_profile.zoning %}
{% if zoning.startswith('RH-1') %}
This property is in an <strong>RH-1</strong> (Residential, House — One Family) district.
Permits involving additional units, ADUs, or building envelope changes may require neighborhood notification
and Planning Department review under the Residential Design Guidelines.
{% elif zoning.startswith('RH-2') %}
This property is in an <strong>RH-2</strong> (Residential, House — Two Family) district.
Up to two dwelling units are permitted by right. ADU conversions may have streamlined approval.
{% elif zoning.startswith('RH-3') %}
This property is in an <strong>RH-3</strong> (Residential, House — Three Family) district.
Up to three dwelling units are permitted. Review Residential Design Guidelines for exterior changes.
{% elif zoning.startswith('RM-') %}
This property is in a <strong>Residential Mixed</strong> ({{ zoning }}) district.
Density limits and height controls apply. Check bulk and setback requirements for additions.
{% elif zoning.startswith('RC-') %}
This property is in a <strong>Residential-Commercial</strong> ({{ zoning }}) district.
Ground-floor commercial uses may be permitted. Mixed-use projects should review use size limits.
{% elif zoning.startswith('NC-') or zoning.startswith('NCD-') %}
This property is in a <strong>Neighborhood Commercial</strong> ({{ zoning }}) district.
Permitted uses vary by sub-district. Formula retail restrictions and use-size limits may apply.
{% elif zoning.startswith('C-') %}
This property is in a <strong>Commercial</strong> ({{ zoning }}) district.
Review permitted uses and conditional use requirements for the specific sub-district.
{% elif zoning.startswith('M-') or zoning.startswith('PDR-') %}
This property is in a <strong>Production/Distribution/Repair</strong> ({{ zoning }}) district.
PDR use protections apply. Conversion to non-PDR uses may require conditional use authorization.
{% elif zoning.startswith('P') %}
This property is in a <strong>Public</strong> ({{ zoning }}) district.
Development is generally limited to institutional and public uses.
{% else %}
This property is zoned <strong>{{ zoning }}</strong>.
Review the SF Planning Code for district-specific development standards and permitted uses.
{% endif %}
</div>
</div>
{% endif %}
{# ===== Section 8: Nearby Activity ===== #}
{% if report.nearby_activity %}
<div class="section" id="section-nearby">
<h2>Nearby Permit Activity</h2>
<div style="overflow-x:auto;">
<table class="data-table">
<thead>
<tr>
<th>Address</th>
<th>Permit #</th>
<th>Description</th>
<th>Cost</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for n in report.nearby_activity %}
<tr>
<td>{{ n.street_number }} {{ n.street_name }} {{ n.street_suffix or '' }}</td>
<td>
<a href="{{ links.permit(n.permit_number) }}">{{ n.permit_number }}</a>
</td>
<td>{{ n.description or '—' }}</td>
<td>
{% if n.estimated_cost %}
${{ '{:,.0f}'.format(n.estimated_cost) }}
{% else %}
—
{% endif %}
</td>
<td>
<span class="status-badge status-{{ (n.status or '')|lower }}">{{ n.status or 'Unknown' }}</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{# ===== Section 9: Consultant Recommendation ===== #}
{% if report.consultant_signal and report.consultant_signal.signal != 'cold' %}
<div class="section" id="section-consultant">
<h2>Do You Need a Consultant?</h2>
<p style="color:var(--text-muted);font-size:0.85rem;margin-bottom:16px;">
Based on this property's permit history, open issues, and complexity profile, here's
our assessment of whether professional consulting help would benefit your project.
Land use consultants specialize in navigating SF's permitting process and can save significant
time on complex or at-risk properties.
</p>
{% set sig = report.consultant_signal %}
{% set exp_url = '/consultants?block=' ~ report.block ~ '&lot=' ~ report.lot ~ '&signal=' ~ sig.signal %}
{% if report.property_profile and report.property_profile.neighborhood %}
{% set exp_url = exp_url ~ '&neighborhood=' ~ report.property_profile.neighborhood %}
{% endif %}
{% if report.complaints and report.complaints | length > 0 %}
{% set exp_url = exp_url ~ '&has_complaint=1' %}
{% endif %}
{% if sig.signal == 'warm' %}
<div class="consultant-callout consultant-warm">
Depending on your project scope, a consultant could save time.
<a href="{{ exp_url }}">Find a consultant →</a>
</div>
{% elif sig.signal == 'recommended' %}
<div class="consultant-callout consultant-recommended">
Based on the risk profile, we recommend using a consultant for this property.
<a href="{{ exp_url }}">Find a consultant →</a>
</div>
{% elif sig.signal == 'strongly_recommended' %}
<div class="consultant-callout consultant-strongly_recommended">
{{ sig.message or 'A consultant is strongly advised given the circumstances of this property.' }}
<br><a href="{{ exp_url }}">Find a consultant →</a>
</div>
{% elif sig.signal == 'essential' %}
<div class="consultant-callout consultant-essential">
{{ sig.message or 'An experienced consultant is essential for navigating the permitting process on this property.' }}
<br><a href="{{ exp_url }}">Find a consultant →</a>
</div>
{% endif %}
{% if sig.factors %}
<ul class="consultant-factors">
{% for f in sig.factors %}
<li>{{ f }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endif %}
</div>
</main>
<footer>
<div class="container">
Generated by <a href="/">sfpermits.ai</a> ·
Data from <a href="https://data.sfgov.org" target="_blank" rel="noopener">SF Open Data</a>
</div>
</footer>
{# ===== Share Modal ===== #}
<div class="modal-overlay" id="share-modal">
<div class="modal-box">
<h3>Share this report</h3>
<form hx-post="/report/{{ report.block }}/{{ report.lot }}/share"
hx-target="#share-result"
hx-swap="innerHTML">
<input type="email" name="email" placeholder="Recipient email address" required
style="width:100%;padding:10px 14px;background:var(--surface-2);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:0.9rem;margin-bottom:8px;">
<textarea name="message" placeholder="Add a personal note (optional)"
rows="3" maxlength="500"
style="width:100%;padding:10px 14px;background:var(--surface-2);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:0.85rem;resize:vertical;font-family:inherit;margin-bottom:4px;"></textarea>
<div id="share-result"></div>
<div class="modal-actions">
<button type="button" class="btn-cancel" onclick="closeShareModal()">Cancel</button>
<button type="submit" class="btn-primary">Send Report</button>
</div>
</form>
</div>
</div>
<script>
function togglePermitDetails(id) {
var el = document.getElementById(id);
if (el) el.classList.toggle('open');
}
function openShareModal() {
document.getElementById('share-modal').classList.add('open');
}
function closeShareModal() {
document.getElementById('share-modal').classList.remove('open');
}
// Close modal on overlay click
document.getElementById('share-modal').addEventListener('click', function(e) {
if (e.target === this) closeShareModal();
});
// Close modal on Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeShareModal();
});
</script>
{% include 'fragments/feedback_widget.html' %}
</body>
</html>