From 84cbb888e634d622779e8ab7fa5b8b3198f2247a Mon Sep 17 00:00:00 2001 From: bolade Date: Wed, 3 Sep 2025 09:41:19 +0100 Subject: [PATCH] Refactor investor-related schemas and models; implement investor CRUD operations and update stage_focus values to uppercase --- app/__pycache__/py_schemas.cpython-312.pyc | Bin 3364 -> 3550 bytes app/api/__pycache__/investors.cpython-312.pyc | Bin 473 -> 9571 bytes app/api/investors.py | 235 +++++++++++++++++- app/command.py | 46 ++++ app/db/__pycache__/models.cpython-312.pyc | Bin 4470 -> 4444 bytes app/db/models.py | 12 +- app/py_schemas.py | 22 +- .../__pycache__/openrouter.cpython-312.pyc | Bin 14148 -> 14261 bytes .../__pycache__/querying.cpython-312.pyc | Bin 3970 -> 3949 bytes 9 files changed, 294 insertions(+), 21 deletions(-) create mode 100644 app/command.py diff --git a/app/__pycache__/py_schemas.cpython-312.pyc b/app/__pycache__/py_schemas.cpython-312.pyc index e6ba64591e2c89ce78200ef5d7d25ff4bdf90ec2..5819f61826af773f0e3e91bcd568bd51e5fda3a4 100644 GIT binary patch delta 1356 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

