
    @j                        d Z ddlmZ ddlZddlZddlmZ ddlmZ ddl	m
Z
 ddlmZ ddlmZ  ej        e          Z G d	 d
          ZdS )uK   LSEG bond analytics service — orchestrates data loading and risk scoring.    )annotationsN)Optional)settings)LsegDataLoader)compute_bond_risk)BondRiskResultc                  |    e Zd ZdZ	 	 dd d	Zd
 Zd!dZd"dZd"dZe	d#d            Z
d$dZd%d&dZd%d'dZd%d(dZdS ))LsegBondServiceu  
    Loads LSEG bond data and computes risk scores.

    Data source priority:
      1. LSEG API (if LSEG_APP_KEY is set) — fetches live data for ISINs in LSEG_ISINS
      2. File fallback (LSEG_DATA_PATH) — reads downloaded Excel/CSV files from disk
    Napp_keyOptional[str]isinsOptional[list[str]]issuers	data_pathc                   |pt           j        | _        |pt           j        | _        |pt           j        | _        t          |pt           j                  | _	        d | _
        d | _        d| _        t          j                    | _        d S )NF)r   lseg_app_key_app_key	isin_list_isinsissuer_list_issuersr   lseg_data_path_file_loader_api_client_cache_loading	threadingLock_lock)selfr   r   r   r   s        !/app/app/services/bond_service.py__init__zLsegBondService.__init__   sp    88#81x178#7*9+O8OPP,0^%%


    c                    | j         2ddlm}  || j        t          j        t          j                  | _         | j         S )Nr   )LsegApiClient)r   usernamepassword)r   app.providers.lseg_apir%   r   r   lseg_usernamelseg_password)r    r%   s     r!   _get_api_clientzLsegBondService._get_api_client$   sR    #<<<<<<,}!/!/     D
 r#   return
list[dict]c                &     j         ra j        rZt                              dt	           j                                                                                   j                  }n j         ra j        rZt                              dt	           j                                                                                   j                  }n3t                              d            j	        
                                } fd|D             S )NzLSEG: fetching %d ISINs via APIz$LSEG: searching bonds for %d issuersz0LSEG: loading from files (no App Key configured)c                :    g | ]}                     |          S  )_enrich).0br    s     r!   
