Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 123 additions & 1 deletion rag/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from langchain_ollama import OllamaEmbeddings
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
from typing import List
from typing import List, Dict, Any
from triangle import triangle_build, dev_select, tail_constant, tail_curve, ibnr_estimate

# Set up logging to stderr to avoid interfering with JSON-RPC over stdout
logging.basicConfig(
Expand Down Expand Up @@ -68,6 +69,7 @@ def generate_answer(question: str, context_docs: List[Document]) -> str:
response = llm.invoke(messages)
return response.content


@mcp.tool()
def search_friedland_paper(prompt: str) -> str:
"""Search the Friedland actuarial paper for information"""
Expand All @@ -88,5 +90,125 @@ def search_both_papers(prompt: str) -> str:
all_docs = friedland_docs + werner_modlin_docs
return generate_answer(prompt, all_docs)

@mcp.tool()
def triangle_build_tool(input_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Build an actuarial triangle from input data using chainladder-python.

Required Input: {
rows: [{origin, dev, value, segment?}],
value_type: "cumulative"|"incremental",
metric: "PaidLoss"|"ReportedLoss"|...,
}

Optional Input: {
exposure: [15000, 18000, 20000], # Exposure per origin period
origin_col: "origin", # Name of origin column in rows (default: 'origin')
dev_col: "dev", # Name of development column in rows (default: 'dev')
value_col: "value" # Name of value column in rows (default: 'value')
}

Output: {
triangle_id,
profile: {n_origin, n_dev, has_exposure},
warnings: []
}
"""
return triangle_build(input_data)

@mcp.tool()
def dev_select_tool(input_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Perform development factor selection using chainladder.

Input: {
triangle_id: "uuid-string",
averaging: "volume"|"simple"|"median",
age_exclusions: [],
min_obs: 1,
tail_spec: null
}

Output: {
age_to_age: [],
LDF: [],
CDF: [],
tail_factor: 1.0,
diagnostics: {}
}
"""
return dev_select(input_data)

@mcp.tool()
def tail_constant_tool(input_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Apply constant tail factor to triangle.

Input: {
triangle_id: "uuid-string",
tail_factor: 1.05
}

Output: {
tail_factor: 1.05,
ldf: [],
cdf: [],
diagnostics: {}
}
"""
return tail_constant(input_data)

@mcp.tool()
def tail_curve_tool(input_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Apply curve-fitted tail to triangle.

Input: {
triangle_id: "uuid-string",
extrap_periods: 100,
fit_period: null
}

Output: {
tail_factor: 1.03,
ldf: [],
cdf: [],
diagnostics: {}
}
"""
return tail_curve(input_data)

@mcp.tool()
def ibnr_estimate_tool(input_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate IBNR and Ultimate using various actuarial methods.

Input: {
triangle_id: "uuid-string",
method: "chainladder"|"bornhuetter_ferguson"|"benktander"|"expected_losses",
apriori: 0.75, # Required for bornhuetter_ferguson, benktander, expected_losses
n_iters: 1,
trend: 0.0
}

Note:
- chainladder method only requires triangle_id
- Other methods require exposure data in triangle_build

Output: {
ultimate: [],
ibnr: [],
latest_diagonal: [],
diagnostics: {}
}

TODO: Implement additional methods:
- case_outstanding: Requires incurred vs paid triangle structure
- cape_cod: Needs specific exposure data formatting
- berquist_sherman: Requires additional parameter configuration
- frequency_severity: Custom implementation needed for the 3 variations(not in chainladder)
"""
return ibnr_estimate(input_data)

if __name__ == "__main__":
mcp.run()
Loading
Loading