A@)h#iI9_R@|c>|^yb!jW`VYqn4yhR{|qt> z4J5H_R%TkbkU78x8+Q~4zmF~c5nL^>al=%w^(QBnPu{v%;)lxoSdkxl!gb!dSPt(k zhWD1ZeKl;n7#=TiJ8Rh1Vt8wb8v`R3^ei5G)9j(cID%hnN@M+of7}n3m4PoUvh)H(AIU^chmRNz~W@p^Bvmy zo(5b0L+en-6=}f;G++taZ-(u*&EY-M)29FQ%%P+5v)5qujb_lvL$(zglV`M`jS36X!6=g9o>o+F zsWJ2*YAfneClE$IoG2j;qc~7Krq&6r@vGG-s9S`e_5}g{Z`|t8a@Vb3IV2QA!pf;) zXuRax`PA349R6{4wT*HIpLJ1u=aZKHRaarp$ww`d1#a@ChvIuGZM~I&;qt)#;=q1z zy~|Ai+0x@(bZS;F*@&2VLS?dhJxb20JVBAT4xMm$@3c0*lvU;uTOqT3V(3@2TMPE z#P)v&`rwPxzc^hC?JD_pH|T_cmpw!QY^eDCE$lJ`2*EN*jCS{t1?Nqha2=3QWxY-w*cOQ0(p zWL#qJxvkN`)e!4sIISMQlZP|@ArV7|72A|vOK-Ig z3#AUTf>MXE-}5v$9^UzW^DbKu(}H{r?$ng@<+`pq4jro}Fgpp^dARKj%GVOfoFvC{ zaO10+enwk^pK%z8>ne$O6K9|dLM2^Jk=sFdq|jCMEE0Zz*(=o>(Py0Lg^VF3aYfD{ zw?K~Ez>}*$^jzd5I4~cJNw{x35A_-c8R|4lHN94AVQE42B9QzBX}7>}gdhp7-|82E zWp1R%jr?k%Ji4zqy6?fk;^^@bccRQq6}hPrcc$v1+#!f1`0lmt$M4(wZg0Q2eO0=# zz0|%N^kUCYxhGoeiI#fCo>R1YY?)tjFXf(atv3R9F035;9k;dWf?|jy+}{5et+~#N z`ZgNVcDP~r(0ZT-eNlxB?%@8Ipu^_-M`}#dk^f(6%qH*3_K{#(wPfi__)h^VxfL3$ zyl1|B0$w$B8Rf@5T5H7y?k?d46rTL%Benw%4<*n@60}?*0hQCaWO6RbyaK5iX6hii(n~qBCqpCb+4F zKRXxK;37FCYUcAMY6^qP&PniWsU%c2OH#^rk_%S|3URUrFK504wa}wPpLisLTv!_D_?t|cWa?%?2&KVqUR;Y(L*=> z;w1&2mt7n^V!|Wvw!mbc`uUZ@5UYo~(Y8|Rj%svU@RQY~FK63Z8>VZ^#D ztQ)Z&$`^W$UEype=qiJShZ%dmBlq(-)rrTu4m=q7_4qTYi}u{ns`y#;KF1F>JO&K` ztnR{g{nz`KK3v_l*k7XeJw9~w;ql*|c}Dd@vvd_d_eY=O2b=A$G5}Tguj}or?;U$M z{oBkl*AVTgv~^a=SJ+{G_Z)nVxYaL!)%V~ZN!(|V>ybihv_x-xyyxJ9XDNYP4S7l20=l$E`5gZ z-k0bibXzElPTdM`o_f3aXJGy@Gr#%Gyk;_Kkh1^r(t4EsD@D5HcBuL?sI}6${TnS1W<$P$5W@_Ev05czM>y&mo?HC&D4AFgY|@%%KLe{SFbt zbu6RA>b^r=PZ$@BUl}LIC4;ygb88)o(*`H9o3z_ftp%%{b~ng!O3d1P3r|V-xKxpq zrVpd#wc&bUxKtd*iadNtMUMwsg9uL|@$iZu!J~5nwMo5bJmMKO^Q3H;BzGNV`ZnF; t8JUJ36a{I1R78A-t= min_check_size) + + if max_check_size is not None: + query = query.filter(InvestorTable.check_size_upper <= max_check_size) + + if geography: + query = query.filter(InvestorTable.geographic_focus.ilike(f"%{geography}%")) + + if min_aum is not None: + query = query.filter(InvestorTable.aum >= min_aum) + + if max_aum is not None: + query = query.filter(InvestorTable.aum <= max_aum) + + # Filter by sector if provided + if sector: + query = query.join(InvestorTable.sectors).filter( + SectorTable.name.ilike(f"%{sector}%") + ) + + investors = query.all() + + # Transform to InvestorData format + investor_data_list = [] + for investor in investors: + investor_data = InvestorData( + investor=investor, + portfolio_companies=investor.portfolio_companies, + team_members=investor.team_members, + sectors=investor.sectors, + ) + investor_data_list.append(investor_data) + + return investor_data_list + + +@router.get("/investors/{investor_id}", response_model=InvestorData) +def read_investor(investor_id: int, db: Session = Depends(get_db)): + """Get a specific investor by ID""" + investor = ( + db.query(InvestorTable) + .options( + selectinload(InvestorTable.portfolio_companies), + selectinload(InvestorTable.team_members), + selectinload(InvestorTable.sectors), + ) + .filter(InvestorTable.id == investor_id) + .first() + ) + + if not investor: + raise HTTPException(status_code=404, detail="Investor not found") + + # Transform to InvestorData format + return InvestorData( + investor=investor, + portfolio_companies=investor.portfolio_companies, + team_members=investor.team_members, + sectors=investor.sectors, + ) + + +@router.post("/investors", response_model=InvestorData) +def create_investor(investor: InvestorCreate, db: Session = Depends(get_db)): + """Create a new investor""" + db_investor = InvestorTable(**investor.dict()) + db.add(db_investor) + db.commit() + db.refresh(db_investor) + + # Reload with relationships + investor_with_relations = ( + db.query(InvestorTable) + .options( + selectinload(InvestorTable.portfolio_companies), + selectinload(InvestorTable.team_members), + selectinload(InvestorTable.sectors), + ) + .filter(InvestorTable.id == db_investor.id) + .first() + ) + + # Transform to InvestorData format + return InvestorData( + investor=investor_with_relations, + portfolio_companies=investor_with_relations.portfolio_companies, + team_members=investor_with_relations.team_members, + sectors=investor_with_relations.sectors, + ) + + +@router.put("/investors/{investor_id}", response_model=InvestorData) +def update_investor( + investor_id: int, investor: InvestorUpdate, db: Session = Depends(get_db) +): + """Update an existing investor""" + db_investor = ( + db.query(InvestorTable).filter(InvestorTable.id == investor_id).first() + ) + if not db_investor: + raise HTTPException(status_code=404, detail="Investor not found") + + update_data = investor.dict(exclude_unset=True) + for field, value in update_data.items(): + setattr(db_investor, field, value) + + db.commit() + db.refresh(db_investor) + + # Reload with relationships + investor_with_relations = ( + db.query(InvestorTable) + .options( + selectinload(InvestorTable.portfolio_companies), + selectinload(InvestorTable.team_members), + selectinload(InvestorTable.sectors), + ) + .filter(InvestorTable.id == investor_id) + .first() + ) + + # Transform to InvestorData format + return InvestorData( + investor=investor_with_relations, + portfolio_companies=investor_with_relations.portfolio_companies, + team_members=investor_with_relations.team_members, + sectors=investor_with_relations.sectors, + ) + + +@router.delete("/investors/{investor_id}") +def delete_investor(investor_id: int, db: Session = Depends(get_db)): + """Delete an investor""" + db_investor = ( + db.query(InvestorTable).filter(InvestorTable.id == investor_id).first() + ) + if not db_investor: + raise HTTPException(status_code=404, detail="Investor not found") + + db.delete(db_investor) + db.commit() + return {"message": "Investor deleted successfully"} diff --git a/app/command.py b/app/command.py new file mode 100644 index 0000000..952d47c --- /dev/null +++ b/app/command.py @@ -0,0 +1,46 @@ +from sqlalchemy.orm import Session +from db.models import InvestorTable +from db.db import get_db + +def update_stage_focus_values(): + """Update existing stage_focus values from lowercase to uppercase""" + db = next(get_db()) + + try: + # Mapping of old lowercase values to new uppercase values + stage_mappings = { + 'seed': 'SEED', + 'series_a': 'SERIES_A', + 'series_b': 'SERIES_B', + 'series_c': 'SERIES_C', + 'growth': 'GROWTH', + 'late_stage': 'LATE_STAGE' + } + + updated_count = 0 + + for old_value, new_value in stage_mappings.items(): + # Update records with the old value + result = db.query(InvestorTable).filter( + InvestorTable.stage_focus == old_value + ).update( + {InvestorTable.stage_focus: new_value}, + synchronize_session=False + ) + + updated_count += result + print(f"Updated {result} records from '{old_value}' to '{new_value}'") + + db.commit() + print(f"Successfully updated {updated_count} total records") + + except Exception as e: + db.rollback() + print(f"Error updating stage_focus values: {e}") + raise + finally: + db.close() + +# Run the update +if __name__ == "__main__": + update_stage_focus_values() \ No newline at end of file diff --git a/app/db/__pycache__/models.cpython-312.pyc b/app/db/__pycache__/models.cpython-312.pyc index 9d8aec7ee72f216c7a700f5c510cb96ba2714351..5076c67022d5d29b7f3e8c166ab3c5b08beb8cc8 100644 GIT binary patch delta 653 zcma)(yH5f^5Qp8voFJTs4^ALr;z>f#U_cQmRD5e?Wupxta0O8wSoE*x(Tyj{=hb-CjpDC9xefxDOix7zSnm2cQBepa+-}*Y4y( z2H_I046FdF06OMtz&Ow$4LKeY0nby*IVRpc)srN$TM})uq}a0a{NE0x$YB%FZw~Yt zkd><@8xu_}-O@>lP~UAlA@dEbSU$39^$O3{tz4;1EQHErw}EIzFx?3LF|UMfghz0a zyrvO#j0G?9gxJ^f8vRP_qkf(d&-!VahDqJfn>0e|fo`=|`gYEjDuu%XYmx5>;ca%K F+z%|YaWen_ delta 694 zcmb7>yGz4B9LJl~#?~0?(kco*>Yx=wrKPRU%~#ce;NoJ9=}`+!qnG$x>SK5C{t}6} z=;Gkyq=Q2c{3~>DQ15$!P|?9aK78+&yWf58@)UcGsdt*z;Nws1{qkHrkW)4x1utk5 zS|WB{|4!@Q$?%jSwRW8iuc53vByT%0kL`>DalsV zXxkW~A5egCpdOfDr)qLy3UL~k0cL?Y0OzJzAP)R+gbPiu5B2^_F~J@iH>;zl<~VBH zgi(@LbMZzR!IUV_0{m@soyWg0-67y$EjnnC<=bC9BGwiGrr@pCbb qx6&zg6F$s{`xXZlTH=<)F~zFdPH(|+(<*G2$s}E4`_0dFX!i-jor_`s diff --git a/app/db/models.py b/app/db/models.py index 7256a0f..4d2ce86 100644 --- a/app/db/models.py +++ b/app/db/models.py @@ -9,12 +9,12 @@ from db.db import Base class InvestmentStage(enum.Enum): - SEED = "seed" - SERIES_A = "series_a" - SERIES_B = "series_b" - SERIES_C = "series_c" - GROWTH = "growth" - LATE_STAGE = "late_stage" + SEED = "SEED" + SERIES_A = "SERIES_A" + SERIES_B = "SERIES_B" + SERIES_C = "SERIES_C" + GROWTH = "GROWTH" + LATE_STAGE = "LATE_STAGE" # Association table for many-to-many relationship between investors and companies diff --git a/app/py_schemas.py b/app/py_schemas.py index 2118bfd..baebe92 100644 --- a/app/py_schemas.py +++ b/app/py_schemas.py @@ -1,16 +1,17 @@ -from pydantic import BaseModel from datetime import datetime -from typing import List, Optional from enum import Enum +from typing import List, Optional + +from pydantic import BaseModel class InvestmentStage(str, Enum): - SEED = "seed" - SERIES_A = "series_a" - SERIES_B = "series_b" - SERIES_C = "series_c" - GROWTH = "growth" - LATE_STAGE = "late_stage" + SEED = "SEED" + SERIES_A = "SERIES_A" + SERIES_B = "SERIES_B" + SERIES_C = "SERIES_C" + GROWTH = "GROWTH" + LATE_STAGE = "LATE_STAGE" class SectorSchema(BaseModel): @@ -64,6 +65,7 @@ class InvestorSchema(BaseModel): class InvestorData(BaseModel): """Comprehensive investor data schema for LLM processing""" + investor: InvestorSchema portfolio_companies: List[CompanySchema] = [] team_members: List[InvestorTeamMemberSchema] = [] @@ -71,7 +73,7 @@ class InvestorData(BaseModel): class Config: from_attributes = True - + class InvestorList(BaseModel): - investors: List[InvestorData] \ No newline at end of file + investors: List[InvestorData] diff --git a/app/services/__pycache__/openrouter.cpython-312.pyc b/app/services/__pycache__/openrouter.cpython-312.pyc index ff9a0fb97b0d855486ae85270f9e679bdf1c2da8..e0e2bde3880ddee85b11e5f46d1f7baeb5458bb5 100644 GIT binary patch delta 351 zcmX?-w>6*lG%qg~0}zC;Y|lvD$opEC(PHyY-3~@cuM$y^I0&S$l`unC3=B0alM7@d zCU4OIWN?lkgc@des2`Kr+}M)k=x#uvoy@CaTI(O%=c zfqk{l3cDRr7ld4{@VHL)G0{(wXW;W;{J_jC$@Sxd6a%mBj~hHjzluG9`db{$T%;He zTJgK^a~$I20kxrHq1;uNhyMyvIa8QI>(vhw%e5 zvn1D#4-yQ#x<79482u{t09w`JXy(Gic+iU9*_`7LCl`=?NSM*thUJimB#5o22_z4T zGdkO`9F~v(u?-kq7+4M)@_^WOj6m`T1CZS8YbwpCrNSzHLr7%0&P1IL%pkUoK7@Aj k1k(>Vc{_40a!P+;RoOhrOoWj?fQ3=~69b4W5(l~k0KOkbY5)KL diff --git a/app/services/__pycache__/querying.cpython-312.pyc b/app/services/__pycache__/querying.cpython-312.pyc index f3237606f10202ccfa1f8318df891adbe98c8482..73076b681ef5736c6043732be020a6105e62822b 100644 GIT binary patch delta 610 zcmY*U&ubGw6rRauH`%1?CabBbq6SPG7h6;5K|x6o8dM5tDHgE^Vabl$#7#E5*@)Gf zc=EO#J?g<*?L`Raf1n4Cy)Ah2>dlk%;Jl6X$2)wy_r34?-psRe-_IIf3?oNmy;44I ze~ye9`Xj2fPf+3#SNF}nxn)w4A}rmjL~qX6McHP1RW$rxZhoImn62Zi*t4T=wNfcX zZW6cfoVdmP)bHiAs8Em6@h%M%-CIiPU|DGD6*?^%>RuHe6I*NR&DO@+=AGt7`^Nh1 z_GeL~0<;{z-PmH$m zPe^7G!nraDfCZGzN_bKgj$RUte23P>+x(~bnj~YbJ9rd4WJejx$Yo{5Fc@(5H$=Rg z@}%u>>e}KZlqfuepyX19eR zUZkhqmO&7~t57@!dh{rWXD@qD@5M_`qJ{R&`ZI@*Z|0l%zWFip@{0Z0wnv$&)!gyU zm)I`zpK*P&b-06o9akGQL|TzKU9qgS?FDlzt<-(W!OXJ zItR?nzt4~zW34Hi<{c}On2-ED8AbsN4|t#%Ksui3xA_z<8Asg0uSUtLs38~xW#nQd z{V6fp9qC6<#K&S?6i6)MJMos6hbtq;oEPx7d3lb$Z|HD?jiJ(YwP3u9NmbnzZX1{ z|5oqQ%v5=Uzz=1psrFCS;aEqVosQjY-lP>Ri~#6SxI<7Qc%a~jL6dkImM50Sr}pzo zI;6f)N<_3j==vQXb7xjzV*e9=nfJWF-R;X|*uazVn)Z#|!in4yoi;82oY8&?