├── debian ├── compat ├── rules ├── changelog ├── control ├── generate_changelog.py └── README.md ├── faslr ├── common │ ├── tree.py │ ├── __init__.py │ ├── triangle.py │ ├── table.py │ └── button.py ├── methods │ ├── cape_cod.py │ ├── __init__.py │ └── development.py ├── samples │ ├── __init__.py │ ├── db │ │ └── __init__.py │ ├── friedland_table_1.csv │ ├── friedland_qs.csv │ ├── friedland_xol.csv │ ├── friedland_auto_bi_insurer.csv │ ├── mack_1997.csv │ ├── friedland_xyz_disp.csv │ ├── xyz_expected_loss.py │ ├── friedland_berq_sher_auto.csv │ ├── friedland_gl_insurer.csv │ ├── friedland_uspp_auto_steady_state.csv │ ├── friedland_uspp_auto_increasing_case.csv │ ├── friedland_us_auto_incr_claim.csv │ ├── friedland_uspp_auto_increasing_claim.csv │ ├── friedland_uspp_increasing_claim_case.csv │ ├── friedland_med_mal.csv │ ├── friedland_us_auto_chg_prod_mix.csv │ ├── friedland_us_auto_steady_state.csv │ ├── friedland_us_industry_auto_case.csv │ ├── friedland_xyz_auto_bi.csv │ ├── friedland_us_industry_auto.csv │ ├── friedland_wc_self_insurer.csv │ ├── friedland_xyz_case.csv │ ├── friedland_autoprop.csv │ ├── friedland_auto_freq_sev.csv │ ├── friedland_xyz_freq_sev.csv │ ├── README.md │ └── friedland_auto_salsub.csv ├── style │ ├── qss │ │ ├── dark.qss │ │ └── light.qss │ ├── project.py │ ├── main.py │ ├── extract.py │ ├── icons │ │ ├── octicons │ │ │ ├── issue-opened-24.svg │ │ │ ├── commit-24.svg │ │ │ ├── git-branch-24.svg │ │ │ └── comment-discussion-24.svg │ │ ├── check.svg │ │ ├── nav-arrow-down.svg │ │ ├── nav-arrow-up.svg │ │ ├── arrow-left.svg │ │ ├── arrow-down.svg │ │ ├── arrow-right.svg │ │ ├── arrow-up.svg │ │ ├── bar-chart-2.svg │ │ ├── cancel.svg │ │ ├── graph-down.svg │ │ ├── open-in-browser.svg │ │ ├── kanban-board.svg │ │ ├── db.svg │ │ ├── delete-circled-outline.svg │ │ ├── message-text.svg │ │ ├── check-circled-outline.svg │ │ ├── text-alt.svg │ │ ├── refresh.svg │ │ ├── README.md │ │ ├── github.svg │ │ ├── no-link.svg │ │ ├── link.svg │ │ ├── bookmark-book.svg │ │ ├── curve-graph.svg │ │ ├── xkcd-svgrepo-com.svg │ │ └── scatter-plot.svg │ ├── triangle.py │ └── plot │ │ └── xkcd.py ├── tests │ ├── core │ │ ├── test_core.py │ │ └── test_application.py │ ├── methods │ │ ├── __init__.py │ │ └── test_development.py │ ├── utilities │ │ ├── __init__.py │ │ ├── test_queries.py │ │ ├── test_sample.py │ │ ├── test_collections.py │ │ ├── test_connection.py │ │ ├── test_gui.py │ │ ├── test_accessors.py │ │ └── test_chainladder.py │ ├── __init__.py │ ├── constants.py │ ├── common │ │ ├── test_triangle.py │ │ └── test_button.py │ ├── .coveragerc │ ├── test_engine.py │ ├── test_project_item.py │ ├── test_about.py │ ├── index │ │ └── test_model_index.py │ ├── assets │ │ └── xyz_table.csv │ ├── copy_config.py │ ├── test_base_table.py │ ├── conftest.py │ ├── test_base_classes.py │ ├── test_schema.py │ ├── test_cascade.py │ ├── test_analysis.py │ └── test_grid_header.py ├── pytest.ini ├── _version.py ├── constants │ ├── settings.py │ ├── connection.py │ ├── model.py │ ├── analysis.py │ ├── diagnostics.py │ ├── expected_loss.py │ ├── triangle.py │ ├── development.py │ ├── role.py │ ├── __init__.py │ └── general.py ├── templates │ └── config_template.ini ├── index │ ├── index_preview.py │ ├── index_constructor.py │ ├── model_index.py │ ├── __init__.py │ └── index_matrix.py ├── model │ ├── __init__.py │ └── model.py ├── demos │ ├── arguments_demo.py │ ├── ldf_dialog_demo.py │ ├── import_wizard_demo.py │ ├── about_demo.py │ ├── methods │ │ ├── expected_loss │ │ │ ├── ratio_average_demo.py │ │ │ ├── expected_loss_blank_demo.py │ │ │ ├── expected_loss_apriori_blank_demo.py │ │ │ ├── expected_loss_demo.py │ │ │ └── expected_loss_ratio_demo.py │ │ ├── development │ │ │ └── factor_demo.py │ │ ├── borhnhuetter │ │ │ └── bf_demo.py │ │ └── benktander │ │ │ └── benktander_demo.py │ ├── file_dialog_demo.py │ ├── settings_demo.py │ ├── template.py │ ├── project_dialog_demo.py │ ├── index │ │ ├── model_index_demo.py │ │ ├── index_inventory_demo.py │ │ ├── index_matrix_demo.py │ │ ├── findex_demo.py │ │ └── index_retrieval_demo.py │ ├── common │ │ ├── model_demo_blank.py │ │ └── model_demo.py │ ├── tail_demo.py │ ├── project_tree_view_demo.py │ ├── sample_db.py │ ├── data_pane_demo.py │ ├── triangle_demo.py │ ├── exhibit_builder_demo.py │ ├── faveragebox_demo.py │ ├── fratioselection_demo.py │ ├── debug │ │ └── grid_header_reprex_demo.py │ ├── analysis_demo.py │ ├── grid_header_demo.py │ └── exhibit_demo.py ├── __init__.py ├── utilities │ ├── collections.py │ ├── queries.py │ ├── dataframe.py │ ├── gui.py │ ├── __init__.py │ ├── accessors.py │ ├── chainladder.py │ └── style_parser.py ├── project_item.py ├── engine.py └── core.py ├── docs ├── user │ ├── methods │ │ ├── cape_cod.rst │ │ ├── tail │ │ │ ├── constant.rst │ │ │ └── index.rst │ │ ├── expected_loss.rst │ │ ├── bf.rst │ │ ├── development │ │ │ ├── index.rst │ │ │ ├── development.rst │ │ │ └── diagnostics.rst │ │ └── index.rst │ ├── starting │ │ ├── tutorial │ │ │ ├── index.rst │ │ │ ├── project.rst │ │ │ ├── importing.rst │ │ │ └── connecting.rst │ │ └── install.rst │ ├── interface │ │ ├── index.rst │ │ ├── analysis.rst │ │ ├── theme.rst │ │ ├── settings.rst │ │ ├── main.rst │ │ ├── menu.rst │ │ └── project.rst │ ├── extensions │ │ └── index.rst │ ├── exhibit │ │ └── index.rst │ ├── schema │ │ └── index.rst │ ├── index.rst │ └── news │ │ └── xkcd.rst ├── _static │ ├── dev_demo.gif │ ├── client_server.png │ ├── favicon-32x32.png │ ├── basic_interface.png │ ├── basic_ui_filled.png │ ├── basic_ui_09082021.png │ ├── favicon_instructions.md │ └── xkcd_reserving_rotation.py ├── nginx │ ├── README.md │ └── faslr.com.conf ├── api │ └── index.rst ├── Makefile ├── db │ └── README.md ├── make.bat ├── README.md ├── gallery │ └── index.rst ├── index.rst ├── build_docs.sh └── contributing │ └── index.rst ├── .gitignore ├── codecov.yml ├── .circleci └── config.yml ├── requirements.txt ├── .github └── workflows │ ├── docs.yml │ └── tests.yml ├── setup.py └── CONTRIBUTING.md /debian/compat: -------------------------------------------------------------------------------- 1 | 10 -------------------------------------------------------------------------------- /faslr/common/tree.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/methods/cape_cod.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/samples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/samples/db/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/style/qss/dark.qss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/style/qss/light.qss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/tests/core/test_core.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/tests/methods/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/tests/utilities/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/samples/friedland_table_1.csv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/style/project.py: -------------------------------------------------------------------------------- 1 | DEFAULT_PROJECT_FONT = 'Ubuntu Regular' 2 | -------------------------------------------------------------------------------- /docs/user/methods/cape_cod.rst: -------------------------------------------------------------------------------- 1 | Cape Cod 2 | ======== 3 | 4 | To be added. -------------------------------------------------------------------------------- /docs/user/methods/tail/constant.rst: -------------------------------------------------------------------------------- 1 | Tail Constant 2 | ============= 3 | -------------------------------------------------------------------------------- /faslr/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from faslr.tests.constants import (ASSET_PATH) -------------------------------------------------------------------------------- /docs/user/methods/expected_loss.rst: -------------------------------------------------------------------------------- 1 | Expected Loss 2 | ============= 3 | 4 | To be added. -------------------------------------------------------------------------------- /docs/user/methods/bf.rst: -------------------------------------------------------------------------------- 1 | Bornhuetter-Ferguson 2 | ==================== 3 | 4 | To be added. -------------------------------------------------------------------------------- /docs/_static/dev_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/casact/FASLR/HEAD/docs/_static/dev_demo.gif -------------------------------------------------------------------------------- /faslr/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = 3 | --strict-markers 4 | 5 | testpaths = tests 6 | -------------------------------------------------------------------------------- /faslr/methods/__init__.py: -------------------------------------------------------------------------------- 1 | from faslr.methods.expected_loss import ( 2 | ExpectedLossWidget 3 | ) -------------------------------------------------------------------------------- /docs/_static/client_server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/casact/FASLR/HEAD/docs/_static/client_server.png -------------------------------------------------------------------------------- /docs/_static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/casact/FASLR/HEAD/docs/_static/favicon-32x32.png -------------------------------------------------------------------------------- /faslr/_version.py: -------------------------------------------------------------------------------- 1 | """ 2 | Canonical location of the FASLR version. 3 | """ 4 | 5 | __version__ = '0.0.6' -------------------------------------------------------------------------------- /faslr/constants/settings.py: -------------------------------------------------------------------------------- 1 | SETTINGS_LIST = [ 2 | "Startup", 3 | "User", 4 | "Plots" 5 | ] 6 | -------------------------------------------------------------------------------- /faslr/tests/constants.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname 2 | 3 | ASSET_PATH = dirname(__file__) + '/assets/' -------------------------------------------------------------------------------- /docs/_static/basic_interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/casact/FASLR/HEAD/docs/_static/basic_interface.png -------------------------------------------------------------------------------- /docs/_static/basic_ui_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/casact/FASLR/HEAD/docs/_static/basic_ui_filled.png -------------------------------------------------------------------------------- /faslr/constants/connection.py: -------------------------------------------------------------------------------- 1 | DB_NOT_FOUND_TEXT = "Invalid database path specified. File does not exist." 2 | -------------------------------------------------------------------------------- /docs/_static/basic_ui_09082021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/casact/FASLR/HEAD/docs/_static/basic_ui_09082021.png -------------------------------------------------------------------------------- /faslr/templates/config_template.ini: -------------------------------------------------------------------------------- 1 | [STARTUP_CONNECTION] 2 | startup_db = None 3 | 4 | [PLOTTING_STYLE] 5 | plotting_style = Regular 6 | -------------------------------------------------------------------------------- /docs/user/methods/tail/index.rst: -------------------------------------------------------------------------------- 1 | Tail Factor Analysis 2 | ==================== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | constant -------------------------------------------------------------------------------- /faslr/common/__init__.py: -------------------------------------------------------------------------------- 1 | from faslr.common.button import ( 2 | AddRemoveButtonWidget, 3 | FOKCancel, 4 | make_corner_button 5 | ) -------------------------------------------------------------------------------- /faslr/constants/model.py: -------------------------------------------------------------------------------- 1 | BASE_MODEL_AVERAGES = { 2 | 'Geometric': 'geometric', 3 | 'Medial': 'medial', 4 | 'Straight': 'simple' 5 | } -------------------------------------------------------------------------------- /faslr/tests/common/test_triangle.py: -------------------------------------------------------------------------------- 1 | from faslr.common.triangle import FTriangle 2 | 3 | 4 | def test_triangle(): 5 | blah = FTriangle() 6 | -------------------------------------------------------------------------------- /docs/user/starting/tutorial/index.rst: -------------------------------------------------------------------------------- 1 | User Tutorial 2 | ============= 3 | 4 | .. toctree:: 5 | 6 | connecting 7 | project 8 | importing 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sql_test.py 2 | faslr.ini 3 | scratch*.py 4 | .idea/ 5 | *.coverage 6 | *.db 7 | *.log 8 | coverage.xml 9 | docs/_build/ 10 | debian/files -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "setup.py" 3 | - "faslr/samples/db/generate_sample_db.py" 4 | - "faslr/tests/copy_config.py" 5 | - "faslr/tests/scratch*.py" -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #! /usr/bin/make -f 2 | 3 | #export DH_VERBOSE = 1 4 | export PYBUILD_NAME = faslr 5 | 6 | %: 7 | dh $@ --with python3 --buildsystem=pybuild -------------------------------------------------------------------------------- /faslr/style/main.py: -------------------------------------------------------------------------------- 1 | MAIN_WINDOW_WIDTH = 2500 2 | MAIN_WINDOW_HEIGHT = 900 3 | 4 | MAIN_WINDOW_TITLE = "FASLR - Free Actuarial System for Loss Reserving" 5 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | faslr (0.0.6) jammy; urgency=medium 2 | 3 | * Additional release 4 | 5 | -- Gene Dan Fri, 24, Feb 2023 07:36:29 -0600 -------------------------------------------------------------------------------- /faslr/constants/analysis.py: -------------------------------------------------------------------------------- 1 | VALUE_TYPES = [ 2 | 'Values', 3 | 'Link Ratios', 4 | 'Diagnostics' 5 | ] 6 | 7 | VALUE_TYPES_COMBO_BOX_WIDTH = 110 8 | -------------------------------------------------------------------------------- /docs/nginx/README.md: -------------------------------------------------------------------------------- 1 | Contains the nginx configuration file for the web server that hosts the docs. 2 | 3 | server file location: 4 | 5 | /etc/nginx/conf.d/faslr.com.conf -------------------------------------------------------------------------------- /docs/user/methods/development/index.rst: -------------------------------------------------------------------------------- 1 | Development Method 2 | ================== 3 | 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | diagnostics 9 | development -------------------------------------------------------------------------------- /faslr/tests/.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | setup.py 4 | faslr/samples/db/generate_sample_db.py 5 | faslr/tests/copy_config.py 6 | faslr/tests/scratch*.py -------------------------------------------------------------------------------- /faslr/common/triangle.py: -------------------------------------------------------------------------------- 1 | from chainladder import ( 2 | Triangle 3 | ) 4 | 5 | 6 | class FTriangle(Triangle): 7 | def __init__(self): 8 | super().__init__() 9 | -------------------------------------------------------------------------------- /faslr/index/index_preview.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import ( 2 | QTabWidget 3 | ) 4 | 5 | class IndexPreview(QTabWidget): 6 | def __init__(self): 7 | super().__init__() -------------------------------------------------------------------------------- /docs/user/interface/index.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | main 8 | menu 9 | project 10 | analysis 11 | settings 12 | theme -------------------------------------------------------------------------------- /faslr/tests/test_engine.py: -------------------------------------------------------------------------------- 1 | from faslr.engine import EngineDialog 2 | 3 | 4 | def test_engine_dialog(qtbot) -> None: 5 | 6 | engine_dialog = EngineDialog() 7 | qtbot.addWidget(engine_dialog) 8 | -------------------------------------------------------------------------------- /faslr/constants/diagnostics.py: -------------------------------------------------------------------------------- 1 | # Default value for Mack development correlation test 2 | MACK_DEVELOPMENT_CRITICAL = 0.5 3 | 4 | # Default value for Mack valuation correlation test 5 | MACK_VALUATION_CRITICAL = 0.10 6 | -------------------------------------------------------------------------------- /docs/user/methods/index.rst: -------------------------------------------------------------------------------- 1 | Methods 2 | ======= 3 | 4 | Section to cover reserving methods. 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | development/index 10 | tail/index 11 | expected_loss 12 | bf 13 | cape_cod 14 | -------------------------------------------------------------------------------- /faslr/model/__init__.py: -------------------------------------------------------------------------------- 1 | from faslr.model.model import ( 2 | FModelWidget 3 | ) 4 | 5 | from faslr.model.index import ( 6 | FModelIndex 7 | ) 8 | 9 | from faslr.model.ibnr import ( 10 | FIBNRModel, 11 | FIBNRWidget 12 | ) -------------------------------------------------------------------------------- /faslr/tests/test_project_item.py: -------------------------------------------------------------------------------- 1 | from faslr.project_item import ProjectItem 2 | 3 | def test_project_item() -> None: 4 | 5 | project_item = ProjectItem( 6 | text="My Project" 7 | ) 8 | 9 | assert project_item.text() == "My Project" -------------------------------------------------------------------------------- /faslr/index/index_constructor.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import ( 2 | QHBoxLayout, 3 | QWidget 4 | ) 5 | 6 | 7 | class IndexConstructor(QWidget): 8 | def __init__(self): 9 | super().__init__() 10 | 11 | self.layout = QHBoxLayout() -------------------------------------------------------------------------------- /docs/_static/favicon_instructions.md: -------------------------------------------------------------------------------- 1 | Generated from https://favicon.io/favicon-generator/. 2 | 3 | Text: F 4 | Font Color: #FFFFFF 5 | Background Color: #459DB9 6 | Background: Square 7 | Font Family: League Spartan 8 | Font Variant: Regular 400 Normal 9 | Font Size: 110 -------------------------------------------------------------------------------- /docs/user/methods/development/development.rst: -------------------------------------------------------------------------------- 1 | The Development Method 2 | ====================== 3 | 4 | The development method, also known as the chain ladder method, is one of the fundamental reserving techniques. 5 | 6 | .. image:: https://faslr.com/media/development_method.png -------------------------------------------------------------------------------- /faslr/demos/arguments_demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from faslr.data import ImportArgumentsTab 4 | 5 | from PyQt6.QtWidgets import QApplication 6 | 7 | app = QApplication(sys.argv) 8 | 9 | arg_tab = ImportArgumentsTab() 10 | 11 | arg_tab.show() 12 | 13 | app.exec() 14 | -------------------------------------------------------------------------------- /faslr/style/extract.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from PyQt6.QtWidgets import ( 4 | QApplication, 5 | QCommonStyle, 6 | QMessageBox, 7 | QWidget 8 | ) 9 | app = QApplication(sys.argv) 10 | 11 | test = QWidget() 12 | test.style() 13 | test.styleSheet() 14 | 15 | app.style() -------------------------------------------------------------------------------- /docs/user/extensions/index.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | One of FASLR's design principles is that it be extensible. This will enable users to create custom models, charts, and other features that might not be available in base FASLR. Information about extensions will be kept here. 5 | -------------------------------------------------------------------------------- /faslr/constants/expected_loss.py: -------------------------------------------------------------------------------- 1 | EXPECTED_LOSS_RATIO_AVERAGES = { 2 | 'Geometric': 'geometric', 3 | 'Medial': 'medial', # latest x years excluding high low 4 | 'Regression': 'regression', 5 | 'Straight': 'simple', 6 | 'Volume': 'volume' 7 | } -------------------------------------------------------------------------------- /faslr/tests/test_about.py: -------------------------------------------------------------------------------- 1 | from faslr.about import AboutDialog 2 | 3 | 4 | def test_about(qtbot): 5 | widget = AboutDialog() 6 | widget.show() 7 | qtbot.addWidget(widget) 8 | 9 | assert widget.windowTitle() == "About" 10 | assert widget.parent is None 11 | 12 | widget.ok() -------------------------------------------------------------------------------- /faslr/style/icons/octicons/issue-opened-24.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/demos/ldf_dialog_demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from faslr.factor import LDFAverageBox, CheckBoxStyle 4 | from PyQt6.QtWidgets import QApplication 5 | 6 | app = QApplication(sys.argv) 7 | 8 | box = LDFAverageBox() 9 | 10 | app.setStyle(CheckBoxStyle()) 11 | 12 | box.show() 13 | 14 | app.exec() 15 | -------------------------------------------------------------------------------- /faslr/demos/import_wizard_demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from faslr.data import DataImportWizard 4 | 5 | from PyQt6.QtWidgets import QApplication 6 | 7 | app = QApplication(sys.argv) 8 | 9 | wizard = DataImportWizard() 10 | wizard.setWindowTitle("Import Wizard") 11 | 12 | wizard.show() 13 | 14 | app.exec() 15 | -------------------------------------------------------------------------------- /faslr/style/icons/check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/user/exhibit/index.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Introduction 3 | ============ 4 | 5 | Exhibits are used to display the results of reserving models to the user, and can also be used for governance tasks such comparing models year over year. 6 | 7 | .. image:: https://faslr.com/media/Exhibit-Builder_006.png 8 | :align: center -------------------------------------------------------------------------------- /faslr/demos/about_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demo for the About dialog box. 3 | """ 4 | import sys 5 | 6 | from faslr.about import AboutDialog 7 | 8 | from PyQt6.QtWidgets import ( 9 | QApplication 10 | ) 11 | 12 | app = QApplication(sys.argv) 13 | 14 | about = AboutDialog() 15 | 16 | about.show() 17 | 18 | app.exec() -------------------------------------------------------------------------------- /faslr/model/model.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import ( 2 | QWidget 3 | ) 4 | 5 | 6 | class FModelWidget(QWidget): 7 | """ 8 | Base model widget. Contains selection_tab, ibnr_summary. 9 | """ 10 | def __init__(self): 11 | super().__init__() 12 | 13 | self.selection_tab = None 14 | -------------------------------------------------------------------------------- /faslr/style/icons/nav-arrow-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/style/icons/nav-arrow-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/__init__.py: -------------------------------------------------------------------------------- 1 | # from faslr.factor import ( 2 | # LDFAverageModel, 3 | # LDFAverageView, 4 | # FactorModel, 5 | # FactorView 6 | # ) 7 | # 8 | # from faslr.analysis import ( 9 | # AnalysisTab 10 | # ) 11 | 12 | from faslr.project import ( 13 | ProjectDialog, 14 | ProjectTreeView 15 | ) 16 | -------------------------------------------------------------------------------- /faslr/style/icons/arrow-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/user/interface/analysis.rst: -------------------------------------------------------------------------------- 1 | Analysis Pane 2 | ============= 3 | 4 | The Analysis Pane is located on the right side of the :doc:`Main Window
` and is the place where the user will do most of their work. It is used to view data, build models, and construct exhibits. 5 | 6 | .. image:: https://faslr.com/media/analysis_pane.png 7 | -------------------------------------------------------------------------------- /faslr/demos/methods/expected_loss/ratio_average_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Boilerplate code for writing demos. 3 | """ 4 | import sys 5 | 6 | from faslr.demos.sample_db import set_sample_db 7 | 8 | from PyQt6.QtWidgets import ( 9 | QApplication 10 | ) 11 | 12 | set_sample_db() 13 | 14 | app = QApplication(sys.argv) 15 | 16 | app.exec() -------------------------------------------------------------------------------- /faslr/style/icons/arrow-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/style/icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/style/icons/arrow-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/demos/file_dialog_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Boilerplate code for writing demos. 3 | """ 4 | import sys 5 | 6 | from PyQt6.QtWidgets import ( 7 | QApplication, 8 | QFileDialog 9 | ) 10 | 11 | app = QApplication(sys.argv) 12 | 13 | dialog = QFileDialog() 14 | dialog.getSaveFileName() 15 | # dialog.show() 16 | 17 | app.exec() 18 | -------------------------------------------------------------------------------- /faslr/style/triangle.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtGui import QColor 2 | 3 | BLANK_TEXT = "" 4 | 5 | EXCL_FACTOR_COLOR = QColor(255, 230, 230) 6 | 7 | LOWER_DIAG_COLOR = QColor(238, 237, 238) 8 | 9 | MAIN_TRIANGLE_COLOR = QColor(255, 255, 255) 10 | 11 | RATIO_STYLE = "{0:,.3f}" 12 | 13 | VALUE_STYLE = "{0:,.0f}" 14 | 15 | PERCENT_STYLE = "{:.1%}" -------------------------------------------------------------------------------- /faslr/demos/settings_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demo for settings dialog box. 3 | """ 4 | import sys 5 | 6 | from faslr.settings import ( 7 | SettingsDialog 8 | ) 9 | 10 | from PyQt6.QtWidgets import ( 11 | QApplication 12 | ) 13 | 14 | app = QApplication(sys.argv) 15 | 16 | settings = SettingsDialog() 17 | 18 | settings.show() 19 | 20 | app.exec() -------------------------------------------------------------------------------- /faslr/demos/template.py: -------------------------------------------------------------------------------- 1 | """ 2 | Boilerplate code for writing demos. 3 | """ 4 | import sys 5 | 6 | from faslr.demos.sample_db import set_sample_db 7 | 8 | from PyQt6.QtWidgets import ( 9 | QApplication 10 | ) 11 | 12 | set_sample_db() 13 | 14 | # widget = ... 15 | 16 | app = QApplication(sys.argv) 17 | 18 | # widget.show() 19 | 20 | app.exec() -------------------------------------------------------------------------------- /faslr/index/model_index.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtGui import QStandardItemModel 2 | 3 | from PyQt6.QtWidgets import ( 4 | QTreeView 5 | ) 6 | 7 | 8 | class ModelIndexView(QTreeView): 9 | def __init__(self): 10 | super().__init__() 11 | 12 | 13 | class ModelIndexModel(QStandardItemModel): 14 | def __init__(self): 15 | super().__init__() -------------------------------------------------------------------------------- /faslr/demos/project_dialog_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Boilerplate code for writing demos. 3 | """ 4 | import sys 5 | 6 | from faslr.project import ProjectDialog 7 | 8 | from PyQt6.QtWidgets import ( 9 | QApplication 10 | ) 11 | 12 | app = QApplication(sys.argv) 13 | 14 | project_dialog = ProjectDialog() 15 | 16 | project_dialog.show() 17 | 18 | app.exec() 19 | -------------------------------------------------------------------------------- /faslr/style/icons/bar-chart-2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/style/icons/cancel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/utilities/collections.py: -------------------------------------------------------------------------------- 1 | def subset_dict(input_dict: dict, keys: list) -> dict: 2 | """ 3 | Given an input dictionary and a list of desired keys, return a subset dictionary containing only those keys. 4 | :param input_dict: 5 | :param keys: 6 | :return: 7 | """ 8 | idx_dict = {k: input_dict[k] for k in keys} 9 | 10 | return idx_dict 11 | -------------------------------------------------------------------------------- /faslr/index/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import ( 2 | calculate_index_factors, 3 | FIndex, 4 | FStandardIndexItem, 5 | IndexTableView, 6 | IndexTableModel, 7 | IndexInventory, 8 | IndexInventoryModel, 9 | IndexInventoryView 10 | ) 11 | 12 | from .index_matrix import ( 13 | IndexMatrixModel, 14 | IndexMatrixView, 15 | IndexMatrixWidget 16 | ) -------------------------------------------------------------------------------- /faslr/style/icons/graph-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /faslr/style/icons/octicons/commit-24.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/tests/utilities/test_queries.py: -------------------------------------------------------------------------------- 1 | from faslr.utilities.queries import delete_country 2 | 3 | from faslr.connection import FaslrConnection 4 | 5 | 6 | def test_delete_country(sample_db: str) -> None: 7 | 8 | f_connection = FaslrConnection( 9 | db_path=sample_db 10 | ) 11 | 12 | delete_country( 13 | country_id=1, 14 | session=f_connection.session 15 | ) 16 | -------------------------------------------------------------------------------- /faslr/utilities/queries.py: -------------------------------------------------------------------------------- 1 | from faslr.schema import ( 2 | LocationTable 3 | ) 4 | 5 | from sqlalchemy.orm import Session 6 | 7 | 8 | def delete_country( 9 | country_id: int, 10 | session: Session 11 | ) -> None: 12 | 13 | country = session.query(LocationTable).filter(LocationTable.location_id == country_id).one() 14 | session.delete(country) 15 | session.commit() 16 | -------------------------------------------------------------------------------- /faslr/style/icons/open-in-browser.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/tests/utilities/test_sample.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from faslr.utilities.sample import load_sample 3 | 4 | 5 | def test_us_industry_auto(): 6 | load_sample('us_industry_auto') 7 | 8 | 9 | def test_xyz(): 10 | load_sample('xyz') 11 | 12 | def test_mack97(): 13 | load_sample('mack97') 14 | 15 | def test_invalid_sample(): 16 | with pytest.raises(Exception): 17 | load_sample("blah") -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: faslr 2 | Maintainer: Gene Dan 3 | Build-Depends: debhelper,dh-python,python3-all,python3-setuptools 4 | Section: devel 5 | Priority: optional 6 | Standards-Version: 3.9.6 7 | X-Python3-Version: >= 3.6 8 | 9 | Package: faslr 10 | Architecture: all 11 | Description: Free Actuarial System for Loss Reserving 12 | Depends: ${python3:Depends},python3-requests,python3-chainladder -------------------------------------------------------------------------------- /faslr/style/icons/kanban-board.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/demos/index/model_index_demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from faslr.demos.sample_db import set_sample_db 4 | 5 | from faslr.model import FModelIndex 6 | 7 | from PyQt6.QtWidgets import ( 8 | QApplication 9 | ) 10 | 11 | set_sample_db() 12 | 13 | app = QApplication(sys.argv) 14 | 15 | widget = FModelIndex() 16 | 17 | widget.setWindowTitle("Model Index Demo") 18 | 19 | widget.show() 20 | 21 | app.exec() 22 | -------------------------------------------------------------------------------- /faslr/samples/friedland_qs.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Gross Reported Claims,Net Reported Claims,Net to Gross 2 | 2005,2005,35839,25087,0.7 3 | 2005,2006,42290,29603,0.7 4 | 2005,2007,47365,33155,0.7 5 | 2005,2008,49733,34813,0.7 6 | 2006,2006,37452,26216,0.7 7 | 2006,2007,44568,31197,0.7 8 | 2006,2008,49024,34317,0.7 9 | 2007,2007,39324,33426,0.85 10 | 2007,2008,46009,39108,0.85 11 | 2008,2008,41212,37091,0.9 12 | -------------------------------------------------------------------------------- /docs/api/index.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============== 3 | 4 | This section covers the collection of Python objects (modules, classes, methods, functions, etc.) that comprise FASLR. One of the aims of FASLR is to demonstrate complete transparency over the actuarial reserving process. This is intended for contributors looking to improve FASLR, as well as users who are interested in understanding how the program works on a technical level. -------------------------------------------------------------------------------- /docs/user/methods/development/diagnostics.rst: -------------------------------------------------------------------------------- 1 | Development Diagnostics 2 | ======================= 3 | 4 | FASLR provides three diagnostic tests to check whether the chain ladder method assumptions are satistfied: 5 | 6 | #. Mack development correlation test 7 | #. Mack individual-year valuation correlation test 8 | #. Mack all-year valuation correlation test 9 | 10 | .. image:: https://faslr.com/media/mack_diagnostics.png -------------------------------------------------------------------------------- /faslr/samples/friedland_xol.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Gross Reported Claims,Net Reported Claims,Ceded Reported Claims 2 | 2005,2005,12199,11752,447 3 | 2005,2006,15615,14076,1539 4 | 2005,2007,18425,16502,1924 5 | 2005,2008,20268,18056,2212 6 | 2006,2006,12992,12992,0 7 | 2006,2007,16890,16890,0 8 | 2006,2008,20267,20267,0 9 | 2007,2007,13901,13644,257 10 | 2007,2008,17655,17303,352 11 | 2008,2008,14735,14735,0 12 | -------------------------------------------------------------------------------- /docs/user/interface/theme.rst: -------------------------------------------------------------------------------- 1 | Theming 2 | ======= 3 | 4 | FASLR's default theme is the Qt Fusion theme. There are plans to also include a dark mode theme, as noted in `this ticket `_. 5 | 6 | Since work should be enjoyable, it is my intention to make FASLR theming as customizable for the user. Those who are interested in making their own custom themes may do so by taking advantage of Qt's QSS functionality. -------------------------------------------------------------------------------- /faslr/demos/common/model_demo_blank.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demo for base model class. 3 | """ 4 | import sys 5 | 6 | from faslr.demos.sample_db import set_sample_db 7 | from faslr.common.model import FSelectionModelWidget 8 | 9 | from PyQt6.QtWidgets import ( 10 | QApplication 11 | ) 12 | 13 | set_sample_db() 14 | 15 | 16 | 17 | app = QApplication(sys.argv) 18 | 19 | model = FSelectionModelWidget() 20 | 21 | model.show() 22 | 23 | app.exec() -------------------------------------------------------------------------------- /faslr/demos/index/index_inventory_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demo of the IndexInventory widget. 3 | """ 4 | 5 | import sys 6 | from faslr.demos.sample_db import set_sample_db 7 | 8 | from faslr.index import ( 9 | IndexInventory 10 | ) 11 | 12 | from PyQt6.QtWidgets import ( 13 | QApplication 14 | ) 15 | 16 | set_sample_db() 17 | 18 | app = QApplication(sys.argv) 19 | 20 | widget = IndexInventory() 21 | 22 | widget.show() 23 | 24 | app.exec() -------------------------------------------------------------------------------- /faslr/demos/methods/development/factor_demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from faslr.methods.development import DevelopmentTab 4 | from PyQt6.QtWidgets import QApplication 5 | from faslr.utilities.sample import load_sample 6 | 7 | test = load_sample("us_industry_auto") 8 | 9 | app = QApplication(sys.argv) 10 | 11 | mytab = DevelopmentTab( 12 | triangle=test, 13 | column='Paid Claims' 14 | ) 15 | 16 | mytab.show() 17 | 18 | app.exec() 19 | -------------------------------------------------------------------------------- /faslr/demos/tail_demo.py: -------------------------------------------------------------------------------- 1 | import chainladder as cl 2 | import sys 3 | 4 | from PyQt6.QtWidgets import QApplication 5 | 6 | from faslr.tail import ( 7 | TailPane 8 | ) 9 | 10 | # triangle = cl.load_sample('genins') 11 | triangle = cl.load_sample('quarterly')['paid'] 12 | app = QApplication(sys.argv) 13 | 14 | tail_pane = TailPane( 15 | triangle=triangle, 16 | xkcd=True 17 | ) 18 | 19 | tail_pane.show() 20 | 21 | app.exec() 22 | -------------------------------------------------------------------------------- /faslr/style/icons/db.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/demos/methods/expected_loss/expected_loss_blank_demo.py: -------------------------------------------------------------------------------- 1 | import chainladder as cl 2 | import sys 3 | 4 | from faslr.demos.sample_db import set_sample_db 5 | 6 | from faslr.methods import ( 7 | ExpectedLossWidget 8 | ) 9 | 10 | from PyQt6.QtWidgets import ( 11 | QApplication 12 | ) 13 | 14 | set_sample_db() 15 | 16 | app = QApplication(sys.argv) 17 | 18 | widget = ExpectedLossWidget() 19 | 20 | widget.show() 21 | 22 | app.exec() -------------------------------------------------------------------------------- /faslr/tests/index/test_model_index.py: -------------------------------------------------------------------------------- 1 | from faslr.index.model_index import ( 2 | ModelIndexView, 3 | ModelIndexModel 4 | ) 5 | 6 | from pytestqt.qtbot import QtBot 7 | 8 | 9 | def test_model_index_view(qtbot: QtBot) -> None: 10 | 11 | model_index_model = ModelIndexModel() 12 | 13 | model_index_view = ModelIndexView() 14 | 15 | qtbot.addWidget(model_index_view) 16 | 17 | model_index_view.setModel(model_index_model) 18 | -------------------------------------------------------------------------------- /faslr/tests/assets/xyz_table.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Paid Claims,Ultimate Paid Claims 2 | 1998,15822.0,15822.0 3 | 1999,24817.0,24908.308424257804 4 | 2000,36782.0,37544.60076838961 5 | 2001,38519.0,40488.68931461702 6 | 2002,44437.0,49301.6771158607 7 | 2003,39320.0,49233.569536754076 8 | 2004,52811.0,80207.50728101416 9 | 2005,40026.0,80811.57248005971 10 | 2006,22819.0,71846.90509646817 11 | 2007,11865.0,78307.86962908747 12 | 2008,3409.0,84722.01961885256 13 | -------------------------------------------------------------------------------- /faslr/tests/utilities/test_collections.py: -------------------------------------------------------------------------------- 1 | from faslr.utilities import subset_dict 2 | 3 | input_dict = { 4 | 'a': [1, 2, 3], 5 | 'b': [4, 5, 6], 6 | 'c': [7, 9, 9] 7 | } 8 | 9 | dict_expectation = { 10 | 'a': [1, 2, 3], 11 | 'b': [4, 5, 6] 12 | } 13 | 14 | def test_subset_dict(): 15 | dict_test = subset_dict( 16 | input_dict=input_dict, 17 | keys=['a', 'b'] 18 | ) 19 | 20 | assert dict_test == dict_expectation -------------------------------------------------------------------------------- /faslr/style/icons/delete-circled-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/tests/utilities/test_connection.py: -------------------------------------------------------------------------------- 1 | from faslr.connection import FaslrConnection 2 | from faslr.constants import DEFAULT_DIALOG_PATH 3 | 4 | from faslr.utilities import set_sqlite_pragma 5 | 6 | 7 | def test_fk_pragma(): 8 | 9 | fconn = FaslrConnection( 10 | db_path=DEFAULT_DIALOG_PATH + '/sample.db' 11 | ) 12 | 13 | set_sqlite_pragma( 14 | dbapi_connection=fconn.raw_connection, 15 | connection_record=None 16 | ) 17 | -------------------------------------------------------------------------------- /faslr/tests/copy_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from os.path import dirname 5 | from shutil import copyfile 6 | 7 | faslr_path = dirname(dirname(dirname(os.path.realpath(__file__)))) 8 | sys.path.append(faslr_path) 9 | 10 | from faslr.constants import ( 11 | CONFIG_PATH, 12 | CONFIG_TEMPLATES_PATH 13 | ) 14 | 15 | if not os.path.exists(CONFIG_PATH): 16 | 17 | copyfile( 18 | src=CONFIG_TEMPLATES_PATH, 19 | dst=CONFIG_PATH 20 | ) -------------------------------------------------------------------------------- /faslr/style/icons/message-text.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/demos/methods/expected_loss/expected_loss_apriori_blank_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Boilerplate code for writing demos. 3 | """ 4 | import sys 5 | 6 | from faslr.demos.sample_db import set_sample_db 7 | from faslr.methods.expected_loss import ExpectedLossAprioriWidget 8 | 9 | from PyQt6.QtWidgets import ( 10 | QApplication 11 | ) 12 | 13 | set_sample_db() 14 | 15 | app = QApplication(sys.argv) 16 | 17 | widget = ExpectedLossAprioriWidget() 18 | 19 | widget.show() 20 | 21 | app.exec() -------------------------------------------------------------------------------- /faslr/demos/project_tree_view_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Boilerplate code for writing demos. 3 | """ 4 | import sys 5 | 6 | from faslr.project import ( 7 | ProjectTreeView, 8 | ProjectModel 9 | ) 10 | 11 | from PyQt6.QtWidgets import ( 12 | QApplication 13 | ) 14 | 15 | app = QApplication(sys.argv) 16 | 17 | project_tree_view = ProjectTreeView() 18 | project_model = ProjectModel() 19 | project_tree_view.setModel(project_model) 20 | 21 | project_tree_view.show() 22 | 23 | app.exec() -------------------------------------------------------------------------------- /faslr/style/icons/check-circled-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/tests/core/test_application.py: -------------------------------------------------------------------------------- 1 | # import sys 2 | # 3 | # import pytest 4 | # 5 | # from faslr.core import FApplication 6 | # 7 | # from pytestqt.qtbot import QtBot 8 | # 9 | # 10 | # @pytest.fixture(scope="session") 11 | # def f_application(): 12 | # 13 | # app = FApplication(sys.argv) 14 | # 15 | # return app 16 | # 17 | # 18 | # def test_f_application( 19 | # qtbot: QtBot, 20 | # f_application: FApplication 21 | # ) -> None: 22 | # 23 | # app = f_application 24 | # 25 | -------------------------------------------------------------------------------- /faslr/tests/utilities/test_gui.py: -------------------------------------------------------------------------------- 1 | from faslr.utilities import open_item_tab 2 | 3 | from PyQt6.QtWidgets import ( 4 | QTabWidget, 5 | QWidget 6 | ) 7 | 8 | def test_open_item_tab(qtbot): 9 | tab_title = "test_title" 10 | tab_widget = QTabWidget() 11 | tab_item = QWidget() 12 | 13 | qtbot.addWidget(tab_widget) 14 | qtbot.addWidget(tab_item) 15 | 16 | open_item_tab( 17 | title=tab_title, 18 | tab_widget=tab_widget, 19 | item_widget=tab_item 20 | ) -------------------------------------------------------------------------------- /faslr/style/icons/text-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/style/icons/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | codecov: codecov/codecov@3.2.2 5 | 6 | jobs: 7 | test-api: 8 | docker: 9 | - image: cimg/python:3.10.2 10 | steps: 11 | - checkout 12 | - run: 13 | name: Install requirements 14 | command: pip install -r requirements.txt 15 | - run: 16 | name: Run tests and collect coverage 17 | command: pytest --cov faslr/tests 18 | - codecov/upload 19 | 20 | workflows: 21 | version: 2.1 22 | build-test: 23 | jobs: 24 | - test-api -------------------------------------------------------------------------------- /faslr/style/icons/README.md: -------------------------------------------------------------------------------- 1 | # Icon Sources 2 | 3 | https://iconoir.com/ 4 | 5 | https://feathericons.com/ 6 | 7 | https://icons.getbootstrap.com/ 8 | 9 | https://thenounproject.com/ 10 | 11 | https://www.iconhunt.site/ 12 | 13 | https://github.com/primer/octicons 14 | 15 | https://www.svgrepo.com/svg/520383/xkcd 16 | 17 | # Purchased 18 | 19 | The following icons were purchased: 20 | 21 | curve-graph.svg was purchased from Noun - royalty free unlimited amount of time 22 | scatter-plot.svg was purchased from Noun - royalty free unlimited amount of time -------------------------------------------------------------------------------- /faslr/constants/triangle.py: -------------------------------------------------------------------------------- 1 | TIME_FIELDS = [ 2 | 'ACCIDENT YEAR', 3 | 'POLICY YEAR', 4 | 'CALENDAR YEAR', 5 | 'REPORT YEAR' 6 | ] 7 | 8 | LOSS_FIELDS = [ 9 | 'PAID LOSS', 10 | 'INCURRED LOSS', 11 | 'CASE RESERVES', 12 | 'CASE INCURRED', 13 | 'PAID CLAIMS', 14 | 'REPORTED CLAIMS' 15 | ] 16 | 17 | ORIGIN_FIELDS = [ 18 | 'ACCIDENT YEAR' 19 | ] 20 | 21 | DEVELOPMENT_FIELDS = [ 22 | 'CALENDAR YEAR' 23 | ] 24 | 25 | GRAINS = [ 26 | 'Annual', 27 | 'Semi-Annual', 28 | 'Quarterly', 29 | 'Monthly' 30 | ] 31 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Python 3.10.6 2 | 3 | chainladder==0.8.24 4 | GitPython==3.1.44 5 | ipython==8.37.0 6 | matplotlib==3.10.3 7 | numba==0.61.2 8 | numpy==2.2.6 9 | pandas==2.3.1 10 | PyQt6==6.4.2 11 | PyQt6-Qt6==6.4.2 12 | PyQt6-sip==13.4.1 13 | scikit-learn==1.7.0 14 | scipy==1.15.3 15 | sparse==0.17.0 16 | setuptools==80.9.0 17 | sqlalchemy==2.0.41 18 | 19 | # Documentation 20 | sphinx==8.1.3 21 | sphinx_design==0.6.1 22 | pydata-sphinx-theme==0.16.1 23 | 24 | # Testing 25 | pynput==1.8.1 26 | pytest==8.4.1 27 | pytest-cov==6.2.1 28 | pytest-qt==4.5.0 29 | pytest-mock==3.14.1 30 | -------------------------------------------------------------------------------- /docs/user/interface/settings.rst: -------------------------------------------------------------------------------- 1 | User Settings 2 | ============= 3 | 4 | The User Settings allow the user to customize FASLR for a more personalized experienced. The User Settings dialog box can be activated by navigating to the File dropdown menu of the :doc:`Main Menu `. 5 | 6 | .. image:: https://faslr.com/media/user_settings.png 7 | :width: 500px 8 | 9 | | 10 | 11 | The current options are: 12 | 13 | - Startup: Allows the user to automatically connect to a database upon startup 14 | - User: Allows the user to delete their configuration file and restore the default user settings 15 | -------------------------------------------------------------------------------- /faslr/style/icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/style/icons/no-link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/constants/development.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | LDF_AVERAGES = { 4 | # 'Geometric': 'geometric', 5 | # 'Medial': 'medial', 6 | 'Regression': 'regression', 7 | 'Straight': 'simple', 8 | 'Volume': 'volume' 9 | } 10 | 11 | TEMP_LDF_LIST = pd.DataFrame( 12 | data=[ 13 | [True, "All-year volume-weighted", "Volume", "9"], 14 | [False, "3-year volume-weighted", "Volume", "3"], 15 | [False, "5-year volume-weighted", "Volume", "5"], 16 | ], 17 | columns=["Selected", "Label", "Type", "Number of Years"] 18 | ) 19 | 20 | 21 | -------------------------------------------------------------------------------- /faslr/style/icons/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/demos/sample_db.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | import faslr.core 5 | import faslr.core as core 6 | 7 | from faslr.constants import ( 8 | ROOT_PATH, 9 | SAMPLE_DB_DEFAULT_PATH 10 | ) 11 | 12 | def set_sample_db() -> None: 13 | """ 14 | Overrides the default db to be the sample db, and not the one the user may be connected to in the config. 15 | """ 16 | 17 | if os.path.exists(SAMPLE_DB_DEFAULT_PATH): 18 | pass 19 | else: 20 | subprocess.run(['python', ROOT_PATH + '/samples/db/generate_sample_db.py']) 21 | core.use_sample = True 22 | faslr.core.set_db(SAMPLE_DB_DEFAULT_PATH) 23 | -------------------------------------------------------------------------------- /faslr/style/icons/bookmark-book.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/user/interface/main.rst: -------------------------------------------------------------------------------- 1 | Main Window 2 | =========== 3 | 4 | The Main Window is the first thing you see when you run FASLR. It consists of three main sections: 5 | 6 | #. Main Menu 7 | #. Project Tree 8 | #. Analysis Pane 9 | 10 | .. image:: https://faslr.com/media/ui_sections.png 11 | 12 | The main menu is used to access various dialogs to configure FASLR. The project tree is used for browsing and selecting projects, and the analysis pane is where most of the work is done. It is used to browse project data, build reserving models, and compile reports. More information about these sections can be found by navigating to their respective articles. 13 | 14 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/user/schema/index.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ================ 3 | 4 | FASLR stores project metadata and data in a relational database. SQLite is the current database technology used for this purpose. While the ultimate vision is to eventually use enterprise-level databases (such as SQL Server), SQLite allows rapid changes to the underlying FASLR schema, makes FASLR easier to distribute, and eases the startup burden on the user. 5 | 6 | Should the project become more serious, the plan is to adapt FASLR to more serious databases. 7 | 8 | More detailed information can be found on the `SchemaSpy `_ page. 9 | 10 | .. image:: https://faslr.com/media/schema.png 11 | -------------------------------------------------------------------------------- /faslr/utilities/dataframe.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains commonly-used DataFrame-related routines. 3 | """ 4 | from __future__ import annotations 5 | 6 | from typing import TYPE_CHECKING 7 | 8 | if TYPE_CHECKING: 9 | from pandas import DataFrame 10 | 11 | def df_set_false(df: DataFrame) -> DataFrame: 12 | """ 13 | Sets an entire DataFrame to False. Used in situations where we want a triangle of booleans where 14 | we first make a copy of another DataFrame full of data (like paid loss) in order to preserve parts of its metadata 15 | (accident years, development periods, etc.). 16 | """ 17 | 18 | df = df.astype(bool) 19 | df.loc[:] = False 20 | 21 | return df -------------------------------------------------------------------------------- /faslr/demos/data_pane_demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from faslr.constants import DEFAULT_DIALOG_PATH 4 | import faslr.core as core 5 | from faslr.data import DataPane 6 | from faslr.__main__ import MainWindow 7 | 8 | from PyQt6.QtWidgets import QApplication, QTabWidget 9 | app = QApplication(sys.argv) 10 | 11 | # main_window = MainWindow() 12 | # main_window.db = DEFAULT_DIALOG_PATH + '/sample.db' 13 | 14 | core.set_db(path=DEFAULT_DIALOG_PATH + '/sample.db') 15 | 16 | parent_tab = QTabWidget() 17 | 18 | data_pane = DataPane(parent=parent_tab) 19 | data_pane.setWindowTitle("Project Data Views") 20 | 21 | parent_tab.addTab(data_pane, "Data Pane") 22 | 23 | parent_tab.show() 24 | 25 | app.exec() 26 | -------------------------------------------------------------------------------- /faslr/style/icons/octicons/git-branch-24.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /faslr/demos/triangle_demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from faslr.triangle_model import ( 4 | TriangleModel, 5 | TriangleView 6 | ) 7 | 8 | from faslr.utilities.sample import load_sample 9 | 10 | from PyQt6.QtWidgets import ( 11 | QApplication, 12 | QWidget, 13 | QVBoxLayout 14 | ) 15 | 16 | 17 | app = QApplication(sys.argv) 18 | 19 | xyz = load_sample('xyz') 20 | xyz = xyz['Reported Claims'] 21 | 22 | triangle_widget = QWidget() 23 | layout = QVBoxLayout() 24 | triangle_widget.setLayout(layout) 25 | 26 | model = TriangleModel(triangle=xyz, value_type='value') 27 | view = TriangleView() 28 | view.setModel(model) 29 | 30 | layout.addWidget(view) 31 | 32 | triangle_widget.show() 33 | 34 | 35 | app.exec() -------------------------------------------------------------------------------- /faslr/demos/exhibit_builder_demo.py: -------------------------------------------------------------------------------- 1 | import chainladder as cl 2 | import sys 3 | 4 | from faslr.exhibit import ExhibitBuilder 5 | from faslr.utilities.sample import load_sample 6 | from PyQt6.QtWidgets import QApplication 7 | 8 | triangle = load_sample('xyz') 9 | 10 | paid = triangle['Paid Claims'] 11 | reported = triangle['Reported Claims'] 12 | 13 | paid_dev = cl.Development().fit_transform(paid) 14 | reported_dev = cl.Development().fit_transform(reported) 15 | 16 | cl_paid = cl.Chainladder().fit(paid_dev) 17 | cl_reported = cl.Chainladder().fit(reported_dev) 18 | app = QApplication(sys.argv) 19 | 20 | exhibit_builder = ExhibitBuilder( 21 | triangles=[cl_paid, cl_reported] 22 | ) 23 | 24 | exhibit_builder.show() 25 | 26 | app.exec() 27 | -------------------------------------------------------------------------------- /faslr/utilities/gui.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import ( 2 | QTabWidget, 3 | QWidget 4 | ) 5 | 6 | 7 | def open_item_tab( 8 | title: str, 9 | tab_widget: QTabWidget, 10 | item_widget: QWidget 11 | ) -> None: 12 | """ 13 | Used to set focus on a tab right after it is opened. 14 | 15 | :param title: The name of the tab to be opened. 16 | :param tab_widget: The QTabWidget object to which the tab will be added. 17 | :param item_widget: The widget object representing the tab to be opened. 18 | :return: 19 | """ 20 | 21 | tab_widget.addTab( 22 | item_widget, 23 | title 24 | ) 25 | 26 | new_index = tab_widget.count() 27 | tab_widget.setCurrentIndex(new_index - 1) 28 | -------------------------------------------------------------------------------- /docs/user/interface/menu.rst: -------------------------------------------------------------------------------- 1 | Main Menu 2 | ========= 3 | 4 | The main menu is the set of dropdown menus right underneath the :doc:`Main Window
` title bar. These are used for managing database connections and configuring FASLR itself, such as modifying user settings. The menus are: 5 | 6 | - File: Used to connect to databases, for creating new projects, and editing user settings. 7 | - Edit: Used for clipboard operations, such as copying and pasting. 8 | - Tools: Used for changing the underlying calculation engine (right now, only `chainladder-python `_ is supported). 9 | - Help: Displays program information, such as version, and links to the `FASLR GitHub repository `_. 10 | -------------------------------------------------------------------------------- /faslr/style/icons/curve-graph.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /faslr/tests/utilities/test_accessors.py: -------------------------------------------------------------------------------- 1 | import chainladder as cl 2 | 3 | from faslr.utilities.accessors import ( 4 | get_column 5 | ) 6 | 7 | 8 | input_tri = cl.load_sample('clrd') 9 | column_expectation = input_tri[input_tri['LOB'] == 'othliab']['IncurLoss'] 10 | column_expectation_none = input_tri['IncurLoss'] 11 | 12 | def test_get_column() -> None: 13 | 14 | column_test = get_column( 15 | triangle=input_tri, 16 | column='IncurLoss', 17 | lob='othliab' 18 | ) 19 | 20 | assert column_test == column_expectation 21 | 22 | def test_get_column_no_lob() -> None: 23 | 24 | column_test = get_column( 25 | triangle=input_tri, 26 | column='IncurLoss', 27 | lob=None 28 | ) 29 | 30 | assert column_test == column_expectation_none -------------------------------------------------------------------------------- /faslr/style/icons/octicons/comment-discussion-24.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/generate_changelog.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('//faslr') 3 | from datetime import datetime 4 | import time 5 | from faslr.constants import BUILD_VERSION 6 | 7 | d = datetime.now() 8 | datetime_string = d.strftime("%a, %d, %b %Y %H:%M:%S ") 9 | tz_offset = str(int(- time.timezone * 100 / 3600)).zfill(5) 10 | datetime_string = datetime_string + tz_offset 11 | 12 | codename = 'jammy' 13 | urgency = 'medium' 14 | package_name = 'faslr' 15 | 16 | changelog_string = """{} ({}) {}; urgency={} 17 | 18 | * Additional release 19 | 20 | -- Gene Dan {}"""\ 21 | .format(package_name, BUILD_VERSION, codename, urgency, datetime_string) 22 | 23 | with open('debian/changelog', 'w') as changelog_file: 24 | changelog_file.write(changelog_string) 25 | 26 | changelog_file.close() -------------------------------------------------------------------------------- /faslr/demos/index/index_matrix_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Displays an index in matrix format, i.e., each cell is a factor that brings the corresponding row year to 3 | that of the corresponding column year. 4 | """ 5 | import sys 6 | 7 | from faslr.index import ( 8 | FIndex, 9 | IndexMatrixWidget 10 | ) 11 | 12 | from faslr.utilities.sample import ( 13 | XYZ_SAMPLE_YEARS, 14 | XYZ_RATE_CHANGES 15 | ) 16 | 17 | from PyQt6.QtWidgets import ( 18 | QApplication 19 | ) 20 | 21 | index = FIndex( 22 | origin=XYZ_SAMPLE_YEARS, 23 | changes=XYZ_RATE_CHANGES 24 | ) 25 | 26 | app = QApplication(sys.argv) 27 | 28 | index_matrix_widget = IndexMatrixWidget( 29 | matrix=index.matrix 30 | ) 31 | index_matrix_widget.setWindowTitle("Index Matrix Demo") 32 | 33 | index_matrix_widget.show() 34 | 35 | app.exec() -------------------------------------------------------------------------------- /faslr/project_item.py: -------------------------------------------------------------------------------- 1 | from faslr.style.project import DEFAULT_PROJECT_FONT 2 | 3 | from PyQt6.QtGui import ( 4 | QColor, 5 | QFont, 6 | QStandardItem 7 | ) 8 | 9 | 10 | class ProjectItem(QStandardItem): 11 | """ 12 | Represents a row in the project tree. Can be nested within other project items. 13 | """ 14 | def __init__( 15 | self, 16 | text='', 17 | font_size=12, 18 | set_bold=False, 19 | text_color=QColor(0, 0, 0) 20 | ): 21 | super().__init__() 22 | 23 | project_font = QFont( 24 | DEFAULT_PROJECT_FONT, 25 | font_size 26 | ) 27 | project_font.setBold(set_bold) 28 | 29 | self.setForeground(text_color) 30 | self.setFont(project_font) 31 | self.setText(text) 32 | -------------------------------------------------------------------------------- /docs/db/README.md: -------------------------------------------------------------------------------- 1 | # Schema documentation instructions 2 | 3 | ## Requirements 4 | 5 | The schema documentation is generated by schemaspy: https://schemaspy.org/. 6 | 7 | ```shell 8 | wget https://github.com/schemaspy/schemaspy/releases/download/v6.2.3/schemaspy-6.2.3.jar 9 | ``` 10 | 11 | A sqlite JDBC driver is required: 12 | 13 | 14 | https://github.com/xerial/sqlite-jdbc 15 | 16 | ```shell 17 | https://github.com/xerial/sqlite-jdbc/releases/download/3.42.0.0/sqlite-jdbc-3.42.0.0.jar 18 | ``` 19 | 20 | This needs Java to run, currently I have openjdk 11 installed on the Linode instance. 21 | 22 | Commands to render the documentation and move the files to the correct place on the server are in the second half of the build_docs.sh file. 23 | 24 | Instructions on how to ssh into the Linode via GitHub Actions can be found here: 25 | 26 | https://nbailey.ca/post/github-actions-ssh/ -------------------------------------------------------------------------------- /faslr/utilities/__init__.py: -------------------------------------------------------------------------------- 1 | from faslr.utilities.gui import open_item_tab 2 | 3 | from sqlalchemy import event 4 | from sqlalchemy.engine import Engine 5 | 6 | from faslr.utilities.chainladder import ( 7 | fetch_cdf, 8 | fetch_latest_diagonal, 9 | fetch_origin, 10 | fetch_ultimate, 11 | table_from_tri 12 | ) 13 | 14 | from faslr.utilities.collections import ( 15 | subset_dict 16 | ) 17 | 18 | from faslr.utilities.dataframe import ( 19 | df_set_false 20 | ) 21 | 22 | from faslr.utilities.sample import ( 23 | auto_bi_olep, 24 | load_sample, 25 | tort_index, 26 | ppa_loss_trend, 27 | ppa_premium_trend 28 | ) 29 | 30 | 31 | @event.listens_for(Engine, "connect") 32 | def set_sqlite_pragma(dbapi_connection, connection_record): 33 | cursor = dbapi_connection.cursor() 34 | cursor.execute("PRAGMA foreign_keys=ON") 35 | cursor.close() 36 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | name: Docs 9 | defaults: 10 | run: 11 | working-directory: . 12 | steps: 13 | - uses: actions/checkout@v1 14 | - uses: actions/setup-python@v2 15 | with: 16 | python-version: '3.10' 17 | - name: Install ubuntu packages 18 | run: sudo apt-get update 19 | - name: Install requirements 20 | run: sudo apt-get install -y libegl1 21 | - name: Install requirements 22 | run: pip install -r requirements.txt 23 | - name: Docs 24 | uses: appleboy/ssh-action@v0.1.10 25 | with: 26 | host: ${{ secrets.SSH_HOST }} 27 | username: ${{ secrets.SSH_USER }} 28 | key: ${{ secrets.SSH_Key }} 29 | script: cd /root/faslr; git pull; bash /root/faslr/docs/build_docs.sh 30 | -------------------------------------------------------------------------------- /docs/user/index.rst: -------------------------------------------------------------------------------- 1 | User Guide 2 | =========== 3 | 4 | The FASLR user guide covers basic setup and installation, user interface features, and the underlying database schema. 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | :caption: Getting Started 9 | 10 | starting/install 11 | starting/tutorial/index 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | :caption: User Interface 16 | 17 | interface/index 18 | 19 | .. toctree:: 20 | :maxdepth: 1 21 | :caption: Database Schema 22 | 23 | schema/index 24 | 25 | .. toctree:: 26 | :maxdepth: 1 27 | :caption: Actuarial Methods 28 | 29 | methods/index 30 | 31 | .. toctree:: 32 | :maxdepth: 1 33 | :caption: Exhibits 34 | 35 | exhibit/index 36 | 37 | .. toctree:: 38 | :maxdepth: 1 39 | :caption: Extensions 40 | 41 | extensions/index 42 | 43 | .. toctree:: 44 | :maxdepth: 1 45 | :caption: News 46 | 47 | news/xkcd -------------------------------------------------------------------------------- /faslr/demos/faveragebox_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Boilerplate code for writing demos. 3 | """ 4 | import pandas as pd 5 | import sys 6 | 7 | from faslr.demos.sample_db import set_sample_db 8 | 9 | from faslr.model.average import ( 10 | FAverageBox 11 | ) 12 | 13 | from PyQt6.QtWidgets import ( 14 | QApplication 15 | ) 16 | 17 | test_data = pd.DataFrame( 18 | data=[ 19 | [True, "All-year Straight", "Straight", "9"], 20 | [False, "All Years Excl. High/Low", "Straight Excluding High/Low", None], 21 | [False, "3-year Straight", "Straight", "3"], 22 | [False, "5-year Straight", "Straight", "5"], 23 | ], 24 | columns=["Selected", "Label", "Type", "Number of Years"] 25 | ) 26 | 27 | set_sample_db() 28 | 29 | app = QApplication(sys.argv) 30 | 31 | average_box = FAverageBox( 32 | title='FAverageBox', 33 | data=test_data 34 | ) 35 | 36 | average_box.show() 37 | 38 | app.exec() -------------------------------------------------------------------------------- /docs/user/interface/project.rst: -------------------------------------------------------------------------------- 1 | Project Tree 2 | ============ 3 | 4 | The project tree is the pane located on the left side of the :doc:`Main Window
`. It contains the lists of projects that have been saved to the underlying database, organized hierarchically by geographic region. 5 | 6 | 7 | .. image:: https://faslr.com/media/project_tree.png 8 | :width: 400px 9 | 10 | The current hierarchy is: 11 | 12 | Country -> State -> Line of Business (LOB) 13 | 14 | This means that each country may contain multiple states, and each state can have multiple lines of business. There are `plans in the future `_ to allow users to implement a custom hierarchy if the current one does not fit their needs. 15 | 16 | Each node in the tree is uniquely identified by a UUID string. This makes it easier to unambiguously refer to a project so that they may be shared with colleagues. 17 | -------------------------------------------------------------------------------- /faslr/constants/role.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom Qt roles. Not all capitalized to match CamelCase of Qt objects 3 | """ 4 | from PyQt6.QtCore import Qt 5 | 6 | ColumnSpanRole = Qt.ItemDataRole.UserRole + 1 7 | RowSpanRole = ColumnSpanRole + 1 8 | RemoveCellLabelRole = RowSpanRole + 1 9 | RemoveRowSpanRole = RemoveCellLabelRole + 1 10 | RemoveColumnSpanRole = RemoveRowSpanRole + 1 11 | ColumnGroupRole = RowSpanRole + 1 12 | ExhibitColumnRole = ColumnGroupRole + 1 13 | AddColumnRole = ExhibitColumnRole + 1 14 | DropColumnRole = AddColumnRole + 1 15 | ColumnSwapRole = DropColumnRole + 1 16 | ColumnRotateRole = ColumnSwapRole + 1 17 | IndexConstantRole = ColumnRotateRole + 1 18 | AddAverageRole = IndexConstantRole + 1 # for adding averages to available averages 19 | SelectAverageRole = AddAverageRole + 1 # for selecting averages via double-click 20 | UpdateIndexRole = SelectAverageRole + 1 # for updating loss model data due to indexation changes -------------------------------------------------------------------------------- /docs/user/starting/tutorial/project.rst: -------------------------------------------------------------------------------- 1 | Creating a New Project 2 | ====================== 3 | 4 | To begin a reserve study, you must create a project. Projects are organized hierarchically: **Country -> State -> Line of Business**. That is, a country may contain multiple states, and a state may have multiple lines of business. 5 | 6 | To create a project, navigate to **Main Menu -> File -> New Project**. A dialog box will appear asking you to fill out the project's hierarchical information. Note that you must be :doc:`connected ` to a database for the New Project menu action to be active: 7 | 8 | .. image:: https://faslr.com/media/new_project.png 9 | 10 | After filling out the information and clicking **OK**, your new project should appear in the :doc:`Project Tree <../../interface/project>` on the left side of the :doc:`Main Window <../../interface/main>`. 11 | 12 | .. image:: https://faslr.com/media/project_created.png 13 | 14 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | The FASLR documentation can be found by going to: 2 | 3 | https://faslr.com/docs 4 | 5 | # Style Guide 6 | 7 | Due to the documentation's current reliance on [PyData Sphinx Theme](https://pydata-sphinx-theme.readthedocs.io/en/stable/), the styling is subject to change as new versions are released. However, there are some things that I'd like to keep consistent, so here's a checklist of things to adjust if a new version changes the look and feel of things: 8 | 9 | - Landing page should only be as big as what you'd see on a standard 4k monitor (that is, the user should be able to see the entire landing page without having to scroll. 10 | - The gallery page should not have sidebars on either side. 11 | 12 | # Deployment 13 | 14 | New versions of the docs are deployed via SSH by logging into the server, and then running the file **build_docs.sh** that is also located in this directory. This build script will pull the repo contents and then run the sphinx command **make html**. -------------------------------------------------------------------------------- /faslr/engine.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import ( 2 | QDialog, 3 | QButtonGroup, 4 | QRadioButton, 5 | QWidget, 6 | QVBoxLayout 7 | ) 8 | 9 | 10 | class EngineDialog(QDialog): 11 | """ 12 | Dialog box to allow user to toggle reserving engine. 13 | """ 14 | def __init__( 15 | self, 16 | parent=None 17 | ): 18 | super().__init__(parent) 19 | 20 | self.parent = parent 21 | 22 | self.setWindowTitle('Select Reserving Engine') 23 | 24 | self.layout = QVBoxLayout() 25 | 26 | chainladder_python_btn = QRadioButton('chainladder-python') 27 | 28 | chainladder_r_btn = QRadioButton('chainladder-r') 29 | 30 | trellis_btn = QRadioButton('trellis') 31 | 32 | for btn in [ 33 | chainladder_python_btn, 34 | chainladder_r_btn, 35 | trellis_btn 36 | ]: 37 | self.layout.addWidget(btn) 38 | 39 | self.setLayout(self.layout) 40 | 41 | -------------------------------------------------------------------------------- /faslr/tests/utilities/test_chainladder.py: -------------------------------------------------------------------------------- 1 | import chainladder as cl 2 | import pandas as pd 3 | 4 | from faslr.tests import ASSET_PATH 5 | 6 | from faslr.utilities import ( 7 | load_sample, 8 | fetch_cdf, 9 | table_from_tri 10 | ) 11 | 12 | xyz_tri = load_sample('xyz')['Paid Claims'] 13 | xyz_cl = cl.Chainladder().fit(xyz_tri) 14 | 15 | table_expectation = pd.read_csv( 16 | ASSET_PATH + 'xyz_table.csv', 17 | dtype={ 18 | 'Accident Year': object 19 | } 20 | ) 21 | 22 | cdf_expectation = [ 23 | 1.0, 24 | 1.00367926922101, 25 | 1.020732988102594, 26 | 1.0511355257046398, 27 | 1.109473571930164, 28 | 1.2521253697038168, 29 | 1.5187651678819594, 30 | 2.0189769769664645, 31 | 3.1485562512146967, 32 | 6.599904730643697, 33 | 24.852455153667517 34 | ] 35 | 36 | def test_table_from_tri(): 37 | 38 | table_test = table_from_tri(xyz_cl) 39 | pd.testing.assert_frame_equal(table_test, table_expectation) 40 | 41 | def test_fetch_cdf(): 42 | 43 | cdf_test = fetch_cdf(xyz_cl) 44 | 45 | assert cdf_test == cdf_expectation 46 | -------------------------------------------------------------------------------- /faslr/demos/fratioselection_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demo for FRatioSelectionWidget. 3 | """ 4 | import pandas as pd 5 | import sys 6 | 7 | from faslr.demos.sample_db import set_sample_db 8 | from faslr.model.ratio import FRatioSelectionWidget 9 | 10 | from PyQt6.QtWidgets import ( 11 | QApplication 12 | ) 13 | 14 | set_sample_db() 15 | 16 | years = [x for x in range(2000,2011)] 17 | 18 | ratios = {} 19 | for year in years: 20 | ratios[str(year)] = [.5 for x in years] 21 | 22 | 23 | ratios_df = pd.DataFrame(data=ratios, index=years) 24 | 25 | test_averages = pd.DataFrame( 26 | data=[ 27 | [True, "All-year Straight", "Straight", "9"], 28 | [False, "All Years Excl. High/Low", "Straight Excluding High/Low", None], 29 | [False, "3-year Straight", "Straight", "3"], 30 | [False, "5-year Straight", "Straight", "5"], 31 | ], 32 | columns=["Selected", "Label", "Type", "Number of Years"] 33 | ) 34 | 35 | app = QApplication(sys.argv) 36 | 37 | fratio_selection_widget = FRatioSelectionWidget( 38 | averages=test_averages, 39 | ratios=ratios_df 40 | ) 41 | 42 | fratio_selection_widget.show() 43 | 44 | app.exec() -------------------------------------------------------------------------------- /docs/gallery/index.rst: -------------------------------------------------------------------------------- 1 | :html_theme.sidebar_secondary.remove: true 2 | 3 | Gallery 4 | ======= 5 | 6 | .. grid:: 7 | 8 | .. grid-item-card:: Main Window 9 | :img-top: https://faslr.com/media/basic_ui_09082021.png 10 | :link: https://faslr.com/media/basic_ui_09082021.png 11 | 12 | .. grid-item-card:: Heatmap 13 | :img-top: https://faslr.com/media/heatmap.png 14 | :link: https://faslr.com/media/heatmap.png 15 | 16 | .. grid-item-card:: Mack Diagnostics 17 | :img-top: https://faslr.com/media/mack_diagnostics.png 18 | :link: https://faslr.com/media/mack_diagnostics.png 19 | 20 | .. grid:: 21 | 22 | .. grid-item-card:: Custom LDFs 23 | :img-top: https://faslr.com/media/custom_ldfs.png 24 | :link: https://faslr.com/media/custom_ldfs.png 25 | 26 | .. grid-item-card:: Import Wizard 27 | :img-top: https://faslr.com/media/import_wizard_gallery.png 28 | :link: https://faslr.com/media/import_wizard_gallery.png 29 | 30 | .. grid-item-card:: Chain Ladder Method 31 | :img-top: https://faslr.com/media/chain_ladder_method.png 32 | :link: https://faslr.com/media/chain_ladder_method.png 33 | -------------------------------------------------------------------------------- /faslr/demos/index/findex_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demo of the FIndex class. Initializes an FIndex object from the first index in the sample database. 3 | """ 4 | from faslr.demos.sample_db import set_sample_db 5 | 6 | from faslr.index import FIndex 7 | 8 | set_sample_db() 9 | 10 | ult_claims = [ 11 | 15901, 12 | 25123, 13 | 37435, 14 | 39543, 15 | 48953, 16 | 47404, 17 | 77662, 18 | 78497, 19 | 65239, 20 | 62960, 21 | 61262 22 | ] 23 | 24 | earned_premium = [ 25 | 20000, 26 | 31500, 27 | 45000, 28 | 50000, 29 | 61183, 30 | 69175, 31 | 99322, 32 | 138151, 33 | 107578, 34 | 62438, 35 | 47797 36 | ] 37 | 38 | prem_trend = FIndex(from_id=1) 39 | loss_trend = FIndex(from_id=2) 40 | tort_reform = FIndex(from_id=3) 41 | 42 | comp_loss_trend = loss_trend.compose(findexes=[tort_reform]) 43 | 44 | trended_loss_matrix = comp_loss_trend.apply_matrix(values=ult_claims) 45 | 46 | on_level_premium_matrix = prem_trend.apply_matrix(values=earned_premium) 47 | 48 | adj_loss_ratios = trended_loss_matrix.div(on_level_premium_matrix) 49 | 50 | 51 | comp_loss_trend.apply_matrix(values=ult_claims) 52 | -------------------------------------------------------------------------------- /faslr/samples/friedland_auto_bi_insurer.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Paid Claims,Reported Claims 2 | 2000,2000,, 3 | 2000,2001,, 4 | 2000,2002,, 5 | 2000,2003,, 6 | 2000,2004,, 7 | 2000,2005,, 8 | 2000,2006,, 9 | 2000,2007,8673913.04347826,9852941.17647059 10 | 2000,2008,9500000,10000000 11 | 2001,2001,, 12 | 2001,2002,, 13 | 2001,2003,, 14 | 2001,2004,, 15 | 2001,2005,, 16 | 2001,2006,, 17 | 2001,2007,6624000,7922330.09708738 18 | 2001,2008,7200000,8000000 19 | 2002,2002,, 20 | 2002,2003,, 21 | 2002,2004,, 22 | 2002,2005,, 23 | 2002,2006,, 24 | 2002,2007,7037037.03703704,8801818.18181818 25 | 2002,2008,7600000,9400000 26 | 2003,2003,, 27 | 2003,2004,, 28 | 2003,2005,, 29 | 2003,2006,, 30 | 2003,2007,6017142.85714286,14300000 31 | 2003,2008,7800000,15600000 32 | 2004,2004,, 33 | 2004,2005,, 34 | 2004,2006,, 35 | 2004,2007,7840000,14142857.1428571 36 | 2004,2008,11200000,16500000 37 | 2005,2005,, 38 | 2005,2006,, 39 | 2005,2007,5100000,14388888.8888889 40 | 2005,2008,10200000,18500000 41 | 2006,2006,, 42 | 2006,2007,2000000,10241379.3103448 43 | 2006,2008,6000000,16500000 44 | 2007,2007,500000,10150000 45 | 2007,2008,3000000,14000000 46 | 2008,2008,750000,8700000 47 | -------------------------------------------------------------------------------- /faslr/samples/mack_1997.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Case Incurred 2 | 1991,1991,5012 3 | 1991,1992,8269 4 | 1991,1993,10907 5 | 1991,1994,11805 6 | 1991,1995,13539 7 | 1991,1996,16181 8 | 1991,1997,18009 9 | 1991,1998,18608 10 | 1991,1999,18662 11 | 1991,2000,18834 12 | 1992,1992,106 13 | 1992,1993,4285 14 | 1992,1994,5396 15 | 1992,1995,10666 16 | 1992,1996,13782 17 | 1992,1997,15599 18 | 1992,1998,15496 19 | 1992,1999,16169 20 | 1992,2000,16704 21 | 1993,1993,3410 22 | 1993,1994,8992 23 | 1993,1995,13873 24 | 1993,1996,16141 25 | 1993,1997,18735 26 | 1993,1998,22214 27 | 1993,1999,22863 28 | 1993,2000,23466 29 | 1994,1994,5655 30 | 1994,1995,11555 31 | 1994,1996,15766 32 | 1994,1997,21266 33 | 1994,1998,23425 34 | 1994,1999,26083 35 | 1994,2000,27067 36 | 1995,1995,1092 37 | 1995,1996,9565 38 | 1995,1997,15836 39 | 1995,1998,22169 40 | 1995,1999,25955 41 | 1995,2000,26180 42 | 1996,1996,1513 43 | 1996,1997,6445 44 | 1996,1998,11702 45 | 1996,1999,12935 46 | 1996,2000,15852 47 | 1997,1997,557 48 | 1997,1998,4020 49 | 1997,1999,10946 50 | 1997,2000,12314 51 | 1998,1998,1351 52 | 1998,1999,6947 53 | 1998,2000,13112 54 | 1999,1999,3133 55 | 1999,2000,5395 56 | 2000,2000,2063 57 | -------------------------------------------------------------------------------- /faslr/utilities/accessors.py: -------------------------------------------------------------------------------- 1 | from chainladder import Triangle 2 | 3 | # Possibly deprecate this since I have yet a need for it. 4 | # def get_cell_scalar( 5 | # triangle: Triangle, 6 | # origin: str, 7 | # age: int, 8 | # column: str 9 | # ) -> float: 10 | # cell_scalar = triangle[triangle.origin == origin][ 11 | # (triangle.development >= age) & (triangle.development <= age+12) 12 | # ][column].link_ratio.to_frame().squeeze() 13 | # 14 | # return cell_scalar 15 | 16 | 17 | def get_column( 18 | triangle: Triangle, 19 | column: str, 20 | lob: [str, None] 21 | ) -> Triangle: 22 | """ 23 | Extracts a single column from a Triangle class. Assumes the triangle has one company divided by LOBs. 24 | 25 | :param triangle: A ChainLadder Triangle object. 26 | :param column: A triangle column (e.g., Paid Loss) 27 | :param lob: A line of business. 28 | :return: A ChainLadder Triangle, with a single column. 29 | """ 30 | if lob is None: 31 | triangle_column = triangle[column] 32 | else: 33 | triangle_column = triangle[triangle['LOB'] == lob][column] 34 | return triangle_column 35 | -------------------------------------------------------------------------------- /faslr/samples/friedland_xyz_disp.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Disposal Rate,Closed Claim Counts,Paid Claims 2 | 2001,2001,0.209,304,1539 3 | 2001,2002,0.468,681,5952 4 | 2001,2003,0.643,936,12319 5 | 2001,2004,0.751,1092,18609 6 | 2001,2005,0.842,1225,24387 7 | 2001,2006,0.933,1357,31090 8 | 2001,2007,0.984,1432,37070 9 | 2001,2008,0.994,1446,38519 10 | 2002,2002,0.131,203,2318 11 | 2002,2003,0.391,607,7932 12 | 2002,2004,0.541,841,13822 13 | 2002,2005,0.701,1089,22095 14 | 2002,2006,0.854,1327,31945 15 | 2002,2007,0.842,1464,40629 16 | 2002,2008,0.98,1523,44437 17 | 2003,2003,0.111,181,1743 18 | 2003,2004,0.377,614,6240 19 | 2003,2005,0.577,941,12683 20 | 2003,2006,0.775,1263,22892 21 | 2003,2007,0.924,1507,34505 22 | 2003,2008,0.962,1568,39320 23 | 2004,2004,0.104,235,2221 24 | 2004,2005,0.375,848,9898 25 | 2004,2006,0.637,1442,25950 26 | 2004,2007,0.819,1852,43439 27 | 2004,2008,0.897,2029,52811 28 | 2005,2005,0.123,295,3043 29 | 2005,2006,0.466,1119,12219 30 | 2005,2007,0.693,1664,27073 31 | 2005,2008,0.81,1946,40026 32 | 2006,2006,0.183,307,3531 33 | 2006,2007,0.54,906,11778 34 | 2006,2008,0.716,1201,22819 35 | 2007,2007,0.251,329,3529 36 | 2007,2008,0.605,791,11865 37 | 2008,2008,0.236,276,3409 38 | -------------------------------------------------------------------------------- /faslr/demos/common/model_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demo for base model class. 3 | """ 4 | import pandas as pd 5 | import sys 6 | 7 | from faslr.demos.sample_db import set_sample_db 8 | from faslr.common.model import FSelectionModelWidget 9 | 10 | from PyQt6.QtWidgets import ( 11 | QApplication 12 | ) 13 | 14 | set_sample_db() 15 | 16 | data = { 17 | 'Accident Year': [2000, 2001, 2002, 2003, 2004, 2005], 18 | 'col1': [1, 2, 3, 4, 5, 6], 19 | 'col2': [7, 8, 9, 10, 11, 12], 20 | 'col3': [13, 14, 15, 16, 17, 18], 21 | 'col4': [19, 20, 21, 22, 23, 24], 22 | 'col5': [25, 26, 27, 28, 29, 30] 23 | } 24 | 25 | df = pd.DataFrame(data=data) 26 | df = df.set_index('Accident Year') 27 | 28 | averages = pd.DataFrame( 29 | data=[ 30 | [True, "All-year Straight", "Straight", "6"], 31 | [False, "All Years Excl. High/Low", "Medial", '6'], 32 | [False, "3-year Straight", "Straight", "3"], 33 | [False, "5-year Straight", "Straight", "5"], 34 | ], 35 | columns=["Selected", "Label", "Type", "Number of Years"] 36 | ) 37 | 38 | app = QApplication(sys.argv) 39 | 40 | model = FSelectionModelWidget(window_title='Base Model', data=df, averages=averages) 41 | 42 | model.show() 43 | 44 | app.exec() -------------------------------------------------------------------------------- /faslr/core.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import os 3 | from faslr.constants import CONFIG_PATH 4 | 5 | 6 | def get_startup_db_path( 7 | config_path: str = CONFIG_PATH 8 | ) -> str: 9 | """ 10 | Extracts the db path when the user opts to connect to one automatically upon startup. 11 | """ 12 | config = configparser.ConfigParser() 13 | config.read(config_path) 14 | config.sections() 15 | startup_db = config['STARTUP_CONNECTION']['startup_db'] 16 | 17 | return startup_db 18 | 19 | 20 | config_path: str = CONFIG_PATH 21 | 22 | use_sample = False 23 | 24 | # Flag to determine whether there is an active database connection. Most project-related functions 25 | # should be disabled unless a connection is established. 26 | connection_established = False 27 | db = None 28 | 29 | 30 | # If a startup db has been indicated, get the path. 31 | if os.path.isfile(config_path): 32 | startup_db: str = get_startup_db_path(config_path=config_path) 33 | if (startup_db is not None) and (not use_sample) and (startup_db != "None"): 34 | db = startup_db 35 | else: 36 | startup_db: None = None 37 | else: 38 | startup_db: None = None 39 | 40 | def set_db(path: str) -> None: 41 | global db 42 | db = path 43 | 44 | 45 | -------------------------------------------------------------------------------- /faslr/tests/test_base_table.py: -------------------------------------------------------------------------------- 1 | from faslr.base_table import ( 2 | FAbstractTableModel, 3 | FTableView 4 | ) 5 | 6 | from pytestqt.qtbot import QtBot 7 | 8 | from PyQt6.QtCore import Qt 9 | 10 | 11 | def test_f_abstract_table_model(qtbot: QtBot) -> None: 12 | 13 | table_model = FAbstractTableModel() 14 | 15 | row_count_test = table_model.rowCount() 16 | 17 | assert row_count_test == 0 18 | 19 | column_count_test = table_model.columnCount() 20 | 21 | assert column_count_test == 0 22 | 23 | 24 | # def test_f_table_view_horizontal(qtbot: QtBot) -> None: 25 | # 26 | # table_view = FTableView() 27 | # table_model = FAbstractTableModel() 28 | # table_view.setModel(table_model) 29 | # 30 | # table_view.setGridHeaderView( 31 | # orientation=Qt.Orientation.Horizontal, 32 | # levels=1 33 | # ) 34 | # 35 | # table_view.copy_selection() 36 | # 37 | # 38 | # def test_f_table_view_vertical(qtbot: QtBot) -> None: 39 | # 40 | # table_view = FTableView() 41 | # table_model = FAbstractTableModel() 42 | # table_view.setModel(table_model) 43 | # 44 | # table_view.setGridHeaderView( 45 | # orientation=Qt.Orientation.Vertical, 46 | # levels=1 47 | # ) 48 | # 49 | # table_view.copy_selection() -------------------------------------------------------------------------------- /faslr/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | import shutil 4 | 5 | from faslr.constants import ( 6 | CONFIG_TEMPLATES_PATH, 7 | DEFAULT_DIALOG_PATH 8 | ) 9 | 10 | from pathlib import Path 11 | 12 | 13 | @pytest.fixture() 14 | def setup_config(tmp_path: Path) -> str: 15 | """ 16 | Use a temporary config file for the test. 17 | :return: str 18 | """ 19 | 20 | if os.path.isfile(CONFIG_TEMPLATES_PATH): 21 | dest_path = tmp_path / 'faslr_test.ini' 22 | shutil.copy(CONFIG_TEMPLATES_PATH, dest_path) 23 | else: 24 | raise FileNotFoundError("CONFIG_PATH does not point to a valid config file.") 25 | 26 | yield dest_path 27 | 28 | # Teardown - delete test config file 29 | 30 | if os.path.isfile(dest_path): 31 | os.remove(dest_path) 32 | 33 | 34 | @pytest.fixture() 35 | def sample_db() -> str: 36 | """ 37 | Make a copy of the sample db, so we do not alter the original. 38 | The tests will use this copy as the backend db. 39 | :return: The path to the copy of the sample db. 40 | """ 41 | db_filename = DEFAULT_DIALOG_PATH + '/sample.db' 42 | test_db_filename = DEFAULT_DIALOG_PATH + '/sample_test.db' 43 | shutil.copy(db_filename, test_db_filename) 44 | yield test_db_filename 45 | 46 | os.remove(test_db_filename) 47 | -------------------------------------------------------------------------------- /docs/nginx/faslr.com.conf: -------------------------------------------------------------------------------- 1 | server { 2 | server_name faslr.com www.faslr.com; 3 | root /var/www/faslr.com; 4 | index index.html; 5 | 6 | gzip on; 7 | gzip_comp_level 3; 8 | gzip_types text/plain text/css application/javascript image/*; 9 | 10 | listen [::]:443 ssl ipv6only=on; # managed by Certbot 11 | listen 443 ssl; # managed by Certbot 12 | ssl_certificate /etc/letsencrypt/live/faslr.com/fullchain.pem; # managed by Certbot 13 | ssl_certificate_key /etc/letsencrypt/live/faslr.com/privkey.pem; # managed by Certbot 14 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 15 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 16 | 17 | location = / { 18 | return 301 https://faslr.com/docs; 19 | } 20 | 21 | } 22 | server { 23 | if ($host = www.faslr.com) { 24 | return 301 https://$host$request_uri; 25 | } # managed by Certbot 26 | 27 | 28 | if ($host = faslr.com) { 29 | return 301 https://$host$request_uri; 30 | } # managed by Certbot 31 | 32 | listen 80; 33 | listen [::]:80; 34 | server_name faslr.com www.faslr.com; 35 | return 404; # managed by Certbot 36 | 37 | 38 | location = / { 39 | return 301 https://faslr.com/docs; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /faslr/common/table.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from faslr.base_table import FTableView 7 | 8 | from PyQt6.QtCore import ( 9 | QSize, 10 | Qt 11 | ) 12 | 13 | from PyQt6.QtWidgets import ( 14 | QAbstractButton, 15 | QLabel, 16 | QStyle, 17 | QStyleOptionHeader, 18 | QVBoxLayout 19 | ) 20 | 21 | 22 | def make_corner_button( 23 | parent: FTableView, 24 | label: str = 'AY' 25 | ) -> QAbstractButton: 26 | 27 | btn = parent.findChild(QAbstractButton) 28 | btn.installEventFilter(parent) 29 | btn_label = QLabel(label) 30 | btn_label.setAlignment(Qt.AlignmentFlag.AlignCenter) 31 | btn_layout = QVBoxLayout() 32 | btn_layout.setContentsMargins(0, 0, 0, 0) 33 | btn_layout.addWidget(btn_label) 34 | btn.setLayout(btn_layout) 35 | opt = QStyleOptionHeader() 36 | 37 | parent.setStyleSheet( 38 | """ 39 | QTableCornerButton::section { 40 | border: 1px outset darkgrey; 41 | } 42 | """ 43 | ) 44 | 45 | s = QSize(btn.style().sizeFromContents( 46 | QStyle.ContentsType.CT_HeaderSection, opt, QSize(), btn). 47 | expandedTo(QSize())) 48 | 49 | if s.isValid(): 50 | parent.verticalHeader().setMinimumWidth(s.width()) 51 | 52 | return btn 53 | -------------------------------------------------------------------------------- /faslr/samples/xyz_expected_loss.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains sample data from XYZ expected loss example. See Exhibit III, page 144 of Friedland. 3 | """ 4 | 5 | XYZ_SAMPLE_YEARS = [ 6 | 1998, 7 | 1999, 8 | 2000, 9 | 2001, 10 | 2002, 11 | 2003, 12 | 2004, 13 | 2005, 14 | 2006, 15 | 2007, 16 | 2008 17 | ] 18 | 19 | XYZ_RATE_CHANGES = [ 20 | 0, 21 | 0, 22 | 0, 23 | 0, 24 | 0, 25 | .05, 26 | .075, 27 | .15, 28 | .10, 29 | -.2, 30 | -.2 31 | ] 32 | 33 | XYZ_RATE_INDEX = { 34 | 'Name': ['XYZ Auto BI Rate Index'], 35 | 'Description': ['Rate change index for XYZ Auto BI'], 36 | 'Origin': XYZ_SAMPLE_YEARS, 37 | 'Change': XYZ_RATE_CHANGES 38 | } 39 | 40 | XYZ_TORT_CHANGES = [ 41 | 0, 42 | 0, 43 | 0, 44 | 0, 45 | 0, 46 | 0, 47 | 0, 48 | 0, 49 | - (1 - .67 / .75), 50 | -.25, 51 | 0 52 | ] 53 | 54 | XYZ_TORT_INDEX = { 55 | 'Name': ['XYZ Auto BI Tort Index'], 56 | 'Description': ['Adjustments to XYZ Auto BI claims due to tort reform.'], 57 | 'Origin': XYZ_SAMPLE_YEARS, 58 | 'Change': XYZ_TORT_CHANGES 59 | } 60 | 61 | XYZ_TREND_INDEX = { 62 | 'Name': ['XYZ Auto BI Loss Trend'], 63 | 'Description': ['3.425% trend for XYZ Auto BI claims.'], 64 | 'Origin': XYZ_SAMPLE_YEARS, 65 | 'Change': [.03425 for x in range(len(XYZ_SAMPLE_YEARS))] 66 | } 67 | -------------------------------------------------------------------------------- /faslr/samples/friedland_berq_sher_auto.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Paid Claims,Closed Claim Counts,Reported Claim Counts,Disposal Rate 2 | 1969,1969,1904,4079,6553,0.522 3 | 1969,1970,5398,6616,7696,0.846 4 | 1969,1971,7496,7192,7770,0.92 5 | 1969,1972,8882,7494,7799,0.958 6 | 1969,1973,9712,7670,7814,0.981 7 | 1969,1974,10071,7749,7819,0.991 8 | 1969,1975,10199,7792,7820,0.996 9 | 1969,1976,10256,7806,7821,0.998 10 | 1970,1970,2235,4429,7277,0.51 11 | 1970,1971,6261,7230,8537,0.833 12 | 1970,1972,8691,7899,8615,0.91 13 | 1970,1973,10443,8291,8661,0.955 14 | 1970,1974,11346,8494,8675,0.978 15 | 1970,1975,11754,8606,8679,0.991 16 | 1970,1976,12031,8647,8682,0.996 17 | 1971,1971,2441,4914,8259,0.494 18 | 1971,1972,7348,8174,9765,0.822 19 | 1971,1973,10662,9068,9884,0.912 20 | 1971,1974,12655,9518,9926,0.957 21 | 1971,1975,13748,9761,9940,0.981 22 | 1971,1976,14235,9855,9945,0.991 23 | 1972,1972,2503,4497,7858,0.464 24 | 1972,1973,8173,7842,9474,0.809 25 | 1972,1974,11810,8747,9615,0.903 26 | 1972,1975,14176,9254,9664,0.955 27 | 1972,1976,15383,9469,9680,0.977 28 | 1973,1973,2838,4419,7808,0.461 29 | 1973,1974,8712,7665,9376,0.799 30 | 1973,1975,12728,8659,9513,0.903 31 | 1973,1976,15278,9093,9562,0.948 32 | 1974,1974,2405,3486,6278,0.447 33 | 1974,1975,7858,6214,7614,0.796 34 | 1974,1976,11771,6916,7741,0.886 35 | 1975,1975,2759,3516,6446,0.437 36 | 1975,1976,9182,6226,7884,0.773 37 | 1976,1976,2801,3230,6115,0.433 38 | -------------------------------------------------------------------------------- /faslr/tests/common/test_button.py: -------------------------------------------------------------------------------- 1 | from faslr.common import ( 2 | AddRemoveButtonWidget, 3 | FOKCancel, 4 | make_corner_button 5 | ) 6 | 7 | from pytestqt.qtbot import QtBot 8 | 9 | def test_add_remove_btn_widget( 10 | qtbot: QtBot 11 | ) -> None: 12 | 13 | add_remove_button_widget = AddRemoveButtonWidget( 14 | p_tool_tip='plus tool tip', 15 | m_tool_tip='minus tool tip' 16 | ) 17 | 18 | assert add_remove_button_widget.add_btn.toolTip() == 'plus tool tip' 19 | 20 | assert add_remove_button_widget.remove_btn.toolTip() == 'minus tool tip' 21 | 22 | assert add_remove_button_widget.add_btn.height() == 22 23 | 24 | assert add_remove_button_widget.add_btn.width() == 22 25 | 26 | assert add_remove_button_widget.remove_btn.height() == 22 27 | 28 | assert add_remove_button_widget.remove_btn.width() == 22 29 | 30 | def test_ok_cancel(qtbot: QtBot) -> None: 31 | 32 | ok_cancel = FOKCancel() 33 | 34 | assert ok_cancel 35 | 36 | def test_make_corner_button( 37 | qtbot: QtBot 38 | ) -> None: 39 | 40 | corner_button = make_corner_button( 41 | text='x', 42 | height=10, 43 | width=15, 44 | tool_tip="tooltip" 45 | ) 46 | 47 | assert corner_button.text() == 'x' 48 | 49 | assert corner_button.height() == 10 50 | 51 | assert corner_button.width() == 15 52 | 53 | assert corner_button.toolTip() == 'tooltip' -------------------------------------------------------------------------------- /faslr/samples/friedland_gl_insurer.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Closed Claim Counts,Reported Claim Counts,Disposal Rate,Paid Claims 2 | 2001,2001,195,1299,0.223,1119962 3 | 2001,2002,375,1077,0.43,4373268 4 | 2001,2003,510,1057,0.584,8398345 5 | 2001,2004,625,965,0.716,13490793 6 | 2001,2005,702,930,0.804,17372233 7 | 2001,2006,752,917,0.862,22052662 8 | 2001,2007,780,864,0.894,27359691 9 | 2001,2008,796,870,0.912,29901361 10 | 2002,2002,199,847,0.277,1411957 11 | 2002,2003,349,945,0.485,6287005 12 | 2002,2004,445,864,0.618,11443820 13 | 2002,2005,508,787,0.706,15520552 14 | 2002,2006,563,784,0.782,21295572 15 | 2002,2007,594,743,0.826,28410418 16 | 2002,2008,626,731,0.87,32468911 17 | 2003,2003,106,800,0.169,984748 18 | 2003,2004,294,831,0.47,6128957 19 | 2003,2005,383,762,0.612,10470758 20 | 2003,2006,453,704,0.724,14604684 21 | 2003,2007,499,669,0.797,21936647 22 | 2003,2008,542,636,0.866,23942499 23 | 2004,2004,126,823,0.2,1158659 24 | 2004,2005,281,862,0.447,5811172 25 | 2004,2006,377,797,0.599,10497504 26 | 2004,2007,445,728,0.707,15087416 27 | 2004,2008,494,684,0.785,18242570 28 | 2005,2005,114,828,0.194,1198767 29 | 2005,2006,249,850,0.423,5103837 30 | 2005,2007,315,765,0.536,9042134 31 | 2005,2008,403,687,0.685,15443929 32 | 2006,2006,114,824,0.206,1220778 33 | 2006,2007,229,809,0.414,4594746 34 | 2006,2008,300,734,0.543,8983864 35 | 2007,2007,79,604,0.18,796774 36 | 2007,2008,188,620,0.429,4233641 37 | 2008,2008,127,812,0.209,1445365 38 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | :html_theme.sidebar_secondary.remove: 2 | 3 | FASLR: Free Actuarial System for Loss Reserving 4 | ================================================ 5 | 6 | FASLR (`fæzlɹ `_) is a Linux-based frontend for open-source loss reserving packages. Current plans are to support the `Chainladder `_ package. 7 | 8 | FASLR will assist in the proper governance of periodic reserve reviews. In addition to being a GUI in which actuarial analyses can be done, it will also serve as a portal through which current and past analyses can be managed. Each reserve analysis will have metadata that indicates its status (in progress, review needed, signed-off), and by storing past analyses, FASLR will make it easy to compare quarter-by-quarter reviews without having to awkwardly navigate company shared folders and spreadsheets. 9 | 10 | The actuarial methods and example data used in this project are derived from publicly available papers and data sources. The GUI is developed in Python using the open-source PyQt6 package. The `source code `_ is available on the `CAS GitHub page `_. 11 | 12 | .. image:: https://faslr.com/media/basic_ui_09082021.png 13 | 14 | .. toctree:: 15 | :hidden: 16 | :maxdepth: 1 17 | :caption: Sections: 18 | 19 | user/index 20 | contributing/index 21 | api/index 22 | gallery/index -------------------------------------------------------------------------------- /faslr/style/icons/xkcd-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/user/starting/tutorial/importing.rst: -------------------------------------------------------------------------------- 1 | Importing Data 2 | ============== 3 | 4 | In the project tree, double click on a project. This will open up the project data view pane in the Analysis Pane of the Main Window. This pane lists the data views associated with the project. If you have not yet uploaded data, this view will be blank. 5 | 6 | .. image:: https://faslr.com/media/empty_data_view.png 7 | 8 | To upload data, click on the **Upload** button in the upper right-hand corner of the project data views pane. This will activate an import wizard, which you can use to upload a CSV file. 9 | 10 | .. image:: https://faslr.com/media/import_wizrd.png 11 | :width: 400px 12 | 13 | To upload a CSV file, click the **Upload File** button. Right now, FASLR only accepts data that conform to the way the chainladder-python package accepts tabular data. For guidelines on how to format the source data, see chainladder `tutorial on triangles `_. 14 | 15 | .. image:: https://faslr.com/media/import_wizard_filled.png 16 | :width: 400px 17 | 18 | Assuming you did that correctly, you should see the data views pane populated with an entry after you press the **OK** button. 19 | 20 | .. image:: https://faslr.com/media/data_view_filled.png 21 | 22 | To see the triangle contained in this view, double click the entry in the table, and a new tab will open up, displaying the triangle data: 23 | 24 | .. image:: https://faslr.com/media/data_view_preview.png -------------------------------------------------------------------------------- /faslr/demos/methods/expected_loss/expected_loss_demo.py: -------------------------------------------------------------------------------- 1 | import chainladder as cl 2 | import sys 3 | 4 | from faslr.demos.sample_db import set_sample_db 5 | 6 | from faslr.methods import ( 7 | ExpectedLossWidget 8 | ) 9 | 10 | import pandas as pd 11 | 12 | from faslr.utilities import ( 13 | load_sample, 14 | table_from_tri, 15 | auto_bi_olep 16 | ) 17 | 18 | from PyQt6.QtWidgets import ( 19 | QApplication 20 | ) 21 | 22 | set_sample_db() 23 | 24 | app = QApplication(sys.argv) 25 | 26 | averages = pd.DataFrame( 27 | data=[ 28 | [True, "7-year Straight", "Straight", "7"], 29 | [False, "7-year Excl. High/Low", "Medial", '7'], 30 | [False, "3-year Straight", "Straight", "3"], 31 | [False, "5-year Straight", "Straight", "5"], 32 | ], 33 | columns=["Selected", "Label", "Type", "Number of Years"] 34 | ) 35 | 36 | 37 | 38 | triangle = load_sample('xyz') 39 | reported = triangle['Reported Claims'] 40 | paid = triangle['Paid Claims'] 41 | 42 | reported_ult = cl.TailConstant(tail=1).fit_transform(cl.Development(n_periods=2, average='volume').fit_transform(reported)) 43 | reported_ult = cl.Chainladder().fit(reported_ult) 44 | 45 | paid_ult = cl.TailConstant(tail=1.010,decay=1).fit_transform(cl.Development(n_periods=2, average='volume').fit_transform(paid)) 46 | paid_ult = cl.Chainladder().fit(paid_ult) 47 | 48 | 49 | widget = ExpectedLossWidget( 50 | triangles=[reported_ult, paid_ult], 51 | premium=auto_bi_olep, 52 | averages=averages 53 | ) 54 | 55 | widget.show() 56 | 57 | app.exec() -------------------------------------------------------------------------------- /faslr/demos/methods/borhnhuetter/bf_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Boilerplate code for writing demos. 3 | """ 4 | import chainladder as cl 5 | import pandas as pd 6 | import sys 7 | 8 | from faslr.demos.sample_db import set_sample_db 9 | from faslr.utilities.sample import auto_bi_olep 10 | from faslr.utilities import load_sample 11 | 12 | from faslr.methods.bornhuetter import BornhuetterWidget 13 | 14 | from PyQt6.QtWidgets import ( 15 | QApplication 16 | ) 17 | 18 | set_sample_db() 19 | 20 | averages = pd.DataFrame( 21 | data=[ 22 | [True, "7-year Straight", "Straight", "7"], 23 | [False, "7-year Excl. High/Low", "Medial", '7'], 24 | [False, "3-year Straight", "Straight", "3"], 25 | [False, "5-year Straight", "Straight", "5"], 26 | ], 27 | columns=["Selected", "Label", "Type", "Number of Years"] 28 | ) 29 | 30 | 31 | 32 | triangle = load_sample('xyz') 33 | reported = triangle['Reported Claims'] 34 | paid = triangle['Paid Claims'] 35 | 36 | reported_ult = cl.TailConstant(tail=1).fit_transform(cl.Development(n_periods=2, average='volume').fit_transform(reported)) 37 | reported_ult = cl.Chainladder().fit(reported_ult) 38 | 39 | paid_ult = cl.TailConstant(tail=1.010,decay=1).fit_transform(cl.Development(n_periods=2, average='volume').fit_transform(paid)) 40 | paid_ult = cl.Chainladder().fit(paid_ult) 41 | 42 | app = QApplication(sys.argv) 43 | 44 | bh_widget = BornhuetterWidget( 45 | triangles=[reported_ult, paid_ult], 46 | premium=auto_bi_olep, 47 | averages=averages 48 | ) 49 | 50 | bh_widget.show() 51 | 52 | app.exec() -------------------------------------------------------------------------------- /faslr/demos/debug/grid_header_reprex_demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from faslr.grid_header import ( 4 | GridTableView, 5 | ) 6 | 7 | from PyQt6.QtCore import Qt 8 | 9 | from PyQt6.QtGui import ( 10 | QStandardItem, 11 | QStandardItemModel 12 | ) 13 | 14 | 15 | from PyQt6.QtWidgets import ( 16 | QApplication, 17 | QVBoxLayout, 18 | QWidget 19 | ) 20 | 21 | 22 | app = QApplication(sys.argv) 23 | 24 | main_widget = QWidget() 25 | layout = QVBoxLayout() 26 | main_widget.setLayout(layout) 27 | 28 | # Generate dummy data 29 | 30 | model = QStandardItemModel() 31 | view = GridTableView(corner_button_label="Accident\nYear") 32 | view.setModel(model) 33 | 34 | for row in range(9): 35 | items = [] 36 | for col in range(2): 37 | items.append(QStandardItem('item(' + str(row) + ',' + str(col) + ')')) 38 | model.appendRow(items) 39 | 40 | layout.addWidget(view) 41 | 42 | view.setGridHeaderView( 43 | orientation=Qt.Orientation.Horizontal, 44 | levels=2 45 | ) 46 | 47 | view.hheader.setSpan( 48 | row=0, 49 | column=0, 50 | row_span_count=1, 51 | column_span_count=2 52 | ) 53 | 54 | # view.hheader.setSpan( 55 | # row=0, 56 | # column=2, 57 | # row_span_count=2, 58 | # column_span_count=1 59 | # ) 60 | 61 | 62 | view.hheader.setCellLabel(0, 0, "header1") 63 | view.hheader.setCellLabel(1, 0, "header2") 64 | view.hheader.setCellLabel(1, 1, "header3") 65 | 66 | main_widget.resize(view.hheader.sizeHint().width(), 315) 67 | main_widget.resize(937, 315) 68 | main_widget.show() 69 | 70 | 71 | app.exec() 72 | -------------------------------------------------------------------------------- /docs/build_docs.sh: -------------------------------------------------------------------------------- 1 | cd /root/faslr/docs; 2 | git pull; 3 | make html; 4 | cp -fR _build/html/* /var/www/faslr.com/docs; 5 | cd /var/www/faslr.com/media; 6 | git pull; 7 | 8 | cd /root/faslr; 9 | python3 faslr/samples/db/generate_sample_db.py; 10 | mv sample.db ../schema/sample.db; 11 | cd /root/schema; 12 | java -jar schemaspy-6.2.3.jar -t sqlite-xerial -dp sqlite-jdbc-3.42.0.0.jar -db sample.db -sso -s sample -cat % -o db; 13 | sed -i "s/sample.db.sample/FASLR/g" db/index.html; 14 | sed -i "s/sample.db/FASLR/g" db/index.html; 15 | sed -i "s/.sample//g" db/index.html; 16 | sed -i "s/sample.db.sample/FASLR/g" db/relationships.html; 17 | sed -i "s/sample.db.sample/FASLR/g" db/columns.html; 18 | sed -i "s/sample.db.sample/FASLR/g" db/anomalies.html; 19 | sed -i "s/sample.db.sample/FASLR/g" db/constraints.html; 20 | sed -i "s/sample.db.sample/FASLR/g" db/orphans.html; 21 | sed -i "s/sample.db.sample/FASLR/g" db/routines.html; 22 | sed -i "s/sample.db/FASLR/g" db/relationships.html; 23 | sed -i "s/sample.db/FASLR/g" db/columns.html; 24 | sed -i "s/sample.db/FASLR/g" db/anomalies.html; 25 | sed -i "s/sample.db/FASLR/g" db/constraints.html; 26 | sed -i "s/sample.db/FASLR/g" db/orphans.html; 27 | sed -i "s/sample.db/FASLR/g" db/routines.html; 28 | sed -i "s/.sample//g" db/relationships.html; 29 | sed -i "s/.sample//g" db/columns.html; 30 | sed -i "s/.sample//g" db/anomalies.html; 31 | sed -i "s/.sample//g" db/constraints.html; 32 | sed -i "s/.sample//g" db/orphans.html; 33 | sed -i "s/.sample//g" db/routines.html; 34 | mv db/sample.db.sample.xml db/FASLR.xml 35 | cp -fR db/* /var/www/faslr.com/db; -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | name: Test Coverage 9 | defaults: 10 | run: 11 | working-directory: . 12 | env: 13 | DISPLAY: ':99.0' 14 | steps: 15 | - uses: actions/checkout@v1 16 | - uses: actions/setup-python@v2 17 | with: 18 | python-version: '3.10' 19 | - name: Install ubuntu packages 20 | run: sudo apt-get update 21 | - name: Install requirements 22 | run: sudo apt-get install -y libegl1 libdbus-1-3 libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 x11-utils libxcb-cursor0 23 | - name: Run xvfb 24 | run: /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX 25 | - name: Install requirements 26 | run: pip install -r requirements.txt 27 | - name: Reinstall opencv 28 | run: pip uninstall opencv-python && pip install opencv-python-headless==4.12.0.88 29 | - name: Generate Sample DB 30 | run: python3 faslr/samples/db/generate_sample_db.py 31 | - name: Copy config file 32 | run: python3 faslr/tests/copy_config.py 33 | - name: Run tests and collect coverage 34 | run: pytest --cov-config=faslr/tests/.coveragerc --cov=./ --cov-report=term --cov-report=xml 35 | - name: Upload coverage reports to Codecov with GitHub Action 36 | uses: codecov/codecov-action@v3 37 | -------------------------------------------------------------------------------- /faslr/demos/index/index_retrieval_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Reprex that demonstrates the storage and retrieval of FASLR indexes in a QListView 3 | """ 4 | 5 | import sys 6 | 7 | from faslr.demos.sample_db import set_sample_db 8 | from faslr.index import ( 9 | FIndex, 10 | FStandardIndexItem 11 | ) 12 | 13 | from PyQt6.QtWidgets import ( 14 | QApplication, 15 | QWidget, 16 | QHBoxLayout, 17 | QListView 18 | ) 19 | 20 | from PyQt6.QtGui import ( 21 | QStandardItemModel, 22 | QStandardItem 23 | ) 24 | 25 | set_sample_db() 26 | 27 | test_index = FIndex(from_id=1) 28 | test_index2 = FIndex(from_id=2) 29 | 30 | class TestWidget(QWidget): 31 | def __init__(self): 32 | super().__init__() 33 | 34 | self.layout = QHBoxLayout() 35 | self.list_view = QListView() 36 | self.model = QStandardItemModel() 37 | self.list_view.setModel(self.model) 38 | 39 | item = FStandardIndexItem(findex=test_index) 40 | item2 = FStandardIndexItem(findex=test_index2) 41 | self.model.appendRow(item) 42 | self.model.appendRow(item2) 43 | 44 | self.layout.addWidget(self.list_view) 45 | self.setLayout(self.layout) 46 | 47 | self.list_view.selectionModel().selectionChanged.connect( # noqa 48 | self.print_index 49 | ) 50 | 51 | def print_index(self): 52 | idx = self.list_view.selectedIndexes()[0] 53 | 54 | print(self.model.itemFromIndex(idx).findex.df) 55 | 56 | app = QApplication(sys.argv) 57 | 58 | test_widget = TestWidget() 59 | test_widget.show() 60 | 61 | app.exec() 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | import setuptools 3 | 4 | # from faslr.constants import BUILD_VERSION 5 | 6 | 7 | def get_property( 8 | prop: str, 9 | project: str 10 | ) -> str: 11 | 12 | result = re.search( 13 | r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format(prop), 14 | open(project + '/_version.py').read()) 15 | 16 | return result.group(1) 17 | 18 | 19 | project_name = 'faslr' 20 | 21 | with open("README.md", "r") as fh: 22 | long_description = fh.read() 23 | 24 | setuptools.setup( 25 | name=project_name, 26 | version=get_property('__version__', project_name), 27 | author="Gene Dan", 28 | author_email="genedan@gmail.com", 29 | description="Free Actuarial System for Loss Reserving", 30 | long_description=long_description, 31 | long_description_content_type="text/markdown", 32 | url="https://github.com/genedan/FASLR", 33 | project_urls={ 34 | "Documentation": "https://genedan.com/faslr/docs" 35 | }, 36 | install_requires=[ 37 | 'bs4', 38 | 'chainladder', 39 | 'GitPython', 40 | 'matplotlib', 41 | 'numpy', 42 | 'pandas', 43 | 'pydata-sphinx-theme', 44 | 'pytest', 45 | 'pytest-cov', 46 | 'PyQt6', 47 | 'sphinx_design', 48 | 'sqlalchemy' 49 | ], 50 | packages=setuptools.find_packages(), 51 | classifiers=[ 52 | "Programming Language :: Python :: 3.10", 53 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 54 | "Operating System :: POSIX :: Linux", 55 | ], 56 | python_requires='>=3.10.0', 57 | ) -------------------------------------------------------------------------------- /faslr/utilities/chainladder.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | import pandas as pd 6 | 7 | if TYPE_CHECKING: # pragma: no cover 8 | from chainladder import Chainladder 9 | from pandas import DataFrame 10 | 11 | 12 | def table_from_tri( 13 | triangle: Chainladder 14 | ) -> DataFrame: 15 | """ 16 | Summarize fitted chain ladder model in 2-D table format. 17 | """ 18 | 19 | origin = fetch_origin(triangle=triangle) 20 | diagonal = fetch_latest_diagonal(triangle=triangle) 21 | column = triangle.X_.columns[0] 22 | 23 | ultimate = fetch_ultimate(triangle=triangle) 24 | 25 | df = pd.DataFrame({ 26 | 'Accident Year': origin, 27 | column: diagonal, 28 | 'Ultimate ' + column: ultimate 29 | }) 30 | 31 | return df 32 | 33 | 34 | def fetch_origin( 35 | triangle: Chainladder 36 | ) -> list: 37 | 38 | origin = triangle.X_.origin.to_frame().index.astype(str).tolist() 39 | 40 | return origin 41 | 42 | def fetch_latest_diagonal( 43 | triangle: Chainladder 44 | ) -> list: 45 | 46 | diagonal = list(triangle.X_.latest_diagonal.to_frame().iloc[:, 0]) 47 | 48 | return diagonal 49 | 50 | def fetch_cdf( 51 | triangle: Chainladder 52 | ) -> list: 53 | 54 | cdf = list(triangle.X_.latest_diagonal.cdf_.to_frame().iloc[:, ].values.flatten()) 55 | cdf.pop() 56 | cdf.reverse() 57 | 58 | return cdf 59 | 60 | def fetch_ultimate( 61 | triangle: Chainladder 62 | ) -> list: 63 | 64 | data = list(triangle.ultimate_.to_frame().iloc[:, 0]) 65 | 66 | return data -------------------------------------------------------------------------------- /faslr/samples/friedland_uspp_auto_steady_state.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Paid Claims,Reported Claims 2 | 1999,1999,294000,539000 3 | 1999,2000,497000,630000 4 | 1999,2001,588000,665000 5 | 1999,2002,644000,686000 6 | 1999,2003,672000,693000 7 | 1999,2004,686000,693000 8 | 1999,2005,693000,700000 9 | 1999,2006,693000,700000 10 | 1999,2007,700000,700000 11 | 1999,2008,700000,700000 12 | 2000,2000,308700,565950 13 | 2000,2001,521850,662600 14 | 2000,2002,617400,698250 15 | 2000,2003,676200,720300 16 | 2000,2004,705600,727650 17 | 2000,2005,720300,727650 18 | 2000,2006,727650,735000 19 | 2000,2007,727650,735000 20 | 2000,2008,735000,735000 21 | 2001,2001,324135,594248 22 | 2001,2002,547943,694575 23 | 2001,2003,648270,733163 24 | 2001,2004,710000,756315 25 | 2001,2005,740880,764033 26 | 2001,2006,756315,764033 27 | 2001,2007,764033,771750 28 | 2001,2008,764033,771750 29 | 2002,2002,340342,623960 30 | 2002,2003,575340,729304 31 | 2002,2004,680864,769821 32 | 2002,2005,745511,794131 33 | 2002,2006,777924,802234 34 | 2002,2007,794131,802234 35 | 2002,2008,802234,810338 36 | 2003,2003,357359,655158 37 | 2003,2004,604107,765769 38 | 2003,2005,714718,808312 39 | 2003,2006,782786,833837 40 | 2003,2007,816820,842346 41 | 2003,2008,833837,842346 42 | 2004,2004,375227,687916 43 | 2004,2005,634312,804057 44 | 2004,2006,750454,848727 45 | 2004,2007,821925,875529 46 | 2004,2008,857661,884463 47 | 2005,2005,393988,722312 48 | 2005,2006,666028,844260 49 | 2005,2007,787976,891164 50 | 2005,2008,863022,919306 51 | 2006,2006,413688,758427 52 | 2006,2007,699329,886473 53 | 2006,2008,827375,935722 54 | 2007,2007,434372,796348 55 | 2007,2008,734295,930797 56 | 2008,2008,456090,836166 57 | -------------------------------------------------------------------------------- /faslr/samples/friedland_uspp_auto_increasing_case.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Paid Claims,Reported Claims 2 | 1999,1999,294000,539000 3 | 1999,2000,497000,630000 4 | 1999,2001,588000,665000 5 | 1999,2002,644000,686000 6 | 1999,2003,672000,693000 7 | 1999,2004,686000,693000 8 | 1999,2005,693000,700000 9 | 1999,2006,693000,700000 10 | 1999,2007,700000,700000 11 | 1999,2008,700000,700000 12 | 2000,2000,308700,565950 13 | 2000,2001,521850,661500 14 | 2000,2002,617400,698250 15 | 2000,2003,676200,720300 16 | 2000,2004,705600,727650 17 | 2000,2005,720300,727650 18 | 2000,2006,727650,735000 19 | 2000,2007,727650,735000 20 | 2000,2008,735000,735000 21 | 2001,2001,324135,594248 22 | 2001,2002,547943,694575 23 | 2001,2003,648270,733163 24 | 2001,2004,710010,756315 25 | 2001,2005,740880,764033 26 | 2001,2006,756315,764033 27 | 2001,2007,764033,771750 28 | 2001,2008,764033,771750 29 | 2002,2002,340342,623960 30 | 2002,2003,575340,729304 31 | 2002,2004,680684,769821 32 | 2002,2005,745511,794131 33 | 2002,2006,777924,802234 34 | 2002,2007,794131,802234 35 | 2002,2008,802234,810338 36 | 2003,2003,357359,655158 37 | 2003,2004,604107,765769 38 | 2003,2005,714718,808312 39 | 2003,2006,782786,833837 40 | 2003,2007,816820,842346 41 | 2003,2008,833837,842346 42 | 2004,2004,375227,687916 43 | 2004,2005,634312,804057 44 | 2004,2006,750454,848727 45 | 2004,2007,821925,878745 46 | 2004,2008,857661,884463 47 | 2005,2005,393988,722312 48 | 2005,2006,666028,844260 49 | 2005,2007,787976,897355 50 | 2005,2008,863022,933377 51 | 2006,2006,413688,758427 52 | 2006,2007,699329,897702 53 | 2006,2008,827375,962808 54 | 2007,2007,434372,818067 55 | 2007,2008,734295,979922 56 | 2008,2008,456090,931185 57 | -------------------------------------------------------------------------------- /faslr/tests/methods/test_development.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pytest 4 | 5 | from faslr.methods.development import DevelopmentTab 6 | from PyQt6.QtCore import Qt 7 | from PyQt6.QtWidgets import QApplication 8 | from faslr.utilities.sample import load_sample 9 | 10 | from pytestqt.qtbot import QtBot 11 | 12 | 13 | @pytest.fixture() 14 | def development_tab( 15 | qtbot: QtBot 16 | ) -> DevelopmentTab: 17 | """ 18 | Set up development tab with some sample data for testing. 19 | 20 | :param qtbot: The QtBot fixture. 21 | :return: The resulting development tab. 22 | """ 23 | 24 | us_auto = load_sample("us_industry_auto") 25 | 26 | tab = DevelopmentTab( 27 | triangle=us_auto, 28 | column='Paid Claims' 29 | ) 30 | 31 | qtbot.addWidget(tab) 32 | 33 | yield tab 34 | 35 | 36 | def test_open_ldf_average_box( 37 | qtbot: QtBot, 38 | development_tab: DevelopmentTab 39 | ) -> None: 40 | """ 41 | Test opening of LDF average dialog box. 42 | 43 | :param qtbot: The QtBot fixture. 44 | :param development_tab: The development_tab fixture. 45 | :return: None 46 | """ 47 | 48 | qtbot.mouseClick( 49 | development_tab.add_ldf_btn, 50 | Qt.MouseButton.LeftButton, 51 | delay=1 52 | ) 53 | 54 | 55 | def test_heatmap( 56 | development_tab: DevelopmentTab 57 | ) -> None: 58 | """ 59 | Test toggling of heatmap. 60 | :param development_tab: The development_tab fixture. 61 | :return: None 62 | """ 63 | 64 | development_tab.check_heatmap.setChecked(True) 65 | 66 | development_tab.check_heatmap.setChecked(False) 67 | -------------------------------------------------------------------------------- /faslr/samples/friedland_us_auto_incr_claim.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Paid Claims,Reported Claims 2 | 1999,1999,294000,539000 3 | 1999,2000,497000,630000 4 | 1999,2001,588000,665000 5 | 1999,2002,644000,686000 6 | 1999,2003,672000,693000 7 | 1999,2004,686000,693000 8 | 1999,2005,693000,700000 9 | 1999,2006,693000,700000 10 | 1999,2007,700000,700000 11 | 1999,2008,700000,700000 12 | 2000,2000,308700,565950 13 | 2000,2001,521850,611500 14 | 2000,2002,617400,698250 15 | 2000,2003,676200,720300 16 | 2000,2004,705600,727650 17 | 2000,2005,720300,727650 18 | 2000,2006,727650,735000 19 | 2000,2007,727650,735000 20 | 2000,2008,735000,735000 21 | 2001,2001,324135,594248 22 | 2001,2002,547943,694575 23 | 2001,2003,648270,733163 24 | 2001,2004,710010,756315 25 | 2001,2005,740880,764033 26 | 2001,2006,756315,764033 27 | 2001,2007,764033,771750 28 | 2001,2008,764033,771750 29 | 2002,2002,340342,623960 30 | 2002,2003,575340,729304 31 | 2002,2004,680684,769821 32 | 2002,2005,745511,794131 33 | 2002,2006,777924,802234 34 | 2002,2007,794131,802234 35 | 2002,2008,802234,810338 36 | 2003,2003,357359,655158 37 | 2003,2004,604107,765769 38 | 2003,2005,714718,808312 39 | 2003,2006,782786,833837 40 | 2003,2007,816820,842346 41 | 2003,2008,833837,842346 42 | 2004,2004,428831,786189 43 | 2004,2005,724928,918923 44 | 2004,2006,857661,969974 45 | 2004,2007,939343,1000605 46 | 2004,2008,980184,1010815 47 | 2005,2005,478414,877093 48 | 2005,2006,808748,1025173 49 | 2005,2007,956828,1082127 50 | 2005,2008,1047955,1116300 51 | 2006,2006,531884,975121 52 | 2006,2007,899137,1139751 53 | 2006,2008,1063768,1203071 54 | 2007,2007,589505,1080759 55 | 2007,2008,996544,1263224 56 | 2008,2008,651558,1194523 57 | -------------------------------------------------------------------------------- /faslr/samples/friedland_uspp_auto_increasing_claim.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Paid Claims,Reported Claims 2 | 1999,1999,294000,539000 3 | 1999,2000,497000,630000 4 | 1999,2001,588000,665000 5 | 1999,2002,644000,686000 6 | 1999,2003,672000,693000 7 | 1999,2004,686000,693000 8 | 1999,2005,693000,700000 9 | 1999,2006,693000,700000 10 | 1999,2007,700000,700000 11 | 1999,2008,700000,700000 12 | 2000,2000,308700,565950 13 | 2000,2001,521850,661500 14 | 2000,2002,617400,698250 15 | 2000,2003,676200,720300 16 | 2000,2004,705600,727650 17 | 2000,2005,720300,727650 18 | 2000,2006,727650,735000 19 | 2000,2007,727650,735000 20 | 2000,2008,735000,735000 21 | 2001,2001,324135,594248 22 | 2001,2002,547943,694575 23 | 2001,2003,648270,733163 24 | 2001,2004,710010,756315 25 | 2001,2005,740880,764033 26 | 2001,2006,756315,764033 27 | 2001,2007,764033,771750 28 | 2001,2008,764033,771750 29 | 2002,2002,340342,623960 30 | 2002,2003,575340,729304 31 | 2002,2004,680684,769821 32 | 2002,2005,745511,794131 33 | 2002,2006,777924,802234 34 | 2002,2007,794131,802234 35 | 2002,2008,802234,810338 36 | 2003,2003,357359,655158 37 | 2003,2004,604107,765769 38 | 2003,2005,714718,808312 39 | 2003,2006,782786,833837 40 | 2003,2007,816820,842346 41 | 2003,2008,833837,842346 42 | 2004,2004,428831,786189 43 | 2004,2005,724928,918923 44 | 2004,2006,857661,969974 45 | 2004,2007,939343,1000605 46 | 2004,2008,980184,1010815 47 | 2005,2005,478414,877093 48 | 2005,2006,808748,1025173 49 | 2005,2007,956828,1082127 50 | 2005,2008,1047955,1116300 51 | 2006,2006,531884,975121 52 | 2006,2007,899137,1139751 53 | 2006,2008,1063768,1203071 54 | 2007,2007,589505,1080759 55 | 2007,2008,996544,1263224 56 | 2008,2008,651558,1194523 57 | -------------------------------------------------------------------------------- /faslr/samples/friedland_uspp_increasing_claim_case.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Paid Claims,Reported Claims 2 | 1999,1999,294000,539000 3 | 1999,2000,497000,630000 4 | 1999,2001,588000,665000 5 | 1999,2002,644000,686000 6 | 1999,2003,672000,693000 7 | 1999,2004,686000,693000 8 | 1999,2005,693000,700000 9 | 1999,2006,693000,700000 10 | 1999,2007,700000,700000 11 | 1999,2008,700000,565950 12 | 2000,2000,308700,661500 13 | 2000,2001,521850,698250 14 | 2000,2002,617400,720300 15 | 2000,2003,676200,727650 16 | 2000,2004,705600,727650 17 | 2000,2005,720300,735000 18 | 2000,2006,727650,735000 19 | 2000,2007,727560,735000 20 | 2000,2008,735000,735000 21 | 2001,2001,324135,594248 22 | 2001,2002,547943,694575 23 | 2001,2003,648270,733163 24 | 2001,2004,710010,756315 25 | 2001,2005,740880,764033 26 | 2001,2006,756315,764033 27 | 2001,2007,764033,771750 28 | 2001,2008,764033,771750 29 | 2002,2002,340342,623960 30 | 2002,2003,575340,729304 31 | 2002,2004,680684,769821 32 | 2002,2005,745511,794131 33 | 2002,2006,777924,802234 34 | 2002,2007,794131,802234 35 | 2002,2008,802234,810338 36 | 2003,2003,357359,655158 37 | 2003,2004,604107,765769 38 | 2003,2005,714718,808312 39 | 2003,2006,782786,833837 40 | 2003,2007,816820,842346 41 | 2003,2008,833837,842346 42 | 2004,2004,428831,786189 43 | 2004,2005,724928,918923 44 | 2004,2006,857661,969974 45 | 2004,2007,939343,1004280 46 | 2004,2008,980184,1010815 47 | 2005,2005,478414,877093 48 | 2005,2006,808748,1025173 49 | 2005,2007,956828,1089645 50 | 2005,2008,1047955,1133386 51 | 2006,2006,531884,975121 52 | 2006,2007,899137,1154188 53 | 2006,2008,1063768,1237897 54 | 2007,2007,589505,1110234 55 | 2008,2008,996544,1329895 56 | 2008,2008,651558,1330264 57 | -------------------------------------------------------------------------------- /faslr/samples/friedland_med_mal.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Reported Claims,Paid Claims,Case Outstanding,Open Claim Counts 2 | 1969,1969,2897000,125000,2772000,749 3 | 1969,1970,5160000,406000,4754000,840 4 | 1969,1971,10714000,1443000,9271000,1001 5 | 1969,1972,15228000,2986000,12242000,1206 6 | 1969,1973,16611000,4467000,12144000,1034 7 | 1969,1974,20899000,8179000,12720000,765 8 | 1969,1975,22892000,12638000,10254000,533 9 | 1969,1976,23506000,15815000,7691000,359 10 | 1970,1970,4828000,43000,4785000,660 11 | 1970,1971,10707000,529000,10178000,957 12 | 1970,1972,16907000,2016000,14891000,1149 13 | 1970,1973,22840000,3641000,19199000,1350 14 | 1970,1974,26211000,7523000,18688000,1095 15 | 1970,1975,31970000,14295000,17675000,755 16 | 1970,1976,32216000,18983000,13233000,539 17 | 1971,1971,5455000,295000,5160000,878 18 | 1971,1972,11941000,1147000,10794000,1329 19 | 1971,1973,20733000,2479000,18254000,1720 20 | 1971,1974,30928000,5071000,25857000,1799 21 | 1971,1975,42395000,11399000,30996000,1428 22 | 1971,1976,48377000,17707000,30670000,1056 23 | 1972,1972,8732000,50000,8682000,1043 24 | 1972,1973,18633000,786000,17847000,1561 25 | 1972,1974,32143000,3810000,28333000,1828 26 | 1972,1975,57196000,9771000,47425000,1894 27 | 1972,1976,61163000,18518000,42645000,1522 28 | 1973,1973,11228000,213000,11015000,1088 29 | 1973,1974,19967000,833000,19134000,1388 30 | 1973,1975,50143000,3599000,46544000,1540 31 | 1973,1976,73733000,11292000,62441000,1877 32 | 1974,1974,8706000,172000,8534000,1033 33 | 1974,1975,33459000,1587000,31872000,1418 34 | 1974,1976,63477000,6267000,57210000,1663 35 | 1975,1975,12928000,210000,12718000,1138 36 | 1975,1976,48904000,1565000,47339000,1472 37 | 1976,1976,15791000,209000,15582000,1196 38 | -------------------------------------------------------------------------------- /faslr/style/plot/xkcd.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | 4 | def draw_stick_figure(ax, x=.5, y=.5, radius=.03, quote=None, color='k', lw=2, xytext=(0, 20)): 5 | """ 6 | Taken from https://alimanfoo.github.io/2016/05/31/matplotlib-xkcd.html. 7 | """ 8 | # draw the head 9 | head = plt.Circle((x, y), radius=radius, transform=ax.transAxes, 10 | edgecolor=color, lw=lw, facecolor='none', zorder=10) 11 | ax.add_patch(head) 12 | 13 | # draw the body 14 | body = plt.Line2D([x, x], [y - radius, y - (radius * 4)], 15 | color=color, lw=lw, transform=ax.transAxes) 16 | ax.add_line(body) 17 | 18 | # draw the arms 19 | arm1 = plt.Line2D([x, x + (radius)], [y - (radius * 1.5), y - (radius * 5)], 20 | color=color, lw=lw, transform=ax.transAxes) 21 | ax.add_line(arm1) 22 | arm2 = plt.Line2D([x, x - (radius * .8)], [y - (radius * 1.5), y - (radius * 5)], 23 | color=color, lw=lw, transform=ax.transAxes) 24 | ax.add_line(arm2) 25 | 26 | # draw the legs 27 | leg1 = plt.Line2D([x, x + (radius)], [y - (radius * 4), y - (radius * 8)], 28 | color=color, lw=lw, transform=ax.transAxes) 29 | ax.add_line(leg1) 30 | leg2 = plt.Line2D([x, x - (radius * .5)], [y - (radius * 4), y - (radius * 8)], 31 | color=color, lw=lw, transform=ax.transAxes) 32 | ax.add_line(leg2) 33 | 34 | # say something 35 | if quote: 36 | ax.annotate(quote, xy=(x + radius, y + radius), xytext=xytext, 37 | xycoords='axes fraction', textcoords='offset points', 38 | arrowprops=dict(arrowstyle='-', lw=1)) -------------------------------------------------------------------------------- /faslr/demos/methods/benktander/benktander_demo.py: -------------------------------------------------------------------------------- 1 | import chainladder as cl 2 | import sys 3 | 4 | from faslr.demos.sample_db import set_sample_db 5 | 6 | from faslr.methods.benktander import ( 7 | BenktanderWidget 8 | ) 9 | 10 | import pandas as pd 11 | 12 | from faslr.utilities import ( 13 | load_sample, 14 | table_from_tri, 15 | auto_bi_olep 16 | ) 17 | 18 | from PyQt6.QtWidgets import ( 19 | QApplication 20 | ) 21 | 22 | set_sample_db() 23 | 24 | app = QApplication(sys.argv) 25 | 26 | averages = pd.DataFrame( 27 | data=[ 28 | [True, "7-year Straight", "Straight", "7"], 29 | [False, "7-year Excl. High/Low", "Medial", '7'], 30 | [False, "3-year Straight", "Straight", "3"], 31 | [False, "5-year Straight", "Straight", "5"], 32 | ], 33 | columns=["Selected", "Label", "Type", "Number of Years"] 34 | ) 35 | 36 | 37 | premium = [ 38 | 1000000, 39 | 1050000, 40 | 1102500, 41 | 1157625, 42 | 1215506, 43 | 1276282, 44 | 1340096, 45 | 1407100, 46 | 1477455, 47 | 1551328 48 | ] 49 | triangle = load_sample('uspp_auto_incr_claim') 50 | reported = triangle['Reported Claims'] 51 | paid = triangle['Paid Claims'] 52 | 53 | reported_ult = cl.TailConstant(tail=1).fit_transform(cl.Development(n_periods=5, average='volume').fit_transform(reported)) 54 | reported_ult = cl.Chainladder().fit(reported_ult) 55 | 56 | paid_ult = cl.TailConstant(tail=1).fit_transform(cl.Development(n_periods=5, average='volume').fit_transform(paid)) 57 | paid_ult = cl.Chainladder().fit(paid_ult) 58 | 59 | widget = BenktanderWidget( 60 | triangles=[reported_ult, paid_ult], 61 | premium=premium, 62 | averages=averages 63 | ) 64 | widget.show() 65 | 66 | app.exec() -------------------------------------------------------------------------------- /faslr/samples/friedland_us_auto_chg_prod_mix.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Paid Claims,Reported Claims 2 | 1999,1999,470000,1011000 3 | 1999,2000,865000,1254000 4 | 1999,2001,1124000,1377000 5 | 1999,2002,1300000,1454000 6 | 1999,2003,1400000,1477000 7 | 1999,2004,1446000,1493000 8 | 1999,2005,1469000,1500000 9 | 1999,2006,1477000,1500000 10 | 1999,2007,1492000,1500000 11 | 1999,2008,1500000,1500000 12 | 2000,2000,493500,1061550 13 | 2000,2001,908250,1316700 14 | 2000,2002,1180200,1445850 15 | 2000,2003,1365000,1526700 16 | 2000,2004,1470000,1550850 17 | 2000,2005,1518300,1567650 18 | 2000,2006,1542450,1575000 19 | 2000,2007,1550850,1575000 20 | 2000,2008,1566600,1575000 21 | 2001,2001,518175,1114628 22 | 2001,2002,953663,1382535 23 | 2001,2003,1239210,1518143 24 | 2001,2004,1433250,1603035 25 | 2001,2005,1543500,1628393 26 | 2001,2006,1594215,1646033 27 | 2001,2007,1619573,1653750 28 | 2001,2008,1628393,1653750 29 | 2002,2002,544084,1170359 30 | 2002,2003,1001346,1451662 31 | 2002,2004,1301171,1594050 32 | 2002,2005,1504913,1683187 33 | 2002,2006,1620675,1709812 34 | 2002,2007,1673926,1728334 35 | 2002,2008,1700551,1736438 36 | 2003,2003,571288,1228877 37 | 2003,2004,1051413,1524245 38 | 2003,2005,1366229,1673752 39 | 2003,2006,1580158,1767346 40 | 2003,2007,1701709,1795303 41 | 2003,2008,1757622,1814751 42 | 2004,2004,599852,1290321 43 | 2004,2005,1103984,1600457 44 | 2004,2006,1434540,1757440 45 | 2004,2007,1659166,1855713 46 | 2004,2008,1786794,1885068 47 | 2005,2005,686001,1505438 48 | 2005,2006,1276601,1879580 49 | 2005,2007,1677289,2072490 50 | 2005,2008,1951435,2193545 51 | 2006,2006,793305,1776491 52 | 2006,2007,1493074,2232389 53 | 2006,2008,1983482,2471446 54 | 2007,2007,927874,2119832 55 | 2007,2008,1766164,2680487 56 | 2008,2008,1097644,2556695 57 | -------------------------------------------------------------------------------- /faslr/samples/friedland_us_auto_steady_state.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Paid Claims,Reported Claims 2 | 1999,1999,470000,1011000 3 | 1999,2000,865000,1254000 4 | 1999,2001,1124000,1377000 5 | 1999,2002,1300000,1454000 6 | 1999,2003,1400000,1477000 7 | 1999,2004,1446000,1493000 8 | 1999,2005,1469000,1500000 9 | 1999,2006,1477000,1500000 10 | 1999,2007,1492000,1500000 11 | 1999,2008,1500000,1500000 12 | 2000,2000,493500,1061550 13 | 2000,2001,908250,1316700 14 | 2000,2002,1180200,1445850 15 | 2000,2003,1365000,1526700 16 | 2000,2004,1470000,1550850 17 | 2000,2005,1518300,1567650 18 | 2000,2006,1542450,1575000 19 | 2000,2007,1550850,1575000 20 | 2000,2008,1566600,1575000 21 | 2001,2001,518175,1114628 22 | 2001,2002,953663,1382535 23 | 2001,2003,1239210,1518143 24 | 2001,2004,1433250,1603035 25 | 2001,2005,1543500,1628393 26 | 2001,2006,1594215,1646033 27 | 2001,2007,1619573,1653750 28 | 2001,2008,1628393,1653750 29 | 2002,2002,544084,1170359 30 | 2002,2003,1001346,1451662 31 | 2002,2004,1301171,1594050 32 | 2002,2005,1504913,1683187 33 | 2002,2006,1620675,1709812 34 | 2002,2007,1673926,1728334 35 | 2002,2008,1700551,1736438 36 | 2003,2003,571288,1228877 37 | 2003,2004,1051413,1524245 38 | 2003,2005,1366229,1673752 39 | 2003,2006,1580158,1767346 40 | 2003,2007,1701709,1795303 41 | 2003,2008,1757622,1814751 42 | 2004,2004,599852,1290321 43 | 2004,2005,1103984,1600457 44 | 2004,2006,1434540,1757440 45 | 2004,2007,1659166,1855713 46 | 2004,2008,1786794,1885068 47 | 2005,2005,629845,1354837 48 | 2005,2006,1159183,1680480 49 | 2005,2007,1506268,1845312 50 | 2005,2008,1742124,1948499 51 | 2006,2006,661337,1422579 52 | 2006,2007,1217142,1764504 53 | 2006,2008,1581581,1937577 54 | 2007,2007,694404,1493707 55 | 2008,2008,1277999,1852729 56 | 2008,2008,729124,1568393 57 | -------------------------------------------------------------------------------- /faslr/constants/__init__.py: -------------------------------------------------------------------------------- 1 | from faslr.constants.analysis import ( 2 | VALUE_TYPES, 3 | VALUE_TYPES_COMBO_BOX_WIDTH 4 | ) 5 | 6 | from faslr.constants.connection import ( 7 | DB_NOT_FOUND_TEXT 8 | ) 9 | 10 | from faslr.constants.development import ( 11 | LDF_AVERAGES, 12 | TEMP_LDF_LIST 13 | ) 14 | 15 | from faslr.constants.diagnostics import ( 16 | MACK_DEVELOPMENT_CRITICAL, 17 | MACK_VALUATION_CRITICAL 18 | ) 19 | 20 | from faslr.constants.settings import ( 21 | SETTINGS_LIST 22 | ) 23 | 24 | from faslr.constants.general import ( 25 | BRANCH_SHA, 26 | BUILD_VERSION, 27 | CONFIG_PATH, 28 | CONFIG_TEMPLATES_PATH, 29 | CURRENT_BRANCH, 30 | CURRENT_COMMIT, 31 | CURRENT_COMMIT_LONG, 32 | DEFAULT_DIALOG_PATH, 33 | DISCUSSIONS_URL, 34 | DOCUMENTATION_URL, 35 | GITHUB_URL, 36 | ICONS_PATH, 37 | ISSUES_URL, 38 | OCTICONS_PATH, 39 | QT_FILEPATH_OPTION, 40 | REPO_PATH, 41 | ROOT_PATH, 42 | SAMPLE_DB_NAME, 43 | SAMPLE_DB_DEFAULT_PATH, 44 | SAMPLE_DIALOG_PATH, 45 | TEMPLATES_PATH 46 | ) 47 | 48 | from faslr.constants.model import ( 49 | BASE_MODEL_AVERAGES 50 | ) 51 | 52 | from faslr.constants.role import ( 53 | ColumnSpanRole, 54 | RowSpanRole, 55 | ColumnGroupRole, 56 | ExhibitColumnRole, 57 | AddColumnRole, 58 | DropColumnRole, 59 | RemoveCellLabelRole, 60 | RemoveRowSpanRole, 61 | RemoveColumnSpanRole, 62 | ColumnSwapRole, 63 | ColumnRotateRole, 64 | IndexConstantRole, 65 | AddAverageRole, 66 | SelectAverageRole, 67 | UpdateIndexRole 68 | ) 69 | 70 | from faslr.constants.triangle import ( 71 | DEVELOPMENT_FIELDS, 72 | GRAINS, 73 | LOSS_FIELDS, 74 | ORIGIN_FIELDS, 75 | TIME_FIELDS 76 | ) 77 | -------------------------------------------------------------------------------- /faslr/samples/friedland_us_industry_auto_case.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Case Outstanding,Incremental Paid Claims 2 | 1998,1998,18478233,18539254 3 | 1998,1999,9937970,14691785 4 | 1998,2000,5506911,6830969 5 | 1998,2001,2892519,3830031 6 | 1998,2002,1440783,2004496 7 | 1998,2003,767842,868887 8 | 1998,2004,413097,455900 9 | 1998,2005,242778,225555 10 | 1998,2006,169222,108579 11 | 1998,2007,98117,88731 12 | 1999,1999,18544291,20410193 13 | 1999,2000,9955034,15680491 14 | 1999,2001,5623522,7168718 15 | 1999,2002,3060431,3899839 16 | 1999,2003,1520760,2049291 17 | 1999,2004,764736,953511 18 | 1999,2005,443528,463714 19 | 1999,2006,284732,253051 20 | 1999,2007,185233,121726 21 | 2000,2000,19034933,22120843 22 | 2000,2001,10395464,16855171 23 | 2000,2002,5969194,7413268 24 | 2000,2003,3217937,4173103 25 | 2000,2004,1567806,2172895 26 | 2000,2005,842849,1004821 27 | 2000,2006,457854,544233 28 | 2000,2007,304704,248891 29 | 2001,2001,19401810,22992259 30 | 2001,2002,10487914,17103939 31 | 2001,2003,5936461,7671637 32 | 2001,2004,3056202,4326081 33 | 2001,2005,1532147,2269520 34 | 2001,2006,777926,1015365 35 | 2001,2007,421141,499620 36 | 2002,2002,20662461,24092782 37 | 2002,2003,11176330,17702531 38 | 2002,2004,6198509,8108490 39 | 2002,2005,3350967,4449081 40 | 2002,2006,1609188,2401492 41 | 2002,2007,785497,1052839 42 | 2003,2003,21078651,24084451 43 | 2003,2004,11098119,17315161 44 | 2003,2005,6398219,7670720 45 | 2003,2006,3431210,4513869 46 | 2003,2007,1634690,2346453 47 | 2004,2004,21047539,24369770 48 | 2004,2005,11150459,17120093 49 | 2004,2006,6316995,7746815 50 | 2004,2007,3201985,4537994 51 | 2005,2005,21260172,25100697 52 | 2005,2006,11087832,17601532 53 | 2005,2007,6141416,7942765 54 | 2006,2006,20973908,25608776 55 | 2006,2007,11034842,17997721 56 | 2007,2007,21623594,27229969 57 | -------------------------------------------------------------------------------- /faslr/samples/friedland_xyz_auto_bi.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Paid Claims,Reported Claims 2 | 1998,1998,, 3 | 1998,1999,, 4 | 1998,2000,6309,11171 5 | 1998,2001,8521,12380 6 | 1998,2002,10082,13216 7 | 1998,2003,11620,14067 8 | 1998,2004,13242,14688 9 | 1998,2005,14419,16366 10 | 1998,2006,15311,16163 11 | 1998,2007,15764,15835 12 | 1998,2008,15822,15822 13 | 1999,1999,, 14 | 1999,2000,4666,13255 15 | 1999,2001,9861,16405 16 | 1999,2002,13971,19639 17 | 1999,2003,18127,22473 18 | 1999,2004,22032,23764 19 | 1999,2005,23511,25094 20 | 1999,2006,24146,24795 21 | 1999,2007,24592,25071 22 | 1999,2008,24817,25107 23 | 2000,2000,1302,15676 24 | 2000,2001,6513,18749 25 | 2000,2002,12139,21900 26 | 2000,2003,17828,27144 27 | 2000,2004,24030,29488 28 | 2000,2005,28853,34458 29 | 2000,2006,33222,36949 30 | 2000,2007,35902,37505 31 | 2000,2008,36782,37246 32 | 2001,2001,1539,11827 33 | 2001,2002,5952,16004 34 | 2001,2003,12319,21022 35 | 2001,2004,18609,26578 36 | 2001,2005,24387,34205 37 | 2001,2006,31090,37136 38 | 2001,2007,37070,38541 39 | 2001,2008,38519,38798 40 | 2002,2002,2318,12811 41 | 2002,2003,7932,20370 42 | 2002,2004,13822,26656 43 | 2002,2005,22095,37667 44 | 2002,2006,31945,44414 45 | 2002,2007,40629,48701 46 | 2002,2008,44437,48169 47 | 2003,2003,1743,9651 48 | 2003,2004,6240,16995 49 | 2003,2005,12683,30354 50 | 2003,2006,22892,40594 51 | 2003,2007,34505,44231 52 | 2003,2008,39320,44373 53 | 2004,2004,2221,16995 54 | 2004,2005,9898,40180 55 | 2004,2006,25950,58866 56 | 2004,2007,43439,71707 57 | 2004,2008,52811,70288 58 | 2005,2005,3043,28674 59 | 2005,2006,12219,47432 60 | 2005,2007,27073,70340 61 | 2005,2008,40026,70655 62 | 2006,2006,3531,27066 63 | 2006,2007,11778,46783 64 | 2006,2008,22819,48804 65 | 2007,2007,3529,19477 66 | 2007,2008,11865,31732 67 | 2008,2008,3409,18632 68 | -------------------------------------------------------------------------------- /docs/user/starting/tutorial/connecting.rst: -------------------------------------------------------------------------------- 1 | Connecting to the Database 2 | ========================== 3 | 4 | When starting FASLR for the first time, you will see the :doc:`Main Window <../../interface/main>`. The Main Window has three sections - the Main Menu, the :doc:`Project Tree <../../interface/project>`, and the :doc:`Analysis Pane <../../interface/analysis>`. At first, the Project Tree and Analysis Pane will be blank. Before you create your first project, you will first need to connect to a :doc:`database <../../schema/index>`. The database is what keeps track of all the project metadata, and stores the associated loss and premium data. 5 | 6 | .. image:: https://faslr.com/media/faslr_blank.png 7 | 8 | To create a new database, navigate to **Main Menu -> File -> Connection**. You will see a dialog box asking whether you want to connect to an existing database, or create a new database. Select Create new database and then click **OK**. This will create a SQLite database where you can store your projects. 9 | 10 | .. image:: https://faslr.com/media/faslr_connect.png 11 | 12 | ================================== 13 | Automatically connect upon startup 14 | ================================== 15 | 16 | By default, FASLR will not connect to this database upon startup. To avoid having to connect to it every time you run the program, you may opt to have FASLR establish the connection automatically. To do this, navigate to **Main Menu -> Settings -> Startup -> Add Connection**. This will open a dialog box asking you to select the SQLite file that you want to automatically connect to. Click OK to confirm, and FASLR will now connect to this database every time you start it. 17 | 18 | .. image:: https://faslr.com/media/faslr_autoconnect.png 19 | 20 | To cancel this association, click Reset Connection. -------------------------------------------------------------------------------- /faslr/samples/friedland_us_industry_auto.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Paid Claims,Reported Claims 2 | 1998,1998,18539254,37017487 3 | 1998,1999,33231039,43169009 4 | 1998,2000,40062008,45568919 5 | 1998,2001,43892039,46784558 6 | 1998,2002,45896535,47337318 7 | 1998,2003,46765422,47533264 8 | 1998,2004,47221322,47634419 9 | 1998,2005,47446877,47689655 10 | 1998,2006,47555456,47724678 11 | 1998,2007,47644187,47742304 12 | 1999,1999,20410193,38954484 13 | 1999,2000,36090684,46045718 14 | 1999,2001,43259402,48882924 15 | 1999,2002,47159241,50219672 16 | 1999,2003,49208532,50729292 17 | 1999,2004,50162043,50926779 18 | 1999,2005,50625757,51069285 19 | 1999,2006,50878808,51163540 20 | 1999,2007,51000534,51185767 21 | 2000,2000,22120843,41155776 22 | 2000,2001,38976014,49371478 23 | 2000,2002,46389282,52358476 24 | 2000,2003,50562385,53780322 25 | 2000,2004,52735280,54303086 26 | 2000,2005,53740101,54582950 27 | 2000,2006,54284334,54742188 28 | 2000,2007,54533225,54837929 29 | 2001,2001,22992259,42394069 30 | 2001,2002,40096198,50584112 31 | 2001,2003,47767835,53704296 32 | 2001,2004,52093916,55150118 33 | 2001,2005,54363436,55895583 34 | 2001,2006,55378801,56156727 35 | 2001,2007,55878421,56299562 36 | 2002,2002,24092782,44755243 37 | 2002,2003,41795313,52971643 38 | 2002,2004,49903803,56102312 39 | 2002,2005,54352884,57703851 40 | 2002,2006,56754376,58363564 41 | 2002,2007,57807215,58592712 42 | 2003,2003,24084451,45163102 43 | 2003,2004,41399612,52497731 44 | 2003,2005,49070332,55468551 45 | 2003,2006,53584201,57015411 46 | 2003,2007,55930654,57565344 47 | 2004,2004,24369770,45417309 48 | 2004,2005,41489863,52640322 49 | 2004,2006,49236678,55553673 50 | 2004,2007,53774672,56976657 51 | 2005,2005,25100697,46360869 52 | 2005,2006,42702229,53790061 53 | 2005,2007,50644994,56786410 54 | 2006,2006,25608776,46582684 55 | 2006,2007,43606497,54641339 56 | 2007,2007,27229969,48853563 57 | -------------------------------------------------------------------------------- /faslr/demos/methods/expected_loss/expected_loss_ratio_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Boilerplate code for writing demos. 3 | """ 4 | import pandas as pd 5 | import sys 6 | 7 | from faslr.demos.sample_db import set_sample_db 8 | from faslr.index import FIndex 9 | from faslr.methods.expected_loss import ( 10 | ExpectedLossRatioWidget 11 | ) 12 | from PyQt6.QtWidgets import ( 13 | QApplication 14 | ) 15 | 16 | set_sample_db() 17 | 18 | origin = [ 19 | 1998, 20 | 1999, 21 | 2000, 22 | 2001, 23 | 2002, 24 | 2003, 25 | 2004, 26 | 2005, 27 | 2006, 28 | 2007, 29 | 2008 30 | ] 31 | 32 | ult_claims = [ 33 | 15901, 34 | 25123, 35 | 37435, 36 | 39543, 37 | 48953, 38 | 47404, 39 | 77662, 40 | 78497, 41 | 65239, 42 | 62960, 43 | 61262 44 | ] 45 | 46 | earned_premium = [ 47 | 20000, 48 | 31500, 49 | 45000, 50 | 50000, 51 | 61183, 52 | 69175, 53 | 99322, 54 | 138151, 55 | 107578, 56 | 62438, 57 | 47797 58 | ] 59 | 60 | prem_trend = FIndex(from_id=1) 61 | loss_trend = FIndex(from_id=2) 62 | tort_reform = FIndex(from_id=3) 63 | 64 | averages = pd.DataFrame( 65 | data=[ 66 | [True, "7-year Straight", "Straight", "7"], 67 | [False, "7-year Excl. High/Low", "Medial", '7'], 68 | [False, "3-year Straight", "Straight", "3"], 69 | [False, "5-year Straight", "Straight", "5"], 70 | ], 71 | columns=["Selected", "Label", "Type", "Number of Years"] 72 | ) 73 | 74 | 75 | app = QApplication(sys.argv) 76 | 77 | expected_loss_ratio_widget = ExpectedLossRatioWidget( 78 | origin=origin, 79 | # premium_indexes=[prem_trend], 80 | # claim_indexes=[loss_trend, tort_reform], 81 | premium=earned_premium, 82 | claims=ult_claims, 83 | averages=averages 84 | ) 85 | 86 | 87 | expected_loss_ratio_widget.show() 88 | 89 | app.exec() -------------------------------------------------------------------------------- /faslr/tests/test_base_classes.py: -------------------------------------------------------------------------------- 1 | from faslr.base_classes import ( 2 | FDoubleSpinBox, 3 | FSpinBox, 4 | FComboBox, 5 | FHContainer 6 | ) 7 | 8 | from pytestqt.qtbot import QtBot 9 | 10 | 11 | def test_f_double_spin_box(qtbot:QtBot) -> None: 12 | 13 | f_double_spin_box = FDoubleSpinBox( 14 | label="My Spin Box", 15 | value=.01, 16 | single_step=.01 17 | ) 18 | 19 | qtbot.addWidget(f_double_spin_box) 20 | 21 | assert f_double_spin_box.label.text() == "My Spin Box" 22 | 23 | assert f_double_spin_box.spin_box.value() == .01 24 | 25 | # Simulate click up 26 | f_double_spin_box.spin_box.stepUp() 27 | 28 | assert f_double_spin_box.spin_box.value() == .02 29 | 30 | # Simulate click down 31 | 32 | f_double_spin_box.spin_box.stepDown() 33 | 34 | assert f_double_spin_box.spin_box.value() == .01 35 | 36 | 37 | def test_f_spin_box(qtbot: QtBot) -> None: 38 | 39 | f_spin_box = FSpinBox( 40 | label="My Spin Box", 41 | value=1, 42 | single_step=1 43 | ) 44 | 45 | qtbot.addWidget(f_spin_box) 46 | 47 | assert f_spin_box.label.text() == "My Spin Box" 48 | 49 | assert f_spin_box.spin_box.value() == 1 50 | 51 | # Simulate click up 52 | 53 | f_spin_box.spin_box.stepUp() 54 | 55 | assert f_spin_box.spin_box.value() == 2 56 | 57 | # Simulate click down 58 | 59 | f_spin_box.spin_box.stepDown() 60 | 61 | assert f_spin_box.spin_box.value() == 1 62 | 63 | 64 | def test_f_combo_box(qtbot: QtBot) -> None: 65 | 66 | f_combo_box = FComboBox( 67 | label="My Combo Box" 68 | ) 69 | 70 | qtbot.addWidget(f_combo_box) 71 | 72 | assert f_combo_box.label.text() == "My Combo Box" 73 | 74 | 75 | def test_fh_container(qtbot: QtBot) -> None: 76 | 77 | fh_container = FHContainer() 78 | 79 | qtbot.addWidget(fh_container) 80 | -------------------------------------------------------------------------------- /faslr/samples/friedland_wc_self_insurer.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Closed Claim Counts,Reported Claim Counts,Paid Claims,Paid Severities,Reported Claims,Reported Severities 2 | 2001,2001,789,1235,1318000,1670,3200000,2591 3 | 2001,2002,1196,1321,2842000,2377,4300000,3255 4 | 2001,2003,1255,1342,3750000,2989,4900000,3651 5 | 2001,2004,1310,1349,4300000,3283,5200000,3855 6 | 2001,2005,1324,1350,4650000,3511,5300000,3926 7 | 2001,2006,1327,1350,4850000,3655,5400000,4000 8 | 2001,2007,1332,1350,5050000,3790,5550000,4111 9 | 2001,2008,1343,1350,5200000,3871,5650000,4185 10 | 2002,2002,978,1555,1780000,1820,4300000,2765 11 | 2002,2003,1506,1660,3817000,2535,5900000,3554 12 | 2002,2004,1609,1685,5016000,3117,6600000,3917 13 | 2002,2005,1629,1695,5750000,3530,6950000,4100 14 | 2002,2006,1669,1700,6100000,3654,7200000,4235 15 | 2002,2007,1676,1700,6300000,3759,7400000,4353 16 | 2002,2008,1683,1700,6555000,3895,7500000,4412 17 | 2003,2003,1070,1628,1890000,1767,4800000,2948 18 | 2003,2004,1557,1740,4184000,2687,6600000,3793 19 | 2003,2005,1665,1762,5500000,3303,7400000,4200 20 | 2003,2006,1721,1771,6300000,3660,7800000,4404 21 | 2003,2007,1738,1775,6800000,3913,8100000,4563 22 | 2003,2008,1748,1775,7100000,4061,8300000,4676 23 | 2004,2004,1029,1600,1900000,1847,4900000,3063 24 | 2004,2005,1525,1714,4100000,2688,6700000,3909 25 | 2004,2006,1618,1740,5560000,3436,7700000,4425 26 | 2004,2007,1688,1747,6430000,3810,8150000,4665 27 | 2004,2008,1717,1750,6950000,4048,8600000,4914 28 | 2005,2005,974,1510,1960000,2012,5200000,3444 29 | 2005,2006,1459,1612,4290000,2941,7100000,4404 30 | 2005,2007,1532,1639,5688000,3712,7900000,4821 31 | 2005,2008,1597,1647,6570000,4113,8350000,5071 32 | 2006,2006,1746,2750,4030000,2308,10100000,3673 33 | 2006,2007,2632,2941,8650000,3286,13800000,4692 34 | 2006,2008,2761,2985,11400000,4129,15500000,5193 35 | 2007,2007,1683,2640,4200000,3496,10500000,3962 36 | 2007,2008,2572,2842,9043000,3516,14400000,5067 37 | 2008,2008,1560,2438,4170000,2673,10300000,4225 38 | -------------------------------------------------------------------------------- /docs/contributing/index.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============= 3 | 4 | There are two main ways to contribute. 5 | 6 | ====================== 7 | Contributing to FASLR 8 | ====================== 9 | 10 | Users who are interested in contributing to FASLR are free to open pull requests on the `project GitHub `_. 11 | 12 | There is currently no roadmap, and as this is an unpaid community volunteer effort, I don't feel comfortable telling people exactly what to do. The closest thing to a roadmap would be the `issues `_ page that lists the open tickets. 13 | 14 | Feel free to volunteer for a ticket by commenting in one, and I will assign it to you. If you'd like new features to be added, you should create your own ticket. 15 | 16 | ================================== 17 | Contributing to the Documentation 18 | ================================== 19 | 20 | The documentation source code is located in the `docs folder `_. It comprises of .rst files, and you should use Sphinx to render them. Media such as images and videos are stored outside of the repository on a separate server to save space - so any pull requests should not include these types of media. 21 | 22 | 23 | ================= 24 | Discussion Board 25 | ================= 26 | 27 | Topics, and questions that don't fit neatly into a ticket can be discussed on the project's `discussion board `_. 28 | 29 | ========= 30 | Chat Room 31 | ========= 32 | 33 | If you'd like to participate in instant-messaging like discussions, join the project's `chat room `_. 34 | 35 | ================= 36 | Project Finances 37 | ================= 38 | 39 | I am currently not accepting funding. Aside from my own free time and that of the volunteers, current costs are $5/month to host this website. 40 | 41 | Further costs may include the commissioning of artwork for things like themes and logos, but that will come at a later time. -------------------------------------------------------------------------------- /faslr/demos/analysis_demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from faslr.analysis import AnalysisTab 4 | from faslr.utilities.sample import load_sample 5 | from PyQt6.QtWidgets import ( 6 | QApplication, 7 | QTabWidget 8 | ) 9 | 10 | auto = load_sample('us_industry_auto') 11 | xyz = load_sample('uspp_incr_case') 12 | m97 = load_sample('mack97') 13 | app = QApplication(sys.argv) 14 | 15 | test_pane = QTabWidget() 16 | 17 | auto_tab = AnalysisTab( 18 | triangle=auto 19 | ) 20 | 21 | uspp_tab = AnalysisTab( 22 | triangle=xyz 23 | ) 24 | 25 | mack_tab = AnalysisTab( 26 | triangle=m97 27 | ) 28 | 29 | auto_tab.resize( 30 | auto_tab.triangle_views['Paid Claims'].horizontalHeader().length() + 31 | auto_tab.triangle_views['Paid Claims'].verticalHeader().width(), 32 | auto_tab.triangle_views['Paid Claims'].verticalHeader().length() + 33 | auto_tab.triangle_views['Paid Claims'].horizontalHeader().height() 34 | ) 35 | 36 | 37 | test_pane.addTab(auto_tab, "Auto") 38 | test_pane.addTab(uspp_tab, "USPP") 39 | test_pane.addTab(mack_tab, 'Mack 97') 40 | 41 | test_pane.resize( 42 | auto_tab.width(), 43 | auto_tab.height() 44 | ) 45 | 46 | # test_pane.setStyleSheet( 47 | # """ 48 | # QTabWidget::pane { 49 | # border: 1px solid darkgrey; 50 | # background: rgb(245, 245, 245); 51 | # } 52 | # 53 | # QTabBar::tab { 54 | # background: rgb(230, 230, 230); 55 | # border: 1px solid darkgrey; 56 | # padding: 5px; 57 | # padding-left: 10px; 58 | # height: 30px; 59 | # } 60 | # 61 | # QTabBar::tab:selected { 62 | # background: rgb(245, 245, 245); 63 | # margin-bottom: -1px; 64 | # } 65 | # 66 | # """ 67 | # ) 68 | 69 | # test_pane.setStyleSheet( 70 | # """ 71 | # QTabBar::tab:selected { 72 | # background: rgb(245, 245, 245); 73 | # } 74 | # """ 75 | # ) 76 | 77 | test_pane.setWindowTitle("Mack Diagnostics Demo") 78 | test_pane.show() 79 | 80 | app.exec() 81 | -------------------------------------------------------------------------------- /faslr/tests/test_schema.py: -------------------------------------------------------------------------------- 1 | # import sqlalchemy as sa 2 | # 3 | # from faslr import schema 4 | # from faslr.schema import UserTable, CountryTable, LocationTable 5 | # from sqlalchemy.orm import sessionmaker 6 | # 7 | # engine = sa.create_engine( 8 | # 'sqlite:///test_schema.db', 9 | # echo=True 10 | # ) 11 | # 12 | # schema.Base.metadata.create_all(engine) 13 | # session = sessionmaker(bind=engine)() 14 | # connection = engine.connect() 15 | # 16 | # country_text = 'USA' 17 | # country_query = session.query(CountryTable).filter(CountryTable.country_name == country_text) 18 | # 19 | # session.query(LocationTable) 20 | # session.commit() 21 | # 22 | # connection.close() 23 | 24 | from faslr.schema import ( 25 | CountryTable, 26 | LocationTable, 27 | StateTable, 28 | LOBTable, 29 | ProjectTable, 30 | UserTable, 31 | ProjectViewTable, 32 | ProjectViewData, 33 | IndexTable, 34 | IndexValuesTable 35 | ) 36 | 37 | def test_country_table() -> None: 38 | country_table = CountryTable() 39 | 40 | repr(country_table) 41 | 42 | def test_location_table() -> None: 43 | location_table = LocationTable() 44 | 45 | repr(location_table) 46 | 47 | def test_state_table() -> None: 48 | state_table = StateTable() 49 | 50 | repr(state_table) 51 | 52 | def test_lob_table() -> None: 53 | 54 | lob_table = LOBTable() 55 | 56 | repr(lob_table) 57 | 58 | def test_project_table() -> None: 59 | 60 | project_table = ProjectTable() 61 | 62 | repr(project_table) 63 | 64 | def test_user_table() -> None: 65 | 66 | user_table = UserTable() 67 | 68 | repr(user_table) 69 | 70 | def test_project_view_table() -> None: 71 | 72 | project_view_table = ProjectViewTable() 73 | 74 | repr(project_view_table) 75 | 76 | def test_project_view_data() -> None: 77 | 78 | project_view_data = ProjectViewData() 79 | 80 | repr(project_view_data) 81 | 82 | def test_index_table() -> None: 83 | 84 | index_table = IndexTable() 85 | 86 | repr(index_table) 87 | 88 | def test_index_values_table() -> None: 89 | 90 | index_values_table = IndexValuesTable() 91 | 92 | repr(index_values_table) 93 | -------------------------------------------------------------------------------- /faslr/tests/test_cascade.py: -------------------------------------------------------------------------------- 1 | # import sqlalchemy as sa 2 | # 3 | # from faslr import schema 4 | # from faslr.schema import ( 5 | # UserTable, 6 | # CountryTable, 7 | # LocationTable, 8 | # StateTable, 9 | # LOBTable, 10 | # ProjectTable 11 | # ) 12 | # 13 | # from sqlalchemy.orm import sessionmaker 14 | # from uuid import uuid4 15 | # 16 | # engine = sa.create_engine( 17 | # 'sqlite:///test_schema.db', 18 | # echo=True, 19 | # connect_args={'check_same_thread': False} 20 | # ) 21 | # 22 | # from sqlalchemy.engine import Engine 23 | # from sqlalchemy import event 24 | # 25 | # 26 | # @event.listens_for(Engine, "connect") 27 | # def set_sqlite_pragma(dbapi_connection, connection_record): 28 | # cursor = dbapi_connection.cursor() 29 | # cursor.execute("PRAGMA foreign_keys=ON") 30 | # cursor.close() 31 | # 32 | # 33 | # schema.Base.metadata.create_all(engine) 34 | # session = sessionmaker(bind=engine)() 35 | # connection = engine.connect() 36 | # 37 | # 38 | # country_uuid = str(uuid4()) 39 | # new_country_project = ProjectTable(project_id=country_uuid) 40 | # new_country_location = LocationTable(hierarchy="country") 41 | # new_state_location = LocationTable(hierarchy="state") 42 | # 43 | # session.add(new_country_project) 44 | # session.add(new_country_location) 45 | # session.add(new_state_location) 46 | # session.flush() 47 | # 48 | # 49 | # new_country = CountryTable( 50 | # location_id=new_country_location.location_id, 51 | # country_name="USA", 52 | # project_id=new_country_project.project_id 53 | # ) 54 | # 55 | # new_state = StateTable(location_id=new_state_location.location_id, state_name="Texas") 56 | # new_lob = LOBTable(lob_type="Auto", location_id=2) 57 | # 58 | # 59 | # session.add(new_country) 60 | # session.add(new_state) 61 | # session.add(new_lob) 62 | # 63 | # session.commit() 64 | # session.close() 65 | # 66 | # old_country = session.query(LocationTable).filter(LocationTable.location_id == 1).one() 67 | # old_state = session.query(LocationTable).filter(LocationTable.location_id == 2).one() 68 | # 69 | # session.delete(old_country) 70 | # session.delete(old_state) 71 | # session.commit() 72 | # session.close() 73 | -------------------------------------------------------------------------------- /faslr/demos/grid_header_demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from faslr.grid_header import ( 4 | GridTableView, 5 | ) 6 | 7 | from PyQt6.QtCore import Qt 8 | 9 | from PyQt6.QtGui import ( 10 | QStandardItem, 11 | QStandardItemModel 12 | ) 13 | 14 | 15 | from PyQt6.QtWidgets import ( 16 | QApplication, 17 | QVBoxLayout, 18 | QWidget 19 | ) 20 | 21 | 22 | app = QApplication(sys.argv) 23 | 24 | main_widget = QWidget() 25 | layout = QVBoxLayout() 26 | main_widget.setLayout(layout) 27 | 28 | # Generate dummy data 29 | 30 | model = QStandardItemModel() 31 | view = GridTableView(corner_button_label="Accident\nYear") 32 | view.setModel(model) 33 | 34 | for row in range(9): 35 | items = [] 36 | for col in range(9): 37 | items.append(QStandardItem('item(' + str(row) + ',' + str(col) + ')')) 38 | model.appendRow(items) 39 | 40 | layout.addWidget(view) 41 | 42 | view.setGridHeaderView( 43 | orientation=Qt.Orientation.Horizontal, 44 | levels=2 45 | ) 46 | 47 | view.hheader.setSpan( 48 | row=0, 49 | column=0, 50 | row_span_count=2, 51 | column_span_count=0 52 | ) 53 | view.hheader.setSpan( 54 | row=0, 55 | column=1, 56 | row_span_count=2, 57 | column_span_count=0 58 | ) 59 | view.hheader.setSpan( 60 | row=0, 61 | column=2, 62 | row_span_count=2, 63 | column_span_count=0 64 | ) 65 | view.hheader.setSpan( 66 | row=0, 67 | column=3, 68 | row_span_count=1, 69 | column_span_count=2 70 | ) 71 | view.hheader.setSpan( 72 | row=0, 73 | column=5, 74 | row_span_count=2, 75 | column_span_count=0 76 | ) 77 | view.hheader.setSpan( 78 | row=0, 79 | column=6, 80 | row_span_count=2, 81 | column_span_count=0 82 | ) 83 | 84 | view.hheader.setCellLabel(0, 0, "cell1") 85 | view.hheader.setCellLabel(0, 1, "cell2") 86 | view.hheader.setCellLabel(0, 2, "cell3") 87 | view.hheader.setCellLabel(0, 3, "cell4") 88 | view.hheader.setCellLabel(1, 3, "cell5") 89 | view.hheader.setCellLabel(1, 4, "cell6") 90 | view.hheader.setCellLabel(0, 5, "cell7") 91 | 92 | main_widget.resize(view.hheader.sizeHint().width(), 315) 93 | main_widget.resize(937, 315) 94 | main_widget.show() 95 | 96 | 97 | app.exec() 98 | -------------------------------------------------------------------------------- /faslr/samples/friedland_xyz_case.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Case Outstanding,Incremental Paid Claims,Incremental Paid to Previous Case,Case to Previous Case 2 | 1998,1998,,,, 3 | 1998,1999,,,, 4 | 1998,2000,4861,6309,, 5 | 1998,2001,3859,2212,0.455,0.794 6 | 1998,2002,3134,1561,0.405,0.812 7 | 1998,2003,2447,1537,0.491,0.781 8 | 1998,2004,1446,1622,0.663,0.591 9 | 1998,2005,1947,1177,0.814,1.346 10 | 1998,2006,853,892,0.458,0.438 11 | 1998,2007,71,453,0.532,0.084 12 | 1998,2008,0,58,0.816,0 13 | 1999,1999,,,, 14 | 1999,2000,8589,4666,, 15 | 1999,2001,6544,5195,0.605,0.762 16 | 1999,2002,5668,4110,0.628,0.866 17 | 1999,2003,4347,4156,0.733,0.767 18 | 1999,2004,1732,3906,0.899,0.398 19 | 1999,2005,1583,1478,0.854,0.914 20 | 1999,2006,649,635,0.401,0.41 21 | 1999,2007,479,446,0.688,0.739 22 | 1999,2008,290,225,0.469,0.605 23 | 2000,2000,14374,1302,, 24 | 2000,2001,12237,5210,0.362,0.851 25 | 2000,2002,9760,5627,0.46,0.798 26 | 2000,2003,9316,5689,0.583,0.954 27 | 2000,2004,5458,6202,0.666,0.586 28 | 2000,2005,5605,4823,0.884,1.027 29 | 2000,2006,3727,4369,0.78,0.665 30 | 2000,2007,1603,2680,0.719,0.43 31 | 2000,2008,465,880,0.549,0.29 32 | 2001,2001,10288,1539,, 33 | 2001,2002,10052,4413,0.429,0.977 34 | 2001,2003,8703,6367,0.633,0.866 35 | 2001,2004,7969,6289,0.723,0.916 36 | 2001,2005,9818,5778,0.725,1.232 37 | 2001,2006,6046,6703,0.683,0.616 38 | 2001,2007,1471,5980,0.989,0.243 39 | 2001,2008,278,1450,0.985,0.189 40 | 2002,2002,10494,2318,, 41 | 2002,2003,12439,5614,0.535,1.185 42 | 2002,2004,12833,5891,0.474,1.032 43 | 2002,2005,15572,8273,0.645,1.213 44 | 2002,2006,12469,9850,0.633,0.801 45 | 2002,2007,8072,8683,0.696,0.647 46 | 2002,2008,3731,3809,0.472,0.462 47 | 2003,2003,7908,1743,, 48 | 2003,2004,10755,4497,0.569,1.36 49 | 2003,2005,17671,6443,0.599,1.643 50 | 2003,2006,17702,10209,0.578,1.002 51 | 2003,2007,9726,11613,0.656,0.549 52 | 2003,2008,5052,4815,0.495,0.519 53 | 2004,2004,14774,2221,, 54 | 2004,2005,30281,7677,0.52,2.05 55 | 2004,2006,32916,16052,0.53,1.087 56 | 2004,2007,28268,17489,0.531,0.859 57 | 2004,2008,17477,9372,0.332,0.618 58 | 2005,2005,25631,3043,, 59 | 2005,2006,35213,9176,0.358,1.374 60 | 2005,2007,43268,14854,0.422,1.229 61 | 2005,2008,30629,12953,0.299,0.708 62 | 2006,2006,23535,3531,, 63 | 2006,2007,35005,8247,0.35,1.487 64 | 2006,2008,25985,11041,0.315,0.742 65 | 2007,2007,15948,3529,, 66 | 2007,2008,19867,8336,0.523,1.246 67 | 2008,2008,15223,3409,, 68 | -------------------------------------------------------------------------------- /faslr/style/icons/scatter-plot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /faslr/constants/general.py: -------------------------------------------------------------------------------- 1 | import git 2 | import os 3 | 4 | from faslr._version import __version__ 5 | from os.path import dirname 6 | from PyQt6.QtWidgets import QFileDialog 7 | 8 | 9 | BUILD_VERSION = __version__ 10 | 11 | # if "PYCHARM_HOSTED" in os.environ: 12 | # QT_FILEPATH_OPTION = QFileDialog.Option.DontUseNativeDialog 13 | # # Don't cover this, can't test without leaving PyCharm. 14 | # else: # pragma no coverage 15 | # QT_FILEPATH_OPTION = QFileDialog.Option.ShowDirsOnly 16 | 17 | QT_FILEPATH_OPTION = QFileDialog.Option.DontUseNativeDialog 18 | 19 | # Path of the main faslr code moduels (e.g., FASLR/faslr) 20 | ROOT_PATH = dirname(dirname(os.path.realpath(__file__))) 21 | 22 | # Path of the FASLR main repo (e.g., FASLR) 23 | REPO_PATH = dirname(ROOT_PATH) 24 | 25 | # Path of the configuration file 26 | CONFIG_PATH = os.path.join(ROOT_PATH, 'faslr.ini') 27 | 28 | # Path for template files, i.e., the generic configuration file 29 | TEMPLATES_PATH = os.path.join(dirname(dirname(os.path.realpath(__file__))), 'templates') 30 | 31 | CONFIG_TEMPLATES_PATH = os.path.join(TEMPLATES_PATH, 'config_template.ini') 32 | 33 | # Default path for file saves and opens 34 | 35 | DEFAULT_DIALOG_PATH = dirname(ROOT_PATH) 36 | 37 | SAMPLE_DIALOG_PATH = dirname(ROOT_PATH) + '/faslr/samples/' 38 | 39 | # Path for icons 40 | ICONS_PATH = os.path.join(dirname(dirname(os.path.realpath(__file__))), 'style/icons/') 41 | 42 | # URL of the documentation website 43 | DOCUMENTATION_URL = 'https://faslr.com' 44 | 45 | GITHUB_URL = 'https://github.com/casact/faslr' 46 | 47 | DISCUSSIONS_URL = 'https://github.com/casact/FASLR/discussions' 48 | 49 | ISSUES_URL = 'https://github.com/casact/FASLR/issues' 50 | 51 | OCTICONS_PATH = os.path.join( 52 | dirname(dirname(os.path.realpath(__file__))), 53 | 'style/icons/octicons/' 54 | ) 55 | 56 | 57 | repo = git.Repo(search_parent_directories=True) 58 | 59 | try: 60 | branch = repo.active_branch 61 | CURRENT_BRANCH = branch.name 62 | BRANCH_SHA = repo.git.rev_parse( 63 | branch, 64 | short=6 65 | ) 66 | # Don't cover this block - won't be able to test it without leaving current commit 67 | except TypeError: # pragma no coverage 68 | CURRENT_BRANCH = "Detached HEAD" 69 | BRANCH_SHA = "None" 70 | 71 | sha = repo.head.commit.hexsha 72 | 73 | CURRENT_COMMIT_LONG = sha 74 | 75 | CURRENT_COMMIT = repo.git.rev_parse( 76 | sha, 77 | short=6 78 | ) 79 | 80 | SAMPLE_DB_NAME = 'sample.db' 81 | 82 | SAMPLE_DB_DEFAULT_PATH = REPO_PATH + '/' + SAMPLE_DB_NAME -------------------------------------------------------------------------------- /faslr/tests/test_analysis.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from faslr.analysis import ( 4 | AnalysisTab, 5 | MackCriticalSpinBox, 6 | MackValuationModel 7 | ) 8 | 9 | from faslr.constants import ( 10 | MACK_VALUATION_CRITICAL 11 | ) 12 | 13 | from faslr.utilities.sample import load_sample 14 | from PyQt6.QtWidgets import ( 15 | QDoubleSpinBox 16 | ) 17 | from PyQt6.QtCore import QSize, Qt 18 | from PyQt6.QtWidgets import QApplication 19 | 20 | app = QApplication(sys.argv) 21 | 22 | 23 | def test_analysis(qtbot) -> None: 24 | auto = load_sample('us_industry_auto') 25 | auto_tab = AnalysisTab( 26 | triangle=auto 27 | ) 28 | 29 | auto_tab.value_box.setCurrentText("Link Ratios") 30 | auto_tab.update_value_type() 31 | auto_tab.value_box.setCurrentText("Values") 32 | auto_tab.update_value_type() 33 | auto_tab.value_box.setCurrentText("Diagnostics") 34 | auto_tab.update_value_type() 35 | 36 | auto_tab.resize( 37 | auto_tab.triangle_views['Paid Claims'].horizontalHeader().length() + 38 | auto_tab.triangle_views['Paid Claims'].verticalHeader().width(), 39 | auto_tab.triangle_views['Paid Claims'].verticalHeader().length() + 40 | auto_tab.triangle_views['Paid Claims'].horizontalHeader().height() 41 | ) 42 | size = QSize() 43 | auto_tab.resizeEvent(size) 44 | 45 | auto_tab.resize( 46 | auto_tab.triangle_views['Paid Claims'].horizontalHeader().length() + 47 | auto_tab.triangle_views['Paid Claims'].verticalHeader().width() + 500, 48 | auto_tab.triangle_views['Paid Claims'].verticalHeader().length() + 49 | auto_tab.triangle_views['Paid Claims'].horizontalHeader().height() 50 | ) 51 | size = QSize() 52 | auto_tab.resizeEvent(size) 53 | 54 | 55 | def test_analysis_single_column(qtbot) -> None: 56 | 57 | auto = load_sample('us_industry_auto')['Paid Claims'] 58 | auto_tab = AnalysisTab( 59 | triangle=auto 60 | ) 61 | 62 | 63 | def test_mack_valuation_model(qtbot) -> None: 64 | auto = load_sample('us_industry_auto') 65 | sb = MackCriticalSpinBox( 66 | starting_value=MACK_VALUATION_CRITICAL 67 | ) 68 | 69 | mack = MackValuationModel( 70 | triangle=auto, 71 | critical=sb 72 | ) 73 | 74 | value_test = mack.data(mack.index(0, 0), role=Qt.ItemDataRole.DisplayRole) 75 | 76 | role_test = mack.data(mack.index(0, 0), role=Qt.ItemDataRole.BackgroundRole) 77 | 78 | mack._data.iloc[0, 0] 79 | 80 | assert value_test == 'Pass' 81 | 82 | -------------------------------------------------------------------------------- /faslr/samples/friedland_autoprop.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Reported ALAE,Paid ALAE,Reported Claims,Paid Claims 2 | 1998,1998,684,512,109286,78144 3 | 1998,1999,953,856,111832,105902 4 | 1998,2000,1031,949,110648,107306 5 | 1998,2001,1062,1003,109174,108135 6 | 1998,2002,1080,1049,108849,108307 7 | 1998,2003,1084,1065,108779,108494 8 | 1998,2004,1089,1075,108786,108523 9 | 1998,2005,1092,1080,108646,108628 10 | 1998,2006,1092,1082,108736,108731 11 | 1998,2007,1092,1084,108735,108730 12 | 1998,2008,1092,1084,108732,108730 13 | 1999,1999,625,529,120639,81290 14 | 1999,2000,929,874,119607,114037 15 | 1999,2001,1006,952,116924,115347 16 | 1999,2002,1033,988,116482,115696 17 | 1999,2003,1041,1016,116332,115843 18 | 1999,2004,1046,1024,116230,115930 19 | 1999,2005,1049,1034,116236,115962 20 | 1999,2006,1051,1040,116161,115969 21 | 1999,2007,1051,1042,116160,115969 22 | 1999,2008,1051,1045,116125,116033 23 | 2000,2000,571,471,115422,83563 24 | 2000,2001,771,720,119143,114175 25 | 2000,2002,821,787,118641,116044 26 | 2000,2003,844,821,117008,116458 27 | 2000,2004,858,846,116782,116620 28 | 2000,2005,861,855,116919,116857 29 | 2000,2006,862,857,116860,116810 30 | 2000,2007,862,860,116825,116807 31 | 2000,2008,862,861,116472,116807 32 | 2001,2001,629,480,129430,91475 33 | 2001,2002,894,802,139925,113761 34 | 2001,2003,943,882,138161,136143 35 | 2001,2004,982,836,137395,136552 36 | 2001,2005,997,975,137269,136818 37 | 2001,2006,1002,987,137033,136838 38 | 2001,2007,1003,995,136998,136960 39 | 2001,2008,1007,998,137056,136995 40 | 2002,2002,618,451,134190,92349 41 | 2002,2003,872,793,143852,138461 42 | 2002,2004,952,887,143093,140904 43 | 2002,2005,1005,956,142360,141323 44 | 2002,2006,1033,1004,142004,141380 45 | 2002,2007,1093,1067,141715,141452 46 | 2002,2008,1110,1098,141627,141461 47 | 2003,2003,757,572,152678,111655 48 | 2003,2004,948,874,166131,158092 49 | 2003,2005,1035,974,166015,161823 50 | 2003,2006,1092,1041,165579,162556 51 | 2003,2007,1095,1069,165229,162802 52 | 2003,2008,1143,1085,163508,163257 53 | 2004,2004,743,557,144595,106032 54 | 2004,2005,915,840,154830,149157 55 | 2004,2006,976,921,154295,151729 56 | 2004,2007,1001,960,154228,152229 57 | 2004,2008,1032,989,153750,152613 58 | 2005,2005,789,563,137791,98270 59 | 2005,2006,948,882,154230,149504 60 | 2005,2007,1001,941,154307,152895 61 | 2005,2008,1032,987,153981,153154 62 | 2006,2006,988,636,159818,107131 63 | 2006,2007,1140,1064,178399,171332 64 | 2006,2008,1198,1132,179384,175602 65 | 2007,2007,1373,774,162205,114337 66 | 2007,2008,1596,1454,178425,171505 67 | 2008,2008,1556,952,176030,124470 68 | -------------------------------------------------------------------------------- /docs/user/starting/install.rst: -------------------------------------------------------------------------------- 1 | Setup and Installation 2 | ======================= 3 | 4 | FASLR is in the early stages of development. As such, rapid implementation of features has been prioritized over the stability of the user experience (sorry). There are currently no binary executables available. People who are interested in seeing what FASLR can do as well as potential contributors will, for the time being, need to run FASLR from source. 5 | 6 | 7 | =========================== 8 | Supported Operating Systems 9 | =========================== 10 | FASLR is developed on Linux, specifically Ubuntu 22.04, which is probably what you want to use if you want to capture the same look and feel that you see in the documentation and in the development blog. 11 | 12 | However, Python and Qt are cross-platform, which means that you may try it on other systems, but there are no guarantees on my end that it'll work as well as it does on Linux. 13 | 14 | ======================= 15 | Cloning the Repository 16 | ======================= 17 | 18 | The first step in using FASLR is to clone the repository from the `CAS GitHub `_. This can be done by opening a terminal, navigating to the directory where you want to keep the repo, and then running the command: 19 | 20 | .. code-block:: shell 21 | 22 | git clone https://github.com/casact/faslr 23 | 24 | ======================== 25 | Installing Dependencies 26 | ======================== 27 | 28 | I recommend that you use a virtual environment to avoid package conflicts. 29 | 30 | The Python package dependencies is located in the `requirements.txt `_ file. These can be installed with the following command: 31 | 32 | .. code-block:: shell 33 | 34 | pip install -r requirements.txt 35 | 36 | ============== 37 | Running FASLR 38 | ============== 39 | 40 | Once the packages have been installed, you can run FASLR by executing the `main.py `_ file in the `faslr directory `_: 41 | 42 | .. code-block:: shell 43 | 44 | python -m faslr 45 | 46 | Alternatively, if you have a virtual environment set up: 47 | 48 | .. code-block:: shell 49 | 50 | source venv/bin/activate 51 | python -m faslr 52 | 53 | =========== 54 | Demo files 55 | =========== 56 | 57 | In addition to the main program, you can test out individual widgets by running the files in the `demos `_ folder. 58 | -------------------------------------------------------------------------------- /faslr/samples/friedland_auto_freq_sev.csv: -------------------------------------------------------------------------------- 1 | Accident Half-Year,Calendar Half-Year,Closed Claim Counts,Reported Claim Counts,Reported Claims,Reported Severity 2 | 2003-02,2003-02,2547,3556,14235000,4003 3 | 2003-02,2004-01,3262,3314,14960000,4514 4 | 2003-02,2004-02,3287,3301,14921000,4520 5 | 2003-02,2005-01,3291,3299,14911000,4520 6 | 2003-02,2005-02,3292,3295,14926000,4530 7 | 2003-02,2006-01,3292,3294,14864000,4512 8 | 2003-02,2006-02,3292,3293,14860000,4513 9 | 2003-02,2007-01,3292,3293,14854000,4511 10 | 2003-02,2007-02,3292,3293,14850000,4510 11 | 2003-02,2008-01,3292,3292,14847000,4510 12 | 2004-01,2004-01,2791,3492,14548000,4166 13 | 2004-01,2004-02,3217,3262,14674000,4498 14 | 2004-01,2005-01,3240,3250,14643000,4506 15 | 2004-01,2005-02,3242,3247,14626000,4505 16 | 2004-01,2006-01,3243,3247,14621000,4503 17 | 2004-01,2006-02,3243,3245,14610000,4502 18 | 2004-01,2007-01,3243,3245,14610000,4502 19 | 2004-01,2007-02,3243,3244,14611000,4504 20 | 2004-01,2008-01,3242,3243,14617000,4507 21 | 2004-02,2004-02,2099,2980,12129000,4070 22 | 2004-02,2005-01,2677,2712,12576000,4637 23 | 2004-02,2005-02,2695,2704,12541000,4638 24 | 2004-02,2006-01,2697,2702,12531000,4638 25 | 2004-02,2006-02,2697,2700,12523000,4683 26 | 2004-02,2007-01,2698,2700,12523000,4638 27 | 2004-02,2007-02,2698,2699,12510000,4635 28 | 2004-02,2008-01,2698,2699,12502000,4632 29 | 2005-01,2005-01,2370,2896,11980000,4137 30 | 2005-01,2005-02,2735,2768,11921000,4307 31 | 2005-01,2006-01,2751,2761,11882000,4304 32 | 2005-01,2006-02,2754,2758,11862000,4301 33 | 2005-01,2007-01,2755,2758,11854000,4298 34 | 2005-01,2007-02,2755,2758,11844000,4294 35 | 2005-01,2008-01,2756,2757,11841000,4295 36 | 2005-02,2005-02,1966,2814,11283000,4010 37 | 2005-02,2006-01,2609,2650,11843000,4469 38 | 2005-02,2006-02,2630,2640,11805000,4472 39 | 2005-02,2007-01,2634,2639,11789000,4467 40 | 2005-02,2007-02,2634,2638,11772000,4462 41 | 2005-02,2008-01,2634,2636,11770000,4465 42 | 2006-01,2006-01,2261,2808,11947000,4254 43 | 2006-01,2006-02,2671,2712,11856000,4372 44 | 2006-01,2007-01,2694,2704,11820000,4371 45 | 2006-01,2007-02,2696,2701,11772000,4359 46 | 2006-01,2008-01,2697,2700,11760000,4356 47 | 2006-02,2006-02,1949,2799,12503000,4467 48 | 2006-02,2007-01,2637,2675,12762000,4771 49 | 2006-02,2007-02,2659,2670,12706000,4759 50 | 2006-02,2008-01,2662,2668,12697000,4759 51 | 2007-01,2007-01,2059,2578,11662000,4524 52 | 2007-01,2007-02,2496,2533,11523000,4549 53 | 2007-01,2008-01,2520,2529,11492000,4544 54 | 2007-02,2007-02,2083,2791,12647000,4531 55 | 2007-02,2008-01,2732,2778,12854000,4627 56 | 2008-01,2008-01,2533,3139,14071000,4483 57 | -------------------------------------------------------------------------------- /faslr/samples/friedland_xyz_freq_sev.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Closed Claim Counts,Reported Claim Counts,Reported Claims,Reported Severities 2 | 1998,1998,,,, 3 | 1998,1999,,,, 4 | 1998,2000,,,1171000, 5 | 1998,2001,510,634,12380000,19526 6 | 1998,2002,547,635,13216000,20813 7 | 1998,2003,575,635,14067000,22152 8 | 1998,2004,598,637,14688000,23058 9 | 1998,2005,612,637,16366000,25692 10 | 1998,2006,620,637,16163000,25374 11 | 1998,2007,635,637,15835000,24859 12 | 1998,2008,637,637,15822000,24839 13 | 1999,1999,,,, 14 | 1999,2000,,,13255000, 15 | 1999,2001,686,1026,16405000,15989 16 | 1999,2002,819,1039,19639000,18902 17 | 1999,2003,910,1047,22473000,21464 18 | 1999,2004,980,1050,23764000,22632 19 | 1999,2005,1007,1053,25094000,23831 20 | 1999,2006,1036,1047,24795000,23682 21 | 1999,2007,1039,1047,25071000,23946 22 | 1999,2008,1044,1047,25107000,23980 23 | 2000,2000,,,15676000, 24 | 2000,2001,650,1354,18749000,13847 25 | 2000,2002,932,1397,21900000,15676 26 | 2000,2003,1095,1411,27144000,19237 27 | 2000,2004,1216,1410,29488000,20914 28 | 2000,2005,1292,1408,34458000,24473 29 | 2000,2006,1367,1408,36949000,26242 30 | 2000,2007,1391,1408,37505000,26637 31 | 2000,2008,1402,1408,37246000,26453 32 | 2001,2001,304,1305,11827000,9063 33 | 2001,2002,681,1421,16004000,11262 34 | 2001,2003,936,1449,21022000,14508 35 | 2001,2004,1092,1458,26578000,18229 36 | 2001,2005,1225,1458,34205000,23460 37 | 2001,2006,1357,1455,37136000,25523 38 | 2001,2007,1432,1455,38541000,26489 39 | 2001,2008,1446,1455,38798000,26665 40 | 2002,2002,203,1342,12811000,9546 41 | 2002,2003,607,1514,20370000,13455 42 | 2002,2004,841,1548,26656000,17219 43 | 2002,2005,1089,1557,37667000,24192 44 | 2002,2006,1327,1549,44414000,28673 45 | 2002,2007,1464,1552,48701000,31379 46 | 2002,2008,1523,1554,48169000,30997 47 | 2003,2003,181,1373,9651000,7029 48 | 2003,2004,614,1616,16995000,10517 49 | 2003,2005,941,1630,30354000,18622 50 | 2003,2006,1263,1626,40594000,24966 51 | 2003,2007,1507,1629,44231000,27152 52 | 2003,2008,1568,1629,44373000,27239 53 | 2004,2004,235,1932,16995000,8796 54 | 2004,2005,848,2168,40180000,18533 55 | 2004,2006,1442,2234,58866000,26350 56 | 2004,2007,1852,2249,71707000,31884 57 | 2004,2008,2029,2258,70288000,31129 58 | 2005,2005,295,2067,28674000,13872 59 | 2005,2006,1119,2293,47432000,20686 60 | 2005,2007,1664,2367,70340000,29717 61 | 2005,2008,1946,2390,70655000,29563 62 | 2006,2006,307,1473,27066000,18375 63 | 2006,2007,906,1645,46783000,28440 64 | 2006,2008,1201,1657,48804000,29453 65 | 2007,2007,329,1192,19477000,16340 66 | 2007,2008,791,1264,31732000,25104 67 | 2008,2008,276,1036,18632000,17985 68 | -------------------------------------------------------------------------------- /faslr/demos/exhibit_demo.py: -------------------------------------------------------------------------------- 1 | import chainladder as cl 2 | import pandas as pd 3 | 4 | from faslr.utilities.sample import load_sample 5 | 6 | from PyQt6.QtWidgets import ( 7 | QWidget 8 | ) 9 | 10 | tri_auto = load_sample('us_industry_auto') 11 | 12 | # Determine tail factor 13 | reported_to_paid = tri_auto['Reported Claims'] / tri_auto['Paid Claims'] 14 | paid_tail = reported_to_paid.latest_diagonal.to_frame(origin_as_datetime=True).iloc[0].squeeze() 15 | 16 | 17 | paid_res = cl.TailConstant(paid_tail).fit_transform( 18 | cl.Development( 19 | average='volume', 20 | n_periods=3 21 | ).fit_transform( 22 | tri_auto['Paid Claims'] 23 | ) 24 | ) 25 | 26 | paid_ultimate = cl.Chainladder().fit(paid_res).ultimate_.to_frame(origin_as_datetime=True) # noqa 27 | 28 | ay = paid_res.origin.to_series() 29 | 30 | paid_cdf = paid_res.cdf_.to_frame(origin_as_datetime=True).T 31 | # align cdfs with respective development periods 32 | paid_cdf = paid_cdf.iloc[::-1] 33 | paid_cdf = paid_cdf.drop(paid_cdf.index[len(paid_cdf)-1]) 34 | paid_cdf.index = ay.index.to_series().sort_values(ascending=False) 35 | 36 | 37 | # Age columns 38 | dev = paid_res.development.sort_index(ascending=False) 39 | dev.index = ay.index 40 | 41 | # Reported claims column 42 | reported = tri_auto['Reported Claims'].latest_diagonal.to_frame(origin_as_datetime=True) 43 | 44 | # Paid claims column 45 | paid = tri_auto['Paid Claims'].latest_diagonal.to_frame(origin_as_datetime=True) 46 | 47 | reported_tail = 1.000 48 | reported_res = cl.TailConstant(reported_tail).fit_transform( 49 | cl.Development( 50 | average='volume', 51 | n_periods=3 52 | ).fit_transform( 53 | tri_auto['Paid Claims'] 54 | ) 55 | ) 56 | 57 | reported_cdf = reported_res.cdf_.to_frame(origin_as_datetime=True).T 58 | # align cdfs with respective development periods 59 | reported_cdf = reported_cdf.iloc[::-1] 60 | reported_cdf = reported_cdf.drop(reported_cdf.index[len(reported_cdf)-1]) 61 | reported_cdf.index = ay.index.to_series().sort_values(ascending=False) 62 | 63 | reported_ultimate = cl.Chainladder().fit(reported_res).ultimate_.to_frame(origin_as_datetime=True) 64 | 65 | 66 | ultimate_exhibit = pd.concat( 67 | [ 68 | ay, 69 | dev, 70 | reported, 71 | paid, 72 | paid_cdf, 73 | reported_cdf, 74 | reported_ultimate, 75 | paid_ultimate 76 | ], 77 | axis=1 78 | ) 79 | 80 | ultimate_exhibit = ultimate_exhibit.reset_index(drop=True) 81 | 82 | columns = [ 83 | 'Accident Year', 84 | 'Development Age (Months)', 85 | 'Reported Claims', 86 | 'Paid Claims', 87 | 'Reported CDF', 88 | 'Paid CDF', 89 | 'Reported Ultimate Claims', 90 | 'Paid Ultimate Claims' 91 | ] 92 | 93 | ultimate_exhibit.columns = columns 94 | 95 | exhibit_pane = QWidget() 96 | -------------------------------------------------------------------------------- /docs/_static/xkcd_reserving_rotation.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | 4 | 5 | def draw_stick_figure(ax, x=.5, y=.5, radius=.03, quote=None, color='k', lw=2, xytext=(0, 20)): 6 | """ 7 | Taken from https://alimanfoo.github.io/2016/05/31/matplotlib-xkcd.html. 8 | """ 9 | # draw the head 10 | head = plt.Circle((x, y), radius=radius, transform=ax.transAxes, 11 | edgecolor=color, lw=lw, facecolor='none', zorder=10) 12 | ax.add_patch(head) 13 | 14 | # draw the body 15 | body = plt.Line2D([x, x], [y - radius, y - (radius * 4)], 16 | color=color, lw=lw, transform=ax.transAxes) 17 | ax.add_line(body) 18 | 19 | # draw the arms 20 | arm1 = plt.Line2D([x, x + (radius)], [y - (radius * 1.5), y - (radius * 5)], 21 | color=color, lw=lw, transform=ax.transAxes) 22 | ax.add_line(arm1) 23 | arm2 = plt.Line2D([x, x - (radius * .8)], [y - (radius * 1.5), y - (radius * 5)], 24 | color=color, lw=lw, transform=ax.transAxes) 25 | ax.add_line(arm2) 26 | 27 | # draw the legs 28 | leg1 = plt.Line2D([x, x + (radius)], [y - (radius * 4), y - (radius * 8)], 29 | color=color, lw=lw, transform=ax.transAxes) 30 | ax.add_line(leg1) 31 | leg2 = plt.Line2D([x, x - (radius * .5)], [y - (radius * 4), y - (radius * 8)], 32 | color=color, lw=lw, transform=ax.transAxes) 33 | ax.add_line(leg2) 34 | 35 | # say something 36 | if quote: 37 | ax.annotate(quote, xy=(x + radius, y + radius), xytext=xytext, 38 | xycoords='axes fraction', textcoords='offset points', 39 | arrowprops=dict(arrowstyle='-', lw=1)) 40 | 41 | 42 | with plt.xkcd(): 43 | # Based on "Stove Ownership" from XKCD by Randall Munroe 44 | # https://xkcd.com/418/ 45 | 46 | fig = plt.figure() 47 | ax = fig.add_axes((0.1, 0.2, 0.8, 0.7)) 48 | ax.spines[['top', 'right']].set_visible(False) 49 | ax.set_xticks([]) 50 | ax.set_yticks([]) 51 | ax.set_ylim([-30, 10]) 52 | 53 | data = np.ones(100) 54 | data[70:] -= np.arange(30) 55 | 56 | ax.annotate( 57 | 'THE DAY I STARTED\nMY ACTUARIAL RESERVING\nROTATION', 58 | xy=(70, 1), arrowprops=dict(arrowstyle='->'), xytext=(15, -10)) 59 | 60 | ax.plot(data) 61 | 62 | ax.set_xlabel('time') 63 | ax.set_ylabel('my overall health') 64 | 65 | # ax.text( 66 | # 40, 67 | # 12, 68 | # 'ROTATIONS', 69 | # color='black', 70 | # # bbox=dict(facecolor='none', edgecolor='black', pad=10.0) 71 | # ) 72 | 73 | draw_stick_figure( 74 | ax, 75 | x=.1, 76 | y=.45, 77 | quote=' ' 78 | ) 79 | 80 | # plt.show() 81 | 82 | plt.savefig('docs/_static/xkcd_reserving_rotation.png') -------------------------------------------------------------------------------- /faslr/samples/README.md: -------------------------------------------------------------------------------- 1 | #Samples 2 | 3 | - **Friedland US Auto** (friedland_us_industry_auto.csv) - Taken from page 106 of the Friedland reserving text. 4 | - **Friedland XYZ Insurer - Auto BI** (friedland_xyz_auto_bi.csv) - Taken from page 110 of the Friedland reserving text. 5 | - **Friedland US PP Auto Steady State** (friedland_uspp_auto_steady_state.csv) - Taken from page 115 of the Friedland reserving text. 6 | - **Friedland USPP Auto Increasing Claim Ratios** (friedland_uspp_auto_increasing_claim.csv) - Taken from page 118 of the Friedland reserving text. 7 | - **Friedland USPP Auto Increasing Case Outstanding Strength** (friedland_uspp_auto_increasing_case.csv) - Taken from page 119 of the Friedland reserving text. 8 | - **Friedland USPP Auto Increasing Claim Ratios and Case Outstanding Strength** (friedland_uspp_auto_increasing_claim_case.csv) - Taken from page 121 of the Friedland reserving text. 9 | - **Friedland US Auto Steady State** (friedland_us_auto_steady_state.csv) - Taken from page 126 of the Friedland reserving text. 10 | - **Friedland US Auto Changing Product Mix** (friedland_us_auto_chg_prod_mix.csv) - Taken from page 128 of the Friedland reserving text. 11 | - **Friedland Auto Collision Insurer Frequency Severity** (friedland_auto_freq_sev.csv)- Taken from pages 215-220 of the Friedland reserving text. 12 | - **Friedland XYZ Insurer - Auto BI Frequency Severity** (friedland_xyz_freq_sev.csv) - Taken from pages 223-227 of the Friedland reserving text. 13 | - **Friedland WC Self-Insurer** (friedland_wc_self_insurer.csv) - Taken from pages 230-236 of the Friedland reserving text. 14 | - **Friedland GL Insurer** (friedland_gl_insurer.csv) - Taken from pages 243-254 of the Friedland reserving text. 15 | - **Friedland XYZ Insurer - Auto BI Disposal Technique** (friedland_xyz_disp.csv) - Taken from pages 255-264 of the Friedland reserving text. 16 | - **Friedland US Industry Auto Case Outstanding Technique** (friedland_us_industry_auto_case.csv) - Taken from pages 270-274 of the Friedland reserving text. 17 | - **Friedland XYZ Insurer Case Outstanding Technique** (friedland_xyz_case.csv) - Taken from pages 275-282 of the Friedland reserving text. 18 | - **Friedland Med Mal Insurer** (friedland_med_mal.csv) - Taken from pages 294-303 of the Friedland reserving text. 19 | - **Friedland Berq-Sher Auto BI Insurer** (friedland_berq_sher_auto.csv) Taken from pages 304-315 of the Friedland reserving text. 20 | - **Friedland Auto Salvage and Subrogation** (friedland_auto_salsub.csv) Taken from pages 333-341 of the Friedland reserving text. 21 | - **Friedland Quota Share** (friedland_qs.csv) Taken from page 342 of the Friedland reserving text. 22 | - **Friedland Excess of Loss**(friedland_xol.csv) Taken from page 343 of the Friedland reserving text. 23 | - **Friedland Auto Property Damage Insurer**(friedland_autoprop.csv) Taken from pages 375-385 of the Friedland reserving text. -------------------------------------------------------------------------------- /faslr/utilities/style_parser.py: -------------------------------------------------------------------------------- 1 | from chainladder import Triangle 2 | from matplotlib.colors import Colormap 3 | from pandas import DataFrame 4 | 5 | from faslr.style.triangle import LOWER_DIAG_COLOR 6 | 7 | 8 | def extract_style( 9 | html_str: str 10 | ) -> str: 11 | """ 12 | Extract the contents of the style tag from an HTML string. 13 | """ 14 | 15 | return html_str.split('')[0] 16 | 17 | 18 | def extract_color_mappings( 19 | style_str: str 20 | ) -> list: 21 | """ 22 | Split the individual color mappings from the style string and store them in a list. 23 | """ 24 | 25 | # Don't include last element, which is an extraneous newline. 26 | return str(style_str.split('{')).split('}')[:-1] 27 | 28 | 29 | def parse_styler( 30 | triangle: Triangle, 31 | cmap: [str, Colormap] 32 | ) -> DataFrame: 33 | """ 34 | Takes a triangle, calculates the heatmap, and then returns a dataframe of the colors. Used in 35 | implementing the heatmap functionality from chainladder to FASLR table. 36 | 37 | :param triangle: 38 | :param cmap: 39 | :return: 40 | """ 41 | 42 | # Extract HTML from triangle heatmap. 43 | heatmap_html: str = triangle.link_ratio.heatmap(cmap=cmap).data 44 | 45 | # Declare a DataFrame with the same dimensions as the link ratio triangle to hold the colors. 46 | color_triangle = triangle.link_ratio.to_frame(origin_as_datetime=False) 47 | color_triangle = color_triangle.astype(str) 48 | 49 | # Initial colors will be the FASLR lower diagonal color for the entire triangle. 50 | # Then we override the upper triangle with the heatmap colors. 51 | color_triangle.loc[:] = LOWER_DIAG_COLOR.name() 52 | 53 | # Parse css to get the background colors. Create a list of cell-color mappings. Each element maps 54 | # all the cells that correspond to a certain color. 55 | 56 | style_str = extract_style(html_str=heatmap_html) 57 | color_mappings = extract_color_mappings(style_str=style_str) 58 | 59 | for mapping in color_mappings: 60 | 61 | # The last element of the mapping is the color, the preceding ones are the cells that it applies to. 62 | mapping_elements = mapping.split(',') 63 | # Last row-column pair item will have a trailing space and single quote, remove them. 64 | mapping_elements[-2] = mapping_elements[-2].replace(' \'', '') 65 | cells = mapping_elements[:-1] 66 | color = mapping_elements[-1][24:31] 67 | 68 | for cell in cells: 69 | # Isolate the row/column indices of each cell. 70 | cell_parts = cell.split('_') 71 | row_num = int(cell_parts[2][3:]) 72 | col_num = int(cell_parts[3][3:]) 73 | 74 | # Assign colors to each cell in the color triangle. 75 | color_triangle.iloc[row_num, col_num] = color 76 | 77 | return color_triangle 78 | -------------------------------------------------------------------------------- /docs/user/news/xkcd.rst: -------------------------------------------------------------------------------- 1 | Introducing xkcd Charts 2 | ======================= 3 | 4 | April 1, 2025 5 | 6 | I am pleased to announce that this special release of FASLR introduces `xkcd `_ charts to brighten up your day. I know reserving might not be the most exciting part of actuarial science, as illustrated by this graph: 7 | 8 | .. image:: https://faslr.com/media/xkcd_reserving_rotation.png 9 | :align: center 10 | 11 | ...which is why I have made it my mission to make reserving more exciting for those students awaiting greener pastures in predictive modeling or economic capital modeling. I'm already getting positive feedback as evidenced by this testimonial, which indicates that we're moving in the right direction: 12 | 13 | .. epigraph:: 14 | 15 | When I learned that I would be starting my reserving rotation, my heart sank. I have had it up to here with these goddamn triangles! Man, if I were going to be spending all day staring at a computer screen, I would have gone into investment banking instead...or maybe I would have just done LeetCode and gotten a job as a software engineer at a FAANG or something. At least then I'd have an equity stake. 16 | 17 | I was about to give up all hope...but when I heard FASLR was coming out with xkcd charts, I realized that everything would be okay. 18 | 19 | -- April Fuhlz, Actuarial Analyst 20 | 21 | 22 | Yeah. I hear you. Well the good news is that introducing `xkcd-style charts `_ is as easy as a flip of the switch, turning this: 23 | 24 | .. image:: https://faslr.com/media/Tail_Analysis_018.png 25 | :align: center 26 | :width: 679 27 | :target: https://faslr.com/media/Tail_Analysis_018.png 28 | 29 | |br| 30 | ...into this! 31 | 32 | .. image:: https://faslr.com/media/Tail_Analysis_017.png 33 | :align: center 34 | :width: 679 35 | :target: https://faslr.com/media/Tail_Analysis_017.png 36 | 37 | |br| 38 | ...and this! 39 | 40 | .. image:: https://faslr.com/media/Tail_Analysis_020.png 41 | :align: center 42 | :width: 679 43 | :target: https://faslr.com/media/Tail_Analysis_020.png 44 | 45 | 46 | |br| 47 | To turn on xkcd charts, go to the settings menu, navigate to plots, and select the xkcd option: 48 | 49 | .. image:: https://faslr.com/media/Settings_002.png 50 | :width: 500 51 | :align: center 52 | 53 | 54 | |br| 55 | To go back, click on the "Regu-, nah you're not gonna do that. 56 | 57 | Enjoy! 58 | 59 | .. epigraph:: 60 | If you are new to FASLR, it stands for "Free Actuarial System for Loss Reserving." The source code can be found on the `CAS GitHub `_. For those interested in seeing what FASLR can do, check out the :doc:`gallery <../../gallery/index>`. If you would like to contribute, check out the :doc:`Contributing Guide <../../contributing/index>`. Otherwise, visit the `Development Blog `_ for further updates. 61 | 62 | .. |br| raw:: html 63 | 64 |
65 | -------------------------------------------------------------------------------- /debian/README.md: -------------------------------------------------------------------------------- 1 | # Debian Packaging notes 2 | 3 | edit the .bashrc file: 4 | 5 | ``` 6 | DEBEMAIL="genedan@gmail.com" 7 | DEBFULLNAME="Gene Dan" 8 | export DEBEMAIL DEBFULLNAME 9 | 10 | # mc related 11 | if [ -f /usr/lib/mc/mc.sh ]; then 12 | . /usr/lib/mc/mc.sh 13 | fi 14 | ``` 15 | 16 | not sure if this is needed, but if I wind up using quilt, add this to .bashrc too: 17 | 18 | ``` 19 | alias dquilt="quilt --quiltrc=${HOME}/.quiltrc-dpkg" 20 | . /usr/share/bash-completion/completions/quilt 21 | complete -F _quilt_completion $_quilt_complete_opt dquilt 22 | ``` 23 | 24 | Create ~/.quiltrc-dpkg 25 | 26 | ``` 27 | d=. 28 | while [ ! -d $d/debian -a `readlink -e $d` != / ]; 29 | do d=$d/..; done 30 | if [ -d $d/debian ] && [ -z $QUILT_PATCHES ]; then 31 | # if in Debian packaging tree with unset $QUILT_PATCHES 32 | QUILT_PATCHES="debian/patches" 33 | QUILT_PATCH_OPTS="--reject-format=unified" 34 | QUILT_DIFF_ARGS="-p ab --no-timestamps --no-index --color=auto" 35 | QUILT_REFRESH_ARGS="-p ab --no-timestamps --no-index" 36 | QUILT_COLORS="diff_hdr=1;32:diff_add=1;34:diff_rem=1;31:diff_hunk=1;33:" 37 | QUILT_COLORS="${QUILT_COLORS}diff_ctx=35:diff_cctx=33" 38 | if ! [ -d $d/debian/patches ]; then mkdir $d/debian/patches; fi 39 | fi 40 | ``` 41 | 42 | create ~/.devscripts 43 | 44 | ``` 45 | DEBUILD_DPKG_BUILDPACKAGE_OPTS="-i -I -us -uc" 46 | DEBUILD_LINTIAN_OPTS="-i -I --show-overrides" 47 | DEBSIGN_KEYID="Your_GPG_keyID" 48 | ``` 49 | 50 | install sbuild and related packages 51 | 52 | ``` 53 | sudo apt install sbuild piuparts autopkgtest lintian 54 | adduser sbuild 55 | ``` 56 | 57 | create ~/.sbuildrc 58 | ``` 59 | ############################################################################## 60 | # PACKAGE BUILD RELATED (source-only-upload as default) 61 | ############################################################################## 62 | # -d 63 | $distribution = 'unstable'; 64 | # -A 65 | $build_arch_all = 1; 66 | # -s 67 | $build_source = 1; 68 | # --source-only-changes 69 | $source_only_changes = 1; 70 | # -v 71 | $verbose = 1; 72 | ############################################################################## 73 | # POST-BUILD RELATED (turn off functionality by setting variables to 0) 74 | ############################################################################## 75 | $run_lintian = 1; 76 | $lintian_opts = ['-i', '-I']; 77 | $run_piuparts = 1; 78 | $piuparts_opts = ['--schroot', 'unstable-amd64-sbuild']; 79 | $run_autopkgtest = 1; 80 | $autopkgtest_root_args = ''; 81 | $autopkgtest_opts = [ '--', 'schroot', '%r-%a-sbuild' ]; 82 | ############################################################################## 83 | # PERL MAGIC 84 | ############################################################################## 85 | 1; 86 | EOF 87 | ``` 88 | 89 | create ~/.gbp.conf 90 | ``` 91 | # Configuration file for "gbp " 92 | [DEFAULT] 93 | # the default build command: 94 | builder = sbuild 95 | # use pristine-tar: 96 | pristine-tar = True 97 | # Use color when on a terminal, alternatives: on/true, off/false or auto 98 | color = auto 99 | ``` -------------------------------------------------------------------------------- /faslr/index/index_matrix.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import numpy as np 4 | import pandas as pd 5 | 6 | from faslr.base_table import ( 7 | FAbstractTableModel, 8 | FTableView 9 | ) 10 | 11 | from faslr.common.table import ( 12 | make_corner_button 13 | ) 14 | 15 | from faslr.style.triangle import RATIO_STYLE 16 | 17 | from PyQt6.QtCore import ( 18 | QModelIndex, 19 | Qt 20 | ) 21 | 22 | from PyQt6.QtWidgets import ( 23 | QVBoxLayout, 24 | QWidget 25 | ) 26 | 27 | import typing 28 | 29 | from typing import TYPE_CHECKING 30 | 31 | if TYPE_CHECKING: 32 | from pandas import DataFrame 33 | 34 | 35 | class IndexMatrixModel(FAbstractTableModel): 36 | def __init__( 37 | self, 38 | matrix: DataFrame = None 39 | ): 40 | super().__init__() 41 | 42 | if not (matrix is None): 43 | 44 | self._data = matrix 45 | 46 | else: 47 | 48 | self._data = pd.DataFrame() 49 | 50 | def data(self, index: QModelIndex, role: int = ...) -> typing.Any: 51 | 52 | if role == Qt.ItemDataRole.DisplayRole: 53 | 54 | value = self._data.iloc[index.row(), index.column()] 55 | col = self._data.columns[index.column()] 56 | 57 | if np.isnan(value): 58 | return "" 59 | else: 60 | return RATIO_STYLE.format(value) 61 | 62 | def headerData( 63 | self, 64 | p_int: int, 65 | qt_orientation: Qt.Orientation, 66 | role: int = None 67 | ) -> typing.Any: 68 | 69 | # section is the index of the column/row. 70 | if role == Qt.ItemDataRole.DisplayRole: 71 | if qt_orientation == Qt.Orientation.Horizontal: 72 | return str(self._data.columns[p_int]) 73 | 74 | if qt_orientation == Qt.Orientation.Vertical: 75 | return str(self._data.index[p_int]) 76 | 77 | def setData( 78 | self, 79 | index: QModelIndex, 80 | value: typing.Any, 81 | role: int = ... 82 | ) -> bool: 83 | 84 | if role == Qt.ItemDataRole.EditRole: 85 | 86 | self._data = value 87 | 88 | self.layoutChanged.emit() 89 | 90 | return True 91 | 92 | class IndexMatrixView(FTableView): 93 | def __init__(self): 94 | super().__init__() 95 | 96 | self.corner_btn = make_corner_button(parent=self) 97 | 98 | class IndexMatrixWidget(QWidget): 99 | def __init__( 100 | self, 101 | matrix: DataFrame = None 102 | ): 103 | super().__init__() 104 | 105 | self.matrix = matrix 106 | 107 | self.layout = QVBoxLayout() 108 | 109 | self.index_matrix_model = IndexMatrixModel(matrix=self.matrix) 110 | self.index_matrix_view = IndexMatrixView() 111 | self.index_matrix_view.setModel(self.index_matrix_model) 112 | 113 | self.layout.addWidget(self.index_matrix_view) 114 | 115 | self.setLayout(self.layout) -------------------------------------------------------------------------------- /faslr/tests/test_grid_header.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pytest 4 | 5 | from faslr.grid_header import ( 6 | GridTableView, 7 | GridTableHeaderView 8 | ) 9 | 10 | from PyQt6.QtCore import Qt, QRect 11 | 12 | from PyQt6.QtGui import ( 13 | QPainter, 14 | QStandardItem, 15 | QStandardItemModel 16 | ) 17 | 18 | 19 | from PyQt6.QtWidgets import ( 20 | QApplication, 21 | QVBoxLayout, 22 | QWidget 23 | ) 24 | 25 | from pytestqt.qtbot import QtBot 26 | 27 | @pytest.fixture() 28 | def grid_header_widget(qtbot: QtBot) -> QWidget: 29 | 30 | main_widget = QWidget() 31 | layout = QVBoxLayout() 32 | main_widget.setLayout(layout) 33 | 34 | # Generate dummy data 35 | 36 | model = QStandardItemModel() 37 | view = GridTableView() 38 | view.setModel(model) 39 | 40 | for row in range(9): 41 | items = [] 42 | for col in range(9): 43 | items.append(QStandardItem('item(' + str(row) + ',' + str(col) + ')')) 44 | model.appendRow(items) 45 | 46 | layout.addWidget(view) 47 | 48 | view.setGridHeaderView( 49 | orientation=Qt.Orientation.Horizontal, 50 | levels=2 51 | ) 52 | 53 | view.hheader.setSpan( 54 | row=0, 55 | column=0, 56 | row_span_count=2, 57 | column_span_count=0 58 | ) 59 | view.hheader.setSpan( 60 | row=0, 61 | column=1, 62 | row_span_count=2, 63 | column_span_count=0 64 | ) 65 | view.hheader.setSpan( 66 | row=0, 67 | column=2, 68 | row_span_count=2, 69 | column_span_count=0 70 | ) 71 | view.hheader.setSpan( 72 | row=0, 73 | column=3, 74 | row_span_count=1, 75 | column_span_count=2 76 | ) 77 | view.hheader.setSpan( 78 | row=0, 79 | column=5, 80 | row_span_count=2, 81 | column_span_count=0 82 | ) 83 | view.hheader.setSpan( 84 | row=0, 85 | column=6, 86 | row_span_count=2, 87 | column_span_count=0 88 | ) 89 | 90 | view.hheader.setCellLabel(0, 0, "cell1") 91 | view.hheader.setCellLabel(0, 1, "cell2") 92 | view.hheader.setCellLabel(0, 2, "cell3") 93 | view.hheader.setCellLabel(0, 3, "cell4") 94 | view.hheader.setCellLabel(1, 3, "cell5") 95 | view.hheader.setCellLabel(1, 4, "cell6") 96 | view.hheader.setCellLabel(0, 5, "cell7") 97 | 98 | main_widget.resize(view.hheader.sizeHint().width(), 315) 99 | # main_widget.resize(937, 315) 100 | qtbot.addWidget(main_widget) 101 | 102 | yield main_widget, view 103 | 104 | 105 | # def test_grid_header(qtbot: QtBot, grid_header_widget: QWidget): 106 | # 107 | # grid_header_widget.view 108 | 109 | def test_grid_table_header_view(qtbot: QtBot): 110 | 111 | grid_table_header_view = GridTableHeaderView( 112 | orientation=Qt.Orientation.Horizontal, 113 | rows=2, 114 | columns=5 115 | ) 116 | 117 | painter = QPainter() 118 | grid_table_header_view.paintSection(painter=painter, rect=QRect(), logicalIndex=0) -------------------------------------------------------------------------------- /faslr/samples/friedland_auto_salsub.csv: -------------------------------------------------------------------------------- 1 | Accident Year,Calendar Year,Reported Salvage and Subrogation,Received Salvage and Subrogation,Reported Claims,Paid Claims 2 | 1998,1998,713000,312000,2412000,1991000 3 | 1998,1999,781000,735000,2862000,2858000 4 | 1998,2000,771000,766000,2864000,2861000 5 | 1998,2001,770000,770000,2864000,2864000 6 | 1998,2002,785000,770000,2864000,2864000 7 | 1998,2003,793000,770000,2864000,2864000 8 | 1998,2004,793000,793000,2864000,2864000 9 | 1998,2005,793000,793000,2864000,2864000 10 | 1998,2006,793000,793000,2864000,2864000 11 | 1998,2007,793000,793000,2864000,2864000 12 | 1998,2008,1328000,793000,2864000,2864000 13 | 1999,1999,1369000,704000,4225000,3558000 14 | 1999,2000,1361000,1324000,4677000,4666000 15 | 1999,2001,1360000,1360000,4695000,4694000 16 | 1999,2002,1360000,1360000,4696000,4696000 17 | 1999,2003,1360000,1360000,4697000,4697000 18 | 1999,2004,1360000,1360000,4697000,4697000 19 | 1999,2005,1360000,1360000,4697000,4697000 20 | 1999,2006,1360000,1360000,4697000,4697000 21 | 1999,2007,1360000,1360000,4697000,4697000 22 | 1999,2008,2180000,1360000,4697000,4697000 23 | 2000,2000,2432000,951000,6968000,5718000 24 | 2000,2001,2423000,2356000,7879000,7869000 25 | 2000,2002,2424000,2407000,7896000,7893000 26 | 2000,2003,2421000,2421000,7900000,7900000 27 | 2000,2004,2421000,2421000,7901000,7901000 28 | 2000,2005,2421000,2421000,7902000,7902000 29 | 2000,2006,2421000,2421000,7902000,7902000 30 | 2000,2007,2421000,2421000,7902000,7902000 31 | 2000,2008,3314000,2421000,7902000,7902000 32 | 2001,2001,3674000,2101000,9063000,7967000 33 | 2001,2002,3656000,3591000,10277000,10253000 34 | 2001,2003,3637000,3619000,10314000,10307000 35 | 2001,2004,3635000,3635000,10318000,10317000 36 | 2001,2005,3637000,3635000,10318000,10317000 37 | 2001,2006,3637000,3637000,10318000,10318000 38 | 2001,2007,3637000,3637000,10319000,10319000 39 | 2001,2008,3807000,3637000,10319000,10319000 40 | 2002,2002,4092000,2251000,9982000,8745000 41 | 2002,2003,4085000,4023000,11115000,11076000 42 | 2002,2004,4088000,4082000,11136000,11126000 43 | 2002,2005,4084000,4084000,11138000,11134000 44 | 2002,2006,4085000,4084000,11139000,11136000 45 | 2002,2007,4091000,4084000,11139000,11136000 46 | 2002,2008,3807000,4090000,11137000,11137000 47 | 2003,2003,4092000,2122000,11396000,9658000 48 | 2003,2004,4085000,4264000,12493000,12459000 49 | 2003,2005,4088000,4317000,12508000,12500000 50 | 2003,2006,4084000,4321000,12527000,12526000 51 | 2003,2007,4085000,4360000,12526000,12526000 52 | 2003,2008,4091000,4365000,12527000,12526000 53 | 2004,2004,4805000,2602000,12878000,11088000 54 | 2004,2005,5166000,5100000,14505000,14466000 55 | 2004,2006,5162000,5156000,14540000,14503000 56 | 2004,2007,5163000,5157000,14544000,14505000 57 | 2004,2008,5160000,5160000,14552000,14521000 58 | 2005,2005,5387000,3279000,15181000,13518000 59 | 2005,2006,5735000,5666000,16815000,16775000 60 | 2005,2007,5731000,5731000,16834000,16827000 61 | 2005,2008,5731000,5731000,16837000,16837000 62 | 2006,2006,5337000,3104000,15117000,13322000 63 | 2006,2007,5752000,5493000,16953000,16872000 64 | 2006,2008,5715000,5655000,16945000,16942000 65 | 2007,2007,5590000,2863000,15092000,13191000 66 | 2007,2008,6031000,5957000,16862000,16822000 67 | 2008,2008,5414000,2710000,14727000,12889000 68 | -------------------------------------------------------------------------------- /faslr/methods/development.py: -------------------------------------------------------------------------------- 1 | from chainladder import Triangle 2 | 3 | from faslr.factor import ( 4 | LDFAverageBox, 5 | FactorModel, 6 | FactorView 7 | ) 8 | 9 | from faslr.utilities.style_parser import parse_styler 10 | 11 | from PyQt6.QtCore import Qt 12 | 13 | from PyQt6.QtWidgets import ( 14 | QCheckBox, 15 | QHBoxLayout, 16 | QPushButton, 17 | QWidget, 18 | QVBoxLayout 19 | ) 20 | 21 | 22 | class DevelopmentTab(QWidget): 23 | 24 | def __init__( 25 | self, triangle: Triangle, 26 | column: str 27 | ): 28 | super().__init__() 29 | 30 | self.triangle = triangle[column] 31 | self.tool_layout = QHBoxLayout() 32 | self.layout = QVBoxLayout() 33 | self.check_heatmap = QCheckBox(text="Heatmap") 34 | self.add_ldf_btn = QPushButton("Available Averages") 35 | self.add_ldf_btn.setFixedWidth(self.add_ldf_btn.sizeHint().width()) 36 | 37 | self.add_ldf_btn.setContentsMargins( 38 | 2, 39 | 2, 40 | 2, 41 | 2 42 | ) 43 | 44 | self.add_ldf_btn.clicked.connect(self.open_ldf_average_box) # noqa 45 | 46 | self.factor_model = FactorModel(self.triangle) 47 | self.factor_view = FactorView() 48 | self.factor_view.setModel(self.factor_model) 49 | 50 | self.tool_container = QWidget() 51 | self.tool_container.setLayout(self.tool_layout) 52 | 53 | self.tool_layout.setContentsMargins( 54 | 0, 55 | 0, 56 | 0, 57 | 0 58 | ) 59 | 60 | self.tool_layout.addWidget(self.check_heatmap) 61 | self.tool_layout.addWidget(self.add_ldf_btn) 62 | self.layout.addWidget( 63 | self.tool_container, 64 | alignment=Qt.AlignmentFlag.AlignRight 65 | ) 66 | self.layout.addWidget(self.factor_view) 67 | self.setLayout(self.layout) 68 | 69 | self.ldf_average_box = LDFAverageBox( 70 | parent=self.factor_model, 71 | view=self.factor_view 72 | ) 73 | 74 | self.check_heatmap.stateChanged.connect(self.toggle_heatmap) # noqa 75 | 76 | self.setWindowTitle("Method: Chain Ladder") 77 | 78 | self.resize( 79 | self.factor_view.horizontalHeader().length() + 80 | self.factor_view.verticalHeader().width() + 81 | self.layout.getContentsMargins()[0] * 3, 82 | self.factor_view.verticalHeader().length() + 83 | self.factor_view.horizontalHeader().height() + 84 | self.layout.getContentsMargins()[0] * 3 85 | ) 86 | 87 | def open_ldf_average_box(self): 88 | 89 | self.ldf_average_box.show() 90 | 91 | def toggle_heatmap(self): 92 | if self.check_heatmap.isChecked(): 93 | self.factor_model.heatmap_checked = True 94 | self.factor_model.heatmap_frame = parse_styler( 95 | self.factor_model.triangle, 96 | cmap="coolwarm" 97 | ) 98 | self.factor_model.layoutChanged.emit() # noqa 99 | else: 100 | self.factor_model.heatmap_checked = False 101 | self.factor_model.layoutChanged.emit() # noqa 102 | -------------------------------------------------------------------------------- /faslr/common/button.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module containing commonly used button classes. 3 | """ 4 | from PyQt6.QtWidgets import ( 5 | QDialogButtonBox, 6 | QHBoxLayout, 7 | QToolButton, 8 | QWidget 9 | ) 10 | 11 | 12 | class AddRemoveButtonWidget(QWidget): 13 | """ 14 | The add/remove buttons for the ConfigTab. These add/remove the tabs containing the tail candidates. 15 | 16 | Parameters 17 | ---------- 18 | p_tool_tip: Optional[str] 19 | The tooltip to be displayed when hovering over the plus button. 20 | m_tool_tip: Optional[str] 21 | The tooltip to be displayed when hovering over the minus button. 22 | btn_height: int 23 | The height of the add/remove buttons. 24 | btn_width: int 25 | The width of the add/remove buttons. 26 | """ 27 | def __init__( 28 | self, 29 | p_tool_tip: str = None, 30 | m_tool_tip: str = None, 31 | btn_height: int = 22, 32 | btn_width: int = 22 33 | ): 34 | super().__init__() 35 | 36 | # Layout holds the two +/- buttons. 37 | self.layout = QHBoxLayout() 38 | 39 | self.layout.setContentsMargins( 40 | 0, 41 | 0, 42 | 0, 43 | 2 44 | ) 45 | 46 | self.setContentsMargins( 47 | 0, 48 | 0, 49 | 0, 50 | 0 51 | ) 52 | 53 | self.setLayout(self.layout) 54 | 55 | # Make corner buttons, these add and remove the tail candidate tabs. 56 | self.add_btn = make_corner_button( 57 | text='+', 58 | width=btn_width, 59 | height=btn_height, 60 | tool_tip=p_tool_tip 61 | ) 62 | 63 | self.remove_btn = make_corner_button( 64 | text='-', 65 | width=self.add_btn.width(), 66 | height=self.add_btn.height(), 67 | tool_tip=m_tool_tip 68 | ) 69 | 70 | # Add some space between the two buttons. 71 | self.layout.setSpacing(2) 72 | 73 | for btn in [ 74 | self.add_btn, 75 | self.remove_btn 76 | ]: 77 | self.layout.addWidget(btn) 78 | 79 | 80 | class FOKCancel(QDialogButtonBox): 81 | def __init__(self): 82 | 83 | self.ok_button = QDialogButtonBox.StandardButton.Ok 84 | self.cancel_button = QDialogButtonBox.StandardButton.Cancel 85 | 86 | super().__init__( 87 | self.ok_button | self.cancel_button 88 | ) 89 | 90 | 91 | def make_corner_button( 92 | text: str, 93 | height: int, 94 | width: int, 95 | tool_tip: str 96 | ) -> QToolButton: 97 | 98 | """ 99 | Used to make the add/remove buttons in the config tab. 100 | 101 | Parameters 102 | ---------- 103 | text: str 104 | The text displayed on the button. 105 | height: int 106 | The height of the button. 107 | width: int 108 | The width of the button. 109 | tool_tip: str 110 | The text displayed when hovering over the button. 111 | """ 112 | 113 | btn = QToolButton() 114 | btn.setText(text) 115 | btn.setToolTip(tool_tip) 116 | btn.setFixedHeight(height) 117 | btn.setFixedWidth(width) 118 | 119 | return btn 120 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | The purpose of this guide is to help people get started with making contributions to FASLR. As the project grows these, guidelines should help to establish conventions to keep things organized between contributors. 4 | 5 | Since FASLR is still a young project, I would expect these guidelines to rapidly change - for example, there is currently no naming convention for the unit tests, so one will be established in the near future. 6 | 7 | ## Virtual Environment 8 | 9 | It is recommended that you establish a virtual environment with the following details below. 10 | 11 | ### Python Version 12 | 13 | 3.10.2 (main, Jan 15 2022, 18:02:07) [GCC 9.3.0] 14 | 15 | ### Package Dependencies 16 | 17 | FASLR depends on some Python packages. These are listed in the file [requirements.txt](https://github.com/casact/FASLR/blob/main/requirements.txt). 18 | 19 | You can install these via pip: 20 | 21 | ```shell 22 | pip install -r requirements.txt 23 | ``` 24 | 25 | ### IDE 26 | 27 | I would recommend using PyCharm since it enforces PEP8 and comes with many other features that aid development, like setting up the virtual environment and marking the namespace package. However, you shouldn't feel bound to use this if you have confidence in another IDE. 28 | 29 | ### Namespace Package 30 | 31 | If your IDE has the ability to mark the directory [FASLR/faslr](https://github.com/casact/FASLR/tree/main/faslr) as the Namespace Package, please do so. 32 | 33 | ## Commit Guide 34 | 35 | Commit messages should be prefixed with a semantic tag followed by a brief summary of the commit's purpose. If necessary, you may add paragraphs to describe what you are doing. Below is a list of prefixes loosely based off of [this guide](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716): 36 | 37 | - `FEAT`: A new feature for the user. 38 | - `FIX`: Bug fixes. 39 | - `DOCS`: Changes to the documentation. 40 | - `STYLE`: Formatting, missing semi colons, etc; no production code change. 41 | - `REFACTOR`: Refactoring production code, e.g. renaming a variable. 42 | - `TEST`: Making changes to the test suite, no production code change. 43 | - `CHORE`: Updating grunt tasks etc.; no production code change. 44 | - `DEPR`: Deprecation of existing features. 45 | 46 | The semantic tag should be capitalized, followed by a colon. The portion that follows after that should begin with a capital letter and end with a period. For example: 47 | 48 | ``` 49 | FEAT: Add a label to describe triangle metadata. 50 | ^--^ ^----------------------------------------^ 51 | | | 52 | | +-> Summary. 53 | | 54 | +-------> Type: chore, docs, feat, fix, refactor, style, or test. 55 | ``` 56 | 57 | ## Unit Testing 58 | 59 | The unit tests for the project can be found in [FASLR/faslr/tests](https://github.com/casact/FASLR/tree/main/faslr/tests). These are tested using [pytest](https://docs.pytest.org/en/7.3.x/). 60 | 61 | A special package called [pytest-qt](https://pytest-qt.readthedocs.io/en/latest/intro.html), which is part of the pytest framework, is used to test the GUI components of FASLR. This is used to simulate user actions, such as clicks and button presses. 62 | 63 | ## Pull Requests 64 | 65 | When making a pull request, please associate it with an outstanding issue. Make sure to run the unit tests using pytest and that they pass. If you are adding a new feature that currently does not have an existing test, please create one for it. --------------------------------------------------------------------------------