From f81abb5f998094fd1f0c4f604bd5c3490a44fcd1 Mon Sep 17 00:00:00 2001 From: Nicholas Karlson Date: Tue, 20 Jan 2026 12:43:21 -0800 Subject: [PATCH] Track D: use shared loaders in Ch06 + Ch08 --- ...ss_ch06_reconciliations_quality_control.py | 15 ++++++-------- ...iptive_statistics_financial_performance.py | 19 ++++++++++-------- src/pystatsv1/assets/workbook_track_d.zip | Bin 157230 -> 157221 bytes ...ss_ch06_reconciliations_quality_control.py | 15 ++++++-------- ...iptive_statistics_financial_performance.py | 19 ++++++++++-------- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/scripts/business_ch06_reconciliations_quality_control.py b/scripts/business_ch06_reconciliations_quality_control.py index af11d41..be55d1a 100644 --- a/scripts/business_ch06_reconciliations_quality_control.py +++ b/scripts/business_ch06_reconciliations_quality_control.py @@ -23,6 +23,8 @@ import pandas as pd +from pystatsv1.trackd.loaders import load_table + from scripts._cli import base_parser from scripts._business_recon import ( build_ar_rollforward, @@ -37,16 +39,11 @@ class Ch06Outputs: bank_exceptions: pd.DataFrame summary: dict[str, Any] -def _read_csv(path: Path) -> pd.DataFrame: - if not path.exists(): - raise FileNotFoundError(f"Missing required input: {path}") - return pd.read_csv(path) - def analyze_ch06(datadir: Path) -> Ch06Outputs: - gl = _read_csv(datadir / "gl_journal.csv") - tb = _read_csv(datadir / "trial_balance_monthly.csv") - ar_events = _read_csv(datadir / "ar_events.csv") - bank = _read_csv(datadir / "bank_statement.csv") + gl = load_table(datadir, "gl_journal.csv") + tb = load_table(datadir, "trial_balance_monthly.csv") + ar_events = load_table(datadir, "ar_events.csv") + bank = load_table(datadir, "bank_statement.csv") # AR rollforward tie-out diff --git a/scripts/business_ch08_descriptive_statistics_financial_performance.py b/scripts/business_ch08_descriptive_statistics_financial_performance.py index 30f54cc..5a519f6 100644 --- a/scripts/business_ch08_descriptive_statistics_financial_performance.py +++ b/scripts/business_ch08_descriptive_statistics_financial_performance.py @@ -40,6 +40,8 @@ import numpy as np import pandas as pd +from pystatsv1.trackd.loaders import load_table, resolve_datadir + from ._business_etl import build_gl_tidy_dataset from ._cli import base_parser @@ -54,18 +56,19 @@ class Ch08Outputs: def _read_csv_required(datadir: Path, filename: str, *, fallbacks: list[str] | None = None) -> pd.DataFrame: - """Read a required CSV, optionally trying fallback filenames. + """Read a required input CSV. - This keeps chapters robust when the simulator/export names evolve. + Uses Track D's shared loader so errors are consistent and beginner-friendly. + Supports a small list of fallback filenames for backward-compatibility. """ + root = resolve_datadir(datadir) candidates = [filename] + (fallbacks or []) for name in candidates: - path = datadir / name + path = root / name if path.exists(): - return pd.read_csv(path) - # If none found, raise using the primary expected name (so error is clear) - raise FileNotFoundError(datadir / filename) - + return load_table(root, name) + # Let the shared loader raise a friendly error for the primary expected name + return load_table(root, filename) def _pivot_statement(df: pd.DataFrame) -> pd.DataFrame: """Return a wide statement frame: index month, columns = line.""" @@ -481,4 +484,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file diff --git a/src/pystatsv1/assets/workbook_track_d.zip b/src/pystatsv1/assets/workbook_track_d.zip index 3ccb56fe46f8734c3a33722502c0f29bb469b701..9c72423a7c6905ade2d5b6c694b48d169e31623e 100644 GIT binary patch delta 7480 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 W9U%Oj4v-p7<^aS;3)2NYRro&*yz#F9 delta 7409 zcmYkBbyO5y(D!#2sa?8Dy1QFKKw7#RDe0D8x>Jy)yQD+9Q@XobQW|LfeeQliv-)e@CHI<{l=k6YLs$9KxH&AWl8~3h_G<>5s zs8y3m<;Nqn;!Gp@k9w#=J|EJ?XI+uJW7`OkaSco!dh2{cAz6F#6`deQDHfj5Jl%-M zL)%p5NzHh+(wVQ3dE83XCOpM~wk0{WW(7cEbW^g46512wO4DsGi9ITgWZn6$$_<4| z?$xB^&EM(!^PCao?~iXxa|QLB+6^TL%8N3qYL_B{K{WkyqXqU8Ij~dlr4W8b3%oy!I=2m8Jc5bpwl4Q$HwxSdkd7$C;JN;a9Bm?ME3&ELh2Q zVLPAZFSMBnS9O4E3BnW&KXvD%%tX=OC^@_Jtdn!n z+_Jc40xr;Yiqs;^ly~0-7VR%N<|$nFdKT$v9C138m<$&Ve+An6ujb_nW#k%7+BA4@ zNnW%@`K^0QDOnAQ@jLVI?9&D#8IFI$a33K-8j1b7){lpZ<7AU|F|)(*Dv?wj%9jIa z6Yf-);j_js|9tAFu#x&9saXlJ^^F&nOB2DLfZT9s?RG?DtP~AkgmD{;BAsJ8C-54S zi1S-nB+XS|>FUv?0g2rPX|awN<3@Nd=xO9Gl^5l*PS`{VAt~h8&2boL*1!II>6ddqXW52rv2Hlw@C@Z_XFRuJz5ZmK3}@^GjkH$A*FJ82EG@M0Z}Q#DOxCQy z1$UuPaBnRL(D$n?#)k}H#9b|uL7NH|z&S}Gi|))e^aEe91xz|RnyTBscVOXlHRtL? z&be-kyO1xYkg4%RhDKiD!-P^51I^q)@#o?4V@!FIkplP~xaicZF|rHt@XE(isTCt8zjE>s zn!Jo{`%DXPd!6S=+eT%9xzmJ+;-|CnLY*Vf*&TfK2nVn-24rzidkAE7RinQ?*-66^oC@_>XRB zu7@qhTO^?fO0O~6oJ^U~RXsM4^95)b*Dmv=;+r|u8}h}8F2MSgtl_xanWMLMNNqBdmCwnq{+ufY?2!h zd>1Y=-}ccwbAWTXKwx4Anq;ZMP^zeMx|NPJveS3fhK?IowZwE=@uAP~w}(ztr0+Y+ zDnZwBiJt&C8x>CCJ+sR^(1VA*yt`VV8;i3h{>jeIrE;qy>IxA*Ge1h+wxJ^dZi!Dz zvzM|ll9EF|e50vl)LX&-V#@Hy@Do-^1_bArn zKA#H!?uu!`n7GO@&_SX7TDU6Jj*oReL_ql)jPr^XN0yw#J48X*WOHAV_nm6+xpqUv zENY<8KObt@bl@L#HxZkE=?s)ojyOhyQ3j!9L=U+_aE3AV`D>8*Cm{8VL+er61&I1= zS4P5~WEjJQ%sZi4$TPhuw%c!ax;zhXIO5~wsinxJk;o6<+7&R>ahJDd8nk442M2pVcI)qaC2Hr=2<=kU`-wf+eD&TwPj49KVgbs)3@%3}x z)i(hAO0fVT=Ds`yR>GS}r;t`I^Aa9vZnmlyv>-jGkUCG7=dwc0)B2=F@=H zVbP$Nc~_bu?d)5r^dfskOeyBI9aI0`Z6&x@bl7VjqG&z56WuH+{XPdD^}7ua!c<+` zi)6)@W_HM`!creAjb%6Gw1We&10K4rv!Z`)#^DVd!TAj zSYE6y;sG78*5df7084aTI=4!0ymKy;I1r>syPcfCsW6qy{Ydf!?x_T6OhmjS$aXC_ z5*&%>L)3I`Rj`0&;K7g8LFO@!bT|CRDkL|+p)s8{4xh9n7+>3C>kGZOGG)&}qk)Mu zD!7@9=@nz=x87P=M7{5#A#yc63+I?9fb`sUn0d^as#Q_bk@}djja{J~OT!wvTfSpe ztHbCd)mj|JFvtLA;iI?giTt$k8{3I`?6_GIas)JK z#vAqay0~6C*;Zxw{ss*saOMxqpuDK}urZ$sX zTt2F6*1Wct=mC&If&CtnPw1*n69_?guYl=Pob-Nkj7hw*HF!)@O1RcD+wA>XF16+C z4>c>H0e6CUI08GiO4-fO&54O1(X)V=h75$){hmEEyKfTgou^10T%}G_)2C?qCT9G6 zlhF)n>=JGiCl&3_I-fZ2WlUy22pKz$*6G^{>^%?_+UcFmanuu_C+8{7poWvg_t7Nm z+GM;_`)oD?Qs;{4$_+uRCULCktc5V$D%W&vIOTvr!taPzK55rO@y0KO5e<@og8C|s zrn0_M8kO~dJ`ytqc2igHVY-UJ8~#%{j`AS2@tY!>&YG1Si>mW3#Pg$$0EnxDfi3T@ zY<9rwX9j2T8?Uc={t`<5&lq(5LUU(u``-t+Lnb*fF^t0%PvK)9YY-Len_mTf1PrNSb5f`42jb`(JJLdAELYSTioV;tWiW|_rYR>w>~ZLAeFH z#I=tptKZ)a=$lJA`roHmV-H@M@*MEZL%SzE88JgRDF-h1RY3Q>QBay)&)de@-N4JPJ%S;v1Bcr+D#q`VkM zZjMkw@f(|)kdhGKc_Z|rLrrGfR5_V(`u%k1>%(JnLec{MTA{R1GVgO-nVk`@UaKI6 zZoD{bIk%vmh(~V~5fW;`&XZ!T-j_SQ71z*eu41}+ga{`zv+#=|twZ{`_J$&pw;_lW zI(1)8T+4w(&XED9Vd{rpP(wKpyM`5zTxStH0-{28j2egyNx9GDwK!0i`t8pqCF;PQ zeXi(kd=Aw=Tk(x?4QCgnS^eee&NRzG`kR=uzJGh>qsS_^sJ{GnS(-P)_w`F(D*%)@bkxfLTWCN7Tku3*4$gg4J-rb?rMJv;Zn$t#h`D@^iyNtQ`IdFV5tj&t&w>(^N;%TO_G(d4+lOut)xFOJ-DN<)R4u#Xk3Ov4-;(~3CNJYl;{~Pfs7;nBiFj#{X@gmsH|9k|L}H@#-aYM9hM=1xp< z^>Z5N6)=uNtKS~8R*Rhc*?9RLkvp7F`u&UcXc$p2O$?(?cklJ%{QSJrAkt<$9l z+lvDAF$c9j;heLw&wHGPtLVUT+;EGTFU_|8sHM69)qcBJu~}KW1K7=>pflkwgQH=r z!7Q;$DH;ua`DdGAQOEFpQ4`HRc|_TiVQwvRBu?!msPwfL(1y$Qx!UB`_O`o)l#pM8 z-XpLIGXJ3FH?BQ4s;OMf%hLj6=gcxP$u7m4GbdB{lj|C=>84Rb@7%(!Nzu+ak8U@X*r+u=E9?dZH(T8h#`nk>3xC$2-5p7C~1& z;K>@YB}G`a=X%3SHIjMDOVs~k)PwnnMiTQgv?H}P^`ZJ`*o=g8V#0!L7s>}C#RER` zrKB9+>nBlg2hr`{Yu=&1-v|+u3Z>-hk=XQf$W-H%i(%3tKEvK@i;~G^XllPVJG?cJ zefZ00lS;%o zv_oj?d#VQ0)U!e=TIfb=lf8_Mq7k)+Mkgkp?HHF&8GE9T_ypCqfr{)tpQ>%zD1i!N zpydtgSXBU*v5cL~z+P;uqUJ!$%-bb-t6NRD6=Z$+~T2OR$lJ@OK8R?(?D;dqu(jS#&5`ojY2xo@`82x$>^vXU6;MgtXJ z@Zxiet6I4xYz#q|aFUP%KSeQkuS*BAYEb(!o4#{px3v(bM?>+67q1MYJ?B~b989`g zs8xj2s4cJZR?-xn_zwH>wBgI7l*@;{n-B;3?AqwF1!T#x_9qN^pV}YSdv~6Q94Uo8^`2JTbfcl%WU{WKFvMV=PxK;u*T+S5*K1R+m~O5J75#Wu z&?qFE^3}rh<<67HzQ~sR`Y!}W)mxyl4+HTl?cCDYdA+>bxTq@ThA;H>MzP8(Mj2|L zRlO~lYeH4zZ)eGjjTT7Gp_uz7Ska!|6X0=!nOmF1d-p3+&S7(Nyj19X%7JiBNMe5{Uu>|M!+D4X z2_m$r~92{P@{6+O)HZqH{rO|zO0bqU<^b?MCL9jTKsqft>z3ERxLs`Pq1sSV_1<{{iKD|-w#p!` zGN)_q!U`177n!i7!NO&7kI$sQJkM7Uku*buZ^+whOX_~VUeGLXghuWs+ry)?`1 zU(OGKDRmNQ;bwBLP)UH_)vJup&Hlq4x`Wt`*mbUK(IY3eUD1MxcvuL7GwcwP*X_6@ zmABCQgApotl}Q0L?~FO@(=Xo6wJ7{*T;ziMvVh6$!If4Yzso3H#lzfpKaVukow zIRq)&S+LmKkm>alEWxHvY^4-bGPCvQ%-ob*i!Mm&*HgDDrO)1DN^jP33&g**3jPQ~=K-*Uyt>=#gSLRQk(LrChZL=(} ze(_XNlU2Co6CzvrD=_RnjQ@emDJ9xj{#!T&X7ttHX~=gc9CgtkP4w3HwcFROy*_ms zDpJufrB2n7Q%=CN1W~~ddbAu%3u{GjDVDLg3j?XBx=h!=V>1Y9W%S$_3U|9X1DMEs z1U!Onap>bHeGEvYTs4WLntD|VHiS#0)wHAs9d0+)itet#w`3?kUjad@UX80>z}{8x z|Lv||-?c#QKsOkA&luGKse&*%{?|z4@1TTf>4F$Rkj4Mh6kU)$=-}6XD#igh zj7jgW>UsB{YO4p522nv^nED_*m;wYy1l#*71F>Nk`XGDIzUY4fx)_ifHmv_QuUO|l z&!i5J9VTq>*W>OA6F2;uob3u^hL!&FsK@^2@r?yCz;F%!mOFxp8G@`qNI(A*dJI8I zAm#Ocm9Sy7Mxb}VX_%Q2ND6N01PD7l`Fn{0qrX{D*o6^D2sj2~G5!k{*yn$Ug%$n< zI_M7&;PT1&v!kmEn;AO~Ohx@~so*={|KBnw?5)Y)j5V0fKXAgn{X-6H>K|TVsHT6R z43qtb8d%6b5Wu?s!5{Yg59Kg{Pk%87bNh!ESj|8DfSvw>6^y~`FCJj#{{V&M{sjmL z9ky-;5(Fl~2+cu4aIcsk7?AdFXY9;Dyl|&9|7(%K`py47EeUpR4q`(HQw9C~;r{`1 CGQ`gS diff --git a/workbooks/track_d_template/scripts/business_ch06_reconciliations_quality_control.py b/workbooks/track_d_template/scripts/business_ch06_reconciliations_quality_control.py index 7ede31a..8021539 100644 --- a/workbooks/track_d_template/scripts/business_ch06_reconciliations_quality_control.py +++ b/workbooks/track_d_template/scripts/business_ch06_reconciliations_quality_control.py @@ -24,6 +24,8 @@ import pandas as pd +from pystatsv1.trackd.loaders import load_table + from scripts._cli import base_parser from scripts._business_recon import ( build_ar_rollforward, @@ -38,16 +40,11 @@ class Ch06Outputs: bank_exceptions: pd.DataFrame summary: dict[str, Any] -def _read_csv(path: Path) -> pd.DataFrame: - if not path.exists(): - raise FileNotFoundError(f"Missing required input: {path}") - return pd.read_csv(path) - def analyze_ch06(datadir: Path) -> Ch06Outputs: - gl = _read_csv(datadir / "gl_journal.csv") - tb = _read_csv(datadir / "trial_balance_monthly.csv") - ar_events = _read_csv(datadir / "ar_events.csv") - bank = _read_csv(datadir / "bank_statement.csv") + gl = load_table(datadir, "gl_journal.csv") + tb = load_table(datadir, "trial_balance_monthly.csv") + ar_events = load_table(datadir, "ar_events.csv") + bank = load_table(datadir, "bank_statement.csv") # AR rollforward tie-out diff --git a/workbooks/track_d_template/scripts/business_ch08_descriptive_statistics_financial_performance.py b/workbooks/track_d_template/scripts/business_ch08_descriptive_statistics_financial_performance.py index 97a824e..6471211 100644 --- a/workbooks/track_d_template/scripts/business_ch08_descriptive_statistics_financial_performance.py +++ b/workbooks/track_d_template/scripts/business_ch08_descriptive_statistics_financial_performance.py @@ -47,6 +47,8 @@ import numpy as np import pandas as pd +from pystatsv1.trackd.loaders import load_table, resolve_datadir + from ._business_etl import build_gl_tidy_dataset from ._cli import base_parser @@ -61,18 +63,19 @@ class Ch08Outputs: def _read_csv_required(datadir: Path, filename: str, *, fallbacks: list[str] | None = None) -> pd.DataFrame: - """Read a required CSV, optionally trying fallback filenames. + """Read a required input CSV. - This keeps chapters robust when the simulator/export names evolve. + Uses Track D's shared loader so errors are consistent and beginner-friendly. + Supports a small list of fallback filenames for backward-compatibility. """ + root = resolve_datadir(datadir) candidates = [filename] + (fallbacks or []) for name in candidates: - path = datadir / name + path = root / name if path.exists(): - return pd.read_csv(path) - # If none found, raise using the primary expected name (so error is clear) - raise FileNotFoundError(datadir / filename) - + return load_table(root, name) + # Let the shared loader raise a friendly error for the primary expected name + return load_table(root, filename) def _pivot_statement(df: pd.DataFrame) -> pd.DataFrame: """Return a wide statement frame: index month, columns = line.""" @@ -488,4 +491,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file