<listcomp>z,LsegBondService._do_load.<locals>.<listcomp>8   s#    ---AQ---r#   )r   r   loggerinfolenr+   fetch_bondsr   fetch_bonds_for_issuersr   
load_bonds)r    raws   ` r!   _do_loadzLsegBondService._do_load.   s    = 	1T[ 	1KK93t{;K;KLLL&&((44T[AACC] 	1t} 	1KK>DM@R@RSSS&&((@@OOCCKKJKKK#..00C--------r#   Nonec                |   	 |                                  }| j        5  || _        d| _        d d d            n# 1 swxY w Y   t                              dt          |                     d S # t          $ rD | j        5  d| _        d d d            n# 1 swxY w Y   t                              d           Y d S w xY w)NFz!LSEG: bond cache ready (%d bonds)zLSEG: bond cache load failed)	r<   r   r   r   r5   r6   r7   	Exception	exception)r    results     r!   _background_loadz LsegBondService._background_load:   s?   		=]]__F & &$ %& & & & & & & & & & & & & & & KK;S[[IIIII 	= 	= 	= & & %& & & & & & & & & & & & & & &;<<<<<<	=sQ   A- 8A- <A- < +A- -B;>BB;B	B;B	B;:B;c                    | j         5  | j        | j        r	 ddd           dS d| _        ddd           n# 1 swxY w Y   t          j        | j        d                                           dS )z9Kick off a background fetch if one isn't already running.NT)targetdaemon)r   r   r   r   ThreadrB   startr    s    r!   start_background_loadz%LsegBondService.start_background_loadF   s    Z 	! 	!{&$-&	! 	! 	! 	! 	! 	! 	! 	! !DM	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	 5dCCCIIKKKKKs   88<<boolc                    | j         S N)r   rH   s    r!   
is_loadingzLsegBondService.is_loadingN   s
    }r#   bonddictc           
     H   t          |                    d          pd|                    d          |                    d          |                    d          |                    d          |                    d                    }i |d	|                                iS )
NisinUNKNOWNrating_moodys	rating_sprating_fitchz_spreadasset_swap_spread)rQ   rS   rT   rU   rV   rW   risk)r   get
model_dump)r    rN   rA   s      r!   r1   zLsegBondService._enrichR   s    !2&!!.Y((?33hh{++.11XXj))"hh':;;"
 "
 "
 5$4 1 1 3 3444r#   Fforce_refreshOptional[list[dict]]c                    |r&| j         5  d | _        d d d            n# 1 swxY w Y   | j         5  | j        | j        cd d d            S 	 d d d            n# 1 swxY w Y   |                                  d S rL   )r   r   rI   )r    r[   s     r!   get_all_bondszLsegBondService.get_all_bonds]   s    	# # #"# # # # # # # # # # # # # # #Z 	# 	#{&{	# 	# 	# 	# 	# 	# 	# 	#&	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	""$$$ts   ""AAArQ   strOptional[dict]c                    |                      |          }|d S |                                                                t          fd|D             d           S )Nr[   c              3  v   K   | ]3}|                     d           pd                                k    /|V  4dS )rQ    N)rY   upper)r2   r3   rD   s     r!   	<genexpr>z+LsegBondService.get_bond.<locals>.<genexpr>l   sH      QQ1!%%--*=2)D)D)F)F&)P)PQ)P)P)P)PQQr#   )r^   re   stripnext)r    rQ   r[   bondsrD   s       @r!   get_bondzLsegBondService.get_bondg   sb    """??=4##%%QQQQQQQSWXXXr#   c                    |                      |          }|d S |s	dd i ddd dS d |D             }i }d}|D ]?}|d         d         }|                    |d          dz   ||<   |d         d         r|dz  }@d	 |D             }d
 |D             }t          |          t          t	          |          t          |          z  d          ||t          |          |z
  |r-t          t	          |          t          |          z  d          nd dS )Nrb   r   )total_bondsavg_composite_scoregrade_distributioninvestment_grade_counthigh_yield_countavg_spread_bpsc                *    g | ]}|d          d         S )rX   composite_scorer0   r2   r3   s     r!   r4   z9LsegBondService.get_portfolio_summary.<locals>.<listcomp>|   s"    >>>1!F)-.>>>r#   rX   composite_grade   is_investment_gradec                b    g | ],}|                     d           p|                     d          -S )rV   rW   )rY   rt   s     r!   r4   z9LsegBondService.get_portfolio_summary.<locals>.<listcomp>   sF     
 
 
 EE*;':!;!;
 
 
r#   c                    g | ]}||S rL   r0   )r2   ss     r!   r4   z9LsegBondService.get_portfolio_summary.<locals>.<listcomp>   s    ???Qqr#   )r^   rY   r7   roundsum)	r    r[   ri   scoresgrade_countsig_countr3   gspread_valss	            r!   get_portfolio_summaryz%LsegBondService.get_portfolio_summaryn   so   """??=4 	 '+&(*+$%"&   ?>>>>') 	 	A&	+,A*..q!44q8LOy./ A
 

 
 
 @?+??? u::#(Vs6{{)BA#F#F".&. #E

X 5OZdeC$4$4s;7G7G$GKKK`d
 
 	
r#   )NNNN)r   r   r   r   r   r   r   r   )r,   r-   )r,   r=   )r,   rJ   )rN   rO   r,   rO   )F)r[   rJ   r,   r\   )rQ   r_   r[   rJ   r,   r`   )r[   rJ   r,   r`   )__name__
__module____qualname____doc__r"   r+   r<   rB   rI   propertyrM   r1   r^   rj   r   r0   r#   r!   r
   r
      s         TXQU	& 	& 	& 	& 	&     
. 
. 
. 
.
= 
= 
= 
=L L L L    X	5 	5 	5 	5    Y Y Y Y Y%
 %
 %
 %
 %
 %
 %
r#   r
   )r   
__future__r   loggingr   typingr   
app.configr   app.providers.lsegr   app.scoring.bondr   app.scoring.typesr   	getLoggerr   r5   r
   r0   r#   r!   <module>r      s    Q Q " " " " " "                  - - - - - - . . . . . . , , , , , ,		8	$	$C
 C
 C
 C
 C
 C
 C
 C
 C
 C
r#   