@sfranalytics/mcp 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +24 -0
- package/README.md +147 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.js +101 -0
- package/dist/config.js.map +1 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +252 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +213 -0
- package/dist/server.js.map +1 -0
- package/dist/services/httpClient.d.ts +19 -0
- package/dist/services/httpClient.js +73 -0
- package/dist/services/httpClient.js.map +1 -0
- package/dist/services/plr.d.ts +26 -0
- package/dist/services/plr.js +75 -0
- package/dist/services/plr.js.map +1 -0
- package/dist/services/sfr.d.ts +33 -0
- package/dist/services/sfr.js +124 -0
- package/dist/services/sfr.js.map +1 -0
- package/dist/services/snowflake.d.ts +12 -0
- package/dist/services/snowflake.js +71 -0
- package/dist/services/snowflake.js.map +1 -0
- package/dist/tools/dateHelper.d.ts +26 -0
- package/dist/tools/dateHelper.js +86 -0
- package/dist/tools/dateHelper.js.map +1 -0
- package/dist/tools/formatters.d.ts +66 -0
- package/dist/tools/formatters.js +219 -0
- package/dist/tools/formatters.js.map +1 -0
- package/dist/tools/health.d.ts +5 -0
- package/dist/tools/health.js +38 -0
- package/dist/tools/health.js.map +1 -0
- package/dist/tools/nextActions.d.ts +10 -0
- package/dist/tools/nextActions.js +23 -0
- package/dist/tools/nextActions.js.map +1 -0
- package/dist/tools/plr/borrowerContacts.d.ts +3 -0
- package/dist/tools/plr/borrowerContacts.js +73 -0
- package/dist/tools/plr/borrowerContacts.js.map +1 -0
- package/dist/tools/plr/borrowerLoans.d.ts +3 -0
- package/dist/tools/plr/borrowerLoans.js +115 -0
- package/dist/tools/plr/borrowerLoans.js.map +1 -0
- package/dist/tools/plr/borrowerProfile.d.ts +3 -0
- package/dist/tools/plr/borrowerProfile.js +201 -0
- package/dist/tools/plr/borrowerProfile.js.map +1 -0
- package/dist/tools/plr/borrowerRankings.d.ts +3 -0
- package/dist/tools/plr/borrowerRankings.js +123 -0
- package/dist/tools/plr/borrowerRankings.js.map +1 -0
- package/dist/tools/plr/borrowerSearch.d.ts +3 -0
- package/dist/tools/plr/borrowerSearch.js +128 -0
- package/dist/tools/plr/borrowerSearch.js.map +1 -0
- package/dist/tools/plr/churnedBorrowers.d.ts +3 -0
- package/dist/tools/plr/churnedBorrowers.js +86 -0
- package/dist/tools/plr/churnedBorrowers.js.map +1 -0
- package/dist/tools/plr/lenderBorrowers.d.ts +3 -0
- package/dist/tools/plr/lenderBorrowers.js +71 -0
- package/dist/tools/plr/lenderBorrowers.js.map +1 -0
- package/dist/tools/plr/lenderRankings.d.ts +3 -0
- package/dist/tools/plr/lenderRankings.js +74 -0
- package/dist/tools/plr/lenderRankings.js.map +1 -0
- package/dist/tools/plr/loansNearby.d.ts +3 -0
- package/dist/tools/plr/loansNearby.js +113 -0
- package/dist/tools/plr/loansNearby.js.map +1 -0
- package/dist/tools/plr/marketTrends.d.ts +3 -0
- package/dist/tools/plr/marketTrends.js +95 -0
- package/dist/tools/plr/marketTrends.js.map +1 -0
- package/dist/tools/plr/msaRankings.d.ts +3 -0
- package/dist/tools/plr/msaRankings.js +74 -0
- package/dist/tools/plr/msaRankings.js.map +1 -0
- package/dist/tools/plr/negativeRemarks.d.ts +3 -0
- package/dist/tools/plr/negativeRemarks.js +94 -0
- package/dist/tools/plr/negativeRemarks.js.map +1 -0
- package/dist/tools/plr/ownerSearch.d.ts +3 -0
- package/dist/tools/plr/ownerSearch.js +57 -0
- package/dist/tools/plr/ownerSearch.js.map +1 -0
- package/dist/tools/plr/portfolioSummary.d.ts +3 -0
- package/dist/tools/plr/portfolioSummary.js +99 -0
- package/dist/tools/plr/portfolioSummary.js.map +1 -0
- package/dist/tools/plr/topBorrowers.d.ts +3 -0
- package/dist/tools/plr/topBorrowers.js +69 -0
- package/dist/tools/plr/topBorrowers.js.map +1 -0
- package/dist/tools/plr/topLenders.d.ts +3 -0
- package/dist/tools/plr/topLenders.js +75 -0
- package/dist/tools/plr/topLenders.js.map +1 -0
- package/dist/tools/plr/transactionHistory.d.ts +3 -0
- package/dist/tools/plr/transactionHistory.js +74 -0
- package/dist/tools/plr/transactionHistory.js.map +1 -0
- package/dist/tools/prompts.d.ts +7 -0
- package/dist/tools/prompts.js +157 -0
- package/dist/tools/prompts.js.map +1 -0
- package/dist/tools/registerToolSafe.d.ts +29 -0
- package/dist/tools/registerToolSafe.js +36 -0
- package/dist/tools/registerToolSafe.js.map +1 -0
- package/dist/tools/sfr/activityHighlights.d.ts +3 -0
- package/dist/tools/sfr/activityHighlights.js +70 -0
- package/dist/tools/sfr/activityHighlights.js.map +1 -0
- package/dist/tools/sfr/bestBuyers.d.ts +3 -0
- package/dist/tools/sfr/bestBuyers.js +60 -0
- package/dist/tools/sfr/bestBuyers.js.map +1 -0
- package/dist/tools/sfr/buyerGrowth.d.ts +3 -0
- package/dist/tools/sfr/buyerGrowth.js +68 -0
- package/dist/tools/sfr/buyerGrowth.js.map +1 -0
- package/dist/tools/sfr/buyerProfile.d.ts +3 -0
- package/dist/tools/sfr/buyerProfile.js +162 -0
- package/dist/tools/sfr/buyerProfile.js.map +1 -0
- package/dist/tools/sfr/distressSearch.d.ts +3 -0
- package/dist/tools/sfr/distressSearch.js +173 -0
- package/dist/tools/sfr/distressSearch.js.map +1 -0
- package/dist/tools/sfr/flipActivity.d.ts +3 -0
- package/dist/tools/sfr/flipActivity.js +110 -0
- package/dist/tools/sfr/flipActivity.js.map +1 -0
- package/dist/tools/sfr/flipStats.d.ts +3 -0
- package/dist/tools/sfr/flipStats.js +98 -0
- package/dist/tools/sfr/flipStats.js.map +1 -0
- package/dist/tools/sfr/getProperty.d.ts +3 -0
- package/dist/tools/sfr/getProperty.js +142 -0
- package/dist/tools/sfr/getProperty.js.map +1 -0
- package/dist/tools/sfr/institutionalOwners.d.ts +3 -0
- package/dist/tools/sfr/institutionalOwners.js +88 -0
- package/dist/tools/sfr/institutionalOwners.js.map +1 -0
- package/dist/tools/sfr/investorActivity.d.ts +3 -0
- package/dist/tools/sfr/investorActivity.js +130 -0
- package/dist/tools/sfr/investorActivity.js.map +1 -0
- package/dist/tools/sfr/marketHighlights.d.ts +3 -0
- package/dist/tools/sfr/marketHighlights.js +100 -0
- package/dist/tools/sfr/marketHighlights.js.map +1 -0
- package/dist/tools/sfr/msaResolver.d.ts +15 -0
- package/dist/tools/sfr/msaResolver.js +109 -0
- package/dist/tools/sfr/msaResolver.js.map +1 -0
- package/dist/tools/sfr/propertyBatch.d.ts +3 -0
- package/dist/tools/sfr/propertyBatch.js +73 -0
- package/dist/tools/sfr/propertyBatch.js.map +1 -0
- package/dist/tools/sfr/propertyComps.d.ts +3 -0
- package/dist/tools/sfr/propertyComps.js +56 -0
- package/dist/tools/sfr/propertyComps.js.map +1 -0
- package/dist/tools/sfr/propertyTransactions.d.ts +3 -0
- package/dist/tools/sfr/propertyTransactions.js +50 -0
- package/dist/tools/sfr/propertyTransactions.js.map +1 -0
- package/dist/tools/sfr/rentalComparables.d.ts +3 -0
- package/dist/tools/sfr/rentalComparables.js +91 -0
- package/dist/tools/sfr/rentalComparables.js.map +1 -0
- package/dist/tools/sfr/rentalMarketAnalysis.d.ts +3 -0
- package/dist/tools/sfr/rentalMarketAnalysis.js +134 -0
- package/dist/tools/sfr/rentalMarketAnalysis.js.map +1 -0
- package/dist/tools/sfr/rentalStats.d.ts +3 -0
- package/dist/tools/sfr/rentalStats.js +118 -0
- package/dist/tools/sfr/rentalStats.js.map +1 -0
- package/dist/tools/sfr/searchProperties.d.ts +3 -0
- package/dist/tools/sfr/searchProperties.js +157 -0
- package/dist/tools/sfr/searchProperties.js.map +1 -0
- package/dist/tools/sfr/topBuyers.d.ts +3 -0
- package/dist/tools/sfr/topBuyers.js +91 -0
- package/dist/tools/sfr/topBuyers.js.map +1 -0
- package/dist/tools/sfr/zipDetail.d.ts +3 -0
- package/dist/tools/sfr/zipDetail.js +85 -0
- package/dist/tools/sfr/zipDetail.js.map +1 -0
- package/dist/tools/sfr/zipFinder.d.ts +3 -0
- package/dist/tools/sfr/zipFinder.js +79 -0
- package/dist/tools/sfr/zipFinder.js.map +1 -0
- package/dist/tools/slugHelper.d.ts +2 -0
- package/dist/tools/slugHelper.js +5 -0
- package/dist/tools/slugHelper.js.map +1 -0
- package/dist/tools/snowflake/compareMarkets.d.ts +2 -0
- package/dist/tools/snowflake/compareMarkets.js +95 -0
- package/dist/tools/snowflake/compareMarkets.js.map +1 -0
- package/dist/tools/snowflake/hviTrend.d.ts +2 -0
- package/dist/tools/snowflake/hviTrend.js +77 -0
- package/dist/tools/snowflake/hviTrend.js.map +1 -0
- package/dist/tools/snowflake/investorActivity.d.ts +2 -0
- package/dist/tools/snowflake/investorActivity.js +138 -0
- package/dist/tools/snowflake/investorActivity.js.map +1 -0
- package/dist/tools/snowflake/marketSnapshot.d.ts +2 -0
- package/dist/tools/snowflake/marketSnapshot.js +185 -0
- package/dist/tools/snowflake/marketSnapshot.js.map +1 -0
- package/dist/tools/snowflake/marketTrends.d.ts +2 -0
- package/dist/tools/snowflake/marketTrends.js +81 -0
- package/dist/tools/snowflake/marketTrends.js.map +1 -0
- package/dist/tools/snowflake/rankRentalZips.d.ts +2 -0
- package/dist/tools/snowflake/rankRentalZips.js +94 -0
- package/dist/tools/snowflake/rankRentalZips.js.map +1 -0
- package/dist/tools/snowflake/rankZips.d.ts +2 -0
- package/dist/tools/snowflake/rankZips.js +86 -0
- package/dist/tools/snowflake/rankZips.js.map +1 -0
- package/dist/tools/snowflake/rentalMarket.d.ts +2 -0
- package/dist/tools/snowflake/rentalMarket.js +111 -0
- package/dist/tools/snowflake/rentalMarket.js.map +1 -0
- package/dist/tools/snowflake/rentalYield.d.ts +2 -0
- package/dist/tools/snowflake/rentalYield.js +143 -0
- package/dist/tools/snowflake/rentalYield.js.map +1 -0
- package/dist/tools/snowflake/zipProfile.d.ts +2 -0
- package/dist/tools/snowflake/zipProfile.js +110 -0
- package/dist/tools/snowflake/zipProfile.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sfQuery } from "../../services/snowflake.js";
|
|
3
|
+
import { structuredResult, markdownTable } from "../formatters.js";
|
|
4
|
+
import { action } from "../nextActions.js";
|
|
5
|
+
import { registerToolSafe } from "../registerToolSafe.js";
|
|
6
|
+
const PROPERTY_TYPES = [
|
|
7
|
+
"All Residential", "Single Family Residential", "Condo/Co-op",
|
|
8
|
+
"Townhouse", "Multi-Family (2-4 Unit)",
|
|
9
|
+
];
|
|
10
|
+
const LocationInput = z.object({
|
|
11
|
+
zipCode: z.string().optional().describe("5-digit ZIP code"),
|
|
12
|
+
city: z.string().optional().describe("City name (fallback)"),
|
|
13
|
+
state: z.string().describe("2-letter state code"),
|
|
14
|
+
});
|
|
15
|
+
const Input = z.object({
|
|
16
|
+
locations: z.array(LocationInput).min(2).max(4).describe("2-4 locations to compare"),
|
|
17
|
+
propertyType: z.enum(PROPERTY_TYPES).optional(),
|
|
18
|
+
});
|
|
19
|
+
function fmt$(v) { return v !== null ? "$" + Math.round(v).toLocaleString() : "—"; }
|
|
20
|
+
function fmtPct(v, d = 1) { return v !== null ? (v * 100).toFixed(d) + "%" : "—"; }
|
|
21
|
+
export function registerCompareMarketsTool(server) {
|
|
22
|
+
registerToolSafe(server, "sfr_compare_markets", {
|
|
23
|
+
title: "Side-by-side market comparison (Snowflake)",
|
|
24
|
+
description: "Compare 2-4 ZIP codes or cities side-by-side on Redfin metrics: price, price/sqft, " +
|
|
25
|
+
"YoY growth, DOM, inventory, sale-to-list. Shows winners per metric.",
|
|
26
|
+
inputSchema: Input,
|
|
27
|
+
}, async (args) => {
|
|
28
|
+
const pt = args.propertyType ?? "All Residential";
|
|
29
|
+
const snaps = [];
|
|
30
|
+
for (const loc of args.locations) {
|
|
31
|
+
const st = loc.state.toUpperCase();
|
|
32
|
+
let rows = [];
|
|
33
|
+
const label = loc.zipCode ? `${loc.zipCode}, ${st}` : `${loc.city}, ${st}`;
|
|
34
|
+
if (loc.zipCode) {
|
|
35
|
+
rows = await sfQuery(`SELECT MEDIAN_SALE_PRICE, MEDIAN_PPSF, MEDIAN_SALE_PRICE_YOY, MEDIAN_DOM,
|
|
36
|
+
INVENTORY, HOMES_SOLD, AVG_SALE_TO_LIST, SOLD_ABOVE_LIST
|
|
37
|
+
FROM ANALYTICS.PUBLIC.REDFIN_ZIP_MARKET
|
|
38
|
+
WHERE REGION = ? AND STATE_CODE = ? AND PROPERTY_TYPE = ?
|
|
39
|
+
AND PERIOD_END = (SELECT MAX(PERIOD_END) FROM ANALYTICS.PUBLIC.REDFIN_ZIP_MARKET WHERE REGION = ? AND STATE_CODE = ?)
|
|
40
|
+
LIMIT 1`, [loc.zipCode, st, pt, loc.zipCode, st]);
|
|
41
|
+
}
|
|
42
|
+
else if (loc.city) {
|
|
43
|
+
rows = await sfQuery(`SELECT MEDIAN_SALE_PRICE, MEDIAN_PPSF, MEDIAN_SALE_PRICE_YOY, MEDIAN_DOM,
|
|
44
|
+
INVENTORY, HOMES_SOLD, AVG_SALE_TO_LIST, SOLD_ABOVE_LIST
|
|
45
|
+
FROM ANALYTICS.PUBLIC.REDFIN_NEIGHBORHOOD_MARKET
|
|
46
|
+
WHERE UPPER(CITY) = UPPER(?) AND STATE_CODE = ? AND PROPERTY_TYPE = ?
|
|
47
|
+
AND PERIOD_END = (SELECT MAX(PERIOD_END) FROM ANALYTICS.PUBLIC.REDFIN_NEIGHBORHOOD_MARKET)
|
|
48
|
+
ORDER BY HOMES_SOLD DESC NULLS LAST LIMIT 1`, [loc.city, st, pt]);
|
|
49
|
+
}
|
|
50
|
+
if (rows[0]) {
|
|
51
|
+
const r = rows[0];
|
|
52
|
+
snaps.push({
|
|
53
|
+
label, medianSalePrice: r.MEDIAN_SALE_PRICE, ppsf: r.MEDIAN_PPSF,
|
|
54
|
+
priceYoy: r.MEDIAN_SALE_PRICE_YOY, dom: r.MEDIAN_DOM,
|
|
55
|
+
inventory: r.INVENTORY, homesSold: r.HOMES_SOLD,
|
|
56
|
+
saleToList: r.AVG_SALE_TO_LIST, soldAboveList: r.SOLD_ABOVE_LIST,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (snaps.length < 2) {
|
|
61
|
+
return structuredResult("## Market Comparison\n\n_Need at least 2 locations with data._", {});
|
|
62
|
+
}
|
|
63
|
+
const metrics = [
|
|
64
|
+
{ name: "Median Price", fn: s => fmt$(s.medianSalePrice), best: (a, b) => (a.medianSalePrice ?? Infinity) < (b.medianSalePrice ?? Infinity) ? a : b },
|
|
65
|
+
{ name: "Price/SqFt", fn: s => fmt$(s.ppsf), best: (a, b) => (a.ppsf ?? Infinity) < (b.ppsf ?? Infinity) ? a : b },
|
|
66
|
+
{ name: "Price YoY", fn: s => fmtPct(s.priceYoy), best: (a, b) => (a.priceYoy ?? -Infinity) > (b.priceYoy ?? -Infinity) ? a : b },
|
|
67
|
+
{ name: "DOM", fn: s => s.dom !== null ? `${s.dom}d` : "—", best: (a, b) => (a.dom ?? Infinity) < (b.dom ?? Infinity) ? a : b },
|
|
68
|
+
{ name: "Inventory", fn: s => s.inventory?.toLocaleString() ?? "—", best: (a, b) => (a.inventory ?? 0) > (b.inventory ?? 0) ? a : b },
|
|
69
|
+
{ name: "Homes Sold", fn: s => s.homesSold?.toLocaleString() ?? "—", best: (a, b) => (a.homesSold ?? 0) > (b.homesSold ?? 0) ? a : b },
|
|
70
|
+
{ name: "Sale-to-List", fn: s => fmtPct(s.saleToList, 2), best: (a, b) => (a.saleToList ?? 0) > (b.saleToList ?? 0) ? a : b },
|
|
71
|
+
];
|
|
72
|
+
const headers = ["Metric", ...snaps.map(s => s.label)];
|
|
73
|
+
const tableRows = metrics.map(m => [m.name, ...snaps.map(s => m.fn(s))]);
|
|
74
|
+
const winners = {};
|
|
75
|
+
for (const m of metrics) {
|
|
76
|
+
const w = snaps.reduce(m.best);
|
|
77
|
+
winners[m.name] = w.label;
|
|
78
|
+
}
|
|
79
|
+
const winnerLines = Object.entries(winners).map(([k, v]) => `- **${k}:** ${v}`);
|
|
80
|
+
const lines = [
|
|
81
|
+
`## Market Comparison (${snaps.length} locations)`,
|
|
82
|
+
"",
|
|
83
|
+
markdownTable(headers, tableRows),
|
|
84
|
+
"",
|
|
85
|
+
"### Winners",
|
|
86
|
+
...winnerLines,
|
|
87
|
+
];
|
|
88
|
+
const actions = snaps.map(s => action("sfr_market_snapshot", `Full snapshot for ${s.label}`, {
|
|
89
|
+
zipCode: s.label.split(",")[0].trim(),
|
|
90
|
+
state: s.label.split(",")[1]?.trim(),
|
|
91
|
+
}));
|
|
92
|
+
return structuredResult(lines.join("\n"), { locations: snaps, winners }, actions);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=compareMarkets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compareMarkets.js","sourceRoot":"","sources":["../../../src/tools/snowflake/compareMarkets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sfQuery } from "../../services/snowflake.js";
|
|
3
|
+
import { structuredResult, kvSummary, markdownTable } from "../formatters.js";
|
|
4
|
+
import { action } from "../nextActions.js";
|
|
5
|
+
import { registerToolSafe } from "../registerToolSafe.js";
|
|
6
|
+
const HVI_TABLE = "MARKETPLACE_ZIP.PUBLIC.ZIP_CODE_HOUSING_VALUE_TREND";
|
|
7
|
+
const Input = z.object({
|
|
8
|
+
zipCode: z.string().describe("5-digit ZIP code"),
|
|
9
|
+
months: z.coerce.number().optional().describe("Months of history (default 24, max 120)"),
|
|
10
|
+
});
|
|
11
|
+
function fmt$(v) { return v !== null ? "$" + Math.round(v).toLocaleString() : "—"; }
|
|
12
|
+
export function registerHviTrendTool(server) {
|
|
13
|
+
registerToolSafe(server, "sfr_hvi_trend", {
|
|
14
|
+
title: "Home Value Index (HVI) + Rental Value Index (RVI) trend (Snowflake)",
|
|
15
|
+
description: "Monthly home value and rental value indices over time for a ZIP code. " +
|
|
16
|
+
"Shows appreciation/depreciation trend with % change. Up to 120 months of history.",
|
|
17
|
+
inputSchema: Input,
|
|
18
|
+
}, async (args) => {
|
|
19
|
+
const months = Math.min(args.months ?? 24, 120);
|
|
20
|
+
const rows = await sfQuery(`SELECT ZIP_CODE, STATE, YEAR_MONTH::VARCHAR AS YM, BLENDED_HVI, BLENDED_RVI
|
|
21
|
+
FROM ${HVI_TABLE} WHERE ZIP_CODE = ?
|
|
22
|
+
AND YEAR_MONTH >= DATEADD(month, -?, CURRENT_DATE)
|
|
23
|
+
ORDER BY YEAR_MONTH ASC`, [args.zipCode, months]);
|
|
24
|
+
if (rows.length === 0) {
|
|
25
|
+
return structuredResult(`## HVI Trend — ${args.zipCode}\n\n_No data found._`, {});
|
|
26
|
+
}
|
|
27
|
+
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
28
|
+
const first = rows[0];
|
|
29
|
+
const last = rows[rows.length - 1];
|
|
30
|
+
const hviChange = first.BLENDED_HVI !== null && last.BLENDED_HVI !== null
|
|
31
|
+
? last.BLENDED_HVI - first.BLENDED_HVI : null;
|
|
32
|
+
const hviPct = first.BLENDED_HVI !== null && last.BLENDED_HVI !== null && first.BLENDED_HVI > 0
|
|
33
|
+
? (last.BLENDED_HVI - first.BLENDED_HVI) / first.BLENDED_HVI : null;
|
|
34
|
+
const rviChange = first.BLENDED_RVI !== null && last.BLENDED_RVI !== null
|
|
35
|
+
? last.BLENDED_RVI - first.BLENDED_RVI : null;
|
|
36
|
+
let direction = "flat";
|
|
37
|
+
if (hviPct !== null) {
|
|
38
|
+
if (hviPct > 0.02)
|
|
39
|
+
direction = "appreciating";
|
|
40
|
+
else if (hviPct < -0.02)
|
|
41
|
+
direction = "depreciating";
|
|
42
|
+
}
|
|
43
|
+
const changeLbl = hviPct !== null ? `${hviPct >= 0 ? "+" : ""}${(hviPct * 100).toFixed(1)}%` : "N/A";
|
|
44
|
+
const lines = [
|
|
45
|
+
`## HVI Trend — ${args.zipCode} (${months} months)`,
|
|
46
|
+
"",
|
|
47
|
+
kvSummary([
|
|
48
|
+
["Trend", `${direction.toUpperCase()} (${changeLbl})`],
|
|
49
|
+
["Start HVI", fmt$(first.BLENDED_HVI)],
|
|
50
|
+
["Latest HVI", fmt$(last.BLENDED_HVI)],
|
|
51
|
+
["HVI Change", hviChange !== null ? `${hviChange >= 0 ? "+" : ""}${fmt$(hviChange)}` : undefined],
|
|
52
|
+
["RVI Change", rviChange !== null ? `${rviChange >= 0 ? "+" : ""}${fmt$(rviChange)}` : undefined],
|
|
53
|
+
]),
|
|
54
|
+
"",
|
|
55
|
+
];
|
|
56
|
+
const tableRows = rows.map((r) => {
|
|
57
|
+
const d = new Date(r.YM);
|
|
58
|
+
return [
|
|
59
|
+
`${monthNames[d.getMonth()]} ${String(d.getFullYear()).slice(2)}`,
|
|
60
|
+
fmt$(r.BLENDED_HVI),
|
|
61
|
+
r.BLENDED_RVI !== null ? fmt$(r.BLENDED_RVI) : "—",
|
|
62
|
+
];
|
|
63
|
+
});
|
|
64
|
+
lines.push(markdownTable(["Date", "HVI", "RVI"], tableRows));
|
|
65
|
+
const actions = [
|
|
66
|
+
action("sfr_zip_profile", "Full ZIP profile", { zipCode: args.zipCode }),
|
|
67
|
+
action("sfr_market_snapshot", "Redfin snapshot", { zipCode: args.zipCode, state: first.STATE ?? "" }),
|
|
68
|
+
action("sfr_market_trends", "Redfin price trend", { zipCode: args.zipCode, state: first.STATE ?? "" }),
|
|
69
|
+
];
|
|
70
|
+
return structuredResult(lines.join("\n"), {
|
|
71
|
+
zipCode: args.zipCode, months, direction, changePct: hviPct,
|
|
72
|
+
startHvi: first.BLENDED_HVI, endHvi: last.BLENDED_HVI,
|
|
73
|
+
hviChange, rviChange, dataPoints: rows.length,
|
|
74
|
+
}, actions);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=hviTrend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hviTrend.js","sourceRoot":"","sources":["../../../src/tools/snowflake/hviTrend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sfQuery } from "../../services/snowflake.js";
|
|
3
|
+
import { structuredResult, kvSummary, markdownTable } from "../formatters.js";
|
|
4
|
+
import { action } from "../nextActions.js";
|
|
5
|
+
import { registerToolSafe } from "../registerToolSafe.js";
|
|
6
|
+
const GEO_FLIPS = "SFRA_PROD.PUBLIC.GEO_FLIPS";
|
|
7
|
+
const ASSESSOR = "ANALYTICS.DBT_FIRST_AMERICAN.STG_ASSESSOR";
|
|
8
|
+
const Input = z.object({
|
|
9
|
+
zipCode: z.string().describe("5-digit ZIP code"),
|
|
10
|
+
state: z.string().describe("2-letter state code"),
|
|
11
|
+
yearsBack: z.coerce.number().optional().describe("Years of transaction history (default 2)"),
|
|
12
|
+
});
|
|
13
|
+
function fmt$(v) { return v !== null ? "$" + Math.round(v).toLocaleString() : "—"; }
|
|
14
|
+
function fmtPct(v, d = 1) { return v !== null ? (v * 100).toFixed(d) + "%" : "—"; }
|
|
15
|
+
function fmtNum(v) { return v !== null ? Math.round(v).toLocaleString() : "—"; }
|
|
16
|
+
export function registerZipInvestorActivityTool(server) {
|
|
17
|
+
registerToolSafe(server, "sfr_zip_investor_activity", {
|
|
18
|
+
title: "Investor activity in a ZIP: cash, corporate, flips, ownership (Snowflake)",
|
|
19
|
+
description: "Comprehensive investor analysis for a ZIP: SFR ownership breakdown (corporate LLC/Inc vs " +
|
|
20
|
+
"personal trusts vs individual investors vs owner-occupied, from assessor records), transaction " +
|
|
21
|
+
"activity (cash %, corporate %, flips, foreclosures), flip profitability, and classification.",
|
|
22
|
+
inputSchema: Input,
|
|
23
|
+
}, async (args) => {
|
|
24
|
+
const st = args.state.toUpperCase();
|
|
25
|
+
const years = args.yearsBack ?? 2;
|
|
26
|
+
const [txRows, classifyRows, ownRows] = await Promise.all([
|
|
27
|
+
sfQuery(`SELECT COUNT(*) AS TOTAL_TX,
|
|
28
|
+
SUM(CASE WHEN TX_CLASSIFY LIKE 'Investor%' THEN 1 ELSE 0 END) AS INVESTOR_TX,
|
|
29
|
+
SUM(CASE WHEN CASH_PURCHASE THEN 1 ELSE 0 END) AS CASH_TX,
|
|
30
|
+
SUM(CASE WHEN CORP_PURCHASE THEN 1 ELSE 0 END) AS CORP_TX,
|
|
31
|
+
SUM(CASE WHEN BASE_1YR_EXITED_FLIP THEN 1 ELSE 0 END) AS FLIP_TX,
|
|
32
|
+
SUM(CASE WHEN FORECLOSURE_FLAG THEN 1 ELSE 0 END) AS FORECL_TX,
|
|
33
|
+
SUM(CASE WHEN REO_SALE_FLAG THEN 1 ELSE 0 END) AS REO_TX,
|
|
34
|
+
SUM(CASE WHEN NEW_CONSTRUCTION_FLAG THEN 1 ELSE 0 END) AS NEWCON_TX,
|
|
35
|
+
MEDIAN(CASE WHEN SALE_AMT > 0 THEN SALE_AMT ELSE NULL END) AS MED_PRICE,
|
|
36
|
+
MEDIAN(CASE WHEN GROSS_MARGIN IS NOT NULL AND BASE_1YR_EXITED_FLIP THEN GROSS_MARGIN ELSE NULL END) AS MED_FLIP_PROFIT,
|
|
37
|
+
AVG(CASE WHEN GROSS_MARGIN IS NOT NULL AND BASE_1YR_EXITED_FLIP THEN GROSS_MARGIN ELSE NULL END) AS AVG_FLIP_PROFIT,
|
|
38
|
+
MEDIAN(CASE WHEN DAYS_DIFF IS NOT NULL AND BASE_1YR_EXITED_FLIP THEN DAYS_DIFF ELSE NULL END) AS MED_FLIP_DAYS
|
|
39
|
+
FROM ${GEO_FLIPS} WHERE ZIP_CODE = ? AND STATE = ?
|
|
40
|
+
AND RECORDING_DATE >= DATEADD(year, -?, CURRENT_DATE)`, [args.zipCode, st, years]),
|
|
41
|
+
sfQuery(`SELECT TX_CLASSIFY, COUNT(*) AS CNT FROM ${GEO_FLIPS}
|
|
42
|
+
WHERE ZIP_CODE = ? AND STATE = ? AND RECORDING_DATE >= DATEADD(year, -?, CURRENT_DATE)
|
|
43
|
+
GROUP BY TX_CLASSIFY ORDER BY CNT DESC`, [args.zipCode, st, years]),
|
|
44
|
+
// Ownership from assessor records (SFR only: land_use_code 1001)
|
|
45
|
+
// Mutually exclusive buckets: owner-occ → corp → trust → individual investor
|
|
46
|
+
sfQuery(`WITH classified AS (
|
|
47
|
+
SELECT
|
|
48
|
+
OWNER_OCCUPIED,
|
|
49
|
+
CORPORATE_OWNER,
|
|
50
|
+
OWNER_1_FULL_NAME,
|
|
51
|
+
CURRENT_AVM_VALUE,
|
|
52
|
+
-- Corp keywords: LLC, Inc, Corp, LP, Ltd, etc.
|
|
53
|
+
CASE WHEN OWNER_1_FULL_NAME RLIKE '.*\\\\b(LLC|INC|CORP|CORPORATION|LP|LTD|PARTNERS|PARTNERSHIP|HOLDINGS|PROPERTIES|VENTURES|CAPITAL|REALTY|INVESTMENTS|GROUP|FUND|REIT|COMPANY)\\\\b.*'
|
|
54
|
+
THEN TRUE ELSE FALSE END AS IS_CORP_NAME,
|
|
55
|
+
-- Trust patterns: TR, TRS, TRUST, TRUSTEE, REVOCABLE, REVOCAB (truncated), TRUS (truncated), FAMILY TR, LIVING TR
|
|
56
|
+
CASE WHEN OWNER_1_FULL_NAME RLIKE '.*(\\\\bTR$|\\\\bTRS$|\\\\bTRUS$|\\\\bTRUST|\\\\bTRSTEE|\\\\bTRST\\\\b|\\\\bREVOCAB|\\\\bFAMILY TR|\\\\bLIVING TR|\\\\bIRREVOCAB)'
|
|
57
|
+
THEN TRUE ELSE FALSE END AS IS_TRUST_NAME
|
|
58
|
+
FROM ${ASSESSOR}
|
|
59
|
+
WHERE ZIP_CODE = ? AND STATE = ? AND LAND_USE_CODE = '1001'
|
|
60
|
+
)
|
|
61
|
+
SELECT
|
|
62
|
+
COUNT(*) AS TOTAL_SFR,
|
|
63
|
+
SUM(CASE WHEN OWNER_OCCUPIED THEN 1 ELSE 0 END) AS OWNER_OCC,
|
|
64
|
+
-- Non-occ true corporate (LLC/Inc/Corp — NOT trusts that also have LLC)
|
|
65
|
+
SUM(CASE WHEN NOT OWNER_OCCUPIED AND IS_CORP_NAME AND NOT IS_TRUST_NAME THEN 1 ELSE 0 END) AS TRUE_CORP,
|
|
66
|
+
-- Non-occ trust-held (personal/family trusts, not corporate trusts like "XYZ TRUST LLC")
|
|
67
|
+
SUM(CASE WHEN NOT OWNER_OCCUPIED AND IS_TRUST_NAME AND NOT IS_CORP_NAME THEN 1 ELSE 0 END) AS TRUST_HELD,
|
|
68
|
+
-- Non-occ individual investor (not flagged corporate at all)
|
|
69
|
+
SUM(CASE WHEN NOT OWNER_OCCUPIED AND NOT CORPORATE_OWNER THEN 1 ELSE 0 END) AS INDIV_INVESTOR,
|
|
70
|
+
-- Non-occ other corporate (flagged corporate but doesn't match corp or trust keywords)
|
|
71
|
+
SUM(CASE WHEN NOT OWNER_OCCUPIED AND CORPORATE_OWNER AND NOT IS_CORP_NAME AND NOT IS_TRUST_NAME THEN 1 ELSE 0 END) AS OTHER_ENTITY,
|
|
72
|
+
MEDIAN(CURRENT_AVM_VALUE) AS MED_AVM
|
|
73
|
+
FROM classified`, [args.zipCode, st]),
|
|
74
|
+
]);
|
|
75
|
+
const tx = txRows[0];
|
|
76
|
+
const own = ownRows[0];
|
|
77
|
+
if ((!tx || tx.TOTAL_TX === 0) && (!own || own.TOTAL_SFR === 0)) {
|
|
78
|
+
return structuredResult(`## Investor Activity\n\n_No data for ${args.zipCode}, ${st}_`, {});
|
|
79
|
+
}
|
|
80
|
+
const total = own?.TOTAL_SFR ?? 0;
|
|
81
|
+
const pctOf = (n) => total > 0 ? fmtPct(n / total) : "—";
|
|
82
|
+
const lines = [`## Investor Activity — ${args.zipCode}, ${st}`, ""];
|
|
83
|
+
if (own && total > 0) {
|
|
84
|
+
const items = [
|
|
85
|
+
["Total Single-Family Homes", fmtNum(total)],
|
|
86
|
+
["Owner-Occupied", `${fmtNum(own.OWNER_OCC)} (${pctOf(own.OWNER_OCC)})`],
|
|
87
|
+
["Corporate-Owned (LLC/Inc/Corp/LP)", `${fmtNum(own.TRUE_CORP)} (${pctOf(own.TRUE_CORP)})`],
|
|
88
|
+
["Trust-Held (personal/family trusts)", `${fmtNum(own.TRUST_HELD)} (${pctOf(own.TRUST_HELD)})`],
|
|
89
|
+
["Individual Investor (non-occ, non-entity)", `${fmtNum(own.INDIV_INVESTOR)} (${pctOf(own.INDIV_INVESTOR)})`],
|
|
90
|
+
];
|
|
91
|
+
if (own.OTHER_ENTITY > 0) {
|
|
92
|
+
items.push(["Other Entity (nonprofit, govt, unclassified)", `${fmtNum(own.OTHER_ENTITY)} (${pctOf(own.OTHER_ENTITY)})`]);
|
|
93
|
+
}
|
|
94
|
+
items.push(["Median AVM", own.MED_AVM ? fmt$(own.MED_AVM) : undefined]);
|
|
95
|
+
lines.push("### SFR Ownership Breakdown (from assessor records)", kvSummary(items), "");
|
|
96
|
+
}
|
|
97
|
+
if (tx && tx.TOTAL_TX > 0) {
|
|
98
|
+
const pct = (n) => tx.TOTAL_TX > 0 ? fmtPct(n / tx.TOTAL_TX) : "—";
|
|
99
|
+
lines.push(`### Transactions (last ${years} years)`, kvSummary([
|
|
100
|
+
["Total Investor Transactions", fmtNum(tx.TOTAL_TX)],
|
|
101
|
+
["Cash Purchases", `${fmtNum(tx.CASH_TX)} (${pct(tx.CASH_TX)})`],
|
|
102
|
+
["Corporate Purchases", `${fmtNum(tx.CORP_TX)} (${pct(tx.CORP_TX)})`],
|
|
103
|
+
["Foreclosures", fmtNum(tx.FORECL_TX)],
|
|
104
|
+
["REO Purchases", fmtNum(tx.REO_TX)],
|
|
105
|
+
["New Construction", fmtNum(tx.NEWCON_TX)],
|
|
106
|
+
["Median Purchase Price", fmt$(tx.MED_PRICE)],
|
|
107
|
+
]), "", "### Flip Activity", kvSummary([
|
|
108
|
+
["Flips (exited <1yr)", fmtNum(tx.FLIP_TX)],
|
|
109
|
+
["Median Flip Profit", tx.MED_FLIP_PROFIT !== null ? fmt$(tx.MED_FLIP_PROFIT) : undefined],
|
|
110
|
+
["Avg Flip Profit", tx.AVG_FLIP_PROFIT !== null ? fmt$(tx.AVG_FLIP_PROFIT) : undefined],
|
|
111
|
+
["Median Hold Time", tx.MED_FLIP_DAYS !== null ? `${Math.round(tx.MED_FLIP_DAYS)} days` : undefined],
|
|
112
|
+
]));
|
|
113
|
+
if (classifyRows.length > 0) {
|
|
114
|
+
lines.push("", "### Transaction Classification");
|
|
115
|
+
lines.push(markdownTable(["Type", "Count"], classifyRows.map((r) => [r.TX_CLASSIFY ?? "Unclassified", r.CNT.toLocaleString()])));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const actions = [
|
|
119
|
+
action("sfr_rental_yield", "Rental yield", { zipCode: args.zipCode, state: st }),
|
|
120
|
+
action("sfr_market_snapshot", "Market snapshot", { zipCode: args.zipCode, state: st }),
|
|
121
|
+
action("sfr_zip_profile", "Demographics", { zipCode: args.zipCode }),
|
|
122
|
+
action("sfr_top_buyers", "Top buyers in ZIP", { search_type: "zip", zips: args.zipCode }),
|
|
123
|
+
];
|
|
124
|
+
return structuredResult(lines.join("\n"), {
|
|
125
|
+
zipCode: args.zipCode, state: st, yearsBack: years,
|
|
126
|
+
totalTx: tx?.TOTAL_TX ?? 0, cashTx: tx?.CASH_TX ?? 0, corpTx: tx?.CORP_TX ?? 0,
|
|
127
|
+
flips: tx?.FLIP_TX ?? 0, medFlipProfit: tx?.MED_FLIP_PROFIT,
|
|
128
|
+
totalSfr: total,
|
|
129
|
+
ownerOccupied: own?.OWNER_OCC ?? 0,
|
|
130
|
+
trueCorp: own?.TRUE_CORP ?? 0,
|
|
131
|
+
trustHeld: own?.TRUST_HELD ?? 0,
|
|
132
|
+
indivInvestor: own?.INDIV_INVESTOR ?? 0,
|
|
133
|
+
otherEntity: own?.OTHER_ENTITY ?? 0,
|
|
134
|
+
corpPct: total > 0 ? (own?.TRUE_CORP ?? 0) / total : null,
|
|
135
|
+
}, actions);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=investorActivity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"investorActivity.js","sourceRoot":"","sources":["../../../src/tools/snowflake/investorActivity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sfQuery } from "../../services/snowflake.js";
|
|
3
|
+
import { structuredResult, kvSummary, markdownTable } from "../formatters.js";
|
|
4
|
+
import { action } from "../nextActions.js";
|
|
5
|
+
import { registerToolSafe } from "../registerToolSafe.js";
|
|
6
|
+
const PROPERTY_TYPES = [
|
|
7
|
+
"All Residential",
|
|
8
|
+
"Single Family Residential",
|
|
9
|
+
"Condo/Co-op",
|
|
10
|
+
"Townhouse",
|
|
11
|
+
"Multi-Family (2-4 Unit)",
|
|
12
|
+
];
|
|
13
|
+
const ZIP_COLUMNS = `
|
|
14
|
+
REGION, CITY, STATE, STATE_CODE, PARENT_METRO_REGION,
|
|
15
|
+
PERIOD_BEGIN, PERIOD_END, MEDIAN_SALE_PRICE, MEDIAN_LIST_PRICE,
|
|
16
|
+
MEDIAN_PPSF, MEDIAN_LIST_PPSF, MEDIAN_SALE_PRICE_YOY,
|
|
17
|
+
MEDIAN_DOM, MEDIAN_DOM_YOY, INVENTORY, MONTHS_OF_SUPPLY,
|
|
18
|
+
HOMES_SOLD, NEW_LISTINGS, AVG_SALE_TO_LIST, SOLD_ABOVE_LIST,
|
|
19
|
+
PRICE_DROPS, OFF_MARKET_IN_TWO_WEEKS`;
|
|
20
|
+
function sanitizeYoY(v) {
|
|
21
|
+
if (v === null)
|
|
22
|
+
return null;
|
|
23
|
+
if (Math.abs(v) > 5)
|
|
24
|
+
return null;
|
|
25
|
+
return Math.max(-2, Math.min(2, v));
|
|
26
|
+
}
|
|
27
|
+
function fmt$(v) {
|
|
28
|
+
if (v === null)
|
|
29
|
+
return "—";
|
|
30
|
+
return "$" + Math.round(v).toLocaleString("en-US");
|
|
31
|
+
}
|
|
32
|
+
function fmtPct(v, d = 1) {
|
|
33
|
+
if (v === null)
|
|
34
|
+
return "—";
|
|
35
|
+
return (v * 100).toFixed(d) + "%";
|
|
36
|
+
}
|
|
37
|
+
function fmtNum(v) {
|
|
38
|
+
if (v === null)
|
|
39
|
+
return "—";
|
|
40
|
+
return Math.round(v).toLocaleString("en-US");
|
|
41
|
+
}
|
|
42
|
+
function buyerLeverage(row) {
|
|
43
|
+
let seller = 0, buyer = 0;
|
|
44
|
+
const stl = row.AVG_SALE_TO_LIST;
|
|
45
|
+
if (stl !== null) {
|
|
46
|
+
if (stl >= 1.02)
|
|
47
|
+
seller += 2;
|
|
48
|
+
else if (stl >= 1.0)
|
|
49
|
+
seller += 1;
|
|
50
|
+
else if (stl < 0.96)
|
|
51
|
+
buyer += 2;
|
|
52
|
+
else if (stl < 0.98)
|
|
53
|
+
buyer += 1;
|
|
54
|
+
}
|
|
55
|
+
if (row.SOLD_ABOVE_LIST !== null) {
|
|
56
|
+
if (row.SOLD_ABOVE_LIST >= 0.4)
|
|
57
|
+
seller += 2;
|
|
58
|
+
else if (row.SOLD_ABOVE_LIST >= 0.25)
|
|
59
|
+
seller += 1;
|
|
60
|
+
else if (row.SOLD_ABOVE_LIST < 0.1)
|
|
61
|
+
buyer += 1;
|
|
62
|
+
}
|
|
63
|
+
if (row.MEDIAN_DOM !== null) {
|
|
64
|
+
if (row.MEDIAN_DOM <= 21)
|
|
65
|
+
seller += 1;
|
|
66
|
+
else if (row.MEDIAN_DOM >= 90)
|
|
67
|
+
buyer += 1;
|
|
68
|
+
}
|
|
69
|
+
if (seller >= 3 && seller > buyer)
|
|
70
|
+
return "LOW";
|
|
71
|
+
if (buyer >= 2 && buyer > seller)
|
|
72
|
+
return "HIGH";
|
|
73
|
+
return "MEDIUM";
|
|
74
|
+
}
|
|
75
|
+
const Input = z.object({
|
|
76
|
+
zipCode: z.string().optional().describe("5-digit ZIP code"),
|
|
77
|
+
city: z.string().optional().describe("City name (fallback if no ZIP)"),
|
|
78
|
+
state: z.string().describe("2-letter state code, e.g. 'TX'"),
|
|
79
|
+
propertyType: z.enum(PROPERTY_TYPES).optional().describe("Property type filter (default: All Residential)"),
|
|
80
|
+
});
|
|
81
|
+
export function registerMarketSnapshotTool(server) {
|
|
82
|
+
registerToolSafe(server, "sfr_market_snapshot", {
|
|
83
|
+
title: "Redfin market snapshot for a ZIP or city (Snowflake)",
|
|
84
|
+
description: "Get comprehensive market data from Redfin: median sale/list price, price/sqft, YoY growth, " +
|
|
85
|
+
"days on market, inventory, months of supply, sale-to-list ratio, buyer leverage signal, " +
|
|
86
|
+
"and a 12-month price trend. ZIP-level data with city/neighborhood fallback.",
|
|
87
|
+
inputSchema: Input,
|
|
88
|
+
}, async (args) => {
|
|
89
|
+
const st = args.state.toUpperCase();
|
|
90
|
+
const pt = args.propertyType ?? "All Residential";
|
|
91
|
+
let row = null;
|
|
92
|
+
let trendRows = [];
|
|
93
|
+
if (args.zipCode) {
|
|
94
|
+
const rows = await sfQuery(`SELECT ${ZIP_COLUMNS} FROM ANALYTICS.PUBLIC.REDFIN_ZIP_MARKET
|
|
95
|
+
WHERE REGION = ? AND STATE_CODE = ? AND PROPERTY_TYPE = ?
|
|
96
|
+
AND PERIOD_END = (SELECT MAX(PERIOD_END) FROM ANALYTICS.PUBLIC.REDFIN_ZIP_MARKET WHERE REGION = ? AND STATE_CODE = ?)
|
|
97
|
+
LIMIT 1`, [args.zipCode, st, pt, args.zipCode, st]);
|
|
98
|
+
row = rows[0] ?? null;
|
|
99
|
+
if (row) {
|
|
100
|
+
trendRows = await sfQuery(`SELECT PERIOD_END, MEDIAN_SALE_PRICE, MEDIAN_DOM, INVENTORY
|
|
101
|
+
FROM ANALYTICS.PUBLIC.REDFIN_ZIP_MARKET
|
|
102
|
+
WHERE REGION = ? AND STATE_CODE = ? AND PROPERTY_TYPE = ?
|
|
103
|
+
AND PERIOD_END >= DATEADD(month, -12, CURRENT_DATE) ORDER BY PERIOD_END ASC`, [args.zipCode, st, pt]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!row && args.city) {
|
|
107
|
+
const rows = await sfQuery(`SELECT ${ZIP_COLUMNS} FROM ANALYTICS.PUBLIC.REDFIN_NEIGHBORHOOD_MARKET
|
|
108
|
+
WHERE UPPER(CITY) = UPPER(?) AND STATE_CODE = ? AND PROPERTY_TYPE = ?
|
|
109
|
+
AND PERIOD_END = (SELECT MAX(PERIOD_END) FROM ANALYTICS.PUBLIC.REDFIN_NEIGHBORHOOD_MARKET)
|
|
110
|
+
ORDER BY HOMES_SOLD DESC NULLS LAST LIMIT 1`, [args.city, st, pt]);
|
|
111
|
+
row = rows[0] ?? null;
|
|
112
|
+
}
|
|
113
|
+
if (!row) {
|
|
114
|
+
return structuredResult(`## Market Snapshot\n\n_No data found for ${args.zipCode ?? args.city}, ${st}_`, {});
|
|
115
|
+
}
|
|
116
|
+
const priceYoY = sanitizeYoY(row.MEDIAN_SALE_PRICE_YOY);
|
|
117
|
+
const domYoY = sanitizeYoY(row.MEDIAN_DOM_YOY);
|
|
118
|
+
const leverage = buyerLeverage(row);
|
|
119
|
+
const loc = `${row.REGION}${row.CITY ? ", " + row.CITY : ""}, ${row.STATE_CODE ?? st}`;
|
|
120
|
+
const lines = [
|
|
121
|
+
`## Market Snapshot — ${loc}`,
|
|
122
|
+
row.PARENT_METRO_REGION ? `_Metro: ${row.PARENT_METRO_REGION}_` : "",
|
|
123
|
+
`_Period: ${row.PERIOD_BEGIN} to ${row.PERIOD_END}_`,
|
|
124
|
+
"",
|
|
125
|
+
"### Pricing",
|
|
126
|
+
kvSummary([
|
|
127
|
+
["Median Sale Price", `${fmt$(row.MEDIAN_SALE_PRICE)}${priceYoY !== null ? ` (${fmtPct(priceYoY)} YoY)` : ""}`],
|
|
128
|
+
["Median List Price", fmt$(row.MEDIAN_LIST_PRICE)],
|
|
129
|
+
["Price/SqFt (Sale)", fmt$(row.MEDIAN_PPSF)],
|
|
130
|
+
["Price/SqFt (List)", fmt$(row.MEDIAN_LIST_PPSF)],
|
|
131
|
+
]),
|
|
132
|
+
"",
|
|
133
|
+
"### Market Speed",
|
|
134
|
+
kvSummary([
|
|
135
|
+
["Days on Market", row.MEDIAN_DOM !== null ? `${row.MEDIAN_DOM}d${domYoY !== null ? ` (${fmtPct(domYoY)} YoY)` : ""}` : undefined],
|
|
136
|
+
["Off-Market in 2 Weeks", fmtPct(row.OFF_MARKET_IN_TWO_WEEKS)],
|
|
137
|
+
]),
|
|
138
|
+
"",
|
|
139
|
+
"### Inventory",
|
|
140
|
+
kvSummary([
|
|
141
|
+
["Active Listings", fmtNum(row.INVENTORY)],
|
|
142
|
+
["Months of Supply", row.MONTHS_OF_SUPPLY !== null ? row.MONTHS_OF_SUPPLY.toFixed(1) : undefined],
|
|
143
|
+
["Homes Sold", fmtNum(row.HOMES_SOLD)],
|
|
144
|
+
["New Listings", fmtNum(row.NEW_LISTINGS)],
|
|
145
|
+
]),
|
|
146
|
+
"",
|
|
147
|
+
"### Negotiation",
|
|
148
|
+
kvSummary([
|
|
149
|
+
["Sale-to-List", fmtPct(row.AVG_SALE_TO_LIST, 2)],
|
|
150
|
+
["Sold Above List", fmtPct(row.SOLD_ABOVE_LIST)],
|
|
151
|
+
["Price Drops", fmtPct(row.PRICE_DROPS)],
|
|
152
|
+
["Buyer Leverage", leverage],
|
|
153
|
+
]),
|
|
154
|
+
];
|
|
155
|
+
if (trendRows.length >= 2) {
|
|
156
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
157
|
+
lines.push("", "### 12-Month Trend");
|
|
158
|
+
const tRows = trendRows.map((r) => {
|
|
159
|
+
const d = new Date(r.PERIOD_END);
|
|
160
|
+
return [
|
|
161
|
+
`${months[d.getMonth()]} ${String(d.getFullYear()).slice(2)}`,
|
|
162
|
+
fmt$(r.MEDIAN_SALE_PRICE),
|
|
163
|
+
r.MEDIAN_DOM !== null ? `${r.MEDIAN_DOM}d` : "—",
|
|
164
|
+
fmtNum(r.INVENTORY),
|
|
165
|
+
];
|
|
166
|
+
});
|
|
167
|
+
lines.push(markdownTable(["Date", "Med. Price", "DOM", "Inventory"], tRows));
|
|
168
|
+
}
|
|
169
|
+
const zip = args.zipCode ?? row.REGION;
|
|
170
|
+
const actions = [
|
|
171
|
+
action("sfr_market_trends", "Price trend over time", { zipCode: zip, state: st }),
|
|
172
|
+
action("sfr_rental_yield", "Rental yield analysis", { zipCode: zip, state: st }),
|
|
173
|
+
action("sfr_zip_profile", "Census demographics", { zipCode: zip }),
|
|
174
|
+
action("sfr_zip_investor_activity", "Investor activity", { zipCode: zip, state: st }),
|
|
175
|
+
];
|
|
176
|
+
return structuredResult(lines.filter(Boolean).join("\n"), {
|
|
177
|
+
region: row.REGION, city: row.CITY, stateCode: row.STATE_CODE, metro: row.PARENT_METRO_REGION,
|
|
178
|
+
periodEnd: row.PERIOD_END, medianSalePrice: row.MEDIAN_SALE_PRICE, priceYoY,
|
|
179
|
+
medianDom: row.MEDIAN_DOM, inventory: row.INVENTORY, saleToList: row.AVG_SALE_TO_LIST,
|
|
180
|
+
soldAboveList: row.SOLD_ABOVE_LIST, buyerLeverage: leverage,
|
|
181
|
+
monthsOfSupply: row.MONTHS_OF_SUPPLY, homesSold: row.HOMES_SOLD,
|
|
182
|
+
}, actions);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=marketSnapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marketSnapshot.js","sourceRoot":"","sources":["../../../src/tools/snowflake/marketSnapshot.ts"],"names":[],"mappings":"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"}
|