deep research addressement

This commit is contained in:
2026-06-01 08:40:10 -04:00
parent c159f07322
commit ba73daa66c
205 changed files with 157390 additions and 951 deletions

View File

@@ -28,6 +28,18 @@ import {
type SubWithEffectiveTier,
} from "~/server/lib/tier";
// ---------------------------------------------------------------------------
// Tier limits for property monitoring
// Guard (basic) 1 property
// Fortress (plus) 3 properties
// Family (premium) 5 properties
// ---------------------------------------------------------------------------
const MAX_PROPERTIES: Record<string, number> = {
basic: 1,
plus: 3,
premium: 5,
};
async function getSubscription(userId: string): Promise<SubWithEffectiveTier> {
const [sub] = await db
.select()
@@ -81,6 +93,25 @@ export async function addProperty(
throw new TRPCError({ code: "FORBIDDEN", message: "HomeTitle requires a Plus subscription or active feature trial" });
}
// Enforce tier property count limits
const maxAllowed = MAX_PROPERTIES[sub.effectiveTier] ?? 1;
const [{ count: activeCount }] = await db
.select({ count: count() })
.from(propertyWatchlistItems)
.where(
and(
eq(propertyWatchlistItems.subscriptionId, sub.id),
eq(propertyWatchlistItems.isActive, true),
),
);
if (activeCount >= maxAllowed) {
throw new TRPCError({
code: "TOO_MANY_REQUESTS",
message: `Property limit reached: ${maxAllowed} active properties for ${sub.effectiveTier} tier`,
});
}
const parsed = parseAddress(address);
const coords = await geocodeAddress(address);
@@ -100,17 +131,60 @@ export async function addProperty(
})
.returning();
// Try to fetch real property data from Attom for the initial snapshot
let initialOwnerName = ownerName ?? "Unknown Owner";
let initialDeedDate: string | null = null;
let initialTaxAmount: number | null = null;
let initialLienCount = 0;
let initialPropertyType = "residential";
let initialAddress: Record<string, string> = { full: address, ...parsed };
let initialTaxId: string | null = null;
try {
const apiKey = process.env.ATTOM_API_KEY;
if (apiKey && !ownerName) {
const record = await fetchCountyRecords(
parcelId ?? null,
parsed.state,
parsed.state,
inserted.id,
sub.id,
userId,
);
if (record) {
initialOwnerName = record.ownerName;
initialDeedDate = record.deedDate;
initialTaxAmount = record.taxAmount ?? null;
initialLienCount = record.lienCount;
initialPropertyType = record.propertyType ?? "residential";
initialAddress = record.address;
initialTaxId = record.taxId;
// Update parcel ID from Attom lookup
if (record.taxId && !parcelId) {
db.update(propertyWatchlistItems)
.set({ ownerName: record.ownerName, parcelId: record.taxId, updatedAt: new Date() })
.where(eq(propertyWatchlistItems.id, inserted.id))
.catch(() => {});
}
}
}
} catch (err) {
// Initial fetch is best-effort; log but proceed with defaults
console.warn("[hometitle] Failed to fetch initial Attom data for property:", err);
}
await db.insert(propertySnapshots).values({
propertyWatchlistItemId: inserted.id,
subscriptionId: sub.id,
capturedAt: new Date(),
ownerName: ownerName ?? "Unknown",
address: { full: address, ...parsed },
deedDate: null,
taxId: null,
propertyType: "residential",
taxAmount: null,
lienCount: 0,
ownerName: initialOwnerName,
address: initialAddress,
deedDate: initialDeedDate,
taxId: initialTaxId,
propertyType: initialPropertyType,
taxAmount: initialTaxAmount,
lienCount: initialLienCount,
});
return inserted;
@@ -286,16 +360,18 @@ async function checkTierLimits(userId: string): Promise<{ allowed: boolean; reas
const sub = await getSubscription(userId);
const tier = sub.effectiveTier;
// Premium has no scan limit
if (tier === "premium") {
return { allowed: true };
}
const maxScans: Record<string, number> = {
basic: 1,
plus: 4,
plus: 3,
};
const maxScanCount = maxScans[tier] ?? 1;
// Basic = monthly window, Plus = weekly window
const periodStart =
tier === "plus"
? new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
@@ -350,9 +426,19 @@ export async function runScan(userId: string): Promise<{ scanId: string }> {
try {
const lastSnapshot = await getLastSnapshot(item.id);
const county = item.state || "Unknown";
const currentRecord = await fetchCountyRecords(item.parcelId, county, item.state ?? "");
const currentRecord = await fetchCountyRecords(
item.parcelId,
county,
item.state ?? "",
item.id,
sub.id,
userId,
);
if (!currentRecord) continue;
if (!currentRecord) {
console.warn(`[hometitle] No Attom data returned for property ${item.id} — monitoring gap may exist`);
continue;
}
const newData: SnapshotData = {
ownerName: currentRecord.ownerName,