技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
plaid-income【数据与研究】Implements income verification features via the Plaid API (IncomeGet, IncomeList) for employment-based and deposited income analysis in lending, underwriting, and financial applications.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
name: plaid-income description: Implements income verification features via the Plaid API (IncomeGet, IncomeList) for employment-based and deposited income analysis in lending, underwriting, and financial applications. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: payments triggers: plaid income, income verification, plaid income get, employment income, salary verification, deposited income, earning capacity analysis, underwriting income archetypes: tactical anti_triggers: - brainstorming - vague ideation response_profile: verbosity: low directive_strength: high abstraction_level: operational role: implementation scope: implementation output-format: code related-skills: plaid-authentication, plaid-transactions
Implements income verification workflows using the Plaid Income API to retrieve employment-based and deposited income data from linked bank accounts. Covers salary verification, income categorization, earning capacity estimation, and underwriting-grade analysis for lending decisions.
IncomeGet after the user has linked an account with sufficient transaction history (>= 60 days recommended)INCOME_NOT_AVAILABLE error gracefully — not all institutions provide income dataannualized_income field for consistencyUse this skill when:
Avoid this skill for:
access_token.import plaid
from dataclasses import dataclass, field
from typing import Optional, List
from datetime import datetime
@dataclass
class IncomeEntry:
"""A single income record from Plaid."""
income_type: str # "employment" or "deposited"
amount: float
currency: str # ISO 4217 (e.g., "USD")
annualized_amount: Optional[float]
frequency: Optional[str]
employer_name: Optional[str]
last_verified_date: Optional[datetime]
@dataclass
class IncomeVerificationResult:
"""Aggregated result of Plaid income verification."""
employment_incomes: List[IncomeEntry] = field(default_factory=list)
deposited_incomes: List[IncomeEntry] = field(default_factory=list)
total_annualized_income: Optional[float] = None
income_confidence: str # "high", "medium", "low"
errors: List[str] = field(default_factory=list)
class PlaidIncomeService:
"""Service for retrieving and analyzing user income data from Plaid."""
def __init__(self, client_id: str, secret: str, environment: str = "sandbox"):
self.client = plaid.Client(
client_id=client_id,
secret=secret,
environment=getattr(plaid.Environment, environment),
)
def get_income(self, access_token: str) -> dict:
"""Fetch income data from Plaid for a linked item."""
response = self.client.IncomeGet(access_token=access_token)
return response
@staticmethod
def _parse_income_entry(data: dict) -> IncomeEntry:
"""Parse a single income entry dictionary into a typed object."""
return IncomeEntry(
income_type=data.get("income_type", "unknown"),
amount=float(data.get("amount", 0)),
currency=data.get("currency", "USD"),
annualized_amount=float(data["annualized_income"]) if data.get("annualized_income") else None,
frequency=data.get("frequency"),
employer_name=data.get("employer", {}).get("name") if data.get("employer") else None,
last_verified_date=datetime.fromisoformat(data["last_verified_at"]) if data.get("last_verified_at") else None,
)
def analyze_income(self, access_token: str) -> IncomeVerificationResult:
"""Retrieve and analyze income data for underwriting-grade analysis."""
result = IncomeVerificationResult()
try:
response = self.get_income(access_token)
except plaid.errors.PlaidApiError as e:
if "INCOME_NOT_AVAILABLE" in str(e):
result.errors.append("Income data is not available for this institution or account")
result.income_confidence = "low"
return result
raise
raw_data = response.get("incomes", [])
for entry in raw_data:
parsed = self._parse_income_entry(entry)
if entry.get("income_type") == "employment":
result.employment_incomes.append(parsed)
elif entry.get("income_type") == "deposited":
result.deposited_incomes.append(parsed)
# Calculate total annualized income
annual_amounts = [i.annualized_amount for i in raw_data if i.annualized_amount]
if annual_amounts:
result.total_annualized_income = sum(annual_amounts)
# Determine confidence level
if len(raw_data) >= 3:
result.income_confidence = "high"
elif len(raw_data) >= 1:
result.income_confidence = "medium"
else:
result.income_confidence = "low"
return result
Plaid returns income data in this structure:
{
"incomes": [
{
"id": "income_123",
"income_type": "employment", # or "deposited"
"amount": 6500.00, # per-period amount
"currency": "USD",
"frequency": "monthly", # monthly, biweekly, weekly, annually
"annualized_income": 78000.00, # annualized figure
"last_verified_at": "2025-01-15T10:30:00Z",
"employer": {
"name": "Acme Corp",
"industry_code": "491112" # NAICS code
}
}
]
}
@dataclass
class LendingCriteria:
"""Lending eligibility criteria for income-based decisions."""
min_annual_income: float
max_debt_to_income_ratio: float = 0.43
required_employment_length_months: int = 6
class IncomeEligibilityChecker:
"""Evaluate lending eligibility against Plaid income data."""
def __init__(self, criteria: LendingCriteria):
self.criteria = criteria
def check_eligibility(
self,
result: IncomeVerificationResult,
monthly_debt_obligations: float = 0.0,
) -> dict:
"""Determine if user meets lending criteria based on income data."""
annual_income = result.total_annualized_income or 0
monthly_income = annual_income / 12.0
dti_ratio = monthly_debt_obligations / monthly_income if monthly_income > 0 else float("inf")
return {
"meets_min_income": annual_income >= self.criteria.min_annual_income,
"dti_ratio": round(dti_ratio, 4),
"within_dti_limit": dti_ratio <= self.criteria.max_debt_to_income_ratio,
"confidence_level": result.income_confidence,
"approved": (
annual_income >= self.criteria.min_annual_income
and dti_ratio <= self.criteria.max_debt_to_income_ratio
and result.income_confidence in ("high", "medium")
),
"rejection_reasons": [
reason
for reason, passed in [
("Insufficient income", annual_income >= self.criteria.min_annual_income),
("Debt-to-income ratio too high", dti_ratio <= self.criteria.max_debt_to_income_ratio),
("Low data confidence", result.income_confidence != "low"),
]
if not passed
],
}
INCOME_NOT_AVAILABLE errors before assuming data retrieval failure.annualized_income field for consistent comparison across different pay frequencies.low confidence results — require manual review.When implementing Plaid Income, output must contain:
IncomeGet invocation with error handling for unavailable data| Skill | Purpose |
|---|---|
plaid-authentication | Initial bank account linking before income verification |
plaid-transactions | Supplement income analysis with transaction-level detail |
Authoritative documentation links for this skill's domain. The model follows markdown links to resolve external references and inline content.