feat: AR-House initial commit

This commit is contained in:
2026-07-03 12:24:58 -04:00
commit 047c05287a
216 changed files with 127552 additions and 0 deletions
+309
View File
@@ -0,0 +1,309 @@
<!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"><title>
Duval County Public Records Search
</title><meta http-equiv="X-UA-Compatible" content="IE=9">
<!--[if IE 9]><link rel="stylesheet" type="text/css" media="screen" href="/Content/ie9specific.css" /><![endif]-->
<!--[if IE 8]><link rel="stylesheet" type="text/css" media="screen" href="/Content/ie8specific.css" /><![endif]-->
<!--[if IE 7]><link rel="stylesheet" type="text/css" media="screen" href="/Content/ie7specific.css" /><![endif]-->
<link href="/Content/common.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="/Scripts/jquery-latest.pack.js"></script>
<script type="text/javascript" src="/Scripts/jquery-migrate-1.4.1.min.js"></script>
<script type="text/javascript" src="/Scripts/jquery.min.js"></script>
<script type="text/javascript" src="/Scripts/jquery-migrate-3.3.0.min.js"></script>
<script type="text/javascript" src="/Scripts/jquery-3.6.1.min.js"></script>
<script type="text/javascript" src="/Scripts/kendo/2023.1.117/kendo.all.min.js"></script>
<script type="text/javascript" src="/Scripts/kendo/2023.1.117/kendo.aspnetmvc.min.js"></script>
<link href="/Content/kendo/kendo.common.min.css" rel="stylesheet" type="text/css"><link href="/Content/kendo/kendo.Office365.min.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="/Scripts/jquery.validate.min.js"></script>
<script type="text/javascript" src="/scripts/jquery.hoverIntent.js"></script>
<script type="text/javascript" src="/Scripts/jquery.cookie.js"></script>
<script type="text/javascript" src="/Scripts/AcclaimCommon.js"></script>
<script type="text/javascript" src="/Scripts/AcclaimCustom.js"></script>
<link href="App_Themes/Duval/categoryBrowser.css" type="text/css" rel="stylesheet"><link href="App_Themes/Duval/Site.css" type="text/css" rel="stylesheet"></head>
<!-- .Combined(true)
.Compress(true)-->
<body>
<div class="page">
<header id="pageheader">
<div id="header">
<div class="navigationBar" style="display:none;">
</div>
<div id="home">
<a href="#" title="County Home Page" onclick="window.open('http://www2.duvalclerk.com')"> </a>
</div>
<input type="hidden" id="resxHeaderLogo" value="">
<input type="hidden" id="resxLogoAlign" value="">
<input type="hidden" id="resxLogoTitle" value="">
<input type="hidden" id="resxHeaderBackroundColor" value="">
</div>
<div id="submenucontainer">
<script>
var EcommerceEnabled = false;
var EnableFbnDownload = false;
var Agent = false;
</script>
<div>
<ul class="k-widget k-reset k-header k-menu k-menu-horizontal" id="Menu" data-role="menu" aria-orientation="horizontal" tabindex="0" role="menubar"><li class="k-item k-state-highlight k-menu-item k-first" aria-haspopup="true" aria-expanded="false" role="menuitem"><a class="k-link k-menu-link" href="/"><img alt="image" class="k-image" src="/Content/Images/searchMenu.png"><span class="k-menu-link-text">Official Records</span><span aria-hidden="true" class="k-menu-expand-arrow"><span class="k-menu-expand-arrow-icon k-icon k-i-arrow-s"></span></span></a><ul class="k-group k-menu-group k-reset k-menu-group-md" role="menu" style="display: none;" aria-hidden="true"><li class="k-item k-menu-item k-first" role="menuitem"><a class="k-link k-menu-link" href="/search/SearchTypeName"><span class="k-menu-link-text">Name</span></a></li><li class="k-item k-menu-item" role="menuitem"><a class="k-link k-menu-link" href="/search/SearchTypeInstrumentNumber"><span class="k-menu-link-text">Instrument #</span></a></li><li class="k-item k-menu-item" role="menuitem"><a class="k-link k-menu-link" href="/search/SearchTypeDocType"><span class="k-menu-link-text">Doc Type</span></a></li><li class="k-item k-menu-item" role="menuitem"><a class="k-link k-menu-link" href="/search/SearchTypeRecordDate"><span class="k-menu-link-text">Record Date</span></a></li><li class="k-item k-menu-item" role="menuitem"><a class="k-link k-menu-link" href="/search/SearchTypeConsideration"><span class="k-menu-link-text">Consideration</span></a></li><li class="k-item k-menu-item" role="menuitem"><a class="k-link k-menu-link" href="/search/SearchTypeBookPage"><span class="k-menu-link-text">Book/Page</span></a></li><li class="k-item k-menu-item k-last" role="menuitem"><a class="k-link k-menu-link" href="/search/SearchTypeCaseNumber"><span class="k-menu-link-text">Case #</span></a></li></ul></li><li class="k-item k-menu-item" role="menuitem"><a class="k-link k-menu-link" href="/Support"><img alt="image" class="k-image" src="/Content/images/supportMenu.png"><span class="k-menu-link-text">Support</span></a></li><li class="k-item k-menu-item" role="menuitem"><a class="k-link k-menu-link" href="/Search/Settings"><img alt="image" class="k-image" src="/Content/images/imageTools24.png"><span class="k-menu-link-text">Settings</span></a></li><li class="k-item right-align k-menu-item k-last" role="menuitem"><a class="k-link k-menu-link" href="/Login"><img alt="image" class="k-image" src="/Content/Images/logout.png"><span class="k-menu-link-text">Login</span></a></li></ul><script>
kendo.syncReady(function(){jQuery("#Menu").kendoMenu({});});
</script>
</div>
<noscript>
<div>
<h2 style="color: White;background-color:Red;"> You must enable Javascript to continue. Click
<a href='Support' style="color: White;background-color:Red;text-decoration:underline">here</a> for help on how to enable Javascript. </h2>
</div>
</noscript>
<script> function addBlankTargetToLink(obj) {
obj.attributes("target", "_blank");
}</script>
<div id="cookiesDisabled" style="display:none">
<h2 style="color: White;background-color:Red;"> You must enable Cookies to continue. Click
<a href="Support" style="color: White;background-color:Red;text-decoration:underline">here</a> for help on how to enable browser cookies. </h2>
</div>
</div>
</header>
</div>
<main id="mainBack">
<div id="main">
<div id="mainForm" tabindex="0" style="height: 516px;">
<div id="divDisclaimerLogin" class="disclaimerLogin t-header" style="display: block; overflow: auto; height: 516px; min-height: 550px;">
<br><br>
<div id="disclaimer" style="display: block;">
<div id="divSimpleTwoColumn" class="paper formSimple" style="width: 400px; border: none;">
<div class="t-header" style="width: 411px; margin-left: -6px; font-size: 1.5em; line-height: 2; font-weight: bold;">Disclaimer</div>
<p>
The County presents the information on this web site as a service to the public.
We have tried to ensure that the information contained in this electronic search
system is accurate. County makes no warranty or guarantee concerning the accuracy
or reliability of the content at this site or at other sites to which we link. Assessing
accuracy and reliability of information is the responsibility of the user. The user
is advised to search on all possible spelling variations of proper names, in order
to maximize search results.</p>
<p>
The County shall not be liable for errors contained herein or for any damages in
connection with the use of the information contained herein.
</p>
<p>
If you choose not to accept the conditions stated above please exit this search
application.
</p>
<form action="/search/Disclaimer" method="post">
<input type="hidden" name="Disclaimer" value="true">
<div style="text-align:right; display:block;">
<input type="submit" id="btnButton" value="I accept the conditions above." class="t-button">
</div>
</form>
</div>
</div>
<br>
<div class="importantMessageDiv" style="display: block;
margin-left: 20px; width: 411px;">
<div class="Alert AlertMessage">
<div class="AlertTitle AlertTitleMessage" style="font-size: 1em;
font-weight: bold; color: white; background-color: #b30000; text-align: left;padding-left:5px ">
Important Message
</div>
<div class="AlertText AlertTextMessage" style="text-align: left;font-size: 1em;color: #444; background-color: white;">
<p>
&nbsp;
The Duval Clerks accessibility policy and requests for accommodation under the ADA can be found by clicking here: <a href="https://www.duvalclerk.com/about/accessibility" style="color:#1a73e8; text-decoration:underline;">Accessibility</a><br><br>Attention: Documents that were recorded during the period January 1, 2004 through January 20, 2004 were recorded containing an extra zero with the Document #. This number has since been removed within our system.<br>Example: 2004000555 now reads 200400555. It is recommended that when searching for documents that were recorded during these dates the user search using Grantor/Grantee or Book/Page.
</p>
</div>
</div>
</div><br>
<div style="display: block;">
<form action="/Login" id="login" method="post"><input name="__RequestVerificationToken" type="hidden" value="IsGtyTb5dOwGDv3XSDc5N6h7f8Vdh06Cgn00UG_UIeFIW_rpRRTwWEW6hvRBg-cTwicNZ6I3yfts1PotzQLBxZ1Wg2PAErfEPgBzGivaSlE1">
<div id="divSimpleTwoColumn" class="paper formSimple" style="width: 400px; border: none;">
<div class="t-header" style="width: 411px; margin-left: -6px; font-size: 1.5em; line-height: 2; font-weight: bold;">
Sign In</div>
<div class="formLabel">
Login As</div>
<div class="formContainer" style="clear: both;">
<div class="formRow">
<div class="formLabel col1" style="width: 85px;">
<label for="Username"> User Name </label>
</div>
<div class="formInput col2" style="width: 300px;">
<input class="t-input required" id="Username" name="Username" style="Width: 295px; margin-bottom: 3px;" type="text" value="">
</div>
</div>
<div class="formRow">
<div class="formLabel col1" style="width: 85px;">
<label for="Password"> Password</label>
</div>
<div class="formInput col2" style="width: 300px;">
<input class="t-input required" id="Password" name="Password" style="Width: 295px" type="password">
</div>
</div>
</div>
<div class="loginFooter">
<span><a href="/Agent/ResetPassword">Forgot Password</a></span>
<span><input type="checkbox" id="Disclaimer" value="true" name="disclaimer"><label for="Disclaimer"> Accept Disclaimer</label></span>
<input type="submit" id="Submit1" value="Login" class="t-button" onclick="acclaimValidate('login');">
</div>
<p class="InstructionsTextStyle">
County registered users (agents) must use their agent key to login. County Agents: Login using your agent key.<br>Users: Login using the email address you registered with.
</p>
</div>
</form>
<script language="javascript" type="text/javascript">
$(document).ready(function () {
$("#Username").focus();
})
</script>
<br>
<br>
</div>
</div>
<h1 class="t-header" style="padding-left: 15px; margin:0;">
Official Records Search</h1>
<div>
<div id="boxHolder2">
<div class="boxName searchIndexDiv">
<h4 style="text-align: center;">
<button class="t-button" onclick="location.href='/search/SearchTypeName'">
<img src="/Content/images/Name.png" alt="Name" style="border: 0px;">
</button><br>
<a id="mnname" href="/search/SearchTypeName" class="subspace">
Name</a></h4>
<div style="height: 100px; overflow: auto;">
<span class="nameDesc">
Perform a Party Name (Grantor and/or Grantee) search on Official Records documents.</span>
</div>
</div>
<div class="boxInstrumentNumber searchIndexDiv">
<h4 style="text-align: center;">
<button class="t-button" onclick="location.href='/search/SearchTypeInstrumentNumber'">
<img src="/Content/images/InstrumentNumber.png" alt="Instrument #" style="border: 0px;">
</button><br>
<a id="mninstrumentnumber" href="/search/SearchTypeInstrumentNumber" class="subspace">
Instrument #</a></h4>
<div style="height: 100px; overflow: auto;">
<span class="nameDesc">
Perform an Instrument Number search on an Official Records document.</span>
</div>
</div>
<div class="boxDocType searchIndexDiv">
<h4 style="text-align: center;">
<button class="t-button" onclick="location.href='/search/SearchTypeDocType'">
<img src="/Content/images/DocType.png" alt="Doc Type" style="border: 0px;">
</button><br>
<a id="mndoctype" href="/search/SearchTypeDocType" class="subspace">
Doc Type</a></h4>
<div style="height: 100px; overflow: auto;">
<span class="nameDesc">
Perform Document Type(s) search on Official Records documents.</span>
</div>
</div>
<div class="boxRecordDate searchIndexDiv">
<h4 style="text-align: center;">
<button class="t-button" onclick="location.href='/search/SearchTypeRecordDate'">
<img src="/Content/images/RecordDate.png" alt="Record Date" style="border: 0px;">
</button><br>
<a id="mnrecorddate" href="/search/SearchTypeRecordDate" class="subspace">
Record Date</a></h4>
<div style="height: 100px; overflow: auto;">
<span class="nameDesc">
Perform a Date search on a single day of Official Records.</span>
</div>
</div>
<div class="boxConsideration searchIndexDiv">
<h4 style="text-align: center;">
<button class="t-button" onclick="location.href='/search/SearchTypeConsideration'">
<img src="/Content/images/Consideration.png" alt="Consideration" style="border: 0px;">
</button><br>
<a id="mnconsideration" href="/search/SearchTypeConsideration" class="subspace">
Consideration</a></h4>
<div style="height: 100px; overflow: auto;">
<span class="nameDesc">
Perform a Consideration range (upper &amp; lower bound price) search on an Official Records conveyance document.</span>
</div>
</div>
<div class="boxBookPage searchIndexDiv">
<h4 style="text-align: center;">
<button class="t-button" onclick="location.href='/search/SearchTypeBookPage'">
<img src="/Content/images/BookPage.png" alt="Book/Page" style="border: 0px;">
</button><br>
<a id="mnbookpage" href="/search/SearchTypeBookPage" class="subspace">
Book/Page</a></h4>
<div style="height: 100px; overflow: auto;">
<span class="nameDesc">
Perform a combined Book Type, Number and Page search on Official Records documents.</span>
</div>
</div>
<div class="boxCaseNumber searchIndexDiv">
<h4 style="text-align: center;">
<button class="t-button" onclick="location.href='/search/SearchTypeCaseNumber'">
<img src="/Content/images/CaseNumber.png" alt="Case #" style="border: 0px;">
</button><br>
<a id="mncasenumber" href="/search/SearchTypeCaseNumber" class="subspace">
Case #</a></h4>
<div style="height: 100px; overflow: auto;">
<span class="nameDesc">
Perform a Case Number search on Official Records documents.</span>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer>
<div id="footer" class="t-header">
<div id="BrandFooter"></div>
Copyright 2026 © Acclaim, is a registered trademark of HARRIS RECORDING SOLUTIONS
| <span style="text-decoration: underline;"><a href="http://www2.duvalclerk.com/" class="t-link">Contact Us</a></span>
</div>
</footer>
<script type="text/javascript">
$(document).ready(function () {
mainHieght();
window.name = 'AcclaimwebMainWindow';
if (!IsCookiesFeatureEnabled()) { $('#cookiesDisabled').show(); }
})
window.onresize = function () {
mainHieght();
}
</script>
</body></html>
+457
View File
@@ -0,0 +1,457 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><head><meta http-equiv="X-UA-Compatible" content="IE=IE8">
<script type="text/javascript" src="/javascript/jquery/jquery.js"></script>
<script type="text/javascript" src="/javascript/plugins.js"></script>
<script type="text/javascript" src="/javascript/actions.js"></script>
<title>
Property Appraiser - Basic Search
</title><link rel="stylesheet" type="text/css" href="../style/base.css" media="screen"><link rel="stylesheet" type="text/css" href="../style/print.css" media="print">
<script type="text/javascript">
function openWin(re,sec) {
var url = "../Feedback.aspx?RE=" + re + "&Section=" + sec
window.open(url,'Feedback','WIDTH=700,HEIGHT=800,toolbar=0,location=0,directories=0,status=0,menubar=0,scrollbars=1,resizable=0,screenX=200,screenY=0,left=100,top=0,address=0')
}
</script>
<link href="../App_Themes/coj/coj.css" type="text/css" rel="stylesheet"><link href="../App_Themes/coj/print.css" type="text/css" rel="stylesheet"></head>
<body>
<form name="aspnetForm" method="post" action="./Search.aspx" id="aspnetForm" role="document">
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTA5MjcxODAyMmQYAQUeX19Db250cm9sc1JlcXVpcmVQb3N0QmFja0tleV9fFgEFJ2N0bDAwJGNwaEJvZHkkaWRCYXNpY0FkZEFkZHJlc3NUb0V4cG9ydBmz10pYcpDChmdhmQzNa0tdGRaUOBFyTc5EzKw6c9hg">
<script type="text/javascript">
//<![CDATA[
function REJump()
{
if (document.forms(0).ctl00_cphBody_tbRE6.value.length == 6)
document.forms(0).ctl00_cphBody_tbRE4.focus();
}
function ConfirmCancel()
{
if (confirm('Are you sure you want to clear the form? Click OK to clear it.'))
{
document.forms(0).ctl00_cphBody_tbRE6.value = '';
document.forms(0).ctl00_cphBody_tbRE4.value = '';
document.forms(0).ctl00_cphBody_tbName.value = '';
document.forms(0).ctl00_cphBody_tbStreetNumber.value = '';
document.forms(0).ctl00_cphBody_tbStreetName.value = '';
document.forms(0).ctl00_cphBody_ddStreetSuffix.selectedIndex = 0;
document.forms(0).ctl00_cphBody_ddStreetPrefix.selectedIndex = 0;
document.forms(0).ctl00_cphBody_tbStreetUnit.value = '';
document.forms(0).ctl00_cphBody_ddCity.selectedIndex = 0;
document.forms(0).ctl00_cphBody_tbZipCode.value = '';
}
}
//]]>
</script>
<input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="B39370EB">
<input type="hidden" name="__PREVIOUSPAGE" id="__PREVIOUSPAGE" value="f4sbtl_e9TGgGuZ-4FZ_mtJeiYKkg6ZH5miMIqPD_L5SppCdEHIslGNj6HEABFM-G4ijamoKqm2aiOmpdnh-EU0bmSTVxekxaSjFUrJR1Z41">
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEdAM0BcVWjcc15WYX4+TIEFSxCuYMDkLbyYkWc3vtraYVsPN6uyjIIcmg5TjRye0AuEmCnORWfHbdceBmXHzXHx4L3xMOe3oYywsxNOKF5jWlKO8vUK+aPSOTaS1Q7zBQ1xlUfDfM9q4hvFbzsI7Prfu73qPZlDHfY8Y1TStdbZ21+N32Y+nW0IrYInsR0RjOvaYz7d0OJbgCd7PtU3nHegAR/4MiNDVzLurPjY2F4IoYR/KZdEtbqL/8fog+w9eJU9dj8uIWc1D+3Aj6AplBp5Wm0TToplEm4ofqAHE3/bLYXlF4kCGb99grFfLZQ7hBi/UUS6vuMP6eaH2DT8VtyLtzx18WH/cLk6KlTZver0YnXCJ45Wb9yTHuwpdL4QFY2t7ikUZ3yWyhaOyg1BwkbrTmGu+j5xVtwlPtBUVqftr+l74i7wmODrur7ksIZf7RK8h/3pK5bGapV9IrIXSInDdwc2C37VdGqLxaCrX8giRPPq+JPzRsPMuH3olBsbqXuH75EEjRkHfATaE+Wmz9AXbFzT6QnfXXu/NL7EucQWH+8gZSsFg/0sbiy+EIg3KZIRwQoJLw+FFAZdJLJlrqq4dWGU22BS/9Iwy1t1L/PeNr0yKf0ABfKr09AtNfJbIG3GDMSZK3Qs/ODIeo7+5rI/wv2HCYshOnuhviMz5UwE2K3WfrRsgq8YNor6DWEOif2uJwL8HmnOxcb0SWBYeyKJ+4L/KXCV9Lh11Tik2EcM5Bs7Tor9fNk4FuN5bATHGedW8vYugB1pyw1uSAug/GwKsuoFVkJxdlfGLWk4M+u8p7gZxjY9JHtWZuC0Imk4fgEeF+OjrQfw8udHQetnrsBGMpyVNv5MkOc83R/1yy/Sxw9UqyF8C3Bksr/c/0GScYl8Wo/Ig/xRyOQ1aVe/HDsRLvrAU1Wvd4HPVtoZT8eHSmPMMYb5+Mlb0zFuC/f9rSXoPp9427aCG3UXoPskrxRcIfR5R+AOujK3JMoooggyPiI67UjzR97EVFJy5NHD+kwVK+RExCb+b9xLMrc40z4bcedEY5IxBE/oGFozUkFDGCGkVk+FUSO3r4xidQD1ehL30yubBfn7iXXOLiFkQl4PBzwJ9L8FGIuBvsYn9TlHlGfJ9k8LBKzlBCuarPwPKgL/H/jsBBu4Tbgg21BDlWsmeg4WIRL2chmcz38uyuUsaEMhTX4ESR4IH+0mMpmzzuAiNEdd5wMAir2SWuXstIrRxt4jFab83CZimjypwtC1Y54yyOliqXkIWAevPZmMN0NyVc1zLlHkL4mZd2D2IrhNg+xb+CqxqgkHo0iwVkjDfKoEaD5fHZvCGO03wCsV9eInC4R+z2pCzG36sSGh2I8xebrt5huHCQQk/RjuRYGOjSTHc/4aPkz/b9Ys5S3l7kVDQTJIj8jiLRkz9fsuH0Lcs0RgrPfjEZOH8ucS505zg8RbJpWsk9xtkt2qcSCPDREOfaHOkPLsAOz7WZs4Nwl77/tz85oot8qR8pSQUdpzAJlaORnzzx/JH8oVYD39eWEr5eLPDz0BVQnner1H2wuLCMehkRXcCXn307Xo+EeMTXtM7Sxoc9Lt+/ongWTFA5p4RwIrN5+44BiKC/9k+0gn8+w4plP0cCFJTu+lymaDKiAXuzXUga+I8zewFD7Dfts3qByKv6lnnlvrf8HadbhbI82cWfCqFyVlUw+8p61CbqimrBphfQErQ+avn6+W0CGZpdO2B3+wsT1zjhe7AAzF/4BXcaLKJTX6WYtI9SCCnVod80eePvPg0izvT/vaSWGPYsf5Lj/LXtxzQ3ISa/pa45OmBu79M/H/PDl+bbjNm8ImYeYt8dLJLaHdW2FVdTnekru0EPacFhHT+CT2SKzV/E+2de6+V1aTFJoIyIQ5jMlhcYnNzCRnaG7Yjk/XSZtAkeBkub5a7k50zhCSzwSOzFc3CtLwDX/Pepbm1WGQfe4UZhWSzCbptVo4xy09qjpswIN/+jhZ0EWlilDufcdaLweCRoanaDaI0oySBz4e/d7u6seWOkmRH53It8ohHFP6ZxCS+NcNFzTOnM2ZocYJaCz1rWrLj3tGFp4K23eMS2XnQphKalbNPQEJLw93Q3o5pl+/mV6HWNEl67du+XS1PuBNNmhwxCH5gVDLYGM8/yPBJmfiuwySCvN/OLMtq9tJU67quf6xqCyNy80egpjNp5l1enc+vjZYHIdQrevV78nAuzwK8fm8Q+qx9jmFivFU6TcdE567Lx5ZngPH4jhNxkM1sdhnA1rcU72DT/MIV5a7Wqn98hXyS0GeRukpGypBNRBzIUB3g8wX8aLzvu00Ii9cwdSN39YInMb//be9g8mRFBWgLkuHkFq/1C3GI1vYquVYpZz2SrjrZulkKyXTZ6K/aezxNR98Vyp4eg+EobJFK+ygW22aiUfWLBNhS00S51I4uoZmXNNWY48CmgVdjR1ibZZbOUO6dmGxNnXW+ChEbWoJxkTBuk6H287p/deu3r0facQgKjK6aJCBml2G3S+Yi+qXshha0tyuZReWSD+nWmz/mo9CeUImJ6nrDb2/gR6dK+mNijc2yHzEip5hSxX8u+Up4ZEkA1EPUgBPWn4K2bjXcFbSyBwVcSAWOG4vTyPEwlUPmSJec0jn7wrDwiCHR5hhnDcfzBrv9+hLV/7hh/jF4ruB7XMkFXsatmf0XOVATgF5gG2u6xPG6/l4dKxRUmLa+rvs+7akCG1H9Db3w3TYI3pLgO0BpkgvBugS2ySvkyTZPZf/HYMNqxvi23vW0a7gmFFb28oQ4vrNwY/zCv4IcZmViAgo6zEkUvyedKW4Ubk/kw/6J/w+a6/kgV7txF19hR9LdcnJ023dOrUe/k4LKeuBunrMJRz4CepRvWmoI4jiBPu70SV6Y1ObjCWOQzaAOh7nemeLEXWIQKRUs0PHI98Kmq0I5OX0mmAGqC/GYjrrwrpdVIPNMuF3ltZ65UkHWhZt3SIFkK2vl4HDvlIa/gFC9pEk9xLocm2wQAq/HrhIonUERavuO/MkX/qkpweDg0Yo092Gwp4c86dBjtMM9x1LWx2Q32ttrBG0/jgTDDjVbb2gN8d4ZKGNOUXRah1fVS/pdW8RIJiStq5h5qwMMLGKruV6d1skHW7mPw0RDlq0lveCZaMcVNiT8sb/810XBYG+ouKf1SjrRk7TMHSw4uzDCV2qRQCHUVMJBYha8C9q6bvBukv4SbY8Pu7Ae337jq/lnvkF/+cYXaw7scCER+2V68tyrMffJQZ6Fo/U++8zP4Eg7dLVgW323/RQ+Llh70vvUwHZZJBH/gXF3CW62J74ukPZyFt/OTqotyvbGQyZGGIGdtIsxm1Ua7es0NRc+f9Xwfi4+3YF+di2y8/p10i+sgJ5oSpzFhHA+254qn0oK2GGkB1OzlxkD+rfSNl1cUUIxm5pK9xmIayS/HZSKjtF1dhlejtgjG0qb1/tpNaLUmNry8fyhP+HhnXcygoBO49mHxLuG06pYlUzrrlPoD1p9fUXIEXNO2fqfSGR8VoSrogXMvxlFyh4KFVkXrf2fe/7M1xLiKGNm0NcNzvEmxvfwqgPv5X6y/HFucWJLvZPkpmsbS68K+lWlSq3alP2U+flitcPThv9oo8nt4y6IVgFgD6Fydy4B2OuRDGxBJRHz0MwQ6QZMAr/ra0i1IVBC060sdlBVvDdKokS3jfKyrF62hrGxgO1Sy0V1W5/4/JObTe5u9FRoDAHdj6bWhtMXyc80h/p2YMPlORgjfdcM3cYJI15M5jagBq/MModdac4T4cVr8K/X2yIzUMg+S4MY05KMvUjoZMJwax7PtfhAlx/8UnLxNCnJrFdlKUeEfYEooRjiBbppcvZnnyoqldq2nPJ5UnyIkL4nZxWsE0ENio/hLWRuMk086L3zLbTa9Ke6ITiDRFfCE9oDYgeGawh+z0ZgdFi9tsZ8CSU6ioTuOn9ZHeFR6G2QpDsSGHgbGw861Z2ldRrBQ6SLOJSJQnzL4LOppPqudCotKCepE8G/vcBvNz4MfgGYVuSX7meTLQerxy+rTUMSK4q98WkXxuAHxrFWJH0w4pInI5Savcuiv0p97wjOL/o2Zl+TU+5uCgfZxjEt71ibExzIGASvU/OoQjdzZQQ31GkS6tEedmdHvILHPzDy914dsMQicRJ2B5dY64swcb1qUdhK/drPOtjUnqOYpCZrOWXNQK8AHCM13bXYF2aajpy2NcB0Q5NHQjtDHlwebQQER30u149QumSu+saGF9P4HR9cR61gmv52w4T/VvoY/hsGYjKqZBtqN04RKRyvf1XpcylsVmp12wIZA5qW1VVsZQTvUV0x/X8yF0u3w6Op2rFwIVSvWaP2YKZm7Hh0+J0ZEzG2S6fXZzRwORlvKrouYWU0xh43OqHkdk+A/4BCQD7nAJxnu4">
<div id="doc3" class="yui-t7">
<!-- header -->
<a href="http://www.coj.net/Departments/Property+Appraiser/default.htm" id="ctl00_home" style="width:1000px;height:500px" title="Home">
<div id="hd" style="height:260px" role="IMG" title="Joyce Morgan Header" alt="City of Jacksonville Florida United States of America Property Appraiser Office Duval County Florida Seal of City of Jacksonville Photo of Jacksonville Waterfront Photo of Jerry Holland Jerry Holland Property Appraiser Fair and Accurate For All">
<div id="cojnetbits">
<!-- breadcrumbs -->
<div id="bc">
<ol id="cphBreadcrumbs">
<li id="last">
</li>
</ol>
</div>
<!-- coj.net search -->
<div id="searchBox"></div>
</div>
</div>
</a>
<!-- body -->
<div id="bd">
<!-- navigation -->
<div class="yui-g">
<div id="navWrapper">
<div id="navContainer">
<ol id="nav">
<li>
<a id="ctl00_menu_ctl00_navItem" class="active" href="/Basic/Search.aspx">Basic Search</a>
</li>
<li>
<a id="ctl00_menu_ctl01_navItem" href="/Sales/Search.aspx">Advanced Search</a>
</li>
<li>
<a id="ctl00_menu_ctl02_navItem" class="last" href="/Tangible/Search.aspx">Tangible Search</a>
</li>
</ol>
</div>
</div>
</div>
<!-- body content -->
<div class="yui-g" id="content">
<div id="searchWrapper">
<div id="searchForm">
<div id="searchInstructions" class="">
<img src="../images/tip.gif" alt="Tool Tip">
<em>For best results, search by RE# or Property Owner Information or by Property Address.</em>
</div>
<h2>Real Estate Number</h2>
<div class="formBox">
<fieldset>
<ol id="basic_re">
<li class="single">
<label>RE #</label>
<input name="ctl00$cphBody$tbRE6" type="text" maxlength="6" id="ctl00_cphBody_tbRE6" title="First 6 digits of Real Estate Number" onkeyup="REJump();">
<span>-</span>
<input name="ctl00$cphBody$tbRE4" type="text" maxlength="4" id="ctl00_cphBody_tbRE4" title="Last 4 digits of Real Estate Number">
</li>
</ol>
</fieldset>
</div>
<h2>Property Owner Information</h2>
<div class="formBox">
<fieldset>
<ol id="basic_owner">
<li id="searchname" class="first single">
<label>Owner or Business Name</label>
<input name="ctl00$cphBody$tbName" type="text" maxlength="50" id="ctl00_cphBody_tbName" title="Enter Owner or Business Name, Last name first">
</li>
<li class="searchex single">
<p id="first">Last name only or Last First (e.g., Doe John).</p>
<p>To search for a spouse's name, enter: last, first.</p>
</li>
</ol>
</fieldset>
</div>
<h2>Property Address</h2>
<div class="formBox">
<fieldset>
<ol id="basic_address">
<li class="first">
<label>Street #</label>
<input name="ctl00$cphBody$tbStreetNumber" type="text" value="3245" maxlength="9" id="ctl00_cphBody_tbStreetNumber" title="Enter the street number">
</li>
<li class="nospace">
<label for="tbStreetName">Street Name</label>
<input name="ctl00$cphBody$tbStreetName" type="text" value="PEARL" maxlength="50" id="ctl00_cphBody_tbStreetName" title="Enter the street name.">
</li>
<li>
<label>Street Type</label>
<select name="ctl00$cphBody$ddStreetSuffix" id="ctl00_cphBody_ddStreetSuffix" aria-label="Street Type">
<option value=""></option>
<option value="ALY">Alley</option>
<option value="ANX">Annex</option>
<option value="AVE">Avenue</option>
<option value="BCH">Beach</option>
<option value="BND">Bend</option>
<option value="BLF">Bluff</option>
<option value="BLFS">Bluffs</option>
<option value="BLVD">Boulevard</option>
<option value="BR">Branch</option>
<option value="BRG">Bridge</option>
<option value="BRK">Brook</option>
<option value="BRKS">Brooks</option>
<option value="BYP">Bypass</option>
<option value="CP">Camp</option>
<option value="CPE">Cape</option>
<option value="CSWY">Causeway</option>
<option value="CTRS">Centers</option>
<option value="CIR">Circle</option>
<option value="CIRS">Circles</option>
<option value="CLF">Cliff</option>
<option value="CLFS">Cliffs</option>
<option value="CLB">Club</option>
<option value="CMN">Common</option>
<option value="COR">Corner</option>
<option value="CORS">Corners</option>
<option value="CRSE">Course</option>
<option value="CT">Court</option>
<option value="CV">Cove</option>
<option value="CVS">Coves</option>
<option value="CRK">Creek</option>
<option value="CRES">Crescent</option>
<option value="CRST">Crest</option>
<option value="XING">Crossing</option>
<option value="XRD">Crossroad</option>
<option value="CURV">Curve</option>
<option value="DV">Divide</option>
<option value="DR">Drive</option>
<option value="DRS">Drives</option>
<option value="EST">Estate</option>
<option value="ESTS">Estates</option>
<option value="EXPY">Expressway</option>
<option value="EXT">Extension</option>
<option value="EXTS">Extensions</option>
<option value="FRY">Ferry</option>
<option value="FLD">Field</option>
<option value="FLDS">Fields</option>
<option value="FRD">Ford</option>
<option value="FRST">Forest</option>
<option value="FRG">Forge</option>
<option value="FRGS">Forges</option>
<option value="FRK">Fork</option>
<option value="FRKS">Forks</option>
<option value="FT">Fort</option>
<option value="GDN">Garden</option>
<option value="GDNS">Gardens</option>
<option value="GTWY">Gateway</option>
<option value="GLN">Glen</option>
<option value="GLNS">Glens</option>
<option value="GRN">Green</option>
<option value="GRNS">Greens</option>
<option value="GRV">Grove</option>
<option value="GRVS">Groves</option>
<option value="HBR">Harbor</option>
<option value="HBRS">Harbors</option>
<option value="HVN">Haven</option>
<option value="HTS">Heights</option>
<option value="HWY">Highway</option>
<option value="HL">Hill</option>
<option value="HLS">Hills</option>
<option value="HOLW">Hollow</option>
<option value="INLT">Inlet</option>
<option value="IS">Island</option>
<option value="ISLE">Isle</option>
<option value="JCT">Junction</option>
<option value="JCTS">Junctions</option>
<option value="KY">Key</option>
<option value="KYS">Keys</option>
<option value="LK">Lake</option>
<option value="LKS">Lakes</option>
<option value="LAND">Land</option>
<option value="LNDG">Landing</option>
<option value="LN">Lane</option>
<option value="LGT">Light</option>
<option value="LGTS">Lights</option>
<option value="LF">Loaf</option>
<option value="LCK">Lock</option>
<option value="LCKS">Locks</option>
<option value="LDG">Lodge</option>
<option value="LOOP">Loop</option>
<option value="MALL">Mall</option>
<option value="MNR">Manor</option>
<option value="MNRS">Manors</option>
<option value="MDW">Meadow</option>
<option value="MDWS">Meadows</option>
<option value="ML">Mill</option>
<option value="MLS">Mills</option>
<option value="MSN">Mission</option>
<option value="MTWY">Motorway</option>
<option value="MT">Mount</option>
<option value="MTN">Mountain</option>
<option value="MTNS">Mountains</option>
<option value="NCK">Neck</option>
<option value="ORCH">Orchard</option>
<option value="OPAS">Overpass</option>
<option value="PARK">Park</option>
<option value="PKWY">Parkway</option>
<option value="PASS">Pass</option>
<option value="PSGE">Passage</option>
<option value="PATH">Path</option>
<option value="PIKE">Pike</option>
<option value="PNE">Pine</option>
<option value="PNES">Pines</option>
<option value="PL">Place</option>
<option value="PLN">Plain</option>
<option value="PLNS">Plains</option>
<option value="PLZ">Plaza</option>
<option value="PT">Point</option>
<option value="PTS">Points</option>
<option value="PRT">Port</option>
<option value="PRTS">Ports</option>
<option value="RAMP">Ramp</option>
<option value="RNCH">Ranch</option>
<option value="RST">Rest</option>
<option value="RDG">Ridge</option>
<option value="RDGS">Ridges</option>
<option value="RIV">River</option>
<option value="RD">Road</option>
<option value="RDS">Roads</option>
<option value="RTE">Route</option>
<option value="ROW">Row</option>
<option value="RUE">Rue</option>
<option value="RUN">Run</option>
<option value="SHL">Shoal</option>
<option value="SHLS">Shoals</option>
<option value="SHR">Shore</option>
<option value="SHRS">Shores</option>
<option value="SPG">Spring</option>
<option value="SPGS">Springs</option>
<option value="SPUR">Spur</option>
<option value="SQ">Square</option>
<option value="SQS">Squares</option>
<option value="STA">Station</option>
<option value="STRM">Stream</option>
<option selected="selected" value="ST">Street</option>
<option value="STS">Streets</option>
<option value="TER">Terrace</option>
<option value="TRWY">Throughway</option>
<option value="TRCE">Trace</option>
<option value="TRAK">Track</option>
<option value="TRFY">Trafficway</option>
<option value="TRL">Trail</option>
<option value="TUNL">Tunnel</option>
<option value="TPKE">Turnpike</option>
<option value="UPAS">Underpass</option>
<option value="UN">Union</option>
<option value="UNS">Unions</option>
<option value="VLY">Valley</option>
<option value="VLYS">Valleys</option>
<option value="VIA">Viaduct</option>
<option value="VW">View</option>
<option value="VWS">Views</option>
<option value="VLG">Village</option>
<option value="VLGS">Villages</option>
<option value="VL">Ville</option>
<option value="VIS">Vista</option>
<option value="WALK">Walk</option>
<option value="WALL">Wall</option>
<option value="WAY">Way</option>
<option value="WAYS">Ways</option>
<option value="WL">Well</option>
<option value="WLS">Wells</option>
</select>
</li>
<li>
<label>Street Direction</label>
<select name="ctl00$cphBody$ddStreetPrefix" id="ctl00_cphBody_ddStreetPrefix" aria-label="Street Direction">
<option value=""></option>
<option selected="selected" value="N">North</option>
<option value="S">South</option>
<option value="E">East</option>
<option value="W">West</option>
</select>
</li>
<li class="nospace">
<label>Unit #</label>
<input name="ctl00$cphBody$tbStreetUnit" type="text" maxlength="15" id="ctl00_cphBody_tbStreetUnit" aria-label="Unit Number">
</li>
<li>
<label>City</label>
<select name="ctl00$cphBody$ddCity" id="ctl00_cphBody_ddCity" title="Not Required" aria-label="City.">
<option selected="selected" value="">Not Required</option>
<option value="Jacksonville">Jacksonville</option>
<option value="Jacksonville Beach">Jacksonville Beach</option>
<option value="Atlantic Beach">Atlantic Beach</option>
<option value="Neptune Beach">Neptune Beach</option>
<option value="Baldwin">Baldwin</option>
<option value="Ponte Vedra">Ponte Vedra</option>
</select>
</li>
<li class="last">
<label>Zip</label>
<input name="ctl00$cphBody$tbZipCode" type="text" maxlength="10" id="ctl00_cphBody_tbZipCode" aria-label="Zip Code">
</li>
</ol>
</fieldset>
</div>
<div class="buttonBox">
<input type="submit" name="ctl00$cphBody$bSearch" value="Search" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;ctl00$cphBody$bSearch&quot;, &quot;&quot;, true, &quot;&quot;, &quot;Results.aspx&quot;, false, false))" id="ctl00_cphBody_bSearch" aria-label="Press for Search Results.">
<input type="button" id="bClear" value="Clear" onclick="ConfirmCancel();" aria-label="Press to Clear form">
</div>
</div>
<div id="searchRelated">
<div id="settings">
<fieldset>
<ol id="basic_settings">
<li>
<label>Search type</label>
<select name="ctl00$cphBody$ddSearchType" id="ctl00_cphBody_ddSearchType" aria-label="Search type">
<option selected="selected" value="All">Match All</option>
<option value="Any">Match Any</option>
</select>
</li>
<li class="last">
<label>Results per page</label>
<select name="ctl00$cphBody$ddResultsPerPage" id="ctl00_cphBody_ddResultsPerPage" aria-label="Results per page">
<option selected="selected" value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="200">200</option>
<option value="500">500</option>
<option value="1000">1000</option>
<option value="2000">2000</option>
<option value="5000">5000</option>
<option value="10000">10000</option>
</select>
</li>
</ol>
</fieldset>
<fieldset>
<span><b><input id="ctl00_cphBody_idBasicAddAddressToExport" type="checkbox" name="ctl00$cphBody$idBasicAddAddressToExport"><label for="ctl00_cphBody_idBasicAddAddressToExport">Include Mailing Address to Export Results</label></b></span>
</fieldset>
<fieldset>
<a id="ctl00_cphBody_idTutorial" title="Click here for Tutorial" role="link" href="http://www.coj.net/departments/property-appraiser/search-tutorial.aspx" target="_blank">Click here for Tutorial</a>
</fieldset>
</div>
<div id="help">
<a href="#" style="text-decoration:none">
<p>
If you dont know the exact address, you may search by partial address or street names.
</p>
<p>
By default, "Match all" will return properties that only include all your search criteria. To restrict a search further, include more data. However, since selection criteria must match the tax roll data exactly, providing <strong>less criteria often yields the best results</strong>.
</p>
<p>
To find properties that include any of your search criteria, select the search type "Match any."
</p>
</a>
</div>
<div id="disclaimer">
<a href="#" style="text-decoration:none">
<p id="includedArea" style="color:#000000;">
The Real Estate Parcel Information &amp; Parcel Description data displayed is updated daily. The database consists of all real estate parcels in Duval County, including <em>Jacksonville</em>, <em>Jacksonville Beach</em>, <em>Atlantic Beach</em>, <em>Neptune Beach</em> and the <em>City of Baldwin</em>.
</p>
</a><p id="includedArea12" style="color:#000000;"><a href="#" style="text-decoration:none">
Values displayed reflect those from the last certified Tax Roll (October
<span id="ctl00_cphBody_lblCertifiedYear" alt="Last Years Changes"></span>) and current "In Progress" values, which are subject to frequent change. These numbers can and do change frequently due to the valuation process. They should not be used for any financial or business calculations. The certified values are the actual values which your last property tax bill was based on. Use of this online database is at your own risk. Please see the posted </a><a href="http://www.coj.net/Departments/Property-Appraiser/Site-Policy.aspx" alt="site policy">site policy</a>.
</p>
</div>
</div>
</div>
</div>
</div>
<!-- footer -->
<div id="ft">
<div id="tools">
<h2>Tools</h2>
<ul>
<li>
<a href="mailto:paadmin@coj.net?subject=Re:Search Problems/Feedback" aria-label="Report Search Problems">Report Search Problems</a>
</li>
<li>
<a id="ctl00_linkPropertyAppraiserCodes" alt="Property Appraiser Codes" href="../Codes.aspx">Property Appraiser Codes</a>
</li>
</ul>
</div>
<div id="questions">
<h2>Have any questions?</h2>
<ul>
<li><a id="ctl00_tcContactUs" title="By clicking this link, you will exit the Property Appraisers Web site and be directed to the Tax Collectors site." href="../Leaving.aspx?Destination=TC">Contact the Tax Collector about a payment</a>.</li>
<li><a href="mailto:paadmin@coj.net" alt="Contact the Property Appraiser">Contact the Property Appraiser about an assessment</a>.</li>
</ul>
<span id="ctl00_lblVersion" class="versionNumber" alt="Version">v1.18.00 - 29</span>
</div>
</div>
</div>
</form>
<div id="tooltip" style="display: none;"><h3>Tooltip</h3><div class="body"></div><div class="url"></div></div></body></html>
+457
View File
@@ -0,0 +1,457 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><head><meta http-equiv="X-UA-Compatible" content="IE=IE8">
<script type="text/javascript" src="/javascript/jquery/jquery.js"></script>
<script type="text/javascript" src="/javascript/plugins.js"></script>
<script type="text/javascript" src="/javascript/actions.js"></script>
<title>
Property Appraiser - Basic Search
</title><link rel="stylesheet" type="text/css" href="../style/base.css" media="screen"><link rel="stylesheet" type="text/css" href="../style/print.css" media="print">
<script type="text/javascript">
function openWin(re,sec) {
var url = "../Feedback.aspx?RE=" + re + "&Section=" + sec
window.open(url,'Feedback','WIDTH=700,HEIGHT=800,toolbar=0,location=0,directories=0,status=0,menubar=0,scrollbars=1,resizable=0,screenX=200,screenY=0,left=100,top=0,address=0')
}
</script>
<link href="../App_Themes/coj/coj.css" type="text/css" rel="stylesheet"><link href="../App_Themes/coj/print.css" type="text/css" rel="stylesheet"></head>
<body>
<form name="aspnetForm" method="post" action="./Search.aspx" id="aspnetForm" role="document">
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTA5MjcxODAyMmQYAQUeX19Db250cm9sc1JlcXVpcmVQb3N0QmFja0tleV9fFgEFJ2N0bDAwJGNwaEJvZHkkaWRCYXNpY0FkZEFkZHJlc3NUb0V4cG9ydJubiT3IBP3XCsXcbveK/2h6Ke0zMUEhJsYCAiiy1FYa">
<script type="text/javascript">
//<![CDATA[
function REJump()
{
if (document.forms(0).ctl00_cphBody_tbRE6.value.length == 6)
document.forms(0).ctl00_cphBody_tbRE4.focus();
}
function ConfirmCancel()
{
if (confirm('Are you sure you want to clear the form? Click OK to clear it.'))
{
document.forms(0).ctl00_cphBody_tbRE6.value = '';
document.forms(0).ctl00_cphBody_tbRE4.value = '';
document.forms(0).ctl00_cphBody_tbName.value = '';
document.forms(0).ctl00_cphBody_tbStreetNumber.value = '';
document.forms(0).ctl00_cphBody_tbStreetName.value = '';
document.forms(0).ctl00_cphBody_ddStreetSuffix.selectedIndex = 0;
document.forms(0).ctl00_cphBody_ddStreetPrefix.selectedIndex = 0;
document.forms(0).ctl00_cphBody_tbStreetUnit.value = '';
document.forms(0).ctl00_cphBody_ddCity.selectedIndex = 0;
document.forms(0).ctl00_cphBody_tbZipCode.value = '';
}
}
//]]>
</script>
<input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="B39370EB">
<input type="hidden" name="__PREVIOUSPAGE" id="__PREVIOUSPAGE" value="iISI1LBBqgYntISnoN5f_-W6u77ij4oWun-4ULIv0ZBpaEgEucHWqOVWyI5K0eSRM62-YxQ8lZCowWk9ICL9MOQnR0C3No7Ry3hp5rDikSo1">
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEdAM0BC5SUBC8UxXyQLivswS7rf4MDkLbyYkWc3vtraYVsPN6uyjIIcmg5TjRye0AuEmCnORWfHbdceBmXHzXHx4L3xMOe3oYywsxNOKF5jWlKO8vUK+aPSOTaS1Q7zBQ1xlUfDfM9q4hvFbzsI7Prfu73qPZlDHfY8Y1TStdbZ21+N32Y+nW0IrYInsR0RjOvaYz7d0OJbgCd7PtU3nHegAR/4MiNDVzLurPjY2F4IoYR/KZdEtbqL/8fog+w9eJU9dj8uIWc1D+3Aj6AplBp5Wm0TToplEm4ofqAHE3/bLYXlF4kCGb99grFfLZQ7hBi/UUS6vuMP6eaH2DT8VtyLtzx18WH/cLk6KlTZver0YnXCJ45Wb9yTHuwpdL4QFY2t7ikUZ3yWyhaOyg1BwkbrTmGu+j5xVtwlPtBUVqftr+l74i7wmODrur7ksIZf7RK8h/3pK5bGapV9IrIXSInDdwc2C37VdGqLxaCrX8giRPPq+JPzRsPMuH3olBsbqXuH75EEjRkHfATaE+Wmz9AXbFzT6QnfXXu/NL7EucQWH+8gZSsFg/0sbiy+EIg3KZIRwQoJLw+FFAZdJLJlrqq4dWGU22BS/9Iwy1t1L/PeNr0yKf0ABfKr09AtNfJbIG3GDMSZK3Qs/ODIeo7+5rI/wv2HCYshOnuhviMz5UwE2K3WfrRsgq8YNor6DWEOif2uJwL8HmnOxcb0SWBYeyKJ+4L/KXCV9Lh11Tik2EcM5Bs7Tor9fNk4FuN5bATHGedW8vYugB1pyw1uSAug/GwKsuoFVkJxdlfGLWk4M+u8p7gZxjY9JHtWZuC0Imk4fgEeF+OjrQfw8udHQetnrsBGMpyVNv5MkOc83R/1yy/Sxw9UqyF8C3Bksr/c/0GScYl8Wo/Ig/xRyOQ1aVe/HDsRLvrAU1Wvd4HPVtoZT8eHSmPMMYb5+Mlb0zFuC/f9rSXoPp9427aCG3UXoPskrxRcIfR5R+AOujK3JMoooggyPiI67UjzR97EVFJy5NHD+kwVK+RExCb+b9xLMrc40z4bcedEY5IxBE/oGFozUkFDGCGkVk+FUSO3r4xidQD1ehL30yubBfn7iXXOLiFkQl4PBzwJ9L8FGIuBvsYn9TlHlGfJ9k8LBKzlBCuarPwPKgL/H/jsBBu4Tbgg21BDlWsmeg4WIRL2chmcz38uyuUsaEMhTX4ESR4IH+0mMpmzzuAiNEdd5wMAir2SWuXstIrRxt4jFab83CZimjypwtC1Y54yyOliqXkIWAevPZmMN0NyVc1zLlHkL4mZd2D2IrhNg+xb+CqxqgkHo0iwVkjDfKoEaD5fHZvCGO03wCsV9eInC4R+z2pCzG36sSGh2I8xebrt5huHCQQk/RjuRYGOjSTHc/4aPkz/b9Ys5S3l7kVDQTJIj8jiLRkz9fsuH0Lcs0RgrPfjEZOH8ucS505zg8RbJpWsk9xtkt2qcSCPDREOfaHOkPLsAOz7WZs4Nwl77/tz85oot8qR8pSQUdpzAJlaORnzzx/JH8oVYD39eWEr5eLPDz0BVQnner1H2wuLCMehkRXcCXn307Xo+EeMTXtM7Sxoc9Lt+/ongWTFA5p4RwIrN5+44BiKC/9k+0gn8+w4plP0cCFJTu+lymaDKiAXuzXUga+I8zewFD7Dfts3qByKv6lnnlvrf8HadbhbI82cWfCqFyVlUw+8p61CbqimrBphfQErQ+avn6+W0CGZpdO2B3+wsT1zjhe7AAzF/4BXcaLKJTX6WYtI9SCCnVod80eePvPg0izvT/vaSWGPYsf5Lj/LXtxzQ3ISa/pa45OmBu79M/H/PDl+bbjNm8ImYeYt8dLJLaHdW2FVdTnekru0EPacFhHT+CT2SKzV/E+2de6+V1aTFJoIyIQ5jMlhcYnNzCRnaG7Yjk/XSZtAkeBkub5a7k50zhCSzwSOzFc3CtLwDX/Pepbm1WGQfe4UZhWSzCbptVo4xy09qjpswIN/+jhZ0EWlilDufcdaLweCRoanaDaI0oySBz4e/d7u6seWOkmRH53It8ohHFP6ZxCS+NcNFzTOnM2ZocYJaCz1rWrLj3tGFp4K23eMS2XnQphKalbNPQEJLw93Q3o5pl+/mV6HWNEl67du+XS1PuBNNmhwxCH5gVDLYGM8/yPBJmfiuwySCvN/OLMtq9tJU67quf6xqCyNy80egpjNp5l1enc+vjZYHIdQrevV78nAuzwK8fm8Q+qx9jmFivFU6TcdE567Lx5ZngPH4jhNxkM1sdhnA1rcU72DT/MIV5a7Wqn98hXyS0GeRukpGypBNRBzIUB3g8wX8aLzvu00Ii9cwdSN39YInMb//be9g8mRFBWgLkuHkFq/1C3GI1vYquVYpZz2SrjrZulkKyXTZ6K/aezxNR98Vyp4eg+EobJFK+ygW22aiUfWLBNhS00S51I4uoZmXNNWY48CmgVdjR1ibZZbOUO6dmGxNnXW+ChEbWoJxkTBuk6H287p/deu3r0facQgKjK6aJCBml2G3S+Yi+qXshha0tyuZReWSD+nWmz/mo9CeUImJ6nrDb2/gR6dK+mNijc2yHzEip5hSxX8u+Up4ZEkA1EPUgBPWn4K2bjXcFbSyBwVcSAWOG4vTyPEwlUPmSJec0jn7wrDwiCHR5hhnDcfzBrv9+hLV/7hh/jF4ruB7XMkFXsatmf0XOVATgF5gG2u6xPG6/l4dKxRUmLa+rvs+7akCG1H9Db3w3TYI3pLgO0BpkgvBugS2ySvkyTZPZf/HYMNqxvi23vW0a7gmFFb28oQ4vrNwY/zCv4IcZmViAgo6zEkUvyedKW4Ubk/kw/6J/w+a6/kgV7txF19hR9LdcnJ023dOrUe/k4LKeuBunrMJRz4CepRvWmoI4jiBPu70SV6Y1ObjCWOQzaAOh7nemeLEXWIQKRUs0PHI98Kmq0I5OX0mmAGqC/GYjrrwrpdVIPNMuF3ltZ65UkHWhZt3SIFkK2vl4HDvlIa/gFC9pEk9xLocm2wQAq/HrhIonUERavuO/MkX/qkpweDg0Yo092Gwp4c86dBjtMM9x1LWx2Q32ttrBG0/jgTDDjVbb2gN8d4ZKGNOUXRah1fVS/pdW8RIJiStq5h5qwMMLGKruV6d1skHW7mPw0RDlq0lveCZaMcVNiT8sb/810XBYG+ouKf1SjrRk7TMHSw4uzDCV2qRQCHUVMJBYha8C9q6bvBukv4SbY8Pu7Ae337jq/lnvkF/+cYXaw7scCER+2V68tyrMffJQZ6Fo/U++8zP4Eg7dLVgW323/RQ+Llh70vvUwHZZJBH/gXF3CW62J74ukPZyFt/OTqotyvbGQyZGGIGdtIsxm1Ua7es0NRc+f9Xwfi4+3YF+di2y8/p10i+sgJ5oSpzFhHA+254qn0oK2GGkB1OzlxkD+rfSNl1cUUIxm5pK9xmIayS/HZSKjtF1dhlejtgjG0qb1/tpNaLUmNry8fyhP+HhnXcygoBO49mHxLuG06pYlUzrrlPoD1p9fUXIEXNO2fqfSGR8VoSrogXMvxlFyh4KFVkXrf2fe/7M1xLiKGNm0NcNzvEmxvfwqgPv5X6y/HFucWJLvZPkpmsbS68K+lWlSq3alP2U+flitcPThv9oo8nt4y6IVgFgD6Fydy4B2OuRDGxBJRHz0MwQ6QZMAr/ra0i1IVBC060sdlBVvDdKokS3jfKyrF62hrGxgO1Sy0V1W5/4/JObTe5u9FRoDAHdj6bWhtMXyc80h/p2YMPlORgjfdcM3cYJI15M5jagBq/MModdac4T4cVr8K/X2yIzUMg+S4MY05KMvUjoZMJwax7PtfhAlx/8UnLxNCnJrFdlKUeEfYEooRjiBbppcvZnnyoqldq2nPJ5UnyIkL4nZxWsE0ENio/hLWRuMk086L3zLbTa9Ke6ITiDRFfCE9oDYgeGawh+z0ZgdFi9tsZ8CSU6ioTuOn9ZHeFR6G2QpDsSGHgbGw861Z2ldRrBQ6SLOJSJQnzL4LOppPqudCotKCepE8G/vcBvNz4MfgGYVuSX7meTLQerxy+rTUMSK4q98WkXxuAHxrFWJH0w4pInI5Savcuiv0p97wjOL/o2Zl+TU+5uCgfZxjEt71ibExzIGASvU/OoQjdzZQQ31GkS6tEedmdHvILHPzDy914dsMQicRJ2B5dY64swcb1qUdhK/drPOtjUnqOYpCZrOWXNQK8AHCM13bXYF2aajpy2NcB0Q5NHQjtDHlwebQQER30u149QumSu+saGF9P4HR9cR61gmv52w4T/VvoY/hsGYjKqZBtqN04RKRyvf1XpcylsVmp12wIZA5qW1VVsZQTvUV0x/X8yF0u3w6Op2rFwIVSvWaP2YKZm7Hh0+J0ZEzG2S6fa/E/KgH//YkQJSp8Hbd0WyevMcizXbtQ3boAFJuHj/x">
<div id="doc3" class="yui-t7">
<!-- header -->
<a href="http://www.coj.net/Departments/Property+Appraiser/default.htm" id="ctl00_home" style="width:1000px;height:500px" title="Home">
<div id="hd" style="height:260px" role="IMG" title="Joyce Morgan Header" alt="City of Jacksonville Florida United States of America Property Appraiser Office Duval County Florida Seal of City of Jacksonville Photo of Jacksonville Waterfront Photo of Jerry Holland Jerry Holland Property Appraiser Fair and Accurate For All">
<div id="cojnetbits">
<!-- breadcrumbs -->
<div id="bc">
<ol id="cphBreadcrumbs">
<li id="last">
</li>
</ol>
</div>
<!-- coj.net search -->
<div id="searchBox"></div>
</div>
</div>
</a>
<!-- body -->
<div id="bd">
<!-- navigation -->
<div class="yui-g">
<div id="navWrapper">
<div id="navContainer">
<ol id="nav">
<li>
<a id="ctl00_menu_ctl00_navItem" class="active" href="/Basic/Search.aspx">Basic Search</a>
</li>
<li>
<a id="ctl00_menu_ctl01_navItem" href="/Sales/Search.aspx">Advanced Search</a>
</li>
<li>
<a id="ctl00_menu_ctl02_navItem" class="last" href="/Tangible/Search.aspx">Tangible Search</a>
</li>
</ol>
</div>
</div>
</div>
<!-- body content -->
<div class="yui-g" id="content">
<div id="searchWrapper">
<div id="searchForm">
<div id="searchInstructions" class="">
<img src="../images/tip.gif" alt="Tool Tip">
<em>For best results, search by RE# or Property Owner Information or by Property Address.</em>
</div>
<h2>Real Estate Number</h2>
<div class="formBox">
<fieldset>
<ol id="basic_re">
<li class="single">
<label>RE #</label>
<input name="ctl00$cphBody$tbRE6" type="text" maxlength="6" id="ctl00_cphBody_tbRE6" title="First 6 digits of Real Estate Number" onkeyup="REJump();">
<span>-</span>
<input name="ctl00$cphBody$tbRE4" type="text" maxlength="4" id="ctl00_cphBody_tbRE4" title="Last 4 digits of Real Estate Number">
</li>
</ol>
</fieldset>
</div>
<h2>Property Owner Information</h2>
<div class="formBox">
<fieldset>
<ol id="basic_owner">
<li id="searchname" class="first single">
<label>Owner or Business Name</label>
<input name="ctl00$cphBody$tbName" type="text" maxlength="50" id="ctl00_cphBody_tbName" title="Enter Owner or Business Name, Last name first">
</li>
<li class="searchex single">
<p id="first">Last name only or Last First (e.g., Doe John).</p>
<p>To search for a spouse's name, enter: last, first.</p>
</li>
</ol>
</fieldset>
</div>
<h2>Property Address</h2>
<div class="formBox">
<fieldset>
<ol id="basic_address">
<li class="first">
<label>Street #</label>
<input name="ctl00$cphBody$tbStreetNumber" type="text" maxlength="9" id="ctl00_cphBody_tbStreetNumber" title="Enter the street number">
</li>
<li class="nospace">
<label for="tbStreetName">Street Name</label>
<input name="ctl00$cphBody$tbStreetName" type="text" maxlength="50" id="ctl00_cphBody_tbStreetName" title="Enter the street name.">
</li>
<li>
<label>Street Type</label>
<select name="ctl00$cphBody$ddStreetSuffix" id="ctl00_cphBody_ddStreetSuffix" aria-label="Street Type">
<option value=""></option>
<option value="ALY">Alley</option>
<option value="ANX">Annex</option>
<option value="AVE">Avenue</option>
<option value="BCH">Beach</option>
<option value="BND">Bend</option>
<option value="BLF">Bluff</option>
<option value="BLFS">Bluffs</option>
<option value="BLVD">Boulevard</option>
<option value="BR">Branch</option>
<option value="BRG">Bridge</option>
<option value="BRK">Brook</option>
<option value="BRKS">Brooks</option>
<option value="BYP">Bypass</option>
<option value="CP">Camp</option>
<option value="CPE">Cape</option>
<option value="CSWY">Causeway</option>
<option value="CTRS">Centers</option>
<option value="CIR">Circle</option>
<option value="CIRS">Circles</option>
<option value="CLF">Cliff</option>
<option value="CLFS">Cliffs</option>
<option value="CLB">Club</option>
<option value="CMN">Common</option>
<option value="COR">Corner</option>
<option value="CORS">Corners</option>
<option value="CRSE">Course</option>
<option value="CT">Court</option>
<option value="CV">Cove</option>
<option value="CVS">Coves</option>
<option value="CRK">Creek</option>
<option value="CRES">Crescent</option>
<option value="CRST">Crest</option>
<option value="XING">Crossing</option>
<option value="XRD">Crossroad</option>
<option value="CURV">Curve</option>
<option value="DV">Divide</option>
<option value="DR">Drive</option>
<option value="DRS">Drives</option>
<option value="EST">Estate</option>
<option value="ESTS">Estates</option>
<option value="EXPY">Expressway</option>
<option value="EXT">Extension</option>
<option value="EXTS">Extensions</option>
<option value="FRY">Ferry</option>
<option value="FLD">Field</option>
<option value="FLDS">Fields</option>
<option value="FRD">Ford</option>
<option value="FRST">Forest</option>
<option value="FRG">Forge</option>
<option value="FRGS">Forges</option>
<option value="FRK">Fork</option>
<option value="FRKS">Forks</option>
<option value="FT">Fort</option>
<option value="GDN">Garden</option>
<option value="GDNS">Gardens</option>
<option value="GTWY">Gateway</option>
<option value="GLN">Glen</option>
<option value="GLNS">Glens</option>
<option value="GRN">Green</option>
<option value="GRNS">Greens</option>
<option value="GRV">Grove</option>
<option value="GRVS">Groves</option>
<option value="HBR">Harbor</option>
<option value="HBRS">Harbors</option>
<option value="HVN">Haven</option>
<option value="HTS">Heights</option>
<option value="HWY">Highway</option>
<option value="HL">Hill</option>
<option value="HLS">Hills</option>
<option value="HOLW">Hollow</option>
<option value="INLT">Inlet</option>
<option value="IS">Island</option>
<option value="ISLE">Isle</option>
<option value="JCT">Junction</option>
<option value="JCTS">Junctions</option>
<option value="KY">Key</option>
<option value="KYS">Keys</option>
<option value="LK">Lake</option>
<option value="LKS">Lakes</option>
<option value="LAND">Land</option>
<option value="LNDG">Landing</option>
<option value="LN">Lane</option>
<option value="LGT">Light</option>
<option value="LGTS">Lights</option>
<option value="LF">Loaf</option>
<option value="LCK">Lock</option>
<option value="LCKS">Locks</option>
<option value="LDG">Lodge</option>
<option value="LOOP">Loop</option>
<option value="MALL">Mall</option>
<option value="MNR">Manor</option>
<option value="MNRS">Manors</option>
<option value="MDW">Meadow</option>
<option value="MDWS">Meadows</option>
<option value="ML">Mill</option>
<option value="MLS">Mills</option>
<option value="MSN">Mission</option>
<option value="MTWY">Motorway</option>
<option value="MT">Mount</option>
<option value="MTN">Mountain</option>
<option value="MTNS">Mountains</option>
<option value="NCK">Neck</option>
<option value="ORCH">Orchard</option>
<option value="OPAS">Overpass</option>
<option value="PARK">Park</option>
<option value="PKWY">Parkway</option>
<option value="PASS">Pass</option>
<option value="PSGE">Passage</option>
<option value="PATH">Path</option>
<option value="PIKE">Pike</option>
<option value="PNE">Pine</option>
<option value="PNES">Pines</option>
<option value="PL">Place</option>
<option value="PLN">Plain</option>
<option value="PLNS">Plains</option>
<option value="PLZ">Plaza</option>
<option value="PT">Point</option>
<option value="PTS">Points</option>
<option value="PRT">Port</option>
<option value="PRTS">Ports</option>
<option value="RAMP">Ramp</option>
<option value="RNCH">Ranch</option>
<option value="RST">Rest</option>
<option value="RDG">Ridge</option>
<option value="RDGS">Ridges</option>
<option value="RIV">River</option>
<option value="RD">Road</option>
<option value="RDS">Roads</option>
<option value="RTE">Route</option>
<option value="ROW">Row</option>
<option value="RUE">Rue</option>
<option value="RUN">Run</option>
<option value="SHL">Shoal</option>
<option value="SHLS">Shoals</option>
<option value="SHR">Shore</option>
<option value="SHRS">Shores</option>
<option value="SPG">Spring</option>
<option value="SPGS">Springs</option>
<option value="SPUR">Spur</option>
<option value="SQ">Square</option>
<option value="SQS">Squares</option>
<option value="STA">Station</option>
<option value="STRM">Stream</option>
<option value="ST">Street</option>
<option value="STS">Streets</option>
<option value="TER">Terrace</option>
<option value="TRWY">Throughway</option>
<option value="TRCE">Trace</option>
<option value="TRAK">Track</option>
<option value="TRFY">Trafficway</option>
<option value="TRL">Trail</option>
<option value="TUNL">Tunnel</option>
<option value="TPKE">Turnpike</option>
<option value="UPAS">Underpass</option>
<option value="UN">Union</option>
<option value="UNS">Unions</option>
<option value="VLY">Valley</option>
<option value="VLYS">Valleys</option>
<option value="VIA">Viaduct</option>
<option value="VW">View</option>
<option value="VWS">Views</option>
<option value="VLG">Village</option>
<option value="VLGS">Villages</option>
<option value="VL">Ville</option>
<option value="VIS">Vista</option>
<option value="WALK">Walk</option>
<option value="WALL">Wall</option>
<option value="WAY">Way</option>
<option value="WAYS">Ways</option>
<option value="WL">Well</option>
<option value="WLS">Wells</option>
</select>
</li>
<li>
<label>Street Direction</label>
<select name="ctl00$cphBody$ddStreetPrefix" id="ctl00_cphBody_ddStreetPrefix" aria-label="Street Direction">
<option value=""></option>
<option value="N">North</option>
<option value="S">South</option>
<option value="E">East</option>
<option value="W">West</option>
</select>
</li>
<li class="nospace">
<label>Unit #</label>
<input name="ctl00$cphBody$tbStreetUnit" type="text" maxlength="15" id="ctl00_cphBody_tbStreetUnit" aria-label="Unit Number">
</li>
<li>
<label>City</label>
<select name="ctl00$cphBody$ddCity" id="ctl00_cphBody_ddCity" title="Not Required" aria-label="City.">
<option value="">Not Required</option>
<option value="Jacksonville">Jacksonville</option>
<option value="Jacksonville Beach">Jacksonville Beach</option>
<option value="Atlantic Beach">Atlantic Beach</option>
<option value="Neptune Beach">Neptune Beach</option>
<option value="Baldwin">Baldwin</option>
<option value="Ponte Vedra">Ponte Vedra</option>
</select>
</li>
<li class="last">
<label>Zip</label>
<input name="ctl00$cphBody$tbZipCode" type="text" maxlength="10" id="ctl00_cphBody_tbZipCode" aria-label="Zip Code">
</li>
</ol>
</fieldset>
</div>
<div class="buttonBox">
<input type="submit" name="ctl00$cphBody$bSearch" value="Search" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;ctl00$cphBody$bSearch&quot;, &quot;&quot;, true, &quot;&quot;, &quot;Results.aspx&quot;, false, false))" id="ctl00_cphBody_bSearch" aria-label="Press for Search Results.">
<input type="button" id="bClear" value="Clear" onclick="ConfirmCancel();" aria-label="Press to Clear form">
</div>
</div>
<div id="searchRelated">
<div id="settings">
<fieldset>
<ol id="basic_settings">
<li>
<label>Search type</label>
<select name="ctl00$cphBody$ddSearchType" id="ctl00_cphBody_ddSearchType" aria-label="Search type">
<option value="All">Match All</option>
<option value="Any">Match Any</option>
</select>
</li>
<li class="last">
<label>Results per page</label>
<select name="ctl00$cphBody$ddResultsPerPage" id="ctl00_cphBody_ddResultsPerPage" aria-label="Results per page">
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="200">200</option>
<option value="500">500</option>
<option value="1000">1000</option>
<option value="2000">2000</option>
<option value="5000">5000</option>
<option value="10000">10000</option>
</select>
</li>
</ol>
</fieldset>
<fieldset>
<span><b><input id="ctl00_cphBody_idBasicAddAddressToExport" type="checkbox" name="ctl00$cphBody$idBasicAddAddressToExport"><label for="ctl00_cphBody_idBasicAddAddressToExport">Include Mailing Address to Export Results</label></b></span>
</fieldset>
<fieldset>
<a id="ctl00_cphBody_idTutorial" title="Click here for Tutorial" role="link" href="http://www.coj.net/departments/property-appraiser/search-tutorial.aspx" target="_blank">Click here for Tutorial</a>
</fieldset>
</div>
<div id="help">
<a href="#" style="text-decoration:none">
<p>
If you dont know the exact address, you may search by partial address or street names.
</p>
<p>
By default, "Match all" will return properties that only include all your search criteria. To restrict a search further, include more data. However, since selection criteria must match the tax roll data exactly, providing <strong>less criteria often yields the best results</strong>.
</p>
<p>
To find properties that include any of your search criteria, select the search type "Match any."
</p>
</a>
</div>
<div id="disclaimer">
<a href="#" style="text-decoration:none">
<p id="includedArea" style="color:#000000;">
The Real Estate Parcel Information &amp; Parcel Description data displayed is updated daily. The database consists of all real estate parcels in Duval County, including <em>Jacksonville</em>, <em>Jacksonville Beach</em>, <em>Atlantic Beach</em>, <em>Neptune Beach</em> and the <em>City of Baldwin</em>.
</p>
</a><p id="includedArea12" style="color:#000000;"><a href="#" style="text-decoration:none">
Values displayed reflect those from the last certified Tax Roll (October
<span id="ctl00_cphBody_lblCertifiedYear" alt="Last Years Changes">2025</span>) and current "In Progress" values, which are subject to frequent change. These numbers can and do change frequently due to the valuation process. They should not be used for any financial or business calculations. The certified values are the actual values which your last property tax bill was based on. Use of this online database is at your own risk. Please see the posted </a><a href="http://www.coj.net/Departments/Property-Appraiser/Site-Policy.aspx" alt="site policy">site policy</a>.
</p>
</div>
</div>
</div>
</div>
</div>
<!-- footer -->
<div id="ft">
<div id="tools">
<h2>Tools</h2>
<ul>
<li>
<a href="mailto:paadmin@coj.net?subject=Re:Search Problems/Feedback" aria-label="Report Search Problems">Report Search Problems</a>
</li>
<li>
<a id="ctl00_linkPropertyAppraiserCodes" alt="Property Appraiser Codes" href="../Codes.aspx">Property Appraiser Codes</a>
</li>
</ul>
</div>
<div id="questions">
<h2>Have any questions?</h2>
<ul>
<li><a id="ctl00_tcContactUs" title="By clicking this link, you will exit the Property Appraisers Web site and be directed to the Tax Collectors site." href="../Leaving.aspx?Destination=TC">Contact the Tax Collector about a payment</a>.</li>
<li><a href="mailto:paadmin@coj.net" alt="Contact the Property Appraiser">Contact the Property Appraiser about an assessment</a>.</li>
</ul>
<span id="ctl00_lblVersion" class="versionNumber" alt="Version">v1.18.00 - 28</span>
</div>
</div>
</div>
</form>
</body></html>
+229
View File
@@ -0,0 +1,229 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><head><meta http-equiv="X-UA-Compatible" content="IE=IE8">
<script type="text/javascript" src="/javascript/jquery/jquery.js"></script>
<script type="text/javascript" src="/javascript/plugins.js"></script>
<script type="text/javascript" src="/javascript/actions.js"></script>
<title>
Property Appraiser - Basic Search Results
</title><link rel="stylesheet" type="text/css" href="../style/base.css" media="screen"><link rel="stylesheet" type="text/css" href="../style/print.css" media="print">
<script type="text/javascript">
function openWin(re,sec) {
var url = "../Feedback.aspx?RE=" + re + "&Section=" + sec
window.open(url,'Feedback','WIDTH=700,HEIGHT=800,toolbar=0,location=0,directories=0,status=0,menubar=0,scrollbars=1,resizable=0,screenX=200,screenY=0,left=100,top=0,address=0')
}
</script>
<link href="../App_Themes/coj/coj.css" type="text/css" rel="stylesheet"><link href="../App_Themes/coj/print.css" type="text/css" rel="stylesheet"></head>
<body>
<form name="aspnetForm" method="post" action="./Results.aspx" id="aspnetForm" role="document">
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTI3Mzk4NTc2Ng9kFgJmD2QWBAIBD2QWAgIBDxUDHC9qYXZhc2NyaXB0L2pxdWVyeS9qcXVlcnkuanMWL2phdmFzY3JpcHQvcGx1Z2lucy5qcxYvamF2YXNjcmlwdC9hY3Rpb25zLmpzZAIDD2QWBgIDDxYCHgtfIUl0ZW1Db3VudAIDFgZmD2QWAgIBDw8WAh4LTmF2aWdhdGVVcmwFEi9CYXNpYy9TZWFyY2guYXNweGQWAmYPFQEMQmFzaWMgU2VhcmNoZAIBD2QWAgIBDw8WAh8BBRIvU2FsZXMvU2VhcmNoLmFzcHhkFgJmDxUBD0FkdmFuY2VkIFNlYXJjaGQCAg9kFgICAQ8PFgIfAQUVL1RhbmdpYmxlL1NlYXJjaC5hc3B4ZBYCZg8VAQ9UYW5naWJsZSBTZWFyY2hkAgcPZBYMAgEPDxYCHgRUZXh0BQEwZGQCBA8QZGQWAWZkAgYPEGRkFgFmZAIIDxBkZBYBZmQCEA8PFgIfAgUqQ2xpY2sgdGhlIFJFICMgZm9yIHRoZSBwcm9wZXJ0eSdzIGRldGFpbHMuZGQCHA8WAh4HVmlzaWJsZWgWBgIBDw8WBB4ISW1hZ2VVcmwFHX4vaW1hZ2VzL2d2L3ByZXZfZGlzYWJsZWQuZ2lmHgdFbmFibGVkaGRkAgMPEGQPFgFmFgEQBQExBQExZxYBZmQCBQ8PFgQfBAUdfi9pbWFnZXMvZ3YvbmV4dF9kaXNhYmxlZC5naWYfBWhkZAIPDw8WAh8CBQ12MS4xOC4wMCAtIDI5ZGQYAQUZY3RsMDAkY3BoQm9keSRncmlkUmVzdWx0cw88KwAMAQhmZAcV9loc0lIm9DuPLEgz9Y64P4/AFAQcy6TmS5IHsQp6">
<input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="8CEEF678">
<input type="hidden" name="__PREVIOUSPAGE" id="__PREVIOUSPAGE" value="tRRSLcTxr1l7_MV3AmRwLmczxf42PYsP1oVW4IKQ1GC_5M28cs1ZgqvdN9HzKrBElS-x0Zo0Uf6W1Ze68BDza0yJ4gzkPd2CFoYs4agOTD01">
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEdADt3vkWWtL1XuKYblwZzicvGeLpdgSzN1b/W9sd7O8Ho/77FT1ZL0wk3pZQxIkvJmj8MJiZgJoee1wcoCWBwhc29urbgNrM4mMJoPnvlH22RMstuVPZ6IxYYv5iFwRtKEmIwKsrqSDe3Po7aqCE6LUP9MytzR1bBX5XzTc0aJ0wrXDqNu1MtAS6n+3Ew7xsMliUUc0UDLXe2qFXxoShPjpt2bUNrcnF4WUhGgyEixWipYxSCK7xWb77rQTjxkYRzvY6s2HEAsuOx9eg/GO0gZ9sUqjwPeD0Xe0SZsh/+eg+6ElraoUk5jt6OUNibyo+ZyAZRqrV9vc67Ex4yQhRxqfEES6os7Flg6pa+TE5sRMZds4t+NBqC2UjgB7UCmxWsaeNHuAb58JA3YjMh5jB8/fLgLhZIZUjuQKe0E02ZOrJatm8i4bcCNQuiXqb3ebuXQUbPU+DZr8LKMwFFF5zzDTJ3IcOUhxcn3XLQYVCGYNi3xUmKjBmR1fmYKQwqIS9JCUbK1HcR2M2XkYMYAOOXfBuQ62yGOXTp2nCbiKH1lncOfHGWk0m3GAfbRwyLzMu/cTF//C8PpfiIXhfeNix3Gry4yBUA3c90gykuH0EjlC0LklyZKdCrazjI4PiGod+Av+iL46cvZ/Yw9N4s6MKj+8u9dBEAioUxubNC5EPXoJ5Ov0tY3Xh+3MvqSj4gAJ4GICzyq3qEaLsvz6vIdB1jvXL4xDRTRmRvpxY+5MrhgVLHaqsX8C/k/COLx539pu4+d8r5bLoqlvxj7ngc1IfKLgp2dbXwVw7r0PiG2H3DpuZIHdY8vN1ypSCKRl2JxUfzWHTgjmUpHL/YAZyGoQHnGxevuyrtM5iltYah3hSzIhVZnOF+heB8/1mttbP5+aKUsSxKcxPSEcZ1PycOT142DrT0qhT3q6guRLDyRhn/Wc/sLOo+MzjLMyWiIgL2N2U6e4KLLHWmopJAxgZ6fDtX8Sv9pGXHEo18tf4Gl6J6TxXUdTNgG11lo9AGMDSIdY1JoOrMI6rZ4IKCKyvc0E2Er5bp1eN2l2r9qJiSxRAMioa74m/NyLnCzOExhHRuutY4iqilLGJzJRM4LiT3OyikI1+iejKZRa4X7B/55ugWG10bPq0rSKI/wlLoPAAp11UbaWk6cTApptRx9CbKx17QEUzqrPQC9badZY5u79NAOKvBLXFtSePInt+fetR4G/WzqzO9UGc+l5g+74VnbejvoxF2K6zRWEoJ3qxbjB7kbXyVDyhGlAdI7MrxxdUyahtoJz6OeASmICRNe+kdqjujuiDM">
<div id="doc3" class="yui-t7">
<!-- header -->
<a href="http://www.coj.net/Departments/Property+Appraiser/default.htm" id="ctl00_home" style="width:1000px;height:500px" title="Home">
<div id="hd" style="height:260px" role="IMG" title="Joyce Morgan Header" alt="City of Jacksonville Florida United States of America Property Appraiser Office Duval County Florida Seal of City of Jacksonville Photo of Jacksonville Waterfront Photo of Jerry Holland Jerry Holland Property Appraiser Fair and Accurate For All">
<div id="cojnetbits">
<!-- breadcrumbs -->
<div id="bc">
<ol id="cphBreadcrumbs">
<li id="last">
</li>
</ol>
</div>
<!-- coj.net search -->
<div id="searchBox"></div>
</div>
</div>
</a>
<!-- body -->
<div id="bd">
<!-- navigation -->
<div class="yui-g">
<div id="navWrapper">
<div id="navContainer">
<ol id="nav">
<li>
<a id="ctl00_menu_ctl00_navItem" class="active" href="/Basic/Search.aspx">Basic Search</a>
</li>
<li>
<a id="ctl00_menu_ctl01_navItem" href="/Sales/Search.aspx">Advanced Search</a>
</li>
<li>
<a id="ctl00_menu_ctl02_navItem" class="last" href="/Tangible/Search.aspx">Tangible Search</a>
</li>
</ol>
</div>
</div>
</div>
<!-- body content -->
<div class="yui-g" id="content">
<div id="resultsWrapper">
<h2>
Search Results
<span>
(<span id="ctl00_cphBody_lblResultsCount">0</span><span id="ctl00_cphBody_lblResultsFound"> properties found</span>)
</span>
</h2>
<div id="sortResults">
Sort Results By
<select name="ctl00$cphBody$ddOrder1" id="ctl00_cphBody_ddOrder1" class="dd" aria-label="Sort Results by.">
<option selected="selected" value="RealEstateNumber ASC">↓ RE#</option>
<option value="RealEstateNumber DESC">↑ RE#</option>
<option value="Name ASC">↓ Owner Name </option>
<option value="Name DESC">↑ Owner Name </option>
<option value="StreetNumber ASC">↓ Street #</option>
<option value="StreetNumber DESC">↑ Street #</option>
<option value="StreetName ASC">↓ Street Name </option>
<option value="StreetName DESC">↑ Street Name</option>
<option value="StreetSuffix ASC">↓ Type</option>
<option value="StreetSuffix DESC">↑ Type</option>
<option value="StreetPrefix ASC">↓ Direction</option>
<option value="StreetPrefix DESC">↑ Direction</option>
<option value="StreetUnit ASC">↓ Unit</option>
<option value="StreetUnit DESC">↑ Unit</option>
<option value="City ASC">↓ City</option>
<option value="City DESC">↑ City</option>
<option value="ZipCode ASC">↓ Zip</option>
<option value="ZipCode DESC">↑ Zip</option>
</select> Then By
<select name="ctl00$cphBody$ddOrder2" id="ctl00_cphBody_ddOrder2" class="dd" aria-label="Then Sort Results by.">
<option selected="selected" value="RealEstateNumber ASC">↓ RE#</option>
<option value="RealEstateNumber DESC">↑ RE#</option>
<option value="Name ASC">↓ Owner Name </option>
<option value="Name DESC">↑ Owner Name </option>
<option value="StreetNumber ASC">↓ Street #</option>
<option value="StreetNumber DESC">↑ Street #</option>
<option value="StreetName ASC">↓ Street Name </option>
<option value="StreetName DESC">↑ Street Name</option>
<option value="StreetSuffix ASC">↓ Type</option>
<option value="StreetSuffix DESC">↑ Type</option>
<option value="StreetPrefix ASC">↓ Direction</option>
<option value="StreetPrefix DESC">↑ Direction</option>
<option value="StreetUnit ASC">↓ Unit</option>
<option value="StreetUnit DESC">↑ Unit</option>
<option value="City ASC">↓ City</option>
<option value="City DESC">↑ City</option>
<option value="ZipCode ASC">↓ Zip</option>
<option value="ZipCode DESC">↑ Zip</option>
</select> Then By
<select name="ctl00$cphBody$ddOrder3" id="ctl00_cphBody_ddOrder3" class="dd" aria-label="Then Sort Results by.">
<option selected="selected" value="RealEstateNumber ASC">↓ RE#</option>
<option value="RealEstateNumber DESC">↑ RE#</option>
<option value="Name ASC">↓ Owner Name </option>
<option value="Name DESC">↑ Owner Name </option>
<option value="StreetNumber ASC">↓ Street #</option>
<option value="StreetNumber DESC">↑ Street #</option>
<option value="StreetName ASC">↓ Street Name </option>
<option value="StreetName DESC">↑ Street Name</option>
<option value="StreetSuffix ASC">↓ Type</option>
<option value="StreetSuffix DESC">↑ Type</option>
<option value="StreetPrefix ASC">↓ Direction</option>
<option value="StreetPrefix DESC">↑ Direction</option>
<option value="StreetUnit ASC">↓ Unit</option>
<option value="StreetUnit DESC">↑ Unit</option>
<option value="City ASC">↓ City</option>
<option value="City DESC">↑ City</option>
<option value="ZipCode ASC">↓ Zip</option>
<option value="ZipCode DESC">↑ Zip</option>
</select>
</div>
<div id="searchControl">
<div class="newSearch">
<a id="ctl00_cphBody_lbNewSearch" href="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;ctl00$cphBody$lbNewSearch&quot;, &quot;&quot;, false, &quot;&quot;, &quot;Search.aspx&quot;, false, true))">New Search</a>
</div>
<div class="refineSearch">
<a id="ctl00_cphBody_lbRefineSearch" href="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;ctl00$cphBody$lbRefineSearch&quot;, &quot;&quot;, false, &quot;&quot;, &quot;Search.aspx?Refine=Y&quot;, false, true))">Refine Search</a>
</div>
</div>
<div id="resultsHeader">
<div id="note">
<span id="ctl00_cphBody_lblNoteHeader" class="noteHeader">Tip: </span>
<span id="ctl00_cphBody_lblNote">Click the RE # for the property's details.</span>
</div>
<div id="export">
<ul>
<li class="first">
<a id="ctl00_cphBody_bExportToExcel" href="javascript:__doPostBack('ctl00$cphBody$bExportToExcel','')">Export to Excel</a>
</li>
<li class="last"><a id="ctl00_cphBody_bExportToText" href="javascript:__doPostBack('ctl00$cphBody$bExportToText','')">Export to plain text</a></li>
</ul>
</div>
</div>
<div class="clear"></div>
<div id="ctl00_cphBody_divNoResults1">
<div id="noResults">
<div id="decor"></div>
<h3 style="color:#000000;">No Results Found</h3>
<p style="color:#000000;">
Please broaden your search criteria or limit the number of categories searched. Because criteria searched must match the tax roll data exactly, less criteria often yields the best results. No Results Found may be the result of misspelled owner or street names.<br>
<a id="ctl00_cphBody_hlRefineSearch" href="Search.aspx?Refine=Y" style="color:#000000;text-shadow:initial;">Refine your search</a> or <a id="ctl00_cphBody_hlSearchAgain" href="Search.aspx" style="color:#000000;">search again</a>
<br><br>
<i>No Results Found </i>
may also be the result of certain statutory exemptions relating to the disclosure of personal information preventing the Property Appraiser from placing the property record online. If you believe you are getting this message in error and have reviewed your search criteria for correctness, or to check for an offline property record, please contact the Property Appraisers Office at
<a href="mailto:paadmin@coj.net" style="color:#000000;">paadmin@coj.net</a>.
</p>
</div>
</div>
<div class="gv">
<div>
<table class="gridview" cellspacing="0" border="0" id="ctl00_cphBody_gridResults">
<tbody><tr>
<td colspan="9">
<em>No information available</em>
</td>
</tr>
</tbody></table>
</div>
</div>
</div>
</div>
</div>
<!-- footer -->
<div id="ft">
<div id="tools">
<h2>Tools</h2>
<ul>
<li>
<a href="mailto:paadmin@coj.net?subject=Re:Search Problems/Feedback" aria-label="Report Search Problems">Report Search Problems</a>
</li>
<li>
<a id="ctl00_linkPropertyAppraiserCodes" alt="Property Appraiser Codes" href="../Codes.aspx">Property Appraiser Codes</a>
</li>
</ul>
</div>
<div id="questions">
<h2>Have any questions?</h2>
<ul>
<li><a id="ctl00_tcContactUs" title="By clicking this link, you will exit the Property Appraisers Web site and be directed to the Tax Collectors site." href="../Leaving.aspx?Destination=TC">Contact the Tax Collector about a payment</a>.</li>
<li><a href="mailto:paadmin@coj.net" alt="Contact the Property Appraiser">Contact the Property Appraiser about an assessment</a>.</li>
</ul>
<span id="ctl00_lblVersion" class="versionNumber" alt="Version">v1.18.00 - 29</span>
</div>
</div>
</div>
</form>
<div id="tooltip" style="display: none;"><h3>Tooltip</h3><div class="body"></div><div class="url"></div></div></body></html>
+143
View File
@@ -0,0 +1,143 @@
"""One-shot script to strip emojis + apply UI text changes to app.py.
Run once, then delete. Idempotent if re-run (no-ops on missing patterns).
"""
import re, sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
APP = ROOT / "app.py"
src = APP.read_text(encoding="utf-8")
orig = src
# ──── Phase A: Specific multi-token replacements (surgical) ─────────────────
SURGICAL = [
# st.set_page_config: page_icon
('page_icon="🏠",', 'page_icon=None,'),
# PAGES dict
('"manual": "🔬 Análisis manual"', '"manual": "Análisis"'),
('"search": "🔍 Buscar deals"', '"search": "Búsqueda"'),
('"inventory": "📦 Inventario"', '"inventory": "Inventario"'),
('"favorites": "⭐ Favoritos"', '"favorites": "Favoritos"'),
('"history": "📚 Histórico análisis"', '"history": "Histórico"'),
('"feed": "📡 Feed de deals"', '"feed": "Feed"'),
('"markets": "🌎 Mercados"', '"markets": "Mercados"'),
# Sidebar
('st.sidebar.title("🏠 AR-House")', 'st.sidebar.title("AR-House")'),
('st.sidebar.caption("Análisis FL real estate · 100% local")',
'st.sidebar.caption("Florida real estate analysis platform")'),
('st.sidebar.metric("⭐ Favoritos"', 'st.sidebar.metric("Favoritos"'),
# Page titles
('st.title("🔬 Análisis manual de deal")',
'st.title("Análisis manual de propiedad")'),
('st.title("📦 Inventario de deals scrapeados")',
'st.title("Inventario de oportunidades")'),
('st.title("⭐ Favoritos")', 'st.title("Favoritos")'),
('st.title("📚 Histórico de análisis")',
'st.title("Histórico de análisis")'),
('st.title("🔍 Buscar deals on-demand")',
'st.title("Búsqueda de propiedades")'),
('st.title("📡 Feed de deals")', 'st.title("Feed de oportunidades")'),
('st.title("🌎 Mercados monitoreados")',
'st.title("Cobertura de mercados")'),
# Subheaders
('st.subheader("🤖 Veredicto final (formato no parseado)")',
'st.subheader("Veredicto final (formato no parseado)")'),
('st.subheader("💰 Análisis financiero (DealAnalyzer)")',
'st.subheader("Análisis financiero")'),
('st.subheader("✉️ Generar email (EmailComposer)")',
'st.subheader("Generar comunicación al vendedor")'),
('st.subheader("1. 🌎 Condados")', 'st.subheader("1. Condados")'),
('st.subheader("2. 🛰️ Fuentes")', 'st.subheader("2. Fuentes")'),
('st.subheader("3. 🎛️ Filtros opcionales")',
'st.subheader("3. Filtros opcionales")'),
('st.subheader("4. 🚦 Preflight + accion")',
'st.subheader("4. Verificación previa y ejecución")'),
('st.subheader("📍 Cobertura por condado")',
'st.subheader("Cobertura por condado")'),
('st.subheader("🎯 Presets disponibles (markets_database.json)")',
'st.subheader("Presets disponibles")'),
('st.subheader("🚧 Gaps de cobertura conocidos")',
'st.subheader("Limitaciones conocidas")'),
# Buttons
('"◀ Anterior"', '"Anterior"'),
('"Siguiente ▶"', '"Siguiente"'),
('"✖ Limpiar banner"', '"Limpiar banner"'),
('" Nuevo análisis"', '"Nuevo análisis"'),
('"💾 Guardar análisis"', '"Guardar análisis"'),
('"📄 Exportar PDF"', '"Exportar PDF"'),
('"✨ Generar email"', '"Generar email"'),
('"📂 Cargar este análisis"', '"Cargar análisis"'),
('"📋 Cargar deal de prueba (Hialeah)"', '"Cargar deal de prueba (Hialeah)"'),
# VERDICT_BADGE
('"PASA": ("🟢", "#16a34a", "PASA"),',
'"PASA": ("", "#047857", "PASA"),'),
('"PASA CON CONDICIONES": ("🟡", "#ca8a04", "PASA CON CONDICIONES"),',
'"PASA CON CONDICIONES": ("", "#B45309", "PASA CON CONDICIONES"),'),
('"NO PASA": ("🔴", "#dc2626", "NO PASA"),',
'"NO PASA": ("", "#B91C1C", "NO PASA"),'),
# PROPERTY_TYPE_LABELS
('"sfr": "🏠 SFR (Single Family Residence)"',
'"sfr": "SFR — Single Family Residence"'),
('"condo": "🏢 Condo"', '"condo": "Condo"'),
('"townhome": "🏘️ Townhome"', '"townhome": "Townhome"'),
('"multi_family": "🏬 Multi-family (2-4 units)"',
'"multi_family": "Multi-family (24 units)"'),
('"land": "🌳 TERRENO / Lote vacante"',
'"land": "Terreno / Vacant lot"'),
('"mobile_home": "🚐 Mobile / Manufactured home"',
'"mobile_home": "Mobile / Manufactured home"'),
('"commercial": "🏛️ Commercial / Mixed-use"',
'"commercial": "Commercial / Mixed-use"'),
# Expanders
('with st.expander("📋 1. Inputs del deal", expanded=True):',
'with st.expander("1. Datos de la propiedad", expanded=True):'),
('with st.expander("🏘️ 1.4 Tipo de propiedad", expanded=True):',
'with st.expander("1.4 Tipo de propiedad", expanded=True):'),
('with st.expander("🔬 Análisis técnico completo (para revisión detallada)", expanded=False):',
'with st.expander("Análisis técnico completo (para revisión detallada)", expanded=False):'),
# Markdown headers in technical section
('st.markdown("### 💰 Análisis financiero (DealAnalyzer)")',
'st.markdown("### Análisis financiero (DealAnalyzer)")'),
('st.markdown("### 💵 ValueEstimator (valor real vs listing)")',
'st.markdown("### ValueEstimator (valor real vs listing)")'),
('st.markdown("### 🎯 OfferStrategist (Strike/Stretch/Walk-Away + estrategia)")',
'st.markdown("### OfferStrategist (Strike/Stretch/Walk-Away + estrategia)")'),
('st.markdown("### 🤖 Veredicto coordinador (Coordinator, markdown crudo)")',
'st.markdown("### Veredicto coordinador (Coordinator, markdown crudo)")'),
('st.markdown("### 💰 Liens Inventory contra la propiedad")',
'st.markdown("### Liens inventory contra la propiedad")'),
# Status messages — replace specific emoji-bearing patterns
('st.toast("Marcado como visto")', 'st.toast("Marcado como visto")'), # noop check
]
for old, new in SURGICAL:
if old in src:
src = src.replace(old, new)
# ──── Phase B: Strip emoji-prefix patterns inside f-strings/strings ─────────
# These are emoji + space at start of a quoted string content.
EMOJI_PREFIXES = [
'🚨🚨 ', '🚨 ', '⚠️ ', '', '', '🤖 ', '🚀 ', '💰 ', '🎯 ',
'🟢 ', '🟡 ', '🔴 ', '', '🔬 ', '💵 ', '🏛️ ', '🛡️ ', '🏢 ',
'🌳 ', '🏘️ ', '🍽 ', '🌍 ', '💼 ', '💎 ', '✉️ ', '👁️ ', '💔 ',
'', '📂 ', '📊 ', '📋 ', '👆 ', '💾 ', '📄 ', '🚧 ', '',
'🧠 ', '📭 ', '🌎 ', '🛰️ ', '🎛️ ', '🚦 ', '📍 ', '📡 ', '📦 ',
'🔍 ', '🔗 ', '', '🔎 ', '🇺🇸 ', '🇨🇴 ', '💡 ',
'🚨🚨', '🚨', '⚠️', '', '', '🤖', '🚀', '💰', '🎯',
'🟢', '🟡', '🔴', '', '🔬', '💵', '🏛️', '🛡️', '🏢',
'🌳', '🏘️', '🍽', '🌍', '💼', '💎', '✉️', '👁️', '💔',
'', '📂', '📊', '📋', '👆', '💾', '📄', '🚧', '',
'🧠', '📭', '🌎', '🛰️', '🎛️', '🚦', '📍', '📡', '📦',
'🔍', '🔗', '', '🔎', '🇺🇸', '🇨🇴', '💡',
]
for em in EMOJI_PREFIXES:
src = src.replace(em, '')
# ──── Phase C: Write back if changed ────────────────────────────────────────
if src != orig:
APP.write_text(src, encoding="utf-8")
print(f"app.py updated ({len(orig) - len(src)} chars removed)")
else:
print("No changes")
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+731
View File
@@ -0,0 +1,731 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html lang="en-US"><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>RealForeclose- Miami-Dade County -Auction Calendar</title>
<meta name="format-detection" content="telephone=no">
<meta name="robots" content="noindex,nofollow">
<!--css libs-->
<link rel="stylesheet" type="text/css" media="screen" href="CORE/System/Themes/Theme_1/CSS/blitzer/jquery-ui.min.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/main_site.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/print.css" media="print">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/frame.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/Search.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/jquery.multiselect.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/AuctionDayNavigator.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/AuctionBox.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/datePicker.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/document.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/LeftNav.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/LogForm.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/footer.css">
<link rel="stylesheet" type="text/css" href="/CORE/System/Themes/Theme_1/CSS/custom/CV_15.css">
<!--<link rel="stylesheet" type="text/css" href="/CORE/System/Themes/Theme_1/CSS/custom/CU_0.css" />-->
<!--css libs end-->
<!--Javascript libs-->
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery-migrate-1.4.1.min.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery-ui.min.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery.jqprint.0.3.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery.blockUI_rev1.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/3rdParty/jquery.debug.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery.fieldtag.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery.cookie.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery.qtip-1.0.0-rc3.min.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/3rdParty/jquery.multiselect.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/custom/SystemAlert.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/3rdParty/jMenu.jquery.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/custom/main_site.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/custom/popup.js"></script>
<script type="text/javascript" src="/CORE/System/JS/auction.js"></script>
<script type="text/javascript" src="/CORE/System/JS/logform.js"></script>
<!--Javascript libs end-->
<!--Javascript-->
<script type="text/javascript">
var bypassPage=1;
</script>
<script type="text/javascript">
offDate = new Date('05/13/2026 09:51:36 PM');
</script>
<script type="text/javascript">
function showExitPopup () {
return confirm('You are now leaving the site.\n\nThe Clerks office and Realauction provide links to third party web sites as a convenience only. Neither party endorses or is responsible for the accuracy, appropriateness, completeness or reliability of the content or information contained within or obtained from third party web sites.');
};
function jumpMenuPopup () {
return confirm('You are now leaving the Miami-Dade county auction site.\n\nDo you want to continue?');
};
</script>
<!--Javascript end-->
</head>
<body class="main_text" marginheight="0" marginwidth="0">
<div id="MAIN_HEADER">
<div id="imgLogo" onclick="window.location.href='/index.cfm'" onkeypress="window.location.href='/index.cfm'" aria-label="Miami-Dade County Sale" role="banner" tabindex="0"></div>
<div id="headerStat" islog="N"></div>
<div class="headerMenu" role="navigation" aria-label="Header nav">
<a tabindex="0" id="Home" href="/index.cfm">Home</a>
<div class="hmItem"></div>
<a tabindex="0" id="AboutUs" href="/index.cfm?zaction=home&amp;zmethod=aboutus">About Us</a>
<div class="hmItem"></div>
<a tabindex="0" id="FAQ" href="/index.cfm?zaction=home&amp;zmethod=faq">FAQ</a>
<div class="hmItem"></div>
<a tabindex="0" id="ContactUs" href="/index.cfm?zaction=ContactUS&amp;zmethod=START">Contact Us</a>
</div>
</div>
<table id="MAIN_TBL" cellspacing="0" cellpadding="0" border="0">
<tbody><tr>
<td id="MAIN_TBL_NAV" class="noPrint" valign="top" tabindex="0"><div id="minHeight"></div>
<div class="ln_page">
<div id="ln_menu" aria-label="Left Rail" role="navigation">
<div id="DivLogForm">
<div id="BadText" class="BadText" style="display:none" tabindex="-2">User Name or Password is Invalid</div>
<div class="LogLabel">
<label for="LogName"> User Name&nbsp;&nbsp;</label><input tabindex="0" id="LogName" class="LogInput inIn" value="">
</div>
<div class="LogLabel">
<label for="LogPass">User Password&nbsp;&nbsp;</label><input tabindex="0" id="LogPass" type="password" class="LogInput" value="">
</div>
<div class="LogLabel">
<div id="LogButton" tabindex="0" aria-label="Submit button">Submit</div>
</div>
<p>
<a href="/index.cfm?zaction=HOME&amp;Zmethod=FORGOTUN" tabindex="0"><span class="LogHelp">Forgot your username?</span> </a><br>
<a href="/index.cfm?zaction=HOME&amp;Zmethod=FORGOT" tabindex="0"><span class="LogHelp">Forgot your password?</span></a>
</p>
</div>
<div class="fspace">&nbsp;</div>
<div align="center" style="margin-bottom:5px;">
</div>
<div class="LN_MH" tabindex="0" role="navigation" aria-label="Training Links"><span class="LN_RT">Training</span><span class="LN_LF">Links</span></div>
<div class="LN_MI"><a href="/index.cfm?ZACTION=HOME&amp;ZMETHOD=TRAINING" tabindex="0"><span class="LN_MT">Training</span></a></div>
<div class="LN_MI"><a href="/index.cfm?ZACTION=HOME&amp;ZMETHOD=FORECLOSE" tabindex="0"><span class="LN_MT">Foreclosure Process</span></a></div>
<div class="LN_MI"><a href="/index.cfm?ZACTION=HOME&amp;ZMETHOD=TAXDEED" tabindex="0"><span class="LN_MT">Tax Deed Sale Process</span></a></div>
<div class="LN_MH" tabindex="0" role="navigation" aria-label="External Links"><span class="LN_RT">External</span><span class="LN_LF">Links</span></div>
<div class="LN_MI"><a href="http://www.miami-dadeclerk.com" onclick="return showExitPopup()" target="_blank" tabindex="0"><span class="LN_MT">Clerk Of Courts</span></a></div>
<div class="LN_MI"><a href="http://www.miamidade.gov/pa/" onclick="return showExitPopup()" target="_blank" tabindex="0"><span class="LN_MT">Property Appraiser</span></a></div>
<div class="LN_MI"><a href="http://www.miamidade.gov/taxcollector/" onclick="return showExitPopup()" target="_blank" tabindex="0"><span class="LN_MT">Tax Collector</span></a></div>
<div class="LN_MI"><a href="https://www.myflorida.gov" onclick="return showExitPopup()" target="_blank" tabindex="0"><span class="LN_MT">myFlorida.gov</span></a></div>
<div class="LN_MI"><a href="/CORE/Public/documents/V15_AO%202013-05.pdf" tabindex="0"><span class="LN_MT">AO 2013-05</span></a></div>
<div class="LN_MH" tabindex="0" role="navigation" aria-label="Jump to"><label for="JMP_MENU_SEL"><span class="LN_RT">Jump</span><span class="LN_LF">To</span></label></div>
<div class="LN_MI">
<select id="JMP_MENU_SEL" title="Jump to other County Clerk Sites">
<option value="0">Select web site</option>
<optgroup label="AZ">
<option value="72">Apache Taxdeed</option>
<option value="69">Coconino Taxdeed</option>
<option value="77">Mohave Taxdeed</option>
</optgroup>
<optgroup label="CO">
<option value="118">Adams Treasurer Deed</option>
<option value="48">Denver Foreclosure</option>
<option value="117">Denver Treasurer Deed</option>
<option value="82">Eagle Foreclosure</option>
<option value="115">Eagle Treasurer Deed</option>
<option value="106">El Paso Foreclosure</option>
<option value="113">El Paso Treasurer Deed</option>
<option value="100">Larimer Foreclosure</option>
<option value="112">Larimer Treasurer Deed</option>
<option value="97">Mesa Foreclosure</option>
<option value="111">Mesa Treasurer Deed</option>
<option value="116">Pitkin Treasurer Deed</option>
<option value="93">Summit Foreclosure</option>
<option value="98">Weld Foreclosure</option>
<option value="114">Weld Treasurer Deed</option>
</optgroup>
<optgroup label="FL">
<option value="58">Alachua Foreclosure</option>
<option value="78">Alachua Taxdeed</option>
<option value="105">Baker Taxdeed</option>
<option value="13">Bay Foreclosure</option>
<option value="73">Bay Taxdeed</option>
<option value="29">Brevard Taxdeed</option>
<option value="17">Broward Foreclosure</option>
<option value="107">Calhoun</option>
<option value="3">Charlotte</option>
<option value="35">Citrus Foreclosure</option>
<option value="30">Citrus Taxdeed</option>
<option value="67">Clay Foreclosure</option>
<option value="68">Clay Taxdeed</option>
<option value="2">Duval Foreclosure</option>
<option value="32">Duval Tax Deed</option>
<option value="19">Escambia Foreclosure</option>
<option value="18">Escambia Taxdeed</option>
<option value="53">Flagler Foreclosure</option>
<option value="51">Flagler Taxdeed</option>
<option value="81">Gilchrist Foreclosure</option>
<option value="55">Gilchrist Taxdeed</option>
<option value="95">Gulf Foreclosure</option>
<option value="96">Gulf Taxdeed</option>
<option value="41">Hendry TaxDeed</option>
<option value="56">Hernando Taxdeed</option>
<option value="75">Highlands Taxdeed</option>
<option value="38">Hillsborough Foreclosure</option>
<option value="64">Hillsborough Taxdeed</option>
<option value="25">Indian River Foreclosure</option>
<option value="54">Indian River Taxdeed</option>
<option value="101">Jackson Foreclosure</option>
<option value="102">Jackson Taxdeed</option>
<option value="37">Lake Taxdeed</option>
<option value="8">Lee Foreclosure</option>
<option value="7">Lee Taxdeed</option>
<option value="44">Leon Foreclosure</option>
<option value="49">Leon Taxdeed</option>
<option value="1">Manatee</option>
<option value="42">Marion Foreclosure</option>
<option value="79">Marion Taxdeed</option>
<option value="26">Martin Foreclosure</option>
<option value="71">Martin Taxdeed</option>
<option value="110">Monroe Taxdeed</option>
<option value="27">Nassau Foreclosure</option>
<option value="92">Nassau Taxdeed</option>
<option value="90">Okeechobee</option>
<option value="21">Orange Foreclosure</option>
<option value="22">Orange Taxdeed</option>
<option value="31">Osceola Taxdeed</option>
<option value="34">Palm Beach Foreclosure</option>
<option value="99">Palm Beach Taxdeed</option>
<option value="14">Pasco Foreclosure</option>
<option value="50">Pasco Taxdeed</option>
<option value="23">Pinellas Foreclosure</option>
<option value="24">Pinellas Taxdeed</option>
<option value="20">Polk Foreclosure</option>
<option value="28">Polk Taxdeed</option>
<option value="57">Putnam Foreclosure</option>
<option value="74">Putnam Taxdeed</option>
<option value="66">Saint Johns Foreclosure</option>
<option value="45">Santa Rosa Foreclosure</option>
<option value="52">Santa Rosa Taxdeed</option>
<option value="11">Sarasota Foreclosure</option>
<option value="70">Sarasota Taxdeed</option>
<option value="103">Seminole Foreclosure</option>
<option value="104">Seminole Taxdeed</option>
<option value="84">St. Lucie</option>
<option value="119">Suwannee Taxdeed</option>
<option value="43">Volusia Foreclosure</option>
<option value="10">Volusia Taxdeed</option>
<option value="6">Walton</option>
<option value="108">Washington Foreclosure</option>
<option value="76">Washington Taxdeed</option>
</optgroup>
<optgroup label="NJ">
<option value="109">Hardyston Foreclosure</option>
<option value="86">Newark</option>
</optgroup>
</select>
</div>
<div>&nbsp;</div>
<div class="leftNavFill"></div>
</div>
</div>
</td>
<td id="MAIN_TBL_CONTENT" valign="top">
<div id="Content_Header" class="noPrint">
<div id="Content_Title" tabindex="0">
<h1>Preview Items For Sale</h1>
</div>
<div id="ContentFadebar"></div>
</div>
<div class="AuctionNav_Main AUTOCENTER">
<div class="BLHeaderDateDisplay" tabindex="0">Wednesday May 13, 2026</div>
<div class="BLSRCH"><span class="popup_search">Search</span></div>
<div class="BLNav">
<div class="BLHeaderPrev BLArrow"><a href="/index.cfm?zaction=AUCTION&amp;zmethod=PREVIEW&amp;AuctionDate=05/12/2026">&lt; &lt; Previous Auction</a></div>
<div class="BLHeaderNext BLArrow"><a href="/index.cfm?zaction=AUCTION&amp;zmethod=PREVIEW&amp;AuctionDate=05/14/2026">Next Auction &gt; &gt;</a></div>
<div class="BLHeaderToday BLArrow">
<a href="/index.cfm?zaction=AUCTION&amp;zmethod=PREVIEW&amp;AuctionDate=05/13/2026">Current</a></div>
</div>
<div class="AuctionNav_END"></div>
</div>
<div id="BID_WINDOW_CONTAINER" class="AUTOCENTER">
<br><br>
<div id="docPgContainer" class="sitedoc">
<div role="main">
<p tabindex="0" style="text-align: center; font-size: 9pt; font-weight: bold;">Pursuant to new legislation effective July 1, 2008, F. S. 45.031(10), and F. S. 197.542 (4)(a),(b) the Clerk may conduct the sale of real or personal property under an order or judgment by electronic means, and tax deed sales in lieu of public outcry.</p>
</div>
</div>
<div class="Head_R">
<div tabindex="0" class="Sub_Title">Running Auctions</div>
<div class="Fadebar"></div>
<div id="Area_R" class="Auct_Area" ref="N" arid="R"></div>
<table id="FRM_Ano_Auct" cellspacing="0" tabindex="0" cellpadding="0" class="FRAMECFC CENTERCONTENT" style="" sid="17">
<tbody><tr>
<td class="FTL FSCN17" role="presentation"></td>
<td class="FTM FSRL17" role="presentation"></td>
<td class="FTR FSCN17" role="presentation"></td>
</tr>
<tr>
<td class="FSL FSSD17" role="presentation"></td>
<td class="FMM FSMM17" role="presentation">
<br>
<p tabindex="0" style="text-align: center;">There are no cases currently being auctioned.</p>
<br>
</td>
<td class="FSR FSSD17" role="presentation"></td>
</tr>
<tr>
<td class="FBL FSCN17" role="presentation"></td>
<td class="FBM FSRL17" role="presentation"></td>
<td class="FBR FSCN17" role="presentation"></td>
</tr>
</tbody></table>
</div>
<div class="Head_W" style="display: none;">
<div tabindex="0" class="Sub_Title">Auctions Waiting</div>
<div class="Fadebar"></div>
<div class="PageFrame" area="W">
<span tabindex="0" class="PageLeft"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Previous Page" width="41" height="16" align="absmiddle"></span>
<span class="PageText"><span tabindex="0">page </span><input id="curPWA" aria-label="Current page" type="text" curpg="1"><span tabindex="0"> of </span><span tabindex="0" aria-label="Total pages" id="maxWA">1</span> </span>
<span tabindex="0" class="PageRight"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Next Page" width="41" height="16" align="absmiddle"></span>
</div>
<div id="Area_W" class="Auct_Area" ref="N" arid="W"></div>
<div class="Fadebar"></div>
<div class="PageFrame" area="W">
<span tabindex="0" class="PageLeft"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Previous Page" width="41" height="16" align="absmiddle"></span>
<span class="PageText"> <span tabindex="0">page</span> <input id="curPWB" aria-label="Current page" type="text" curpg="1"> <span tabindex="0"> of </span><span tabindex="0" aria-label="Total pages" id="maxWB">1</span> </span>
<span tabindex="0" class="PageRight"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Next Page" width="41" height="16" align="absmiddle"></span>
</div>
</div>
<div class="Head_C">
<div tabindex="0" class="Sub_Title">Auctions Closed or Canceled</div>
<div class="Fadebar"></div>
<div class="PageFrame" area="C">
<span tabindex="0" class="PageLeft"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Previous Page" width="41" height="16" align="absmiddle"></span>
<span class="PageText"> <span tabindex="0"> page </span><input id="curPCA" aria-label="Current page" type="text" curpg="1"> <span tabindex="0"> of </span><span tabindex="0" aria-label="Total pages" id="maxCA">1</span></span>
<span tabindex="0" class="PageRight"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Next Page" width="41" height="16" align="absmiddle"></span>
</div>
<div id="Area_C" class="Auct_Area" ref="N" arid="C"><div id="AITEM_1498226" aria-label="Auction Details" class="AUCTION_ITEM PREVIEW" aid="1498226" rem="0" isset="1"><div class="AUCTION_STATS" tabindex="0"><div class="ASTAT_MSGA ASTAT_LBL">Auction Status</div><div class="ASTAT_MSGB Astat_DATA">Canceled per Order</div><div class="ASTAT_MSGC ASTAT_LBL"></div> <div class="ASTAT_MSGD Astat_DATA"></div><div class="ASTAT_MSG_SOLDTO_Label ASTAT_LBL"></div><div class="ASTAT_MSG_SOLDTO_MSG Astat_DATA"></div></div><div class="AUCTION_DETAILS"><table class="ad_tab"><tbody><tr><td tabindex="0" class="AD_LBL" scope="row">Auction Type:</td><td tabindex="0" class="AD_DTA">FORECLOSURE</td></tr><tr><td tabindex="0" class="AD_LBL" scope="row" aria-label="Case Number">Case #:</td><td tabindex="0" class="AD_DTA"> 2016-022214-CA-01</td></tr><tr><td tabindex="0" class="AD_LBL" scope="row">Final Judgment Amount:</td><td tabindex="0" class="AD_DTA">$353,041.78</td></tr><tr><td tabindex="0" class="AD_LBL" scope="row">Parcel ID:</td><td tabindex="0" class="AD_DTA"> <a href="https://www.miamidade.gov/Apps/PA/propertysearch/#/?folio=3220230081440" onclick="return showExitPopup();" target="_blank">32-2023-008-1440</a></td></tr><tr><td tabindex="0" class="AD_LBL" scope="row">Property Address:</td><td tabindex="0" class="AD_DTA">7355 POINCIANA CT</td></tr><tr><td class="AD_LBL" scope="row"></td><td tabindex="0" class="AD_DTA">MIAMI LAKES, FL- 33014</td></tr> <tr><td tabindex="0" class="AD_LBL" scope="row">Assessed Value:</td><td tabindex="0" class="AD_DTA">$345,036.00</td></tr><tr><td tabindex="0" class="AD_LBL" scope="row">Plaintiff Max Bid:</td><td tabindex="0" class="AD_DTA ASTAT_MSGPB">Hidden</td></tr></tbody></table></div></div><div class="AUCTION_ITEM_SPACER">&nbsp;</div> </div>
<div class="Fadebar"></div>
<div class="PageFrame" area="C">
<span tabindex="0" class="PageLeft"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Previous Page" width="41" height="16" align="absmiddle"></span>
<span class="PageText"><span tabindex="0">page </span></span> <input id="curPCB" aria-label="Current page" type="text" curpg="1"> <span tabindex="0"> of </span><span tabindex="0" aria-label="Total pages" id="maxCB">1</span>
<span tabindex="0" class="PageRight"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Next Page" width="41" height="16" align="absmiddle"></span>
</div>
<div tabindex="0" id="popup" style="display:none"></div>
</div>
<div tabindex="0" class="Bottom_Mark">&nbsp;</div>
</div>
<div tabindex="0" id="DivBidBox" style="display:none"></div>
<div tabindex="0" id="ALB" style="display:none">1498226</div>
</td>
</tr>
</tbody></table>
<div id="MAIN_FOOTER" class="noPrint">
<div id="footer" align="center;">
<div id="footerDiv1">
<div id="leftNavImage" style="float:left; width:33%;">
<div id="pingbox"><div id="pingtxt">Connection</div>
<div id="ping_I" style="background-position: 80% 0%;"></div>
<div id="ping_S" style="background-position: 100% 0%;"></div>
</div>
<div id="ping_t">I:55 W:1</div>
</div>
<div id="footerlinks" style="height:100%; float:left; width:33%;" role="navigation" aria-label="Footer nav">
<ul class="navigation">
<li><a tabindex="0" href="index.cfm?zaction=home&amp;zmethod=aboutUs" class="footer">About Us</a> |</li>
<li><a tabindex="0" href="index.cfm?zaction=home&amp;zmethod=siteMap" class="footer">Site Map</a> |</li>
<li><a tabindex="0" href="index.cfm?zaction=home&amp;zmethod=privpol" class="footer">Privacy Policy</a> |</li>
<li><a tabindex="0" href="index.cfm?zaction=home&amp;zmethod=agreement" class="footer">User Agreement</a> |</li>
<li><a tabindex="0" href="index.cfm?zaction=home&amp;zmethod=welcome" class="footer">Bidder Letter</a> |</li>
<li><a tabindex="0" href="index.cfm?zaction=ContactUS&amp;zmethod=START" class="footer">Contact Us</a></li>
</ul>
<p>
<a href="http://www.realauction.com" tabindex="0" class="footer">© 2026 Realauction.com LLC</a>
</p>
</div>
<div style="float:right; width:33%;" align="right;">
<a href="http://www.realauction.com" class="footer" title="ip-10-0-33-211.Realauction.com">
<div style="float:right;" id="poweredBy" class="RATxt">Visit Realaction.com</div>
</a>
</div>
</div>
</div>
<div id="leavingSite" style="display:none;">
You have chosen to leave the site. Do you want to proceed?
</div>
</div>
<div id="popupmain" style="display:none"></div>
<div id="divSysAlert" style="display:none"></div>
<div id="SaveKey" key="" style="display:none"></div>
<script>
var jsEnvironment = '/CORE/System/JS/production';
</script>
<div id="DEBUG"><ol></ol></div></body></html>
+731
View File
@@ -0,0 +1,731 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html lang="en-US"><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>RealForeclose- Miami-Dade County -Auction Calendar</title>
<meta name="format-detection" content="telephone=no">
<meta name="robots" content="noindex,nofollow">
<!--css libs-->
<link rel="stylesheet" type="text/css" media="screen" href="CORE/System/Themes/Theme_1/CSS/blitzer/jquery-ui.min.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/main_site.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/print.css" media="print">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/frame.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/Search.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/jquery.multiselect.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/AuctionDayNavigator.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/AuctionBox.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/datePicker.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/document.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/LeftNav.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/LogForm.css">
<link rel="stylesheet" type="text/css" href="CORE/System/Themes/Theme_1/CSS/footer.css">
<link rel="stylesheet" type="text/css" href="/CORE/System/Themes/Theme_1/CSS/custom/CV_15.css">
<!--<link rel="stylesheet" type="text/css" href="/CORE/System/Themes/Theme_1/CSS/custom/CU_0.css" />-->
<!--css libs end-->
<!--Javascript libs-->
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery-migrate-1.4.1.min.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery-ui.min.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery.jqprint.0.3.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery.blockUI_rev1.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/3rdParty/jquery.debug.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery.fieldtag.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery.cookie.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/JQUERY/jquery.qtip-1.0.0-rc3.min.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/3rdParty/jquery.multiselect.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/custom/SystemAlert.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/3rdParty/jMenu.jquery.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/custom/main_site.js"></script>
<script type="text/javascript" src="/CORE/System/JS/production/custom/popup.js"></script>
<script type="text/javascript" src="/CORE/System/JS/auction.js"></script>
<script type="text/javascript" src="/CORE/System/JS/logform.js"></script>
<!--Javascript libs end-->
<!--Javascript-->
<script type="text/javascript">
var bypassPage=1;
</script>
<script type="text/javascript">
offDate = new Date('05/13/2026 09:52:20 PM');
</script>
<script type="text/javascript">
function showExitPopup () {
return confirm('You are now leaving the site.\n\nThe Clerks office and Realauction provide links to third party web sites as a convenience only. Neither party endorses or is responsible for the accuracy, appropriateness, completeness or reliability of the content or information contained within or obtained from third party web sites.');
};
function jumpMenuPopup () {
return confirm('You are now leaving the Miami-Dade county auction site.\n\nDo you want to continue?');
};
</script>
<!--Javascript end-->
</head>
<body class="main_text" marginheight="0" marginwidth="0">
<div id="MAIN_HEADER">
<div id="imgLogo" onclick="window.location.href='/index.cfm'" onkeypress="window.location.href='/index.cfm'" aria-label="Miami-Dade County Sale" role="banner" tabindex="0"></div>
<div id="headerStat" islog="N"></div>
<div class="headerMenu" role="navigation" aria-label="Header nav">
<a tabindex="0" id="Home" href="/index.cfm">Home</a>
<div class="hmItem"></div>
<a tabindex="0" id="AboutUs" href="/index.cfm?zaction=home&amp;zmethod=aboutus">About Us</a>
<div class="hmItem"></div>
<a tabindex="0" id="FAQ" href="/index.cfm?zaction=home&amp;zmethod=faq">FAQ</a>
<div class="hmItem"></div>
<a tabindex="0" id="ContactUs" href="/index.cfm?zaction=ContactUS&amp;zmethod=START">Contact Us</a>
</div>
</div>
<table id="MAIN_TBL" cellspacing="0" cellpadding="0" border="0">
<tbody><tr>
<td id="MAIN_TBL_NAV" class="noPrint" valign="top" tabindex="0"><div id="minHeight"></div>
<div class="ln_page">
<div id="ln_menu" aria-label="Left Rail" role="navigation">
<div id="DivLogForm">
<div id="BadText" class="BadText" style="display:none" tabindex="-2">User Name or Password is Invalid</div>
<div class="LogLabel">
<label for="LogName"> User Name&nbsp;&nbsp;</label><input tabindex="0" id="LogName" class="LogInput inIn" value="">
</div>
<div class="LogLabel">
<label for="LogPass">User Password&nbsp;&nbsp;</label><input tabindex="0" id="LogPass" type="password" class="LogInput" value="">
</div>
<div class="LogLabel">
<div id="LogButton" tabindex="0" aria-label="Submit button">Submit</div>
</div>
<p>
<a href="/index.cfm?zaction=HOME&amp;Zmethod=FORGOTUN" tabindex="0"><span class="LogHelp">Forgot your username?</span> </a><br>
<a href="/index.cfm?zaction=HOME&amp;Zmethod=FORGOT" tabindex="0"><span class="LogHelp">Forgot your password?</span></a>
</p>
</div>
<div class="fspace">&nbsp;</div>
<div align="center" style="margin-bottom:5px;">
</div>
<div class="LN_MH" tabindex="0" role="navigation" aria-label="Training Links"><span class="LN_RT">Training</span><span class="LN_LF">Links</span></div>
<div class="LN_MI"><a href="/index.cfm?ZACTION=HOME&amp;ZMETHOD=TRAINING" tabindex="0"><span class="LN_MT">Training</span></a></div>
<div class="LN_MI"><a href="/index.cfm?ZACTION=HOME&amp;ZMETHOD=FORECLOSE" tabindex="0"><span class="LN_MT">Foreclosure Process</span></a></div>
<div class="LN_MI"><a href="/index.cfm?ZACTION=HOME&amp;ZMETHOD=TAXDEED" tabindex="0"><span class="LN_MT">Tax Deed Sale Process</span></a></div>
<div class="LN_MH" tabindex="0" role="navigation" aria-label="External Links"><span class="LN_RT">External</span><span class="LN_LF">Links</span></div>
<div class="LN_MI"><a href="http://www.miami-dadeclerk.com" onclick="return showExitPopup()" target="_blank" tabindex="0"><span class="LN_MT">Clerk Of Courts</span></a></div>
<div class="LN_MI"><a href="http://www.miamidade.gov/pa/" onclick="return showExitPopup()" target="_blank" tabindex="0"><span class="LN_MT">Property Appraiser</span></a></div>
<div class="LN_MI"><a href="http://www.miamidade.gov/taxcollector/" onclick="return showExitPopup()" target="_blank" tabindex="0"><span class="LN_MT">Tax Collector</span></a></div>
<div class="LN_MI"><a href="https://www.myflorida.gov" onclick="return showExitPopup()" target="_blank" tabindex="0"><span class="LN_MT">myFlorida.gov</span></a></div>
<div class="LN_MI"><a href="/CORE/Public/documents/V15_AO%202013-05.pdf" tabindex="0"><span class="LN_MT">AO 2013-05</span></a></div>
<div class="LN_MH" tabindex="0" role="navigation" aria-label="Jump to"><label for="JMP_MENU_SEL"><span class="LN_RT">Jump</span><span class="LN_LF">To</span></label></div>
<div class="LN_MI">
<select id="JMP_MENU_SEL" title="Jump to other County Clerk Sites">
<option value="0">Select web site</option>
<optgroup label="AZ">
<option value="72">Apache Taxdeed</option>
<option value="69">Coconino Taxdeed</option>
<option value="77">Mohave Taxdeed</option>
</optgroup>
<optgroup label="CO">
<option value="118">Adams Treasurer Deed</option>
<option value="48">Denver Foreclosure</option>
<option value="117">Denver Treasurer Deed</option>
<option value="82">Eagle Foreclosure</option>
<option value="115">Eagle Treasurer Deed</option>
<option value="106">El Paso Foreclosure</option>
<option value="113">El Paso Treasurer Deed</option>
<option value="100">Larimer Foreclosure</option>
<option value="112">Larimer Treasurer Deed</option>
<option value="97">Mesa Foreclosure</option>
<option value="111">Mesa Treasurer Deed</option>
<option value="116">Pitkin Treasurer Deed</option>
<option value="93">Summit Foreclosure</option>
<option value="98">Weld Foreclosure</option>
<option value="114">Weld Treasurer Deed</option>
</optgroup>
<optgroup label="FL">
<option value="58">Alachua Foreclosure</option>
<option value="78">Alachua Taxdeed</option>
<option value="105">Baker Taxdeed</option>
<option value="13">Bay Foreclosure</option>
<option value="73">Bay Taxdeed</option>
<option value="29">Brevard Taxdeed</option>
<option value="17">Broward Foreclosure</option>
<option value="107">Calhoun</option>
<option value="3">Charlotte</option>
<option value="35">Citrus Foreclosure</option>
<option value="30">Citrus Taxdeed</option>
<option value="67">Clay Foreclosure</option>
<option value="68">Clay Taxdeed</option>
<option value="2">Duval Foreclosure</option>
<option value="32">Duval Tax Deed</option>
<option value="19">Escambia Foreclosure</option>
<option value="18">Escambia Taxdeed</option>
<option value="53">Flagler Foreclosure</option>
<option value="51">Flagler Taxdeed</option>
<option value="81">Gilchrist Foreclosure</option>
<option value="55">Gilchrist Taxdeed</option>
<option value="95">Gulf Foreclosure</option>
<option value="96">Gulf Taxdeed</option>
<option value="41">Hendry TaxDeed</option>
<option value="56">Hernando Taxdeed</option>
<option value="75">Highlands Taxdeed</option>
<option value="38">Hillsborough Foreclosure</option>
<option value="64">Hillsborough Taxdeed</option>
<option value="25">Indian River Foreclosure</option>
<option value="54">Indian River Taxdeed</option>
<option value="101">Jackson Foreclosure</option>
<option value="102">Jackson Taxdeed</option>
<option value="37">Lake Taxdeed</option>
<option value="8">Lee Foreclosure</option>
<option value="7">Lee Taxdeed</option>
<option value="44">Leon Foreclosure</option>
<option value="49">Leon Taxdeed</option>
<option value="1">Manatee</option>
<option value="42">Marion Foreclosure</option>
<option value="79">Marion Taxdeed</option>
<option value="26">Martin Foreclosure</option>
<option value="71">Martin Taxdeed</option>
<option value="110">Monroe Taxdeed</option>
<option value="27">Nassau Foreclosure</option>
<option value="92">Nassau Taxdeed</option>
<option value="90">Okeechobee</option>
<option value="21">Orange Foreclosure</option>
<option value="22">Orange Taxdeed</option>
<option value="31">Osceola Taxdeed</option>
<option value="34">Palm Beach Foreclosure</option>
<option value="99">Palm Beach Taxdeed</option>
<option value="14">Pasco Foreclosure</option>
<option value="50">Pasco Taxdeed</option>
<option value="23">Pinellas Foreclosure</option>
<option value="24">Pinellas Taxdeed</option>
<option value="20">Polk Foreclosure</option>
<option value="28">Polk Taxdeed</option>
<option value="57">Putnam Foreclosure</option>
<option value="74">Putnam Taxdeed</option>
<option value="66">Saint Johns Foreclosure</option>
<option value="45">Santa Rosa Foreclosure</option>
<option value="52">Santa Rosa Taxdeed</option>
<option value="11">Sarasota Foreclosure</option>
<option value="70">Sarasota Taxdeed</option>
<option value="103">Seminole Foreclosure</option>
<option value="104">Seminole Taxdeed</option>
<option value="84">St. Lucie</option>
<option value="119">Suwannee Taxdeed</option>
<option value="43">Volusia Foreclosure</option>
<option value="10">Volusia Taxdeed</option>
<option value="6">Walton</option>
<option value="108">Washington Foreclosure</option>
<option value="76">Washington Taxdeed</option>
</optgroup>
<optgroup label="NJ">
<option value="109">Hardyston Foreclosure</option>
<option value="86">Newark</option>
</optgroup>
</select>
</div>
<div>&nbsp;</div>
<div class="leftNavFill"></div>
</div>
</div>
</td>
<td id="MAIN_TBL_CONTENT" valign="top">
<div id="Content_Header" class="noPrint">
<div id="Content_Title" tabindex="0">
<h1>Preview Items For Sale</h1>
</div>
<div id="ContentFadebar"></div>
</div>
<div class="AuctionNav_Main AUTOCENTER">
<div class="BLHeaderDateDisplay" tabindex="0">Wednesday May 13, 2026</div>
<div class="BLSRCH"><span class="popup_search">Search</span></div>
<div class="BLNav">
<div class="BLHeaderPrev BLArrow"><a href="/index.cfm?zaction=AUCTION&amp;zmethod=PREVIEW&amp;AuctionDate=05/12/2026">&lt; &lt; Previous Auction</a></div>
<div class="BLHeaderNext BLArrow"><a href="/index.cfm?zaction=AUCTION&amp;zmethod=PREVIEW&amp;AuctionDate=05/14/2026">Next Auction &gt; &gt;</a></div>
<div class="BLHeaderToday BLArrow">
<a href="/index.cfm?zaction=AUCTION&amp;zmethod=PREVIEW&amp;AuctionDate=05/13/2026">Current</a></div>
</div>
<div class="AuctionNav_END"></div>
</div>
<div id="BID_WINDOW_CONTAINER" class="AUTOCENTER">
<br><br>
<div id="docPgContainer" class="sitedoc">
<div role="main">
<p tabindex="0" style="text-align: center; font-size: 9pt; font-weight: bold;">Pursuant to new legislation effective July 1, 2008, F. S. 45.031(10), and F. S. 197.542 (4)(a),(b) the Clerk may conduct the sale of real or personal property under an order or judgment by electronic means, and tax deed sales in lieu of public outcry.</p>
</div>
</div>
<div class="Head_R">
<div tabindex="0" class="Sub_Title">Running Auctions</div>
<div class="Fadebar"></div>
<div id="Area_R" class="Auct_Area" ref="N" arid="R"></div>
<table id="FRM_Ano_Auct" cellspacing="0" tabindex="0" cellpadding="0" class="FRAMECFC CENTERCONTENT" style="" sid="17">
<tbody><tr>
<td class="FTL FSCN17" role="presentation"></td>
<td class="FTM FSRL17" role="presentation"></td>
<td class="FTR FSCN17" role="presentation"></td>
</tr>
<tr>
<td class="FSL FSSD17" role="presentation"></td>
<td class="FMM FSMM17" role="presentation">
<br>
<p tabindex="0" style="text-align: center;">There are no cases currently being auctioned.</p>
<br>
</td>
<td class="FSR FSSD17" role="presentation"></td>
</tr>
<tr>
<td class="FBL FSCN17" role="presentation"></td>
<td class="FBM FSRL17" role="presentation"></td>
<td class="FBR FSCN17" role="presentation"></td>
</tr>
</tbody></table>
</div>
<div class="Head_W" style="display: none;">
<div tabindex="0" class="Sub_Title">Auctions Waiting</div>
<div class="Fadebar"></div>
<div class="PageFrame" area="W">
<span tabindex="0" class="PageLeft"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Previous Page" width="41" height="16" align="absmiddle"></span>
<span class="PageText"><span tabindex="0">page </span><input id="curPWA" aria-label="Current page" type="text" curpg="1"><span tabindex="0"> of </span><span tabindex="0" aria-label="Total pages" id="maxWA">1</span> </span>
<span tabindex="0" class="PageRight"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Next Page" width="41" height="16" align="absmiddle"></span>
</div>
<div id="Area_W" class="Auct_Area" ref="N" arid="W"></div>
<div class="Fadebar"></div>
<div class="PageFrame" area="W">
<span tabindex="0" class="PageLeft"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Previous Page" width="41" height="16" align="absmiddle"></span>
<span class="PageText"> <span tabindex="0">page</span> <input id="curPWB" aria-label="Current page" type="text" curpg="1"> <span tabindex="0"> of </span><span tabindex="0" aria-label="Total pages" id="maxWB">1</span> </span>
<span tabindex="0" class="PageRight"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Next Page" width="41" height="16" align="absmiddle"></span>
</div>
</div>
<div class="Head_C">
<div tabindex="0" class="Sub_Title">Auctions Closed or Canceled</div>
<div class="Fadebar"></div>
<div class="PageFrame" area="C">
<span tabindex="0" class="PageLeft"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Previous Page" width="41" height="16" align="absmiddle"></span>
<span class="PageText"> <span tabindex="0"> page </span><input id="curPCA" aria-label="Current page" type="text" curpg="1"> <span tabindex="0"> of </span><span tabindex="0" aria-label="Total pages" id="maxCA">1</span></span>
<span tabindex="0" class="PageRight"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Next Page" width="41" height="16" align="absmiddle"></span>
</div>
<div id="Area_C" class="Auct_Area" ref="N" arid="C"><div id="AITEM_1498226" aria-label="Auction Details" class="AUCTION_ITEM PREVIEW" aid="1498226" rem="0" isset="1"><div class="AUCTION_STATS" tabindex="0"><div class="ASTAT_MSGA ASTAT_LBL">Auction Status</div><div class="ASTAT_MSGB Astat_DATA">Canceled per Order</div><div class="ASTAT_MSGC ASTAT_LBL"></div> <div class="ASTAT_MSGD Astat_DATA"></div><div class="ASTAT_MSG_SOLDTO_Label ASTAT_LBL"></div><div class="ASTAT_MSG_SOLDTO_MSG Astat_DATA"></div></div><div class="AUCTION_DETAILS"><table class="ad_tab"><tbody><tr><td tabindex="0" class="AD_LBL" scope="row">Auction Type:</td><td tabindex="0" class="AD_DTA">FORECLOSURE</td></tr><tr><td tabindex="0" class="AD_LBL" scope="row" aria-label="Case Number">Case #:</td><td tabindex="0" class="AD_DTA"> 2016-022214-CA-01</td></tr><tr><td tabindex="0" class="AD_LBL" scope="row">Final Judgment Amount:</td><td tabindex="0" class="AD_DTA">$353,041.78</td></tr><tr><td tabindex="0" class="AD_LBL" scope="row">Parcel ID:</td><td tabindex="0" class="AD_DTA"> <a href="https://www.miamidade.gov/Apps/PA/propertysearch/#/?folio=3220230081440" onclick="return showExitPopup();" target="_blank">32-2023-008-1440</a></td></tr><tr><td tabindex="0" class="AD_LBL" scope="row">Property Address:</td><td tabindex="0" class="AD_DTA">7355 POINCIANA CT</td></tr><tr><td class="AD_LBL" scope="row"></td><td tabindex="0" class="AD_DTA">MIAMI LAKES, FL- 33014</td></tr> <tr><td tabindex="0" class="AD_LBL" scope="row">Assessed Value:</td><td tabindex="0" class="AD_DTA">$345,036.00</td></tr><tr><td tabindex="0" class="AD_LBL" scope="row">Plaintiff Max Bid:</td><td tabindex="0" class="AD_DTA ASTAT_MSGPB">Hidden</td></tr></tbody></table></div></div><div class="AUCTION_ITEM_SPACER">&nbsp;</div> </div>
<div class="Fadebar"></div>
<div class="PageFrame" area="C">
<span tabindex="0" class="PageLeft"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Previous Page" width="41" height="16" align="absmiddle"></span>
<span class="PageText"><span tabindex="0">page </span></span> <input id="curPCB" aria-label="Current page" type="text" curpg="1"> <span tabindex="0"> of </span><span tabindex="0" aria-label="Total pages" id="maxCB">1</span>
<span tabindex="0" class="PageRight"><img src="/CORE/System/Themes/Theme_1/Images/Common/blank.gif" alt="Next Page" width="41" height="16" align="absmiddle"></span>
</div>
<div tabindex="0" id="popup" style="display:none"></div>
</div>
<div tabindex="0" class="Bottom_Mark">&nbsp;</div>
</div>
<div tabindex="0" id="DivBidBox" style="display:none"></div>
<div tabindex="0" id="ALB" style="display:none">1498226</div>
</td>
</tr>
</tbody></table>
<div id="MAIN_FOOTER" class="noPrint">
<div id="footer" align="center;">
<div id="footerDiv1">
<div id="leftNavImage" style="float:left; width:33%;">
<div id="pingbox"><div id="pingtxt">Connection</div>
<div id="ping_I" style="background-position: 50% 0%;"></div>
<div id="ping_S" style="background-position: 100% 0%;"></div>
</div>
<div id="ping_t">I:124 W:0</div>
</div>
<div id="footerlinks" style="height:100%; float:left; width:33%;" role="navigation" aria-label="Footer nav">
<ul class="navigation">
<li><a tabindex="0" href="index.cfm?zaction=home&amp;zmethod=aboutUs" class="footer">About Us</a> |</li>
<li><a tabindex="0" href="index.cfm?zaction=home&amp;zmethod=siteMap" class="footer">Site Map</a> |</li>
<li><a tabindex="0" href="index.cfm?zaction=home&amp;zmethod=privpol" class="footer">Privacy Policy</a> |</li>
<li><a tabindex="0" href="index.cfm?zaction=home&amp;zmethod=agreement" class="footer">User Agreement</a> |</li>
<li><a tabindex="0" href="index.cfm?zaction=home&amp;zmethod=welcome" class="footer">Bidder Letter</a> |</li>
<li><a tabindex="0" href="index.cfm?zaction=ContactUS&amp;zmethod=START" class="footer">Contact Us</a></li>
</ul>
<p>
<a href="http://www.realauction.com" tabindex="0" class="footer">© 2026 Realauction.com LLC</a>
</p>
</div>
<div style="float:right; width:33%;" align="right;">
<a href="http://www.realauction.com" class="footer" title="ip-10-0-32-115.Realauction.com">
<div style="float:right;" id="poweredBy" class="RATxt">Visit Realaction.com</div>
</a>
</div>
</div>
</div>
<div id="leavingSite" style="display:none;">
You have chosen to leave the site. Do you want to proceed?
</div>
</div>
<div id="popupmain" style="display:none"></div>
<div id="divSysAlert" style="display:none"></div>
<div id="SaveKey" key="" style="display:none"></div>
<script>
var jsEnvironment = '/CORE/System/JS/production';
</script>
<div id="DEBUG"><ol></ol></div></body></html>
@@ -0,0 +1,6 @@
<html><head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
</body></html>
+304
View File
@@ -0,0 +1,304 @@
clear search text buttonSubmit Search
For sale
Price
Beds & baths
Property type
Filters
Save search
# Miami-Dade County FL Single Family Homes
## 4,899 results
Sort: Homes for You
- [$50,000,000](https://www.zillow.com/homedetails/1413-N-Venetian-Way-Miami-Beach-FL-33139/43833721_zpid/)
- **7** bds
- **8** ba
- **10,689** sqft
New construction
[1413 N Venetian Way, Miami Beach, FL 33139](https://www.zillow.com/homedetails/1413-N-Venetian-Way-Miami-Beach-FL-33139/43833721_zpid/)
COMPASS FLORIDA, LLC
More
Showcase
Save
[![1413 N Venetian Way, Miami Beach, FL 33139](https://photos.zillowstatic.com/fp/9b622b8dffffae54ed8a49e01b9aa7b6-p_e.webp)](https://www.zillow.com/homedetails/1413-N-Venetian-Way-Miami-Beach-FL-33139/43833721_zpid/)
[![1413 N Venetian Way, Miami Beach, FL 33139](https://photos.zillowstatic.com/fp/e8a261a5a53302e3e71a6ff7d6cef697-p_e.webp)](https://www.zillow.com/homedetails/1413-N-Venetian-Way-Miami-Beach-FL-33139/43833721_zpid/)
[![1413 N Venetian Way, Miami Beach, FL 33139](https://photos.zillowstatic.com/fp/5fe39e51533a3edacbb391225b302b67-p_e.webp)](https://www.zillow.com/homedetails/1413-N-Venetian-Way-Miami-Beach-FL-33139/43833721_zpid/)
![](https://photos.zillowstatic.com/fp/a8b02d5ac61ac8b9a11bda64382b4f89-zillow_web_48_23.jpg)
- [$1,250,000](https://www.zillow.com/homedetails/1644-NW-8th-Ter-Miami-FL-33125/43825743_zpid/)
- **4** bds
- **5** ba
- **2,640** sqft
New construction
[1644 NW 8th Ter, Miami, FL 33125](https://www.zillow.com/homedetails/1644-NW-8th-Ter-Miami-FL-33125/43825743_zpid/)
MIAMI CAPITAL REALTY GROUP
More
Showcase
Save
[![1644 NW 8th Ter, Miami, FL 33125](https://photos.zillowstatic.com/fp/dbcf282fa106cca0456cd5e5e55ca23c-p_e.webp)](https://www.zillow.com/homedetails/1644-NW-8th-Ter-Miami-FL-33125/43825743_zpid/)
[![1644 NW 8th Ter, Miami, FL 33125](https://photos.zillowstatic.com/fp/e5be3bad8472b5f98b9293614b29d4c7-p_e.webp)](https://www.zillow.com/homedetails/1644-NW-8th-Ter-Miami-FL-33125/43825743_zpid/)
[![1644 NW 8th Ter, Miami, FL 33125](https://photos.zillowstatic.com/fp/f184edd2683033c41b724bedcbfaf476-p_e.webp)](https://www.zillow.com/homedetails/1644-NW-8th-Ter-Miami-FL-33125/43825743_zpid/)
![](https://photos.zillowstatic.com/fp/a8b02d5ac61ac8b9a11bda64382b4f89-zillow_web_48_23.jpg)
- Loading
Loading...
- [$635,000](https://www.zillow.com/homedetails/10220-SW-168th-St-Miami-FL-33157/44302315_zpid/)
- **4** bds
- **2** ba
- **1,224** sqft
House for sale
[10220 SW 168th St, Miami, FL 33157](https://www.zillow.com/homedetails/10220-SW-168th-St-Miami-FL-33157/44302315_zpid/)
LIFESTYLE INTERNATIONAL REALTY
More
Showcase
Save
[![10220 SW 168th St, Miami, FL 33157](https://photos.zillowstatic.com/fp/78a02e7e72be1eeed5fc587744bd8cd5-p_e.webp)](https://www.zillow.com/homedetails/10220-SW-168th-St-Miami-FL-33157/44302315_zpid/)
[![10220 SW 168th St, Miami, FL 33157](https://photos.zillowstatic.com/fp/592aa7792903533ecb19d8ea40d8b3b5-p_e.webp)](https://www.zillow.com/homedetails/10220-SW-168th-St-Miami-FL-33157/44302315_zpid/)
[![10220 SW 168th St, Miami, FL 33157](https://photos.zillowstatic.com/fp/bb05af08de78c9f73df55ebc47905530-p_e.webp)](https://www.zillow.com/homedetails/10220-SW-168th-St-Miami-FL-33157/44302315_zpid/)
![](https://photos.zillowstatic.com/fp/a8b02d5ac61ac8b9a11bda64382b4f89-zillow_web_48_23.jpg)
- [$875,000](https://www.zillow.com/homedetails/6651-SW-148th-Ct-Miami-FL-33193/44259458_zpid/)
- **3** bds
- **2** ba
- **1,892** sqft
House for sale
[6651 SW 148th Ct, Miami, FL 33193](https://www.zillow.com/homedetails/6651-SW-148th-Ct-Miami-FL-33193/44259458_zpid/)
LUXE PROPERTIES
More
Showcase
Save
[![6651 SW 148th Ct, Miami, FL 33193](https://photos.zillowstatic.com/fp/df39f68504c7cd2042063ec590b9bfb5-p_e.webp)](https://www.zillow.com/homedetails/6651-SW-148th-Ct-Miami-FL-33193/44259458_zpid/)
[![6651 SW 148th Ct, Miami, FL 33193](https://photos.zillowstatic.com/fp/315f8b9c0c1e1c086c103ec6cfffa95c-p_e.webp)](https://www.zillow.com/homedetails/6651-SW-148th-Ct-Miami-FL-33193/44259458_zpid/)
[![6651 SW 148th Ct, Miami, FL 33193](https://photos.zillowstatic.com/fp/92901644464f1a2d82efae462b32ee91-p_e.webp)](https://www.zillow.com/homedetails/6651-SW-148th-Ct-Miami-FL-33193/44259458_zpid/)
![](https://photos.zillowstatic.com/fp/a8b02d5ac61ac8b9a11bda64382b4f89-zillow_web_48_23.jpg)
- [$1,750,000](https://www.zillow.com/homedetails/29371-SW-173rd-Ct-Homestead-FL-33030/156412591_zpid/)
- **5** bds
- **4** ba
- **3,198** sqft
House for sale
[29371 SW 173rd Ct, Homestead, FL 33030](https://www.zillow.com/homedetails/29371-SW-173rd-Ct-Homestead-FL-33030/156412591_zpid/)
THE KEYES COMPANY
More
Showcase
Save
[![29371 SW 173rd Ct, Homestead, FL 33030](https://photos.zillowstatic.com/fp/576d1b74085f8d57d34b5b45b79b5c69-p_e.webp)](https://www.zillow.com/homedetails/29371-SW-173rd-Ct-Homestead-FL-33030/156412591_zpid/)
[![29371 SW 173rd Ct, Homestead, FL 33030](https://photos.zillowstatic.com/fp/103256d431cc66f7730de37da7b3bd3b-p_e.webp)](https://www.zillow.com/homedetails/29371-SW-173rd-Ct-Homestead-FL-33030/156412591_zpid/)
[![29371 SW 173rd Ct, Homestead, FL 33030](https://photos.zillowstatic.com/fp/58c99b3bd4c19e594778b556f6fde776-p_e.webp)](https://www.zillow.com/homedetails/29371-SW-173rd-Ct-Homestead-FL-33030/156412591_zpid/)
![](https://photos.zillowstatic.com/fp/a8b02d5ac61ac8b9a11bda64382b4f89-zillow_web_48_23.jpg)
- [$945,000](https://www.zillow.com/homedetails/6230-SW-27th-St-Miami-FL-33155/44187290_zpid/)
- **2** bds
- **2** ba
- **1,411** sqft
House for sale
[6230 SW 27th St, Miami, FL 33155](https://www.zillow.com/homedetails/6230-SW-27th-St-Miami-FL-33155/44187290_zpid/)
COLDWELL BANKER REALTY
More
Showcase
Save
[![6230 SW 27th St, Miami, FL 33155](https://photos.zillowstatic.com/fp/ab873ca4fb027d6bfb73d3b34eed057e-p_e.webp)](https://www.zillow.com/homedetails/6230-SW-27th-St-Miami-FL-33155/44187290_zpid/)
[![6230 SW 27th St, Miami, FL 33155](https://photos.zillowstatic.com/fp/ba27c0d521af9df42dea429909a82202-p_e.webp)](https://www.zillow.com/homedetails/6230-SW-27th-St-Miami-FL-33155/44187290_zpid/)
[![6230 SW 27th St, Miami, FL 33155](https://photos.zillowstatic.com/fp/8a88d7a7f88f32a4ec6e2d54a6cf9aa5-p_e.webp)](https://www.zillow.com/homedetails/6230-SW-27th-St-Miami-FL-33155/44187290_zpid/)
![](https://photos.zillowstatic.com/fp/a8b02d5ac61ac8b9a11bda64382b4f89-zillow_web_48_23.jpg)
- [$515,000](https://www.zillow.com/homedetails/3090-SE-7th-Pl-Homestead-FL-33033/44003765_zpid/)
- **4** bds
- **3** ba
- **2,060** sqft
House for sale
[3090 SE 7th Pl, Homestead, FL 33033](https://www.zillow.com/homedetails/3090-SE-7th-Pl-Homestead-FL-33033/44003765_zpid/)
LUXE PROPERTIES
More
Open: Sat 12-2pm (5/16)
Save
[![3090 SE 7th Pl, Homestead, FL 33033](https://photos.zillowstatic.com/fp/7c835c9cc30adc38fd91a84376a0a927-p_e.webp)](https://www.zillow.com/homedetails/3090-SE-7th-Pl-Homestead-FL-33033/44003765_zpid/)
[![3090 SE 7th Pl, Homestead, FL 33033](https://photos.zillowstatic.com/fp/535e80c4e6e6c99ecaaebe8cb630038a-p_e.webp)](https://www.zillow.com/homedetails/3090-SE-7th-Pl-Homestead-FL-33033/44003765_zpid/)
[![3090 SE 7th Pl, Homestead, FL 33033](https://photos.zillowstatic.com/fp/90cca03daae29db1e266e77a0eb179e7-p_e.webp)](https://www.zillow.com/homedetails/3090-SE-7th-Pl-Homestead-FL-33033/44003765_zpid/)
![](https://photos.zillowstatic.com/fp/a8b02d5ac61ac8b9a11bda64382b4f89-zillow_web_48_23.jpg)
- [$599,000](https://www.zillow.com/homedetails/1886-NW-68th-St-Miami-FL-33147/250761158_zpid/)
- **4** bds
- **3** ba
- **2,270** sqft
House for sale
[1886 NW 68th St, Miami, FL 33147](https://www.zillow.com/homedetails/1886-NW-68th-St-Miami-FL-33147/250761158_zpid/)
THE KEYES COMPANY
More
Showcase
Save
[![1886 NW 68th St, Miami, FL 33147](https://photos.zillowstatic.com/fp/5ba9f98bd50aee68881e761f2fec311d-p_e.webp)](https://www.zillow.com/homedetails/1886-NW-68th-St-Miami-FL-33147/250761158_zpid/)
[![1886 NW 68th St, Miami, FL 33147](https://photos.zillowstatic.com/fp/608d9343aae97e0f96079cb6fbf130cd-p_e.webp)](https://www.zillow.com/homedetails/1886-NW-68th-St-Miami-FL-33147/250761158_zpid/)
[![1886 NW 68th St, Miami, FL 33147](https://photos.zillowstatic.com/fp/be73ae4593b17ad1c82b7cfc21e7c8cc-p_e.webp)](https://www.zillow.com/homedetails/1886-NW-68th-St-Miami-FL-33147/250761158_zpid/)
![](https://photos.zillowstatic.com/fp/a8b02d5ac61ac8b9a11bda64382b4f89-zillow_web_48_23.jpg)
- [$11,100,000](https://www.zillow.com/homedetails/1460-S-Treasure-Dr-North-Bay-Village-FL-33141/44028079_zpid/)
- **6** bds
- **7** ba
- **6,816** sqft
House for sale
[1460 S Treasure Dr, North Bay Village, FL 33141](https://www.zillow.com/homedetails/1460-S-Treasure-Dr-North-Bay-Village-FL-33141/44028079_zpid/)
COLDWELL BANKER REALTY
More
Showcase
Save
[![1460 S Treasure Dr, North Bay Village, FL 33141](https://photos.zillowstatic.com/fp/a10359fdc45e62bdc6956a81decfc1c9-p_e.webp)](https://www.zillow.com/homedetails/1460-S-Treasure-Dr-North-Bay-Village-FL-33141/44028079_zpid/)
[![1460 S Treasure Dr, North Bay Village, FL 33141](https://photos.zillowstatic.com/fp/dc85a07bbd9929f015cef0658071aa21-p_e.webp)](https://www.zillow.com/homedetails/1460-S-Treasure-Dr-North-Bay-Village-FL-33141/44028079_zpid/)
[![1460 S Treasure Dr, North Bay Village, FL 33141](https://photos.zillowstatic.com/fp/978e4b609ef8a854cf9ebc9dbfb24f5b-p_e.webp)](https://www.zillow.com/homedetails/1460-S-Treasure-Dr-North-Bay-Village-FL-33141/44028079_zpid/)
![](https://photos.zillowstatic.com/fp/a8b02d5ac61ac8b9a11bda64382b4f89-zillow_web_48_23.jpg)
- Loading
Loading...
[Search](https://www.zillow.com/homes) [Updates](https://www.zillow.com/login/updates/) [Favorites](https://www.zillow.com/login/favorites/) [Home Loans](https://www.zillow.com/homeloans) [Inbox](https://www.zillow.com/login/inbox/)
+101
View File
@@ -0,0 +1,101 @@
"""Backfill clerk deal photos via County Property Appraiser sites (GRATIS).
Alternativa a backfill_zillow_photos.py — usa Playwright sobre PA sites,
cero costo Firecrawl.
Coverage actual: solo Broward (~70 deals). Phase 3.5.B: agregar Duval, etc.
Solo procesa deals que NO tienen foto AUN. Idempotent.
"""
from __future__ import annotations
import argparse, io, json, sys, time
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from deals_db import init_db, _get_conn
from data_fetchers.pa_photo_lookup import _fetch_broward_batch
COUNTY_TO_SOURCE = {
"Broward": "broward_clerk",
# "Duval": "duval_clerk", # Phase 3.5.B
# "Hillsborough": "hillsborough_clerk", # Phase 3.5.B
}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--county", default="Broward", help="County to backfill (default Broward)")
ap.add_argument("--limit", type=int, default=None)
ap.add_argument("--dry-run", action="store_true")
args = ap.parse_args()
init_db()
conn = _get_conn()
source = COUNTY_TO_SOURCE.get(args.county)
if not source:
print(f"ERROR: county '{args.county}' not yet supported. Available: {list(COUNTY_TO_SOURCE.keys())}")
return 1
# Find clerk deals WITHOUT photo
q = (
"SELECT id, parcel_id, address FROM deals "
"WHERE source = ? "
"AND parcel_id IS NOT NULL AND parcel_id != '' "
"AND (photos_urls IS NULL OR photos_urls = '' OR photos_urls = '[]') "
"ORDER BY id"
)
if args.limit:
q += f" LIMIT {args.limit}"
rows = conn.execute(q, (source,)).fetchall()
print(f"Found {len(rows)} {args.county} deals sin foto")
if not rows:
return 0
parcel_ids = [r["parcel_id"] for r in rows]
print(f"Starting batch fetch via {args.county} PA (Playwright, gratis)...")
print(f"Estimated time: ~{len(parcel_ids) * 12}s ({len(parcel_ids) * 12 // 60}m)")
print()
t0 = time.perf_counter()
results = _fetch_broward_batch(parcel_ids, timeout_seconds=20)
elapsed = time.perf_counter() - t0
hits = 0
misses = 0
for r in rows:
photo = results.get(r["parcel_id"])
if photo:
hits += 1
if not args.dry_run:
conn.execute(
"UPDATE deals SET photos_urls = ? WHERE id = ?",
(json.dumps([photo]), r["id"]),
)
print(f" ✓ id={r['id']} parcel={r['parcel_id']}{photo[-60:]}")
else:
misses += 1
if not args.dry_run:
conn.execute(
"UPDATE deals SET photos_urls = ? WHERE id = ?",
("[]", r["id"]),
)
print(f" ✗ id={r['id']} parcel={r['parcel_id']} no photo found")
print()
print("=" * 50)
print(f"DONE in {elapsed:.0f}s ({elapsed/60:.1f} min)")
print(f" Hits: {hits}/{len(rows)}")
print(f" Misses: {misses}/{len(rows)}")
print(f" Hit rate: {hits*100//len(rows)}%")
print(f" Cost: $0 (Playwright gratis)")
return 0
if __name__ == "__main__":
sys.exit(main())
+170
View File
@@ -0,0 +1,170 @@
"""scripts/backfill_zillow_photos.py — Buscar fotos de Zillow para deals sin foto.
ESTRATEGIA:
1. Query deals WHERE source IN (clerks) AND photos_urls IS NULL/empty
2. Para cada deal: address + county + 'FL' → fetch_zillow_photos_by_address
3. UPDATE photos_urls (JSON list)
4. Si no encuentra: graba '[]' como sentinel (no re-intentar)
RATE LIMIT: 1.2s entre requests (Firecrawl + ser ciudadano de bien con Zillow).
BUDGET: 1 credit por request.
USO:
python scripts/backfill_zillow_photos.py # all clerks pendientes
python scripts/backfill_zillow_photos.py --limit 20 # solo 20 deals
python scripts/backfill_zillow_photos.py --source duval_clerk # un source
"""
from __future__ import annotations
import argparse, io, json, sys, time
from pathlib import Path
from collections import Counter
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from dotenv import load_dotenv
load_dotenv(ROOT / ".env")
from deals_db import init_db, _get_conn, firecrawl_budget_status, record_firecrawl_usage
from data_fetchers.zillow_photo_lookup import fetch_zillow_photos_by_address
CLERK_SOURCES = (
"miami_dade_clerk", "duval_clerk", "broward_clerk",
"hillsborough_clerk", "orange_clerk", "indian_river_clerk",
"st_lucie_clerk", "martin_clerk", "lee_clerk", "sarasota_clerk",
"pinellas_clerk", "volusia_clerk", "brevard_clerk", "pasco_clerk",
"polk_clerk", "lake_clerk", "osceola_clerk", "seminole_clerk",
"manatee_clerk", "marion_clerk",
)
RATE_LIMIT_SECONDS = 1.2
def build_zillow_query_address(deal: dict) -> str:
"""Construye el address string para query Zillow.
Format: 'STREET, {COUNTY} COUNTY, {STATE}'
Probado en small sample: county-level query da mejor hit rate
que sin city (Zillow no encuentra) o con city wrong (mistakeo de ciudad).
"""
addr = (deal.get("address") or "").strip()
county = (deal.get("county") or "").strip()
state = (deal.get("state") or "FL").strip()
if county:
# Strip "County" suffix si ya viene en el campo
c = county.replace(" County", "").strip()
return f"{addr}, {c} COUNTY, {state}"
return f"{addr}, {state}"
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--limit", type=int, default=None, help="Cap on deals to process")
ap.add_argument("--source", type=str, default=None, help="Filter to one source")
ap.add_argument("--max-budget-pct", type=int, default=80,
help="Stop si Firecrawl usage hits N%% del budget mensual")
ap.add_argument("--dry-run", action="store_true", help="No DB writes")
args = ap.parse_args()
init_db()
conn = _get_conn()
# Build query: clerks with NO photos
source_filter = (
" AND source = ?" if args.source else
" AND source IN ({})".format(",".join("?" * len(CLERK_SOURCES)))
)
params = [args.source] if args.source else list(CLERK_SOURCES)
query = (
"SELECT id, source, address, city, state, county FROM deals "
"WHERE (photos_urls IS NULL OR photos_urls = '' OR photos_urls = '[]')"
+ source_filter
+ " AND address IS NOT NULL AND address != ''"
" ORDER BY id"
)
if args.limit:
query += f" LIMIT {args.limit}"
rows = conn.execute(query, params).fetchall()
total = len(rows)
print(f"Found {total} deals sin foto en clerks")
if not total:
return 0
# Budget check
b = firecrawl_budget_status()
print(f"Firecrawl budget: {b['credits_used']}/{b['credits_budget']} "
f"used ({b['usage_pct']}%) — pause threshold {b['pause_threshold_pct']}%")
if b["usage_pct"] >= args.max_budget_pct:
print(f"STOP: budget usage ({b['usage_pct']}%) >= max-budget-pct ({args.max_budget_pct}%)")
return 1
stats = Counter()
t0 = time.perf_counter()
for i, row in enumerate(rows, 1):
deal = dict(row)
addr_query = build_zillow_query_address(deal)
# Re-check budget mid-loop every 20 deals
if i % 20 == 0:
b = firecrawl_budget_status()
if b["usage_pct"] >= args.max_budget_pct:
print(f"[{i}/{total}] Budget reached {b['usage_pct']}% — stopping")
break
# Throttle
if i > 1:
time.sleep(RATE_LIMIT_SECONDS)
print(f"[{i}/{total}] deal #{deal['id']} ({deal['source']}) — {addr_query[:70]}")
photos, meta = fetch_zillow_photos_by_address(addr_query)
# Record Firecrawl usage
if meta.get("credits_used") and not args.dry_run:
try:
record_firecrawl_usage(
source="zillow_photo_backfill",
credits=meta["credits_used"],
url=meta.get("url_attempted"),
description=f"photos for clerk deal id={deal['id']} ({deal['source']})",
)
except Exception as e:
print(f" [warn] could not record firecrawl_usage: {e}")
if photos:
stats["found"] += 1
print(f" OK: {len(photos)} fotos")
if not args.dry_run:
conn.execute(
"UPDATE deals SET photos_urls = ?, last_seen_at = ? WHERE id = ?",
(json.dumps(photos), time.strftime("%Y-%m-%dT%H:%M:%S"), deal["id"]),
)
else:
stats["empty"] += 1
reason = meta.get("error") or "no photos in markdown"
print(f" EMPTY: {reason[:100]}")
if not args.dry_run:
# Mark as attempted by setting empty list (vs NULL)
conn.execute(
"UPDATE deals SET photos_urls = ? WHERE id = ?",
("[]", deal["id"]),
)
elapsed = time.perf_counter() - t0
print()
print("=" * 60)
print(f"DONE in {elapsed:.0f}s")
print(f" Photos found: {stats['found']}/{total}")
print(f" Empty result: {stats['empty']}/{total}")
print(f" Hit rate: {stats['found']/total*100:.1f}%")
b = firecrawl_budget_status()
print(f" Firecrawl now: {b['credits_used']}/{b['credits_budget']} ({b['usage_pct']}%)")
return 0
if __name__ == "__main__":
sys.exit(main())
+48
View File
@@ -0,0 +1,48 @@
"""Classify the 9 zillow deals (status='new') manually."""
from __future__ import annotations
import io, sys, time
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
import data_fetchers # noqa: F401
from deals_db import list_deals, update_classification, init_db
from deal_classifier import classify_deal
init_db()
# Get zillow deals with status='new'
zd = list_deals(source="zillow", status="new", limit=20)
print(f"Zillow deals with status=new: {len(zd)}")
if zd:
print("Classifying...")
t0 = time.perf_counter()
for d in zd:
result = classify_deal(d)
update_classification(
deal_id=d["id"],
status=result["classification_status"],
score=result["score"],
reasons=result["reasons"],
strategy=result["strategy"],
)
addr = (d.get("address") or "?")[:55]
print(f" ${d.get('listing_price'):>11,.0f} | score={result['score']:>3} {result['classification_status']:<19} {result['strategy']:<14} | {addr}")
elapsed = time.perf_counter() - t0
print(f"\nClassified {len(zd)} deals in {elapsed:.1f}s ({elapsed/len(zd):.1f}s/deal avg)")
# Show final state
print()
print("=== Final Zillow deals state ===")
zd_all = list_deals(source="zillow", limit=20)
for d in zd_all:
addr = (d.get("address") or "?")[:55]
score = d.get("classification_score")
cls = d.get("classification_status") or "?"
strat = d.get("classification_strategy") or "?"
price = d.get("listing_price") or 0
print(f" ${price:>11,.0f} | score={score!s:>3} {cls:<19} {strat:<14} | {addr}")
+73
View File
@@ -0,0 +1,73 @@
"""Cleanup script: NULL out duplicate photo_urls so user can re-scrape.
Strategy: any photo_url shared by 2+ deals is suspect. NULL them ALL — let
the next Zillow scrape (now with fixed parser) re-populate correctly.
Run with --dry-run first to preview impact.
"""
from __future__ import annotations
import argparse
import sqlite3
import sys
def main():
p = argparse.ArgumentParser()
p.add_argument("--dry-run", action="store_true", help="Show impact without writing")
p.add_argument("--db", default="data/deals.db")
args = p.parse_args()
conn = sqlite3.connect(args.db)
cur = conn.cursor()
# Find duplicate photo_urls
cur.execute("""
SELECT photos_urls, COUNT(DISTINCT id) AS deals_sharing,
GROUP_CONCAT(id) AS deal_ids,
GROUP_CONCAT(DISTINCT substr(address,1,50)) AS addresses
FROM deals
WHERE photos_urls IS NOT NULL AND photos_urls != '[]' AND photos_urls != ''
GROUP BY photos_urls
HAVING deals_sharing > 1
""")
duplicates = cur.fetchall()
if not duplicates:
print("No duplicate photos found — DB clean.")
return
print(f"Found {len(duplicates)} duplicate photo cases affecting "
f"{sum(r[1] for r in duplicates)} deals total.")
print()
affected_deal_ids: list[int] = []
for photos_url, count, deal_ids_csv, addresses in duplicates:
deal_ids = [int(x) for x in deal_ids_csv.split(",")]
affected_deal_ids.extend(deal_ids)
photo_preview = photos_url[:80].encode("ascii", "replace").decode("ascii")
print(f" {count} deals share: {photo_preview}")
print(f" deal IDs: {deal_ids[:10]}{'...' if len(deal_ids) > 10 else ''}")
print(f" addresses: {addresses[:100].encode('ascii','replace').decode('ascii')}")
print()
print(f"Total affected deals: {len(affected_deal_ids)}")
if args.dry_run:
print("\nDRY RUN — no changes written. Re-run without --dry-run to clean.")
return
# NULL out these deals' photos_urls
placeholders = ",".join("?" * len(affected_deal_ids))
cur.execute(
f"UPDATE deals SET photos_urls = NULL WHERE id IN ({placeholders})",
affected_deal_ids,
)
conn.commit()
print(f"\nDONE. NULL'd photos_urls for {cur.rowcount} deals.")
print("Next Zillow scrape (with fixed parser) will re-populate correctly.")
conn.close()
if __name__ == "__main__":
main()
+105
View File
@@ -0,0 +1,105 @@
"""Diagnose Civitek results page structure — save HTML + look for tables."""
from pathlib import Path
def diag():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "civitek"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context().new_page()
# Capture POST requests to see exactly what gets submitted
captured_posts: list[dict] = []
def on_request(req):
if req.method == "POST":
try:
body = req.post_data or ""
except Exception:
body = ""
captured_posts.append({"url": req.url, "body": body[:3000]})
page.on("request", on_request)
# Walk through
page.goto("https://www.civitekflorida.com/ocrs/county/27/")
page.wait_for_timeout(1500)
page.locator("button:has-text('Public')").first.click()
page.wait_for_timeout(2500)
page.locator("button:has-text('I Agree')").first.click()
page.wait_for_timeout(2500)
# Set value via JS with proper events that JSF listens to
page.evaluate("""
const inp = document.getElementById('form:search_tab:businessname');
inp.focus();
inp.value = 'BANK OF AMERICA';
inp.dispatchEvent(new Event('input', { bubbles: true }));
inp.dispatchEvent(new Event('change', { bubbles: true }));
inp.dispatchEvent(new Event('blur', { bubbles: true }));
""")
page.wait_for_timeout(500)
val_after = page.locator("#form\\:search_tab\\:businessname").input_value()
print(f"businessname value (via JS): {val_after!r}")
# Find Search button — there's only one (j_idt1095) but ID is unstable.
# Better: query by JS by text + type=submit
btn_id = page.evaluate("""
() => {
const btns = Array.from(document.querySelectorAll('button[type=submit]'));
const m = btns.find(b => b.innerText.trim() === 'Search');
return m ? m.id : null;
}
""")
print(f"Search button id detected: {btn_id!r}")
if btn_id:
# Click via JS to avoid CSS selector escaping issues with ":"
page.evaluate(f"document.getElementById('{btn_id}').click()")
else:
search = page.locator("button:has(.ui-button-text:text-is('Search'))").first
search.click()
# Wait extra long
page.wait_for_timeout(12000)
print(f"URL: {page.url}")
body = page.inner_text("body")
print(f"\nBody length: {len(body)}")
print(f"\nFirst 3000 chars of body:\n{body[:3000]}")
# Check for "records" text indicating count
for kw in ["records", "found", "match", "search", "no result", "displaying"]:
import re
for m in re.finditer(rf".{{0,80}}{kw}.{{0,80}}", body, re.IGNORECASE):
t = m.group(0).strip()
if t and len(t) < 200:
print(f"\n Match '{kw}': {t}")
break
# Look for all tables
print(f"\n\nTables on page: {page.locator('table').count()}")
print(f"DataTables (.ui-datatable): {page.locator('.ui-datatable').count()}")
print(f"Data grids (role=grid): {page.locator('[role=grid]').count()}")
# Save full HTML
full_html = page.content()
(out_dir / "07_business_search_results.html").write_text(full_html, encoding="utf-8")
page.screenshot(path=str(out_dir / "07_results.png"), full_page=True)
# Print snippet around any "datatable" reference
idx = full_html.lower().find("ui-datatable")
if idx > 0:
print(f"\nHTML around 'ui-datatable':\n{full_html[idx:idx+800]}")
# Print captured POSTs
print(f"\n\n===== Captured POST requests ({len(captured_posts)}) =====")
for i, p in enumerate(captured_posts):
print(f"\n[{i}] URL: {p['url']}")
print(f" BODY ({len(p['body'])} chars): {p['body'][:1000]}")
browser.close()
if __name__ == "__main__":
diag()
+71
View File
@@ -0,0 +1,71 @@
"""Explore or.duvalclerk.com structure for lis pendens search."""
from __future__ import annotations
import io, sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
USER_AGENT = "AR-House/1.0 (real estate investment analysis)"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context(user_agent=USER_AGENT).new_page()
page.set_default_timeout(20_000)
print("Loading or.duvalclerk.com ...")
page.goto("https://or.duvalclerk.com/", wait_until="networkidle", timeout=20_000)
print("URL:", page.url, "Title:", page.title())
print()
# Look for all links
print("--- ALL LINKS ---")
for i, link in enumerate(page.locator("a").all()[:40]):
href = link.get_attribute("href") or ""
text = (link.text_content() or "").strip()[:80]
if href and not href.startswith("javascript:") and not href.startswith("mailto:"):
print(f" [{i}] href={href} text='{text}'")
print()
print("--- ALL BUTTONS ---")
for i, btn in enumerate(page.locator("button, input[type='button'], input[type='submit']").all()[:15]):
attrs = page.evaluate("""(el) => {
const out = {};
for (const a of el.attributes) out[a.name] = a.value;
return out;
}""", btn.element_handle())
text = (btn.text_content() or "").strip()[:50]
print(f" [{i}] {attrs} text='{text}'")
# Save HTML
with open("scripts/_duval_or_landing.html", "w", encoding="utf-8") as f:
f.write(page.content())
print(f"\nFull HTML saved: scripts/_duval_or_landing.html")
# Look for forms
print()
print("--- FORMS ---")
forms = page.locator("form").all()
print(f" {len(forms)} forms")
for i, f in enumerate(forms[:3]):
action = f.get_attribute("action") or ""
method = f.get_attribute("method") or ""
print(f" [{i}] action={action} method={method}")
# Look for inputs
print()
print("--- INPUTS (first 20) ---")
for i, inp in enumerate(page.locator("input").all()[:20]):
attrs = page.evaluate("""(el) => {
const out = {};
for (const a of el.attributes) out[a.name] = a.value;
return out;
}""", inp.element_handle())
print(f" [{i}] {attrs}")
# Check if there's a disclaimer that needs accepting
print()
print("--- Text content (first 1000 chars) ---")
body_text = page.locator("body").inner_text()[:1000]
print(body_text)
browser.close()
+82
View File
@@ -0,0 +1,82 @@
"""Dump the Duval Property Appraiser search page DOM to find real selectors."""
from __future__ import annotations
import io, sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
USER_AGENT = "AR-House/1.0 (real estate investment analysis)"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context(user_agent=USER_AGENT).new_page()
page.set_default_timeout(15_000)
# Try landing page first
print("=" * 70)
print("LANDING: https://paopropertysearch.coj.net/")
print("=" * 70)
page.goto("https://paopropertysearch.coj.net/", wait_until="domcontentloaded")
print("Final URL:", page.url)
print()
# Dump ALL inputs on the page
print("--- ALL <input> elements ---")
inputs = page.locator("input").all()
for i, inp in enumerate(inputs[:30]):
try:
attrs = page.evaluate("""(el) => {
const out = {};
for (const a of el.attributes) out[a.name] = a.value;
return out;
}""", inp.element_handle())
print(f" [{i}] {attrs}")
except Exception as e:
print(f" [{i}] (couldn't get attrs: {e})")
print()
print("--- ALL <select> elements ---")
for i, sel in enumerate(page.locator("select").all()[:10]):
try:
attrs = page.evaluate("""(el) => {
const out = {};
for (const a of el.attributes) out[a.name] = a.value;
return out;
}""", sel.element_handle())
print(f" [{i}] {attrs}")
except Exception as e:
print(f" [{i}] (couldn't get attrs: {e})")
print()
print("--- ALL <a> links (first 30) ---")
for i, link in enumerate(page.locator("a").all()[:30]):
try:
href = link.get_attribute("href") or ""
text = (link.text_content() or "").strip()[:80]
if href and not href.startswith("#"):
print(f" [{i}] href={href} text='{text}'")
except Exception:
pass
print()
print("--- All buttons + submit inputs ---")
for i, btn in enumerate(page.locator("button, input[type='submit'], input[type='button']").all()[:15]):
try:
attrs = page.evaluate("""(el) => {
const out = {};
for (const a of el.attributes) out[a.name] = a.value;
return out;
}""", btn.element_handle())
text = (btn.text_content() or "").strip()[:50]
print(f" [{i}] {attrs} text='{text}'")
except Exception:
pass
# Save full HTML for inspection
html = page.content()
out_file = "scripts/_duval_pa_landing.html"
with open(out_file, "w", encoding="utf-8") as f:
f.write(html)
print(f"\nFull HTML saved to: {out_file} ({len(html):,} chars)")
browser.close()
+102
View File
@@ -0,0 +1,102 @@
"""Inspeccionar el submit real: que pasa cuando llenamos el form y clickeamos Search."""
from __future__ import annotations
import io, sys, time
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
USER_AGENT = "AR-House/1.0 (real estate investment analysis)"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(user_agent=USER_AGENT)
page = context.new_page()
page.set_default_timeout(20_000)
# Capture console + network errors
page.on("console", lambda msg: print(f" CONSOLE [{msg.type}]: {msg.text[:200]}"))
page.on("pageerror", lambda err: print(f" PAGEERROR: {err}"))
page.goto("https://paopropertysearch.coj.net/Basic/Search.aspx", wait_until="domcontentloaded")
print("Loaded:", page.url)
# Llenar form
page.locator("#ctl00_cphBody_tbStreetNumber").fill("3245")
print("Street # filled")
# Inspeccionar opciones del dropdown direction
print("\nDirection options:")
opts = page.locator("#ctl00_cphBody_ddStreetPrefix option").all()
for o in opts[:15]:
val = o.get_attribute("value") or ""
txt = (o.text_content() or "").strip()
print(f" value='{val}' text='{txt}'")
print("\nSuffix options:")
opts = page.locator("#ctl00_cphBody_ddStreetSuffix option").all()
for o in opts[:30]:
val = o.get_attribute("value") or ""
txt = (o.text_content() or "").strip()
print(f" value='{val}' text='{txt}'")
# Probar select N (direction)
try:
page.locator("#ctl00_cphBody_ddStreetPrefix").select_option(value="N")
print("Direction N selected")
except Exception as e:
print(f"Direction N select FAILED: {e}")
page.locator("#ctl00_cphBody_tbStreetName").fill("PEARL")
print("Street name PEARL filled")
try:
page.locator("#ctl00_cphBody_ddStreetSuffix").select_option(value="ST")
print("Suffix ST selected")
except Exception as e:
print(f"Suffix select FAILED: {e}")
# Click Search
print("\nClicking Search...")
page.locator("#ctl00_cphBody_bSearch").click()
# Wait a few seconds and capture state
time.sleep(4)
print("\nPost-click URL:", page.url)
print("Post-click title:", page.title())
# Dump for inspection
html = page.content()
with open("scripts/_duval_pa_after_submit.html", "w", encoding="utf-8") as f:
f.write(html)
print(f"HTML saved: scripts/_duval_pa_after_submit.html ({len(html):,} chars)")
# Buscar error messages explícitos
for sel in [".error", "#error", "[class*='error']", "[class*='alert']", ".validation-summary"]:
try:
count = page.locator(sel).count()
if count > 0:
for i in range(min(count, 5)):
txt = (page.locator(sel).nth(i).text_content() or "").strip()
if txt:
print(f" ERR [{sel}]: {txt[:200]}")
except Exception:
pass
# Buscar table de resultados
tables = page.locator("table").all()
print(f"\nFound {len(tables)} tables on results page")
for i, t in enumerate(tables[:5]):
try:
rows = t.locator("tr").all()
if len(rows) > 1:
print(f" Table [{i}] has {len(rows)} rows. First row:")
first_cells = rows[0].locator("td, th").all()[:8]
print(f" Headers: {[c.text_content().strip()[:30] for c in first_cells]}")
if len(rows) > 1:
data_cells = rows[1].locator("td").all()[:8]
print(f" Row 1: {[c.text_content().strip()[:30] for c in data_cells]}")
except Exception as e:
print(f" Table [{i}] error: {e}")
browser.close()
+76
View File
@@ -0,0 +1,76 @@
"""Probar variantes del address 3245 Pearl St en Duval."""
from __future__ import annotations
import io, sys, time
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
USER_AGENT = "AR-House/1.0 (real estate investment analysis)"
variants = [
{"num": "3245", "prefix": "N", "name": "PEARL", "suffix": "ST"},
{"num": "3245", "prefix": "", "name": "PEARL", "suffix": "ST"},
{"num": "3245", "prefix": "", "name": "PEARL", "suffix": ""},
{"num": "3245", "prefix": "N", "name": "PEARL", "suffix": ""},
# Probar otra direccion conocida (Jacksonville fl typical address)
{"num": "1234", "prefix": "", "name": "MAIN", "suffix": "ST"},
]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(user_agent=USER_AGENT)
for v in variants:
page = context.new_page()
page.set_default_timeout(20_000)
page.goto("https://paopropertysearch.coj.net/Basic/Search.aspx",
wait_until="networkidle", timeout=20_000)
page.locator("#ctl00_cphBody_tbStreetNumber").fill(v["num"])
if v["prefix"]:
page.locator("#ctl00_cphBody_ddStreetPrefix").select_option(value=v["prefix"])
page.locator("#ctl00_cphBody_tbStreetName").fill(v["name"])
if v["suffix"]:
try:
page.locator("#ctl00_cphBody_ddStreetSuffix").select_option(value=v["suffix"])
except Exception:
pass
page.evaluate("""() => {
const form = document.forms[0] || document.querySelector('form');
form.action = 'Results.aspx';
let hidden = document.createElement('input');
hidden.type = 'hidden';
hidden.name = 'ctl00$cphBody$bSearch';
hidden.value = 'Search';
form.appendChild(hidden);
form.submit();
}""")
try:
page.wait_for_url("**Results.aspx**", timeout=10_000)
page.wait_for_load_state("networkidle", timeout=8_000)
except Exception as e:
print(f" variant {v}: submit fallo ({e})")
page.close()
continue
# Check results
body_text = page.locator("body").inner_text()
if "No Results Found" in body_text:
print(f" variant {v}: NO RESULTS")
elif "No information available" in body_text:
print(f" variant {v}: NO INFO AVAILABLE")
else:
# Print first table info
tables = page.locator("table").all()
print(f" variant {v}: {len(tables)} tables")
for t in tables[:1]:
rows = t.locator("tr").all()
for j, r in enumerate(rows[:3]):
cells = r.locator("td, th").all()
texts = [(c.text_content() or "").strip()[:40] for c in cells]
print(f" Row {j}: {texts}")
page.close()
time.sleep(2) # rate limit
browser.close()
+82
View File
@@ -0,0 +1,82 @@
"""Dump Results.aspx HTML to find owner/RE# patterns."""
from __future__ import annotations
import io, sys, time
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
USER_AGENT = "AR-House/1.0 (real estate investment analysis)"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(user_agent=USER_AGENT)
page = context.new_page()
page.set_default_timeout(20_000)
page.goto("https://paopropertysearch.coj.net/Basic/Search.aspx",
wait_until="networkidle", timeout=20_000)
page.locator("#ctl00_cphBody_tbStreetNumber").fill("3245")
page.locator("#ctl00_cphBody_ddStreetPrefix").select_option(value="N")
page.locator("#ctl00_cphBody_tbStreetName").fill("PEARL")
page.locator("#ctl00_cphBody_ddStreetSuffix").select_option(value="ST")
# Submit via form.submit
page.evaluate("""() => {
const form = document.forms[0] || document.querySelector('form');
form.action = 'Results.aspx';
let hidden = document.createElement('input');
hidden.type = 'hidden';
hidden.name = 'ctl00$cphBody$bSearch';
hidden.value = 'Search';
form.appendChild(hidden);
form.submit();
}""")
page.wait_for_url("**Results.aspx**", timeout=10_000)
page.wait_for_load_state("networkidle", timeout=10_000)
print("URL:", page.url)
print("Title:", page.title())
print()
# Buscar tablas
tables = page.locator("table").all()
print(f"Tables: {len(tables)}")
for i, t in enumerate(tables):
try:
rows = t.locator("tr").all()
print(f"\n--- Table [{i}] ({len(rows)} rows) ---")
for j, r in enumerate(rows[:5]):
cells = r.locator("td, th").all()
texts = [(c.text_content() or "").strip()[:60] for c in cells]
print(f" Row {j}: {texts}")
except Exception as e:
print(f" Error: {e}")
# Dump full HTML for parsing
html = page.content()
with open("scripts/_duval_pa_results.html", "w", encoding="utf-8") as f:
f.write(html)
print(f"\nFull HTML: scripts/_duval_pa_results.html ({len(html):,} chars)")
# Search for any text containing "Owner" or "RE"
print("\n--- Text matches for 'Owner', 'RE Number', 'Year Built' ---")
body_text = page.locator("body").inner_text()
for kw in ["Owner", "RE Number", "RE #", "Year Built", "Just Value", "Assessed Value", "Last Sale"]:
if kw in body_text:
idx = body_text.find(kw)
print(f" '{kw}' at pos {idx}: ...{body_text[max(0,idx-30):idx+100]}...")
else:
print(f" '{kw}': NOT FOUND")
# Buscar <a> que apunten a propiedades individuales
links = page.locator("a").all()
print(f"\n--- Links ({len(links)} total) ---")
for i, l in enumerate(links):
href = l.get_attribute("href") or ""
if "Property" in href or "RE=" in href or "RealEstate" in href or "Detail" in href:
print(f" [{i}] href={href} text='{(l.text_content() or '').strip()[:50]}'")
if i > 50:
break
browser.close()
+142
View File
@@ -0,0 +1,142 @@
"""Intercept network calls during HUD search to find the JSON API."""
from __future__ import annotations
import io, sys, time, json
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
REAL_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
captured_requests = []
captured_responses = []
def on_request(req):
url = req.url
if any(kw in url.lower() for kw in ["api", "search", "property", "listing", "result", "auto"]):
captured_requests.append({
"method": req.method,
"url": url,
"headers": dict(req.headers),
"post_data": req.post_data,
})
def on_response(resp):
url = resp.url
if any(kw in url.lower() for kw in ["api", "search", "property", "listing", "result", "auto"]):
try:
ct = resp.headers.get("content-type", "")
if "json" in ct:
body = resp.json()
captured_responses.append({
"url": url,
"status": resp.status,
"content_type": ct,
"body_preview": json.dumps(body)[:500] if body else "",
"body_keys": list(body.keys()) if isinstance(body, dict) else type(body).__name__,
})
elif resp.status >= 200 and resp.status < 400:
captured_responses.append({
"url": url,
"status": resp.status,
"content_type": ct,
"body_size": len(resp.body() or b""),
})
except Exception:
pass
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent=REAL_UA, viewport={"width": 1400, "height": 900},
locale="en-US", timezone_id="America/New_York",
)
page = context.new_page()
page.on("request", on_request)
page.on("response", on_response)
page.set_default_timeout(30_000)
# 1. Load landing
print("[1] Loading landing...")
page.goto("https://www.hudhomestore.gov/", wait_until="networkidle")
time.sleep(2)
# 2. Type "FL" via JavaScript directly (label intercepts pointer events)
print("[2] Setting cityStateZip value via JS + triggering oninput...")
page.evaluate("""() => {
const inp = document.getElementById('cityStateZip');
inp.value = 'FL';
// Trigger the oninput handler manually
const event = new Event('input', { bubbles: true });
inp.dispatchEvent(event);
// Also call the explicit ysi handler if exists
if (typeof ysi !== 'undefined' && ysi.corpsearchfilter) {
ysi.corpsearchfilter.changeInput('FL', 'home');
}
}""")
time.sleep(3) # Wait for autocomplete
# 3. Inspect autocomplete options
print("[3] Inspecting autocomplete dropdown...")
autocomplete_items = page.locator("#cityStateZipautocomplete-list li, [role='option']").all()
print(f" {len(autocomplete_items)} autocomplete items")
for i, item in enumerate(autocomplete_items[:10]):
text = (item.text_content() or "").strip()[:80]
print(f" [{i}] {text!r}")
# 4. Click "Florida" option via JS
if autocomplete_items:
for item in autocomplete_items:
text = (item.text_content() or "").lower()
if "florida" in text:
print(f"[4] Clicking Florida option: {text[:60]}")
try:
item.click(force=True)
except Exception as e:
print(f" click failed: {e}, trying evaluate")
page.evaluate("(el) => el.click()", item.element_handle())
break
else:
# fallback: click first
print("[4] No Florida match found; clicking first item")
try:
autocomplete_items[0].click(force=True)
except Exception:
page.evaluate("(el) => el.click()", autocomplete_items[0].element_handle())
time.sleep(5)
# 5. Wait for search results to populate
page.wait_for_load_state("networkidle", timeout=15_000)
time.sleep(3)
print()
print("[5] After search — final URL:", page.url)
print()
# Show all captured API endpoints
print("=" * 60)
print("CAPTURED REQUESTS")
print("=" * 60)
for req in captured_requests[:30]:
url_short = req["url"][:150]
print(f" {req['method']} {url_short}")
if req.get("post_data"):
print(f" post_data: {req['post_data'][:200]}")
print()
print("=" * 60)
print("CAPTURED RESPONSES (JSON only)")
print("=" * 60)
for resp in captured_responses[:20]:
url_short = resp["url"][:120]
print(f"\n {resp['status']} {url_short}")
print(f" ct: {resp['content_type']}")
if resp.get("body_keys"):
print(f" body_keys: {resp['body_keys']}")
if resp.get("body_preview"):
print(f" preview: {resp['body_preview']}")
browser.close()
+119
View File
@@ -0,0 +1,119 @@
"""Find the exact DOM structure for HUD property cards."""
from __future__ import annotations
import io, sys, time, re
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
REAL_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context(
user_agent=REAL_UA, viewport={"width": 1400, "height": 900},
).new_page()
page.set_default_timeout(30_000)
page.goto("https://www.hudhomestore.gov/", wait_until="networkidle")
time.sleep(2)
page.goto("https://www.hudhomestore.gov/searchresult?citystate=FL", wait_until="networkidle")
time.sleep(6)
# Find each property card via its case # link pattern
# Look for ALL elements containing a 'Case #:' label
print("--- Finding all cards via case # text ---")
# Use Playwright to find parents of "Case #" text
# Each property card seems to contain the price, address, and case # near each other
# Find via XPath the nearest div ancestor of "Case #:" text
cards = page.evaluate("""
() => {
const allElements = Array.from(document.querySelectorAll('*'));
const cardLikeElements = new Set();
for (const el of allElements) {
if (!el.children.length) continue;
const text = el.textContent || '';
const hasCase = /Case #:\\s*\\d/.test(text);
const hasPrice = /\\$[\\d,]+/.test(text);
const hasBeds = /\\d+\\s+Beds/.test(text);
if (hasCase && hasPrice && hasBeds && text.length < 600) {
// Likely a card-sized element
cardLikeElements.add(el);
}
}
// Return their tag/class info
return Array.from(cardLikeElements).slice(0, 5).map(el => ({
tagName: el.tagName,
id: el.id || null,
className: el.className || null,
textPreview: (el.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 200),
}));
}
""")
print(f"Found {len(cards)} card-like elements:")
for i, c in enumerate(cards):
print(f" [{i}] tag={c['tagName']} class={c['className']!r}")
print(f" preview: {c['textPreview']!r}")
# Now find ALL such cards (no slice)
all_cards_count = page.evaluate("""
() => {
const allElements = Array.from(document.querySelectorAll('*'));
const seen = new Set();
for (const el of allElements) {
if (!el.children.length) continue;
const text = el.textContent || '';
if (/Case #:\\s*\\d/.test(text) && /\\$[\\d,]+/.test(text)
&& /\\d+\\s+Beds/.test(text) && text.length < 600) {
seen.add(el);
}
}
return seen.size;
}
""")
print(f"\nTotal card-like elements: {all_cards_count}")
# Now look at SMALL elements (deepest containers of one card)
# Find ones where text is unique to one case
print("\n--- Finding the most specific 'card' element per property ---")
cards_details = page.evaluate("""
() => {
const allElements = Array.from(document.querySelectorAll('*'));
// For each element with Case #, find the SMALLEST ancestor (deepest) that contains exactly ONE case #
const caseMatches = {}; // caseNumber -> array of elements
for (const el of allElements) {
if (!el.children || !el.children.length) continue;
const text = el.textContent || '';
const cases = (text.match(/Case #:\\s*\\d{3}-\\d{6}/g) || []);
if (cases.length === 1) {
const num = cases[0];
if (!caseMatches[num]) caseMatches[num] = [];
caseMatches[num].push(el);
}
}
// For each case, the smallest element is the card
const cards = [];
for (const [caseNum, elements] of Object.entries(caseMatches)) {
// Sort by element size (smallest = most specific)
elements.sort((a, b) => (a.textContent || '').length - (b.textContent || '').length);
const smallest = elements[0];
cards.push({
case_number: caseNum,
tagName: smallest.tagName,
id: smallest.id || null,
className: smallest.className || null,
text_length: (smallest.textContent || '').length,
text_preview: (smallest.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 250),
});
}
return cards.slice(0, 10); // first 10
}
""")
print(f"\nUnique cards found: {len(cards_details)}")
for i, c in enumerate(cards_details):
print(f"\n [{i}] {c['case_number']}")
print(f" tag={c['tagName']} class={c['className']!r}")
print(f" text_length={c['text_length']}")
print(f" preview: {c['text_preview']!r}")
browser.close()
+74
View File
@@ -0,0 +1,74 @@
"""Probe the discovered URL pattern ?citystate=FL."""
from __future__ import annotations
import io, sys, time
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
REAL_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent=REAL_UA, viewport={"width": 1400, "height": 900},
locale="en-US", timezone_id="America/New_York",
)
page = context.new_page()
page.set_default_timeout(30_000)
# First load landing (set cookies)
page.goto("https://www.hudhomestore.gov/", wait_until="networkidle")
time.sleep(2)
# Then load FL results
print("Loading citystate=FL...")
page.goto("https://www.hudhomestore.gov/searchresult?citystate=FL",
wait_until="networkidle", timeout=30_000)
print(f"URL: {page.url}")
print(f"Title: {page.title()}")
# Wait longer for SPA to render property cards
time.sleep(6)
# Check body for results count
body_text = page.locator("body").inner_text()
print()
print("--- BODY TEXT (first 1500) ---")
print(body_text[:1500])
# Count property cards / listings
print()
print("--- LISTING SELECTORS ---")
for sel in ["[class*='property-card']", "[class*='propertyCard']",
"[class*='listing-item']", "[class*='listing-card']",
"[class*='home-card']", "[class*='result-item']",
"[data-property]", "article", ".property", ".listing",
"[id*='property-list']"]:
n = page.locator(sel).count()
if n > 0:
print(f" {sel}: {n}")
# Save full HTML
html = page.content()
with open("scripts/_hud_citystate_fl.html", "w", encoding="utf-8") as f:
f.write(html)
print(f"\nHTML saved: scripts/_hud_citystate_fl.html ({len(html):,} chars)")
# Try common property card class patterns from Yardi
candidates = page.locator("li[id^='property'], div[id^='property'], li[class*='listing'], li[class*='card']").all()
print(f"\nPropertycards via id^=property selector: {len(candidates)}")
for i, el in enumerate(candidates[:5]):
txt = (el.text_content() or "").strip()[:300]
print(f" [{i}] {txt!r}")
# Try article elements
articles = page.locator("article").all()
print(f"\nArticles: {len(articles)}")
for i, a in enumerate(articles[:5]):
txt = (a.text_content() or "").strip()[:300]
attrs = page.evaluate("(el) => { const o={}; for(const a of el.attributes) o[a.name]=a.value; return o; }", a.element_handle())
print(f" [{i}] {attrs}")
print(f" text: {txt!r}")
browser.close()
+91
View File
@@ -0,0 +1,91 @@
"""Probe possible HUD deep-link URL patterns to find which one renders a single property."""
from __future__ import annotations
import io, sys, time
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
REAL_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
# Real case # from earlier exploration
CASE_NUM = "093-676572" # 4641 Samoset Dr, Sarasota
URLS_TO_PROBE = [
f"https://www.hudhomestore.gov/searchresult?caseNumber={CASE_NUM}",
f"https://www.hudhomestore.gov/searchresult?CaseNumber={CASE_NUM}",
f"https://www.hudhomestore.gov/searchresult?searchText={CASE_NUM}",
f"https://www.hudhomestore.gov/searchresult?cityStateZip={CASE_NUM}",
f"https://www.hudhomestore.gov/Listing/PropertyDetails?caseNumber={CASE_NUM}",
f"https://www.hudhomestore.gov/Property/Details?caseNumber={CASE_NUM}",
f"https://www.hudhomestore.gov/PropertyDetails?caseNumber={CASE_NUM}",
f"https://www.hudhomestore.gov/Listing?caseNumber={CASE_NUM}",
f"https://www.hudhomestore.gov/property/{CASE_NUM}",
f"https://www.hudhomestore.gov/listing/{CASE_NUM}",
f"https://www.hudhomestore.gov/casenumber/{CASE_NUM}",
]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context(
user_agent=REAL_UA, viewport={"width": 1400, "height": 900},
).new_page()
page.set_default_timeout(15_000)
# Set session via landing
page.goto("https://www.hudhomestore.gov/", wait_until="networkidle")
time.sleep(1.5)
for url in URLS_TO_PROBE:
print(f"\n=== {url} ===")
try:
r = page.goto(url, wait_until="networkidle", timeout=15_000)
time.sleep(2)
print(f" status: {r.status}, final URL: {page.url}")
body_text = page.locator("body").inner_text()
# Heuristic: does the body show property-specific info?
has_addr = "4641 Samoset" in body_text or "samoset" in body_text.lower()
has_case = CASE_NUM in body_text
has_price = "$446" in body_text
has_specific_result_count = "1 Properties Listed" in body_text or "1 Property Listed" in body_text
print(f" has_address={has_addr}, has_case#={has_case}, has_price={has_price}, single_result={has_specific_result_count}")
# If we found the address, this is likely the right URL
if has_addr:
print(f" ✅ THIS URL RENDERS THE SPECIFIC PROPERTY")
except Exception as e:
print(f" ERROR: {e}")
# Also try the citystate flow + then drill into a single case
# via the case number search input
print()
print("=== Step 7: Try entering case # in cityStateZip search ===")
page.goto("https://www.hudhomestore.gov/", wait_until="networkidle")
time.sleep(1.5)
page.evaluate(f"""() => {{
const inp = document.getElementById('cityStateZip');
inp.value = '{CASE_NUM}';
const event = new Event('input', {{ bubbles: true }});
inp.dispatchEvent(event);
if (typeof ysi !== 'undefined' && ysi.corpsearchfilter) {{
ysi.corpsearchfilter.changeInput('{CASE_NUM}', 'home');
}}
}}""")
time.sleep(3)
# Inspect autocomplete + URL after
print(f" URL after typing case#: {page.url}")
autocomplete_items = page.locator("#cityStateZipautocomplete-list li, [role='option']").all()
print(f" autocomplete suggestions: {len(autocomplete_items)}")
for i, item in enumerate(autocomplete_items[:5]):
text = (item.text_content() or "").strip()[:100]
print(f" [{i}] {text!r}")
if autocomplete_items:
try:
autocomplete_items[0].click(force=True)
time.sleep(3)
print(f" URL after selecting first: {page.url}")
except Exception as e:
print(f" click failed: {e}")
browser.close()
+78
View File
@@ -0,0 +1,78 @@
"""Find the deep-link URL pattern for HUD property detail pages."""
from __future__ import annotations
import io, sys, time, re
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
REAL_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context(
user_agent=REAL_UA, viewport={"width": 1400, "height": 900},
).new_page()
page.set_default_timeout(30_000)
page.goto("https://www.hudhomestore.gov/", wait_until="networkidle")
time.sleep(2)
page.goto("https://www.hudhomestore.gov/searchresult?citystate=FL", wait_until="networkidle")
time.sleep(6)
# Find all links inside cards
print("=== ALL <a> elements inside property cards ===")
cards = page.locator("div.topMap-card.card-body").all()
print(f"Found {len(cards)} cards")
# Inspect the FIRST card in detail
if cards:
c1 = cards[0]
print()
print("--- CARD #1 HTML structure (first 2000 chars) ---")
html = c1.evaluate("(el) => el.outerHTML")
# Filter out script/style noise
cleaned = re.sub(r"\s+", " ", html)[:2500]
print(cleaned)
print()
print("--- CARD #1 ALL <a> hrefs ---")
anchors = c1.locator("a").all()
for i, a in enumerate(anchors[:15]):
href = a.get_attribute("href") or ""
text = (a.text_content() or "").strip()[:60]
print(f" [{i}] href={href} | text='{text}'")
# Also look for onclick handlers + data attributes
print()
print("--- CARD #1 elements with onclick / data-* ---")
clickables = c1.locator("[onclick], [data-href], [data-url], [data-link], [data-property]").all()
for el in clickables[:10]:
attrs = page.evaluate("""(el) => {
const out = {};
for (const a of el.attributes) out[a.name] = a.value;
return out;
}""", el.element_handle())
print(f" {el.evaluate('(el) => el.tagName')}: {attrs}")
# Check if there's a global pattern for property detail URLs in the page
print()
print("=== Looking for '/propertydetails' / '/Listing' anywhere in page ===")
full_html = page.content()
# Find href patterns
urls = re.findall(
r'href="([^"]*(?:propertydetail|propertyDetail|listing/PropertyDetail|case[Nn]umber)[^"]*)"',
full_html, re.IGNORECASE,
)
for u in set(urls[:10]):
print(f" {u}")
# Also look for data attribs with case#
case_links = re.findall(
r'(href|data-[a-z]+)="([^"]*093-?\d{6}[^"]*)"',
full_html, re.IGNORECASE,
)
print(f"\nLinks containing a case number (093-XXXXXX):")
for attr, url in case_links[:8]:
print(f" {attr}={url}")
browser.close()
+69
View File
@@ -0,0 +1,69 @@
"""Inspect actual content of /searchresult?state=FL."""
from __future__ import annotations
import io, sys, time
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
REAL_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent=REAL_UA, viewport={"width": 1400, "height": 900},
locale="en-US", timezone_id="America/New_York",
)
page = context.new_page()
page.set_default_timeout(30_000)
# Load landing first to set session
page.goto("https://www.hudhomestore.gov/", wait_until="networkidle")
time.sleep(2)
# Then go to search
page.goto("https://www.hudhomestore.gov/searchresult?state=FL", wait_until="networkidle", timeout=30_000)
time.sleep(3)
print("URL:", page.url)
print("Title:", page.title())
print()
body_text = page.locator("body").inner_text()
print("--- BODY TEXT (first 3000 chars) ---")
print(body_text[:3000])
# Save full HTML
with open("scripts/_hud_search_fl.html", "w", encoding="utf-8") as f:
f.write(page.content())
print(f"\nFull HTML saved: scripts/_hud_search_fl.html")
# Dump all <div> with class containing 'property' or 'listing' or 'home' or 'card'
print()
print("--- LISTING DIVS ---")
for cls in ["[class*='property']", "[class*='listing']", "[class*='home-card']",
"[class*='card']", "[class*='result']", "[class*='item']"]:
els = page.locator(f"div{cls}").all()
if els:
print(f"\n div{cls}: {len(els)} elements")
for i, el in enumerate(els[:3]):
txt = (el.text_content() or "").strip()[:300]
if txt:
print(f" [{i}] {txt!r}")
# Try links to detail pages
print()
print("--- LINKS TO POTENTIAL DETAIL PAGES ---")
for link in page.locator("a").all()[:60]:
try:
href = link.get_attribute("href") or ""
text = (link.text_content() or "").strip()[:80]
if ("propertydetails" in href.lower() or "casenumber" in href.lower()
or "listing" in href.lower() or "?case" in href.lower()
or "property-details" in href.lower()):
if "javascript" not in href:
print(f" href={href} | text='{text}'")
except Exception:
pass
browser.close()
+71
View File
@@ -0,0 +1,71 @@
"""Probe HUD Homestore /searchresult with direct query parameters."""
from __future__ import annotations
import io, sys, time
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
REAL_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
# Try various URL patterns
URLS_TO_TRY = [
"https://www.hudhomestore.gov/searchresult?state=FL",
"https://www.hudhomestore.gov/searchresult?searchText=FL",
"https://www.hudhomestore.gov/searchresult?st=FL",
"https://www.hudhomestore.gov/searchresult?CityStateZip=FL",
"https://www.hudhomestore.gov/searchresult?cityStateZip=FL",
"https://www.hudhomestore.gov/searchresult?state=Florida",
"https://www.hudhomestore.gov/searchresult?State=FL&sortBy=0",
]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent=REAL_UA, viewport={"width": 1400, "height": 900},
locale="en-US", timezone_id="America/New_York",
)
page = context.new_page()
page.set_default_timeout(30_000)
# FIRST: load landing to set cookies / session
page.goto("https://www.hudhomestore.gov/", wait_until="networkidle")
time.sleep(2)
for url in URLS_TO_TRY:
print(f"\n=== {url} ===")
try:
r = page.goto(url, wait_until="networkidle", timeout=20_000)
time.sleep(2)
print(f" status={r.status}, final={page.url}")
# Check for results
body = page.locator("body").inner_text()
# Common indicators of results vs landing
has_results_text = any(kw in body.lower() for kw in [
"result(s)", "of property", "of properties", "$", "sale price", "list price",
])
no_results = any(kw in body.lower() for kw in [
"no result", "no properties", "no homes", "no match",
])
print(f" has_results_text: {has_results_text}, no_results: {no_results}")
# Count tables and property-looking elements
tables = page.locator("table").count()
divs_listing = page.locator("div[class*='listing'], div[class*='property'], div[class*='result']").count()
print(f" tables: {tables}, listing divs: {divs_listing}")
if has_results_text and not no_results:
print(f" → LIKELY HAS RESULTS")
# save html
slug = url.split("=")[-1] or "search"
with open(f"scripts/_hud_search_{slug}.html", "w", encoding="utf-8") as f:
f.write(page.content())
print(f" saved: scripts/_hud_search_{slug}.html")
# Dump first 5 listing-like elements
if divs_listing > 0:
for i, el in enumerate(page.locator("div[class*='property'], div[class*='listing']").all()[:5]):
txt = (el.text_content() or "").strip()[:200]
print(f" [{i}] {txt!r}")
break
except Exception as e:
print(f" ERROR: {e}")
browser.close()
+190
View File
@@ -0,0 +1,190 @@
"""Explore HUD Homestore (hudhomestore.gov) site structure with Playwright.
Goals:
1. Find URL pattern for FL state results
2. Find the search form input names/IDs
3. Document what fields each listing shows in results
4. Find URL pattern for individual property detail pages
"""
from __future__ import annotations
import io, sys, time
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
# Real Chrome UA — federal sites often block non-standard UAs
REAL_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
def main():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent=REAL_UA, viewport={"width": 1400, "height": 900},
locale="en-US", timezone_id="America/New_York",
)
page = context.new_page()
page.set_default_timeout(30_000)
# Step 1: Load landing page
print("=" * 70)
print("Step 1: landing page")
print("=" * 70)
response = page.goto("https://www.hudhomestore.gov/", wait_until="networkidle", timeout=30_000)
print(f" status: {response.status}")
print(f" url: {page.url}")
print(f" title: {page.title()}")
time.sleep(2)
# Save landing HTML
landing_html = page.content()
with open("scripts/_hud_landing.html", "w", encoding="utf-8") as f:
f.write(landing_html)
print(f" HTML saved: scripts/_hud_landing.html ({len(landing_html):,} chars)")
# Inspect form inputs
print()
print("--- INPUTS (first 30) ---")
for i, inp in enumerate(page.locator("input").all()[:30]):
try:
attrs = page.evaluate("""(el) => {
const out = {};
for (const a of el.attributes) out[a.name] = a.value;
return out;
}""", inp.element_handle())
print(f" [{i}] {attrs}")
except Exception:
pass
# Inspect select dropdowns (state selector likely)
print()
print("--- SELECTS ---")
for i, sel in enumerate(page.locator("select").all()[:10]):
try:
attrs = page.evaluate("""(el) => {
const out = {};
for (const a of el.attributes) out[a.name] = a.value;
return out;
}""", sel.element_handle())
opts_count = sel.locator("option").count()
print(f" [{i}] {attrs} ({opts_count} options)")
except Exception:
pass
# Inspect navigation links
print()
print("--- KEY LINKS (search, results, etc) ---")
for link in page.locator("a").all()[:80]:
try:
href = link.get_attribute("href") or ""
text = (link.text_content() or "").strip()[:80]
if any(kw in href.lower() for kw in ["search", "result", "state", "property"]) or \
any(kw in text.lower() for kw in ["search", "browse", "list", "view all"]):
if href and not href.startswith("javascript:") and not href.startswith("#"):
print(f" href={href} | text='{text}'")
except Exception:
pass
# Step 2: try direct URL paths
print()
print("=" * 70)
print("Step 2: probe direct URLs")
print("=" * 70)
urls_to_try = [
"https://www.hudhomestore.gov/searchresult",
"https://www.hudhomestore.gov/SearchResult",
"https://www.hudhomestore.gov/Search",
"https://www.hudhomestore.gov/Listing/PropertyDetails",
]
for url in urls_to_try:
try:
r = page.goto(url, wait_until="domcontentloaded", timeout=15_000)
print(f" {url}: status={r.status}, final={page.url}, title={page.title()}")
except Exception as e:
print(f" {url}: ERROR {e}")
# Step 3: Try searching with FL state via search form
print()
print("=" * 70)
print("Step 3: search FL via form")
print("=" * 70)
page.goto("https://www.hudhomestore.gov/", wait_until="networkidle", timeout=30_000)
time.sleep(2)
# Try filling state select with FL
state_filled = False
for sel in ["select[name*='State']", "select[id*='State']", "select[id*='state']"]:
try:
if page.locator(sel).count() > 0:
page.locator(sel).first.select_option(value="FL")
state_filled = True
print(f" State filled with 'FL' via {sel}")
break
except Exception as e:
pass
if not state_filled:
# Try option label "Florida"
for sel in ["select"]:
try:
selects = page.locator(sel).all()
for s in selects:
try:
s.select_option(label="Florida")
state_filled = True
print(f" State filled with 'Florida' label")
break
except Exception:
continue
if state_filled:
break
except Exception:
pass
# Now try submit search
try:
page.locator("button:has-text('Search'), input[type='submit']").first.click()
page.wait_for_load_state("networkidle", timeout=15_000)
time.sleep(2)
print(f" Search submitted. URL after: {page.url}")
results_html = page.content()
with open("scripts/_hud_results_fl.html", "w", encoding="utf-8") as f:
f.write(results_html)
print(f" Results HTML saved: scripts/_hud_results_fl.html ({len(results_html):,} chars)")
# Inspect what came back — tables or cards?
tables = page.locator("table").all()
print(f" tables found: {len(tables)}")
cards = page.locator(".propertyResult, .property-result, .listing, [class*='listing']").all()
print(f" card-like elements: {len(cards)}")
# If table-based, dump first 3 rows of each table
for i, t in enumerate(tables[:3]):
rows = t.locator("tr").all()
print(f" Table [{i}]: {len(rows)} rows")
for j, r in enumerate(rows[:3]):
cells = [(c.text_content() or "").strip()[:50] for c in r.locator("td, th").all()]
non_empty = [c for c in cells if c]
if non_empty:
print(f" Row {j}: {non_empty}")
# Look for links to property detail
print()
print(" Property detail links (sample):")
for link in page.locator("a").all()[:50]:
try:
href = link.get_attribute("href") or ""
if "property" in href.lower() or "case" in href.lower() or "listing" in href.lower():
if "javascript" not in href:
text = (link.text_content() or "").strip()[:60]
print(f" href={href} | text='{text}'")
except Exception:
pass
except Exception as e:
print(f" Search submit ERROR: {e}")
browser.close()
if __name__ == "__main__":
main()
@@ -0,0 +1,94 @@
"""Deep-dive on the Miami-Dade Realforeclose auction calendar page."""
from __future__ import annotations
import io, sys, time
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
REAL_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
PREVIEW_URL = "https://www.miamidade.realforeclose.com/index.cfm?zaction=AUCTION&Zmethod=PREVIEW"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent=REAL_UA,
viewport={"width": 1280, "height": 800},
locale="en-US",
timezone_id="America/New_York",
)
page = context.new_page()
page.set_default_timeout(20_000)
print("Loading PREVIEW page...")
response = page.goto(PREVIEW_URL, wait_until="networkidle", timeout=25_000)
print(f"Status: {response.status}, URL: {page.url}")
print(f"Title: {page.title()}")
# Wait extra for any JS rendering
time.sleep(3)
# Inspect what's on the page after JS rendering
print()
print("--- ALL TABLES ---")
tables = page.locator("table").all()
print(f"Found {len(tables)} tables")
for i, t in enumerate(tables[:8]):
try:
rows = t.locator("tr").all()
print(f"\n Table [{i}]: {len(rows)} rows")
for j, r in enumerate(rows[:4]):
cells = [(c.text_content() or "").strip()[:40] for c in r.locator("td, th").all()]
print(f" Row {j}: {cells}")
except Exception as e:
print(f" Table [{i}] error: {e}")
print()
print("--- DIVs with id or class containing 'auction' / 'sale' / 'calendar' ---")
selectors_to_probe = [
"div[id*='auction']", "div[id*='sale']", "div[id*='calendar']",
"div[class*='auction']", "div[class*='sale']", "div[class*='calendar']",
"div[class*='content']", "div.AUCTION_DETAILS_DIV",
]
for sel in selectors_to_probe:
try:
els = page.locator(sel).all()
if els:
print(f" {sel}: {len(els)} elements")
for e in els[:3]:
text = (e.text_content() or "").strip()[:200]
if text:
print(f"{text!r}")
except Exception:
pass
print()
print("--- All links with 'auction', 'sale', 'case' in href or text ---")
for link in page.locator("a").all()[:80]:
try:
href = link.get_attribute("href") or ""
text = (link.text_content() or "").strip()[:80]
if any(kw in href.lower() for kw in ["auction", "sale", "case"]) or any(kw in text.lower() for kw in ["auction", "calendar", "sale", "scheduled"]):
if not href.startswith("javascript:") and not href.startswith("#"):
print(f" href={href} | text='{text}'")
except Exception:
pass
# Save the rendered HTML
html = page.content()
with open("scripts/_miamidade_preview_rendered.html", "w", encoding="utf-8") as f:
f.write(html)
print(f"\nRendered HTML saved: scripts/_miamidade_preview_rendered.html ({len(html):,} chars)")
# Show key body text snippets
print()
print("--- BODY TEXT SNIPPETS (around 'auction' or 'calendar' keywords) ---")
body_text = page.locator("body").inner_text()
import re
for kw in ["Auction Calendar", "Today's Auctions", "Upcoming Auctions", "View Auction", "Scheduled Sales", "Number of Auctions"]:
idx = body_text.find(kw)
if idx >= 0:
print(f" '{kw}' at pos {idx}: ...{body_text[max(0,idx-50):idx+300]!r}")
browser.close()
@@ -0,0 +1,64 @@
"""Explore Realforeclose with real Chrome UA + multiple entry paths."""
from __future__ import annotations
import io, sys, time
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
# Use real Chrome UA to bypass 403 anti-bot
REAL_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
URLS_TO_PROBE = [
"https://www.miamidade.realforeclose.com/",
"https://www.miamidade.realforeclose.com/index.cfm",
"https://www.miamidade.realforeclose.com/index.cfm?zaction=AUCTION&Zmethod=PREVIEW",
"https://www.miamidade.realforeclose.com/index.cfm?zaction=USER&Zmethod=CALENDAR",
"https://www.miamidade.realforeclose.com/index.cfm?zaction=AUCTION&Zmethod=DISPLAY",
]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent=REAL_UA,
viewport={"width": 1280, "height": 800},
locale="en-US",
timezone_id="America/New_York",
extra_http_headers={
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Sec-Fetch-Site": "none",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-User": "?1",
"Sec-Fetch-Dest": "document",
"Upgrade-Insecure-Requests": "1",
},
)
page = context.new_page()
page.set_default_timeout(20_000)
for url in URLS_TO_PROBE:
print(f"\n=== Probing {url} ===")
try:
response = page.goto(url, wait_until="networkidle", timeout=25_000)
status = response.status if response else "?"
title = page.title()
print(f" status={status}, final_url={page.url}, title={title}")
content = page.content()
print(f" html_len={len(content)}")
if 200 <= status < 400 and len(content) > 500:
print(" → SUCCESS — page loaded with substantial content")
# Show first 500 chars of visible text
body = page.locator("body").inner_text()[:500]
print(f" body_text_preview: {body[:500]!r}")
# Save HTML for inspection
slug = url.split("=")[-1] or "landing"
with open(f"scripts/_miamidade_{slug}.html", "w", encoding="utf-8") as f:
f.write(content)
print(f" HTML saved: scripts/_miamidade_{slug}.html")
break
except Exception as e:
print(f" ERROR: {e}")
browser.close()
+60
View File
@@ -0,0 +1,60 @@
"""Probe tomorrow's auction to see real case listings + nail down the parse structure."""
from __future__ import annotations
import io, sys, time
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
from playwright.sync_api import sync_playwright
REAL_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
# Probe multiple dates to find one with real cases
DATES_TO_PROBE = [
"05/14/2026", "05/15/2026", "05/16/2026", "05/19/2026", "05/20/2026", "05/21/2026",
]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent=REAL_UA, viewport={"width": 1280, "height": 800},
locale="en-US", timezone_id="America/New_York",
)
page = context.new_page()
page.set_default_timeout(20_000)
for date in DATES_TO_PROBE:
url = f"https://www.miamidade.realforeclose.com/index.cfm?zaction=AUCTION&zmethod=PREVIEW&AuctionDate={date}"
print(f"\n=== {date} ===")
try:
response = page.goto(url, wait_until="networkidle", timeout=20_000)
time.sleep(2)
body = page.locator("body").inner_text()
# Quick check for cases
has_no_cases = "no cases currently being" in body.lower() or "no auction" in body.lower()
has_case_number = "case #" in body.lower() or "Case #:" in body
# Count Case # occurrences as proxy for # of cases
case_count = body.count("Case #:") + body.count("Case #")
print(f" status={response.status} | has_no_cases_text={has_no_cases} | Case # markers found: {case_count}")
if case_count > 1 or (has_case_number and not has_no_cases):
# Save this HTML for detailed inspection
with open(f"scripts/_mdc_auction_{date.replace('/', '-')}.html", "w", encoding="utf-8") as f:
f.write(page.content())
print(f" → SAVED: scripts/_mdc_auction_{date.replace('/', '-')}.html")
# Print first few rows of the case tables
tables = page.locator("table").all()
print(f" Tables: {len(tables)}")
for ti, t in enumerate(tables[:3]):
rows = t.locator("tr").all()
print(f" Table [{ti}] rows={len(rows)}")
for ri, r in enumerate(rows[:20]):
cells = [(c.text_content() or "").strip()[:50] for c in r.locator("td, th").all()]
non_empty = [c for c in cells if c]
if non_empty:
print(f" Row {ri}: {non_empty}")
break # found a date with cases
except Exception as e:
print(f" ERROR: {e}")
browser.close()
+95
View File
@@ -0,0 +1,95 @@
"""Test Firecrawl scrape on Zillow Miami-Dade county page.
EXPECTED COST: ~3-5 Firecrawl credits (1 page scrape).
Goals:
1. Verify Firecrawl can bypass Zillow's anti-bot
2. Inspect the markdown structure of search results
3. Decide on parser strategy (regex vs LLM extract)
"""
from __future__ import annotations
import io, os, sys
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
# Force-load .env via data_fetchers
import data_fetchers # noqa: F401
from firecrawl import FirecrawlApp
def main() -> int:
api_key = os.getenv("FIRECRAWL_API_KEY", "")
if not api_key:
print("❌ FIRECRAWL_API_KEY not set")
return 1
print("=" * 70)
print("Zillow Firecrawl smoke test — Miami-Dade County FL")
print("=" * 70)
print(f"Using API key: {api_key[:10]}...")
url = "https://www.zillow.com/miami-dade-county-fl/houses/"
print(f"URL: {url}")
print()
app = FirecrawlApp(api_key=api_key)
# Print credit usage before
try:
usage_before = app.get_credit_usage()
print(f"Credit usage BEFORE: {usage_before}")
except Exception as e:
print(f"(could not fetch credit usage before: {e})")
print()
print("Calling scrape() (formats=markdown)...")
try:
result = app.scrape(url, formats=["markdown"])
except Exception as e:
print(f"❌ Firecrawl call failed: {type(e).__name__}: {e}")
return 1
# Print credit usage after
try:
usage_after = app.get_credit_usage()
print(f"Credit usage AFTER: {usage_after}")
except Exception:
pass
print(f"Result type: {type(result).__name__}")
# Save full markdown for inspection
md = result.markdown if hasattr(result, "markdown") else (result.get("markdown") if isinstance(result, dict) else None)
if md:
print(f"Markdown length: {len(md):,} chars")
out_file = ROOT / "scripts" / "_zillow_miami_md.txt"
out_file.write_text(md, encoding="utf-8")
print(f"Saved: {out_file}")
# Sample of markdown content
print()
print("--- FIRST 3000 CHARS ---")
print(md[:3000])
print()
print("--- CHARS 3000-6000 (probably listings) ---")
print(md[3000:6000])
# Inspect result attrs / keys (Firecrawl SDK may have changed)
print()
print("--- RESULT META ---")
if hasattr(result, "metadata"):
print(f"metadata: {result.metadata}")
if hasattr(result, "credits_used"):
print(f"credits_used: {result.credits_used}")
if isinstance(result, dict):
for k in result.keys():
print(f" key: {k}")
return 0
if __name__ == "__main__":
sys.exit(main())
+49
View File
@@ -0,0 +1,49 @@
"""Idempotent init script para data/deals.db.
Crea schema + indexes. Safe para correr multiples veces (CREATE TABLE IF NOT EXISTS).
Usage:
python scripts/init_deals_db.py
"""
from __future__ import annotations
import io, sys
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from deals_db import init_db, _DB_PATH, _get_conn # noqa: E402
def main() -> int:
print(f"Initializing deals DB at: {_DB_PATH}")
init_db()
conn = _get_conn()
# Verify all tables created
tables = [r[0] for r in conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
).fetchall()]
print(f"Tables present: {tables}")
# Verify indexes
indexes = [r[0] for r in conn.execute(
"SELECT name FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%' ORDER BY name"
).fetchall()]
print(f"Indexes present: {indexes}")
# Sanity check
expected_tables = {"deals", "scraper_runs", "firecrawl_usage"}
missing = expected_tables - set(tables)
if missing:
print(f"ERROR: missing tables {missing}")
return 1
print("OK — deals.db initialized successfully")
return 0
if __name__ == "__main__":
sys.exit(main())
+56
View File
@@ -0,0 +1,56 @@
"""Migration script for deals.db schema changes (Phase 3B1 fixes).
Wipes the deals table and recreates with new schema:
- address: nullable (vacant lots have only parcel_id)
- listing_price: nullable (foreclosure pre-auction bids hidden)
- NEW columns: parcel_id, final_judgment_amount
Phase 3 is still in dev, no production data lost.
"""
from __future__ import annotations
import io, sys
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
def main() -> int:
from deals_db import _get_conn, init_db, _DB_PATH
conn = _get_conn()
# Show current state
pre_count = conn.execute("SELECT COUNT(*) AS n FROM deals").fetchone()[0]
pre_runs = conn.execute("SELECT COUNT(*) AS n FROM scraper_runs").fetchone()[0]
pre_fcr = conn.execute("SELECT COUNT(*) AS n FROM firecrawl_usage").fetchone()[0]
print(f"Pre-migration state:")
print(f" deals: {pre_count}")
print(f" scraper_runs: {pre_runs}")
print(f" firecrawl_usage: {pre_fcr}")
# Wipe + recreate deals table only (keep history of runs + firecrawl usage)
conn.execute("DROP TABLE IF EXISTS deals")
print()
print("Dropped deals table.")
init_db()
print("Recreated schema with new columns (address nullable, listing_price nullable, +parcel_id, +final_judgment_amount).")
# Verify
cols = [r["name"] for r in conn.execute("PRAGMA table_info(deals)").fetchall()]
expected_new = ["parcel_id", "final_judgment_amount"]
for c in expected_new:
if c in cols:
print(f" ✅ column '{c}' present")
else:
print(f" ❌ column '{c}' MISSING")
return 1
print()
print(f"deals.db migrated OK at {_DB_PATH}")
return 0
if __name__ == "__main__":
sys.exit(main())
+98
View File
@@ -0,0 +1,98 @@
"""Retroactive migration: regenerate source_url for existing HUD deals.
Background: B3 v1 bug saved generic URL `?citystate=FL` for ALL 39 HUD deals.
B3 v1.1 fix: derive source_url from case_number via build_deep_link().
This script:
1. Iterates all deals where source='hud_homestore'
2. For each: regenerates source_url from case_number
3. Updates the row
4. Reports: how many fixed, how many had no case_number (would mark as NULL)
"""
from __future__ import annotations
import io, sys
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from deals_db import init_db, _get_conn
from scrapers.hud_homestore import build_deep_link
def main() -> int:
init_db()
conn = _get_conn()
rows = conn.execute(
"SELECT id, case_number, source_url, address FROM deals WHERE source = 'hud_homestore'"
).fetchall()
print(f"HUD deals to migrate: {len(rows)}")
print()
fixed = 0
no_case = 0
already_ok = 0
unchanged_other = 0
for r in rows:
deal_id = r["id"]
case_number = r["case_number"]
old_url = r["source_url"]
addr = (r["address"] or "?")[:50]
new_url = build_deep_link(case_number)
if new_url is None:
# No case_number → cannot construct deep-link; nullify
if old_url is None:
unchanged_other += 1
else:
conn.execute("UPDATE deals SET source_url = NULL WHERE id = ?", (deal_id,))
no_case += 1
print(f" id={deal_id} case=None → set NULL (was {old_url[:60] if old_url else None})")
print(f" addr: {addr}")
continue
if old_url == new_url:
already_ok += 1
continue
conn.execute("UPDATE deals SET source_url = ? WHERE id = ?", (new_url, deal_id))
fixed += 1
if fixed <= 5:
print(f" id={deal_id} case={case_number}")
print(f" old: {old_url}")
print(f" new: {new_url}")
print(f" addr: {addr}")
print()
print(f"=== Migration summary ===")
print(f" Fixed (URL regenerated): {fixed}")
print(f" No case_number (set NULL): {no_case}")
print(f" Already correct: {already_ok}")
print(f" Other unchanged: {unchanged_other}")
print()
print(f"Total HUD deals: {len(rows)}")
# Verify
print()
print("=== Verification: 5 random URLs post-migration ===")
rows2 = conn.execute(
"SELECT id, case_number, source_url FROM deals WHERE source='hud_homestore' LIMIT 5"
).fetchall()
for r in rows2:
url = r["source_url"]
case = r["case_number"]
case_in_url = case in (url or "") if case else None
print(f" id={r['id']} case={case}")
print(f" url={url}")
print(f" case_in_url={case_in_url}")
return 0
if __name__ == "__main__":
sys.exit(main())
+144
View File
@@ -0,0 +1,144 @@
"""Migration for REDEEMED bug fix in Miami-Dade Clerk scraper.
Steps:
1. ALTER TABLE deals ADD COLUMN auction_status (if not exists).
2. Re-parse all cached Miami-Dade HTML using the new parser to get a SET of
live case_numbers (i.e., cases that did NOT have a dead status).
3. For each miami_dade_clerk deal in deals.db:
- If case_number in live set → update auction_status to the live status.
- If NOT in live set → it's likely a dead case (REDEEMED/CANCELED/etc) that
the old parser kept. Mark it auction_status='Dismissed (re-parse)' so
the search UI filters it out.
"""
from __future__ import annotations
import io, sys, glob
from pathlib import Path
from collections import defaultdict
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from deals_db import init_db, _get_conn
from scrapers.miami_dade_clerk import _parse_cases_from_html
def add_column_if_missing(conn, table: str, col: str, coltype: str = "TEXT"):
cols = [r["name"] for r in conn.execute(f"PRAGMA table_info({table})").fetchall()]
if col not in cols:
conn.execute(f"ALTER TABLE {table} ADD COLUMN {col} {coltype}")
return True
return False
def main() -> int:
init_db()
conn = _get_conn()
# ─── Step 1: ALTER TABLE ───────────────────────────────────────────────
added = add_column_if_missing(conn, "deals", "auction_status", "TEXT")
print(f"Step 1: auction_status column {'ADDED' if added else 'already exists'}")
# ─── Step 2: Re-parse cached HTML to get live case set ─────────────────
print()
print("Step 2: Re-parsing cached Miami-Dade HTML...")
cached_files = sorted(glob.glob(str(ROOT / ".cache/scrapers/miami_dade_clerk/*.html")))
print(f" cached HTMLs found: {len(cached_files)}")
live_case_to_status: dict[str, str] = {}
files_processed = 0
for f in cached_files:
html = Path(f).read_text(encoding="utf-8")
if len(html) < 20_000:
continue # skip "no results" placeholder pages
files_processed += 1
cases = _parse_cases_from_html(html)
for case in cases:
cn = case.get("case_number")
if cn and cn not in live_case_to_status:
live_case_to_status[cn] = case.get("auction_status") or "scheduled"
print(f" files processed: {files_processed}")
print(f" live case_numbers extracted: {len(live_case_to_status)}")
# ─── Step 3: Compare with deals.db ─────────────────────────────────────
print()
print("Step 3: Matching against deals.db...")
rows = conn.execute(
"SELECT id, case_number, address, listing_price, deal_type FROM deals "
"WHERE source = 'miami_dade_clerk'"
).fetchall()
print(f" deals in db (miami_dade_clerk): {len(rows)}")
live_updates = 0
dead_updates = 0
no_cache_match = 0 # case was in db but not in any cached HTML (cache expired or different date)
samples = {"live": [], "dead": [], "no_cache": []}
for r in rows:
cn = r["case_number"]
if not cn:
continue
if cn in live_case_to_status:
status = live_case_to_status[cn]
conn.execute("UPDATE deals SET auction_status = ? WHERE id = ?",
(status, r["id"]))
live_updates += 1
if len(samples["live"]) < 3:
samples["live"].append((cn, r["address"], status))
else:
# Case was in db but NOT found in current cache.
# Two possibilities:
# (a) cached HTML is stale or doesn't cover this date — case may still be live
# (b) case was REDEEMED/CANCELED — old parser kept it, new one filters
# Conservative: mark as 'Dismissed (re-parse miss)' so it filters out.
# If user wants them back, can be undone by changing status manually.
conn.execute(
"UPDATE deals SET auction_status = ? WHERE id = ?",
("Dismissed (re-parse miss)", r["id"]),
)
dead_updates += 1
if len(samples["dead"]) < 5:
samples["dead"].append((cn, r["address"], r["deal_type"]))
print(f"\n Live (updated with current status): {live_updates}")
print(f" Dismissed (not in cache, filtered): {dead_updates}")
# ─── Step 4: Show samples ──────────────────────────────────────────────
print()
print("Samples of LIVE updates:")
for cn, addr, status in samples["live"]:
print(f" {cn:<25} status={status:<15} addr={(addr or '?')[:50]}")
print()
print("Samples of DISMISSED (filtered) deals:")
for cn, addr, dtype in samples["dead"]:
print(f" {cn:<25} type={dtype:<10} addr={(addr or '?')[:50]}")
# ─── Step 5: Verify totals ─────────────────────────────────────────────
print()
print("=== Final state ===")
rows = conn.execute("""
SELECT auction_status, COUNT(*) AS n
FROM deals
WHERE source = 'miami_dade_clerk'
GROUP BY auction_status
""").fetchall()
for r in rows:
print(f" status={r['auction_status']}: {r['n']}")
# Search-time count (mimicking what user sees)
visible = conn.execute("""
SELECT COUNT(*) AS n FROM deals
WHERE source='miami_dade_clerk'
AND COALESCE(LOWER(auction_status), '') NOT IN
('redeemed', 'canceled', 'cancelled', 'sold', 'closed',
'title transferred', 'withdrawn', 'dismissed',
'dismissed (re-parse miss)')
""").fetchone()["n"]
print(f"\n Visible in search results (post-filter): {visible}")
return 0
if __name__ == "__main__":
sys.exit(main())
+92
View File
@@ -0,0 +1,92 @@
"""Migration: agregar property_type + backfill de deals existentes.
Pasos:
1. init_db() — aplica ALTER TABLE idempotente (agrega columna property_type)
2. Para cada deal en deals.db:
- Si property_type ya esta seteado y valido → skip
- Else → infer_property_type() usando description/address/beds/source
3. UPDATE en bulk + reporte de distribucion
Usage:
python scripts/migrate_property_type.py
"""
from __future__ import annotations
import io, sys
from pathlib import Path
from collections import Counter
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from deals_db import init_db, _get_conn
from property_type_inference import infer_property_type, VALID_TYPES
def main() -> int:
init_db() # idempotent: aplica ALTER TABLE si la columna no existe
conn = _get_conn()
# Verificar que la columna existe ahora
cols = {r["name"] for r in conn.execute("PRAGMA table_info(deals)").fetchall()}
if "property_type" not in cols:
print("ERROR: property_type column missing after init_db()")
return 1
print("Step 1: property_type column OK")
rows = conn.execute(
"SELECT id, source, address, listing_description, beds, baths, sqft, "
"deal_type, property_type FROM deals"
).fetchall()
print(f"Step 2: {len(rows)} deals to evaluate")
print()
counter_before = Counter()
counter_after = Counter()
updates = 0
by_source: dict[str, Counter] = {}
for r in rows:
d = dict(r)
before = d.get("property_type") or "(null)"
counter_before[before] += 1
new_pt = infer_property_type(d)
counter_after[new_pt] += 1
by_source.setdefault(d["source"], Counter())[new_pt] += 1
if before != new_pt:
conn.execute(
"UPDATE deals SET property_type = ? WHERE id = ?",
(new_pt, r["id"]),
)
updates += 1
print(f"Updated: {updates}/{len(rows)} deals")
print()
print("=== Distribution BEFORE ===")
for pt, n in sorted(counter_before.items(), key=lambda x: -x[1]):
print(f" {pt:<15} {n:>4}")
print()
print("=== Distribution AFTER ===")
for pt, n in sorted(counter_after.items(), key=lambda x: -x[1]):
print(f" {pt:<15} {n:>4}")
print()
print("=== Breakdown por source ===")
for source, c in by_source.items():
print(f" {source}:")
for pt, n in sorted(c.items(), key=lambda x: -x[1]):
print(f" {pt:<15} {n:>4}")
# Sanity: verificar que todos los property_type sean validos
invalid = [pt for pt in counter_after if pt not in VALID_TYPES]
if invalid:
print(f"\nWARNING: invalid property_types produced: {invalid}")
return 2
print("\nDone.")
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,94 @@
"""Migration: separar Zillow zpids del campo case_number.
BUG ORIGINAL:
El Zillow scraper estaba guardando el zpid en la columna case_number, que es
INCORRECTO porque case_number es solo para court cases reales (foreclosure
judicial, tax_deed). Esto contaminaba el dataset con falsos positivos de
"deals judiciales".
FIX:
1. Aplica ALTER TABLE para agregar external_id (idempotent via init_db)
2. Para cada deal de source='zillow': mover case_number → external_id
3. Tambien aplica a hud_homestore (su HUD case# es trackeo, no court case judicial)
Idempotent: si ya se corrio, los nuevos rows seran 0.
"""
from __future__ import annotations
import io, sys
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from deals_db import init_db, _get_conn
def main() -> int:
init_db() # ensures external_id column exists
conn = _get_conn()
cols = {r["name"] for r in conn.execute("PRAGMA table_info(deals)").fetchall()}
if "external_id" not in cols:
print("ERROR: external_id column missing after init_db()")
return 1
# Step 1: Zillow zpids — move case_number → external_id
rows_z = conn.execute(
"SELECT COUNT(*) FROM deals WHERE source = 'zillow' "
"AND case_number IS NOT NULL AND case_number != '' "
"AND (external_id IS NULL OR external_id = '')"
).fetchone()
print(f"Zillow deals needing migration: {rows_z[0]}")
if rows_z[0] > 0:
conn.execute(
"UPDATE deals SET external_id = case_number, case_number = NULL "
"WHERE source = 'zillow' "
"AND case_number IS NOT NULL AND case_number != '' "
"AND (external_id IS NULL OR external_id = '')"
)
print(f" Migrated {rows_z[0]} Zillow zpid values")
# Step 2: HUD Homestore — HUD case# is a tracking number, NOT a court case.
# We still move it to external_id; case_number stays NULL for HUD (since
# HUD listings are REO, not judicial proceedings).
rows_h = conn.execute(
"SELECT COUNT(*) FROM deals WHERE source = 'hud_homestore' "
"AND case_number IS NOT NULL AND case_number != '' "
"AND (external_id IS NULL OR external_id = '')"
).fetchone()
print(f"HUD deals needing migration: {rows_h[0]}")
if rows_h[0] > 0:
conn.execute(
"UPDATE deals SET external_id = case_number, case_number = NULL "
"WHERE source = 'hud_homestore' "
"AND case_number IS NOT NULL AND case_number != '' "
"AND (external_id IS NULL OR external_id = '')"
)
print(f" Migrated {rows_h[0]} HUD case# values")
# Step 3: Verify clerks NOT affected (their case_number IS a real court case)
clerk_with_case = conn.execute(
"SELECT COUNT(*) FROM deals WHERE source LIKE '%_clerk' "
"AND case_number IS NOT NULL AND case_number != ''"
).fetchone()[0]
print(f"\nClerk deals with case_number (court cases, unchanged): {clerk_with_case}")
# Final state
print()
print("=== Final state ===")
rows = conn.execute("""
SELECT source,
SUM(CASE WHEN case_number IS NOT NULL AND case_number != '' THEN 1 ELSE 0 END) AS with_case,
SUM(CASE WHEN external_id IS NOT NULL AND external_id != '' THEN 1 ELSE 0 END) AS with_ext
FROM deals GROUP BY source ORDER BY source
""").fetchall()
print(f"{'source':<22} {'case_number':>12} {'external_id':>12}")
for r in rows:
print(f" {r['source']:<22} {r['with_case']:>12} {r['with_ext']:>12}")
return 0
if __name__ == "__main__":
sys.exit(main())
+91
View File
@@ -0,0 +1,91 @@
"""Find the JSON API endpoint that bcpa.net's JS calls to populate property data."""
from pathlib import Path
import json
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "bcpa"
folio = "484226062150"
json_responses = []
all_xhr = []
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36",
)
page = ctx.new_page()
def on_response(resp):
try:
u = resp.url
if any(x in u for x in (".css", ".png", ".jpg", ".woff", ".ico", ".gif", "google", "fonts.")):
return
ct = resp.headers.get("content-type", "")
all_xhr.append({"url": u[:200], "status": resp.status, "ct": ct})
if "json" in ct.lower():
try:
body = resp.text()
json_responses.append({
"url": u,
"status": resp.status,
"body_preview": body[:500],
"body_full": body,
})
except Exception:
pass
except Exception:
pass
page.on("response", on_response)
# Try the SPA URL (which we know loads data)
spa_url = f"https://web.bcpa.net/bcpaclient/#/Record-Search?folio={folio}"
print(f"[1] Loading SPA: {spa_url}")
page.goto(spa_url, wait_until="domcontentloaded", timeout=30000)
page.wait_for_timeout(15000)
print(f"\n[2] All XHR/network responses captured ({len(all_xhr)}):")
for r in all_xhr:
if any(x in r["ct"] for x in ("json", "xml", "html")):
print(f" {r['status']} [{r['ct'][:30]}] {r['url'][:130]}")
print(f"\n[3] JSON responses found: {len(json_responses)}")
for jr in json_responses:
print(f"\n URL: {jr['url']}")
print(f" Status: {jr['status']}")
print(f" Preview: {jr['body_preview']}")
# Save full JSON responses
if json_responses:
(out_dir / "json_responses.json").write_text(
json.dumps(json_responses, indent=2), encoding="utf-8"
)
# Also try directly hitting common ASP.NET WebMethod endpoints
print(f"\n[4] Probing common WebMethod endpoint patterns directly:")
candidates = [
f"https://web.bcpa.net/bcpaclient/search.aspx/getOwnerInfo",
f"https://web.bcpa.net/bcpaclient/search.aspx/getPropertyData",
f"https://web.bcpa.net/bcpaclient/search.aspx/getRecordByFolio",
f"https://web.bcpa.net/bcpaclient/search.aspx/GetRecordByFolio",
]
for url_test in candidates:
try:
resp = page.request.post(url_test, data=json.dumps({"folioNumber": folio}),
headers={"Content-Type": "application/json"})
ct = resp.headers.get("content-type", "")
body = (resp.text())[:200]
print(f" POST {url_test}: {resp.status} [{ct[:30]}] body={body!r}")
except Exception as e:
print(f" POST {url_test}: ERROR {e}")
browser.close()
print(f"\n[OK] saved to {out_dir}/")
if __name__ == "__main__":
probe()
+93
View File
@@ -0,0 +1,93 @@
"""Wait 25s + dump all element IDs that have text content."""
from playwright.sync_api import sync_playwright
from pathlib import Path
import time
def probe():
folio = "484226062150"
url = f"https://web.bcpa.net/bcpaclient/#/Record-Search?folio={folio}"
out_dir = Path(__file__).parent.parent / "_probe_out" / "bcpa"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/131"
).new_page()
page.goto(url, wait_until="domcontentloaded", timeout=20000)
time.sleep(25)
# Save full rendered HTML
(out_dir / "FINAL_rendered.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "FINAL_rendered.png"), full_page=True)
# Extract ALL elements with id attribute that have text content
elements = page.evaluate("""
() => {
const out = [];
const all = document.querySelectorAll('[id]');
for (const el of all) {
const txt = (el.textContent || '').trim();
// Only collect leaf-like elements with reasonable text
if (txt && txt.length < 300 && el.children.length < 3) {
// Find the closest visible label (preceding sibling td.lblRecinfoNew or label)
let label = '';
const parent = el.closest('tr, div.row, p, .info-row');
if (parent) {
const labelEl = parent.querySelector('.lblRecinfoNew, .searchTblCategory, .searchTblCategory2, label, .info-label');
if (labelEl) label = (labelEl.textContent || '').trim().substring(0, 80);
}
out.push({id: el.id, text: txt.substring(0, 200), label: label});
}
}
return out;
}
""")
print(f"Elements with text content: {len(elements)}\n")
for e in elements:
label = e['label'][:50] if e['label'] else ''
print(f" #{e['id']:35s} [{label[:30]:30s}] = {e['text'][:80]!r}")
# Also extract table data — populated cells
print("\n\n===== TABLE DATA (rows with non-empty cells) =====")
tables_data = page.evaluate("""
() => {
const out = [];
const tables = document.querySelectorAll('table');
tables.forEach((tbl, idx) => {
const rows = [];
for (const tr of tbl.querySelectorAll('tr')) {
const cells = [];
for (const c of tr.querySelectorAll('td, th')) {
cells.push((c.textContent || '').trim());
}
if (cells.some(c => c && c.length > 0)) {
rows.push(cells);
}
}
if (rows.length > 0) {
// First row sometimes has the table identifier
out.push({idx, rows: rows.slice(0, 15)});
}
});
return out;
}
""")
for t in tables_data:
if not t["rows"]:
continue
first_row = " | ".join(t["rows"][0][:6])[:120]
print(f"\n--- Table {t['idx']} ({len(t['rows'])} rows) ---")
print(f" Header/R0: {first_row}")
for r in t["rows"][1:6]:
line = " | ".join(c[:35] for c in r[:6])[:140]
print(f" R: {line}")
browser.close()
if __name__ == "__main__":
probe()
+119
View File
@@ -0,0 +1,119 @@
"""Map ALL extractable fields from bcpa.net Broward PA SPA.
Uses real folio: 484226062150 (id=143, 31 NW 17 CT).
"""
from pathlib import Path
import json
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "bcpa"
out_dir.mkdir(parents=True, exist_ok=True)
folio = "484226062150"
url = f"https://web.bcpa.net/bcpaclient/#/Record-Search?folio={folio}"
# Also capture all XHR/fetch — bcpa SPA may load data via API endpoints
captured_apis: list[dict] = []
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36",
viewport={"width": 1400, "height": 1000},
)
page = ctx.new_page()
def on_response(resp):
try:
u = resp.url
if any(x in u for x in (".js", ".css", ".png", ".jpg", ".woff", ".ico", "google-analytics")):
return
ct = resp.headers.get("content-type", "")
if "json" in ct or "xml" in ct or "text" in ct:
captured_apis.append({
"url": u[:200],
"status": resp.status,
"content_type": ct,
})
except Exception:
pass
page.on("response", on_response)
print(f"[1] Loading {url}")
page.goto(url, wait_until="domcontentloaded", timeout=30000)
page.wait_for_timeout(12000) # SPA Angular render full data
# Save full HTML rendered
html = page.content()
(out_dir / "01_record.html").write_text(html, encoding="utf-8")
page.screenshot(path=str(out_dir / "01_record.png"), full_page=True)
print(f"\n[2] Page title: {page.title()}")
print(f" URL: {page.url}")
# Dump all visible text (sections)
body = page.inner_text("body")
print(f"\n[3] Body length: {len(body)} chars")
# Save full text
(out_dir / "02_text.txt").write_text(body, encoding="utf-8")
# First 4000 chars of body
print(f"\n[4] First 3000 chars of rendered text:\n{body[:3000]}")
# Look for key sections by keywords
print(f"\n[5] Key data sections detected (search by keyword):")
keywords = [
"owner", "mailing", "property address", "site address",
"assessed", "market", "just value", "taxable",
"year built", "living area", "adj bldg", "lot size",
"sale", "deed", "qualification", "doc#", "instrument",
"tax", "exemption", "homestead", "millage", "mill",
"improvement", "feature", "use code", "land",
"permit", "building", "construction",
]
for kw in keywords:
cnt = body.lower().count(kw)
if cnt > 0:
# Find first occurrence
idx = body.lower().find(kw)
snippet = body[max(0, idx-30):idx+150].replace("\n", " ")
print(f" '{kw}' ({cnt}x): ...{snippet[:180]}...")
# All tables on page
print(f"\n[6] Tables on page:")
tables = page.locator("table").all()
for i, tbl in enumerate(tables[:15]):
try:
rows = tbl.locator("tr").count()
if rows < 1:
continue
hdr_cells = tbl.locator("tr").first.locator("th, td").all()
hdrs = [(h.inner_text() or "").strip()[:30] for h in hdr_cells[:8]]
print(f" [{i}] rows={rows} headers={hdrs}")
except Exception:
pass
# API endpoints discovered
print(f"\n[7] API/XHR responses captured ({len(captured_apis)}):")
seen = set()
for api in captured_apis:
url_short = api["url"].split("?")[0]
if url_short in seen:
continue
seen.add(url_short)
print(f" {api['status']} {api['url'][:130]}")
# Save APIs to JSON
(out_dir / "03_apis.json").write_text(json.dumps(captured_apis, indent=2), encoding="utf-8")
browser.close()
print(f"\n[OK] Saved to {out_dir}/")
if __name__ == "__main__":
probe()
+73
View File
@@ -0,0 +1,73 @@
"""Try various wait strategies for bcpa.net SPA."""
from playwright.sync_api import sync_playwright
import time
def probe():
folio = "484226062150"
url = f"https://web.bcpa.net/bcpaclient/#/Record-Search?folio={folio}"
with sync_playwright() as p:
# Try with headless=False to see if it's headless detection
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131",
viewport={"width": 1400, "height": 900},
)
page = ctx.new_page()
print(f"[1] Loading {url} (domcontentloaded)")
page.goto(url, wait_until="domcontentloaded", timeout=20000)
print(" loaded; sleeping 15s for SPA to render...")
time.sleep(15)
# Check what's actually on screen now
body = page.inner_text("body")
print(f"\n[2] Body length: {len(body)}")
print(f"\n[3] Body sample (lines with numbers or $):")
for line in body.split("\n"):
line = line.strip()
if not line or len(line) < 3:
continue
if any(c.isdigit() for c in line) and len(line) < 200:
print(f" {line[:150]}")
# Look for photos
photos = page.evaluate(
"Array.from(document.querySelectorAll('img'))"
".filter(i => i.src.includes('/Photographs/') && i.naturalWidth > 200)"
".map(i => i.src)"
)
print(f"\n[4] Photos found via JS query: {len(photos)}")
for p_url in photos[:3]:
print(f" {p_url}")
# Check actualAgeId now
for elid in ["actualAgeId", "effectiveAgeId", "currentTaxYearMobileId", "lastTaxYearMobileId"]:
try:
txt = page.locator(f"#{elid}").inner_text(timeout=1500)
print(f" #{elid}: {txt!r}")
except Exception:
print(f" #{elid}: empty/error")
# Try waiting longer
print("\n[5] Sleeping another 10s and re-checking...")
time.sleep(10)
for elid in ["actualAgeId", "currentTaxYearMobileId"]:
try:
txt = page.locator(f"#{elid}").inner_text(timeout=1500)
print(f" #{elid}: {txt!r}")
except Exception:
print(f" #{elid}: empty/error")
body2 = page.inner_text("body")
print(f"\n[6] Body length after total 25s: {len(body2)}")
# If body got bigger, data appeared
if len(body2) > 5000:
print(f"\n Visible data: {body2[2000:4500]}")
browser.close()
if __name__ == "__main__":
probe()
+63
View File
@@ -0,0 +1,63 @@
"""Wait longer + extract values from BCPA via specific element IDs."""
from playwright.sync_api import sync_playwright
def probe():
folio = "484226062150"
url = f"https://web.bcpa.net/bcpaclient/#/Record-Search?folio={folio}"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131"
).new_page()
print(f"Loading {url}")
page.goto(url, wait_until="domcontentloaded", timeout=30000)
# Wait for Angular SPA — explicitly wait for actualAgeId div to have text
print("Waiting for actualAgeId div to populate...")
try:
page.wait_for_function(
"() => { const el = document.getElementById('actualAgeId'); return el && el.textContent.trim().length > 0; }",
timeout=25000,
)
print(" populated!")
except Exception as e:
print(f" TIMEOUT: {e}")
# Extract values from known IDs
ids = [
"actualAgeId", "effectiveAgeId", "currentTaxYearMobileId",
"lastTaxYearMobileId", "lastTwoTaxYearMobileId",
]
print("\nValues by element ID:")
for elid in ids:
try:
txt = page.locator(f"#{elid}").inner_text(timeout=2000)
print(f" #{elid}: {txt!r}")
except Exception as e:
print(f" #{elid}: ERROR {e}")
# Try get FULL inner text of property page
print("\n\nFull body text (filtered for non-empty data):")
body = page.inner_text("body")
# Find lines with $ or numeric data
for line in body.split("\n"):
line = line.strip()
if not line:
continue
if any(s in line for s in ("$", "ASSESS", "OWNER", "PROPERTY", "TAX", "SALE", "DEED", "YEAR")):
print(f" {line[:120]}")
# Save updated HTML
from pathlib import Path
out_dir = Path(__file__).parent.parent / "_probe_out" / "bcpa"
(out_dir / "03_after_wait.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "03_after_wait.png"), full_page=True)
browser.close()
if __name__ == "__main__":
probe()
+145
View File
@@ -0,0 +1,145 @@
"""scripts/probe_civitek.py — exploratory probe of Civitek OCRS flow.
NO production code. Just maps the click sequence + DOM for civitek_ocrs.py.
Usage:
python scripts/probe_civitek.py
"""
from __future__ import annotations
import sys
import time
from pathlib import Path
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "civitek"
out_dir.mkdir(parents=True, exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120",
)
page = context.new_page()
# STEP 1: County entry page (Hernando = 27)
print("[1] GET county entry page...")
page.goto("https://www.civitekflorida.com/ocrs/county/27/", wait_until="networkidle")
print(f" Title: {page.title()}")
print(f" URL: {page.url}")
(out_dir / "01_entry.html").write_text(page.content(), encoding="utf-8")
# STEP 2: Click "Public" button
print("\n[2] Click 'Public' button...")
public_btn = page.locator("button:has-text('Public')").first
print(f" Public button visible: {public_btn.is_visible()}")
public_btn.click()
page.wait_for_timeout(3000) # JSF AJAX settle
print(f" After click URL: {page.url}")
print(f" After click title: {page.title()}")
(out_dir / "02_after_public.html").write_text(page.content(), encoding="utf-8")
# STEP 3: Look for disclaimer / accept
body_text = page.inner_text("body")[:2000]
print(f"\n[3] Page body snippet (first 800 chars):\n{body_text[:800]}")
# Check for common disclaimer button names
for txt in ["Accept", "I Accept", "I Agree", "Agree", "Continue"]:
loc = page.locator(f"button:has-text('{txt}'), input[type=submit][value*='{txt}']")
count = loc.count()
if count > 0:
print(f" Found '{txt}' button: {count} match(es)")
# STEP 4: Look for search form fields
print("\n[4] Form fields on current page:")
inputs = page.locator("input").all()
for i, inp in enumerate(inputs[:30]):
try:
name = inp.get_attribute("name") or ""
type_ = inp.get_attribute("type") or ""
value = (inp.get_attribute("value") or "")[:30]
placeholder = inp.get_attribute("placeholder") or ""
if type_ in ("hidden",) and not name.startswith("javax"):
continue
if type_ == "hidden":
continue
print(f" [{i}] name={name!r} type={type_!r} value={value!r} placeholder={placeholder!r}")
except Exception as e:
print(f" [{i}] error: {e}")
print("\n[5] Buttons on page:")
buttons = page.locator("button, input[type=submit]").all()
for i, btn in enumerate(buttons[:15]):
try:
txt = (btn.inner_text() or btn.get_attribute("value") or "").strip()[:50]
btn_id = btn.get_attribute("id") or ""
print(f" [{i}] text={txt!r} id={btn_id!r}")
except Exception:
pass
# Snapshot
page.screenshot(path=str(out_dir / "02_after_public.png"), full_page=True)
# STEP 6: Click "I Agree" disclaimer
print("\n[6] Click 'I Agree'...")
agree = page.locator("button:has-text('I Agree')").first
if agree.count() > 0:
agree.click()
page.wait_for_timeout(3000) # JSF AJAX settle
print(f" After agree URL: {page.url}")
(out_dir / "03_after_agree.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "03_after_agree.png"), full_page=True)
print("\n[7] Search form fields after disclaimer:")
for inp in page.locator("input, select").all()[:40]:
try:
tag = inp.evaluate("el => el.tagName.toLowerCase()")
name = inp.get_attribute("name") or ""
type_ = inp.get_attribute("type") or ""
id_ = inp.get_attribute("id") or ""
placeholder = inp.get_attribute("placeholder") or ""
label_for = ""
if id_:
lbl = page.locator(f"label[for='{id_}']").first
if lbl.count() > 0:
label_for = lbl.inner_text()[:50]
if type_ == "hidden":
continue
print(f" <{tag}> id={id_!r} name={name!r} type={type_!r} placeholder={placeholder!r} label={label_for!r}")
except Exception:
pass
print("\n[8] Buttons after disclaimer:")
for btn in page.locator("button, input[type=submit]").all()[:20]:
try:
txt = (btn.inner_text() or btn.get_attribute("value") or "").strip()[:50]
btn_id = btn.get_attribute("id") or ""
print(f" text={txt!r} id={btn_id!r}")
except Exception:
pass
print("\n[9] Links / nav after disclaimer (first 20):")
for a in page.locator("a").all()[:20]:
try:
txt = (a.inner_text() or "").strip()[:60]
href = a.get_attribute("href") or ""
if txt:
print(f" {txt!r}{href[:80]}")
except Exception:
pass
print(f"\n[OK] Screenshots and HTML saved to {out_dir}/")
browser.close()
if __name__ == "__main__":
try:
probe()
except Exception as e:
import traceback
print(f"ERROR: {e}", file=sys.stderr)
traceback.print_exc()
sys.exit(1)
+92
View File
@@ -0,0 +1,92 @@
"""Probe Civitek Case Search tab structure."""
from pathlib import Path
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "civitek"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context().new_page()
# Walk to search
page.goto("https://www.civitekflorida.com/ocrs/county/27/")
page.wait_for_timeout(1500)
page.locator("button:has-text('Public')").first.click()
page.wait_for_timeout(2500)
page.locator("button:has-text('I Agree')").first.click()
page.wait_for_timeout(2500)
# Click "Case Search" tab (data-index=1)
print("[1] Clicking 'Case Search' tab...")
case_tab = page.locator("li[role='tab']:has-text('Case Search')").first
case_tab.click()
page.wait_for_timeout(2500)
(out_dir / "05_case_search_tab.html").write_text(page.content(), encoding="utf-8")
# Find new fields exposed under Case Search tab
print("\n[2] Inputs visible after switching tab:")
for inp in page.locator("input:visible, select:visible").all()[:30]:
try:
tag = inp.evaluate("el => el.tagName.toLowerCase()")
id_ = inp.get_attribute("id") or ""
name = inp.get_attribute("name") or ""
type_ = inp.get_attribute("type") or ""
placeholder = inp.get_attribute("placeholder") or ""
if type_ == "hidden":
continue
# Get label
label_for = ""
if id_:
lbl = page.locator(f"label[for='{id_}']").first
if lbl.count() > 0:
label_for = lbl.inner_text()[:50]
print(f" <{tag}> id={id_!r} type={type_!r} label={label_for!r}")
except Exception:
pass
# Test: search for a known case_number from our realauction inventory
# Most realauction cases are like "2024-CA-001234" or "23-2024-CA-001234"
# Try a generic test case number first to see the UI behavior
print("\n[3] Looking for case_number input field name...")
case_inputs = page.locator("input[id*='case' i], input[id*='Case'], input[name*='case' i]").all()
for inp in case_inputs[:5]:
try:
id_ = inp.get_attribute("id") or ""
name = inp.get_attribute("name") or ""
print(f" case-like input: id={id_!r} name={name!r}")
except Exception:
pass
# Submit empty to see validation errors
print("\n[4] Submitting empty Case Search to see required fields...")
search_btn = page.locator("button:has(.ui-button-text:text-is('Search'))").first
if search_btn.count() == 0:
search_btn = page.locator("button:has-text('Search')").first
try:
search_btn.click()
page.wait_for_timeout(2500)
except Exception as e:
print(f" click error: {e}")
# Capture error messages
print("\n[5] Validation messages after empty submit:")
for sel in [".ui-messages-error", ".ui-message-error", ".ui-message"]:
for m in page.locator(sel).all()[:5]:
try:
t = (m.inner_text() or "").strip()[:200]
if t:
print(f" [{sel}] {t}")
except Exception:
pass
(out_dir / "06_case_search_empty_submit.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "06_case_search.png"), full_page=True)
print(f"\n[OK] saved to {out_dir}/")
browser.close()
if __name__ == "__main__":
probe()
+107
View File
@@ -0,0 +1,107 @@
"""Probe Civitek search results structure with a real query."""
from __future__ import annotations
from pathlib import Path
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "civitek"
out_dir.mkdir(parents=True, exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(user_agent="Mozilla/5.0 Chrome/120")
page = ctx.new_page()
# Walk through to search form
page.goto("https://www.civitekflorida.com/ocrs/county/27/")
page.wait_for_timeout(1500)
page.locator("button:has-text('Public')").first.click()
page.wait_for_timeout(2500)
page.locator("button:has-text('I Agree')").first.click()
page.wait_for_timeout(2500)
print(f"[1] Search page URL: {page.url}")
# Fill search: business name (most foreclosure plaintiffs are entities)
page.fill("#form\\:search_tab\\:businessname", "BANK OF AMERICA")
# Skip case-type filter for first test (no checkbox click)
# Submit — button id is auto-generated, use text
print("[3] Clicking Search button (by text)...")
# PrimeFaces buttons render as button[type=submit] with ui-button-text
search_btn = page.locator("button:has(.ui-button-text:text-is('Search'))").first
if search_btn.count() == 0:
search_btn = page.locator("button:has-text('Search')").first
print(f" Search button visible: {search_btn.is_visible()}")
search_btn.click()
page.wait_for_timeout(6000)
print(f"[4] After submit URL: {page.url}")
(out_dir / "04_results.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "04_results.png"), full_page=True)
# Look for results table
print("\n[5] Tables on results page:")
tables = page.locator("table").all()
for i, tbl in enumerate(tables[:8]):
try:
rows = tbl.locator("tr").count()
cols = tbl.locator("tr").first.locator("td, th").count() if rows > 0 else 0
role = tbl.get_attribute("role") or ""
tbl_id = tbl.get_attribute("id") or ""
# Skip empty layout tables
if rows < 1:
continue
print(f" [{i}] id={tbl_id!r} role={role!r} rows={rows} cols={cols}")
# First header row
if rows > 0:
headers = tbl.locator("tr").first.locator("th, td").all()
hdr_texts = [(h.inner_text() or "").strip()[:25] for h in headers[:10]]
print(f" headers: {hdr_texts}")
# First data row (skip header)
if rows > 1:
row1 = tbl.locator("tr").nth(1).locator("td").all()
row1_texts = [(c.inner_text() or "").strip()[:30] for c in row1[:10]]
print(f" row1: {row1_texts}")
except Exception as e:
print(f" [{i}] error: {e}")
# Look for messages (no results, errors)
print("\n[6] Messages on page:")
msgs = page.locator(".ui-messages-error, .ui-messages-warn, .ui-messages-info, .ui-message").all()
for m in msgs[:10]:
try:
txt = (m.inner_text() or "").strip()[:200]
if txt:
print(f" msg: {txt}")
except Exception:
pass
# Look for links to case details
print("\n[7] Case detail links (first 5):")
case_links = page.locator("a[href*='case'], a[href*='detail']").all()[:5]
for a in case_links:
try:
txt = (a.inner_text() or "").strip()[:50]
href = a.get_attribute("href") or ""
print(f" {txt!r}{href[:100]}")
except Exception:
pass
# Pagination
print("\n[8] Pagination indicators:")
for txt in ["of ", "Page ", "Next", "records"]:
loc = page.locator(f"text=/{txt}/")
if loc.count() > 0:
try:
t = loc.first.inner_text()[:80]
print(f" '{txt}'{t!r}")
except Exception:
pass
print(f"\n[OK] saved to {out_dir}/")
browser.close()
if __name__ == "__main__":
probe()
+141
View File
@@ -0,0 +1,141 @@
"""Probe Duval Property Appraiser detail page — map ALL extractable fields.
Test address: 2352 Scenic View Ct, Jacksonville, FL 32218 (user's bug report).
"""
from pathlib import Path
import time
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "duval_pa"
out_dir.mkdir(parents=True, exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131",
)
page = ctx.new_page()
# Step 1: search page
print("[1] Loading search page...")
page.goto("https://paopropertysearch.coj.net/Basic/Search.aspx",
wait_until="networkidle", timeout=20000)
print(f" URL: {page.url}")
# Step 2: Fill address — 2352 SCENIC VIEW CT
print("[2] Filling form (2352 SCENIC VIEW CT 32218)...")
page.locator("#ctl00_cphBody_tbStreetNumber").fill("2352")
# No prefix
page.locator("#ctl00_cphBody_tbStreetName").fill("SCENIC VIEW")
# Suffix CT
try:
page.locator("#ctl00_cphBody_ddStreetSuffix").select_option(value="CT")
except Exception:
try:
page.locator("#ctl00_cphBody_ddStreetSuffix").select_option(label="CT")
except Exception:
pass
try:
page.locator("#ctl00_cphBody_tbZipCode").fill("32218")
except Exception:
pass
# Submit
page.locator("#ctl00_cphBody_bSearch").click()
page.wait_for_timeout(4000)
print(f"[3] After submit URL: {page.url}")
body_text = page.inner_text("body")[:500]
print(f" Body preview: {body_text[:400].encode('ascii', 'replace').decode('ascii')}")
(out_dir / "01_results.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "01_results.png"), full_page=True)
# If results table, click first row to get detail page
results_table = page.locator("table:has(tr:has(td))").first
try:
tables = page.locator("table").all()
for t in tables[:10]:
rows = t.locator("tr").count()
if rows < 2:
continue
hdrs = [(h.inner_text() or "").strip().lower() for h in t.locator("tr").first.locator("th, td").all()]
if any("re" in h or "parcel" in h or "owner" in h or "address" in h for h in hdrs):
# First data row → click first link
first_row = t.locator("tr").nth(1)
link = first_row.locator("a").first
if link.count() > 0:
href = link.get_attribute("href")
link_text = link.inner_text()
print(f"[4] Clicking result link: text={link_text!r} href={href}")
link.click()
page.wait_for_timeout(5000)
break
except Exception as e:
print(f" Click result error: {e}")
print(f"\n[5] Detail page URL: {page.url}")
(out_dir / "02_detail.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "02_detail.png"), full_page=True)
# Dump ALL element IDs that have text
elements = page.evaluate("""
() => {
const out = [];
const all = document.querySelectorAll('[id]');
for (const el of all) {
const txt = (el.textContent || '').trim();
if (txt && txt.length < 300 && el.children.length < 4) {
out.push({id: el.id, text: txt.substring(0, 200)});
}
}
return out;
}
""")
print(f"\n[6] Elements with text content: {len(elements)}\n")
for e in elements:
tid = e["id"]
# Skip nav/utility
if tid.startswith("uw-") or tid.startswith("__"):
continue
txt_safe = e['text'][:120].encode('ascii', 'replace').decode('ascii')
print(f" #{tid:50s} = {txt_safe!r}")
# All tables data
print("\n\n===== TABLES =====")
tables_data = page.evaluate("""
() => {
const out = [];
document.querySelectorAll('table').forEach((tbl, idx) => {
const rows = [];
for (const tr of tbl.querySelectorAll('tr')) {
const cells = [];
for (const c of tr.querySelectorAll('td, th')) {
cells.push((c.textContent || '').trim());
}
if (cells.some(c => c && c.length > 0)) rows.push(cells);
}
if (rows.length > 0) out.push({idx, rows});
});
return out;
}
""")
for t in tables_data:
rows = t["rows"]
if not rows or len(rows) < 1:
continue
print(f"\n--- Table {t['idx']} ({len(rows)} rows) ---")
for r in rows[:8]:
line = " | ".join(c[:50] for c in r[:8])[:200]
line_safe = line.encode('ascii', 'replace').decode('ascii')
print(f" {line_safe}")
browser.close()
print(f"\n[OK] saved to {out_dir}/")
if __name__ == "__main__":
probe()
+42
View File
@@ -0,0 +1,42 @@
"""Probe Hillsborough PA structure for one real parcel."""
from pathlib import Path
import time
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "hcpa"
out_dir.mkdir(parents=True, exist_ok=True)
parcel = "1932071V5000000002960U" # 609 NW 1ST AVE
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context(
user_agent="Mozilla/5.0 Chrome/131"
).new_page()
# Try deep link with folio
urls_to_try = [
f"https://gis.hcpafl.org/propertysearch/#/nav/Folio/{parcel}",
f"https://gis.hcpafl.org/propertysearch/#/Map/Folio/{parcel}",
f"https://www.hcpafl.org/property/{parcel}",
]
for url in urls_to_try:
page.goto(url, wait_until="domcontentloaded", timeout=20000)
time.sleep(7)
body = page.inner_text("body")
print(f"[{url}] body len: {len(body)}")
if "owner" in body.lower() and ("year built" in body.lower() or "year" in body.lower()):
print(f" HIT! Showing property details")
snippet = body[:1500].encode("ascii", "replace").decode("ascii")
print(f" Body preview: {snippet[:1200]}")
(out_dir / "01_detail.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "01_detail.png"), full_page=True)
break
browser.close()
if __name__ == "__main__":
probe()
+67
View File
@@ -0,0 +1,67 @@
"""Probe full Hillsborough PA flow — landing → search → detail."""
from pathlib import Path
import time
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "hcpa"
out_dir.mkdir(parents=True, exist_ok=True)
parcel = "1932071V5000000002960U"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context(user_agent="Mozilla/5.0 Chrome/131").new_page()
# Capture all GET XHR calls to find the property data API
captured = []
page.on("request", lambda req: captured.append({"m": req.method, "url": req.url})
if req.method in ("GET", "POST") else None)
# Land on the GIS PropertySearch SPA
page.goto("https://gis.hcpafl.org/propertysearch/", wait_until="domcontentloaded")
time.sleep(7)
print(f"Landing URL: {page.url}")
print(f"Title: {page.title()}")
# Look for inputs visible
inputs = page.locator("input:visible").all()
print(f"\nVisible inputs ({len(inputs)}):")
for inp in inputs[:10]:
try:
id_ = inp.get_attribute("id") or ""
name = inp.get_attribute("name") or ""
ph = inp.get_attribute("placeholder") or ""
aria = inp.get_attribute("aria-label") or ""
print(f" id={id_!r} name={name!r} placeholder={ph!r} aria={aria!r}")
except Exception:
pass
# Try to fill the folio input — find any input that mentions 'folio'
folio_input = page.locator(
"input[placeholder*='folio' i], input[aria-label*='folio' i], "
"input[placeholder*='parcel' i], input[name*='folio' i]"
).first
if folio_input.count() > 0:
print("\nFilling folio input...")
folio_input.fill(parcel)
time.sleep(1)
# Press Enter or click Search
page.keyboard.press("Enter")
time.sleep(8)
print(f"After search URL: {page.url}")
body = page.inner_text("body")[:1000]
print(f"Body: {body.encode('ascii','replace').decode('ascii')[:800]}")
# Filter captured to property-data calls
print(f"\nRelevant network calls ({len(captured)} total):")
for c in captured[-20:]:
if any(kw in c["url"] for kw in ("Property", "Folio", "Parcel", "GetProp", "json")):
print(f" {c['m']} {c['url'][:150]}")
browser.close()
if __name__ == "__main__":
probe()
+117
View File
@@ -0,0 +1,117 @@
"""Probe Miami-Dade PA detail page — fetch real folio and map fields."""
from pathlib import Path
import time
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "mdpa"
folio = "31-2202-034-2470" # 19201 COLLINS AVE — real deal
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(
user_agent="Mozilla/5.0 Chrome/131",
)
page = ctx.new_page()
# Land then go to detail directly if URL is parameterized
# Try direct deep link via folio
# MIA PA accepts folio in URL: /PropertySearch/#/?folio=XXXXXX (likely)
folio_clean = folio.replace("-", "")
deep_urls = [
f"https://apps.miamidadepa.gov/PropertySearch/#/?folio={folio_clean}",
f"https://apps.miamidadepa.gov/PropertySearch/#/details?folio={folio_clean}",
f"https://apps.miamidadepa.gov/PropertySearch/#/property/{folio_clean}",
]
for url in deep_urls:
print(f"[Try] {url}")
page.goto(url, wait_until="domcontentloaded", timeout=30000)
time.sleep(8)
print(f" URL after load: {page.url}")
print(f" Title: {page.title()}")
body = page.inner_text("body")[:300]
print(f" Body: {body[:200].encode('ascii','replace').decode('ascii')}")
# If we see property details, stop
if any(kw in body.lower() for kw in ("owner", "folio:", "year built")):
print(" HIT - detail page!")
break
time.sleep(2)
# If deep link didn't work, do search via form
body = page.inner_text("body")
if "owner" not in body.lower() or "year built" not in body.lower():
print("\n[Fallback] Doing form search via Folio tab...")
page.goto("https://apps.miamidadepa.gov/PropertySearch/", wait_until="domcontentloaded")
time.sleep(6)
# Click Folio tab
print(" Clicking Folio tab...")
folio_tab = page.locator("li[id^='k-tabstrip-tab']:has-text('Folio')").first
folio_tab.click()
time.sleep(2)
# Fill folio input
print(f" Filling folio {folio_clean}...")
folio_input = page.locator("kendo-textbox[formcontrolname='folio'] input").first
if folio_input.count() == 0:
# Try alternate selector
folio_input = page.locator("input.k-input-inner").nth(0)
folio_input.fill(folio_clean)
time.sleep(1)
# Click search button
search_btn = page.locator("button[aria-label='Search button']").first
search_btn.click()
time.sleep(8)
print(f" URL after search: {page.url}")
(out_dir / "02_detail.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "02_detail.png"), full_page=True)
# Dump all element IDs with text
print("\n[Dumping populated elements...]")
elements = page.evaluate("""
() => {
const out = [];
const all = document.querySelectorAll('[id], [class*="owner"], [class*="folio"], [class*="year"], [class*="value"]');
for (const el of all) {
const txt = (el.textContent || '').trim();
if (txt && txt.length < 200 && el.children.length < 4) {
out.push({
id: el.id || '(no id)',
cls: (el.className || '').substring(0, 60),
text: txt.substring(0, 150),
});
}
}
// Dedupe by (id, text)
const seen = new Set();
return out.filter(e => {
const k = e.id + '|' + e.text;
if (seen.has(k)) return false;
seen.add(k);
return true;
});
}
""")
# Print only elements with values that look meaningful
keywords = ("owner", "folio", "year", "built", "address", "value", "tax",
"sale", "deed", "bed", "bath", "sqft", "lot", "use", "subdivision",
"zoning", "homestead", "assessed", "market")
for e in elements[:300]:
txt_lower = e["text"].lower()
cls_lower = e["cls"].lower()
id_lower = e["id"].lower()
if any(k in txt_lower or k in cls_lower or k in id_lower for k in keywords):
safe = e["text"][:120].encode("ascii", "replace").decode("ascii")
print(f" {e['id'][:40]:40s} cls={e['cls'][:40]:40s} = {safe!r}")
browser.close()
if __name__ == "__main__":
probe()
+72
View File
@@ -0,0 +1,72 @@
"""Probe Miami-Dade PA portal — map ALL extractable fields.
URL: apps.miamidadepa.gov/PropertySearch/
"""
from pathlib import Path
import time
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "mdpa"
out_dir.mkdir(parents=True, exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131",
)
page = ctx.new_page()
print("[1] Loading landing page...")
page.goto("https://apps.miamidadepa.gov/PropertySearch/",
wait_until="domcontentloaded", timeout=30000)
time.sleep(8)
print(f" URL: {page.url}")
print(f" Title: {page.title()}")
# Visible inputs
print("\n[2] Visible inputs:")
for inp in page.locator("input:visible, select:visible").all()[:25]:
try:
tag = inp.evaluate("el => el.tagName.toLowerCase()")
id_ = inp.get_attribute("id") or ""
name = inp.get_attribute("name") or ""
type_ = inp.get_attribute("type") or ""
placeholder = inp.get_attribute("placeholder") or ""
aria = inp.get_attribute("aria-label") or ""
if type_ == "hidden":
continue
print(f" <{tag}> id={id_!r} name={name!r} type={type_!r} placeholder={placeholder!r} aria={aria!r}")
except Exception:
pass
# Buttons visible
print("\n[3] Visible buttons (first 10):")
for btn in page.locator("button:visible, a.button:visible, input[type=submit]:visible").all()[:10]:
try:
txt = (btn.inner_text() or btn.get_attribute("value") or "").strip()[:50]
btn_id = btn.get_attribute("id") or ""
print(f" text={txt!r} id={btn_id!r}")
except Exception:
pass
# Test search by a known Miami-Dade address.
# Use a famous address: "1 NE 1 St Miami FL 33132" (Miami courthouse)
# Or pick from the inputs we found.
# Save full HTML
(out_dir / "01_landing.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "01_landing.png"), full_page=True)
print(f"\n[4] Saved landing to {out_dir}/")
# Check for SPA framework hints
body_text = page.inner_text("body")[:500]
print(f"\n[5] Body preview: {body_text[:400].encode('ascii','replace').decode('ascii')}")
browser.close()
if __name__ == "__main__":
probe()
+52
View File
@@ -0,0 +1,52 @@
"""Probe Orange County PA — find right deep link + data structure."""
from pathlib import Path
import time
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "ocpa"
out_dir.mkdir(parents=True, exist_ok=True)
parcel = "292433310901080" # 3142 TIMUCUA CIR
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context(user_agent="Mozilla/5.0 Chrome/131").new_page()
# Capture XHR
responses = []
page.on("response", lambda r: responses.append(
{"url": r.url, "status": r.status, "ct": r.headers.get("content-type", "")[:30]}
) if r.status >= 200 and r.status < 400 else None)
urls = [
f"https://ocpaweb.ocpafl.org/parcelsearch?parcel={parcel}",
f"https://ocpaweb.ocpafl.org/Parcel/{parcel}",
f"https://www.ocpafl.org/searches/parcelsearch.aspx?parcel={parcel}",
]
for url in urls:
page.goto(url, wait_until="domcontentloaded", timeout=20000)
time.sleep(8)
body = page.inner_text("body")[:500]
print(f"\n[{url}]")
print(f" URL after: {page.url}")
print(f" Body snippet: {body[:300].encode('ascii','replace').decode('ascii')}")
# Check if data is present
if "owner" in body.lower() and any(w in body.lower() for w in ("year built", "just value", "sale")):
print(" DATA FOUND!")
(out_dir / "01_detail.html").write_text(page.content(), encoding="utf-8")
break
# Print relevant API responses
print(f"\nRelevant XHR responses ({len(responses)}):")
for r in responses[-15:]:
if "ocpa" in r["url"].lower() and ("json" in r["ct"] or "Parcel" in r["url"] or "Property" in r["url"]):
print(f" {r['status']} [{r['ct']}] {r['url'][:140]}")
browser.close()
if __name__ == "__main__":
probe()
+105
View File
@@ -0,0 +1,105 @@
"""Probe qPublic (Schneider) for Indian River — verify Turnstile is passive,
map search flow, extract sample fields."""
from pathlib import Path
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "qpublic"
out_dir.mkdir(parents=True, exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120",
)
page = ctx.new_page()
# Capture POST requests
captured = []
page.on("request", lambda r: captured.append({"m": r.method, "url": r.url}) if r.method == "POST" else None)
url = "https://qpublic.schneidercorp.com/Application.aspx?App=IndianRiverCountyFL&PageType=Search"
page.goto(url, wait_until="domcontentloaded")
page.wait_for_timeout(3000)
print(f"[1] Page loaded: {page.title()}")
# Look for disclaimer / accept page (qPublic typically has one on first visit)
body_text = page.inner_text("body")[:1500]
print(f"\n[2] Body snippet first 1000 chars:\n{body_text[:1000]}")
# Check for disclaimer button
for txt in ["Accept", "I Accept", "I Agree", "Agree", "Continue", "Disclaimer", "Acknowledge"]:
loc = page.locator(f"button:has-text('{txt}'), input[value*='{txt}'], a:has-text('{txt}')")
if loc.count() > 0:
try:
visible = loc.first.is_visible()
print(f" Found '{txt}': count={loc.count()} visible={visible}")
except Exception:
pass
# Try clicking Accept/I Agree if exists
for txt in ["Agree", "Accept"]:
loc = page.locator(f"button:has-text('{txt}'), input[value*='{txt}']").first
if loc.count() > 0:
try:
if loc.is_visible():
loc.click()
page.wait_for_timeout(3000)
print(f"[3] Clicked '{txt}' button")
break
except Exception as e:
print(f" Click {txt} error: {e}")
(out_dir / "01_landing.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "01_landing.png"), full_page=True)
# Look for search inputs
print(f"\n[4] Visible inputs:")
for inp in page.locator("input:visible, select:visible").all()[:20]:
try:
tag = inp.evaluate("el => el.tagName.toLowerCase()")
id_ = inp.get_attribute("id") or ""
name = inp.get_attribute("name") or ""
type_ = inp.get_attribute("type") or ""
placeholder = inp.get_attribute("placeholder") or ""
if type_ == "hidden":
continue
# Try to find a label
label = ""
if id_:
lbl = page.locator(f"label[for='{id_}']").first
if lbl.count() > 0:
label = lbl.inner_text()[:50]
print(f" <{tag}> id={id_!r} name={name!r} type={type_!r} placeholder={placeholder!r} label={label!r}")
except Exception:
pass
# Look for tabs/links to switch search modes
print(f"\n[5] Tabs / links found:")
for el in page.locator("a, button").all()[:30]:
try:
txt = (el.inner_text() or "").strip()[:60]
if any(k in txt.lower() for k in ("owner", "address", "parcel", "search", "by ")):
href = el.get_attribute("href") or ""
print(f" text={txt!r} href={href[:80]}")
except Exception:
pass
# Look for Turnstile widget instance
print(f"\n[6] Turnstile elements on page:")
for sel in [".cf-turnstile", "[data-sitekey]", "iframe[src*='turnstile']"]:
n = page.locator(sel).count()
print(f" {sel}: {n}")
# Captured POSTs so far (during landing)
print(f"\n[7] POSTs during landing: {len(captured)}")
for c in captured:
print(f" {c['m']} {c['url'][:100]}")
browser.close()
if __name__ == "__main__":
probe()
+132
View File
@@ -0,0 +1,132 @@
"""Probe qPublic Indian River address search with real address from user's deal.
Address: 674 30th Ave SW Vero Beach FL 32968 (Zillow MLS deal in screenshot).
"""
from pathlib import Path
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "qpublic"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120",
)
page = ctx.new_page()
# Capture network POSTs
captured = []
page.on("request", lambda r: captured.append({"m": r.method, "url": r.url[:150]}) if r.method == "POST" else None)
url = "https://qpublic.schneidercorp.com/Application.aspx?App=IndianRiverCountyFL&PageType=Search"
page.goto(url, wait_until="domcontentloaded")
page.wait_for_timeout(3000)
print(f"[1] Page: {page.title()}")
# Dismiss disclaimer/cookie banner if present (the "Agree" we saw)
for txt in ["I Agree", "Agree", "Accept All", "Accept"]:
loc = page.locator(f"button:has-text('{txt}'), a:has-text('{txt}')").first
if loc.count() > 0:
try:
if loc.is_visible():
print(f"[2] Clicking '{txt}' (likely cookie/disclaimer banner)")
loc.click()
page.wait_for_timeout(1500)
break
except Exception:
pass
# Search by Location Address: txtAddress
print("[3] Filling address search...")
addr_input_id = "ctlBodyPane_ctl01_ctl01_txtAddress"
page.evaluate(f"""
const inp = document.getElementById('{addr_input_id}');
inp.focus();
inp.value = '674 30th Ave SW';
inp.dispatchEvent(new Event('input', {{ bubbles: true }}));
inp.dispatchEvent(new Event('change', {{ bubbles: true }}));
""")
page.wait_for_timeout(800)
val = page.locator(f"#{addr_input_id}").input_value()
print(f" address input value: {val!r}")
# The search button is sibling to the address input — find Search button in same section
# qPublic uses ASP.NET LinkButton. Find any submit near the address input.
# Try the button just after txtAddress in DOM
print("[4] Looking for Search button near address input...")
search_btn_id = page.evaluate(f"""
() => {{
const inp = document.getElementById('{addr_input_id}');
if (!inp) return null;
// Find the parent panel/div, then look for a submit-like element
let parent = inp.closest('.panel-body, .form-group, fieldset, div');
while (parent) {{
const btn = parent.querySelector('input[type=submit], button[type=submit], a.btn');
if (btn) return btn.id || btn.outerHTML.substring(0, 200);
parent = parent.parentElement;
}}
return null;
}}
""")
print(f" found button: {search_btn_id!r}")
# Try the button by clicking the input's parent's Search button
# Use a simpler approach: click the button labeled "Search" closest to address input
try:
# In qPublic, each search section has its own Search button. The one for address
# is typically btnSearch right after txtAddress
btn = page.locator(f"#ctlBodyPane_ctl01_ctl01_btnSearch")
if btn.count() > 0:
btn.click()
else:
# Fallback: find any Search button in the address panel
section = page.locator("div:has(#ctlBodyPane_ctl01_ctl01_txtAddress)").first
section.locator("input[type=submit], button:has-text('Search')").first.click()
page.wait_for_timeout(15000) # longer wait for Cloudflare challenge
print(f"[5] After search: URL={page.url[:100]}")
except Exception as e:
print(f" Search click error: {e}")
# Dump body for diagnosis
body_full = page.inner_text("body")
print(f"\n[5b] Full body text ({len(body_full)} chars):\n{body_full[:2000]}")
(out_dir / "02_results.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "02_results.png"), full_page=True)
# Check for results
body = page.inner_text("body")
print(f"\n[6] Body length: {len(body)}")
# Look for parcel result table or "no results"
print("\n[7] Result indicators:")
for kw in ["no results", "not found", "no matches", "search results", "parcel"]:
cnt = body.lower().count(kw)
if cnt:
print(f" '{kw}': {cnt} occurrences")
# Look for result links
result_links = page.locator("a[href*='KeyValue='], a[href*='ParcelID='], a[href*='PageID=']").all()
print(f"\n[8] Result links (first 5):")
for a in result_links[:5]:
try:
txt = (a.inner_text() or "").strip()[:80]
href = a.get_attribute("href") or ""
if txt:
print(f" {txt!r}")
print(f" href: {href[:120]}")
except Exception:
pass
# Final POSTs check
print(f"\n[9] Total POSTs during search: {len(captured)}")
for c in captured[-8:]:
print(f" {c['m']} {c['url']}")
browser.close()
if __name__ == "__main__":
probe()
+105
View File
@@ -0,0 +1,105 @@
"""Re-backfill PA photos para Broward deals affected by Zillow photo misassignment bug.
Logic:
1. Find Broward deals whose current photos_urls SHARES URL with other deals
(= affected by bug)
2. Para esos, re-fetch foto from bcpa.net via pa_photo_lookup.fetch_pa_photo (gratis)
3. Update DB with correct PA photo URL
Cero costos Firecrawl. Usa Playwright local contra bcpa.net SPA.
"""
from __future__ import annotations
import argparse
import json
import sqlite3
import time
import sys
sys.path.insert(0, ".")
def main():
p = argparse.ArgumentParser()
p.add_argument("--dry-run", action="store_true")
p.add_argument("--db", default="data/deals.db")
p.add_argument("--limit", type=int, default=0, help="Cap for testing (0 = no limit)")
args = p.parse_args()
conn = sqlite3.connect(args.db)
cur = conn.cursor()
# Step 1: find Broward deals SIN foto (NULL/empty) OR con duplicate Zillow URL
# parcel_id valid + county Broward + sin photo o photo bugged
cur.execute("""
SELECT id, parcel_id, address, photos_urls
FROM deals
WHERE county = 'Broward'
AND parcel_id IS NOT NULL
AND parcel_id != ''
AND parcel_id NOT LIKE 'Property Appraiser%'
AND (
photos_urls IS NULL OR photos_urls = '' OR photos_urls = '[]'
OR photos_urls IN (
SELECT photos_urls FROM deals
WHERE photos_urls IS NOT NULL AND photos_urls != '[]'
GROUP BY photos_urls
HAVING COUNT(DISTINCT id) > 1
)
)
""")
affected = cur.fetchall()
if not affected:
print("No Broward deals affected by bug — DB clean.")
return
print(f"Found {len(affected)} Broward deals affected by photo bug.")
if args.limit:
affected = affected[: args.limit]
print(f"Limiting to first {args.limit} for testing.")
if args.dry_run:
for deal_id, parcel, addr, old_photos in affected[:10]:
print(f" Would re-fetch: id={deal_id} parcel={parcel} addr={(addr or '?')[:50]}")
print(f"\nDRY RUN — no changes. Re-run sin --dry-run to execute.")
return
# Step 2: re-fetch via PA
from data_fetchers.pa_photo_lookup import fetch_pa_photo
updated = 0
failed = 0
t0 = time.time()
for deal_id, parcel, addr, old_photos in affected:
addr_safe = (addr or "?")[:50].encode("ascii", "replace").decode("ascii")
print(f" Fetching id={deal_id} parcel={parcel} addr={addr_safe}...", flush=True)
try:
photo_url, meta = fetch_pa_photo(county="Broward", parcel_id=parcel,
timeout_seconds=25)
if photo_url:
new_photos = json.dumps([photo_url])
cur.execute("UPDATE deals SET photos_urls = ? WHERE id = ?",
(new_photos, deal_id))
conn.commit()
updated += 1
print(f" PA photo: {photo_url[-50:]}")
else:
# Set to NULL so card shows no photo (instead of stale Zillow)
cur.execute("UPDATE deals SET photos_urls = NULL WHERE id = ?", (deal_id,))
conn.commit()
failed += 1
err = (meta.get("error") or "no photo found")[:80]
print(f" NO PA photo found ({err}). photos_urls NULLed.")
except Exception as e:
failed += 1
err = str(e)[:80].encode("ascii", "replace").decode("ascii")
print(f" EXCEPTION: {err}")
time.sleep(2) # don't hammer if errors
elapsed = time.time() - t0
print()
print(f"Done in {elapsed:.0f}s. Updated {updated}, failed/no-photo {failed}.")
conn.close()
if __name__ == "__main__":
main()
+82
View File
@@ -0,0 +1,82 @@
"""Full HUD pipeline: scrape FL → persist → classify."""
from __future__ import annotations
import io, sys, time
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
def main() -> int:
print("=" * 70)
print("HUD Homestore FULL PIPELINE (FL only)")
print("=" * 70)
from scrapers.hud_homestore import run_scraper_to_db
from deals_db import init_db, list_deals
init_db()
def log(m): print(f" {m}")
t0 = time.perf_counter()
summary = run_scraper_to_db(states=["FL"], auto_classify=True, status_cb=log)
elapsed = time.perf_counter() - t0
print()
print("=" * 70)
print(f"PIPELINE SUMMARY (elapsed {elapsed:.0f}s = {elapsed/60:.1f} min)")
print("=" * 70)
for k, v in summary.items():
if k == "errors":
print(f" {k}: ({len(v)} items)")
for e in v[:3]:
print(f" - {e}")
else:
print(f" {k}: {v}")
# Show classification breakdown for HUD source
print()
print("--- HUD deals by classification ---")
hud = list_deals(source="hud_homestore", limit=200)
by_class = {}
for d in hud:
cs = d.get("classification_status") or "(unclassified)"
by_class[cs] = by_class.get(cs, 0) + 1
for cs in sorted(by_class.keys()):
print(f" {cs}: {by_class[cs]}")
print()
print("--- TOP 10 HUD deals by classification_score ---")
top = sorted(hud, key=lambda d: (d.get("classification_score") or 0), reverse=True)[:10]
print(f"{'#':<3} {'Score':<6} {'Status':<20} {'Strategy':<14} {'Price':<10} {'Beds':<5} Address (county)")
print("-" * 130)
import json as _json
for i, d in enumerate(top, 1):
score = d.get("classification_score") or 0
cls = d.get("classification_status") or "?"
strat = d.get("classification_strategy") or "?"
price = d.get("listing_price")
price_str = f"${price:,.0f}" if price else "N/A"
beds = d.get("beds")
addr = (d.get("address") or "?")[:70]
county = d.get("county") or ""
print(f"{i:<3} {score:<6} {cls:<20} {strat:<14} {price_str:<10} {beds!s:<5} {addr} ({county})")
# Print 3 sample reasons
print()
print("--- Sample reasons (top 3) ---")
for i, d in enumerate(top[:3], 1):
print(f"\n [{i}] {d.get('case_number')}{d.get('classification_status')} score {d.get('classification_score')}")
try:
reasons = _json.loads(d.get("classification_reasons") or "[]")
for r in reasons:
print(f" - {r}")
except Exception:
pass
return 0
if __name__ == "__main__":
sys.exit(main())
+110
View File
@@ -0,0 +1,110 @@
"""Full pipeline run: Miami-Dade Clerk scraper → deals.db → auto-classify.
Reports:
- Total deals scraped (today + N days ahead)
- Deals new / updated / errors
- Classifications by status (potential_winner / maybe / pass / red_flag)
- Sample of top 5 deals by classification_score
"""
from __future__ import annotations
import io, sys, time
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
def main() -> int:
print("=" * 70)
print("Miami-Dade Clerk FULL PIPELINE (scrape + persist + classify)")
print("=" * 70)
from scrapers.miami_dade_clerk import run_scraper_to_db
from deals_db import init_db, list_deals, count_deals_by_status
init_db()
def log(m: str) -> None:
print(f" {m}")
t0 = time.perf_counter()
summary = run_scraper_to_db(
days_ahead=14,
days_back=0,
auto_classify=True,
status_cb=log,
# max_dates not set → full 15 days
)
elapsed = time.perf_counter() - t0
print()
print("=" * 70)
print(f"PIPELINE SUMMARY (elapsed {elapsed:.0f}s = {elapsed/60:.1f} min)")
print("=" * 70)
for k, v in summary.items():
if k == "errors":
print(f" {k}: ({len(v)} items)")
for e in v[:5]:
print(f" - {e}")
else:
print(f" {k}: {v}")
# Show count breakdown
print()
print("--- deals.db counts by status ---")
counts = count_deals_by_status()
for s, n in sorted(counts.items()):
print(f" {s}: {n}")
# Show top 5 by classification score
print()
print("--- TOP 5 by classification_score ---")
top = list_deals(
classification=None,
source="miami_dade_clerk",
limit=200,
order_by="classification_score DESC NULLS LAST",
)[:5]
for i, d in enumerate(top, 1):
score = d.get("classification_score")
cls = d.get("classification_status")
strategy = d.get("classification_strategy")
addr = (d.get("address") or "(no address)")[:60]
sb = d.get("starting_bid")
sb_str = f"${sb:,.0f}" if sb else "Hidden/None"
av = d.get("estimated_arv")
av_str = f"${av:,.0f}" if av else "N/A"
fj = d.get("final_judgment_amount")
fj_str = f"${fj:,.0f}" if fj else "N/A"
reasons_raw = d.get("classification_reasons", "[]")
import json as _json
try:
reasons = _json.loads(reasons_raw) if reasons_raw else []
except Exception:
reasons = []
print(f"\n [{i}] score={score} status={cls} strategy={strategy}")
print(f" Case: {d.get('case_number')} | Type: {d.get('deal_type')}")
print(f" Address: {addr}")
print(f" Starting bid: {sb_str} | Assessed: {av_str} | Final Judgment: {fj_str}")
print(f" Reasons:")
for r in reasons[:4]:
print(f" - {r}")
# Show classifications by status
print()
print("--- Classifications by status (Miami-Dade only) ---")
by_class = {}
all_md = list_deals(source="miami_dade_clerk", limit=500)
for d in all_md:
cs = d.get("classification_status") or "(unclassified)"
by_class[cs] = by_class.get(cs, 0) + 1
for cs, n in sorted(by_class.items()):
print(f" {cs}: {n}")
print()
print(f"✅ B1.4 COMPLETE — {summary['deals_new']} new deals persisted + classified")
return 0
if __name__ == "__main__":
sys.exit(main())
+132
View File
@@ -0,0 +1,132 @@
"""Smoke test integral — validate all 4 county PA adapters work end-to-end.
Pulls one real deal from DB per county, runs PA fetch via unified router,
verifies key fields populated. Output: pass/fail summary for user confidence.
"""
from __future__ import annotations
import sys
import time
import sqlite3
sys.path.insert(0, ".")
def test_county(county: str, expect_real_data: bool = True) -> dict:
"""Pick a deal from DB for this county, run PA fetch, validate."""
out = {"county": county, "status": "?", "errors": [], "details": {}}
# Find a testable deal
conn = sqlite3.connect("data/deals.db")
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute(
"SELECT id, parcel_id, address FROM deals "
"WHERE county=? AND parcel_id IS NOT NULL AND parcel_id != '' LIMIT 1",
(county,)
)
row = cur.fetchone()
conn.close()
if not row:
out["status"] = "SKIP"
out["errors"].append(f"No deals in DB for {county}")
return out
deal_id = row["id"]
parcel = row["parcel_id"]
address = row["address"]
out["details"] = {
"deal_id": deal_id, "parcel_id": parcel, "address": address[:60] if address else "?",
}
# Run PA fetch via unified router
try:
from data_fetchers.property_appraiser import fetch_pa_record, is_pa_supported
if not is_pa_supported(county, "FL"):
out["status"] = "NOT_IMPLEMENTED"
return out
t0 = time.perf_counter()
rec = fetch_pa_record(county_name=county, state="FL",
address=address, parcel_id=parcel)
elapsed = round(time.perf_counter() - t0, 1)
out["details"]["elapsed_sec"] = elapsed
if not rec:
out["status"] = "FAIL"
out["errors"].append("PA router returned None")
return out
if rec.get("errors"):
out["status"] = "FAIL"
out["errors"].extend(rec["errors"][:2])
# Validate key fields
required = ["owner_name", "year_built", "just_value_current"]
missing = [f for f in required if not rec.get(f)]
if missing and expect_real_data:
out["status"] = "PARTIAL"
out["errors"].append(f"Missing fields: {missing}")
elif not missing:
out["status"] = "OK"
# Capture the good stuff
out["details"].update({
"owner": rec.get("owner_name"),
"year_built": rec.get("year_built"),
"just_value": rec.get("just_value_current"),
"assessed_value": rec.get("assessed_value_current"),
"homestead": rec.get("homestead_active"),
"sales_count": len(rec.get("sales_history") or []),
"renovation_signal": (rec.get("renovation_signal") or {}).get("is_flip_in_progress", False),
})
except Exception as e:
import traceback
out["status"] = "ERROR"
out["errors"].append(f"{type(e).__name__}: {e}")
out["details"]["trace"] = traceback.format_exc()[:300]
return out
def main():
counties = ["Duval", "Broward", "Miami-Dade", "Palm Beach"]
results = []
print("=" * 75)
print("PA Adapters Smoke Test — 4 counties")
print("=" * 75)
for c in counties:
print(f"\nTesting {c}...")
result = test_county(c)
results.append(result)
status = result["status"]
icon = {"OK": "PASS", "PARTIAL": "PART", "SKIP": "SKIP",
"FAIL": "FAIL", "ERROR": "ERR ", "NOT_IMPLEMENTED": "NOIM"}.get(status, "??")
det = result["details"]
print(f" [{icon}] {c} (deal id={det.get('deal_id','?')})")
if status in ("OK", "PARTIAL"):
print(f" Address: {det.get('address')}")
print(f" Owner: {det.get('owner')!r}")
print(f" Built: {det.get('year_built')}")
print(f" Just $: {det.get('just_value')}")
print(f" Assessed: {det.get('assessed_value')}")
print(f" Homestead:{det.get('homestead')}")
print(f" Sales: {det.get('sales_count')} records")
print(f" Elapsed: {det.get('elapsed_sec')}s")
if result["errors"]:
for e in result["errors"][:2]:
e_safe = str(e)[:120].encode("ascii", "replace").decode("ascii")
print(f" ERROR: {e_safe}")
# Summary
print()
print("=" * 75)
pass_count = sum(1 for r in results if r["status"] == "OK")
print(f"SUMMARY: {pass_count}/{len(results)} counties PASS")
print("=" * 75)
for r in results:
icon = "PASS" if r["status"] == "OK" else r["status"]
print(f" {icon:6s} {r['county']}")
if __name__ == "__main__":
main()
+13
View File
@@ -0,0 +1,13 @@
# stop-streamlit.ps1
# Mata solo el proceso python.exe que esta corriendo Streamlit.
# Preserva otros procesos python.exe que el usuario pueda tener corriendo
# en paralelo en otros proyectos.
#
# Llamado desde AR-House-Stop.vbs.
Get-CimInstance Win32_Process -Filter "Name='python.exe'" |
Where-Object { $_.CommandLine -like '*streamlit*' } |
ForEach-Object {
Write-Host "Stopping streamlit PID $($_.ProcessId): $($_.CommandLine)"
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
}
+193
View File
@@ -0,0 +1,193 @@
"""Test bug fix: ValueEstimator should NOT apply blind age deductions when
listing condition is 'Updated/Remodeled' or description mentions new items.
Test fixture: 2352 SCENIC VIEW Court Jacksonville FL (Zillow 44455413_zpid).
Year built: 1997, Condition: "Updated/Remodeled", Features: "BRAND NEW ROOF",
"NEW AC", "Fresh paint", description: "Fully updated throughout and move in ready".
EXPECTED before fix: total_deductions $28,000 (AC + roof + plumbing? No, plumbing
no aplica porque 1997 > 1995. Solo AC + roof = $16K).
EXPECTED after fix: total_deductions = $0 (condition + keywords ambos disparan suprimir).
"""
from __future__ import annotations
import sys
sys.path.insert(0, "D:/Proyectos Software/AR-House")
def test_scenic_view_data():
"""Real user fixture — 2352 SCENIC VIEW Court."""
from data_fetchers.property_value import calculate_age_deductions
# Real Zillow data as user reported
fixture = {
"year_built": 1997,
"listing_description": (
"Fully updated throughout and move in ready. Fresh paint, "
"BRAND NEW ROOF, garage epoxy floor, NEW AC, exterior paint."
),
"condition_status": "Updated/Remodeled",
"features_special": [
"Fresh paint",
"BRAND NEW ROOF",
"Garage epoxy",
"NEW AC",
"Exterior paint",
],
}
# CASE A: global skip via condition_status='Updated/Remodeled'
result_a = calculate_age_deductions(**fixture)
print("=" * 70)
print("CASE A — Full fixture (condition + features + description):")
print(f" total: ${result_a['total']:,}")
print(f" _skipped_global: {result_a.get('_skipped_global')}")
print(f" _skip_reason: {result_a.get('_skip_reason')}")
assert result_a["total"] == 0, f"Expected 0 deductions, got ${result_a['total']:,}"
assert result_a["_skipped_global"] is True, "Expected _skipped_global=True"
print(" PASS: total=$0, _skipped_global=True")
# CASE B: only condition_status, no description/features → still skip
result_b = calculate_age_deductions(
year_built=1997,
condition_status="Updated/Remodeled",
)
print()
print("CASE B — Only condition_status='Updated/Remodeled':")
print(f" total: ${result_b['total']:,}")
assert result_b["total"] == 0, f"Expected 0, got ${result_b['total']:,}"
print(" PASS: total=$0 (condition tag alone suffices)")
# CASE C: only description, no condition_status → still skip via 'fully updated'
result_c = calculate_age_deductions(
year_built=1997,
listing_description="Fully updated throughout and move in ready.",
)
print()
print("CASE C — Only description 'fully updated, move in ready':")
print(f" total: ${result_c['total']:,}")
print(f" _skip_reason: {result_c.get('_skip_reason')}")
assert result_c["total"] == 0, f"Expected 0, got ${result_c['total']:,}"
print(" PASS: global keyword detected, total=$0")
# CASE D: only features array (NEW AC, BRAND NEW ROOF) → per-item suppression
# No 'Updated/Remodeled' tag, no global 'fully updated' keyword.
# AC suppressed (year<2010), Roof suppressed (year<2005 N/A since 1997).
result_d = calculate_age_deductions(
year_built=1997,
features_special=["BRAND NEW ROOF", "NEW AC"],
)
print()
print("CASE D — Only features_special tags (no condition, no description):")
print(f" total: ${result_d['total']:,}")
print(f" ac: ${result_d['ac']:,} (year<2010, expecting suppression)")
print(f" roof: ${result_d['roof']:,} (year=1997 > 2005, no deduction triggered anyway)")
print(f" _suppressed_items: {result_d.get('_suppressed_items')}")
print(f" _reasons: {result_d.get('_reasons')}")
assert result_d["ac"] == 0, f"AC should be suppressed (NEW AC in features), got ${result_d['ac']:,}"
assert "ac" in result_d["_suppressed_items"], "ac should be in suppressed_items"
print(" PASS: AC deduction suppressed by features tag 'NEW AC'")
# CASE E: baseline — old property NO renovation evidence → all deductions apply
result_e = calculate_age_deductions(year_built=1985)
print()
print("CASE E — Old property (1985), no renovation evidence (baseline):")
print(f" total: ${result_e['total']:,}")
print(f" ac: ${result_e['ac']:,}, roof: ${result_e['roof']:,}, plumbing: ${result_e['plumbing']:,}, panel: ${result_e['panel']:,}")
assert result_e["ac"] > 0, "AC should apply (year<2010)"
assert result_e["roof"] > 0, "Roof should apply (year<2005)"
assert result_e["plumbing"] > 0, "Plumbing polybutylene should apply (1985 in 1978-1995)"
assert result_e["panel"] > 0, "Panel should apply (year<1990)"
print(f" PASS: all 4 deductions apply correctly = ${result_e['total']:,}")
# CASE F: same old property BUT description says repiped + new panel
result_f = calculate_age_deductions(
year_built=1985,
listing_description="Re-piped 2022 with PEX. New 200 amp panel. Original AC and roof.",
)
print()
print("CASE F — Old property (1985) with partial renovation evidence:")
print(f" total: ${result_f['total']:,}")
print(f" ac: ${result_f['ac']:,} (should apply — Original AC)")
print(f" roof: ${result_f['roof']:,} (should apply — Original roof)")
print(f" plumbing: ${result_f['plumbing']:,} (should be 0 — repiped)")
print(f" panel: ${result_f['panel']:,} (should be 0 — new panel)")
print(f" _suppressed_items: {result_f.get('_suppressed_items')}")
assert result_f["plumbing"] == 0, "Plumbing should be suppressed by 'Re-piped'"
assert result_f["panel"] == 0, "Panel should be suppressed by 'New 200 amp panel'"
assert result_f["ac"] > 0, "AC should still apply (Original AC mentioned)"
assert result_f["roof"] > 0, "Roof should still apply"
print(" PASS: partial suppression works correctly")
print()
print("=" * 70)
print("ALL TESTS PASSED. Bug fix verified.")
print("=" * 70)
def test_zillow_detail_parser():
"""Test the markdown parser extracts condition/features/status correctly."""
from scrapers.zillow import _parse_property_detail_md
# Simulated Zillow markdown for 2352 SCENIC VIEW
fake_md = """
# 2352 Scenic View Ct, Jacksonville, FL 32218
**$265,000**
Status: Active under contract
## What's special
- Fresh paint
- BRAND NEW ROOF
- Garage epoxy
- NEW AC
- Exterior paint
## Description
Fully updated throughout and move in ready. This 1997 home features fresh paint
inside and out, brand new roof, new AC, garage epoxy floor.
## Facts & Features
Year built: 1997
Condition: Updated/Remodeled
Type: Single Family
## Home value
Zestimate: $261,800
Tax assessed value: $222,653
"""
parsed = _parse_property_detail_md(fake_md)
print()
print("=" * 70)
print("ZILLOW DETAIL PARSER TEST:")
print("=" * 70)
for k, v in parsed.items():
if isinstance(v, list):
print(f" {k}: {v[:5]}")
elif isinstance(v, str) and len(v) > 80:
print(f" {k}: {v[:100]}...")
else:
print(f" {k}: {v}")
assert parsed["condition_status"] in ("Updated/Remodeled", "Updated/Remodeled".lower().capitalize()), \
f"Expected condition_status='Updated/Remodeled', got {parsed['condition_status']!r}"
assert parsed["year_built"] == 1997, f"Expected year_built=1997, got {parsed['year_built']}"
assert "active under contract" in (parsed["home_status"] or "").lower(), \
f"Expected active under contract, got {parsed['home_status']!r}"
assert parsed["active_under_contract"] is True, "Expected active_under_contract=True"
assert len(parsed["features_special"]) >= 4, \
f"Expected ≥4 features, got {len(parsed['features_special'])}: {parsed['features_special']}"
assert parsed["zestimate"] == 261800, f"Expected zestimate=261800, got {parsed['zestimate']}"
assert parsed["tax_assessed_value"] == 222653, \
f"Expected tax_assessed=222653, got {parsed['tax_assessed_value']}"
assert len(parsed["renovation_keywords_found"]) > 0, "Expected renovation keywords detected"
print()
print("PASS: parser extracts all expected fields correctly.")
if __name__ == "__main__":
test_scenic_view_data()
test_zillow_detail_parser()
+105
View File
@@ -0,0 +1,105 @@
"""Test interactivo de court_records.py — solo Duval.
Empieza con sitios publicos:
1. Property Appraiser (paopropertysearch.coj.net) sin login
2. Official Records (or.duvalclerk.com) con disclaimer
Si los selectores no matchean, dump page.content() a un archivo para
inspeccionar la estructura real y ajustar.
"""
from __future__ import annotations
import io
import os
import sys
import time
from pathlib import Path
# Stdout UTF-8 para emojis
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
# Forzar el flag para esta sesion de test
os.environ["ENABLE_COURT_RECORDS"] = "true"
from data_fetchers.court_records import fetch_court_records # noqa: E402
def main() -> int:
print("=" * 70)
print("Test interactivo: court_records.py — Duval scraper")
print("=" * 70)
print(f"ENABLE_COURT_RECORDS: {os.getenv('ENABLE_COURT_RECORDS')}")
print()
test_addresses = [
("117 W Duval St, Jacksonville, FL 32202", "Jacksonville City Hall — confirma scraper funciona"),
("5005 N Pearl St, Jacksonville, FL 32206", "Pearl St real (encontrado en busqueda broad)"),
("3245 N Pearl St, Jacksonville, FL 32206", "Fictional address — expect no results (graceful)"),
]
for addr, desc in test_addresses:
print("" * 70)
print(f"TEST: {addr}")
print(f" ({desc})")
print("" * 70)
t0 = time.perf_counter()
result = fetch_court_records(address=addr, county_name="Duval")
elapsed = time.perf_counter() - t0
print(f" status: {result.get('status')}")
print(f" owner_name: {result.get('owner_name')}")
print(f" re_number: {result.get('re_number')}")
print(f" lis_pendens_count: {result.get('lis_pendens_count', 0)}")
print(f" sources_used: {result.get('sources_used', [])}")
print(f" errors: {len(result.get('errors', []))} errors")
for e in (result.get('errors') or [])[:3]:
print(f" - {e[:200]}")
print(f" elapsed: {elapsed:.1f}s")
print()
return 0
def main_OLD() -> int:
address = "3245 N Pearl St, Jacksonville, FL 32206"
print(f"Target address: {address}")
t0 = time.perf_counter()
result = fetch_court_records(address=address, county_name="Duval")
elapsed = time.perf_counter() - t0
print(f"Tiempo total: {elapsed:.1f}s")
print()
print("=" * 70)
print("RESULTADO")
print("=" * 70)
for k, v in result.items():
if k == "lis_pendens" and isinstance(v, list):
print(f" {k}: {len(v)} case(s)")
for i, c in enumerate(v, 1):
print(f" [{i}] {c}")
elif k == "errors":
print(f" {k}: ({len(v)} errores)")
for e in v:
print(f" - {e}")
else:
print(f" {k}: {v}")
print()
if result.get("status") == "LIS_PENDENS_ACTIVE":
print(" → DETECCION CONFIRMADA: foreclosure case activo en Duval")
return 0
elif result.get("status") == "CLEAN":
print(" → Sin lis pendens encontrado (puede ser limpio o owner_name no matchea)")
return 0
elif result.get("status") == "UNKNOWN":
print(" → UNKNOWN: scraper fallo en algun paso. Ver errors arriba.")
return 1
else:
print(f" → Status: {result.get('status')}")
return 1
if __name__ == "__main__":
sys.exit(main())
+272
View File
@@ -0,0 +1,272 @@
"""Unit + smoke tests para Phase 3A — deals_db + DealClassifier.
Tests:
1. deals_db CRUD: init, insert, dedup, list, update_classification, update_status
2. firecrawl tracking: record_usage, get_month_usage, alert levels
3. DealClassifier: precompute_heuristics, build_prompt, parse output
4. Smoke test: clasificar 4 deals reales (cada uno con expectativa clara)
"""
from __future__ import annotations
import io, sys, time, os
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
def run_unit_tests():
"""Test deals_db CRUD + Firecrawl tracking sin llamar Ollama."""
print("=" * 70)
print("UNIT TESTS — deals_db + Firecrawl tracking")
print("=" * 70)
# Set DB path to a temp location for isolated testing
test_db = ROOT / "data" / "deals_test.db"
if test_db.exists():
test_db.unlink()
os.environ.setdefault("DEALS_DB_PATH", str(test_db.relative_to(ROOT)))
import deals_db
# Override module-level _DB_PATH to point at test DB
deals_db._DB_PATH = test_db
# Reset thread-local connection
if hasattr(deals_db._LOCAL, "conn"):
deals_db._LOCAL.conn.close()
del deals_db._LOCAL.conn
from deals_db import (
init_db, insert_deal, get_deal_by_hash, get_deal_by_id,
update_classification, update_status, list_deals,
record_scraper_run, finish_scraper_run, list_recent_scraper_runs,
record_firecrawl_usage, get_firecrawl_month_usage,
firecrawl_alert_level, is_firecrawl_paused, firecrawl_budget_status,
count_deals_by_status, compute_deal_hash,
)
init_db()
print("init_db OK")
# Test 1: insert + dedup
d1 = {
"source": "miami_dade_clerk",
"source_url": "https://example.com/case/12345",
"address": "123 Main St, Miami, FL 33101",
"city": "Miami", "state": "FL", "zip": "33101", "county": "Miami-Dade",
"listing_price": 150000,
"deal_type": "foreclosure",
"starting_bid": 80000,
"estimated_arv": 240000,
"beds": 3, "baths": 2.0, "sqft": 1400, "year_built": 1985,
"case_number": "2025-CA-001234",
"auction_date": "2026-06-15",
}
id1, is_new = insert_deal(d1)
assert is_new, f"first insert should be new, got is_new={is_new}"
assert id1 > 0
print(f"INSERT 1: id={id1}, is_new={is_new} OK")
# Test 2: re-insert same → should update, not insert
id1b, is_new_b = insert_deal(d1)
assert id1b == id1
assert not is_new_b
print(f"INSERT 1 (re-insert): id={id1b} same as first, is_new={is_new_b} OK")
# Test 3: different source → new row
d2 = dict(d1)
d2["source"] = "zillow"
d2["source_url"] = "https://zillow.com/123"
id2, is_new_2 = insert_deal(d2)
assert is_new_2
assert id2 != id1
print(f"INSERT 2 (different source): id={id2} OK")
# Test 4: hash function deterministic
h1 = compute_deal_hash("miami_dade_clerk", "123 main st miami fl", 150000)
h2 = compute_deal_hash("miami_dade_clerk", "123 Main St Miami FL", 150000)
assert h1 == h2, "case-insensitive hash failed"
print("compute_deal_hash case-insensitive OK")
# Test 5: update_classification
update_classification(
deal_id=id1,
status="potential_winner",
score=85,
reasons=["price_per_sqft $107 in Class C → 25% below market",
"cap_rate_rough 8.5% above buy_hold threshold"],
strategy="buy_hold",
)
deal = get_deal_by_id(id1)
assert deal["classification_status"] == "potential_winner"
assert deal["classification_score"] == 85
assert deal["status"] == "classified", f"status should auto-flip to classified, got {deal['status']}"
assert "Class C" in deal["classification_reasons"]
print("update_classification OK (auto-flipped status new→classified)")
# Test 6: list_deals filter
winners = list_deals(classification="potential_winner")
assert len(winners) == 1
assert winners[0]["id"] == id1
print(f"list_deals(classification=potential_winner): {len(winners)} deal OK")
# Test 7: update_status
update_status(id1, "interesting")
deal = get_deal_by_id(id1)
assert deal["status"] == "interesting"
print("update_status OK")
# Test 8: count_deals_by_status
counts = count_deals_by_status()
print(f"count_deals_by_status: {counts}")
assert counts.get("interesting", 0) == 1
assert counts.get("new", 0) == 1
# Test 9: scraper runs
run_id = record_scraper_run("miami_dade_clerk")
assert run_id > 0
finish_scraper_run(run_id, deals_found=15, deals_new=3, deals_updated=12,
errors_count=0, firecrawl_credits_used=0, status="success")
runs = list_recent_scraper_runs(source="miami_dade_clerk")
assert len(runs) == 1
assert runs[0]["status"] == "success"
assert runs[0]["deals_new"] == 3
print(f"scraper_runs: id={run_id} deals_new={runs[0]['deals_new']} OK")
# Test 10: firecrawl tracking
record_firecrawl_usage(source="zillow_scraper", credits=5, url="https://...")
record_firecrawl_usage(source="realtor_scraper", credits=8, url="https://...")
total = get_firecrawl_month_usage()
assert total == 13, f"expected 13, got {total}"
print(f"firecrawl_month_usage: {total} credits OK")
# Test 11: alert level
level = firecrawl_alert_level()
assert level == "ok", f"with 13 credits and budget 500, should be 'ok', got {level}"
paused = is_firecrawl_paused()
assert not paused
print(f"firecrawl_alert_level: {level} OK, paused={paused}")
# Test 12: simulate hitting 80% threshold
record_firecrawl_usage(source="bulk_test", credits=400)
level = firecrawl_alert_level()
assert level == "warn", f"with 413/500 credits should be 'warn', got {level}"
print(f"firecrawl alert at 82% usage: {level} OK")
# Test 13: simulate hitting 95% pause
record_firecrawl_usage(source="bulk_test", credits=65)
level = firecrawl_alert_level()
assert level == "pause", f"with 478/500 credits should be 'pause', got {level}"
assert is_firecrawl_paused()
print(f"firecrawl auto-pause at 95.6% usage: {level} OK")
# Test 14: budget snapshot
snap = firecrawl_budget_status()
print(f"firecrawl_budget_status: {snap}")
# Cleanup
deals_db._LOCAL.conn.close()
del deals_db._LOCAL.conn
test_db.unlink()
print()
print("=== ALL UNIT TESTS PASSED ===")
return 0
def run_classifier_smoke():
"""Smoke test: clasificar 4 deals reales con expectativas."""
print()
print("=" * 70)
print("SMOKE TEST — DealClassifier con 4 deals reales")
print("=" * 70)
from deal_classifier import classify_deal
test_cases = [
{
"name": "Miami foreclosure $80K starting bid, ARV $240K",
"expected_status": "potential_winner",
"deal": {
"source": "miami_dade_clerk",
"deal_type": "foreclosure",
"address": "789 NE 1st St, Miami, FL 33132",
"city": "Miami", "county": "Miami-Dade", "state": "FL", "zip": "33132",
"listing_price": 80000, "starting_bid": 80000, "estimated_arv": 240000,
"beds": 3, "baths": 2.0, "sqft": 1400, "year_built": 1995,
"case_number": "2025-CA-001234",
"auction_date": "2026-06-15",
},
},
{
"name": "Miami MLS retail $450K Class B normal price",
"expected_status": "maybe", # normal MLS dentro de market
"deal": {
"source": "zillow",
"deal_type": "mls",
"address": "100 Brickell Ave, Miami, FL 33131",
"city": "Miami", "county": "Miami-Dade", "state": "FL", "zip": "33131",
"listing_price": 450000,
"beds": 3, "baths": 2.0, "sqft": 1800, "year_built": 2005,
},
},
{
"name": "Jacksonville $25K tax_deed 1967 build (red flag)",
"expected_status": "red_flag",
"deal": {
"source": "duval_tax_collector",
"deal_type": "tax_deed",
"address": "456 W 21st St, Jacksonville, FL 32209",
"city": "Jacksonville", "county": "Duval", "state": "FL", "zip": "32209",
"listing_price": 25000, "starting_bid": 25000,
"beds": 2, "baths": 1.0, "sqft": 900, "year_built": 1967,
},
},
{
"name": "Hialeah MLS $600K Class C overpriced",
"expected_status": "pass",
"deal": {
"source": "realtor",
"deal_type": "mls",
"address": "1234 W 49th St, Hialeah, FL 33012",
"city": "Hialeah", "county": "Miami-Dade", "state": "FL", "zip": "33012",
"listing_price": 600000,
"beds": 3, "baths": 2.0, "sqft": 1500, "year_built": 1970,
},
},
]
results = []
for i, tc in enumerate(test_cases, 1):
print(f"\n--- [{i}/{len(test_cases)}] {tc['name']} ---")
print(f" Expected: {tc['expected_status']}")
t0 = time.perf_counter()
result = classify_deal(tc["deal"])
dur = time.perf_counter() - t0
actual = result["classification_status"]
match = "" if actual == tc["expected_status"] else "⚠️"
print(f" Actual: {actual} (score {result['score']})")
print(f" Strategy: {result['strategy']}")
print(f" Reasons:")
for r in result["reasons"]:
print(f" - {r}")
print(f" Match: {match} | Duration: {dur:.1f}s | tokens: {result['_meta']['tokens']}")
if result["_meta"].get("ollama_error"):
print(f" ❌ Ollama error: {result['_meta']['ollama_error']}")
if result["_meta"].get("parse_error"):
print(f" ⚠️ Parse error: {result['_meta'].get('parse_error_detail')}")
results.append((tc["name"], tc["expected_status"], actual, dur))
print()
print("=" * 70)
print("SUMMARY")
print("=" * 70)
matches = sum(1 for _, exp, act, _ in results if exp == act)
avg_dur = sum(d for _, _, _, d in results) / len(results)
print(f" Match rate: {matches}/{len(results)}")
print(f" Avg duration: {avg_dur:.1f}s per deal")
return 0
if __name__ == "__main__":
rc1 = run_unit_tests()
rc2 = run_classifier_smoke()
sys.exit(rc1 or rc2)
+147
View File
@@ -0,0 +1,147 @@
"""Validate B3 source_url bugfix.
1. Unit test: build_deep_link() with sample case numbers.
2. Scrape fresh HUD listings verify each deal_record has unique deep-link.
3. HTTP-verify (via Playwright) that 3 random deep-links actually open the property.
"""
from __future__ import annotations
import io, sys, time
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from scrapers.hud_homestore import build_deep_link, scrape_hud_homestore
def test_unit():
"""Unit tests for build_deep_link()."""
print("=" * 60)
print("UNIT TESTS — build_deep_link()")
print("=" * 60)
cases = [
("093-676572", "https://www.hudhomestore.gov/PropertyDetails?caseNumber=093-676572"),
("093-612260", "https://www.hudhomestore.gov/PropertyDetails?caseNumber=093-612260"),
(None, None),
("", None),
(" 093-727486 ", "https://www.hudhomestore.gov/PropertyDetails?caseNumber=093-727486"),
]
failures = 0
for inp, expected in cases:
actual = build_deep_link(inp)
ok = "" if actual == expected else ""
print(f" {ok} build_deep_link({inp!r}) → {actual}")
if actual != expected:
failures += 1
print(f" expected: {expected}")
return failures
def test_scrape_unique_urls():
"""Scrape fresh + verify each deal has a unique deep-link."""
print()
print("=" * 60)
print("SCRAPE TEST — fresh scrape, verify unique deep-links")
print("=" * 60)
deals = scrape_hud_homestore(
states=["FL"],
status_cb=lambda m: print(f" {m}"),
use_cache=True,
)
print(f"\n Total: {len(deals)} deals scraped")
# Verify uniqueness
urls = [d.get("source_url") for d in deals]
unique = set(urls)
print(f" Unique source_urls: {len(unique)} (should be == {len(deals)})")
# Sample 5
print()
print(" Sample 5 deal source_urls:")
for d in deals[:5]:
url = d.get("source_url")
case = d.get("case_number")
addr = (d.get("address") or "?")[:50]
# Verify URL contains case_number
case_in_url = case in (url or "") if case else False
print(f" case={case} url={url}")
print(f" addr={addr} | case_in_url={case_in_url}")
return deals
def test_http_verify(deals, n_samples=3):
"""HTTP-load 3 random deep-links via Playwright; verify page renders the property."""
print()
print("=" * 60)
print(f"HTTP VERIFY — {n_samples} random deep-links open correct property")
print("=" * 60)
from playwright.sync_api import sync_playwright
REAL_UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
# Pick first, middle, last
samples = [deals[0], deals[len(deals)//2], deals[-1]] if len(deals) >= 3 else deals
failures = 0
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_context(user_agent=REAL_UA, viewport={"width": 1400, "height": 900}).new_page()
page.set_default_timeout(20_000)
# Set session
page.goto("https://www.hudhomestore.gov/", wait_until="networkidle")
time.sleep(1.5)
for d in samples:
url = d.get("source_url")
case = d.get("case_number")
addr = d.get("address") or ""
print(f"\n Testing case={case}")
print(f" URL: {url}")
print(f" Expected address: {addr[:60]}")
try:
page.goto(url, wait_until="networkidle", timeout=20_000)
time.sleep(3)
body = page.locator("body").inner_text()
# Verify body contains the case number AND part of the expected address
# Use the street number as a strong signal
import re
street_num_match = re.match(r"^(\d+)", addr)
street_num = street_num_match.group(1) if street_num_match else None
has_case = case in body
has_street_num = (street_num in body) if street_num else False
has_price = (str(int(d.get("listing_price") or 0)) in body.replace(",", ""))
print(f" has_case#={has_case}, has_street_num={has_street_num}, has_price={has_price}")
if has_case and (has_street_num or has_price):
print(f" ✅ Deep-link renders the correct property")
else:
print(f" ❌ Deep-link does NOT render the expected property")
failures += 1
except Exception as e:
print(f" ❌ ERROR: {e}")
failures += 1
browser.close()
return failures
def main():
rc = 0
rc += test_unit()
deals = test_scrape_unique_urls()
if deals:
rc += test_http_verify(deals, n_samples=3)
print()
print("=" * 60)
if rc == 0:
print("✅ ALL TESTS PASSED")
else:
print(f"{rc} failures")
return rc
if __name__ == "__main__":
sys.exit(main())
+245
View File
@@ -0,0 +1,245 @@
"""Test end-to-end del pipeline AR-House con el escenario Jacksonville $70K.
Valida que los 3 bugs estan arreglados:
- Bug 1: outputs exhaustivos (>= 400 palabras/seccion en agentes tecnicos)
- Bug 2: CRITICAL_RED_FLAG detectado + inyectado a todos los agentes
- Bug 3: anomalias detectadas (Cap Rate >12%, etc.) + DealAnalyzer incluye
seccion "Validacion de Inputs Requerida"
Corre los 8 agentes Ollama en secuencia (~5-8 min). Imprime status en stdout
y guarda JSON completo en analyses/. Al final hace assertions y prints
"PASS/FAIL" para cada bug.
"""
from __future__ import annotations
import io
import json
import sys
import time
from pathlib import Path
# Forzar stdout UTF-8 para Windows (los emojis en logs sino crashean cp1252)
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
# Imports despues del fix de stdout
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from orchestrator import DealInputs, BuyerProfile, analyze_deal # noqa: E402
def status_cb(msg: str) -> None:
"""Print status con timestamp."""
print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True)
def count_words(text: str) -> int:
if not text:
return 0
return len(text.split())
def main() -> int:
print("=" * 70)
print("AR-House — End-to-end test: Jacksonville $70K (Bug 1+2+3)")
print("=" * 70)
# Escenario clasico "demasiado bueno para ser verdad" con ADDRESS REAL
# 5005 N Pearl St (Duval PA confirmed: owner JONES JOHN N, RE# 027301-0000)
# Precio $70K simulado + rent $1,500/mo → triggers suspicious_low + anomalias.
# Con ENABLE_COURT_RECORDS=true, court_records debe popular owner_name.
deal = DealInputs(
address="5005 N Pearl St, Jacksonville, FL 32206",
price=70_000,
rent=1_500,
property_tax=2_000,
insurance=1_800,
hoa=0,
sqft=1_200,
beds=3,
baths=2.0,
year_built=1985,
arv=180_000,
rehab_override=25_000, # skipping PhotoInspector
deal_type="mls",
)
profile = BuyerProfile(
profile_class="C",
fico=720,
capital_available=50_000,
nationality="Argentina",
)
print(f"\nDEAL: {deal.address}")
print(f" price={deal.price:,} rent={deal.rent:,}/mo arv={deal.arv:,}")
print(f" year_built={deal.year_built}, rehab_override={deal.rehab_override:,}")
print()
t0 = time.perf_counter()
result = analyze_deal(deal, profile, photo_bytes=None, status_cb=status_cb)
elapsed = time.perf_counter() - t0
print()
print("=" * 70)
print(f"ANALISIS COMPLETADO en {elapsed:.0f}s")
print("=" * 70)
# ════════════════════════════════════════════════════════════
# Validaciones automaticas
# ════════════════════════════════════════════════════════════
# analyze_deal devuelve AnalysisResult dataclass; los AgentResult internos
# son convertidos a dict por asdict() en la construccion. Acceder via [key].
# Wave 1.5A: check court records flow
court = (result.verified_data or {}).get("court_records") or {}
pv = result.price_validation or {}
anomalies = (result.computed_scenarios or {}).get("anomalies", {})
deal_an = (result.deal_analysis or {}).get("output", "") or ""
coord = (result.final or {}).get("output", "") or ""
research = (result.research or {}).get("output", "") or ""
lender = (result.lender or {}).get("output", "") or ""
value_est = (result.value_estimate or {}).get("output", "") or ""
offer_str = (result.offer_strategy or {}).get("output", "") or ""
briefing = (result.executive_briefing or {}).get("output", "") or ""
print()
print("" * 70)
print("WAVE 1.5A — Court Records Flow")
print("" * 70)
print(f" court_records.status: {court.get('status')}")
print(f" court_records.county: {court.get('county')}")
print(f" court_records.owner_name: {court.get('owner_name')}")
print(f" court_records.re_number: {court.get('re_number')}")
print(f" court_records.lis_pendens_count: {court.get('lis_pendens_count', 0)}")
print(f" sources_used: {court.get('sources_used', [])}")
owner = court.get('owner_name') or ''
owner_mentions = {
"DealAnalyzer": owner in deal_an if owner else False,
"FloridaResearcher": owner in research if owner else False,
"LenderMatcher": owner in lender if owner else False,
"Coordinator": owner in coord if owner else False,
"ValueEstimator": owner in value_est if owner else False,
"OfferStrategist": owner in offer_str if owner else False,
"ContextualGlossaryAgent": owner in briefing if owner else False,
}
print(f" Owner name '{owner}' mentions in agent outputs:")
for agent, mentioned in owner_mentions.items():
print(f" {agent}: {'' if mentioned else '⚠️'}")
wave15a_pass = (
court.get('status') in ('OWNER_VERIFIED', 'LIS_PENDENS_ACTIVE')
and bool(owner)
and sum(owner_mentions.values()) >= 2
)
print(f" → Wave 1.5A flow: {'✅ PASS' if wave15a_pass else '⚠️ PARTIAL/FAIL'}")
print()
print("" * 70)
print("BUG 2 — Price Discrepancy Detection")
print("" * 70)
pv_status = pv.get("status")
pv_disc = pv.get("signed_max_discrepancy_pct")
print(f" price_validation.status: {pv_status}")
print(f" signed_max_discrepancy_pct: {pv_disc}")
print(f" sources_used: {pv.get('sources_used', [])}")
bug2_pass = pv_status in ("CRITICAL_RED_FLAG", "WARNING")
print(f" → Bug 2 detection: {'✅ PASS' if bug2_pass else '❌ FAIL (status not flagging)'}")
# ¿El red flag se inyecto en los prompts? Verificamos por el efecto:
# ¿los agentes lo mencionan en sus outputs?
flag_mentions = {
"DealAnalyzer": "red flag" in deal_an.lower() or "precio anomalo" in deal_an.lower()
or "anomalo" in deal_an.lower() or "investigacion" in deal_an.lower()
or "investigación" in deal_an.lower() or "due diligence" in deal_an.lower(),
"FloridaResearcher": "red flag" in research.lower() or "anomalo" in research.lower()
or "discrepancia" in research.lower(),
"LenderMatcher": "red flag" in lender.lower() or "anomalo" in lender.lower()
or "validar" in lender.lower(),
"Coordinator": "red flag" in coord.lower() or "anomalo" in coord.lower()
or "alerta" in coord.lower(),
"ValueEstimator": "red flag" in value_est.lower() or "anomalo" in value_est.lower()
or "discrepancia" in value_est.lower(),
"OfferStrategist": "red flag" in offer_str.lower() or "anomalo" in offer_str.lower(),
"ContextualGlossaryAgent": ("alerta" in briefing.lower() or "🚨" in briefing
or "precio anomalo" in briefing.lower()
or "ANOMALO" in briefing),
}
print(" Mencion del red flag en outputs:")
for agent, mentioned in flag_mentions.items():
print(f" {agent}: {'' if mentioned else '⚠️'}")
print()
print("" * 70)
print("BUG 3 — Anomaly Detection")
print("" * 70)
print(f" has_anomalies: {anomalies.get('has_anomalies')}")
print(f" is_critical: {anomalies.get('is_critical')}")
print(f" count: {anomalies.get('anomaly_count')} "
f"(HIGH={anomalies.get('high_severity_count')}, MEDIUM={anomalies.get('medium_severity_count')})")
if anomalies.get("flagged_metrics"):
print(" flagged_metrics:")
for f in anomalies["flagged_metrics"]:
print(f" - {f['scenario']} / {f['metric']} = {f['value']} ({f['severity']})")
bug3_python_pass = anomalies.get("has_anomalies", False)
print(f" → Bug 3 Python detection: {'✅ PASS' if bug3_python_pass else '❌ FAIL'}")
# ¿DealAnalyzer incluyo la seccion obligatoria "Validacion de Inputs"?
has_validation_section = (
"validacion de inputs" in deal_an.lower()
or "validación de inputs" in deal_an.lower()
or "validar inputs" in deal_an.lower()
)
print(f" DealAnalyzer incluye '## ⚠️ Validacion de Inputs Requerida': "
f"{'✅ PASS' if has_validation_section else '❌ FAIL — modelo se la salteo'}")
print()
print("" * 70)
print("BUG 1 — Exhaustividad (mínimo 400 palabras por agente técnico)")
print("" * 70)
word_counts = {
"DealAnalyzer": count_words(deal_an),
"FloridaResearcher": count_words(research),
"LenderMatcher": count_words(lender),
"Coordinator": count_words(coord),
"ValueEstimator": count_words(value_est),
"OfferStrategist": count_words(offer_str),
}
bug1_pass = True
for agent, wc in word_counts.items():
symbol = "" if wc >= 400 else "⚠️"
print(f" {agent}: {wc} palabras {symbol}")
if wc < 400:
bug1_pass = False
# ContextualGlossaryAgent SI puede ser mas corto (es el briefing)
print(f" ContextualGlossaryAgent (briefing — sin minimo): {count_words(briefing)} palabras")
print(f" → Bug 1 exhaustividad: "
f"{'✅ PASS' if bug1_pass else '⚠️ PARTIAL (algunos agentes <400)'}")
print()
print("=" * 70)
print("RESUMEN GLOBAL")
print("=" * 70)
print(f" Bug 1 (exhaustividad): {'✅ PASS' if bug1_pass else '⚠️ PARTIAL'}")
print(f" Bug 2 (price red flag): {'✅ PASS' if bug2_pass else '❌ FAIL'}")
print(f" Bug 3 (anomalias): {'✅ PASS' if bug3_python_pass else '❌ FAIL'}")
print(f" Bug 3 (LLM secciona): {'✅ PASS' if has_validation_section else '❌ FAIL'}")
print(f" Total: {elapsed:.0f}s")
# Excerpt de cada agente para ojo humano
print()
print("=" * 70)
print("EXCERPTS (primeras 500 chars de cada agente)")
print("=" * 70)
for name, text in [
("DealAnalyzer", deal_an),
("Coordinator", coord),
("ContextualGlossaryAgent", briefing),
]:
print(f"\n─── {name} ───")
print(text[:500] + ("..." if len(text) > 500 else ""))
return 0
if __name__ == "__main__":
sys.exit(main())
+149
View File
@@ -0,0 +1,149 @@
"""Test e2e del LAND-simplified pipeline con escenario Vero Beach $7K.
Verifica:
1. detect_property_type_anomalies dispara should_bypass=True
2. analyze_deal route a _analyze_land_simplified
3. NO se ejecutan DealAnalyzer/LenderMatcher/ValueEstimator/OfferStrategist
4. NO se computa Cap Rate / DSCR (computed_scenarios queda {})
5. Briefing arranca con "🚨 ALERTA CRITICA: PROPIEDAD TIPO TERRENO DETECTADA"
6. Pipeline mode = land_simplified
"""
from __future__ import annotations
import io, sys, time
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from orchestrator import DealInputs, BuyerProfile, analyze_deal # noqa: E402
def main() -> int:
print("=" * 70)
print("LAND bugfix test — Vero Beach $7K (user mistakenly declared SFR)")
print("=" * 70)
deal = DealInputs(
address="5800 32nd Ave, Vero Beach, FL 32966",
price=7_000,
rent=0,
property_tax=200,
insurance=0,
hoa=0,
sqft=0,
beds=0,
baths=0,
year_built=0,
arv=0,
property_type="sfr", # USER MISTAKENLY DECLARED SFR
listing_description="0.23 acre buildable lot, ready for construction. Cleared and graded.",
)
profile = BuyerProfile(
profile_class="C",
fico=720,
capital_available=50_000,
)
print(f"DEAL: {deal.address}")
print(f" price=${deal.price:,} | property_type DECLARED='{deal.property_type}'")
print(f" year_built={deal.year_built}, sqft={deal.sqft}, beds/baths={deal.beds}/{deal.baths}")
print(f" rent={deal.rent}, arv={deal.arv}")
print(f" description: {deal.listing_description}")
print()
def log(msg):
print(f" [{time.strftime('%H:%M:%S')}] {msg}")
t0 = time.perf_counter()
result = analyze_deal(deal, profile, photo_bytes=None, status_cb=log)
elapsed = time.perf_counter() - t0
print()
print("=" * 70)
print(f"PIPELINE COMPLETED in {elapsed:.0f}s")
print("=" * 70)
# ─── Validaciones ──────────────────────────────────────────────────────
ptw = result.property_type_warning or {}
mode = result.pipeline_mode
print()
print("VALIDATIONS:")
print(f" pipeline_mode: {mode}")
assert mode == "land_simplified", f"FAIL: expected land_simplified, got {mode}"
print(" ✅ pipeline_mode == 'land_simplified'")
print(f" warnings detectados: {len(ptw.get('warnings', []))}")
assert len(ptw.get("warnings", [])) >= 4, f"FAIL: expected >=4 warnings"
print(" ✅ >= 4 warnings dispararon detection")
print(f" should_bypass_sfr_pipeline: {ptw.get('should_bypass_sfr_pipeline')}")
assert ptw.get("should_bypass_sfr_pipeline") is True
print(" ✅ should_bypass=True")
# SFR agents should be SKIPPED
da_out = (result.deal_analysis or {}).get("output", "")
le_out = (result.lender or {}).get("output", "")
ve_out = (result.value_estimate or {}).get("output", "")
os_out = (result.offer_strategy or {}).get("output", "")
print(f" DealAnalyzer output (should be SKIPPED): {da_out[:80]!r}")
print(f" LenderMatcher output (should be SKIPPED): {le_out[:80]!r}")
print(f" ValueEstimator output (should be SKIPPED): {ve_out[:80]!r}")
print(f" OfferStrategist output (should be SKIPPED): {os_out[:80]!r}")
assert "SKIPPED" in da_out and "land" in da_out.lower()
assert "SKIPPED" in le_out
assert "SKIPPED" in ve_out
assert "SKIPPED" in os_out
print(" ✅ Los 4 agentes SFR-specific NO se ejecutaron (SKIPPED)")
# computed_scenarios should be empty (no DSCR/Cap Rate)
cs = result.computed_scenarios or {}
print(f" computed_scenarios (should be empty for LAND): {len(cs)} keys")
assert cs == {}, f"FAIL: computed_scenarios should be empty for LAND, got: {cs}"
print(" ✅ NO se computó finance_calculator (no hay Cap Rate / DSCR inflado)")
# Briefing should start with land warning
briefing_out = (result.executive_briefing or {}).get("output", "")
print(f"\n Briefing first 300 chars:")
print(f" {briefing_out[:300]}")
briefing_lower = briefing_out.lower()
has_alert = any(kw in briefing_lower for kw in [
"alerta", "terreno", "land", "propiedad tipo terreno",
])
assert has_alert, "FAIL: briefing missing LAND alert"
print()
print(" ✅ Briefing destaca alerta LAND")
# Coordinator output should NOT have DSCR/Cap Rate inflados
coord_out = (result.final or {}).get("output", "")
coord_lower = coord_out.lower()
has_dscr = "dscr" in coord_lower
has_caprate = "cap rate" in coord_lower
has_coc = "coc" in coord_lower or "cash on cash" in coord_lower
print(f" Coordinator mentions DSCR: {has_dscr}")
print(f" Coordinator mentions Cap Rate: {has_caprate}")
print(f" Coordinator mentions CoC: {has_coc}")
# Es OK que el LLM las mencione para EXPLICAR que no aplican, pero NO debe presentarlas
# como metricas calculadas. Validamos que mencione "no aplica" o "land" si las menciona.
if has_dscr or has_caprate or has_coc:
explains_inapplicable = any(kw in coord_lower for kw in [
"no aplica", "no apply", "land", "terreno", "no se calcul",
])
if explains_inapplicable:
print(" ✅ Coordinator menciona metricas SFR pero CLARIFICA que no aplican")
else:
print(" ⚠️ Coordinator menciona DSCR/Cap Rate sin clarificar — review")
print()
print("=" * 70)
print("✅ ALL VALIDATIONS PASSED — LAND bug FIXED")
print("=" * 70)
# Save the result for inspection
print(f"\nFull result saved by orchestrator. Check analyses/*_LAND.json")
return 0
if __name__ == "__main__":
sys.exit(main())
+128
View File
@@ -0,0 +1,128 @@
"""Test Miami-Dade parser filters REDEEMED/CANCELED/SOLD cases."""
from __future__ import annotations
import io, sys, glob
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from scrapers.miami_dade_clerk import _parse_cases_from_html, _is_status_dead
def test_status_classifier():
print("=" * 60)
print("UNIT: _is_status_dead()")
print("=" * 60)
dead = ["Redeemed", "REDEEMED", "redeemed", "Canceled", "Cancelled",
"Sold", "Closed", "Title Transferred", "Withdrawn", "Dismissed"]
live = ["", "Pending", "Scheduled", "Active", "Auction", "Postponed", None]
failures = 0
for s in dead:
r = _is_status_dead(s)
status = "" if r else ""
print(f" {status} _is_status_dead({s!r}) = {r}")
if not r:
failures += 1
for s in live:
r = _is_status_dead(s)
status = "" if not r else ""
print(f" {status} _is_status_dead({s!r}) = {r}")
if r:
failures += 1
return failures
def test_parse_cached():
print()
print("=" * 60)
print("PARSE: cached HTML files (real Miami-Dade data)")
print("=" * 60)
files = sorted(glob.glob(str(ROOT / ".cache/scrapers/miami_dade_clerk/*.html")))
print(f"Found {len(files)} cached HTML files")
total_cases = 0
total_filtered = 0
file_stats = []
for f in files:
html = Path(f).read_text(encoding="utf-8")
if len(html) < 20_000:
continue # skip "no results" pages
logs: list[str] = []
cases = _parse_cases_from_html(html, log_fn=lambda m: logs.append(m))
# Look for "filtered N dead" log
filtered_n = 0
for log in logs:
import re
m = re.search(r"filtered (\d+) dead", log)
if m:
filtered_n = int(m.group(1))
break
fname = Path(f).name[:30]
total_cases += len(cases)
total_filtered += filtered_n
file_stats.append((fname, len(cases), filtered_n, logs))
print()
print(f"{'File':<35} {'live':>5} {'filtered_dead':>14}")
print("-" * 60)
for fname, n_live, n_filtered, _ in file_stats:
print(f"{fname:<35} {n_live:>5} {n_filtered:>14}")
print("-" * 60)
print(f"{'TOTAL':<35} {total_cases:>5} {total_filtered:>14}")
print()
print(f"Total live cases parsed: {total_cases}")
print(f"Total dead cases filtered out: {total_filtered}")
# Inspect statuses of LIVE cases — make sure no Redeemed/etc leaked through
print()
print("=== STATUS audit on live cases (sample 10) ===")
sample_count = 0
for fname, _, _, _ in file_stats:
if sample_count >= 10:
break
html = (ROOT / f".cache/scrapers/miami_dade_clerk/{fname}").read_text(encoding="utf-8") if (ROOT / f".cache/scrapers/miami_dade_clerk/{fname}").exists() else None
# Direct: re-parse first non-empty file and show statuses
for f in files:
html = Path(f).read_text(encoding="utf-8")
if len(html) < 20_000:
continue
cases = _parse_cases_from_html(html)
if not cases:
continue
print(f" Statuses in {Path(f).name[:30]}:")
statuses_seen = set()
for c in cases[:50]:
statuses_seen.add(c.get("auction_status") or "(empty)")
print(f" unique statuses on live cases: {statuses_seen}")
# Verify NO dead status leaked through
dead_leak = [c for c in cases if _is_status_dead(c.get("auction_status"))]
if dead_leak:
print(f"{len(dead_leak)} DEAD status leaked through filter!")
return 1
else:
print(f" ✅ All {len(cases)} live cases have non-dead status")
break
return 0
def main() -> int:
rc = test_status_classifier()
rc += test_parse_cached()
print()
print("=" * 60)
if rc == 0:
print("✅ ALL TESTS PASSED — REDEEMED/CANCELED filtering works")
else:
print(f"{rc} failure(s)")
return rc
if __name__ == "__main__":
sys.exit(main())
+141
View File
@@ -0,0 +1,141 @@
"""Verify price_validator detection logic survives Firecrawl SDK API rename.
Tests:
1. Heuristic path (no Firecrawl needed): suspicious_low_listing detection
2. With existing comps (no Firecrawl needed): CRITICAL_RED_FLAG detection
3. With tax_assessed (no Firecrawl needed): WARNING / NORMAL detection
4. Confirm: module imports cleanly (no syntax errors from rename)
"""
from __future__ import annotations
import io, sys
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
import ast
# Parse-test the fixed files
for f in ["data_fetchers/price_validator.py", "data_fetchers/property_value.py"]:
ast.parse(open(f, encoding="utf-8").read())
print(f"✅ Both files parse OK")
import data_fetchers # load .env
from data_fetchers.price_validator import validate_price
def main() -> int:
print()
print("=" * 70)
print("PRICE VALIDATOR — post Firecrawl SDK API rename")
print("=" * 70)
failures = 0
# ─── Test 1: Heuristic path — Jacksonville $70K listing ────────────────
print("\n--- Test 1: Jacksonville $70K (heuristic path, no Firecrawl) ---")
r = validate_price(
address="3245 N Pearl St, Jacksonville, FL 32209",
listing_price=70_000,
tax_assessed_value=None,
existing_comps_estimate=None,
existing_comps_confidence=None,
existing_comps_sources=None,
neighborhood_class=None,
use_firecrawl=False, # explicit: don't call Firecrawl
)
print(f" status: {r['status']}")
print(f" suspicious_low_listing: {r.get('suspicious_low_listing')}")
print(f" possible_reasons count: {len(r.get('possible_reasons') or [])}")
if r.get("possible_reasons"):
print(f" first reason: {r['possible_reasons'][0][:90]}")
if r["status"] == "UNKNOWN" and r.get("suspicious_low_listing"):
print(f" ✅ Detected suspicious low listing (FORECLOSURE hypothesis)")
else:
print(f" ❌ Expected UNKNOWN + suspicious_low_listing=True")
failures += 1
# ─── Test 2: With existing high-confidence comps ($70K vs $280K mid) ───
print("\n--- Test 2: Jacksonville $70K vs $280K comps mid (CRITICAL_RED_FLAG expected) ---")
r = validate_price(
address="3245 N Pearl St, Jacksonville, FL 32209",
listing_price=70_000,
existing_comps_estimate=280_000,
existing_comps_confidence="medium", # not 'low' — should be accepted
existing_comps_sources=["Comps Firecrawl (Jacksonville)"],
neighborhood_class="C",
use_firecrawl=False,
)
print(f" status: {r['status']}")
print(f" signed_max_discrepancy_pct: {r.get('signed_max_discrepancy_pct')}")
print(f" possible_reasons: {len(r.get('possible_reasons') or [])}")
if r["status"] == "CRITICAL_RED_FLAG" and r.get("signed_max_discrepancy_pct", 0) < 0:
print(f" ✅ CRITICAL_RED_FLAG fired (listing {r['signed_max_discrepancy_pct']}% below market)")
else:
print(f" ❌ Expected CRITICAL_RED_FLAG with negative discrepancy")
failures += 1
# ─── Test 3: Low-confidence comps should be REJECTED ───────────────────
print("\n--- Test 3: low-confidence comps (heuristic-only) should be rejected ---")
r = validate_price(
address="3245 N Pearl St, Jacksonville, FL 32209",
listing_price=70_000,
existing_comps_estimate=37_000,
existing_comps_confidence="low", # → should reject
existing_comps_sources=["Deductions por edad (heuristica FL)"],
neighborhood_class="D",
use_firecrawl=False,
)
print(f" status: {r['status']}")
print(f" rejected_sources: {len(r.get('rejected_sources') or [])}")
if r["status"] == "UNKNOWN" and r.get("rejected_sources"):
print(f" ✅ Low-confidence comps correctly rejected, fallback to UNKNOWN+suspicious")
else:
print(f" ❌ Expected UNKNOWN with rejected_sources")
failures += 1
# ─── Test 4: NORMAL case (listing in line with comps) ───────────────────
print("\n--- Test 4: $275K listing with $280K comps (NORMAL expected) ---")
r = validate_price(
address="100 Main St, Tampa, FL 33602",
listing_price=275_000,
existing_comps_estimate=280_000,
existing_comps_confidence="medium",
existing_comps_sources=["Comps Firecrawl"],
neighborhood_class="B",
use_firecrawl=False,
)
print(f" status: {r['status']}")
print(f" signed_max_discrepancy_pct: {r.get('signed_max_discrepancy_pct')}")
if r["status"] == "NORMAL":
print(f" ✅ NORMAL fired (within ±10%)")
else:
print(f" ❌ Expected NORMAL")
failures += 1
# ─── Test 5: Confirm Firecrawl-disabled path still returns errors gracefully ─
print("\n--- Test 5: ENABLE_FIRECRAWL_PRICE_CHECK=false → should NOT crash ---")
import os
os.environ.pop("ENABLE_FIRECRAWL_PRICE_CHECK", None)
from data_fetchers.price_validator import fetch_zillow_zestimate, fetch_redfin_estimate
z, errz = fetch_zillow_zestimate("123 Test St, Miami, FL")
print(f" Zillow: result={z}, errors={errz[0][:80] if errz else None}")
rfn, errrfn = fetch_redfin_estimate("123 Test St, Miami, FL")
print(f" Redfin: result={rfn}, errors={errrfn[0][:80] if errrfn else None}")
if z is None and rfn is None:
print(f" ✅ Both return None gracefully when flag is off (no crash)")
else:
print(f" ❌ Expected None for both")
failures += 1
print()
print("=" * 70)
if failures == 0:
print("✅ ALL 5 TESTS PASSED — detection logic intact after API rename")
else:
print(f"{failures} test(s) failed")
return failures
if __name__ == "__main__":
sys.exit(main())
+59
View File
@@ -0,0 +1,59 @@
"""HUD Homestore scraper smoke test (FL state only)."""
from __future__ import annotations
import io, sys, time
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
def main() -> int:
print("=" * 70)
print("HUD Homestore scraper smoke test (FL)")
print("=" * 70)
from scrapers.hud_homestore import scrape_hud_homestore
def log(m: str) -> None:
print(f" {m}")
t0 = time.perf_counter()
deals = scrape_hud_homestore(states=["FL"], status_cb=log)
elapsed = time.perf_counter() - t0
print(f"\nResult: {len(deals)} deals in {elapsed:.1f}s")
if not deals:
print("❌ NO DEALS scraped. Aborting.")
return 1
print()
print("--- SAMPLE DEALS (first 5) ---")
for i, d in enumerate(deals[:5], 1):
print(f"\n [{i}] {d.get('case_number')}")
for k in ("deal_type", "auction_date", "address", "city", "county",
"state", "zip", "listing_price", "beds", "baths"):
print(f" {k}: {d.get(k)}")
print(f" desc: {(d.get('listing_description') or '')[:120]}")
# Validate required fields
print()
print("--- VALIDATION ---")
required = ["source", "deal_type", "case_number", "listing_price", "address"]
failures = 0
for i, d in enumerate(deals):
for f in required:
if not d.get(f):
failures += 1
if failures <= 5:
print(f" ⚠️ deal {i+1} ({d.get('case_number')}): missing {f}")
if failures == 0:
print(f" ✅ All {len(deals)} deals have required fields")
else:
print(f" ⚠️ {failures} field-missing instances")
return 0
if __name__ == "__main__":
sys.exit(main())
+85
View File
@@ -0,0 +1,85 @@
"""Test scraper Miami-Dade Clerk con 3 dias para verificar parsing antes del full run."""
from __future__ import annotations
import io, sys, time
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
def main() -> int:
print("=" * 70)
print("Miami-Dade Clerk scraper smoke test")
print("=" * 70)
def log(m: str) -> None:
print(f" {m}")
# ───────────────────────────────────────────────────────────────────────
# PASO 1: scrape sin persistir, solo para ver que data sale
# ───────────────────────────────────────────────────────────────────────
from scrapers.miami_dade_clerk import scrape_miami_dade_auctions
print("\n--- PASO 1: scrape (no DB), 3 dias ahead ---")
t0 = time.perf_counter()
deals = scrape_miami_dade_auctions(days_ahead=3, status_cb=log)
elapsed = time.perf_counter() - t0
print(f"\nResult: {len(deals)} deals in {elapsed:.1f}s")
if not deals:
print("❌ NO DEALS scraped. Aborting test.")
return 1
# Print first 5 deals con detalle
print()
print("--- SAMPLE DEALS (first 5) ---")
for i, d in enumerate(deals[:5], 1):
print(f"\n Deal [{i}]")
for k in ["deal_type", "case_number", "auction_date", "starting_bid",
"estimated_arv", "address", "city", "state", "zip", "county",
"listing_price"]:
v = d.get(k)
print(f" {k}: {v}")
desc = d.get("listing_description", "")[:120]
print(f" description: {desc}")
# ───────────────────────────────────────────────────────────────────────
# PASO 2: Validacion estructural
# ───────────────────────────────────────────────────────────────────────
print()
print("--- PASO 2: estructura ---")
required_fields = ["source", "deal_type", "case_number", "auction_date",
"county", "listing_price"]
failures = 0
for i, d in enumerate(deals):
for f in required_fields:
if d.get(f) is None or d.get(f) == "":
if f == "listing_price":
# OK si tiene starting_bid pero no listing_price
if d.get("starting_bid"):
continue
print(f" ⚠️ deal {i+1} ({d.get('case_number')}): missing {f}")
failures += 1
if failures == 0:
print(f" ✅ All {len(deals)} deals have required fields")
else:
print(f" ⚠️ {failures} field-missing instances across deals")
# Field types
types_ok = True
for d in deals[:5]:
if d.get("starting_bid") and not isinstance(d["starting_bid"], (int, float)):
print(f" ⚠️ starting_bid not numeric in {d.get('case_number')}: {d['starting_bid']!r}")
types_ok = False
if d.get("auction_date") and not (isinstance(d["auction_date"], str) and len(d["auction_date"]) == 10):
print(f" ⚠️ auction_date not ISO YYYY-MM-DD in {d.get('case_number')}")
types_ok = False
if types_ok:
print(" ✅ Field types OK (numeric pricing, ISO dates)")
return 0
if __name__ == "__main__":
sys.exit(main())
+84
View File
@@ -0,0 +1,84 @@
"""E2E test of search_engine.py against real deals.db (125 deals from B1+B3)."""
from __future__ import annotations
import io, sys
from pathlib import Path
from collections import Counter
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from search_engine import search_existing_only, preflight_check
from scrapers.registry import list_sources, get_sources_for_county
def main() -> int:
print("=" * 70)
print("E2E TEST: Search Engine against real deals.db (125 deals)")
print("=" * 70)
# Sim 1
print("\n--- Sim 1: Miami-Dade, both sources, no filters ---")
r = search_existing_only(
counties=["Miami-Dade"], source_ids=["miami_dade_clerk", "hud_homestore"]
)
print(f" {len(r)} deals matched")
for d in r[:3]:
addr = (d.get("address") or "?")[:50]
price = d.get("listing_price") or 0
print(f" {d.get('case_number')}: {d.get('deal_type'):<12} "
f"${price:>10,.0f} score={d.get('classification_score')} {addr}")
# Sim 2: HUD nationwide
print("\n--- Sim 2: HUD only, no county filter (all FL counties) ---")
r = search_existing_only(source_ids=["hud_homestore"])
print(f" {len(r)} HUD deals total")
counties = Counter(d.get("county") for d in r)
print(f" Top 10 counties:")
for c, n in counties.most_common(10):
print(f" {c}: {n}")
# Sim 3: Price + beds filter
print("\n--- Sim 3: $300K-$400K, beds>=3 ---")
r = search_existing_only(
filters={"min_price": 300000, "max_price": 400000, "beds_min": 3}
)
print(f" {len(r)} deals in range")
for d in r[:5]:
addr = (d.get("address") or "?")[:50]
print(f" {d.get('source'):<22} | {d.get('deal_type'):<12} | "
f"${d.get('listing_price') or 0:>8,.0f} | {d.get('beds')}bd | {addr}")
# Sim 4: only red_flag classification
print("\n--- Sim 4: classification=red_flag ---")
r = search_existing_only(filters={"classifications": ["red_flag"]})
print(f" {len(r)} red_flag deals")
# Sim 5: preflight check
print("\n--- Sim 5: preflight_check(both free sources) ---")
pf = preflight_check(["miami_dade_clerk", "hud_homestore"])
print(f" total_credits: {pf['total_credits_estimated']}")
print(f" ok_to_run: {pf['ok_to_run']}")
print(f" budget used: {pf['budget_snapshot']['credits_used']}/{pf['budget_snapshot']['credits_budget']}")
print(f" warnings: {pf['warnings']}")
# Sim 6: get_sources_for_county
print("\n--- Sim 6: get_sources_for_county('Miami-Dade') ---")
sources = get_sources_for_county("Miami-Dade")
for s in sources:
print(f" {s['id']}: free={s['free']}, deal_types={s['deal_types_produced']}")
print("\n--- Sim 7: get_sources_for_county('Broward') (no clerk yet) ---")
sources = get_sources_for_county("Broward")
for s in sources:
print(f" {s['id']}: free={s['free']}, deal_types={s['deal_types_produced']}")
print()
print("=" * 70)
print("✅ Search engine works against real deals.db")
print("=" * 70)
return 0
if __name__ == "__main__":
sys.exit(main())
+174
View File
@@ -0,0 +1,174 @@
"""Test focalizado de Wave 2 (ValueEstimator + OfferStrategist) con deal real.
DEAL: 446 VERMONT Avenue, Green Cove Springs, FL 32043 $219K MLS retail
- Clay County (NO court_records scraper soft-fail NOT_IMPLEMENTED esperado)
- 2005 build, 3/2, 1461 sqft, tax $3348, insurance $876, rent $1789, no HOA
- ARV $240K (estimacion razonable para zona Class B/C 2005 build)
Criterios validados (8 puntos del spec del usuario):
1. ValueEstimator: comparison listing vs comps
2. ValueEstimator: rango low/mid/high
3. OfferStrategist: Strike/Stretch/Walk-Away
4. OfferStrategist: angulo de ataque (psychological points)
5. OfferStrategist: contra-ofertas anticipadas
6. OfferStrategist: si auction MAB, si MLS Strike (este deal es MLS)
7. Briefing: seccion "Valor Real vs Listing"
8. Briefing: seccion "Oferta Recomendada"
"""
from __future__ import annotations
import io
import sys
import time
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from orchestrator import DealInputs, BuyerProfile, analyze_deal # noqa: E402
def status_cb(msg: str) -> None:
print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True)
def main() -> int:
print("=" * 70)
print("Wave 2 Validation — 446 Vermont Ave, Green Cove Springs FL $219K MLS")
print("=" * 70)
deal = DealInputs(
address="446 VERMONT Avenue, Green Cove Springs, FL 32043",
price=219_000,
rent=1_789,
property_tax=3_348,
insurance=876,
hoa=0,
sqft=1_461,
beds=3,
baths=2.0,
year_built=2005,
arv=240_000, # estimacion razonable; sin rehab significativo para 2005 build
rehab_override=8_000, # cosmetics solo, no major rehab
deal_type="mls", # NORMAL MLS retail, no auction
)
profile = BuyerProfile(
profile_class="C",
fico=720,
capital_available=65_000,
nationality="Argentina",
)
print(f"DEAL: {deal.address}")
print(f" price=${deal.price:,} rent=${deal.rent:,}/mo arv=${deal.arv:,}")
print(f" beds={deal.beds}/baths={deal.baths} sqft={deal.sqft} year={deal.year_built}")
print(f" tax=${deal.property_tax:,}/y insurance=${deal.insurance:,}/y hoa=${deal.hoa}/mo")
print(f" deal_type={deal.deal_type}, rehab_override=${deal.rehab_override:,}")
print()
t0 = time.perf_counter()
result = analyze_deal(deal, profile, photo_bytes=None, status_cb=status_cb)
elapsed = time.perf_counter() - t0
print()
print("=" * 70)
print(f"COMPLETADO en {elapsed:.0f}s ({elapsed/60:.1f} min)")
print("=" * 70)
# Get outputs
ve_out = (result.value_estimate or {}).get("output", "") or ""
os_out = (result.offer_strategy or {}).get("output", "") or ""
briefing_out = (result.executive_briefing or {}).get("output", "") or ""
# ===========================================================
# 8 CRITERIOS DE VALIDACION (spec del usuario)
# ===========================================================
print()
print("" * 70)
print("VALIDACION 8 CRITERIOS")
print("" * 70)
# 1. ValueEstimator: comparison listing vs comps
c1 = any(kw in ve_out.lower() for kw in [
"listing", "precio listado", "$219", "comp", "comparable"
])
print(f" 1. ValueEstimator: comparison listing vs comps: {'' if c1 else ''}")
# 2. ValueEstimator: rango low/mid/high
has_low = "low" in ve_out.lower() or "bajo:" in ve_out.lower()
has_mid = "mid" in ve_out.lower() or "medio:" in ve_out.lower()
has_high = "high" in ve_out.lower() or "alto:" in ve_out.lower()
c2 = has_low and has_mid and has_high
print(f" 2. ValueEstimator: rango low/mid/high: {'' if c2 else '⚠️ (parcial)'}")
# 3. OfferStrategist: Strike/Stretch/Walk-Away
has_strike = "strike" in os_out.lower()
has_stretch = "stretch" in os_out.lower()
has_walkaway = "walk-away" in os_out.lower() or "walk away" in os_out.lower()
c3 = has_strike and has_stretch and has_walkaway
print(f" 3. OfferStrategist: Strike/Stretch/Walk-Away: {'' if c3 else '⚠️ (parcial)'}")
# 4. OfferStrategist: angulo de ataque
c4 = any(kw in os_out.lower() for kw in [
"angulo de ataque", "ángulo de ataque", "angulo", "psicologic", "psicológ",
"presentacion", "argumento",
])
print(f" 4. OfferStrategist: angulo de ataque (psicologico): {'' if c4 else ''}")
# 5. OfferStrategist: contra-ofertas anticipadas
c5 = any(kw in os_out.lower() for kw in [
"contra-oferta", "contra oferta", "counter-offer", "counter offer",
"contraoferta", "anticipad",
])
print(f" 5. OfferStrategist: contra-ofertas anticipadas: {'' if c5 else ''}")
# 6. MLS deal → debe usar Strike NO MAB (este test es MLS)
has_mab_only = "mab" in os_out.lower() and not has_strike
c6 = has_strike and not has_mab_only
print(f" 6. MLS deal usa Strike (no MAB exclusivo): {'' if c6 else ''}")
# 7. Briefing: seccion "Valor Real" / "Valor vs Listing"
c7 = any(kw in briefing_out.lower() for kw in [
"valor real", "valor estimado", "estimación de valor", "estimacion de valor",
"valor vs listing", "valor del inmueble",
])
print(f" 7. Briefing incluye seccion Valor Real: {'' if c7 else ''}")
# 8. Briefing: seccion "Oferta Recomendada"
c8 = any(kw in briefing_out.lower() for kw in [
"oferta recomendada", "recomendación de oferta", "recomendacion de oferta",
"estrategia de oferta", "oferta sugerida", "strike", "walk-away",
])
print(f" 8. Briefing incluye seccion Oferta Recomendada: {'' if c8 else ''}")
print()
score = sum([c1, c2, c3, c4, c5, c6, c7, c8])
print(f"SCORE: {score}/8")
# ===========================================================
# Excerpts para revisión humana
# ===========================================================
print()
print("=" * 70)
print("EXCERPTS — ValueEstimator (primeras 800 chars)")
print("=" * 70)
print(ve_out[:800])
print()
print("=" * 70)
print("EXCERPTS — OfferStrategist (primeras 1000 chars)")
print("=" * 70)
print(os_out[:1000])
print()
print("=" * 70)
print("EXCERPTS — ContextualGlossaryAgent (primeras 1200 chars)")
print("=" * 70)
print(briefing_out[:1200])
return 0 if score >= 7 else 1
if __name__ == "__main__":
sys.exit(main())
+67
View File
@@ -0,0 +1,67 @@
"""E2E test of Zillow scraper using CACHED markdown (0 Firecrawl credits)."""
from __future__ import annotations
import io, sys
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
import data_fetchers # noqa: F401 — load .env
import scrapers.zillow as zillow_mod
import scrapers._cache as cache_mod
from scrapers.zillow import run_scraper_to_db
from deals_db import list_deals, init_db
def main() -> int:
init_db()
# Pre-populate cache with the test markdown from prior exploration
test_url = zillow_mod._build_zillow_url("Miami-Dade", "FL", 1)
print(f"Cache target URL: {test_url}")
md_file = ROOT / "scripts" / "_zillow_miami_md.txt"
if not md_file.exists():
print(f"❌ Test markdown not found at {md_file}")
return 1
md = md_file.read_text(encoding="utf-8")
cache_mod.save_cache(
"zillow", test_url, md,
status_code=200, ttl_seconds=cache_mod.DEFAULT_TTL_SECONDS_HOURLY,
)
print(f"Cached: {len(md):,} chars")
# Run pipeline (cache hit, 0 credits, auto_classify=True to test full flow)
print()
print("Running zillow.run_scraper_to_db (auto_classify=True — ~5s/deal LLM)...")
result = run_scraper_to_db(
counties=["Miami-Dade"], state="FL", pages_per_county=1,
auto_classify=True,
status_cb=lambda m: print(f" {m}"),
)
print()
print("Result:")
for k, v in result.items():
print(f" {k}: {v}")
# Verify in DB
print()
print("=== zillow source in deals.db ===")
zd = list_deals(source="zillow", limit=20)
print(f"Total zillow deals: {len(zd)}")
for d in zd[:5]:
addr = (d.get("address") or "?")[:55]
price = d.get("listing_price") or 0
beds = d.get("beds")
baths = d.get("baths")
sqft = d.get("sqft")
print(f" zpid {d.get('case_number'):<10} | ${price:>11,.0f} | {beds!s:>2}bd/{baths!s:>3}ba/{sqft!s:>5}sqft | {addr}")
return 0
if __name__ == "__main__":
sys.exit(main())