From 7b58834316866fe78ac1d2ab70c58bc8df6d58a5 Mon Sep 17 00:00:00 2001 From: bolade Date: Tue, 2 Sep 2025 15:51:35 +0100 Subject: [PATCH] Refactor investor-related schemas and models; update database configuration and enhance investor processing logic --- app/__pycache__/main.cpython-312.pyc | Bin 2264 -> 2307 bytes app/__pycache__/py_schemas.cpython-312.pyc | Bin 0 -> 3364 bytes .../pydantic_schemas.cpython-312.pyc | Bin 1640 -> 1640 bytes app/__pycache__/schemas.cpython-312.pyc | Bin 885 -> 3361 bytes app/api/__pycache__/investors.cpython-312.pyc | Bin 481 -> 473 bytes app/api/investors.py | 4 +- app/db/__pycache__/db.cpython-312.pyc | Bin 1623 -> 1625 bytes app/db/__pycache__/models.cpython-312.pyc | Bin 0 -> 4470 bytes app/db/db.py | 2 +- app/db/models.py | 22 +- app/main.py | 7 +- app/py_schemas.py | 77 +++++++ .../__pycache__/openrouter.cpython-312.pyc | Bin 8681 -> 14148 bytes app/services/openrouter.py | 193 ++++++++++++++---- app/services/querying.py | 4 +- 15 files changed, 258 insertions(+), 51 deletions(-) create mode 100644 app/__pycache__/py_schemas.cpython-312.pyc create mode 100644 app/db/__pycache__/models.cpython-312.pyc create mode 100644 app/py_schemas.py diff --git a/app/__pycache__/main.cpython-312.pyc b/app/__pycache__/main.cpython-312.pyc index 6d7f7be4f30b04b061da4f639c9e031a8a6d3e58..87493374378a71fc83428a114b6e289f52113ac6 100644 GIT binary patch delta 772 zcmX|9F>ljA6uxsVcAVr$(ng^PDFq4?3zV@esH()uijZ!JoZJN*Y$v>PD|J9cY9%Bl zA`dKx8L49>2L1p@T{?mRfy5u^1c-@y4(*ft{Jr;m_ujo{e`@S4@FUL4#MqyccaNUW zczgFXCFB*6MADFm`I`#M#8y~{m|zrhg|Hae!j2r_djLSh^s;P>liibNH58@!{OF!`kzE){QcWEsZ*8T2+Xhrih-6)gkvGoA(bSu=jx_Q{rl9godhUPl>jKG$ky5Z0bo zuI18x5N|7;OhAKY=qu^WetNeHU4t`w>c`U8(7~YKPXj0*D#}4m4gjk`5fjeH*9p=# zrf>u(rT3in#e5?TtzbJaJL%;OgZN>7Ww@2@IhR|w){IRjI?3nD3I>3ezIVE}aobQw zhnx6Yx)V+0R;V^$!-V)l=�|6ey)%i2IW)ek1Mgq?hv2n)8|P{o0A0u9x00Eb$K( C^R;RK delta 760 zcmYjPy>1gh5Z=B2vwi-_DREAA6cUKY2`LRoG=TC5GzjS$%l59p;eO2Q*^-5jkw`^> zu&$s%yg;b1M8OMC(4+%h6e$ltks2Ci&%|P+oBd|KZ)a}i?)BQwhV$KVN(Alg*Dp_} zbH|&#p@h6Aj4&FKK%PoSBPCGiGN%R_=G0J+jKC;zT4+XAU`2LdN2Q=tcfDZ?eX-eL|pB%;p6izK?fwC;LDT>oj**ufzC= z$Bf7QY4(TCXf=DGJZd+vUlkwWpnF9wD(<_-r{%3vZ3}9PU&=SiEhO^t2$DV*qSF_X zTw}w2>YK2J47idu`$?SgIL#F{$nAgH1fVHN3c|$m+R~sdbM~kUg3D@f17mlwsUOl{ z9bs|DSHPAs=c?`VF^8$IEnDtM+3uBAXozI(W#w8S?F{1y7ij_la4BRb`)s_NwV;JK zSG%Jjs1g~YZp^v@JQ?V$cffietL5gPD^e-bF#>}MBd`>y;9P?Jus=(lw*O4vB$ n58ytA^4AbQ&|RvO(lcV86Yq?;=VUv(ZQWEZt*mQ()a32IoDQ`9 diff --git a/app/__pycache__/py_schemas.cpython-312.pyc b/app/__pycache__/py_schemas.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6ba64591e2c89ce78200ef5d7d25ff4bdf90ec2 GIT binary patch literal 3364 zcmbVO&2Q936t{Q1w)Zn1B#;D>ENS|&DrG@5l~8G`K$avl2`Wj2*s8J|dy-hJ?d{l0 zw471}q|#G3k#lc|dyoAGdLi1&b|ln8PrX!avsEQdeQ&&R)(a?6E&2J)oAG?S-|xLQ zKWDQk0iJ7r{kZW*LJ)q##=#@EmHnSUc_NsCDOLnqEQ=!Qu}aL2m1A6wSK_u*mh411 z!P`DzHJ#!~d2FNv3L=quWH&SW44u z*Q`}g&uH2=HNDdAk(NuP8Ky3mZq1jLwdqj18fw>=JbUZr?Ugx}T9{rbY0E3qv!$oP zJ|6C6skq_Vr07;^o4ThvwpE;QjT-LWE8ceL-QwcsGezC;T}RurC|RShpQ2u^7OQp5 zGd75=d*`clnuNKyP(bnWARY+Kbl=uYvuAi~u0v!8x2`w)M!&s5M-G08xV%jnVgDoO zK6Lq)0*MpJ6t94lBtVOy#^H|x{L-@+G~p@B^+Z8tnPp=5E?wpuWHHNR61pq0QV4D@ z{CBt#f?JOj;xA8h&T;;l>#SMpD@#u$isNY?ihdBRXN|hHru#m%R%<@-=rDHi)1AhC zIrx>Hg~kB9`{*L@&dAuKo?sZVYw4-dJ#pM}eAc6By5qRMjx_ZE{kZ2-ihp#iLr2ku z0{7>g1o2R4s_E_7W-7Zq|F0w_#{U*T1Z(2Jh&w{Ze-Ax}?s&c9IRx<#42f$piQ~}$ zoJ6~wXt(8dTWPnIuC|J84kphAA((1CUC6NPHP^1{PCY=Aj=@+u4uYwcW7fbOb*5Ha zgL{Z&*4&z75>u-aoib&Uta_GDSjwOTLdyi4SgKaVJb-${14j{JAhM5;5p)7K6a_s1 z=n{1B&bM&{X9S13knDn*zKa9iLs3A1nV2FVuLb&j>_9ODuXh7PC(zAY-}Yi4`bTyz zJ^r8}jW@G{JI1508`4O#cW8HLuXmy$O?0T;BBWIb2tVYvezzY?BllXwQTc!6bcy=Jps$j8QA&9m+k zt>SJH8nM)>RkW-Vcb)3h4a?BhpcHv5&Ff*iF$gspIHI|0niUpWk6r+G(Tm;QJc=KK z(Y+so6*I8JC%9G@D+;OOgfy>`Z;b1Yab6U781D{<2#NZJci(zE+K|SY$;|d#^W<n2!5E$<(5P!x+QG6kcy%0u!70$j823xW$ zj_nw`AGQQgTKx%eeCKjYfKRJ84f=dbfKTg;B98CYTLOGq139qYX$kOYO{h5j9|1nx GhyMYdr0j73 literal 0 HcmV?d00001 diff --git a/app/__pycache__/pydantic_schemas.cpython-312.pyc b/app/__pycache__/pydantic_schemas.cpython-312.pyc index 761fc876e847fe16ff24ae90e532fabf0d47fd75..f3767d2ff80017c58cc5b8db4ce4463e3457fadc 100644 GIT binary patch delta 20 acmaFC^MZ%_G%qg~0}%ZDux%rEEE@nuLI$A# delta 20 acmaFC^MZ%_G%qg~0}$+%+_aH9mJI+uas`M0 diff --git a/app/__pycache__/schemas.cpython-312.pyc b/app/__pycache__/schemas.cpython-312.pyc index f60949db15b9c38669ff4631de3cd2a7fe72f3cf..426200d1870fcc1a053dfbc9d01830ad95b20b9d 100644 GIT binary patch literal 3361 zcmbVO&2Q936t{Q1w)Zn1B#;D>ENS|&DrG@5l~8G`Ktd9l1eK&hY*kr~J)2mp?d{l0 zw471}q|#G3k#lc|dyoAGdLi12I}+-lr(UYI*{Tw!zBk@%)(a?6E&2J)oAG?S-|xLQ zKWDQk0iJ7r{kZW*LJ)q##=#@El>MJTc_NsCDOLnqEQuoOu}aL2m110vSK_u*lI%n& z!P`?u1PA;P)*$@zGag_j7f`@=QH(Y)wf(n z$6jgHso6%jxloF&#{MKIPXr>AL{limOtBOc#N^Mj3bjP-eGpp|b>w>Rj>!7mYixA-FLe+1o! z{{B)RaUz-G6|j;7Xff0{_Hn>odKQBwJSDlFD99|cLJZ%fD|~}2W|>SvUqx1GBijrA z9j=7P)?mAP_h_}IzxF(Z09v#3* zwAzVQTW+I|UQ>~{98J4}~+Ev}DhiK9<7)!@NFx7I*8n~m*)QW3x z53x+ytvMz!wK~x$Q#Q$(XZeJs3`!ubOu&hyYE?`Es7E|-6d?v8`)D(QPT+>3poaim zf)3vKHjdzo;7}KmT~O0^alm^h3MeoQQv~F-K);V2D2CwmZh+_nI>_~HFNLCiWcSkJ z4;s>VkR9AH9(~=AMuOg<-J!kSiH0=Mp>~r4Cy%(MO%7b`|B!=&%d0{gT$$&}p)gmv zbd~7#60z4vD{4D%mZ)1HECIR3szkPR6k_9Vn*12xU51mMgO_%JN8dqX7u-{5LxG(< z{Y4W#$O^QYCHM|=sD8SGC7E_FcvXIhOvlR7=@^X@rP+Mj64N29&{wxToD)lRpgGXtqM+XgUM&lP%D?!d>dglF0$oEykp+EyiG`Fl-?JDch3*at#vD=$R@nbN$ z_hYbP2A22)*Xm+LA$6RP=2h~Ias4sQivkbh-2o9HQQz?HTaQN@(pZqpY|jTLha1vx zME2zyQa(66hC_}O=D61D*;j#{PWE9@Boax4Wp^FQEKx|vmxRf=2eu^GQk+9@B|^@C zEe*Cz%f|W06M+moQrbMq)>XLZWDc;H(S7~?6y{n=Hi+X{_eeg3+NF6&+s@UlP2C3yz}9 ziOy{vgD{KWhk1BkAxy4|Fig=j(={}WQy?q`{71N>hv}X!pvF@Trs1&QPojnnKD=?z z>)1pw4X=lkeJK1QeHP@-G^8^@@#1so)b7Bpy*GTWAzchEO>HgiOg=wV-0Q#4kfwr( z$*sBXR(H(ES&ay%h6-JRIh~h-B0NkFaZMNeFfsrxG0J*gs~UG=&!=r7s6mumc_9h zWB0?R07|nzA&&1{ZVK>e_NGB!XbSLYo>9c{-Fj1iPjes#_B%}hKFtXg$NwY1hx_n9 D&J61& literal 885 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 T073K-^`xFCy^nAx%jW+8*hSa1 delta 2688 zcmY)wZA@F&^}hG)=MSEZ3FgBX;ukOwdW*QoH@AcCMcZ z>`FfGeBE=;J?GqW{O9!7gYAFyc-#oqKm8lY-v|)daxyS_E zu3dlsNKAGtJk7(wwh6cFQd~*}Tn@<&nVGi9Zr}JsO5MU>{|aAppxk^x+0TAJ4k@sgy?E4tXBq~?spvfpqKEE>v!Ax^7`ChJ{dw8To! zr=}FF)BC@Y*5@%;)U^GIq69p8hJv-uMN!cc(wriTQd$-hnY1B+N9jxn#TiXZBqb~* z3p?WPyvKc6tbLpiQ#DeR$gFUeJmR#_;}ItM@$MRJOLO)#DDxSCdtczmgW ztoo{(%G3d>cn83{sOa_w23BlWJgW9;2u%ls;8NzIVtRJ38`o&XyIxsHD5av0cyKE<6QQ7!tXFAZ ztNuX0ZwMIr5`)mc(0AYszcG2>5!1XEwnX_wff_{SyoV&p{E95E2wAEmA+ToxeMBGNc3&k8NkovC>iHiOlj{?n4k;~Py za^@cm?OQcX*CtmdxBQLQMpj3*f-Qw$Pd?bQ)zn&OI+AZXveg(aG)D7{(XG1BwcKj% zna2_IuAD3Skf*93H0FiIf*|GvaV_{*=q^@+*`go0JOyWc-dSI8hVstPW9Om&=(NUv zakBno1IHv-@@1$_UglAVKkW^O>VxbZr07NoY@j;tRlGn<|CnP zkD6XfewwOl}RXzHvnD?k6=(*QfI} ztwnp(o^XRG*s?Z|Z|K~i!lKNy*lTY!|Do%4R}levzpwOp_`(jQio=Z0-h6B5ZvE}i zA_DZDu|q$_aFuSy+_z0UHIIv6>C4H6rueenGvO|1jy8c1p8WlhQLVZ`E`>k-(G%?V5t-&I!Q47S7^oLGR2<9>A%hssf;|YDKFz+gMjuNy$e8*V;JTO List: + async def _process_batch( + self, batch: pd.DataFrame, batch_idx: int + ) -> List[InvestorData]: """Process a single batch of data""" # Convert batch to string representation - clean the data batch_str = "" @@ -102,25 +112,101 @@ Return the data as a structured list of investors.""" print(f"Error processing batch {batch_idx + 1}: {e}") return [] - async def _save_to_sql(self, investors: List[Investor]) -> None: - """Save investors to SQL database""" + async def _save_to_sql(self, investor_data_list: List[InvestorData]) -> None: + """Save investors and related data to SQL database""" if not self.sql_session: return - # Implement SQL saving logic here - for investor in investors: - db_investor = InvestorTable( - name=investor.name, - aum=investor.aum, - check_size=investor.check_size, - sector_focus=investor.sector_focus, - stage_focus=investor.stage_focus, - region=investor.region, - ) - self.sql_session.add(db_investor) - self.sql_session.commit() + try: + for investor_data in investor_data_list: + # Save investor + db_investor = InvestorTable( + name=investor_data.investor.name, + description=investor_data.investor.description, + aum=investor_data.investor.aum, + check_size_lower=investor_data.investor.check_size_lower, + check_size_upper=investor_data.investor.check_size_upper, + geographic_focus=investor_data.investor.geographic_focus, + stage_focus=investor_data.investor.stage_focus, + number_of_investments=investor_data.investor.number_of_investments, + ) + self.sql_session.add(db_investor) + self.sql_session.flush() # Get the ID - async def _save_to_vector_db(self, investors: List[Investor]) -> None: + # Save sectors and create associations + for sector_data in investor_data.sectors: + # Check if sector exists, create if not + existing_sector = ( + self.sql_session.query(SectorTable) + .filter(SectorTable.name == sector_data.name) + .first() + ) + + if not existing_sector: + db_sector = SectorTable(name=sector_data.name) + self.sql_session.add(db_sector) + self.sql_session.flush() + # Add sector to investor's sectors + db_investor.sectors.append(db_sector) + else: + # Add existing sector to investor if not already there + if existing_sector not in db_investor.sectors: + db_investor.sectors.append(existing_sector) + + # Save companies and create portfolio associations + for company_data in investor_data.portfolio_companies: + # Check if company exists, create if not + existing_company = ( + self.sql_session.query(CompanyTable) + .filter(CompanyTable.name == company_data.name) + .first() + ) + + if not existing_company: + db_company = CompanyTable( + name=company_data.name, + industry=company_data.industry, + location=company_data.location, + founded_year=company_data.founded_year, + website=company_data.website, + ) + self.sql_session.add(db_company) + self.sql_session.flush() + + # Add to investor's portfolio + db_investor.portfolio_companies.append(db_company) + else: + # Add existing company to portfolio if not already there + if existing_company not in db_investor.portfolio_companies: + db_investor.portfolio_companies.append(existing_company) + + # Save team members + for team_member_data in investor_data.team_members: + # Check if team member exists + existing_member = ( + self.sql_session.query(InvestorTeamMember) + .filter(InvestorTeamMember.email == team_member_data.email) + .first() + ) + + if not existing_member: + db_team_member = InvestorTeamMember( + name=team_member_data.name, + role=team_member_data.role, + email=team_member_data.email, + investor_id=db_investor.id, + ) + self.sql_session.add(db_team_member) + + self.sql_session.commit() + print(f"Successfully saved {len(investor_data_list)} investors to database") + + except Exception as e: + self.sql_session.rollback() + print(f"Error saving to SQL database: {e}") + raise + + async def _save_to_vector_db(self, investor_data_list: List[InvestorData]) -> None: """Save investors to vector database""" if not self.vector_db_client: return @@ -129,19 +215,47 @@ Return the data as a structured list of investors.""" metadatas = [] ids = [] - for i, investor in enumerate(investors): - doc_text = f"{investor.investor_description}\nInvestment Thesis: {investor.investment_thesis}" + for i, investor_data in enumerate(investor_data_list): + investor = investor_data.investor + sectors = ", ".join([s.name for s in investor_data.sectors]) + companies = ", ".join([c.name for c in investor_data.portfolio_companies]) + + doc_text = f""" + Investor: {investor.name} + Description: {investor.description or "N/A"} + AUM: ${investor.aum:,} + Check Size: ${investor.check_size_lower:,} - ${investor.check_size_upper:,} + Geographic Focus: {investor.geographic_focus} + Stage Focus: {investor.stage_focus.value} + Sectors: {sectors} + Portfolio Companies: {companies} + """.strip() + documents.append(doc_text) - metadatas.append({"name": investor.name}) - ids.append(f"investor_{i}_{investor.name.replace(' ', '_')}") + metadatas.append( + { + "name": investor.name, + "stage_focus": investor.stage_focus.value, + "geographic_focus": investor.geographic_focus, + "aum": investor.aum, + } + ) + ids.append( + f"investor_{i}_{investor.name.replace(' ', '_').replace('/', '_')}" + ) if documents: - # Use add method with proper parameters - self.collection.add(documents=documents, metadatas=metadatas, ids=ids) + try: + self.collection.add(documents=documents, metadatas=metadatas, ids=ids) + print( + f"Successfully saved {len(documents)} investors to vector database" + ) + except Exception as e: + print(f"Error saving to vector database: {e}") async def process_csv( self, df: pd.DataFrame, batch_size: int = 10, max_concurrent: int = 10 - ) -> List: + ) -> List[InvestorData]: """Process CSV data in parallel batches and save to databases""" results = [] @@ -172,6 +286,7 @@ Return the data as a structured list of investors.""" # Save to databases if results: + print(f"Successfully processed {len(results)} investors") await self._save_to_sql(results) await self._save_to_vector_db(results) diff --git a/app/services/querying.py b/app/services/querying.py index e253b66..c15dff9 100644 --- a/app/services/querying.py +++ b/app/services/querying.py @@ -6,7 +6,7 @@ from langchain_community.agent_toolkits import SQLDatabaseToolkit from langchain_community.utilities import SQLDatabase from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent -from pydantic_schemas import Investor, InvestorList +from py_schemas import InvestorList from settings import settings # Connect to SQLite @@ -29,7 +29,7 @@ class QueryProcessor: api_key=settings.OPENROUTER_API_KEY, base_url="https://openrouter.ai/api/v1", model="google/gemini-2.5-flash-lite", - temperature=0, + temperature=0.3, ) self.toolkit = SQLDatabaseToolkit(db=db, llm=self.llm) self.agent = create_react_agent(