fdic-mcp-server 1.25.2 → 1.27.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/dist/index.js +195 -13
- package/dist/server.js +195 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
|
32
32
|
var import_express2 = __toESM(require("express"));
|
|
33
33
|
|
|
34
34
|
// src/constants.ts
|
|
35
|
-
var VERSION = true ? "1.
|
|
35
|
+
var VERSION = true ? "1.27.0" : process.env.npm_package_version ?? "0.0.0-dev";
|
|
36
36
|
var FDIC_API_BASE_URL = "https://banks.data.fdic.gov/api";
|
|
37
37
|
var CHARACTER_LIMIT = 5e4;
|
|
38
38
|
var DEFAULT_FDIC_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
@@ -32043,6 +32043,99 @@ var FdicBankDeepDiveOutputSchema = import_zod2.z.object({
|
|
|
32043
32043
|
warnings: import_zod2.z.array(import_zod2.z.string()),
|
|
32044
32044
|
sources: import_zod2.z.array(Source)
|
|
32045
32045
|
});
|
|
32046
|
+
var PeerStatsSchema = import_zod2.z.object({
|
|
32047
|
+
peer_count: import_zod2.z.number().int(),
|
|
32048
|
+
peer_median: import_zod2.z.number(),
|
|
32049
|
+
peer_mean: import_zod2.z.number(),
|
|
32050
|
+
subject_value: import_zod2.z.number(),
|
|
32051
|
+
subject_percentile: import_zod2.z.number(),
|
|
32052
|
+
robust_z_score: import_zod2.z.number(),
|
|
32053
|
+
is_outlier: import_zod2.z.boolean(),
|
|
32054
|
+
outlier_direction: import_zod2.z.enum(["high", "low"]).optional()
|
|
32055
|
+
});
|
|
32056
|
+
var PeerHealthMetricRowSchema = import_zod2.z.object({
|
|
32057
|
+
name: import_zod2.z.string(),
|
|
32058
|
+
label: import_zod2.z.string(),
|
|
32059
|
+
subject: import_zod2.z.number().nullable(),
|
|
32060
|
+
peer_median: import_zod2.z.number().nullable(),
|
|
32061
|
+
peer_weighted_avg: import_zod2.z.number().nullable(),
|
|
32062
|
+
percentile: import_zod2.z.number().nullable(),
|
|
32063
|
+
higher_is_better: import_zod2.z.boolean(),
|
|
32064
|
+
is_outlier: import_zod2.z.boolean(),
|
|
32065
|
+
outlier_direction: import_zod2.z.enum(["high", "low"]).nullable()
|
|
32066
|
+
});
|
|
32067
|
+
var PeerHealthInstitutionSchema = import_zod2.z.object({
|
|
32068
|
+
cert: import_zod2.z.number().int(),
|
|
32069
|
+
name: import_zod2.z.string(),
|
|
32070
|
+
name_source: import_zod2.z.enum(["fdic_institution_profile", "cert_fallback"]),
|
|
32071
|
+
city: import_zod2.z.string().nullable(),
|
|
32072
|
+
state: import_zod2.z.string().nullable(),
|
|
32073
|
+
total_assets: import_zod2.z.number().nullable(),
|
|
32074
|
+
proxy_score: import_zod2.z.number(),
|
|
32075
|
+
proxy_band: import_zod2.z.string(),
|
|
32076
|
+
composite_rating: import_zod2.z.number(),
|
|
32077
|
+
composite_label: import_zod2.z.string(),
|
|
32078
|
+
component_ratings: import_zod2.z.record(import_zod2.z.number()),
|
|
32079
|
+
flags: import_zod2.z.array(import_zod2.z.string())
|
|
32080
|
+
});
|
|
32081
|
+
var PeerHealthProxySummarySchema = import_zod2.z.object({
|
|
32082
|
+
model: import_zod2.z.literal("public_camels_proxy_v1"),
|
|
32083
|
+
official_status: import_zod2.z.literal("public off-site proxy, not official CAMELS"),
|
|
32084
|
+
score: import_zod2.z.number(),
|
|
32085
|
+
band: import_zod2.z.string(),
|
|
32086
|
+
components: import_zod2.z.array(
|
|
32087
|
+
import_zod2.z.object({
|
|
32088
|
+
name: import_zod2.z.string(),
|
|
32089
|
+
label: import_zod2.z.string(),
|
|
32090
|
+
score: import_zod2.z.number(),
|
|
32091
|
+
legacy_rating: import_zod2.z.number(),
|
|
32092
|
+
legacy_label: import_zod2.z.string(),
|
|
32093
|
+
flags: import_zod2.z.array(import_zod2.z.string())
|
|
32094
|
+
})
|
|
32095
|
+
),
|
|
32096
|
+
capital_classification: import_zod2.z.object({
|
|
32097
|
+
category: import_zod2.z.string(),
|
|
32098
|
+
label: import_zod2.z.string(),
|
|
32099
|
+
binding_constraint: import_zod2.z.string().nullable(),
|
|
32100
|
+
ratios_used: import_zod2.z.record(import_zod2.z.number().nullable())
|
|
32101
|
+
}),
|
|
32102
|
+
management_overlay: import_zod2.z.object({
|
|
32103
|
+
level: import_zod2.z.string(),
|
|
32104
|
+
caps_band: import_zod2.z.boolean(),
|
|
32105
|
+
reason_codes: import_zod2.z.array(import_zod2.z.string())
|
|
32106
|
+
}),
|
|
32107
|
+
risk_signal_count: import_zod2.z.number().int(),
|
|
32108
|
+
risk_signal_severities: import_zod2.z.record(import_zod2.z.number().int()),
|
|
32109
|
+
trend_count: import_zod2.z.number().int(),
|
|
32110
|
+
data_quality: import_zod2.z.object({
|
|
32111
|
+
report_date: import_zod2.z.string(),
|
|
32112
|
+
staleness: import_zod2.z.string(),
|
|
32113
|
+
gaps_count: import_zod2.z.number().int(),
|
|
32114
|
+
gaps: import_zod2.z.array(import_zod2.z.string())
|
|
32115
|
+
})
|
|
32116
|
+
});
|
|
32117
|
+
var FdicPeerHealthOutputSchema = import_zod2.z.object({
|
|
32118
|
+
model: import_zod2.z.literal("public_camels_proxy_v1"),
|
|
32119
|
+
official_status: import_zod2.z.literal("public off-site proxy, not official CAMELS"),
|
|
32120
|
+
proxy: import_zod2.z.unknown().nullable(),
|
|
32121
|
+
proxy_summary: PeerHealthProxySummarySchema.nullable(),
|
|
32122
|
+
report_date: import_zod2.z.string(),
|
|
32123
|
+
sort_by: import_zod2.z.string(),
|
|
32124
|
+
total_institutions: import_zod2.z.number().int(),
|
|
32125
|
+
returned_count: import_zod2.z.number().int(),
|
|
32126
|
+
subject_cert: import_zod2.z.number().int().nullable(),
|
|
32127
|
+
subject_rank: import_zod2.z.number().int().nullable(),
|
|
32128
|
+
metrics: import_zod2.z.array(PeerHealthMetricRowSchema),
|
|
32129
|
+
institutions: import_zod2.z.array(PeerHealthInstitutionSchema),
|
|
32130
|
+
peer_context: import_zod2.z.object({
|
|
32131
|
+
peer_count: import_zod2.z.number().int(),
|
|
32132
|
+
peer_definition: import_zod2.z.string(),
|
|
32133
|
+
broadening_steps: import_zod2.z.array(import_zod2.z.string()),
|
|
32134
|
+
subject_rank: import_zod2.z.number().int().nullable(),
|
|
32135
|
+
subject_percentiles: import_zod2.z.record(PeerStatsSchema),
|
|
32136
|
+
weighted_peer_averages: import_zod2.z.record(import_zod2.z.number())
|
|
32137
|
+
}).nullable()
|
|
32138
|
+
}).passthrough();
|
|
32046
32139
|
var FdicAnalysisOutputSchema = import_zod2.z.object({}).passthrough();
|
|
32047
32140
|
|
|
32048
32141
|
// src/tools/institutions.ts
|
|
@@ -35819,6 +35912,58 @@ function computePeerStats(subjectValue, peerValues, options) {
|
|
|
35819
35912
|
}
|
|
35820
35913
|
|
|
35821
35914
|
// src/tools/peerHealth.ts
|
|
35915
|
+
var PEER_METRICS = [
|
|
35916
|
+
// legacyKey preserves the original camelCase peer_context map keys for backward compatibility.
|
|
35917
|
+
// New UI consumers should bind to the flat metrics[].name snake_case values instead.
|
|
35918
|
+
{ key: "roaPct", legacyKey: "roaPct", name: "roa_pct", label: "Return on assets", higherIsBetter: true },
|
|
35919
|
+
{ key: "equityCapitalRatioPct", legacyKey: "equityCapitalRatioPct", name: "equity_capital_ratio_pct", label: "Equity capital ratio", higherIsBetter: true },
|
|
35920
|
+
{ key: "netInterestMarginPct", legacyKey: "netInterestMarginPct", name: "net_interest_margin_pct", label: "Net interest margin", higherIsBetter: true },
|
|
35921
|
+
{ key: "efficiencyRatioPct", legacyKey: "efficiencyRatioPct", name: "efficiency_ratio_pct", label: "Efficiency ratio", higherIsBetter: false },
|
|
35922
|
+
{ key: "loanToDepositPct", legacyKey: "loanToDepositPct", name: "loan_to_deposit_pct", label: "Loan-to-deposit ratio", higherIsBetter: false }
|
|
35923
|
+
];
|
|
35924
|
+
function buildProxySummary(proxy) {
|
|
35925
|
+
if (!proxy) return null;
|
|
35926
|
+
const componentEntries = [
|
|
35927
|
+
{ name: "capital", assessment: proxy.component_assessment.capital },
|
|
35928
|
+
{ name: "asset_quality", assessment: proxy.component_assessment.asset_quality },
|
|
35929
|
+
{ name: "earnings", assessment: proxy.component_assessment.earnings },
|
|
35930
|
+
{ name: "liquidity_funding", assessment: proxy.component_assessment.liquidity_funding },
|
|
35931
|
+
{ name: "sensitivity_proxy", assessment: proxy.component_assessment.sensitivity_proxy }
|
|
35932
|
+
];
|
|
35933
|
+
const riskSignalSeverities = {};
|
|
35934
|
+
for (const signal of proxy.risk_signals) {
|
|
35935
|
+
riskSignalSeverities[signal.severity] = (riskSignalSeverities[signal.severity] ?? 0) + 1;
|
|
35936
|
+
}
|
|
35937
|
+
return {
|
|
35938
|
+
model: proxy.model,
|
|
35939
|
+
official_status: proxy.official_status,
|
|
35940
|
+
score: proxy.overall.score,
|
|
35941
|
+
band: proxy.overall.band,
|
|
35942
|
+
components: componentEntries.map(({ name, assessment }) => ({
|
|
35943
|
+
name,
|
|
35944
|
+
label: assessment.label,
|
|
35945
|
+
score: assessment.score,
|
|
35946
|
+
legacy_rating: assessment.legacy_rating,
|
|
35947
|
+
legacy_label: assessment.legacy_label,
|
|
35948
|
+
flags: assessment.flags
|
|
35949
|
+
})),
|
|
35950
|
+
capital_classification: {
|
|
35951
|
+
category: proxy.capital_classification.category,
|
|
35952
|
+
label: proxy.capital_classification.label,
|
|
35953
|
+
binding_constraint: proxy.capital_classification.binding_constraint ?? null,
|
|
35954
|
+
ratios_used: proxy.capital_classification.ratios_used
|
|
35955
|
+
},
|
|
35956
|
+
management_overlay: {
|
|
35957
|
+
level: proxy.management_overlay.level,
|
|
35958
|
+
caps_band: proxy.management_overlay.caps_band,
|
|
35959
|
+
reason_codes: proxy.management_overlay.reason_codes
|
|
35960
|
+
},
|
|
35961
|
+
risk_signal_count: proxy.risk_signals.length,
|
|
35962
|
+
risk_signal_severities: riskSignalSeverities,
|
|
35963
|
+
trend_count: proxy.trend_insights.length,
|
|
35964
|
+
data_quality: proxy.data_quality
|
|
35965
|
+
};
|
|
35966
|
+
}
|
|
35822
35967
|
var PeerHealthInputSchema = import_zod11.z.object({
|
|
35823
35968
|
cert: import_zod11.z.number().int().positive().optional().describe("Subject institution CERT to highlight in the ranking. Optional."),
|
|
35824
35969
|
certs: import_zod11.z.array(import_zod11.z.number().int().positive()).max(50).optional().describe("Explicit list of CERTs to compare (max 50)."),
|
|
@@ -35853,11 +35998,11 @@ Three usage modes:
|
|
|
35853
35998
|
|
|
35854
35999
|
Optionally provide cert to highlight a subject institution's position in the ranking.
|
|
35855
36000
|
|
|
35856
|
-
Output:
|
|
36001
|
+
Output: structuredContent includes {model, official_status, report_date, institutions, metrics, peer_context, proxy_summary, proxy}. Institutions include proxy scores and name_source. When a subject cert is provided, metrics is a flat subject-vs-peer array and proxy_summary is a flattened subject proxy for UI binding while peer_context and proxy preserve the legacy detailed payloads. Auto-peer selection derives asset bands from report-date financials and broadens the cohort if fewer than 10 peers match.
|
|
35857
36002
|
|
|
35858
36003
|
NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`,
|
|
35859
36004
|
inputSchema: PeerHealthInputSchema,
|
|
35860
|
-
outputSchema:
|
|
36005
|
+
outputSchema: FdicPeerHealthOutputSchema,
|
|
35861
36006
|
annotations: {
|
|
35862
36007
|
readOnlyHint: true,
|
|
35863
36008
|
destructiveHint: false,
|
|
@@ -36031,6 +36176,33 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36031
36176
|
const c = asNumber(r.CERT);
|
|
36032
36177
|
if (c !== null) profileMap.set(c, r);
|
|
36033
36178
|
}
|
|
36179
|
+
const financialCerts = allFinancials.map((r) => asNumber(r.CERT)).filter((cert) => cert !== null);
|
|
36180
|
+
const missingProfileCerts = financialCerts.filter((cert) => {
|
|
36181
|
+
const profile = profileMap.get(cert);
|
|
36182
|
+
return !profile || typeof profile.NAME !== "string" || profile.NAME.length === 0;
|
|
36183
|
+
});
|
|
36184
|
+
if (missingProfileCerts.length > 0) {
|
|
36185
|
+
const missingProfileFilters = buildCertFilters(missingProfileCerts);
|
|
36186
|
+
const missingProfileResponses = await mapWithConcurrency(
|
|
36187
|
+
missingProfileFilters,
|
|
36188
|
+
MAX_CONCURRENCY,
|
|
36189
|
+
async (certFilter) => queryEndpoint(
|
|
36190
|
+
ENDPOINTS.INSTITUTIONS,
|
|
36191
|
+
{
|
|
36192
|
+
filters: certFilter,
|
|
36193
|
+
fields: "CERT,NAME,CITY,STALP",
|
|
36194
|
+
limit: 1e4,
|
|
36195
|
+
sort_by: "CERT",
|
|
36196
|
+
sort_order: "ASC"
|
|
36197
|
+
},
|
|
36198
|
+
{ signal: controller.signal }
|
|
36199
|
+
)
|
|
36200
|
+
);
|
|
36201
|
+
for (const r of missingProfileResponses.flatMap(extractRecords)) {
|
|
36202
|
+
const c = asNumber(r.CERT);
|
|
36203
|
+
if (c !== null) profileMap.set(c, r);
|
|
36204
|
+
}
|
|
36205
|
+
}
|
|
36034
36206
|
await sendProgressNotification(server.server, progressToken, 0.7, "Computing proxy assessments");
|
|
36035
36207
|
const subjectHistory = params.cert ? await fetchHistoryEvents(params.cert, { signal: controller.signal, repdte: params.repdte }) : [];
|
|
36036
36208
|
let subjectProxy = null;
|
|
@@ -36069,7 +36241,8 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36069
36241
|
];
|
|
36070
36242
|
entries.push({
|
|
36071
36243
|
cert,
|
|
36072
|
-
name:
|
|
36244
|
+
name: typeof profile?.NAME === "string" && profile.NAME.length > 0 ? profile.NAME : `CERT ${cert}`,
|
|
36245
|
+
name_source: typeof profile?.NAME === "string" && profile.NAME.length > 0 ? "fdic_institution_profile" : "cert_fallback",
|
|
36073
36246
|
city: profile?.CITY ? String(profile.CITY) : null,
|
|
36074
36247
|
state: profile?.STALP ? String(profile.STALP) : null,
|
|
36075
36248
|
total_assets: asNumber(fin.ASSET),
|
|
@@ -36094,18 +36267,12 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36094
36267
|
});
|
|
36095
36268
|
const subjectRank = params.cert ? entries.findIndex((e) => e.cert === params.cert) + 1 : null;
|
|
36096
36269
|
let peerContext = null;
|
|
36270
|
+
let metricRows = [];
|
|
36097
36271
|
if (params.cert) {
|
|
36098
36272
|
const subjectFin = allFinancials.find((f) => asNumber(f.CERT) === params.cert);
|
|
36099
36273
|
if (subjectFin) {
|
|
36100
36274
|
const subjectExtraction = extractCanonicalMetrics(subjectFin);
|
|
36101
36275
|
const sm = subjectExtraction.metrics;
|
|
36102
|
-
const PEER_METRICS = [
|
|
36103
|
-
{ key: "roaPct", label: "roaPct", higherIsBetter: true },
|
|
36104
|
-
{ key: "equityCapitalRatioPct", label: "equityCapitalRatioPct", higherIsBetter: true },
|
|
36105
|
-
{ key: "netInterestMarginPct", label: "netInterestMarginPct", higherIsBetter: true },
|
|
36106
|
-
{ key: "efficiencyRatioPct", label: "efficiencyRatioPct", higherIsBetter: false },
|
|
36107
|
-
{ key: "loanToDepositPct", label: "loanToDepositPct", higherIsBetter: false }
|
|
36108
|
-
];
|
|
36109
36276
|
const peerFinancials = allFinancials.filter((f) => asNumber(f.CERT) !== params.cert);
|
|
36110
36277
|
const peerExtractions = peerFinancials.map((f) => extractCanonicalMetrics(f));
|
|
36111
36278
|
const subjectPercentiles = {};
|
|
@@ -36124,15 +36291,28 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36124
36291
|
}
|
|
36125
36292
|
}
|
|
36126
36293
|
if (peerValues.length === 0) continue;
|
|
36294
|
+
let stats = null;
|
|
36127
36295
|
if (subjectVal !== null) {
|
|
36128
|
-
|
|
36296
|
+
stats = computePeerStats(subjectVal, peerValues, {
|
|
36129
36297
|
higherIsBetter: pm.higherIsBetter
|
|
36130
36298
|
});
|
|
36299
|
+
subjectPercentiles[pm.legacyKey] = stats;
|
|
36131
36300
|
}
|
|
36132
36301
|
const weighted = computeWeightedAggregate(weightedEntries);
|
|
36133
36302
|
if (weighted !== null) {
|
|
36134
|
-
weightedPeerAverages[pm.
|
|
36303
|
+
weightedPeerAverages[pm.legacyKey] = Math.round(weighted * 100) / 100;
|
|
36135
36304
|
}
|
|
36305
|
+
metricRows.push({
|
|
36306
|
+
name: pm.name,
|
|
36307
|
+
label: pm.label,
|
|
36308
|
+
subject: subjectVal,
|
|
36309
|
+
peer_median: stats?.peer_median ?? null,
|
|
36310
|
+
peer_weighted_avg: weighted !== null ? Math.round(weighted * 100) / 100 : null,
|
|
36311
|
+
percentile: stats?.subject_percentile ?? null,
|
|
36312
|
+
higher_is_better: pm.higherIsBetter,
|
|
36313
|
+
is_outlier: stats?.is_outlier ?? false,
|
|
36314
|
+
outlier_direction: stats?.outlier_direction ?? null
|
|
36315
|
+
});
|
|
36136
36316
|
}
|
|
36137
36317
|
let peerDef;
|
|
36138
36318
|
if (params.certs) {
|
|
@@ -36207,12 +36387,14 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36207
36387
|
model: "public_camels_proxy_v1",
|
|
36208
36388
|
official_status: "public off-site proxy, not official CAMELS",
|
|
36209
36389
|
proxy: subjectProxy,
|
|
36390
|
+
proxy_summary: buildProxySummary(subjectProxy),
|
|
36210
36391
|
report_date: params.repdte,
|
|
36211
36392
|
sort_by: params.sort_by,
|
|
36212
36393
|
total_institutions: entries.length,
|
|
36213
36394
|
returned_count: returned.length,
|
|
36214
36395
|
subject_cert: params.cert ?? null,
|
|
36215
36396
|
subject_rank: subjectRank,
|
|
36397
|
+
metrics: metricRows,
|
|
36216
36398
|
institutions: returned,
|
|
36217
36399
|
peer_context: peerContext
|
|
36218
36400
|
}
|
package/dist/server.js
CHANGED
|
@@ -47,7 +47,7 @@ var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
|
47
47
|
var import_express2 = __toESM(require("express"));
|
|
48
48
|
|
|
49
49
|
// src/constants.ts
|
|
50
|
-
var VERSION = true ? "1.
|
|
50
|
+
var VERSION = true ? "1.27.0" : process.env.npm_package_version ?? "0.0.0-dev";
|
|
51
51
|
var FDIC_API_BASE_URL = "https://banks.data.fdic.gov/api";
|
|
52
52
|
var CHARACTER_LIMIT = 5e4;
|
|
53
53
|
var DEFAULT_FDIC_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
@@ -32058,6 +32058,99 @@ var FdicBankDeepDiveOutputSchema = import_zod2.z.object({
|
|
|
32058
32058
|
warnings: import_zod2.z.array(import_zod2.z.string()),
|
|
32059
32059
|
sources: import_zod2.z.array(Source)
|
|
32060
32060
|
});
|
|
32061
|
+
var PeerStatsSchema = import_zod2.z.object({
|
|
32062
|
+
peer_count: import_zod2.z.number().int(),
|
|
32063
|
+
peer_median: import_zod2.z.number(),
|
|
32064
|
+
peer_mean: import_zod2.z.number(),
|
|
32065
|
+
subject_value: import_zod2.z.number(),
|
|
32066
|
+
subject_percentile: import_zod2.z.number(),
|
|
32067
|
+
robust_z_score: import_zod2.z.number(),
|
|
32068
|
+
is_outlier: import_zod2.z.boolean(),
|
|
32069
|
+
outlier_direction: import_zod2.z.enum(["high", "low"]).optional()
|
|
32070
|
+
});
|
|
32071
|
+
var PeerHealthMetricRowSchema = import_zod2.z.object({
|
|
32072
|
+
name: import_zod2.z.string(),
|
|
32073
|
+
label: import_zod2.z.string(),
|
|
32074
|
+
subject: import_zod2.z.number().nullable(),
|
|
32075
|
+
peer_median: import_zod2.z.number().nullable(),
|
|
32076
|
+
peer_weighted_avg: import_zod2.z.number().nullable(),
|
|
32077
|
+
percentile: import_zod2.z.number().nullable(),
|
|
32078
|
+
higher_is_better: import_zod2.z.boolean(),
|
|
32079
|
+
is_outlier: import_zod2.z.boolean(),
|
|
32080
|
+
outlier_direction: import_zod2.z.enum(["high", "low"]).nullable()
|
|
32081
|
+
});
|
|
32082
|
+
var PeerHealthInstitutionSchema = import_zod2.z.object({
|
|
32083
|
+
cert: import_zod2.z.number().int(),
|
|
32084
|
+
name: import_zod2.z.string(),
|
|
32085
|
+
name_source: import_zod2.z.enum(["fdic_institution_profile", "cert_fallback"]),
|
|
32086
|
+
city: import_zod2.z.string().nullable(),
|
|
32087
|
+
state: import_zod2.z.string().nullable(),
|
|
32088
|
+
total_assets: import_zod2.z.number().nullable(),
|
|
32089
|
+
proxy_score: import_zod2.z.number(),
|
|
32090
|
+
proxy_band: import_zod2.z.string(),
|
|
32091
|
+
composite_rating: import_zod2.z.number(),
|
|
32092
|
+
composite_label: import_zod2.z.string(),
|
|
32093
|
+
component_ratings: import_zod2.z.record(import_zod2.z.number()),
|
|
32094
|
+
flags: import_zod2.z.array(import_zod2.z.string())
|
|
32095
|
+
});
|
|
32096
|
+
var PeerHealthProxySummarySchema = import_zod2.z.object({
|
|
32097
|
+
model: import_zod2.z.literal("public_camels_proxy_v1"),
|
|
32098
|
+
official_status: import_zod2.z.literal("public off-site proxy, not official CAMELS"),
|
|
32099
|
+
score: import_zod2.z.number(),
|
|
32100
|
+
band: import_zod2.z.string(),
|
|
32101
|
+
components: import_zod2.z.array(
|
|
32102
|
+
import_zod2.z.object({
|
|
32103
|
+
name: import_zod2.z.string(),
|
|
32104
|
+
label: import_zod2.z.string(),
|
|
32105
|
+
score: import_zod2.z.number(),
|
|
32106
|
+
legacy_rating: import_zod2.z.number(),
|
|
32107
|
+
legacy_label: import_zod2.z.string(),
|
|
32108
|
+
flags: import_zod2.z.array(import_zod2.z.string())
|
|
32109
|
+
})
|
|
32110
|
+
),
|
|
32111
|
+
capital_classification: import_zod2.z.object({
|
|
32112
|
+
category: import_zod2.z.string(),
|
|
32113
|
+
label: import_zod2.z.string(),
|
|
32114
|
+
binding_constraint: import_zod2.z.string().nullable(),
|
|
32115
|
+
ratios_used: import_zod2.z.record(import_zod2.z.number().nullable())
|
|
32116
|
+
}),
|
|
32117
|
+
management_overlay: import_zod2.z.object({
|
|
32118
|
+
level: import_zod2.z.string(),
|
|
32119
|
+
caps_band: import_zod2.z.boolean(),
|
|
32120
|
+
reason_codes: import_zod2.z.array(import_zod2.z.string())
|
|
32121
|
+
}),
|
|
32122
|
+
risk_signal_count: import_zod2.z.number().int(),
|
|
32123
|
+
risk_signal_severities: import_zod2.z.record(import_zod2.z.number().int()),
|
|
32124
|
+
trend_count: import_zod2.z.number().int(),
|
|
32125
|
+
data_quality: import_zod2.z.object({
|
|
32126
|
+
report_date: import_zod2.z.string(),
|
|
32127
|
+
staleness: import_zod2.z.string(),
|
|
32128
|
+
gaps_count: import_zod2.z.number().int(),
|
|
32129
|
+
gaps: import_zod2.z.array(import_zod2.z.string())
|
|
32130
|
+
})
|
|
32131
|
+
});
|
|
32132
|
+
var FdicPeerHealthOutputSchema = import_zod2.z.object({
|
|
32133
|
+
model: import_zod2.z.literal("public_camels_proxy_v1"),
|
|
32134
|
+
official_status: import_zod2.z.literal("public off-site proxy, not official CAMELS"),
|
|
32135
|
+
proxy: import_zod2.z.unknown().nullable(),
|
|
32136
|
+
proxy_summary: PeerHealthProxySummarySchema.nullable(),
|
|
32137
|
+
report_date: import_zod2.z.string(),
|
|
32138
|
+
sort_by: import_zod2.z.string(),
|
|
32139
|
+
total_institutions: import_zod2.z.number().int(),
|
|
32140
|
+
returned_count: import_zod2.z.number().int(),
|
|
32141
|
+
subject_cert: import_zod2.z.number().int().nullable(),
|
|
32142
|
+
subject_rank: import_zod2.z.number().int().nullable(),
|
|
32143
|
+
metrics: import_zod2.z.array(PeerHealthMetricRowSchema),
|
|
32144
|
+
institutions: import_zod2.z.array(PeerHealthInstitutionSchema),
|
|
32145
|
+
peer_context: import_zod2.z.object({
|
|
32146
|
+
peer_count: import_zod2.z.number().int(),
|
|
32147
|
+
peer_definition: import_zod2.z.string(),
|
|
32148
|
+
broadening_steps: import_zod2.z.array(import_zod2.z.string()),
|
|
32149
|
+
subject_rank: import_zod2.z.number().int().nullable(),
|
|
32150
|
+
subject_percentiles: import_zod2.z.record(PeerStatsSchema),
|
|
32151
|
+
weighted_peer_averages: import_zod2.z.record(import_zod2.z.number())
|
|
32152
|
+
}).nullable()
|
|
32153
|
+
}).passthrough();
|
|
32061
32154
|
var FdicAnalysisOutputSchema = import_zod2.z.object({}).passthrough();
|
|
32062
32155
|
|
|
32063
32156
|
// src/tools/institutions.ts
|
|
@@ -35834,6 +35927,58 @@ function computePeerStats(subjectValue, peerValues, options) {
|
|
|
35834
35927
|
}
|
|
35835
35928
|
|
|
35836
35929
|
// src/tools/peerHealth.ts
|
|
35930
|
+
var PEER_METRICS = [
|
|
35931
|
+
// legacyKey preserves the original camelCase peer_context map keys for backward compatibility.
|
|
35932
|
+
// New UI consumers should bind to the flat metrics[].name snake_case values instead.
|
|
35933
|
+
{ key: "roaPct", legacyKey: "roaPct", name: "roa_pct", label: "Return on assets", higherIsBetter: true },
|
|
35934
|
+
{ key: "equityCapitalRatioPct", legacyKey: "equityCapitalRatioPct", name: "equity_capital_ratio_pct", label: "Equity capital ratio", higherIsBetter: true },
|
|
35935
|
+
{ key: "netInterestMarginPct", legacyKey: "netInterestMarginPct", name: "net_interest_margin_pct", label: "Net interest margin", higherIsBetter: true },
|
|
35936
|
+
{ key: "efficiencyRatioPct", legacyKey: "efficiencyRatioPct", name: "efficiency_ratio_pct", label: "Efficiency ratio", higherIsBetter: false },
|
|
35937
|
+
{ key: "loanToDepositPct", legacyKey: "loanToDepositPct", name: "loan_to_deposit_pct", label: "Loan-to-deposit ratio", higherIsBetter: false }
|
|
35938
|
+
];
|
|
35939
|
+
function buildProxySummary(proxy) {
|
|
35940
|
+
if (!proxy) return null;
|
|
35941
|
+
const componentEntries = [
|
|
35942
|
+
{ name: "capital", assessment: proxy.component_assessment.capital },
|
|
35943
|
+
{ name: "asset_quality", assessment: proxy.component_assessment.asset_quality },
|
|
35944
|
+
{ name: "earnings", assessment: proxy.component_assessment.earnings },
|
|
35945
|
+
{ name: "liquidity_funding", assessment: proxy.component_assessment.liquidity_funding },
|
|
35946
|
+
{ name: "sensitivity_proxy", assessment: proxy.component_assessment.sensitivity_proxy }
|
|
35947
|
+
];
|
|
35948
|
+
const riskSignalSeverities = {};
|
|
35949
|
+
for (const signal of proxy.risk_signals) {
|
|
35950
|
+
riskSignalSeverities[signal.severity] = (riskSignalSeverities[signal.severity] ?? 0) + 1;
|
|
35951
|
+
}
|
|
35952
|
+
return {
|
|
35953
|
+
model: proxy.model,
|
|
35954
|
+
official_status: proxy.official_status,
|
|
35955
|
+
score: proxy.overall.score,
|
|
35956
|
+
band: proxy.overall.band,
|
|
35957
|
+
components: componentEntries.map(({ name, assessment }) => ({
|
|
35958
|
+
name,
|
|
35959
|
+
label: assessment.label,
|
|
35960
|
+
score: assessment.score,
|
|
35961
|
+
legacy_rating: assessment.legacy_rating,
|
|
35962
|
+
legacy_label: assessment.legacy_label,
|
|
35963
|
+
flags: assessment.flags
|
|
35964
|
+
})),
|
|
35965
|
+
capital_classification: {
|
|
35966
|
+
category: proxy.capital_classification.category,
|
|
35967
|
+
label: proxy.capital_classification.label,
|
|
35968
|
+
binding_constraint: proxy.capital_classification.binding_constraint ?? null,
|
|
35969
|
+
ratios_used: proxy.capital_classification.ratios_used
|
|
35970
|
+
},
|
|
35971
|
+
management_overlay: {
|
|
35972
|
+
level: proxy.management_overlay.level,
|
|
35973
|
+
caps_band: proxy.management_overlay.caps_band,
|
|
35974
|
+
reason_codes: proxy.management_overlay.reason_codes
|
|
35975
|
+
},
|
|
35976
|
+
risk_signal_count: proxy.risk_signals.length,
|
|
35977
|
+
risk_signal_severities: riskSignalSeverities,
|
|
35978
|
+
trend_count: proxy.trend_insights.length,
|
|
35979
|
+
data_quality: proxy.data_quality
|
|
35980
|
+
};
|
|
35981
|
+
}
|
|
35837
35982
|
var PeerHealthInputSchema = import_zod11.z.object({
|
|
35838
35983
|
cert: import_zod11.z.number().int().positive().optional().describe("Subject institution CERT to highlight in the ranking. Optional."),
|
|
35839
35984
|
certs: import_zod11.z.array(import_zod11.z.number().int().positive()).max(50).optional().describe("Explicit list of CERTs to compare (max 50)."),
|
|
@@ -35868,11 +36013,11 @@ Three usage modes:
|
|
|
35868
36013
|
|
|
35869
36014
|
Optionally provide cert to highlight a subject institution's position in the ranking.
|
|
35870
36015
|
|
|
35871
|
-
Output:
|
|
36016
|
+
Output: structuredContent includes {model, official_status, report_date, institutions, metrics, peer_context, proxy_summary, proxy}. Institutions include proxy scores and name_source. When a subject cert is provided, metrics is a flat subject-vs-peer array and proxy_summary is a flattened subject proxy for UI binding while peer_context and proxy preserve the legacy detailed payloads. Auto-peer selection derives asset bands from report-date financials and broadens the cohort if fewer than 10 peers match.
|
|
35872
36017
|
|
|
35873
36018
|
NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`,
|
|
35874
36019
|
inputSchema: PeerHealthInputSchema,
|
|
35875
|
-
outputSchema:
|
|
36020
|
+
outputSchema: FdicPeerHealthOutputSchema,
|
|
35876
36021
|
annotations: {
|
|
35877
36022
|
readOnlyHint: true,
|
|
35878
36023
|
destructiveHint: false,
|
|
@@ -36046,6 +36191,33 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36046
36191
|
const c = asNumber(r.CERT);
|
|
36047
36192
|
if (c !== null) profileMap.set(c, r);
|
|
36048
36193
|
}
|
|
36194
|
+
const financialCerts = allFinancials.map((r) => asNumber(r.CERT)).filter((cert) => cert !== null);
|
|
36195
|
+
const missingProfileCerts = financialCerts.filter((cert) => {
|
|
36196
|
+
const profile = profileMap.get(cert);
|
|
36197
|
+
return !profile || typeof profile.NAME !== "string" || profile.NAME.length === 0;
|
|
36198
|
+
});
|
|
36199
|
+
if (missingProfileCerts.length > 0) {
|
|
36200
|
+
const missingProfileFilters = buildCertFilters(missingProfileCerts);
|
|
36201
|
+
const missingProfileResponses = await mapWithConcurrency(
|
|
36202
|
+
missingProfileFilters,
|
|
36203
|
+
MAX_CONCURRENCY,
|
|
36204
|
+
async (certFilter) => queryEndpoint(
|
|
36205
|
+
ENDPOINTS.INSTITUTIONS,
|
|
36206
|
+
{
|
|
36207
|
+
filters: certFilter,
|
|
36208
|
+
fields: "CERT,NAME,CITY,STALP",
|
|
36209
|
+
limit: 1e4,
|
|
36210
|
+
sort_by: "CERT",
|
|
36211
|
+
sort_order: "ASC"
|
|
36212
|
+
},
|
|
36213
|
+
{ signal: controller.signal }
|
|
36214
|
+
)
|
|
36215
|
+
);
|
|
36216
|
+
for (const r of missingProfileResponses.flatMap(extractRecords)) {
|
|
36217
|
+
const c = asNumber(r.CERT);
|
|
36218
|
+
if (c !== null) profileMap.set(c, r);
|
|
36219
|
+
}
|
|
36220
|
+
}
|
|
36049
36221
|
await sendProgressNotification(server.server, progressToken, 0.7, "Computing proxy assessments");
|
|
36050
36222
|
const subjectHistory = params.cert ? await fetchHistoryEvents(params.cert, { signal: controller.signal, repdte: params.repdte }) : [];
|
|
36051
36223
|
let subjectProxy = null;
|
|
@@ -36084,7 +36256,8 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36084
36256
|
];
|
|
36085
36257
|
entries.push({
|
|
36086
36258
|
cert,
|
|
36087
|
-
name:
|
|
36259
|
+
name: typeof profile?.NAME === "string" && profile.NAME.length > 0 ? profile.NAME : `CERT ${cert}`,
|
|
36260
|
+
name_source: typeof profile?.NAME === "string" && profile.NAME.length > 0 ? "fdic_institution_profile" : "cert_fallback",
|
|
36088
36261
|
city: profile?.CITY ? String(profile.CITY) : null,
|
|
36089
36262
|
state: profile?.STALP ? String(profile.STALP) : null,
|
|
36090
36263
|
total_assets: asNumber(fin.ASSET),
|
|
@@ -36109,18 +36282,12 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36109
36282
|
});
|
|
36110
36283
|
const subjectRank = params.cert ? entries.findIndex((e) => e.cert === params.cert) + 1 : null;
|
|
36111
36284
|
let peerContext = null;
|
|
36285
|
+
let metricRows = [];
|
|
36112
36286
|
if (params.cert) {
|
|
36113
36287
|
const subjectFin = allFinancials.find((f) => asNumber(f.CERT) === params.cert);
|
|
36114
36288
|
if (subjectFin) {
|
|
36115
36289
|
const subjectExtraction = extractCanonicalMetrics(subjectFin);
|
|
36116
36290
|
const sm = subjectExtraction.metrics;
|
|
36117
|
-
const PEER_METRICS = [
|
|
36118
|
-
{ key: "roaPct", label: "roaPct", higherIsBetter: true },
|
|
36119
|
-
{ key: "equityCapitalRatioPct", label: "equityCapitalRatioPct", higherIsBetter: true },
|
|
36120
|
-
{ key: "netInterestMarginPct", label: "netInterestMarginPct", higherIsBetter: true },
|
|
36121
|
-
{ key: "efficiencyRatioPct", label: "efficiencyRatioPct", higherIsBetter: false },
|
|
36122
|
-
{ key: "loanToDepositPct", label: "loanToDepositPct", higherIsBetter: false }
|
|
36123
|
-
];
|
|
36124
36291
|
const peerFinancials = allFinancials.filter((f) => asNumber(f.CERT) !== params.cert);
|
|
36125
36292
|
const peerExtractions = peerFinancials.map((f) => extractCanonicalMetrics(f));
|
|
36126
36293
|
const subjectPercentiles = {};
|
|
@@ -36139,15 +36306,28 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36139
36306
|
}
|
|
36140
36307
|
}
|
|
36141
36308
|
if (peerValues.length === 0) continue;
|
|
36309
|
+
let stats = null;
|
|
36142
36310
|
if (subjectVal !== null) {
|
|
36143
|
-
|
|
36311
|
+
stats = computePeerStats(subjectVal, peerValues, {
|
|
36144
36312
|
higherIsBetter: pm.higherIsBetter
|
|
36145
36313
|
});
|
|
36314
|
+
subjectPercentiles[pm.legacyKey] = stats;
|
|
36146
36315
|
}
|
|
36147
36316
|
const weighted = computeWeightedAggregate(weightedEntries);
|
|
36148
36317
|
if (weighted !== null) {
|
|
36149
|
-
weightedPeerAverages[pm.
|
|
36318
|
+
weightedPeerAverages[pm.legacyKey] = Math.round(weighted * 100) / 100;
|
|
36150
36319
|
}
|
|
36320
|
+
metricRows.push({
|
|
36321
|
+
name: pm.name,
|
|
36322
|
+
label: pm.label,
|
|
36323
|
+
subject: subjectVal,
|
|
36324
|
+
peer_median: stats?.peer_median ?? null,
|
|
36325
|
+
peer_weighted_avg: weighted !== null ? Math.round(weighted * 100) / 100 : null,
|
|
36326
|
+
percentile: stats?.subject_percentile ?? null,
|
|
36327
|
+
higher_is_better: pm.higherIsBetter,
|
|
36328
|
+
is_outlier: stats?.is_outlier ?? false,
|
|
36329
|
+
outlier_direction: stats?.outlier_direction ?? null
|
|
36330
|
+
});
|
|
36151
36331
|
}
|
|
36152
36332
|
let peerDef;
|
|
36153
36333
|
if (params.certs) {
|
|
@@ -36222,12 +36402,14 @@ NOTE: Public off-site analytical proxy \u2014 not official supervisory ratings.`
|
|
|
36222
36402
|
model: "public_camels_proxy_v1",
|
|
36223
36403
|
official_status: "public off-site proxy, not official CAMELS",
|
|
36224
36404
|
proxy: subjectProxy,
|
|
36405
|
+
proxy_summary: buildProxySummary(subjectProxy),
|
|
36225
36406
|
report_date: params.repdte,
|
|
36226
36407
|
sort_by: params.sort_by,
|
|
36227
36408
|
total_institutions: entries.length,
|
|
36228
36409
|
returned_count: returned.length,
|
|
36229
36410
|
subject_cert: params.cert ?? null,
|
|
36230
36411
|
subject_rank: subjectRank,
|
|
36412
|
+
metrics: metricRows,
|
|
36231
36413
|
institutions: returned,
|
|
36232
36414
|
peer_context: peerContext
|
|
36233
36415
|
}
|