@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,98 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { structuredResult, markdownTable, fmtDollars } from "../formatters.js";
|
|
3
|
+
import { action, locationArgs } from "../nextActions.js";
|
|
4
|
+
import { registerToolSafe } from "../registerToolSafe.js";
|
|
5
|
+
import { resolveCanonicalMsa } from "./msaResolver.js";
|
|
6
|
+
const Input = z.object({
|
|
7
|
+
search_type: z
|
|
8
|
+
.enum(["state", "city", "zip", "msa"])
|
|
9
|
+
.describe("Location search type"),
|
|
10
|
+
state: z.string().optional().describe("State abbreviation, e.g. 'AZ'"),
|
|
11
|
+
city: z.string().optional().describe("City name (requires state param when search_type='city')"),
|
|
12
|
+
zips: z.string().optional().describe("Comma-separated zip codes, e.g. '85281'"),
|
|
13
|
+
msa: z.string().optional().describe("MSA name"),
|
|
14
|
+
metric: z
|
|
15
|
+
.enum(["hold-time", "gross-margin", "both"])
|
|
16
|
+
.default("both")
|
|
17
|
+
.describe("Which flip metric to retrieve"),
|
|
18
|
+
});
|
|
19
|
+
/** Format monthly time series as a readable table (last 12 months) */
|
|
20
|
+
function formatTimeSeries(items, valueKey, formatValue, valueHeader) {
|
|
21
|
+
if (!Array.isArray(items) || items.length === 0)
|
|
22
|
+
return "_No data_";
|
|
23
|
+
// Show last 12 months
|
|
24
|
+
const recent = items.slice(-12);
|
|
25
|
+
const rows = recent.map((row) => {
|
|
26
|
+
const date = row.date ?? "—";
|
|
27
|
+
const month = date.length >= 7 ? date.slice(0, 7) : date;
|
|
28
|
+
const val = row[valueKey];
|
|
29
|
+
return [month, val != null ? formatValue(Number(val)) : "—"];
|
|
30
|
+
});
|
|
31
|
+
return markdownTable(["Month", valueHeader], rows);
|
|
32
|
+
}
|
|
33
|
+
export function registerFlipStatsTool(server, sfr) {
|
|
34
|
+
registerToolSafe(server, "sfr_flip_stats", {
|
|
35
|
+
title: "Flip profitability stats (SFR)",
|
|
36
|
+
description: "Get flip profitability TRENDS for a market: hold time ranges and gross margin ranges. " +
|
|
37
|
+
"Choose metric='hold-time', 'gross-margin', or 'both'. " +
|
|
38
|
+
"Requires search_type + location param (search_type='city' requires state). " +
|
|
39
|
+
"For market-level flip aggregates (volume, flipper counts), use sfr_flip_activity.",
|
|
40
|
+
inputSchema: Input,
|
|
41
|
+
}, async (args) => {
|
|
42
|
+
const query = { search_type: args.search_type };
|
|
43
|
+
if (args.state)
|
|
44
|
+
query.state = args.state;
|
|
45
|
+
if (args.city)
|
|
46
|
+
query.city = args.city;
|
|
47
|
+
if (args.zips)
|
|
48
|
+
query.zips = args.zips;
|
|
49
|
+
if (args.msa)
|
|
50
|
+
query.msa = args.msa;
|
|
51
|
+
let effectiveMsa = args.msa;
|
|
52
|
+
let resolutionMethod = "exact";
|
|
53
|
+
// MSA resolution probe — check before making metric-specific calls
|
|
54
|
+
if (args.search_type === "msa" && args.msa) {
|
|
55
|
+
const probeData = await sfr.getFlipHoldTime(query);
|
|
56
|
+
const probeItems = Array.isArray(probeData) ? probeData : probeData?.data ?? [];
|
|
57
|
+
if (probeItems.length === 0) {
|
|
58
|
+
resolutionMethod = "none";
|
|
59
|
+
const resolved = await resolveCanonicalMsa(sfr, { requestedMsa: args.msa });
|
|
60
|
+
if (resolved.resolvedMsa
|
|
61
|
+
&& resolved.resolvedMsa.toLowerCase() !== args.msa.trim().toLowerCase()) {
|
|
62
|
+
effectiveMsa = resolved.resolvedMsa;
|
|
63
|
+
query.msa = effectiveMsa;
|
|
64
|
+
resolutionMethod = "fallback";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const loc = args.zips ?? args.city ?? (effectiveMsa ?? args.msa) ?? args.state ?? "All";
|
|
69
|
+
const lines = [`## Flip Statistics — ${loc}`, ""];
|
|
70
|
+
if (resolutionMethod === "fallback") {
|
|
71
|
+
lines.push(`_MSA normalized: '${args.msa}' -> '${effectiveMsa}'_`, "");
|
|
72
|
+
}
|
|
73
|
+
const result = {};
|
|
74
|
+
if (args.metric !== "gross-margin") {
|
|
75
|
+
const holdTime = await sfr.getFlipHoldTime(query);
|
|
76
|
+
const items = Array.isArray(holdTime) ? holdTime : holdTime?.data ?? [];
|
|
77
|
+
result.holdTime = items;
|
|
78
|
+
lines.push("### Median Hold Time (last 12 months)");
|
|
79
|
+
lines.push(formatTimeSeries(items, "daysDiff", (v) => `${Math.round(v)} days`, "Median Days"));
|
|
80
|
+
lines.push("");
|
|
81
|
+
}
|
|
82
|
+
if (args.metric !== "hold-time") {
|
|
83
|
+
const margin = await sfr.getFlipGrossMargin(query);
|
|
84
|
+
const items = Array.isArray(margin) ? margin : margin?.data ?? [];
|
|
85
|
+
result.grossMargin = items;
|
|
86
|
+
lines.push("### Median Gross Margin (last 12 months)");
|
|
87
|
+
lines.push(formatTimeSeries(items, "grossMargin", (v) => fmtDollars(v), "Median Margin"));
|
|
88
|
+
}
|
|
89
|
+
const locArgs = locationArgs(args);
|
|
90
|
+
const actions = [
|
|
91
|
+
action("sfr_flip_activity", "Browse individual flip transactions", locArgs),
|
|
92
|
+
action("sfr_search_properties", "All transactions in area", locArgs),
|
|
93
|
+
action("sfr_top_buyers", "Active buyers in area", locArgs),
|
|
94
|
+
];
|
|
95
|
+
return structuredResult(lines.join("\n"), result, actions);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=flipStats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flipStats.js","sourceRoot":"","sources":["../../../src/tools/sfr/flipStats.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { structuredResult, kvSummary } from "../formatters.js";
|
|
3
|
+
import { action } from "../nextActions.js";
|
|
4
|
+
import { registerToolSafe } from "../registerToolSafe.js";
|
|
5
|
+
const Input = z.object({
|
|
6
|
+
address: z.string().describe("Full property address, e.g. '123 Main St, Phoenix, AZ 85004'"),
|
|
7
|
+
});
|
|
8
|
+
export function registerGetPropertyTool(server, sfr) {
|
|
9
|
+
registerToolSafe(server, "sfr_get_property", {
|
|
10
|
+
title: "Get property details by address (SFR)",
|
|
11
|
+
description: "Get comprehensive property details by address: valuation (AVM), tax assessments, " +
|
|
12
|
+
"owner info, structure (beds/baths/sqft), last sale, lot size, and investment metrics. " +
|
|
13
|
+
"For multiple addresses at once, use sfr_property_batch. " +
|
|
14
|
+
"For transaction history, use sfr_property_transactions.",
|
|
15
|
+
inputSchema: Input,
|
|
16
|
+
}, async (args) => {
|
|
17
|
+
const data = await sfr.getPropertyByAddress(args.address);
|
|
18
|
+
const addr = data?.address;
|
|
19
|
+
const fullAddr = addr
|
|
20
|
+
? `${addr.formatted_street_address}, ${addr.city}, ${addr.state} ${addr.zip_code}`
|
|
21
|
+
: args.address;
|
|
22
|
+
const lines = [
|
|
23
|
+
`## Property Details — ${fullAddr}`,
|
|
24
|
+
"",
|
|
25
|
+
"### Location",
|
|
26
|
+
kvSummary([
|
|
27
|
+
["Address", addr?.formatted_street_address],
|
|
28
|
+
["City", addr?.city],
|
|
29
|
+
["State", addr?.state],
|
|
30
|
+
["Zip", addr?.zip_code],
|
|
31
|
+
["County", data?.county],
|
|
32
|
+
["MSA", data?.msa],
|
|
33
|
+
["Latitude", addr?.latitude],
|
|
34
|
+
["Longitude", addr?.longitude],
|
|
35
|
+
]),
|
|
36
|
+
"",
|
|
37
|
+
"### Structure",
|
|
38
|
+
kvSummary([
|
|
39
|
+
["Beds", data?.structure?.beds_count],
|
|
40
|
+
["Baths", data?.structure?.baths],
|
|
41
|
+
["Sqft", data?.structure?.total_area_sq_ft ?? data?.structure?.living_area_sqft],
|
|
42
|
+
["Lot Size (sqft)", data?.parcel?.area_sq_ft],
|
|
43
|
+
["Year Built", data?.structure?.year_built],
|
|
44
|
+
["Stories", data?.structure?.stories],
|
|
45
|
+
["Property Type", data?.property_type ?? data?.property_class_description],
|
|
46
|
+
["Units", data?.structure?.units_count],
|
|
47
|
+
]),
|
|
48
|
+
"",
|
|
49
|
+
"### Valuation & Assessments",
|
|
50
|
+
kvSummary([
|
|
51
|
+
["Assessed Value", data?.assessments?.assessed_value ? `$${Number(data.assessments.assessed_value).toLocaleString()}` : undefined],
|
|
52
|
+
["Land Value", data?.assessments?.land_value ? `$${Number(data.assessments.land_value).toLocaleString()}` : undefined],
|
|
53
|
+
["Market Value", data?.assessments?.market_value ? `$${Number(data.assessments.market_value).toLocaleString()}` : undefined],
|
|
54
|
+
["AVM", data?.valuation?.value ? `$${Number(data.valuation.value).toLocaleString()}` : undefined],
|
|
55
|
+
["Tax Amount", data?.tax_amount ? `$${Number(data.tax_amount).toLocaleString()}` : undefined],
|
|
56
|
+
]),
|
|
57
|
+
"",
|
|
58
|
+
"### Last Sale",
|
|
59
|
+
kvSummary([
|
|
60
|
+
["Date", data?.last_sale?.date],
|
|
61
|
+
["Price", data?.last_sale?.price ? `$${Number(data.last_sale.price).toLocaleString()}` : undefined],
|
|
62
|
+
["Document Type", data?.last_sale?.document_type],
|
|
63
|
+
["Mortgage Amount", data?.last_sale?.mtg_amount ? `$${Number(data.last_sale.mtg_amount).toLocaleString()}` : undefined],
|
|
64
|
+
["Lender", data?.last_sale?.lender],
|
|
65
|
+
["Months Owned", data?.months_owned],
|
|
66
|
+
]),
|
|
67
|
+
"",
|
|
68
|
+
"### Owner",
|
|
69
|
+
kvSummary([
|
|
70
|
+
["Name", data?.owner?.name],
|
|
71
|
+
["Owner Occupied", data?.owner?.owner_occupied],
|
|
72
|
+
["Corporate", data?.owner?.corporate_owner],
|
|
73
|
+
["Owner Type", data?.owner_type],
|
|
74
|
+
["Purchase Method", data?.purchase_method],
|
|
75
|
+
]),
|
|
76
|
+
"",
|
|
77
|
+
"### Status",
|
|
78
|
+
kvSummary([
|
|
79
|
+
["Listing Status", data?.listing_status],
|
|
80
|
+
["HOA", data?.hoa],
|
|
81
|
+
["Vacant", data?.vacant],
|
|
82
|
+
["Pre-foreclosure", data?.pre_foreclosure?.flag],
|
|
83
|
+
]),
|
|
84
|
+
];
|
|
85
|
+
const actions = [
|
|
86
|
+
action("sfr_rental_comparables", "Estimate rental income", { address: fullAddr }),
|
|
87
|
+
action("sfr_property_comps", "Sales comps for valuation", { address: fullAddr }),
|
|
88
|
+
action("sfr_property_transactions", "Full ownership history", { address: fullAddr }),
|
|
89
|
+
action("plr_loans_nearby", "Private lending activity nearby", { propertyAddress: fullAddr }),
|
|
90
|
+
];
|
|
91
|
+
const structured = {
|
|
92
|
+
address: addr ? {
|
|
93
|
+
formatted_street_address: addr.formatted_street_address,
|
|
94
|
+
city: addr.city,
|
|
95
|
+
state: addr.state,
|
|
96
|
+
zip_code: addr.zip_code,
|
|
97
|
+
latitude: addr.latitude,
|
|
98
|
+
longitude: addr.longitude,
|
|
99
|
+
} : undefined,
|
|
100
|
+
county: data?.county,
|
|
101
|
+
msa: data?.msa,
|
|
102
|
+
structure: data?.structure ? {
|
|
103
|
+
beds_count: data.structure.beds_count,
|
|
104
|
+
baths: data.structure.baths,
|
|
105
|
+
total_area_sq_ft: data.structure.total_area_sq_ft ?? data.structure.living_area_sqft,
|
|
106
|
+
year_built: data.structure.year_built,
|
|
107
|
+
stories: data.structure.stories,
|
|
108
|
+
units_count: data.structure.units_count,
|
|
109
|
+
} : undefined,
|
|
110
|
+
property_type: data?.property_type ?? data?.property_class_description,
|
|
111
|
+
lot_sq_ft: data?.parcel?.area_sq_ft,
|
|
112
|
+
assessments: data?.assessments ? {
|
|
113
|
+
assessed_value: data.assessments.assessed_value,
|
|
114
|
+
land_value: data.assessments.land_value,
|
|
115
|
+
market_value: data.assessments.market_value,
|
|
116
|
+
} : undefined,
|
|
117
|
+
valuation: data?.valuation?.value != null ? { value: data.valuation.value } : undefined,
|
|
118
|
+
tax_amount: data?.tax_amount,
|
|
119
|
+
last_sale: data?.last_sale ? {
|
|
120
|
+
date: data.last_sale.date,
|
|
121
|
+
price: data.last_sale.price,
|
|
122
|
+
document_type: data.last_sale.document_type,
|
|
123
|
+
mtg_amount: data.last_sale.mtg_amount,
|
|
124
|
+
lender: data.last_sale.lender,
|
|
125
|
+
} : undefined,
|
|
126
|
+
months_owned: data?.months_owned,
|
|
127
|
+
owner: data?.owner ? {
|
|
128
|
+
name: data.owner.name,
|
|
129
|
+
owner_occupied: data.owner.owner_occupied,
|
|
130
|
+
corporate_owner: data.owner.corporate_owner,
|
|
131
|
+
} : undefined,
|
|
132
|
+
owner_type: data?.owner_type,
|
|
133
|
+
purchase_method: data?.purchase_method,
|
|
134
|
+
listing_status: data?.listing_status,
|
|
135
|
+
hoa: data?.hoa,
|
|
136
|
+
vacant: data?.vacant,
|
|
137
|
+
pre_foreclosure: data?.pre_foreclosure?.flag,
|
|
138
|
+
};
|
|
139
|
+
return structuredResult(lines.join("\n"), structured, actions);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=getProperty.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getProperty.js","sourceRoot":"","sources":["../../../src/tools/sfr/getProperty.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { structuredResult, markdownTable, truncatedJson } from "../formatters.js";
|
|
3
|
+
import { action, locationArgs } from "../nextActions.js";
|
|
4
|
+
import { registerToolSafe } from "../registerToolSafe.js";
|
|
5
|
+
import { resolveCanonicalMsa } from "./msaResolver.js";
|
|
6
|
+
const Input = z.object({
|
|
7
|
+
search_type: z.enum(["state", "city", "zip", "msa"]).describe("Location search type"),
|
|
8
|
+
state: z.string().optional().describe("State abbreviation, e.g. 'AZ'"),
|
|
9
|
+
city: z.string().optional().describe("City name (requires state param when search_type='city')"),
|
|
10
|
+
zips: z.string().optional().describe("Comma-separated zip codes"),
|
|
11
|
+
msa: z.string().optional().describe("MSA name"),
|
|
12
|
+
page: z.coerce.number().default(1).describe("Page number (default 1)"),
|
|
13
|
+
page_size: z.coerce.number().default(25).describe("Results per page (default 25, max 100)"),
|
|
14
|
+
});
|
|
15
|
+
export function registerInstitutionalOwnersTool(server, sfr) {
|
|
16
|
+
registerToolSafe(server, "sfr_institutional_owners", {
|
|
17
|
+
title: "Institutional REITs & funds owning SFR in area (SFR)",
|
|
18
|
+
description: "Find institutional SFR owners (REITs and private equity funds) in a location, " +
|
|
19
|
+
"ranked by portfolio size. Covers tracked entities like Invitation Homes, American Homes 4 Rent, " +
|
|
20
|
+
"Progress Residential, Tricon, FirstKey, etc. " +
|
|
21
|
+
"Different from sfr_top_buyers which shows ALL active buyers (individuals + institutions). " +
|
|
22
|
+
"Different from sfr_market_highlights which shows MSA-only buyer rankings by acquisition count. " +
|
|
23
|
+
"NOTE: search_type='city' requires state param (e.g. city='Phoenix', state='AZ'). " +
|
|
24
|
+
"Use sfr_buyer_profile to get full profile for any listed owner. " +
|
|
25
|
+
"CHAIN: To find where an institution is SELLING, use sfr_search_properties with seller_name filter (e.g. seller_name='INVITATION').",
|
|
26
|
+
inputSchema: Input,
|
|
27
|
+
}, async (args) => {
|
|
28
|
+
const query = { search_type: args.search_type };
|
|
29
|
+
if (args.state)
|
|
30
|
+
query.state = args.state;
|
|
31
|
+
if (args.city)
|
|
32
|
+
query.city = args.city;
|
|
33
|
+
if (args.zips)
|
|
34
|
+
query.zips = args.zips;
|
|
35
|
+
if (args.msa)
|
|
36
|
+
query.msa = args.msa;
|
|
37
|
+
query.page = args.page;
|
|
38
|
+
query.page_size = args.page_size;
|
|
39
|
+
let effectiveMsa = args.msa;
|
|
40
|
+
let resolutionMethod = "exact";
|
|
41
|
+
const data = await sfr.getInstitutionalLargestOwners(query);
|
|
42
|
+
let items = data?.data ?? (Array.isArray(data) ? data : []);
|
|
43
|
+
let total = data?.total ?? items.length;
|
|
44
|
+
if (items.length === 0 && args.search_type === "msa" && args.msa) {
|
|
45
|
+
resolutionMethod = "none";
|
|
46
|
+
const resolved = await resolveCanonicalMsa(sfr, { requestedMsa: args.msa });
|
|
47
|
+
if (resolved.resolvedMsa
|
|
48
|
+
&& resolved.resolvedMsa.toLowerCase() !== args.msa.trim().toLowerCase()) {
|
|
49
|
+
effectiveMsa = resolved.resolvedMsa;
|
|
50
|
+
const retryData = await sfr.getInstitutionalLargestOwners({ ...query, msa: effectiveMsa });
|
|
51
|
+
items = retryData?.data ?? (Array.isArray(retryData) ? retryData : []);
|
|
52
|
+
total = retryData?.total ?? items.length;
|
|
53
|
+
if (items.length > 0)
|
|
54
|
+
resolutionMethod = "fallback";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const loc = args.zips ?? args.city ?? (effectiveMsa ?? args.msa) ?? args.state ?? "All";
|
|
58
|
+
const lines = [
|
|
59
|
+
`## Institutional Owners — ${loc}`,
|
|
60
|
+
`**Total:** ${total}`,
|
|
61
|
+
"",
|
|
62
|
+
];
|
|
63
|
+
if (resolutionMethod === "fallback") {
|
|
64
|
+
lines.push(`_MSA normalized: '${args.msa}' -> '${effectiveMsa}'_`, "");
|
|
65
|
+
}
|
|
66
|
+
if (items.length > 0) {
|
|
67
|
+
const rows = items.slice(0, 25).map((o, i) => [
|
|
68
|
+
i + 1,
|
|
69
|
+
o.fundName ?? o.name ?? o.owner_name ?? "—",
|
|
70
|
+
o.totalHomes ?? o.properties ?? o.property_count ?? "—",
|
|
71
|
+
]);
|
|
72
|
+
lines.push(markdownTable(["#", "Owner", "Properties"], rows));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
lines.push("```json");
|
|
76
|
+
lines.push(truncatedJson(data, 3000));
|
|
77
|
+
lines.push("```");
|
|
78
|
+
}
|
|
79
|
+
const firstName = items[0]?.fundName ?? items[0]?.name ?? items[0]?.owner_name;
|
|
80
|
+
const actions = [
|
|
81
|
+
...(firstName ? [action("sfr_buyer_profile", "Profile top institutional owner", { name: firstName })] : []),
|
|
82
|
+
...(args.zips ? [action("sfr_zip_detail", "Deep-dive zip metrics", { zipCode: args.zips.split(",")[0] })] : []),
|
|
83
|
+
action("sfr_top_buyers", "All buyers (not just institutional)", locationArgs(args)),
|
|
84
|
+
];
|
|
85
|
+
return structuredResult(lines.join("\n"), { items, total, page: args.page }, actions);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=institutionalOwners.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"institutionalOwners.js","sourceRoot":"","sources":["../../../src/tools/sfr/institutionalOwners.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;IACrB,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,sBAAsB,CAAC;IACrF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;IACtE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0DAA0D,CAAC;IAChG,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;IACjE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC/C,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,yBAAyB,CAAC;IACtE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,wCAAwC,CAAC;CAC5F,CAAC,CAAC;AAEH,MAAM,UAAU,+BAA+B,CAAC,MAAiB,EAAE,GAAc;IAC/E,gBAAgB,CAAC,MAAM,EACrB,0BAA0B,EAC1B;QACE,KAAK,EAAE,sDAAsD;QAC7D,WAAW,EACT,gFAAgF;YAChF,kGAAkG;YAClG,+CAA+C;YAC/C,4FAA4F;YAC5F,iGAAiG;YACjG,mFAAmF;YACnF,kEAAkE;YAClE,oIAAoI;QACtI,WAAW,EAAE,KAAK;KACnB,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACX,MAAM,KAAK,GAA4B,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC;QACzE,IAAI,IAAI,CAAC,KAAK;YAAE,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzC,IAAI,IAAI,CAAC,IAAI;YAAE,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtC,IAAI,IAAI,CAAC,IAAI;YAAE,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtC,IAAI,IAAI,CAAC,GAAG;YAAE,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;QACnC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAEjC,IAAI,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC;QAC5B,IAAI,gBAAgB,GAAkC,OAAO,CAAC;QAE9D,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,6BAA6B,CAAC,KAAK,CAAQ,CAAC;QACnE,IAAI,KAAK,GAAG,IAAI,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5D,IAAI,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;QAExC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,WAAW,KAAK,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACjE,gBAAgB,GAAG,MAAM,CAAC;YAC1B,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YAC5E,IACE,QAAQ,CAAC,WAAW;mBACjB,QAAQ,CAAC,WAAW,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EACvE,CAAC;gBACD,YAAY,GAAG,QAAQ,CAAC,WAAW,CAAC;gBACpC,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,6BAA6B,CAAC,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,CAAQ,CAAC;gBAClG,KAAK,GAAG,SAAS,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACvE,KAAK,GAAG,SAAS,EAAE,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;gBACzC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;oBAAE,gBAAgB,GAAG,UAAU,CAAC;YACtD,CAAC;QACH,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC;QACxF,MAAM,KAAK,GAAG;YACZ,6BAA6B,GAAG,EAAE;YAClC,cAAc,KAAK,EAAE;YACrB,EAAE;SACH,CAAC;QACF,IAAI,gBAAgB,KAAK,UAAU,EAAE,CAAC;YACpC,KAAK,CAAC,IAAI,CAAC,qBAAqB,IAAI,CAAC,GAAG,SAAS,YAAY,IAAI,EAAE,EAAE,CAAC,CAAC;QACzE,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,CAAS,EAAE,EAAE,CAAC;gBACzD,CAAC,GAAG,CAAC;gBACL,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,IAAI,GAAG;gBAC3C,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,cAAc,IAAI,GAAG;aACxD,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QAChE,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtB,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;YACtC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;QAED,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC;QAC/E,MAAM,OAAO,GAAG;YACd,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,mBAAmB,EAAE,iCAAiC,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3G,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,EAAE,uBAAuB,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/G,MAAM,CAAC,gBAAgB,EAAE,qCAAqC,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;SACpF,CAAC;QACF,OAAO,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IAC1F,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { structuredResult, markdownTable } from "../formatters.js";
|
|
3
|
+
import { action } from "../nextActions.js";
|
|
4
|
+
import { registerToolSafe } from "../registerToolSafe.js";
|
|
5
|
+
import { autoFillDateRange } from "../dateHelper.js";
|
|
6
|
+
import { resolveCanonicalMsa } from "./msaResolver.js";
|
|
7
|
+
const Input = z.object({
|
|
8
|
+
msa: z.string().describe("MSA name, e.g. 'Phoenix-Mesa-Chandler, AZ'"),
|
|
9
|
+
sales_date_min: z.string().optional().describe("Start date, e.g. '2024-01-01'"),
|
|
10
|
+
sales_date_max: z.string().optional().describe("End date"),
|
|
11
|
+
is_cash_buyer: z.boolean().optional().describe("Filter for cash purchases only"),
|
|
12
|
+
seller_name: z.string().optional().describe("Filter by seller name (partial match)"),
|
|
13
|
+
page: z.coerce.number().default(1).describe("Page number (default 1)"),
|
|
14
|
+
page_size: z.coerce.number().default(20).describe("Results per page (default 20, max 100)"),
|
|
15
|
+
});
|
|
16
|
+
export function registerInvestorActivityTool(server, sfr) {
|
|
17
|
+
registerToolSafe(server, "sfr_investor_activity", {
|
|
18
|
+
title: "Browse investor transactions in MSA — with portfolio size (SFR)",
|
|
19
|
+
description: "Browse individual investor PURCHASE transactions in an MSA market. " +
|
|
20
|
+
"Best for BUYER analysis: shows cash vs financed, corporate vs individual, and buyer's total portfolio size. " +
|
|
21
|
+
"Each transaction includes isCashBuyer flag — use is_cash_buyer=true to find cash buyer prospects. " +
|
|
22
|
+
"Also supports seller_name filter, but for 'where is [institution] selling?' queries prefer sfr_search_properties which has broader date coverage. " +
|
|
23
|
+
"To calculate yields on purchases, chain with sfr_rental_stats by zip: yield = (annual rent / purchase price). " +
|
|
24
|
+
"Requires an MSA name. For non-MSA transaction searches, use sfr_search_properties. " +
|
|
25
|
+
"See sfr_buyer_profile for full profiles and sfr_flip_activity for flip stats.",
|
|
26
|
+
inputSchema: Input,
|
|
27
|
+
}, async (args) => {
|
|
28
|
+
const query = {
|
|
29
|
+
search_type: "msa",
|
|
30
|
+
msa: args.msa,
|
|
31
|
+
page: args.page,
|
|
32
|
+
page_size: args.page_size,
|
|
33
|
+
};
|
|
34
|
+
if (args.sales_date_min)
|
|
35
|
+
query.sales_date_min = args.sales_date_min;
|
|
36
|
+
if (args.sales_date_max)
|
|
37
|
+
query.sales_date_max = args.sales_date_max;
|
|
38
|
+
if (args.is_cash_buyer !== undefined)
|
|
39
|
+
query.is_cash_buyer = args.is_cash_buyer;
|
|
40
|
+
if (args.seller_name)
|
|
41
|
+
query.seller_name = args.seller_name;
|
|
42
|
+
autoFillDateRange(query);
|
|
43
|
+
let effectiveMsa = args.msa;
|
|
44
|
+
let resolutionMethod = "exact";
|
|
45
|
+
const data = await sfr.getBuyerActivity(query);
|
|
46
|
+
let items = Array.isArray(data) ? data : data?.data ?? [];
|
|
47
|
+
if (items.length === 0) {
|
|
48
|
+
resolutionMethod = "none";
|
|
49
|
+
const resolved = await resolveCanonicalMsa(sfr, {
|
|
50
|
+
requestedMsa: args.msa,
|
|
51
|
+
salesDateMin: typeof query.sales_date_min === "string" ? query.sales_date_min : undefined,
|
|
52
|
+
salesDateMax: typeof query.sales_date_max === "string" ? query.sales_date_max : undefined,
|
|
53
|
+
});
|
|
54
|
+
if (resolved.resolvedMsa
|
|
55
|
+
&& resolved.resolvedMsa.toLowerCase() !== args.msa.trim().toLowerCase()) {
|
|
56
|
+
effectiveMsa = resolved.resolvedMsa;
|
|
57
|
+
const retryQuery = { ...query, msa: effectiveMsa };
|
|
58
|
+
const retryData = await sfr.getBuyerActivity(retryQuery);
|
|
59
|
+
items = Array.isArray(retryData) ? retryData : retryData?.data ?? [];
|
|
60
|
+
if (items.length > 0)
|
|
61
|
+
resolutionMethod = "fallback";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const loc = effectiveMsa;
|
|
65
|
+
const lines = [
|
|
66
|
+
`## Market Investor Activity — ${loc}`,
|
|
67
|
+
`**Showing:** ${items.length} transactions`,
|
|
68
|
+
"",
|
|
69
|
+
];
|
|
70
|
+
if (resolutionMethod === "fallback") {
|
|
71
|
+
lines.push(`_MSA normalized: '${args.msa}' -> '${effectiveMsa}'_`, "");
|
|
72
|
+
}
|
|
73
|
+
if (items.length > 0) {
|
|
74
|
+
const fmt$ = (v) => v != null ? `$${Number(v).toLocaleString()}` : "—";
|
|
75
|
+
const rows = items.slice(0, 25).map((t) => [
|
|
76
|
+
t.buyerName ?? "—",
|
|
77
|
+
t.address ? `${t.address}, ${t.city ?? ""}` : "—",
|
|
78
|
+
fmt$(t.saleValue),
|
|
79
|
+
t.isCashBuyer ? "Cash" : "Financed",
|
|
80
|
+
t.isCorporate ? "Corp" : "Individual",
|
|
81
|
+
t.buyerPropertiesCount ?? "—",
|
|
82
|
+
]);
|
|
83
|
+
lines.push(markdownTable(["Buyer", "Address", "Price", "Type", "Entity", "Portfolio"], rows));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
lines.push("_No transactions found for this market._ " +
|
|
87
|
+
"Try widening the date range" +
|
|
88
|
+
(args.seller_name ? ` or using a shorter seller name (partial match on '${args.seller_name}')` : "") +
|
|
89
|
+
(args.is_cash_buyer ? " or removing the cash buyer filter" : "") +
|
|
90
|
+
".");
|
|
91
|
+
}
|
|
92
|
+
// Slim items — raw API has 30+ fields, many null (exit, profit, URL slugs, lat/lng)
|
|
93
|
+
const slimItems = items.slice(0, 25).map((t) => ({
|
|
94
|
+
buyerName: t.buyerName,
|
|
95
|
+
address: t.address,
|
|
96
|
+
city: t.city,
|
|
97
|
+
zip: t.zipCode,
|
|
98
|
+
state: t.state,
|
|
99
|
+
propertyType: t.propertyType,
|
|
100
|
+
saleValue: t.saleValue,
|
|
101
|
+
saleDate: t.saleDate,
|
|
102
|
+
avmValue: t.avmValue || undefined,
|
|
103
|
+
isCashBuyer: t.isCashBuyer || undefined,
|
|
104
|
+
isCorporate: t.isCorporate || undefined,
|
|
105
|
+
isDiscountedPurchase: t.isDiscountedPurchase || undefined,
|
|
106
|
+
loanAmount: t.loanAmount || undefined,
|
|
107
|
+
lenderName: t.lenderName || undefined,
|
|
108
|
+
buyerPropertiesCount: t.buyerPropertiesCount || undefined,
|
|
109
|
+
buyerTransactionsCount: t.buyerTransactionsCount || undefined,
|
|
110
|
+
sqft: t.buildingArea || undefined,
|
|
111
|
+
beds: t.bedrooms || undefined,
|
|
112
|
+
}));
|
|
113
|
+
const firstBuyer = items[0]?.buyerName;
|
|
114
|
+
const firstAddr = items[0] ? `${items[0].address}, ${items[0].city}, ${items[0].state} ${items[0].zipCode}` : undefined;
|
|
115
|
+
const actions = [
|
|
116
|
+
...(firstBuyer ? [action("sfr_buyer_profile", "Profile top investor", { name: firstBuyer })] : []),
|
|
117
|
+
...(firstAddr ? [action("sfr_get_property", "Details on first property", { address: firstAddr })] : []),
|
|
118
|
+
action("sfr_search_properties", "Search with more filters", { search_type: "msa", msa: effectiveMsa }),
|
|
119
|
+
];
|
|
120
|
+
return structuredResult(lines.join("\n"), {
|
|
121
|
+
items: slimItems,
|
|
122
|
+
total: items.length,
|
|
123
|
+
page: args.page,
|
|
124
|
+
requested_msa: args.msa,
|
|
125
|
+
resolved_msa: effectiveMsa,
|
|
126
|
+
msa_resolution_method: resolutionMethod,
|
|
127
|
+
}, actions);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=investorActivity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"investorActivity.js","sourceRoot":"","sources":["../../../src/tools/sfr/investorActivity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { structuredResult, markdownTable } from "../formatters.js";
|
|
3
|
+
import { action } from "../nextActions.js";
|
|
4
|
+
import { registerToolSafe } from "../registerToolSafe.js";
|
|
5
|
+
import { autoFillDateRange } from "../dateHelper.js";
|
|
6
|
+
import { resolveCanonicalMsa } from "./msaResolver.js";
|
|
7
|
+
const Input = z.object({
|
|
8
|
+
msa: z.string().describe("MSA name, e.g. 'Phoenix-Mesa-Chandler, AZ'"),
|
|
9
|
+
city: z.string().optional().describe("Optional city filter within the MSA"),
|
|
10
|
+
sales_date_min: z.string().optional().describe("Start date, e.g. '2024-01-01'"),
|
|
11
|
+
sales_date_max: z.string().optional().describe("End date"),
|
|
12
|
+
page_size: z.coerce.number().default(25).describe("Results per page (default 25, max 100)"),
|
|
13
|
+
});
|
|
14
|
+
export function registerMarketHighlightsTool(server, sfr) {
|
|
15
|
+
registerToolSafe(server, "sfr_market_highlights", {
|
|
16
|
+
title: "Top buyers in MSA market (SFR)",
|
|
17
|
+
description: "Rank buyers in an MSA market by acquisition count. Requires an MSA name. " +
|
|
18
|
+
"Supports optional city filter and date range within the MSA. " +
|
|
19
|
+
"Different from sfr_top_buyers which accepts any geography (state/city/zip/MSA). " +
|
|
20
|
+
"Different from sfr_institutional_owners which shows only institutional REITs/funds. " +
|
|
21
|
+
"Use sfr_buyer_profile to drill into any buyer.",
|
|
22
|
+
inputSchema: Input,
|
|
23
|
+
}, async (args) => {
|
|
24
|
+
const query = { msa: args.msa };
|
|
25
|
+
if (args.city)
|
|
26
|
+
query.city = args.city;
|
|
27
|
+
if (args.sales_date_min)
|
|
28
|
+
query.sales_date_min = args.sales_date_min;
|
|
29
|
+
if (args.sales_date_max)
|
|
30
|
+
query.sales_date_max = args.sales_date_max;
|
|
31
|
+
if (args.page_size)
|
|
32
|
+
query.page_size = args.page_size;
|
|
33
|
+
autoFillDateRange(query);
|
|
34
|
+
let effectiveMsa = args.msa;
|
|
35
|
+
let resolutionMethod = "exact";
|
|
36
|
+
const data = await sfr.getMarketTopBuyers(query);
|
|
37
|
+
// API ignores page_size param — enforce it client-side
|
|
38
|
+
let rawBuyers = Array.isArray(data) ? data : data?.data ?? [];
|
|
39
|
+
if (rawBuyers.length === 0) {
|
|
40
|
+
resolutionMethod = "none";
|
|
41
|
+
const resolved = await resolveCanonicalMsa(sfr, {
|
|
42
|
+
requestedMsa: args.msa,
|
|
43
|
+
salesDateMin: typeof query.sales_date_min === "string" ? query.sales_date_min : undefined,
|
|
44
|
+
salesDateMax: typeof query.sales_date_max === "string" ? query.sales_date_max : undefined,
|
|
45
|
+
});
|
|
46
|
+
if (resolved.resolvedMsa
|
|
47
|
+
&& resolved.resolvedMsa.toLowerCase() !== args.msa.trim().toLowerCase()) {
|
|
48
|
+
effectiveMsa = resolved.resolvedMsa;
|
|
49
|
+
const retryQuery = { ...query, msa: effectiveMsa };
|
|
50
|
+
const retryData = await sfr.getMarketTopBuyers(retryQuery);
|
|
51
|
+
rawBuyers = Array.isArray(retryData) ? retryData : retryData?.data ?? [];
|
|
52
|
+
if (rawBuyers.length > 0)
|
|
53
|
+
resolutionMethod = "fallback";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const buyers = rawBuyers.slice(0, args.page_size);
|
|
57
|
+
const lines = [
|
|
58
|
+
`## Market Top Buyers — ${effectiveMsa}${args.city ? ` (${args.city})` : ""}`,
|
|
59
|
+
`**Showing:** ${buyers.length} buyers`,
|
|
60
|
+
"",
|
|
61
|
+
];
|
|
62
|
+
if (resolutionMethod === "fallback") {
|
|
63
|
+
lines.push(`_MSA normalized: '${args.msa}' -> '${effectiveMsa}'_`, "");
|
|
64
|
+
}
|
|
65
|
+
if (buyers.length > 0) {
|
|
66
|
+
const rows = buyers.slice(0, 30).map((b, i) => [
|
|
67
|
+
i + 1,
|
|
68
|
+
b.name ?? "—",
|
|
69
|
+
b.acquisitions ?? b.count ?? "—",
|
|
70
|
+
b.hasContactInfo ? "Yes" : "No",
|
|
71
|
+
]);
|
|
72
|
+
lines.push(markdownTable(["#", "Buyer", "Acquisitions", "Contact Info"], rows));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
lines.push("_No buyers found for this market._");
|
|
76
|
+
}
|
|
77
|
+
// Slim buyers — strip formattedName URL slugs and unnecessary metadata
|
|
78
|
+
const slimBuyers = buyers.slice(0, 50).map((b) => ({
|
|
79
|
+
name: b.name ?? b.buyer_name,
|
|
80
|
+
acquisitions: b.acquisitions ?? b.count,
|
|
81
|
+
hasProfile: b.hasProfilePage || undefined,
|
|
82
|
+
hasContact: b.hasContactInfo || undefined,
|
|
83
|
+
}));
|
|
84
|
+
const firstName = buyers[0]?.name ?? buyers[0]?.buyer_name;
|
|
85
|
+
const actions = [
|
|
86
|
+
...(firstName ? [action("sfr_buyer_profile", "Profile top buyer", { name: firstName })] : []),
|
|
87
|
+
action("sfr_top_buyers", "Full buyer list with profiles", { search_type: "msa", msa: effectiveMsa }),
|
|
88
|
+
action("sfr_search_properties", "Browse individual transactions", { search_type: "msa", msa: effectiveMsa }),
|
|
89
|
+
];
|
|
90
|
+
return structuredResult(lines.join("\n"), {
|
|
91
|
+
items: slimBuyers,
|
|
92
|
+
total: buyers.length,
|
|
93
|
+
page: 1,
|
|
94
|
+
requested_msa: args.msa,
|
|
95
|
+
resolved_msa: effectiveMsa,
|
|
96
|
+
msa_resolution_method: resolutionMethod,
|
|
97
|
+
}, actions);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=marketHighlights.js.map
|