From 0281c544f809ac584113eb510ce8792c5aba9727 Mon Sep 17 00:00:00 2001 From: Linnea Date: Tue, 20 Jan 2026 18:51:49 -0800 Subject: [PATCH 1/5] Refactor to use our own database copy of CCFS instead of querying the CCFS website --- experiments/gre_apartments.ods | Bin 23880 -> 24275 bytes processors/corp_owners.py | 230 ++++++++++++++------------------- processors/gre-llc.py | 43 ------ processors/test.py | 25 ++++ 4 files changed, 121 insertions(+), 177 deletions(-) delete mode 100644 processors/gre-llc.py create mode 100644 processors/test.py diff --git a/experiments/gre_apartments.ods b/experiments/gre_apartments.ods index e3c45168d38818ddc83b53dcb4bec9c758048890..514125a6eec14c7f6fe9fd51532ab537156950b4 100644 GIT binary patch delta 22055 zcmZU)b95#_*S8znxntXQGO^8xZQGgTj*W?J+Y{TK*tTtb^M22H)_Tu5{ZH+#>grvq zs=MmyU+o@k2c7Q%MN*OlhX86olB^N!0u(reNUmm>t>5Ht(KeV};*ujd|) zx0K>^e-hQ6xJkCIclkTvu^#UhX~?EQSz@iaor2P@d8y+pqwih!zwS<{>Rb&rzpg0c z(eU%#1Ti`i)BZ8X$${IH== zZ;{NJ85-V%Rj%T{_WUs&dyivQ{(KrlWhIoCctg)?<|Oy_H~)I3YauG%f5(6JHsp}| zQfz4gwMv~%QJI0gWL0Q6z+)Un2s#Pge)flPD~f7Z?d)R!Rg#%%8^xdLOyPPw?D;t# z=phB*r`Pk+UNcvz3|n{YSYcAi9(j<2<~jY3Pu&>XiXwC>;(X!)El|jd<##B?i5F9t>)M zlu3cn;92m4mi0AG=T`_8cC31>E$&BhmW)DRWDv#xRKItXb7fAxH?7FcR>6YrzVD0d zIOgM;0p#)!v$PF|4Z+^PyPgH+_C7aUJsEl_NFf&A{0P&{vR_M_>843s8 z(iYFCw7a=heTEIdW^CI1hzcT0mfRdr9*l~5#1iVBN%@K&LrtsSYib8mGHARxU}kSo z<|GsNG5`8v9_WtyGP(8{j>H^BW9k_git#er^bk6?(sceRf zisrQ6!4hdkO)K?DD~O45UPPJoWnB3j{yZ#PzrqxXHQK@x#885(6Jn4+uO^p$?`#B+ z&7o~|Uc;rRk_wmh?N~-I?RLQ>EfCmODkb>&)q>@XG^%b?e?%6}Eg!Y*){C|u1V5VQg^%J@8WDL&!xZ~op-VQl`x{(9jl-E}w&|+m2 zS86;DH@o9wj#qQY90%LjOVI2$$X5uplFI>1MKQqbh@3kJElJNw773jCn2X42#oNsrMQo}1D<(p8R zZPkF#l`K~6i9+nZNnAh`JJ+vD=SC+U3T{pglAsGDmfnyx-A?mpLJyeXkLADL94CGL zWU+_!L4x-VDO_e&RzjrYSQDK070ydZ8`O@{3FPq()D21gNwZ`tBw1I%h^Lsdmc~Wo zu6n(iIfkIZ@Wng7ntQ#18j3|m@3Zwug(I+uS*j((D;MWue0qWSl{a`5{rtKyw6-U4 z%pHQC6`dX=PGh`%;hcZrmorm*>!h&X93xJ&%5@tqxK*zS8+d&$=BOmq7nxN%9x&G~ zii8y@?lv(;Srl=m7wx&l5eB)30v$GlU{6l(`@|dmC=r$?0wtA>4q@ðHDcRqY*% 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 delta 21567 zcmZU)1B@oo7A@R%_q1)>wr$()X`5f$wr$(Sv~Alqrt!~xH#hn7UZs-Cs;XT(b#^6t zowZIa_5hD{0wXBOfP$d{0YL!)c_CowBp@h){pXm}0Zf=Qr45r14fS6P{|{RLqyHBK z`~wC$aB%6z>>67$8 z@Bnd}?PxzceZq=!h2YYc^*L*HSBCD3*D1wfiM9gtmJx~T1)>4PapMLCcMEh{hf_!8 z7K{;ZJBK{)hh%*|uH)g(!qFy~_S=RhT?i8-*oVB^oqax^Sk+q^UAx`IQh_AR5mqi! ziu6DjuV9X+i3!>EFrl0i;HZ^xB$FJdPJk&6t|`Rba7z)`WSx0A4fCRXa3$-Qg|pAq}*e9E!-sjo4MCh{fiMrL5IP!&nanL1mepSsFll> zvhq~6(sjYr0QYeyL5gHpyAID`2cpu>x{k|OiexjD5;A)|_MijCy~PP{*b$*GPr%TF|}j@l1j0dv90t1CNns=;OO&4m$`&P)}o$=r9DM~)MhgZ zfS<6({W77=jcAqVu2T@RIDpBvjY}YxEYT1OZFM@qw6s0tZK)nZel-V^^j<=`S<3Y~ zW>qL*I3iK483SPuRnQ19YBYK{baZ^V;l2=ll=k z%5hwPunxx^Y{3T9)FO%O{EAqIb@&+*#+BdY_rrj#J1pTS<84h|BtVI}K3Y45Tw5;n z<=l`;yoBuYskTbfm{KTh;J~tiVci>^vI268y@2%VI)~ancS>HWZGt9tLO1JF{Ez&3 zD6IHxF<)?F&EMa>9=*Y^s|iV)(XDpCiyNAFG#Xmbca)U>#y}01q*9BB3SsU= zANNrbS51z(s*Qe>dBe=lA+|r4$fM1*!;llXw&*mn_ny((wTI9FpM)!F3@=WZs795i z2n;!P0#}Qyd%`K=Yf?X;0s%ViE`<84s%GYB9kle1QLf=Z+7c`%1^9GsQFHPMa$ zugN&+WGOky=OG)d7eNbs?s($FR3D}1v+rK{eFK#5Ih1%?E+}Fw&y_pj3^}*N5txkK zg{o?&Cl4J956Cr(-zI`+SEM;7z$`ii4P^RZdji0FdGuqAI>lYjeqvN%kkPVh5|e6# zqT^6g+F9HAJ13G#9pOX<3&k^gK|lsC2GyICh_R+((m`*dGNz8Q3C;;)aT+i=z3;T& z?-}b>*KV(r0a}_-_T5KwD z8BRvFC`c285;ddA)T|AHBCl%(s#E>7x@ErbAZj+ByTlyKpty8$Ng^LY z071v2T^h}=kcBt$&6v%$el=99cCSut&+n~Q0-&`sN=;j(=a4}|rN0$}_4)#|Z~}u2 z`q0ck3Rr=ZiAA1XP~V`-T>D9(`&kZ8v(ZW~$D=*W595LH<+H)pTPb+!r?%yO;YUha zZ5#w}p|@3|)~2@=Saw@pnX+5TmAyY=0O{vYLHH|LxqQ1#O+WE`Ynn}aTE)kC!RbDi zeBwPHguDf{->{3GS z+gNQhg9l1nz7m{w`HMK!okY%Fl#iLM)$98eU!1pC1wlfbUj;u>+Qv7HzbHjc0Mwf9 zp9)`vW*&!dmzKQc+IOBBjBp^V+AW@5; zEaLqG`=Rb@a5|qi3mh+kZex!&DgFDsy-?vFchI%=UKm~N<+JSD5Fv(pGhs+^ThuaJ zgO%SW#GP~F`PaOy0O+-8&dyrdLa7H^`Xn4U*WgY~EpB){`WZNe_G{E8fSJ|$CH;!F z>ED@0u1-uuVf#N?2$vMg1HZNKR9Dd-PI^Gdj{BghRcHB^(*(?dt#LVns4kXdMmKMZ z@_Xmg_3G^G5q05-g1WB?eQu{&CvyMX3|}4=Kt43TccooyLWI1h5g;yXb!pV;_dIRH zl$Kb67o_-RjGd&GGzGQ}0EVIsOqPvk;lI*cHhCp~v7Ow4*l5?(nABWXCgee{V9z+( z^vJu_l3n$@%MVHR4$AjFp@>xYM5t_h&!JvG*uQ#z2?T&wG+mquxEQa_tywCIG@FUR zg*>bIdRF%ed1G|SkAum_;~v|<`?Z68!vw>+pZ?ALc=k03G2Qx_H)w#cSnW!cZM`&X z(Spbt@oI%i{hlzG5~(mQ@{=k18`*Wf>#lUq-l4B2*pu3>Q+El{d3_m&SVwT){cSIO z^9`ve0|_;zaM=EmLGt)9Jzh z1OCqARKd@3;l18V9>`hG$bUNCnDX$XThHmtt9#0g*i}dK{L4skrg{l)V!L707nYHDX+G^fD+(OkeBNE-3 zZ(g=`ZDpRN%(A%=P4cbm|0S$bbUO7qPTve%P%+I$0A0KT>G3?YabQF=n1k-swY`$K zgF6w#SL=GcdYN!Y=McU`cmN`XzoYrS+d478G9laxfV8)?dd0q4bf`NGi!Xn}mmrpK zjstI??82q$Y)52v(Xxr4+X$7UeVKBU=@K_i`0T*1xj%oQ#-ZDc4)I7{;LT>0M7mtB zusXk;9-9#N+7r!DS}rn*7Ilzl4XQ;D*J}_oOl4q$F0A{@k$W|bky)@jKyM zXCI(>t|s5F&>F5AvEU76JA%+4Xcj}(YAJIDyOcQXIUq7`)(m&cNYjHAFE~re0?T+m zS;M3z(%aL&!0no*g+39I>C{jQzW_Jb(rqM8QZN%Arbgf$Y>-_x{9qpcoyyxeLD@b8 zpv@rO%MYjV-<@5ZV?%lCoLA>FMS;lHA2JS=nFsE+xy3TIQE6*WD z^sAr8yuJF?7NG`c2V&3pNy7?QJ}Kq{scplu(Ne$!>ezvh$e<9MgdA913<%!6L?`!4kLo z6?!TYm>x-Rq^6G!)}>hG|AueOzk8L_%<`TEyx72%nBU7zAFF*+MB$bJA$};}JHtflsDpzc>;9YGbIgEzj_I(2I{jWz#}>kw5HJoCcD4 z%Af03hMYr+xA%wBusfP}NqSsel6^Yr9LOvf-RsC*!V@E73aSS~Lt7c29|5-S=7`t4L)MGF;8GvXX43MiIju6AuZ_#DpO3F<`_KEsTMN*`IKb^urkbYbZs&nC<7K4% zs^CiKyX4mR5_jaY+u(ZB5sUEr%K4mHbnCo-ORG_O*LsOKR~!5%pyw<^=_3UeTP>?k zc4#R86i(B*&i_z)q*=#amT7hGG{UT_?eKoDS(pi>Ae}&lvec+w8wWc^nR|TxR+QZL z=y0q`lcXLZz!$dGJ83pGs{)Lj=J;;CabS7L&8~W!!Ukl){GIWwW%DAQ-#UWdnqg7G zO^uE$esX85LY}A-uv0xLAu6XSO^oyAJr8wE6hj3KR@wSpDMVRStIan{b~ko$38v9s z!S`t_mtF9efDwem!L*Z;DL}=%%B%3tN0^F96@1dq#qm=5&~5}a9e^xv78pBV(vZBe zrxF{!C&wWC$X2qBwVSQ|S`GpMbCW9~fd`1AC&ESt<W3SX+morb}5*Lu~zaKyGqholtn)f5Og2{wr^an%e^B(|Z5f|~t5(g{h zFJ2}=2>*q7B16mxMNF8-*pvA)qf$C=CkQ21v_+Iuv^$9Z@DT&0Av@@dggVsppd6oO8d_!(9WX69K1V&$QSp9`0JS@>^*8 zcZ$jZ#J7AgX90~Cepi(L`*JVioLo7mGqcAb16)7g*u(ml*NwyPpsM>Nu6x)ZH{z_9 z(Fy%^93airI8P&>%dp9`HWF-#bO}jXEC>!&KP7F?eGC6kqhF1>3wA<_<;j68pQf){ z=ffZzhXvQ>9FwaC1p1@<-a90&>1V281ay5^>pD<9YVws_pE;5(?gWHFRbQGSVQgz4 zvEWYt^Ka{4E4#*a&ZVs;9ea~=j^1U{Hl4XJ;y(@<)4|WJb}JL+t#IG+M+fwb?L?Ae zVl^+J=7J8e*_TGd=^T_Pv#Hl0?mjj_y=Gl2N5f810=V-6YiT6csY>?3nj90`x$JlD z#A&;MV`ab9E0i<6;8{89&|-a~l_E6#a5jQ8?~fjav8DR3{zXZU)Og{n5LUBI@Dc3x z$1O=Vn8|5a4>9iOt@vA>mZ2JTar zQ-dU>j2e(4Z?(B{X7$5FKPU27>ohy74Wc2BfVYO|jwtGK6MgM@nZnHWfixIl!W(PC zyhAb5mR@?!XV-u%G|Yhg=hae8_5d4x#|c2;we3v9edZazmD6vB{+Tj)+M2389<^+B zXU?vS926k6WRi&3?KmsGY0$<^Vy1QA>0ZQrNxXf7~I$`8^t_J<0GO(HC|V;o(n+D zMw+oyJ_(*nymmv|v5caj#A~)SVS9V8KDo^GF+O6U>8+vvVXO^#Xk(q@I5xOK({HEK zyd&n^<-I5hV{mqp)e=TYo$HdY{U_3RH}G6Fp1~hFJls%>`D4l5^22SEr`NLAQP3l# zX>33uxezo*oAZVj1!#5-gkmE#`?DiQuh)0$3b|)jvvZ2ISKKX=?duQ}zJva!bZ$+k zjfKEKK=PpfPw6VK@Bv!R2b_q0y9Nmdt~5bJqj0K;R6^}ehzC)@9P4(`=voib%zK|7 zbU0(?Q1Z+WWo~8U3-`Rsq6fo>K}gh zqgVuS5wNatT}nW*c+!fIj0X6$AQPtB$G>GiAC*Zpb4;X6a{&85rg|N&_#iqARejld z3K4V?=*jixo>qmbnha^DkMC*k^BDA>P9^G9DT{5F71@uc5zsG*O)gGqi?NfFr>E=Z zixOBm*UV!yWa8upG(lAkg)ZYC;*hz>wFdd{&_pgky>Z{)QkK)GSya7vsN7T%qB*miV>F@Paf1xqn z*Y#PtEvmGJF0`n7NN)UkMzLE4QVs_210=E`&2i0r8u0{eWsBsNlIJ5+H6Yf)hFf<< z1*W0xWZ$|;#!5FGILrLC&V?hOoUkFCDRliU28Fk$1*56*cKzeFIiprERWg#+kk<&O z${a*6n*m%Z&h(|=@zzd%`qgwbk>Nr`@V4Z5HXt){XWRWlfq2DCzz^69Bj-$Qz${Nr zy@K{q2h!%rE?u!9_%C1P#Vq>1oRA|J||_a?`ssdXK3gE z)prPx#H|7#c!jZO!MkKY2LLj8@3n&(8C%SHkW!54&6Yke+}JZ}gw+|AC$bN7HZPx@ z%a<&@WI{tBu5vkPk+0kwm>|g?7Y86{G#oHg!Ai)&do4`bvO#}O!74=x{is9h`0Yu& z_;)yw)5Zx;20UY>6*Pd%L7`T(vME>smN+K817!fqb7E!T6TQJ{6`r=$4OU%BbjJ2`rUVe7>$Uv3&JPd@{P6I$ z=R$qYYC3vA&EQ%rxCA6Cax~X9G@ulB4nh*TfouaUcZa_2|@D!J=ked zadG3wu=32v z>+1#PH zj14B!^GRr28@b@12UVa@@y<_^M&G^HMfBS`1Yg}@vM0LVFBv7%L;(PKKZh2IMb2j=)ooyDD)rJpFgbuR#zdV_a${m`v-&;+_{6n?NiTm}Pn>8q*Qa|!wi z^OwDEl{Xr4=q(yFn@#?h<0jX2$VfPf@I*vAYZmC0J49Z&cwz)+DlrFOx z>2N=S-Ro@msS+;+zZNh!+U|G@!TP}MwgqR+gujunYGvc083o~Ha>C5#hpSwJ^<-fe zru-_UL)w=!V|?IRy+LUy(`K#5Gvvlwxp3xK{&UUu#6~gDv2?SOt#Z8dTXB(jsC4tu!8RpB)IN!2t*L+je5}apJSEA4@e+k&QXkH(?2}9H@Gap?+ z)>o3LYtI1>nY^2+$W;uE577P!u;RdW>so2N{!?S8@TXMG=KOUxey8PBew3d*tmS;{ zsLb3IRk!d@4_k@5+ax=Fb5f|47UMD@Rx@n1&^7E5Pn`H&!k69_0H374=C@$vET`}-RR2#H6P z3?lM}fbvD^JNKmzKa&2byMFYS6=J9m??G`8qQD1QAGyrgZ0YAgLDbQwmBW#J+uzLs z${MfSJIEO}oWJ~Se~OyCcsnx;P!2RM#t!T7JRNiP$pENd1_&d+@2T9U9XaW~P{|}N zcl1$CcARjz?3@Tf&b_Wi(!suvA=}5j@x|+QFKKB=K1(7A?R8zBjF!B&mEZ;h!^T?= zEghSW*P0lse~>AqZ$HumkxV5{xAYHxfz!h%F=^5PakBH|Qqv3oHj%_-?EBoBQdsO> zBHKC^_W)h}U0TeBHxSGG8`qg&;VDM)LnN_j^S0jzl**l+kE}+l?NU1K5j}?fbGk^h zAYu}jMAYoM8>Y7~Y0aB>wQl;qr-IGiCIWN__Jhwyz&l%fQ3o7TQY4Dj@Upd%C&_7&gYUJOOkf6N&j%@kIlAZu_m>$pek+2N|7u zG!Sry7n42xe;;)aOPSW@zYn<=F*a|IGz2Q+C0&XS888cSZ)t7iEo*uD!y$y;KLQPy zBBGi3nC?g`Iff19o?3Yx=gzo6#WMnLwomPlQ!ed8q3SotKZ+BqdqAPHp{swn3l?l8 z`vESP9)Lr*56;ji)fr^t@{8+mT6kCtflzOnfm2L!4Ki4*mhh~6+S@0#PM{-BEG&*J zezD<__znN%0n!o$jHjI+cxj<;Ji!Yuu9|D4(-`wwDoz3oG!9C~Ez~Y*Q{@xv-#7q?!eo!NyEZc@s$vB$qAap5qLj$5fJ-E1$cbS_7tNbp$%iXW%5 zC{4sRE<2&2e%eDRw@R#W{3_CoI}x7Xn(U(<6qze)Wyeg&{e-2n*q{>*FH?`qVs*nU zy+azTB6-@ASk*}+z?Uro;ksy$%>Z(NPZyi(cR@Q2VlE!!-CWCxyy_q_N$>YL_pFPs zmC7*rx;G1(<*<9I6tR`T@mgP2cC!e)ysC$KFEy|+l=-7kov^Jkmoz>xH{CaO;4oTm zx5gsJd#ghp#ow8gmV=DDNN5+Bg3WKXLCel+3z5u26&1g2KeY3|?;2LJa{-J1Gz%v+ zlH8K55o0qQY6TnhSJ=F$kuAXeaA(y)r; zUBrOkpu=giC*Y`-1uh1#_bUqj1I-#Q;JPqVRnJ`RMB-svtZR~%EG`&+3=B9iv^_5x zX@LxI9!~dpDg4pLn^v`sIl!$3tQ3B&G}Uu>Wd1?57!nUFN#_Mm*U603#LyAKh#*r; zRIQI|vQMQ~K8-Z!qvhD&wgOGs*;h)r9ZDYe0GimKHtnw~d=t2p)g13^;Y_CmZqAEG z#UmFC?9P-bn!_9js1T8pOO1EWsptdq>1+b;+{+GbYI8)0sS4yWVZaaDo&+PqojsD` zaOfQn^iY!JMz{)$V`swQW>aV&#v;6C_?EOYj~@l~AJ>fTi?s#97v8`?mETinH|i;B zEdiOsJ&qD5d4K8zm4Z2;Lxr1$xv5pJV8;v0#SSwHG%2aCJTubtCB#nTOyrvPq6NYk z>hPw}L>MgmQ7U=)GyuHer5lljW1|#xJYs*)(7+GAH8F@5cXDVDy@#;3b~VuWz~3g6h#GpKP`jd_1r8!u$|bOI zUONu*t^U|Io&pDgx>iOal<}mCDI%ju9N3a8eGY>1DE5D)ZEzM;2$H&lJFKOQy_7mK`d*`XuH zVROsm)M{~|M6qnqLkQ)vD1I^@nhrOe#ZW1L#q|!Bt;{)+YLZcwncB0LBQ0@)Yl$0Q z>O@B{M>wybtN~>(mJi1AujvPCFhy3a2c)sm@7NBL!LxD-S?osnMT7tRPM+zEiQ_Wt zg5*eNf5MxZhYZWe?TJy>HuZUr`-YK->(Q9TLDE&n5I#Wj0%t{j0y->bNbqU4YN4Mvh^)^TTG(78pRYLEUD= z`oOy+>AkSwQ3PP$edU3y-+pZqOH}ERFw;R0ABZs2G7mz5W7(zo2?p1de)liL`;v*{ zH%#d{L4n;4;7I-Xu=vuV?&A&bVwz&9jQ=c2si8Wwj&Ug_;DBJyYAFsni=*89xx@2t zQ}Opt4B!_2S?=QG-mm)R^Y8r1*JGZ_U;XgoLu9Y!4U>~#j+{VekWk)%*Ju3-a`o7m zSL1izM7$yeUKzyScy)54Z$5bnw>lt1-X6#0%)3Q(?GUr`C^BO*QG<_`>&pS_So~CM z9V#5Naew-LXtD)njkJa`J2mA_X%flIH(F@>9=;B)jedDrDGPxW{up%!C}?Z z4XHRZRd^po#k?7-1>H`w=(vsi;a3&nJGMN=fDBw-vpDY%G{3_`!+dZ;FNa5yX0P8G z^m##n2#~d9R!vu$D)sDd?r7r2GfMLo?I=jOwAGeNgIP7{$Q!casmMK*^TaC_O)Duk zFn}=xWBL4}^^2%F!^q?GqtX-MYJUhIwt%t||0eIR!oc}+(*c2vCk$J@%j)B)&xD_o zf?~sGaft&}^erTb)=fVIm_oa)SS)`+UtA% z@z$BCUsDU=QD=I>ah!MyjC-PO;8=TS)p-HJm;4mNib#Z*sbRD#6iARk&=S=aWfcNj*Gf9l!n1{B{(rCX+QX5e}IHp z!1VoZjlS8oZ4X4S8R!`?zf_`jXPRb8E)2@S;2j6WDOK z*rWx|4SjBBK`%CnPad*eNkqELPJMzfmAr+KDt{3@c)Wx_4&h&E3l{1H0CVGzh~s?y zFL<90I7IBOlD3&gZM}@Q$c&n94*&xen^7`n&#p*cT>p%{_fClSK z0&rwx0hId(YT#g{?#WEpa3)$l5Xn23Sexvm&0jK}ZQ*zU9Jy4|jzXR8oM3#TqFN@= zvYUj5ux1@aDl>WPzwEyIimH@E9g>}o9tMZJV&J7~t_~R+DJ;=id_^Cgj(}i=*hU2* zqNSKo<>iDzzNs^6%lbW>Vz;Fg<~SE1n1%X%RItcwt&D3n^57Zi0I}1mAs%sTdr`1# zrGcjk-375~M7pRZt%2FHPr8btKr3VXPF}~ku5AiR>relxIL$jT$E-OH$*BFX4Azn$ z$dS_%7)1{LaCU6MH+^dqMnGeZp4y(BdipWVYzPr~;OImq_*5wMO4)e{`Mocs^SG$wA&P-n;hg|(dJ2tci2%#m1 z|0F%^kl0m|xOkVsG>(vgq9@fes4uJ2iY9aJF;R@)Ro|W*?=RhX`iCu8)W@6WGtNW9 zYObQeq|nrmpi%xCA|O(r#OM7KjqQR%A_RN+$5hoN;B@4%Zqr8~Ku?9k=IC#t^RlYa z9Hc%eq&}#*Mq)_`+6MZZb;S{x(d~Vvaq4SH_vx~7tq2+`bt#5%o(q{a#nU4nDvShw zF*qKoG3c1d3dMozD6Z%5Ryi$HVoqHuy{kfoig1J`V#s8XA)qp|?8y`G&PU0wuuZ6k zvXDNM+?IbFI8)->YT#B=Y}efT+10d=}^kkcJK*(^R`KQQP~eq?tt)p?xAt8URVIR~)E+xW5UnSWI^##z z4OwHg5ygnA51>77nAu@AcdRThu9!xJEKap5tpDVs5AIwSujv%3R3qFq75;-n=F?Bc zwf0&p<|0fXH31w;p)CCy*hVRlS)rvjaCK*boZhUDeY1q{eCip6{0lR+>L|(?!!&jn z$Z*oH4wWpO#y3^oxA3IHP})(NJ#KHLzkiyE*&b+aACP2HmQ#c%^KOkWR7BiJ{-tUv znD0bn8bZ$ihP$- zjKP&u0-S$J!9DJ?B3zhvs&XV%sdqSW*a1g*Kw;kB~4zhZb|D z4-^pPblGmJV={N)KofLol1@HS1u9U*ZX>>~8MFE}xJgMvZ8o+tJi z0vE&2Mj^8E`%O`uxbfQo4Jon!1cts4Dgpu{mO(QpBwRvk5|ST4f; z1B?&=LMa%P46RnOQ6wrBjAa^<4Uu+Lf+DFZ&6hMxaU#1uZ`xMP)33`U=^N_2iJO<@ zKYJ5+KzYR}zAc4pkCTuG`73^fr^S%NWHL^U- z4ItFA7$(T*diazx8pt}aOhTiZq??q6s`VymQM`G<;Rsv#gX&3y{Ln3Zjt77$;G6>{3E&%qOsZ_wK#2qzTs|>DK}pE2M??nO0tTkU z^w-c%u!Mz07L=VtP9rYp+GQQh&jSsnZ>jLlW^+dx&t$IV(Wrf4`QB^1lQ`F*tsWDP zU23A;bkqfD%jmjr3oVm6ePn^xfgUu>u^3t_1_V!CPh6Q?fn9RB-JAKmZb4wa6TnnF zJ-r2<>WqTsh+$UWj&}MNQ6YP!Tj!U5zp*w!8NP$6p|JCkbx3iu)C&(P=Azh5Qs(2) zC=^y5$a-)`Rkhy6DB-?BT%dxvw_W^2U`6*{ObGIb!ZpxF>4xO($|-3Du6e?Szs7k; z(z`5Qiy}wbMX0b;L%l?w=WR)SB%p1I9DV5w%Lc3b3N5dzTHA5`cj4!H|7X*hTl*QqFk+#_8vy=A1 zBOG2i??clp(Pc;jlk(fOGV6yNl_l~y?T68|h#z3zKrG?c)zboF5FV&C0LXeG_Mg{r zg}<~TU&$dXf;o8X2klmAh2m1LcgOz5lY>`tb4Qt3h&O7~PqY<8?&Z4b2jyK`aO zcZIQ9H|X$&JkO`ASfQdI0;C(=4_GQ)!6}M63&^=<=%1D5Zs7J1PI9OlrFY2_#P@~% zs7U)R-FZ^tFTu;$5)Oa_1@%o3ykm<(r+#3F`ImS|tJ(Prb16_{9DTb)YoNs+Ar6jMQi{Ip0$3>A)K&kj+G2uU zAHeBIfw9hUESM-m=sR>RDn>Q~p-Tdv46lZ*P__v3joT9ZDQu1G=$Cq9g0s^O9IQhP zO?6_~syPndazkMI8-NRXMV3aZMr2`D6|ZU~&k8JLv|C^aN^O1CQ(*a$lD;4JctpE8G0ma%xzsgbVBbTe%8fBy`HYmi=qcHxwc(FDg^eZ+?xI zBJ=!^C>{hgzl2d16<)b`E?LLz=J+RoDVxP$zljV%rc98sB-#4q&d-t8bg%aaD~pF9 z-^=Srh5yz|CNW3I;m|AfB*SafZSfo$=3$F#=A-;PI54Ab8n7X=B*8U*S}?a1Q#&Y_ zT$3vTp`a=&GLNp_^=WL;qp#?w^ov;_AP`vtae8z@%qH7L3RK>#cs8mr`KS-E2SE@&@bdf7k+ zV%cD(2pnP}$Ue^vbYxDrPnjxwPNYJKQk<2_xs;%8vZyG_=4K5)jD`Y?`VJpTg5|zuIjV+&QnD1^R{ske`JxX@y7dSKxewAg|tB|fi92YX^%haNsl@UNbOq^%t3GsEQ*Jd%ItKKok z;Estmcor2gWdgCSIG>$QuH5rW*jsAO*q@9*s%JZSGPGs;ua~^B&ZRU!$+s+bt3A0C zhfm1AIUdelpH1j{L;9T><#vo!;qqY# zfOM3K(sudQVaV;w2=&u%T7vq-Ls3yNH05Ari z;i}xWQlsPvcX)}bumD$*6jD>eti)T15PTHGrRahl5?9QcXkK-k-(=ocw&3n-oqF71 z)G(>@i}Y;uz=JpTdv2=QBWwzp*n3bV!-trkWV5S>E}lkK;K%G^?uLF_l}*(FE$Gr# z1xSz-r>@y~fnuDWIH&==mZotyfWJnU-H@!HG-C_)B97-8XNTOEl**q15bz^#^qS2` z?jbe|hOa`AxJ+m#OGx9ya?CMX2UE6{omb(XW?`Gy4bczKQP#zplZu_VW1Z%VuWSBd zi8bj}h1Ht1(CA2!waNqfq6u>p4&f&W8Kke4k=sG*uO8YaC^kK$z&qLr;IgIy`ra{a?r090A70D!9a=eFSKyuN2ct zXlq!gFSvLvF;glM_)3k!QG)pr&9S8AeAC(Xc)8^FM_9_x&;biHVv|u&)2`o7i%)-@L(cxa8RpcOc%ne1 zQDLDWbKkjWrNwFn0WOmzoXoK6!y~T4_8C&Q{8xJD>VhPbSDw>9&GS^ z7DZ^(d!C|tUdE0B1^=)aGgJ!w5evSh{#k|3M^ej%)tH49z_?3y(~33KIyP2|-tcU+ z%ayj=XM`fWnyrrOznNRuLc=17urJ58r{Al)x##nr-99oR7A2#6M#ax&!;7$5)(dw@ z`y~cHb^tSV>U(`<|fa9;>9g(qIl7hj`vIS!?b!&62%j=nH_ zFGH7+jwnqKAlvy4F{`1vQD{Tme@&986GOa=Q#1pmf{(nCLQlTv@LgLsjdU}knB5w_ zZRNBoVdM+*ZJ1zy?a%CTk}LD+%)VB6+Ql*q#^ zWM+V(1=ya@bL?E43US8fWIlqnMKpNGCbk-!oCfNfv2%i9_wXn5MU4Hpk%fL&=% zIX0(bQK~VJtjhHcs%v-ti4w&-i;}7Qo5Ju196s(c^tXDiHh881I6sd7i7PK86j@33 z2w+huf`N8zOw$wqnjQkbZKpo!tQA^VAc)Uk=6Q3uG+hY^Raoe{B|6L~<&ycNNG_lw zEi!OW`RWN=`fF*bdI}5eUv1%lj&HiU7LgL1azE>ZYe+ES8Br(3#WFFI}(8>P2sm!t>s0aR0MF$yoi#Bd=De)j%QsEU5Yy5%9bI-El( zWR8rChtxQVHH_t)aix7y$Fk9b4bUo@Q9v}W_uqUWx#_|5bpu2 zz4(Mttc@2PFJaG0>q$@65M7$KPB(w8vUHO{d^rv$A9#t1F0ux5_`Y$)u(@$* z5HSNC^ZMwXl}ZOc7h>4t4EZ0F(sEj(<=C_Aa7g0T1oU6%X@eFP+xQTixVYAF)(<#k`7Inx<5aa5oXLKiXwx@*%P>Gu+uvPi^Xa@x&&meg*h2_ z$p_MvMU`d|u3o2bQyf;8M>QoXmvzp}=WA`ui}r2dANY%p*V!^X3B$e*>Hjs~`aoe^ zKl$4p1RRln?#mSowgAWn+EmY*_QP6?V(hG){^vImMHoK49y{SB@IGojvkWg`CGb$7 zig+oOUcsWujoQ4he9{7UhEtia>m)uhw@oE8*pQ9Kn#@h0lShV2=1>=|l+^-7H%tS##Gz=1N zRh~$wGFTk0AplN^9mig%^5YfpkR#aN-{Pu@X|)N%I8cIxo#l5_Z$`iLsne}TG|OOK zbq1D>C7dA?Uh10%2p41u7Z=RkU7Q(8VNs(JvO_{8HKh{L__ANv&&Mh2Z>eAQnYdA9 zTrd^gG>P!(Ce=@`fd~0#S#U%^|9I~&x)Z1}E`Mq>J_C*bAc0KtK(0OCuWjb<|aFauyJ7?t_L>HT`u{?RS)5f{B*!K;!p)4G65@ z^weS`}NY(`?;vr+c=WPuw&ZPz|)N_5k!L$8aQ0$Rve5cqG``gSA1e<>6GBUEmcqLLMqFrWLc$aV( z!GzquXG3vw`+KE1qOL01TegLYu?*$sT=(95U}-uo6ed&H-9SMGCpl8v2w&)_$wnqi z?;U{9fviH~O*5uKF~}H3fo$S?Toe!g?>qzFXkH~nSvnk~{Xm2(CV&d91f1~nq`Cj+YqqH9UZXe&MYxT-MVAe^v0&2uThPq?TS2Ukx`Kgn=43Br zR*&8#j7n)+Q7<|v2O@#$BY2gOTXb1frsT2j%d_jNV&~Vz)6m z0nP=au$bu4w`aC;Vt&fbab%nSwN3}0TQm*)NKF`nqsN{#Cl-n${4pXa2B0v`?c~>` zkma&MD!YE~yc+`)Zo}YOHZDK=S@xl8Q`Wg7<65#{ps~&zHLjSPC4Q823M#`CprH?8 zHcB`bBe_iO-j-Bo(BQn;CVz89@xjXtSi)FKAB1E>sW)pjbv)YH7aIK7DtSFHNUMYK zAW=!sx`jtit;QZhNL6m}1F)e33U1I24-MveJj4?y>oS?8GSENW*9>54J<-vL0FKMk zONtS6#AcNVt59)u&QnbV3bDq7gvTE?noxu9Y(xrjI96XFw2?F|)@2_pr6c}0MDQ@XckEfPGOXERA2_-UG(#!=wX|o%ZhMGBJRiESkYvU}#qH4E3 zK0`ZzfD9pxN~d&}w8YRI5)#r4!fd1&K!lM-8KjYvZjly{M(GAgNy)(%-{(B%@tpTv zU-o_NYu*2K-5>V8)>?b7-*Y_accvVp^_;h|dkAOHGu1_33PI4)a!G^C$US8E3#j^K;Y5kBsaL?wq&rX=Qc$3+<~wAMG$$ew^fa1wN#VTHLJk1SBeVOx}jN`L=>}ICX$$ z$T3v9T+ODqR=u+IF29#e+ijOmeO+Kj(u=mrxFP6g2XI`t?&ECpiZojGD{J0WGDMiJi~VaUP@dJms^b(eX-@>N zL}Bu>g^U*e;L6R@UvN5L2<>p6f_iEpr3_P2M7|8vdH0B4C|;OJRtxg-Gsnee4$m-SS{JB|?oR1f4Tn(pG4eCE-0IS_-q;lh=$?BP$&`zSh|{_*g_^LkM2 zxfk$dl!cKDC#XfBEOBXyA{VcvO?@k27c#r4dob2hMiSID?`t!jLepScKH*C$Aag~F zm$7Kc#ItKEpZWoH0(K44U8*uiEz|e1#LuZ9_Z*_;*;h@k2wFM!yVsg2jEKyc5379f z^P>h&h&IFM^uh&>-@f2Rr5<>EGutHnx=e~-%h&EhM|p|aV_sK0vXJW^@dlJ+;B}OD zM@cd@V>+a2REBd`v3~@HDrdMn0KUwg&XRBwZAgX$;uLv?hQOT~PF@cXy&xnfo1ZH< zBHSOlbyRA}ex7Hk+GR{BH0I1xV@GcLlSA0Y(F>flx)GIYpYCBGf?1Z>$7?{4LMv9= z&Hs2E@m$HxZtV_#YkK8Mw|quQ^C{syZ(Sh;Y*|@rM51-M9=XTpMxiBZJ#aD(e`e!l z&-#fJ;?0v<3};K#zJ3I$M3m&MpC_U~Oe7N?G6V4Km_}AR8;ngUnN?3kv(uVWUZ0f4 zT2?y}O1z{u8P-n}tNMMp28V>^h&j$@Bj`jDH;Dz{;wfqmp@QE1>-0vMITVb1%Smnt z#3P`G7;)>tO6|HAA1t@@fvLyVI3&Ev{abMoqhgM^y_hznxVC24U17!~k-Nw}M3~?# zOu0Vp=^_Q6c<5zkT&^~q9Y?~%;H9Iv!`K{2;+rJfN)Ff}j<|ag^A|qDaP}RDln)=j zoxNn_7`Dzte@IbgNiiv3+#MI*AsHiV?$jhoAWT33>@Lbw$NqSH z%|t^_yZS+;#0aZLG?_&jB(p?$NAWaTUTI&!7b`L@(`?wHCW-9T`uM;?^$!AKE~jkm z+`PBJ9UAwc%tFt!${?8WKsF|)-2!VGT}B#y+PT@RdGV+Pj=dzjmPT4!N!G7JQ2!#!-fCV& zP|r*~>$7YHY0v8UFBdVNhe8}en|}x7m~Qw{G_0{<+Ll=wP~K>$<8yS)nF2#@Kalc?Ic2eimAB_y-JlP$$D$;w`~kuu>tuC-2a?!Q-Zyi{)t>u}SisCK?)qIg`| zC@QWdDTqs7*Po&HgM6%e#Y}_$r@?_Lse5knju{7_USvaW@raRGX7aJRqAVXcL)T)4Z-XAab3(=RS{=xAUk5JH_6VbTy_KB2 z6bd5R0%txp#}apbANLsQoLgruAXy-2U_Io`_R$@sHw@T|C7%v_UKU{$=fvVf&*VKC z7d_W{U;(0gI*!g~=ja<@qJh);pM8GX*tM<*j2U7$4CC0;Ls)U&IjO4ph@RtwCMYe0 zyc-T0U3#gyOquNh$vkSsGH4$&6zvt-&B$CwK!niq4|~-2?ve?}YgD5Gv#q!6HU@N;qaMLV=nMJfu{E_q{bmCJ zOhh*zfLn(-N7HQkK`Y~1ah1rPltCdWLjN@xtNRB-?e=gpP0#Rgi)h7F1mydh;Epfe zDtABEOe(dpv0o-A2Vp}v5XbP?nzow;7tap`&;7VZaYho& z!IzpVF499}5;E0HrwE?N&)LUjQ*CNMhNR~frj8*6)U4;z!&bg~ru681*d0NI9uqA_ zG2Z257MDw$6mIcSZC*OUts2z=Znxwq3S7MRE~wLiJn*VqNyfAp{k1qyo;0$% ze6NGHamo+plJh$*wr|>IAxeNQH9xKO49!?fy=;@Ke5+)zR8-_HLDqe=SKa1Rj-n5Q zIY1cwjmK`Oh*QLmNLfI-c+TQb2<;uT7^;8J_=M@@o^W`?+8bT-iHttAwzn_QZ{gfo zCuv&r=(7{G6*L0h_6+i9j*=CCOGAN7#NO`5{nyc=6&&gR<4k)ckpbbCt zJfK!yIb^bu7om~+A|vSbx&>;o^rLSP7THm~cc9ImvBNXlENHWi8>fIBr`?KzGyB_L zs6Sz*4C9H|pSPg$^Km~0Hr(>LRrvz{M|x*Yi-*gUrz62|ke!+8v-zgV3a#oq_$6p* z_3m8ZeXAi$Wg5YFbB)&=34RJx!J1_z6G9b~E5sV5&mUrWew~EZ8pH<_M}8eljN!aK zY$WNzN@ZkP1~EZ`4z)OeGiDyJhGHWc(H|-z_MN;T1h1SOR|y^65F#4i{&^iI&GpVe zXj!=>2MJ+#23JiDJEe6FgUg%JfWbFGzMrVbId)h52Up44{N{Q`nj*gvo?c2dgZ=Bu zKGz$=H;iI6xjqz*ylIbFtme=+_7C$M&y8u^ih}#<;z2KMgJ+`vW@gn|e_0m601aUf z3mi8m>x7VDMcGu5`mB27;&tz1Pq*})5q5`G|2Y{>4C}f2*R5hSaifRg{e@~{Suo$C zoKGp>j?#j6RmkSuy#5@C4Ndv*n|F*w@8P$U?=f6igSGZ$_MuT!!OvK~$>GRToG!}Y z>CD^|D@)!T4Mau&#!%h*Vp9$3iue~`v-X$*Q`?OR)_?Y(^A4+xfbw>cQry_6;^h7Y zYO$~$mAdXQMUNSF9!6+hAx^dnQyHg47_q};oM*Ew%c-ez;76lXR?5P-kwRQWGK@qQ zSqA;CZockG6c2Fy<-;3?$Tni8D7S`W-4(EnK(zhGsY1Z{=RzehVNgL~uw!gDH#Pre zVQ*IUO>vu|^O7QSu)U~3UnYH$H8y@Dc_G;9*z)_Yd+aten+sBYa0*{v7fiQ#RXWI#Qb~QPxaljQy?z#3#(I6- z)my(Nf6mr#Myc`wz>> zOHBe%_oA7LyrZVmdMu9TIbvLKxUCv}=o=GaP(SU+a$Ey_yeD#n)x* z?ZaPkf`{IXSLtcR>XI(ufb}d?960^e+t@|{upWy-&wz|Zf3}GXT<@!AG1*f624~~> z;@^61GVBj$(Of@jde4yYiGsW;zuq@M;KR%3#6r3Hy=uHAh%>3vkLXlq>YRXy98^9uKpTtc3Y z2W2>y(8+{fhuL0xGBLV7T*T$UINg><1^`So{tw!ggASQr_R~MAOd!XXmvdbsJ-a)e z_hegy$>bfjI=o0!M9n#$rz}kImVo{=ieaDg{t6CH{6HiMrmAY$$(wvR;if?#wR#&{ z+(Kq?_&)CDtl8xS0t>UZWH0%O25|%Jdp#>H+gf^j-b>?PSvL83Iee!dBc?*i3{X!U zGuC9SNRpI3TpIL9P2be$#?NE4vikIE(C(7s+EyhLL|C&E?8pl(=p!JGu7;)DO_EN9 zm5#ASay=2zR9f--3?#6QlDB#&PKpIKoS)^!fJN1buC`cRMXgvbU9rtia<{i;J>1U|lzZ6dVX@^766MMWbI4Cs+C0}3z^Qr3VC`O1(GBQ+AKWypoJG{pU9c?)Bp z16gUng4{PC08>HlImfx9g>lO3K?Kd@Yi_|=2RF) S28l)#$QE;AoI%6iuKxqkJqY6f diff --git a/processors/corp_owners.py b/processors/corp_owners.py index 56f40b8..f745cba 100644 --- a/processors/corp_owners.py +++ b/processors/corp_owners.py @@ -14,155 +14,90 @@ import json import os import re import urllib.parse +import psycopg2 +from dotenv import load_dotenv -search_for_business_url = 'https://ccfs-api.prod.sos.wa.gov/api/BusinessSearch/GetBusinessSearchList' -# Old search URL, holding onto in case the above gets blocked -# search_for_business_url = 'https://cfda.sos.wa.gov/api/BusinessSearch/GetBusinessSearchList' -principal_url = 'https://ccfs-api.prod.sos.wa.gov/api/BusinessSearch/GetAdvanceBusinessSearchList' - -principal_headers = { - 'Accept-Language': 'en-US,en;q=0.8,es-AR;q=0.5,es;q=0.3', - 'Referer': 'https://ccfs.sos.wa.gov/', - 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', # this might be an issue - 'Origin': 'https://ccfs.sos.wa.gov' -} - -def get_business_search_payload(business_name, page_count, page_num): - return { - 'Type': 'BusinessName', - 'SearchEntityName': business_name, - 'SearchType': 'BusinessName', - 'SortType': 'ASC', - 'SortBy': 'Entity Name', - 'SearchValue': business_name, - 'SearchCriteria': 'Contains', - 'IsSearch': 'true', - 'PageID': page_num, - 'PageCount': page_count, - } - -def get_business_details(business_id): - """ Get business details from the Corporation and charities filing database. - """ - url = f"https://ccfs-api.prod.sos.wa.gov/api/BusinessSearch/BusinessInformation?businessID={business_id}" - # Old search URL, holding onto in case the above gets blocked - # url = 'https://cfda.sos.wa.gov/#/BusinessSearch/BusinessInformation?businessID={business_id}'.format(business_id=business_id) - if(os.path.exists(f"../data/inputs/principals_json/{business_id}.json")): - with open(f"../data/inputs/principals_json/{business_id}.json", 'r') as f: - return json.load(f) - else: - r = requests.get(url) - # Try to read the response text - try: - r_json = json.loads(r.text) - except: - r_json = {} - - try: - # TODO: Will this write an empty string if no actual request result? - with open(f"../data/inputs/principals_json/{business_id}.json", 'r') as f: - str_json = json.dumps(r_json) - f.write(str_json) - except: - pass - return r_json +load_dotenv() +DB_NAME = os.environ.get("DB_NAME") +DB_USER = os.environ.get("DB_USER") +DB_PASS = os.environ.get("DB_PASS") +DB_HOST = os.environ.get("DB_HOST") +DB_PORT = os.environ.get("DB_PORT") class LookupCompaniesHelper: def __init__(self, out_path: str): self.output_path = out_path # Absolute path to where the file will be saved def _get_empty_df(self): - return pd.DataFrame([], columns = ['SearchTerm', 'BusinessName', 'UBINumber', 'BusinessId', - 'Address', 'Status', 'address_match']) + return pd.DataFrame([], columns = ['SearchTerm', 'BusinessName','address_match']) - def _get_business_search_results(self, business_name_orig, page_num): + def _query_db(self,business_name): + conn = psycopg2.connect(database=DB_NAME, + user=DB_USER, + password=DB_PASS, + host=DB_HOST, + port=DB_PORT) + conn.autocommit = True + with conn.cursor() as cur: + cur.execute(""" + select + corporations."Ubi", + corporations."BusinessName", + corporations."Type", + corporations."TypeDescription", + corporations."RecordStatus", + business_info."MailingAddressLine1", + business_info."MailingCity", + business_info."MailingState", + business_info."MailingCountry", + business_info."MailingZip5" + from business_info + inner join corporations + on business_info."Ubi" = corporations."Ubi" + where corporations."BusinessName" ~ %s + limit 10 + ; + """, (business_name+"*",)) + rows = cur.fetchall() + + row_names = ["Ubi", "BusinessName","Type","TypeDescription","RecordStatus", "MailingAddressLine1", "MailingCity","MailingState","MailingCountry","MailingZip5"] + table = [] + for r in rows: + table += [dict(zip(row_names, r))] + return table + + def _get_business_search_results(self, business_name_orig): business_name = business_name_orig.strip() no_result = True result = {} while no_result and len(business_name) > 0: print(f"searching with name {business_name}") - r = requests.post(search_for_business_url, get_business_search_payload(business_name, 100, page_num)) - # TODO: add some more error handling in case of connectivity issues. - if r.status_code == 429: - # TODO: Raise an error - print("This IP address has likely been blocked by CCFS, try using a vpn") - result = json.loads(r.text) - if len(result) > 0: + result = self._query_db(business_name) + + # If no search results, try removing the last word in the name + # This seems to be a decent heuristic because final words are things like LTD, APTS + # TODO: A more robust search function could make this irrelevant + if(len(result) > 0): no_result = False else: - # Strip off the last word from the search term and try again next iteration - try: - # Get the index of the last space in the name - last_space = business_name[::-1].index(" ") - business_name = business_name[: -1 - last_space].strip() - except ValueError: - # TODO: In this case, try with the LastBuyer in stead of ListedOwner? - print(f"Found no business with name {business_name_orig}\n") - business_name = "" - - - return result + # Get the index of the last space in the name + last_space = business_name[::-1].index(" ") + business_name = business_name[: -1 - last_space].strip() - def _extract_search_results(self, search_term, search_req_response): - res_list = [] - for res in search_req_response: - # build up the known responses - # get more business data from that id - business_info = get_business_details(res["BusinessID"]) - res_list += [[search_term.strip(), - res.get('BusinessName').strip(), - res.get('UBINumber'), - res.get('BusinessID'), - res.get('PrincipalOffice')['PrincipalStreetAddress']['FullAddress'], - res.get("BusinessStatus"), - business_info.get("BINAICSCodeDesc", "NOT_FOUND")]] - # return an empty row if no search results - if len(search_req_response) == 0: - res_list += [[search_term, "NOT_FOUND", "NOT_FOUND", "NOT_FOUND", "NOT_FOUND", "NOT_FOUND", "NOT_FOUND"]] - - res_df = pd.DataFrame(res_list, columns=['SearchTerm', 'BusinessName', 'UBINumber', 'BusinessId', 'Address', "Status", "BusinessNature"]) - - # Clean some of the results a bit more: - # Keep only active companies and searches that yielded no results - res_df = res_df[(res_df["Status"]=="Active") | (res_df["Status"]=="NOT_FOUND")] - - # TODO: Maybe add a filter on BusinessNature for only real estate/ property investments - # TODO: First need to get an idea of all the BusinessNature types - - # Keep a list of exact matches, or later build a list of potential matches that we give to human verifiers - # This check is very simple heuristic and more robust matching will occur later in processing - exact_match = res_df.index[res_df['BusinessName'] == search_term].tolist() - if exact_match: - res_df = pd.concat([res_df.iloc[[exact_match[0]],:], res_df.drop(exact_match[0], axis=0)], axis=0) - - return res_df - - def _determine_search_matches(self, search_results_df): - """ - Mark row as potential match: UBI number is a duplicate, or Address is the same - df.duplicated just sees if that address is already in the dataframe, NOT that the serach term - and result have the same address. Could add search terms as a subset for duplicated call - """ - search_results_df['address_match'] = search_results_df.duplicated(subset=['Address'], keep=False) - - def _get_all_company_name_match_search_results(self, owner_name): - n = 1 - res_length = 100 - search_results = [] - - res = self._get_business_search_results(owner_name, n) - return res + df = pd.DataFrame(result) + df["SearchTerm"] = business_name_orig + return df """ """ - def _get_potential_company_name_matches(self, owner_name): - all_search_results = self._get_all_company_name_match_search_results(owner_name) - extracted_results = self._extract_search_results(owner_name, all_search_results) - self._determine_search_matches(extracted_results) - return extracted_results + def _get_potential_company_name_matches(self, owner_name): + all_search_results = self._get_business_search_results(owner_name) + df = pd.DataFrame(all_search_results) + df["SearchTerm"] = owner_name + return df - def _separate_search_results(self, results): + def _separate_search_results(self, results, searchTerm): """ utils to separate search results into exact match, potential match (where no exact match was found), and additional matches (extra matches if there was an exact match and additional matches) @@ -171,9 +106,9 @@ class LookupCompaniesHelper: - Partnership - etc. """ - def is_exact_match(row): + def is_exact_match(row, searchTerm): """ Extract exact matches, including some regex magic. """ - search = row["SearchTerm"] + search = searchTerm result = row["BusinessName"] # examples: LLC, LLP, L L C, L.L.C., L.L.C. L.L.P., L.L.P, LLC. @@ -201,7 +136,7 @@ class LookupCompaniesHelper: exact_matches = self._get_empty_df() potential_matches = self._get_empty_df() - exact_match = results[results.apply(lambda row: is_exact_match(row), axis=1)] + exact_match = results[results.apply(lambda row: is_exact_match(row, searchTerm), axis=1)] # TODO: If going to do len(results) check, then need to filter by business nature sooner # Len results heuristic doesn't work for empty searches, or the recursive search if len(exact_match) > 0: #or len(results) == 1: @@ -224,8 +159,8 @@ class LookupCompaniesHelper: for owner in owner_list: owner = owner.strip() # Clean owner name slightly - matches = self._get_potential_company_name_matches(owner) - temp_exact, temp_potential = self._separate_search_results(matches) + matches = self._get_business_search_results(owner) + temp_exact, temp_potential = self._separate_search_results(matches, owner) exact_matches = pd.concat([temp_exact, exact_matches], ignore_index=True) potential_matches = pd.concat([temp_potential, potential_matches], ignore_index=True) return exact_matches, potential_matches @@ -242,6 +177,33 @@ class LookupCompaniesHelper: exact_matches.to_csv(f'{self.output_path}/exact_matches_{x}.csv') potential_matches.to_csv(f'{self.output_path}/potential_matches_{x}.csv') +def get_business_details(business_id): + """ Get business details from the Corporation and charities filing database. + """ + url = f"https://ccfs-api.prod.sos.wa.gov/api/BusinessSearch/BusinessInformation?businessID={business_id}" + # Old search URL, holding onto in case the above gets blocked + # url = 'https://cfda.sos.wa.gov/#/BusinessSearch/BusinessInformation?businessID={business_id}'.format(business_id=business_id) + if(os.path.exists(f"../data/inputs/principals_json/{business_id}.json")): + with open(f"../data/inputs/principals_json/{business_id}.json", 'r') as f: + return json.load(f) + else: + r = requests.get(url) + # Try to read the response text + try: + r_json = json.loads(r.text) + except: + r_json = {} + + try: + # TODO: Will this write an empty string if no actual request result? + with open(f"../data/inputs/principals_json/{business_id}.json", 'r') as f: + str_json = json.dumps(r_json) + f.write(str_json) + except: + pass + return r_json + +# Not currently in use, needs to be updated class GroupCompaniesHelper: def __init__(self, out_path: str, out_name: str): self.output_path = out_path # The path to the output file to save the output file @@ -390,4 +352,4 @@ class GroupCompaniesHelper: results.to_csv(f"{self.output_path}/{self.output_name}") results.to_csv(f"{self.output_path}/{self.output_name}") - return results \ No newline at end of file + return results diff --git a/processors/gre-llc.py b/processors/gre-llc.py deleted file mode 100644 index 52989f0..0000000 --- a/processors/gre-llc.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python3get_company_list_name_matches -# -*- coding: utf-8 -*- -""" -Created on Fri Aug 15 19:06:45 2025 - -@author: linnea - -Script to find exact and potential search results for a parcel owner in the CCFS database - -A representative example for the parcel owner (assessor) data scraping step -Address: 308 4th Ave S, Seattle, WA, 98104 -ParcelNumber: 5247801370 -ListedOwner: GRE DOWNTOWNER LLC -PreviousBuyer: CENTRAL PUGET SOUND REGIONAL TRASNSIT AUTHORITY - -We happen to already know the answer, -which is this address is part of Goodman Real Estate's extensive portfolio -GRE List: https://goodmanre.com/our-projects/ - -TODO: - - Make a flag that shows if the buywer / owner are similar - - Get the address field from CCFS, put in corp_owners - - If the previous buyer doesn't make sense, - get the year of the last buying to see if it's at all recent for sanity checks -""" - -from corp_owners import LookupCompaniesHelper, GroupCompaniesHelper -import pandas as pd - -lookup_helper = LookupCompaniesHelper(("../data/intermediates")) - -# Option 1: Uncomment the two lines to run the full script. -# df = pd.read_csv("../data/intermediates/owners_listed.csv") -# owner_names = df["ListedOwner"].unique() - -# Option 2: Uncomment two lines to run with a specific subset for debugging -df = pd.read_excel("../experiments/gre_apartments.ods", engine='odf') -df = df.iloc[1] -owner_names = [df["ListedOwner"]] - -exact, potential = lookup_helper.get_company_list_name_matches(owner_names) - - diff --git a/processors/test.py b/processors/test.py new file mode 100644 index 0000000..f984d80 --- /dev/null +++ b/processors/test.py @@ -0,0 +1,25 @@ +""" +Helper script for testing out changes to business lookup. +Uses the GRE data that we were able to collect by hanad for verification. +""" + +from corp_owners import LookupCompaniesHelper, GroupCompaniesHelper +import pandas as pd +import os + +lookup_helper = LookupCompaniesHelper(("../data/intermediates")) + +print(os.getcwd()) +df = pd.read_excel("./experiments/gre_apartments.ods", engine='odf') + +# Option 1: iterate through the whole list of GRE apartment names +owner_names = df["ListedOwner"].unique() + +# Option 2: pick a specific owner name +# owner_names = ["GRE 4TH AVE S LLC"] + + +exact, potential = lookup_helper.get_company_list_name_matches(owner_names) + +exact.to_csv("./data/intermediates/exact.csv") +potential.to_csv("./data/intermediates/potential.csv") -- 2.49.0 From 56c1ba767618cd699538504d143351d3c2fade42 Mon Sep 17 00:00:00 2001 From: Linnea Date: Tue, 20 Jan 2026 18:58:38 -0800 Subject: [PATCH 2/5] add dotenv to requirements --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 1bed897..16df71f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,3 +30,5 @@ argon2-cffi-bindings==25.1.0 cffi==2.0.0 minio==7.2.16 pycryptodome==3.23.0 +dotenv==0.9.9 +python-dotenv==1.2.1 -- 2.49.0 From 01a4de143697008ba468115bbd23944dcc8ae453 Mon Sep 17 00:00:00 2001 From: Linnea Date: Tue, 20 Jan 2026 19:07:41 -0800 Subject: [PATCH 3/5] update readme --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e3373a..a7bd07d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ Relevant but not 1-1 walkthrough of how to programmatically find building owners ## Project Structure - `processors/`: directory for Python scripts to extract, transform, and load the data -- `postgis-data/`: directory that will hold the PostgreSQL data - `data/`: currently ignored by git, need to share manually - `inputs/`: directory with only files that come directly from APIs or public websites - `intermediates/`: directory for folders containing intermediate transformed versions of the inputs @@ -27,6 +26,7 @@ TODO: Find a good source for eviction filing data. Those with access can refer t ## Object Storage +### King County Assessor Data An S3 compatible storage is hosted on [minio.radmin.live](minio.radmin.live) SDK documentation: https://github.com/minio/minio-py/blob/master/docs/API.md @@ -36,3 +36,6 @@ Use `lib/minio_helper.py` to extend the functionality Run `test_minio` in `lib/main.py` to test out that it works (TODO: move this to own testing script, perhaps unit tests) Note: You will need to have minio_access_key and minio_secret_key in your env before running for this to work, contact @linnealovespie or @ammaratef45 to obtain these keys) + +### CCFS Data +We have our own copy of the [CCFS database](https://kingcounty.gov/en/dept/kcit/data-information-services/gis-center/property-research). Contact @linnealovespie or @jessib to get the `.env` file needed to load in the connection string secrets. \ No newline at end of file -- 2.49.0 From 39fe1c17c1d7fb92becfb1f0a2bf7717ae747030 Mon Sep 17 00:00:00 2001 From: Linnea Date: Tue, 20 Jan 2026 19:40:08 -0800 Subject: [PATCH 4/5] gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 09833da..601dce3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ tmp/ *.org~ data/ venv/ -*~ \ No newline at end of file +*~ +.env \ No newline at end of file -- 2.49.0 From 66f2ac404a661d54f6d5cdabf12cb5d5606fb9ed Mon Sep 17 00:00:00 2001 From: Linnea Date: Tue, 20 Jan 2026 20:11:05 -0800 Subject: [PATCH 5/5] reqs --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 16df71f..beeeac8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,3 +32,4 @@ minio==7.2.16 pycryptodome==3.23.0 dotenv==0.9.9 python-dotenv==1.2.1 +psycopg2-binary==2.9.10 -- 2.49.0