dfm-python 0.4.8__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. dfm_python-0.5.0/PKG-INFO +393 -0
  2. dfm_python-0.5.0/README.md +337 -0
  3. dfm_python-0.5.0/lightning_logs/ddfm/version_0/checkpoints/ddfm-epoch=00-train_loss=0.8925.ckpt +0 -0
  4. dfm_python-0.5.0/lightning_logs/ddfm/version_0/metrics.csv +2 -0
  5. dfm_python-0.5.0/lightning_logs/ddfm/version_1/checkpoints/ddfm-epoch=00-train_loss=0.8013.ckpt +0 -0
  6. dfm_python-0.5.0/lightning_logs/ddfm/version_1/metrics.csv +2 -0
  7. dfm_python-0.5.0/lightning_logs/dfm/version_0/metrics.csv +11 -0
  8. dfm_python-0.5.0/lightning_logs/dfm/version_1/metrics.csv +11 -0
  9. dfm_python-0.5.0/lightning_logs/dfm/version_10/metrics.csv +11 -0
  10. dfm_python-0.5.0/lightning_logs/dfm/version_101/metrics.csv +12 -0
  11. dfm_python-0.5.0/lightning_logs/dfm/version_102/metrics.csv +12 -0
  12. dfm_python-0.5.0/lightning_logs/dfm/version_103/metrics.csv +12 -0
  13. dfm_python-0.5.0/lightning_logs/dfm/version_104/metrics.csv +12 -0
  14. dfm_python-0.5.0/lightning_logs/dfm/version_105/metrics.csv +12 -0
  15. dfm_python-0.5.0/lightning_logs/dfm/version_106/metrics.csv +12 -0
  16. dfm_python-0.5.0/lightning_logs/dfm/version_109/metrics.csv +12 -0
  17. dfm_python-0.5.0/lightning_logs/dfm/version_11/metrics.csv +11 -0
  18. dfm_python-0.5.0/lightning_logs/dfm/version_110/metrics.csv +12 -0
  19. dfm_python-0.5.0/lightning_logs/dfm/version_111/metrics.csv +12 -0
  20. dfm_python-0.5.0/lightning_logs/dfm/version_112/metrics.csv +12 -0
  21. dfm_python-0.5.0/lightning_logs/dfm/version_113/metrics.csv +12 -0
  22. dfm_python-0.5.0/lightning_logs/dfm/version_114/metrics.csv +12 -0
  23. dfm_python-0.5.0/lightning_logs/dfm/version_115/metrics.csv +12 -0
  24. dfm_python-0.5.0/lightning_logs/dfm/version_117/metrics.csv +12 -0
  25. dfm_python-0.5.0/lightning_logs/dfm/version_118/metrics.csv +12 -0
  26. dfm_python-0.5.0/lightning_logs/dfm/version_119/metrics.csv +12 -0
  27. dfm_python-0.5.0/lightning_logs/dfm/version_12/metrics.csv +11 -0
  28. dfm_python-0.5.0/lightning_logs/dfm/version_120/metrics.csv +12 -0
  29. dfm_python-0.5.0/lightning_logs/dfm/version_121/metrics.csv +12 -0
  30. dfm_python-0.5.0/lightning_logs/dfm/version_122/metrics.csv +12 -0
  31. dfm_python-0.5.0/lightning_logs/dfm/version_123/metrics.csv +12 -0
  32. dfm_python-0.5.0/lightning_logs/dfm/version_124/metrics.csv +12 -0
  33. dfm_python-0.5.0/lightning_logs/dfm/version_125/metrics.csv +12 -0
  34. dfm_python-0.5.0/lightning_logs/dfm/version_126/metrics.csv +12 -0
  35. dfm_python-0.5.0/lightning_logs/dfm/version_127/metrics.csv +12 -0
  36. dfm_python-0.5.0/lightning_logs/dfm/version_128/metrics.csv +12 -0
  37. dfm_python-0.5.0/lightning_logs/dfm/version_129/metrics.csv +12 -0
  38. dfm_python-0.5.0/lightning_logs/dfm/version_13/metrics.csv +11 -0
  39. dfm_python-0.5.0/lightning_logs/dfm/version_130/metrics.csv +12 -0
  40. dfm_python-0.5.0/lightning_logs/dfm/version_14/metrics.csv +11 -0
  41. dfm_python-0.5.0/lightning_logs/dfm/version_15/metrics.csv +11 -0
  42. dfm_python-0.5.0/lightning_logs/dfm/version_16/metrics.csv +11 -0
  43. dfm_python-0.5.0/lightning_logs/dfm/version_17/metrics.csv +11 -0
  44. dfm_python-0.5.0/lightning_logs/dfm/version_18/metrics.csv +11 -0
  45. dfm_python-0.5.0/lightning_logs/dfm/version_19/metrics.csv +11 -0
  46. dfm_python-0.5.0/lightning_logs/dfm/version_2/metrics.csv +11 -0
  47. dfm_python-0.5.0/lightning_logs/dfm/version_20/metrics.csv +11 -0
  48. dfm_python-0.5.0/lightning_logs/dfm/version_21/metrics.csv +11 -0
  49. dfm_python-0.5.0/lightning_logs/dfm/version_22/metrics.csv +11 -0
  50. dfm_python-0.5.0/lightning_logs/dfm/version_24/metrics.csv +4 -0
  51. dfm_python-0.5.0/lightning_logs/dfm/version_26/metrics.csv +4 -0
  52. dfm_python-0.5.0/lightning_logs/dfm/version_27/metrics.csv +4 -0
  53. dfm_python-0.5.0/lightning_logs/dfm/version_28/metrics.csv +4 -0
  54. dfm_python-0.5.0/lightning_logs/dfm/version_29/metrics.csv +4 -0
  55. dfm_python-0.5.0/lightning_logs/dfm/version_3/metrics.csv +11 -0
  56. dfm_python-0.5.0/lightning_logs/dfm/version_30/metrics.csv +4 -0
  57. dfm_python-0.5.0/lightning_logs/dfm/version_31/metrics.csv +4 -0
  58. dfm_python-0.5.0/lightning_logs/dfm/version_32/metrics.csv +2 -0
  59. dfm_python-0.5.0/lightning_logs/dfm/version_33/metrics.csv +2 -0
  60. dfm_python-0.5.0/lightning_logs/dfm/version_34/metrics.csv +12 -0
  61. dfm_python-0.5.0/lightning_logs/dfm/version_35/metrics.csv +12 -0
  62. dfm_python-0.5.0/lightning_logs/dfm/version_36/metrics.csv +12 -0
  63. dfm_python-0.5.0/lightning_logs/dfm/version_37/metrics.csv +12 -0
  64. dfm_python-0.5.0/lightning_logs/dfm/version_38/metrics.csv +12 -0
  65. dfm_python-0.5.0/lightning_logs/dfm/version_39/metrics.csv +12 -0
  66. dfm_python-0.5.0/lightning_logs/dfm/version_4/metrics.csv +11 -0
  67. dfm_python-0.5.0/lightning_logs/dfm/version_40/metrics.csv +12 -0
  68. dfm_python-0.5.0/lightning_logs/dfm/version_41/metrics.csv +12 -0
  69. dfm_python-0.5.0/lightning_logs/dfm/version_42/metrics.csv +12 -0
  70. dfm_python-0.5.0/lightning_logs/dfm/version_43/metrics.csv +12 -0
  71. dfm_python-0.5.0/lightning_logs/dfm/version_44/metrics.csv +12 -0
  72. dfm_python-0.5.0/lightning_logs/dfm/version_45/metrics.csv +12 -0
  73. dfm_python-0.5.0/lightning_logs/dfm/version_46/metrics.csv +12 -0
  74. dfm_python-0.5.0/lightning_logs/dfm/version_47/metrics.csv +12 -0
  75. dfm_python-0.5.0/lightning_logs/dfm/version_48/metrics.csv +12 -0
  76. dfm_python-0.5.0/lightning_logs/dfm/version_49/metrics.csv +12 -0
  77. dfm_python-0.5.0/lightning_logs/dfm/version_5/metrics.csv +11 -0
  78. dfm_python-0.5.0/lightning_logs/dfm/version_50/metrics.csv +12 -0
  79. dfm_python-0.5.0/lightning_logs/dfm/version_51/metrics.csv +12 -0
  80. dfm_python-0.5.0/lightning_logs/dfm/version_52/metrics.csv +12 -0
  81. dfm_python-0.5.0/lightning_logs/dfm/version_53/metrics.csv +12 -0
  82. dfm_python-0.5.0/lightning_logs/dfm/version_54/metrics.csv +12 -0
  83. dfm_python-0.5.0/lightning_logs/dfm/version_55/metrics.csv +12 -0
  84. dfm_python-0.5.0/lightning_logs/dfm/version_56/metrics.csv +12 -0
  85. dfm_python-0.5.0/lightning_logs/dfm/version_57/metrics.csv +12 -0
  86. dfm_python-0.5.0/lightning_logs/dfm/version_58/metrics.csv +12 -0
  87. dfm_python-0.5.0/lightning_logs/dfm/version_59/metrics.csv +12 -0
  88. dfm_python-0.5.0/lightning_logs/dfm/version_6/metrics.csv +11 -0
  89. dfm_python-0.5.0/lightning_logs/dfm/version_60/metrics.csv +12 -0
  90. dfm_python-0.5.0/lightning_logs/dfm/version_61/metrics.csv +12 -0
  91. dfm_python-0.5.0/lightning_logs/dfm/version_62/metrics.csv +12 -0
  92. dfm_python-0.5.0/lightning_logs/dfm/version_63/metrics.csv +12 -0
  93. dfm_python-0.5.0/lightning_logs/dfm/version_64/metrics.csv +12 -0
  94. dfm_python-0.5.0/lightning_logs/dfm/version_65/metrics.csv +12 -0
  95. dfm_python-0.5.0/lightning_logs/dfm/version_66/metrics.csv +12 -0
  96. dfm_python-0.5.0/lightning_logs/dfm/version_67/metrics.csv +12 -0
  97. dfm_python-0.5.0/lightning_logs/dfm/version_68/metrics.csv +12 -0
  98. dfm_python-0.5.0/lightning_logs/dfm/version_7/metrics.csv +11 -0
  99. dfm_python-0.5.0/lightning_logs/dfm/version_70/metrics.csv +12 -0
  100. dfm_python-0.5.0/lightning_logs/dfm/version_71/metrics.csv +12 -0
  101. dfm_python-0.5.0/lightning_logs/dfm/version_72/metrics.csv +12 -0
  102. dfm_python-0.5.0/lightning_logs/dfm/version_73/metrics.csv +12 -0
  103. dfm_python-0.5.0/lightning_logs/dfm/version_74/metrics.csv +12 -0
  104. dfm_python-0.5.0/lightning_logs/dfm/version_75/metrics.csv +12 -0
  105. dfm_python-0.5.0/lightning_logs/dfm/version_76/metrics.csv +12 -0
  106. dfm_python-0.5.0/lightning_logs/dfm/version_77/metrics.csv +12 -0
  107. dfm_python-0.5.0/lightning_logs/dfm/version_78/metrics.csv +12 -0
  108. dfm_python-0.5.0/lightning_logs/dfm/version_79/metrics.csv +12 -0
  109. dfm_python-0.5.0/lightning_logs/dfm/version_8/metrics.csv +11 -0
  110. dfm_python-0.5.0/lightning_logs/dfm/version_80/metrics.csv +12 -0
  111. dfm_python-0.5.0/lightning_logs/dfm/version_81/metrics.csv +12 -0
  112. dfm_python-0.5.0/lightning_logs/dfm/version_82/metrics.csv +12 -0
  113. dfm_python-0.5.0/lightning_logs/dfm/version_84/metrics.csv +12 -0
  114. dfm_python-0.5.0/lightning_logs/dfm/version_85/metrics.csv +12 -0
  115. dfm_python-0.5.0/lightning_logs/dfm/version_86/metrics.csv +12 -0
  116. dfm_python-0.5.0/lightning_logs/dfm/version_87/metrics.csv +12 -0
  117. dfm_python-0.5.0/lightning_logs/dfm/version_88/metrics.csv +12 -0
  118. dfm_python-0.5.0/lightning_logs/dfm/version_89/metrics.csv +12 -0
  119. dfm_python-0.5.0/lightning_logs/dfm/version_9/metrics.csv +11 -0
  120. dfm_python-0.5.0/lightning_logs/dfm/version_93/metrics.csv +12 -0
  121. dfm_python-0.5.0/lightning_logs/dfm/version_94/metrics.csv +12 -0
  122. dfm_python-0.5.0/lightning_logs/dfm/version_95/metrics.csv +12 -0
  123. dfm_python-0.5.0/lightning_logs/dfm/version_96/metrics.csv +12 -0
  124. dfm_python-0.5.0/lightning_logs/dfm/version_97/metrics.csv +12 -0
  125. dfm_python-0.5.0/lightning_logs/dfm/version_98/metrics.csv +12 -0
  126. dfm_python-0.5.0/lightning_logs/dfm/version_99/metrics.csv +12 -0
  127. {dfm_python-0.4.8 → dfm_python-0.5.0}/pyproject.toml +1 -1
  128. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/__init__.py +1 -4
  129. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/config/results.py +53 -1
  130. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/data/__init__.py +2 -0
  131. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/data/utils.py +1 -2
  132. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/lightning/data_module.py +49 -7
  133. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/logger/logger.py +22 -9
  134. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/models/__init__.py +2 -0
  135. dfm_python-0.5.0/src/dfm_python/models/base.py +856 -0
  136. dfm_python-0.5.0/src/dfm_python/models/ddfm.py +1566 -0
  137. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/models/dfm.py +139 -77
  138. dfm_python-0.5.0/src/dfm_python/models/mcmc.py +491 -0
  139. dfm_python-0.5.0/src/dfm_python/models/utils.py +467 -0
  140. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/ssm/em.py +104 -135
  141. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/ssm/kalman.py +28 -45
  142. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/ssm/utils.py +2 -2
  143. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/utils/__init__.py +9 -46
  144. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/utils/helpers.py +1 -1
  145. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/utils/statespace.py +4 -5
  146. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/test/test_models.py +24 -0
  147. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/test/test_nowcast.py +129 -6
  148. dfm_python-0.5.0/src/test/test_nowcast_implementation.py +305 -0
  149. dfm_python-0.5.0/src/test/test_predict.py +322 -0
  150. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/test/test_sktime_integration.py +20 -8
  151. dfm_python-0.5.0/tutorial/tutorial_finance_ddfm.py +298 -0
  152. dfm_python-0.5.0/tutorial/tutorial_finance_dfm.py +312 -0
  153. dfm_python-0.5.0/tutorial/tutorial_macro_ddfm.py +320 -0
  154. dfm_python-0.5.0/tutorial/tutorial_macro_dfm.py +322 -0
  155. dfm_python-0.4.8/PKG-INFO +0 -1198
  156. dfm_python-0.4.8/README.md +0 -1142
  157. dfm_python-0.4.8/src/dfm_python/models/base.py +0 -525
  158. dfm_python-0.4.8/src/dfm_python/models/ddfm.py +0 -2551
  159. {dfm_python-0.4.8 → dfm_python-0.5.0}/.git +0 -0
  160. {dfm_python-0.4.8 → dfm_python-0.5.0}/.gitignore +0 -0
  161. {dfm_python-0.4.8 → dfm_python-0.5.0}/.python-version +0 -0
  162. {dfm_python-0.4.8 → dfm_python-0.5.0}/LICENSE +0 -0
  163. {dfm_python-0.4.8 → dfm_python-0.5.0}/MANIFEST.in +0 -0
  164. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/experiment/test_finance.yaml +0 -0
  165. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/experiment/test_macro.yaml +0 -0
  166. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/model/ddfm.yaml +0 -0
  167. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/model/dfm.yaml +0 -0
  168. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KO3YEARC.yaml +0 -0
  169. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBFDSAAF.yaml +0 -0
  170. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBFINAAF.yaml +0 -0
  171. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBFXSAAF.yaml +0 -0
  172. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBSESI_R.yaml +0 -0
  173. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBSIBCEQ.yaml +0 -0
  174. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBSIBCOQ.yaml +0 -0
  175. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBSIBNER.yaml +0 -0
  176. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBSIBNMR.yaml +0 -0
  177. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBSIEMAR.yaml +0 -0
  178. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBSIHAPR.yaml +0 -0
  179. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBSIIMER.yaml +0 -0
  180. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBSIIMPR.yaml +0 -0
  181. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBSIXMER.yaml +0 -0
  182. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOBUSINVR.yaml +0 -0
  183. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCALL_.yaml +0 -0
  184. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCNFBUSR.yaml +0 -0
  185. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCNFCONR.yaml +0 -0
  186. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCNGOV_D.yaml +0 -0
  187. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCNPER_D.yaml +0 -0
  188. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCOBUSIR.yaml +0 -0
  189. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCONPMTH.yaml +0 -0
  190. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCONPRCF.yaml +0 -0
  191. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCONSTRP.yaml +0 -0
  192. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCPCOREF.yaml +0 -0
  193. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCPEXFEF.yaml +0 -0
  194. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCREU__A.yaml +0 -0
  195. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCSECDMR.yaml +0 -0
  196. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCSECEMR.yaml +0 -0
  197. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCSEMEMR.yaml +0 -0
  198. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCSEOHDR.yaml +0 -0
  199. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCSEOHSR.yaml +0 -0
  200. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCSHHEMR.yaml +0 -0
  201. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCSHHLMR.yaml +0 -0
  202. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCSMTTOA.yaml +0 -0
  203. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCSPHIMR.yaml +0 -0
  204. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCSPSPMR.yaml +0 -0
  205. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOCYLEADR.yaml +0 -0
  206. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KODBLNEQA.yaml +0 -0
  207. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KODBLNHSA.yaml +0 -0
  208. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOEMPCONF.yaml +0 -0
  209. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOEMPMANF.yaml +0 -0
  210. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOEMPTOTO.yaml +0 -0
  211. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOEMPWREP.yaml +0 -0
  212. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOEQUIMAE.yaml +0 -0
  213. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOEQUIPTE.yaml +0 -0
  214. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOEQUITTE.yaml +0 -0
  215. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOEXCHINA.yaml +0 -0
  216. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOEXNGS_D.yaml +0 -0
  217. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOEXPGDSA.yaml +0 -0
  218. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOEXPPRCF.yaml +0 -0
  219. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOGCFCNSD.yaml +0 -0
  220. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOGCFFNVD.yaml +0 -0
  221. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOGDP___D.yaml +0 -0
  222. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOGFCF__D.yaml +0 -0
  223. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOHWRWEMP.yaml +0 -0
  224. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOIMNGS_D.yaml +0 -0
  225. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOIMPCONA.yaml +0 -0
  226. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOIMPGDDA.yaml +0 -0
  227. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOIMPGDSA.yaml +0 -0
  228. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOIMPPRCF.yaml +0 -0
  229. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOIPALL_G.yaml +0 -0
  230. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOIPMIMAG.yaml +0 -0
  231. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOLRLTCO.yaml +0 -0
  232. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOLRLTHH.yaml +0 -0
  233. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOMPCAPGG.yaml +0 -0
  234. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOMPCONDG.yaml +0 -0
  235. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOMPCONGG.yaml +0 -0
  236. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOMPCONNG.yaml +0 -0
  237. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOMPRI30G.yaml +0 -0
  238. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOMSCAPGG.yaml +0 -0
  239. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOMSCONGG.yaml +0 -0
  240. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KONETTRDF.yaml +0 -0
  241. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KONEWCONA.yaml +0 -0
  242. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOPICAPGG.yaml +0 -0
  243. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOPICONGG.yaml +0 -0
  244. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOPIMANUG.yaml +0 -0
  245. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOPOPACTE.yaml +0 -0
  246. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOPPSRM_F.yaml +0 -0
  247. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOPROPRCF.yaml +0 -0
  248. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOPSMANUG.yaml +0 -0
  249. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KORCNST.yaml +0 -0
  250. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KORELEC.yaml +0 -0
  251. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KORFBEV.yaml +0 -0
  252. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KORMACH.yaml +0 -0
  253. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KORMCMP.yaml +0 -0
  254. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOROCOINF.yaml +0 -0
  255. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KORWHLS.yaml +0 -0
  256. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOSHRPRCF.yaml +0 -0
  257. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOSIAHOTE.yaml +0 -0
  258. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOSIAI__E.yaml +0 -0
  259. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOSIARRLE.yaml +0 -0
  260. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOSIAWRTE.yaml +0 -0
  261. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOSSBFMBE.yaml +0 -0
  262. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOUN_TOTQ.yaml +0 -0
  263. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOUS____.yaml +0 -0
  264. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOVALCONA.yaml +0 -0
  265. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOWRCCNSE.yaml +0 -0
  266. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOWRCDURE.yaml +0 -0
  267. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOWRCNODE.yaml +0 -0
  268. {dfm_python-0.4.8 → dfm_python-0.5.0}/config/series/KOWRCSEME.yaml +0 -0
  269. {dfm_python-0.4.8 → dfm_python-0.5.0}/data/finance.csv +0 -0
  270. {dfm_python-0.4.8 → dfm_python-0.5.0}/data/macro.csv +0 -0
  271. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/config/__init__.py +0 -0
  272. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/config/adapter.py +0 -0
  273. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/config/schema.py +0 -0
  274. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/config/utils.py +0 -0
  275. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/data/dataloader.py +0 -0
  276. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/data/dataset.py +0 -0
  277. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/encoder/__init__.py +0 -0
  278. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/encoder/base.py +0 -0
  279. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/encoder/pca.py +0 -0
  280. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/encoder/vae.py +0 -0
  281. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/lightning/__init__.py +0 -0
  282. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/lightning/scaling.py +0 -0
  283. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/logger/__init__.py +0 -0
  284. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/logger/inference_logger.py +0 -0
  285. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/logger/train_logger.py +0 -0
  286. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/ssm/__init__.py +0 -0
  287. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/trainer/__init__.py +0 -0
  288. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/trainer/ddfm.py +0 -0
  289. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/trainer/dfm.py +0 -0
  290. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/utils/data.py +0 -0
  291. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/utils/diagnostics.py +0 -0
  292. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/dfm_python/utils/time.py +0 -0
  293. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/test/test_config.py +0 -0
  294. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/test/test_helpers.py +0 -0
  295. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/test/test_logger.py +0 -0
  296. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/test/test_ssm.py +0 -0
  297. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/test/test_trainer.py +0 -0
  298. {dfm_python-0.4.8 → dfm_python-0.5.0}/src/test/test_utils.py +0 -0
  299. {dfm_python-0.4.8 → dfm_python-0.5.0}/uv.lock +0 -0
