feat: AR-House initial commit
This commit is contained in:
@@ -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>
|
||||
|
||||
The Duval Clerk’s 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 & 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>
|
||||
@@ -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("ctl00$cphBody$bSearch", "", true, "", "Results.aspx", 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 don’t 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 & 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 Appraiser’s Web site and be directed to the Tax Collector’s 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>
|
||||
@@ -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("ctl00$cphBody$bSearch", "", true, "", "Results.aspx", 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 don’t 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 & 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 Appraiser’s Web site and be directed to the Tax Collector’s 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>
|
||||
@@ -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("ctl00$cphBody$lbNewSearch", "", false, "", "Search.aspx", false, true))">New Search</a>
|
||||
</div>
|
||||
|
||||
<div class="refineSearch">
|
||||
<a id="ctl00_cphBody_lbRefineSearch" href="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("ctl00$cphBody$lbRefineSearch", "", false, "", "Search.aspx?Refine=Y", 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 Appraiser’s 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 Appraiser’s Web site and be directed to the Tax Collector’s 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>
|
||||
@@ -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 (2–4 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
@@ -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&zmethod=aboutus">About Us</a>
|
||||
<div class="hmItem"></div>
|
||||
<a tabindex="0" id="FAQ" href="/index.cfm?zaction=home&zmethod=faq">FAQ</a>
|
||||
<div class="hmItem"></div>
|
||||
<a tabindex="0" id="ContactUs" href="/index.cfm?zaction=ContactUS&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 </label><input tabindex="0" id="LogName" class="LogInput inIn" value="">
|
||||
</div>
|
||||
<div class="LogLabel">
|
||||
<label for="LogPass">User Password </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&Zmethod=FORGOTUN" tabindex="0"><span class="LogHelp">Forgot your username?</span> </a><br>
|
||||
<a href="/index.cfm?zaction=HOME&Zmethod=FORGOT" tabindex="0"><span class="LogHelp">Forgot your password?</span></a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="fspace"> </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&ZMETHOD=TRAINING" tabindex="0"><span class="LN_MT">Training</span></a></div>
|
||||
|
||||
<div class="LN_MI"><a href="/index.cfm?ZACTION=HOME&ZMETHOD=FORECLOSE" tabindex="0"><span class="LN_MT">Foreclosure Process</span></a></div>
|
||||
|
||||
<div class="LN_MI"><a href="/index.cfm?ZACTION=HOME&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> </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&zmethod=PREVIEW&AuctionDate=05/12/2026">< < Previous Auction</a></div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="BLHeaderNext BLArrow"><a href="/index.cfm?zaction=AUCTION&zmethod=PREVIEW&AuctionDate=05/14/2026">Next Auction > ></a></div>
|
||||
|
||||
|
||||
<div class="BLHeaderToday BLArrow">
|
||||
<a href="/index.cfm?zaction=AUCTION&zmethod=PREVIEW&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"> </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"> </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&zmethod=aboutUs" class="footer">About Us</a> |</li>
|
||||
<li><a tabindex="0" href="index.cfm?zaction=home&zmethod=siteMap" class="footer">Site Map</a> |</li>
|
||||
<li><a tabindex="0" href="index.cfm?zaction=home&zmethod=privpol" class="footer">Privacy Policy</a> |</li>
|
||||
<li><a tabindex="0" href="index.cfm?zaction=home&zmethod=agreement" class="footer">User Agreement</a> |</li>
|
||||
<li><a tabindex="0" href="index.cfm?zaction=home&zmethod=welcome" class="footer">Bidder Letter</a> |</li>
|
||||
<li><a tabindex="0" href="index.cfm?zaction=ContactUS&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>
|
||||
@@ -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&zmethod=aboutus">About Us</a>
|
||||
<div class="hmItem"></div>
|
||||
<a tabindex="0" id="FAQ" href="/index.cfm?zaction=home&zmethod=faq">FAQ</a>
|
||||
<div class="hmItem"></div>
|
||||
<a tabindex="0" id="ContactUs" href="/index.cfm?zaction=ContactUS&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 </label><input tabindex="0" id="LogName" class="LogInput inIn" value="">
|
||||
</div>
|
||||
<div class="LogLabel">
|
||||
<label for="LogPass">User Password </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&Zmethod=FORGOTUN" tabindex="0"><span class="LogHelp">Forgot your username?</span> </a><br>
|
||||
<a href="/index.cfm?zaction=HOME&Zmethod=FORGOT" tabindex="0"><span class="LogHelp">Forgot your password?</span></a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="fspace"> </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&ZMETHOD=TRAINING" tabindex="0"><span class="LN_MT">Training</span></a></div>
|
||||
|
||||
<div class="LN_MI"><a href="/index.cfm?ZACTION=HOME&ZMETHOD=FORECLOSE" tabindex="0"><span class="LN_MT">Foreclosure Process</span></a></div>
|
||||
|
||||
<div class="LN_MI"><a href="/index.cfm?ZACTION=HOME&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> </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&zmethod=PREVIEW&AuctionDate=05/12/2026">< < Previous Auction</a></div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="BLHeaderNext BLArrow"><a href="/index.cfm?zaction=AUCTION&zmethod=PREVIEW&AuctionDate=05/14/2026">Next Auction > ></a></div>
|
||||
|
||||
|
||||
<div class="BLHeaderToday BLArrow">
|
||||
<a href="/index.cfm?zaction=AUCTION&zmethod=PREVIEW&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"> </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"> </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&zmethod=aboutUs" class="footer">About Us</a> |</li>
|
||||
<li><a tabindex="0" href="index.cfm?zaction=home&zmethod=siteMap" class="footer">Site Map</a> |</li>
|
||||
<li><a tabindex="0" href="index.cfm?zaction=home&zmethod=privpol" class="footer">Privacy Policy</a> |</li>
|
||||
<li><a tabindex="0" href="index.cfm?zaction=home&zmethod=agreement" class="footer">User Agreement</a> |</li>
|
||||
<li><a tabindex="0" href="index.cfm?zaction=home&zmethod=welcome" class="footer">Bidder Letter</a> |</li>
|
||||
<li><a tabindex="0" href="index.cfm?zaction=ContactUS&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>
|
||||
@@ -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
|
||||
|
||||
[](https://www.zillow.com/homedetails/1413-N-Venetian-Way-Miami-Beach-FL-33139/43833721_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/1413-N-Venetian-Way-Miami-Beach-FL-33139/43833721_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/1413-N-Venetian-Way-Miami-Beach-FL-33139/43833721_zpid/)
|
||||
|
||||

|
||||
|
||||
- [$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
|
||||
|
||||
[](https://www.zillow.com/homedetails/1644-NW-8th-Ter-Miami-FL-33125/43825743_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/1644-NW-8th-Ter-Miami-FL-33125/43825743_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/1644-NW-8th-Ter-Miami-FL-33125/43825743_zpid/)
|
||||
|
||||

|
||||
|
||||
- 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
|
||||
|
||||
[](https://www.zillow.com/homedetails/10220-SW-168th-St-Miami-FL-33157/44302315_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/10220-SW-168th-St-Miami-FL-33157/44302315_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/10220-SW-168th-St-Miami-FL-33157/44302315_zpid/)
|
||||
|
||||

|
||||
|
||||
- [$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
|
||||
|
||||
[](https://www.zillow.com/homedetails/6651-SW-148th-Ct-Miami-FL-33193/44259458_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/6651-SW-148th-Ct-Miami-FL-33193/44259458_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/6651-SW-148th-Ct-Miami-FL-33193/44259458_zpid/)
|
||||
|
||||

|
||||
|
||||
- [$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
|
||||
|
||||
[](https://www.zillow.com/homedetails/29371-SW-173rd-Ct-Homestead-FL-33030/156412591_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/29371-SW-173rd-Ct-Homestead-FL-33030/156412591_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/29371-SW-173rd-Ct-Homestead-FL-33030/156412591_zpid/)
|
||||
|
||||

|
||||
|
||||
- [$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
|
||||
|
||||
[](https://www.zillow.com/homedetails/6230-SW-27th-St-Miami-FL-33155/44187290_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/6230-SW-27th-St-Miami-FL-33155/44187290_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/6230-SW-27th-St-Miami-FL-33155/44187290_zpid/)
|
||||
|
||||

|
||||
|
||||
- [$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
|
||||
|
||||
[](https://www.zillow.com/homedetails/3090-SE-7th-Pl-Homestead-FL-33033/44003765_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/3090-SE-7th-Pl-Homestead-FL-33033/44003765_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/3090-SE-7th-Pl-Homestead-FL-33033/44003765_zpid/)
|
||||
|
||||

|
||||
|
||||
- [$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
|
||||
|
||||
[](https://www.zillow.com/homedetails/1886-NW-68th-St-Miami-FL-33147/250761158_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/1886-NW-68th-St-Miami-FL-33147/250761158_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/1886-NW-68th-St-Miami-FL-33147/250761158_zpid/)
|
||||
|
||||

|
||||
|
||||
- [$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
|
||||
|
||||
[](https://www.zillow.com/homedetails/1460-S-Treasure-Dr-North-Bay-Village-FL-33141/44028079_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/1460-S-Treasure-Dr-North-Bay-Village-FL-33141/44028079_zpid/)
|
||||
|
||||
[](https://www.zillow.com/homedetails/1460-S-Treasure-Dr-North-Bay-Village-FL-33141/44028079_zpid/)
|
||||
|
||||

|
||||
|
||||
- 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/)
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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}")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user