@@ -0,0 +1,393 @@
1
+ Metadata-Version: 2.4
2
+ Name: dfm-python
3
+ Version: 0.5.0
4
+ Summary: Dynamic Factor Model (DFM) estimation and nowcasting in Python
5
+ Project-URL: Homepage, https://github.com/yourusername/dfm-python
6
+ Project-URL: Documentation, https://github.com/yourusername/dfm-python#readme
7
+ Project-URL: Repository, https://github.com/yourusername/dfm-python
8
+ Project-URL: Issues, https://github.com/yourusername/dfm-python/issues
9
+ Author: DFM Python Contributors
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: dfm,dynamic-factor-model,econometrics,forecasting,nowcasting,time-series
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: hydra-core>=1.3.2
23
+ Requires-Dist: matplotlib>=3.5.0
24
+ Requires-Dist: numpy>=1.24.0
25
+ Requires-Dist: pandas>=2.0.0
26
+ Requires-Dist: pyarrow>=22.0.0
27
+ Requires-Dist: pytorch-lightning>=2.0.0
28
+ Requires-Dist: scikit-learn>=1.7.2
29
+ Requires-Dist: scipy>=1.10.0
30
+ Requires-Dist: sktime>=0.40.1
31
+ Requires-Dist: torch>=2.0.0
32
+ Requires-Dist: twine>=6.2.0
33
+ Provides-Extra: all
34
+ Requires-Dist: hydra-core>=1.3.0; extra == 'all'
35
+ Requires-Dist: omegaconf>=2.3.0; extra == 'all'
36
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'all'
37
+ Requires-Dist: pytest>=7.0.0; extra == 'all'
38
+ Requires-Dist: scikit-learn>=1.0.0; extra == 'all'
39
+ Requires-Dist: sktime>=0.27.0; extra == 'all'
40
+ Requires-Dist: sqlalchemy>=2.0.0; extra == 'all'
41
+ Provides-Extra: db
42
+ Requires-Dist: sqlalchemy>=2.0.0; extra == 'db'
43
+ Provides-Extra: deep
44
+ Provides-Extra: dev
45
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
46
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
47
+ Requires-Dist: scikit-learn>=1.0.0; extra == 'dev'
48
+ Provides-Extra: forecasting
49
+ Requires-Dist: sktime[forecasting]>=0.40.1; extra == 'forecasting'
50
+ Provides-Extra: hydra
51
+ Requires-Dist: hydra-core>=1.3.0; extra == 'hydra'
52
+ Requires-Dist: omegaconf>=2.3.0; extra == 'hydra'
53
+ Provides-Extra: transform
54
+ Requires-Dist: sktime>=0.27.0; extra == 'transform'
55
+ Description-Content-Type: text/markdown
56
+
57
+ # dfm-python: Dynamic Factor Models for Python
58
+
59
+ [![PyPI version](https://img.shields.io/pypi/v/dfm-python.svg)](https://pypi.org/project/dfm-python/)
60
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
61
+
62
+ A comprehensive Python implementation of **Dynamic Factor Models (DFM)** and **Deep Dynamic Factor Models (DDFM)** for forecasting high-dimensional time series. Built with PyTorch Lightning for production-ready training and inference.
63
+
64
+ ## Features
65
+
66
+ ### Core Capabilities
67
+ - ✅ **Mixed-frequency data**: Monthly, quarterly, semi-annual, annual series in one model
68
+ - ✅ **Clock-based framework**: All factors evolve at a common clock frequency
69
+ - ✅ **Block structure**: Flexible factor organization (global + sector-specific factors)
70
+ - ✅ **Idiosyncratic components**: Per-series state augmentation for better fit
71
+ - ✅ **Preprocessed data**: Users handle preprocessing (imputation, scaling) using sktime or other tools
72
+ - ✅ **Forecasting**: Generate predictions for any horizon
73
+ - ✅ **Nowcasting**: Estimate current period values using incomplete data
74
+ - ✅ **Deep DFM (DDFM)**: Nonlinear encoder with PyTorch for capturing complex factor structures
75
+
76
+ ### Technical Features
77
+ - ✅ **PyTorch Lightning**: Standard training interface with DataModule and Trainer
78
+ - ✅ **Multiple configuration methods**: YAML files, Python dictionaries, or Hydra
79
+ - ✅ **Advanced numerical stability**: Adaptive regularization, spectral radius capping, variance floors
80
+ - ✅ **Production-ready**: Comprehensive error handling, extensive testing, well-documented
81
+
82
+ ## Installation
83
+
84
+ ```bash
85
+ pip install dfm-python
86
+ ```
87
+
88
+ **Requirements**:
89
+ - Python >= 3.10
90
+ - numpy >= 1.24.0
91
+ - pandas >= 2.0.0
92
+ - scipy >= 1.10.0
93
+ - pytorch-lightning >= 2.0.0
94
+ - torch >= 2.0.0
95
+ - sktime >= 0.40.1
96
+
97
+ ## Quick Start
98
+
99
+ ### Example 1: Linear DFM
100
+
101
+ ```python
102
+ from dfm_python import DFM, DFMDataModule, DFMTrainer
103
+ import pandas as pd
104
+ import numpy as np
105
+
106
+ # Load and preprocess data
107
+ df = pd.read_csv('data/macro.csv')
108
+ df_processed = df[[col for col in df.columns if col != 'date']]
109
+
110
+ # Create DataModule
111
+ dm = DFMDataModule(
112
+ config_path='config/model/dfm.yaml',
113
+ data=df_processed
114
+ )
115
+ dm.setup()
116
+
117
+ # Create model and load config
118
+ model = DFM()
119
+ model.load_config('config/model/dfm.yaml')
120
+
121
+ # Create trainer and fit
122
+ trainer = DFMTrainer(max_iter=10, threshold=1e-4)
123
+ trainer.fit(model, dm)
124
+
125
+ # Predict
126
+ Xf, Zf = model.predict(horizon=6)
127
+ ```
128
+
129
+ ### Example 2: Deep DFM (DDFM)
130
+
131
+ ```python
132
+ from dfm_python import DDFM, DFMDataModule, DDFMTrainer
133
+ import pandas as pd
134
+
135
+ # Load and preprocess data
136
+ df = pd.read_csv('data/finance.csv')
137
+ df_processed = df[[col for col in df.columns if col != 'date']]
138
+
139
+ # Create DataModule
140
+ dm = DFMDataModule(
141
+ config_path='config/model/ddfm.yaml',
142
+ data=df_processed
143
+ )
144
+ dm.setup()
145
+
146
+ # Create DDFM model
147
+ ddfm_model = DDFM(
148
+ encoder_layers=[64, 32],
149
+ num_factors=2,
150
+ learning_rate=0.005,
151
+ epochs=100
152
+ )
153
+ ddfm_model.load_config('config/model/ddfm.yaml')
154
+
155
+ # Create trainer and fit
156
+ trainer = DDFMTrainer(max_epochs=10)
157
+ trainer.fit(ddfm_model, dm)
158
+
159
+ # Predict
160
+ Xf, Zf = ddfm_model.predict(horizon=6)
161
+ ```
162
+
163
+ ### Example 3: Nowcasting
164
+
165
+ ```python
166
+ from dfm_python import DFM, DFMDataModule, DFMTrainer
167
+ import pandas as pd
168
+
169
+ # Load data and train model (same as above)
170
+ df = pd.read_csv('data/macro.csv')
171
+ df_processed = df[[col for col in df.columns if col != 'date']]
172
+
173
+ dm = DFMDataModule(config_path='config/model/dfm.yaml', data=df_processed)
174
+ dm.setup()
175
+
176
+ model = DFM()
177
+ model.load_config('config/model/dfm.yaml')
178
+
179
+ trainer = DFMTrainer(max_iter=10, threshold=1e-4)
180
+ trainer.fit(model, dm)
181
+
182
+ # Nowcast a target series at a specific view date
183
+ result = model.nowcast(
184
+ target_series='KOEQUIPTE',
185
+ view_date='2024-01-15'
186
+ )
187
+
188
+ print(f"Nowcast value: {result.nowcast_value}")
189
+ print(f"Confidence interval: {result.confidence_interval}")
190
+ ```
191
+
192
+ ## Configuration
193
+
194
+ ### YAML Configuration (Recommended)
195
+
196
+ Create a YAML configuration file:
197
+
198
+ ```yaml
199
+ # config/model/dfm.yaml
200
+ clock: m # Monthly clock frequency
201
+ blocks:
202
+ Block_Global:
203
+ factors: 2
204
+ ar_lag: 1
205
+ clock: m
206
+
207
+ series:
208
+ - series_id: KOEQUIPTE
209
+ frequency: m
210
+ transformation: lin
211
+ blocks: [1]
212
+ - series_id: KOGDP___D
213
+ frequency: q
214
+ transformation: lin
215
+ blocks: [1]
216
+
217
+ augment_idio: true
218
+ augment_idio_slow: true
219
+ ```
220
+
221
+ ### Python Configuration
222
+
223
+ ```python
224
+ from dfm_python.config import DFMConfig, SeriesConfig, BlockConfig
225
+
226
+ config = DFMConfig(
227
+ clock='m',
228
+ series=[
229
+ SeriesConfig(series_id='series1', frequency='m', transformation='lin', blocks=[1]),
230
+ SeriesConfig(series_id='series2', frequency='q', transformation='lin', blocks=[1]),
231
+ ],
232
+ blocks={'Block_Global': BlockConfig(factors=2, ar_lag=1, clock='m')},
233
+ augment_idio=True,
234
+ augment_idio_slow=True
235
+ )
236
+ ```
237
+
238
+ ## Data Format
239
+
240
+ ### CSV Format
241
+
242
+ Your data CSV should have:
243
+ - **First column**: `date` (YYYY-MM-DD format)
244
+ - **Subsequent columns**: One per time series, column names must match `series_id` in configuration
245
+ - **Missing values**: Empty cells or `NaN`
246
+
247
+ **Example**:
248
+ ```csv
249
+ date,KOEQUIPTE,KOGDP___D
250
+ 2000-01-01,100.2,
251
+ 2000-02-01,100.5,
252
+ 2000-03-01,100.8,100.5
253
+ 2000-04-01,101.0,
254
+ ```
255
+
256
+ **Important**:
257
+ - Data must be preprocessed before passing to the package
258
+ - Use sktime or other tools for imputation, scaling, and transformations
259
+ - Quarterly series should only have values at quarter-end months
260
+
261
+ ## Tutorials
262
+
263
+ The package includes tutorial scripts demonstrating complete workflows:
264
+
265
+ ```bash
266
+ # Linear DFM tutorial
267
+ python tutorial/tutorial_macro_dfm.py
268
+ python tutorial/tutorial_finance_dfm.py
269
+
270
+ # Deep DFM tutorial
271
+ python tutorial/tutorial_macro_ddfm.py
272
+ python tutorial/tutorial_finance_ddfm.py
273
+ ```
274
+
275
+ ## API Reference
276
+
277
+ ### Core Classes
278
+
279
+ - **`DFM`**: Linear Dynamic Factor Model (EM algorithm)
280
+ - **`DDFM`**: Deep Dynamic Factor Model (PyTorch encoder)
281
+ - **`DFMDataModule`**: PyTorch Lightning DataModule for data handling
282
+ - **`DFMTrainer`**: Trainer for DFM (EM algorithm)
283
+ - **`DDFMTrainer`**: Trainer for DDFM (gradient descent)
284
+
285
+ ### Key Methods
286
+
287
+ ```python
288
+ # Configuration
289
+ model.load_config(source) # Load from YAML, dict, or Hydra config
290
+
291
+ # Training
292
+ trainer.fit(model, datamodule) # Standard Lightning pattern
293
+
294
+ # Prediction
295
+ Xf, Zf = model.predict(horizon=6) # Forecast future values
296
+
297
+ # Nowcasting
298
+ result = model.nowcast(target_series, view_date) # Estimate current period
299
+ ```
300
+
301
+ ### Result Objects
302
+
303
+ ```python
304
+ # DFMResult / DDFMResult
305
+ result.Z # (T+1 × m) Smoothed factor estimates
306
+ result.C # (N × m) Factor loadings matrix
307
+ result.A # (m × m) Factor transition matrix
308
+ result.Q # (m × m) Innovation covariance
309
+ result.R # (N × N) Observation covariance
310
+ result.converged # bool: Convergence status
311
+ result.num_iter # int: Number of iterations
312
+
313
+ # NowcastResult
314
+ result.nowcast_value # Estimated value
315
+ result.confidence_interval # Confidence interval
316
+ result.factors_at_view # Factor state at view date
317
+ ```
318
+
319
+ ## Architecture
320
+
321
+ ### Core Modules
322
+
323
+ - **`models/`**: Model implementations
324
+ - `base.py`: BaseFactorModel (common interface)
325
+ - `dfm.py`: Linear DFM (EM algorithm)
326
+ - `ddfm.py`: Deep DFM (PyTorch encoder)
327
+
328
+ - **`ssm/`**: State-space model components
329
+ - `kalman.py`: Kalman filter and smoother
330
+ - `em.py`: EM algorithm implementation
331
+
332
+ - **`config/`**: Configuration management
333
+ - `schema.py`: Configuration dataclasses
334
+ - `results.py`: Result dataclasses
335
+
336
+ - **`lightning/`**: PyTorch Lightning integration
337
+ - `data_module.py`: DFMDataModule
338
+ - `scaling.py`: Data scaling utilities
339
+
340
+ ## Testing
341
+
342
+ Run the test suite:
343
+
344
+ ```bash
345
+ pytest src/test/ -v
346
+ ```
347
+
348
+ Run specific tests:
349
+
350
+ ```bash
351
+ pytest src/test/test_nowcast_implementation.py -v
352
+ pytest src/test/test_models.py -v
353
+ pytest src/test/test_trainer.py -v
354
+ ```
355
+
356
+ ## Troubleshooting
357
+
358
+ ### Convergence Issues
359
+
360
+ - Increase `max_iter` in `DFMTrainer`
361
+ - Relax `threshold` (default: 1e-4)
362
+ - Check data quality and preprocessing
363
+
364
+ ### Numerical Instability
365
+
366
+ - The package includes automatic stability features:
367
+ - Adaptive regularization for ill-conditioned matrices
368
+ - Spectral radius capping (< 0.99) for stationarity
369
+ - Variance floors for all covariance matrices
370
+
371
+ ### Missing Data
372
+
373
+ - Preprocess data before passing to the package
374
+ - Use sktime for imputation and scaling
375
+ - Verify frequency settings match your data
376
+
377
+ ## Project Status
378
+
379
+ **Version**: 0.4.9
380
+ **Status**: Stable and production-ready
381
+ **Python**: 3.10+
382
+
383
+ ## License
384
+
385
+ MIT License
386
+
387
+ ## Contributing
388
+
389
+ Contributions are welcome! Please ensure:
390
+ - All tests pass: `pytest src/test/ -q`
391
+ - Code follows existing patterns and style
392
+ - New features include tests
393
+ - Documentation is updated