
    Yaj(                        d dl mZ d dlZd dlmZ d dlZd dlmZmZm	Z	m
Z
mZmZmZmZmZ d dlmZmZ d dlmZ d dlmZ d ZddZd dZd!dZd"dZd"dZd"dZd"dZd"dZd"dZ d"dZ!d!dZ"d!dZ#d"dZ$d"dZ%dS )#    )annotationsN)Path)	CandidateInputconfirm_candidatedeterministic_parse_docx#import_confirmed_candidate_snapshotqwen_extract_candidatesqwen_resolve_staged_candidatesresolve_candidatestage_docx_candidatesupsert_candidate)CatalogEntryInputupsert_catalog_entry)connect_memory)apply_migrationsc                     t                      } t          |            |                     d           |                     d           | S )NzINSERT INTO platforms(platform_id,name,platform_type,default_currency,created_at) VALUES('p1','PostFinance','broker','CHF','now')zINSERT INTO accounts(account_id,platform_id,account_name,account_type,currency,created_at) VALUES('a1','p1','Depot','brokerage','CHF','now'))r   r   execute)conns    /tests/unit/test_instrument_import_candidates.py
setup_connr      s^    DTLL  U  V  V  VLL  `  a  a  aK    pathr   lines	list[str]returnc                    d                     d |D                       }d| d}t          j        | d          5 }|                    d|           d d d            n# 1 swxY w Y   | S )N c              3  "   K   | ]
}d | dV  dS )z<w:p><w:r><w:t>z</w:t></w:r></w:p>N ).0lines     r   	<genexpr>zmake_docx.<locals>.<genexpr>!   s.      PP$=T===PPPPPPr   z<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>z</w:body></w:document>wzword/document.xml)joinzipfileZipFilewritestr)r   r   bodyxmlzfs        r   	make_docxr+       s    77PP%PPPPPD{\`{ { {C	s	#	# .r
'---. . . . . . . . . . . . . . .Ks   AA"%A"r   c                 ~    t          ddddddddd	dddd	d
          }|                    |            t          di |S )Nbroker.docxpostfinance1
Sample ETFSAMPr   CHF1.2345etfneeds_manual_review)source_fileplatformsource_row_numberraw_name
raw_tickerraw_isinraw_currencyraw_quantityraw_asset_classproposed_nameproposed_tickerproposed_currencyproposed_asset_classmapping_statusr   )dictupdater   )	overridesdatas     r   	candidaterH   )   sh    !"",  D  	KK	!!D!!!r   tmp_pathNonec                    t          | dz  dg          }t          |d          }t          |          dk    sJ |d         j        dk    sJ |d         j        dk    sJ d S )	NzPostfinance.docx"Sample ETF CH0000000001 CHF 12.345r.   r7      r   CH0000000001exact_isin_match)r+   r   lenr;   rC   )rI   docxrowss      r   ,test_deterministic_parser_creates_candidatesrT   >   sw    X 225Y4Z[[D#D=AAADt99>>>>7~----7!%7777777r   c                   i  G d d          fd}|                      d|           t                      }t          dgdd          \  }}|d	u sJ t          |          d
k    sJ dd         vsJ |d         j        dk    sJ t          ||d                    |                    d                                          d         d
k    sJ |                    d                                          d         dk    sJ d S )Nc                       e Zd Zd Zd Zd ZdS )Ntest_qwen_extraction_creates_only_candidates_no_bookings.<locals>.FakeResponsec                    | S Nr   selfs    r   	__enter__zXtest_qwen_extraction_creates_only_candidates_no_bookings.<locals>.FakeResponse.__enter__I       D[r   c                    dS NFr   r[   argss     r   __exit__zWtest_qwen_extraction_creates_only_candidates_no_bookings.<locals>.FakeResponse.__exit__J       %%r   c                    dS )Ns   {"choices":[{"message":{"content":"{\"positions\":[{\"name\":\"Q ETF\",\"ticker\":\"QETF\",\"currency\":\"CHF\",\"quantity\":\"1.23\",\"asset_class\":\"etf\",\"row_number\":2,\"confidence\":\"medium\"}]}"}}]}r   rZ   s    r   readzStest_qwen_extraction_creates_only_candidates_no_bookings.<locals>.FakeResponse.readK   s     _  _r   N__name__
__module____qualname__r\   rb   re   r   r   r   FakeResponserW   H   s:        (((///	_ 	_ 	_ 	_ 	_r   rj   c                R    | j                             d          d<                S Nzutf-8r(   rG   decodereqkwargsrj   captureds     r   fake_urlopenzNtest_qwen_extraction_creates_only_candidates_no_bookings.<locals>.fake_urlopenM   '    8??733|~~r   <jarvis_finance.imports.instrument_candidates.request.urlopenzQ ETF CHF 1.23z	True.docxtrue_wealth)r6   r7   TrN   z1.23r(   r   r   6SELECT COUNT(*) AS c FROM instrument_import_candidatesc&SELECT COUNT(*) AS c FROM transactions)setattrr   r	   rQ   r=   r   r   fetchone)monkeypatchrs   r   rS   	availablerj   rr   s        @@r   8test_qwen_extraction_creates_only_candidates_no_bookingsr~   F   sd   H_ _ _ _ _ _ _ _
      VXdeee<<D-/?.@kdqrrrOD)t99>>>>&)))))72%%%%T47###<<PQQZZ\\]`aefffff<<@AAJJLLSQUVVVVVVVr   c                     t                      } t          | t          ddd                    }t          | |          }|d         dk    sJ d S )Nr   r;   proposed_isinproposed_exchangerC   r5   )r   r   rH   r   r   cidresults      r   5test_candidate_without_isin_stays_needs_manual_reviewr   \   sW    <<D
4Bb\^!_!_!_
`
`CtS))F"#'<<<<<<<r   c                     t                      } t          | t          ddddddd          d	           t          | t	          dd
                    }t          | |          }|d         dk    sJ d S )Nr4   r0   rO   r1   SWXr2   )asset_classnameisintickerexchangetrading_currencyinstrument_currencytest)noter;   r   rC   rP   )r   r   r   r   rH   r   r   s      r   0test_candidate_with_isin_can_be_exact_isin_matchr   c   s    <<D0U\jsy  EJ  ]b  x}   ~   ~   ~  EK  L  L  L  L
4NR`!a!a!a
b
bCtS))F"#'9999999r   c            	     z   t                      } t          | t          dddd                    }t          j        t
                    5  t          | |dd           d d d            n# 1 swxY w Y   t          j        t
                    5  t          | |dddd	
           d d d            d S # 1 swxY w Y   d S )Nr   r1   )r;   r   r   r@   manual review confirms)r   r@   a1
2025-12-31noT
account_idsnapshot_dater   confirm)r   r   rH   pytestraises
ValueErrorr   r   r   r   s     r   ,test_ticker_alone_does_not_confirm_or_importr   k   sb   <<D
4Bb\^pv!w!w!w
x
xC	z	"	" \ \$*BTZ[[[[\ \ \ \ \ \ \ \ \ \ \ \ \ \ \	z	"	" } }+D#$Vbimw{||||} } } } } } } } } } } } } } } } } }s$   	A))A-0A-B00B47B4c                 X   t                      } t          | t                                }t          | |dddd           |                     d|f                                          }|d         dk    sJ |                     d	                                          d
         dk    sJ d S )Nr   r1   r   r2   )r   r@   r   rA   zLSELECT mapping_status FROM instrument_import_candidates WHERE candidate_id=?rC   	confirmedzDSELECT COUNT(*) AS c FROM audit_log WHERE action='confirm_candidate'rx   rN   )r   r   rH   r   r   r{   )r   r   rows      r   'test_manual_confirmation_sets_confirmedr   t   s    <<D
4
-
-CdC&>PVjo  DI  J  J  J  J
,,ehkgm
n
n
w
w
y
yC K////<<^__hhjjknostttttttr   c            	     (   t                      } t          | t          dd                    }t          | |ddddd           t	          | |dd	d
d          }|                     d|f                                          }|d         dk    sJ |d         dk    sJ |d         dk    sJ |                     d                                          d         dk    sJ |                     d                                          d         dk    sJ d S )NrO   r   r   r1   r   r2   )r   r   r@   r   rA   r   r   zapproved importTr   zWSELECT transaction_type, source_type, quantity FROM transactions WHERE transaction_id=?transaction_typeinitial_position_snapshotsource_typebroker_import_reviewed_snapshotquantityr3   zESELECT COUNT(*) AS c FROM alerts WHERE rule_id='cost_basis_uncertain'rx   rN   zVSELECT COUNT(*) AS c FROM audit_log WHERE action='import_confirmed_candidate_snapshot')r   r   rH   r   r   r   r{   )r   r   tx_idr   s       r   Ntest_confirmed_candidate_can_create_initial_snapshot_with_uncertain_cost_basisr   }   s   <<D
4NR`!a!a!a
b
bCdC&>nnt  IN  bg  h  h  h  h/cdZfm~  IM  N  N  NE
,,psxrz
{
{  E  E  G  GC!"&AAAAA}!BBBBBz?h&&&&<<_``iikkloptuuuuu<<pqqzz||  ~A  B  FG  G  G  G  G  G  Gr   c            	     L   t                      } t          | t                                }t          j        t
                    5  t          | |dddd           d d d            n# 1 swxY w Y   |                     d                                          d         dk    sJ d S )	Nr   r   blockedTr   ry   rx   r   )	r   r   rH   r   r   r   r   r   r{   r   s     r   5test_unconfirmed_candidate_cannot_productively_importr      s    <<D
4
-
-C	z	"	" B B+D#$Vbir  }A  	B  	B  	B  	BB B B B B B B B B B B B B B B<<@AAJJLLSQUVVVVVVVs   A&&A*-A*c                    t                      }t          | dz  dg          }t          ||gd          }|j        dk    sJ |                    d                                          d         dk    sJ d S )	NzTrue_Wealth.docxrL   T)dry_runrN   rw   rx   r   )r   r+   r   candidates_totalr   r{   )rI   r   rR   r   s       r   1test_stage_docx_candidates_dry_run_writes_no_rowsr      s    <<DX 225Y4Z[[D"4$>>>F"a''''<<PQQZZ\\]`aefffffffr   c                    | dz  dz  }|j                                          t          |dg           t          |d          sJ d S )Nrepor-   z Synthetic ETF CH0000000001 CHF 1r   rM   )parentmkdirr+   r   )rI   	repo_docxs     r   0test_no_real_data_in_repo_git_safety_placeholderr      sY    6!M1Ii<=>>>#I????????r   c                   t                      }t          |t          dd                    i  G fdd          fd}|                     d|           t	          |d	          }|j        d
u sJ |j        d
u sJ |j        dk    sJ dd         vsJ dd         vsJ |                    df          	                                }|d         dk    sJ d|d         v sJ d S )Nz999.123r   )r=   r   c                  &    e Zd Zd Zd Z fdZdS )Htest_qwen_resolver_sends_sanitized_candidates_only.<locals>.FakeResponsec                    | S rY   r   rZ   s    r   r\   zRtest_qwen_resolver_sends_sanitized_candidates_only.<locals>.FakeResponse.__enter__   r]   r   c                    dS r_   r   r`   s     r   rb   zQtest_qwen_resolver_sends_sanitized_candidates_only.<locals>.FakeResponse.__exit__   rc   r   c                r    dz  }dd l }|                    ddd|iigi                                          S )Na  {"updates":[{"candidate_id":"%s","proposed_name":"Sample ETF","proposed_ticker":"SAMP","proposed_isin":"","proposed_exchange":"","proposed_currency":"CHF","proposed_asset_class":"etf","mapping_status":"needs_manual_review","review_note":"Ticker-only candidate remains manual review."}]}r   choicesmessagecontentjsondumpsencode)r[   r(   r   r   s      r   re   zMtest_qwen_resolver_sends_sanitized_candidates_only.<locals>.FakeResponse.read   sQ     t  wz  zDKKK::yI	47H+I*JKLLSSUUUr   Nrf   r   s   r   rj   r      G        (((///	V 	V 	V 	V 	V 	V 	Vr   rj   c                R    | j                             d          d<                S rl   rm   ro   s     r   rs   zHtest_qwen_resolver_sends_sanitized_candidates_only.<locals>.fake_urlopen   rt   r   ru   zqwen-3.6-agent)modelTr   r(   r=   zYSELECT mapping_status, review_note FROM instrument_import_candidates WHERE candidate_id=?rC   r5   zmanual reviewreview_note)
r   r   rH   rz   r
   qwen_available	qwen_usedconfirmed_afterr   r{   )r|   r   rs   r   r   rj   rr   r   s        @@@r   2test_qwen_resolver_sends_sanitized_candidates_onlyr      s   <<D
4	UW!X!X!X
Y
YCHV V V V V V V V V V      VXdeee+D8HIIIF D((((t####!Q&&&&HV,,,,,&!11111
,,ruxtz
{
{  E  E  G  GC $99999c-0000000r   c                x   t                      }t          |t          ddd                     G fdd          |                     dfd           t	          |          }|j        dk    sJ |                    df                                          }|d	         dk    sJ |d
         dk    sJ d S )Nr   r   c                  &    e Zd Zd Zd Z fdZdS )Ftest_qwen_resolver_does_not_accept_invented_isin.<locals>.FakeResponsec                    | S rY   r   rZ   s    r   r\   zPtest_qwen_resolver_does_not_accept_invented_isin.<locals>.FakeResponse.__enter__   r]   r   c                    dS r_   r   r`   s     r   rb   zOtest_qwen_resolver_does_not_accept_invented_isin.<locals>.FakeResponse.__exit__   rc   r   c                r    dd l }dz  }|                    ddd|iigi                                          S )Nr   a  {"updates":[{"candidate_id":"%s","proposed_name":"Sample ETF","proposed_ticker":"SAMP","proposed_isin":"CH9999999999","proposed_exchange":"SWX","proposed_currency":"CHF","proposed_asset_class":"etf","mapping_status":"probable","review_note":"Qwen guessed an ISIN."}]}r   r   r   r   )r[   r   r(   r   s      r   re   zKtest_qwen_resolver_does_not_accept_invented_isin.<locals>.FakeResponse.read   sQ    KKK a  dg  gD::yI	47H+I*JKLLSSUUUr   Nrf   r   s   r   rj   r      r   r   rj   ru   c                                  S rY   r   )akrj   s     r   <lambda>zBtest_qwen_resolver_does_not_accept_invented_isin.<locals>.<lambda>   s    hththvhv r   rN   z[SELECT proposed_isin, mapping_status FROM instrument_import_candidates WHERE candidate_id=?r   rC   probable)r   r   rH   rz   r
   warningsr   r{   )r|   r   r   r   rj   r   s       @@r   0test_qwen_resolver_does_not_accept_invented_isinr      s   <<D
4Bb\^!_!_!_
`
`CV V V V V V V V V V VXvXvXvXvwww+D11F?a
,,twzv|
}
}  G  G  I  IC2%%%% J......r   )r   r   r   r   r   r   )r   r   )rI   r   r   rJ   )r   rJ   )&
__future__r   r%   pathlibr   r   ,jarvis_finance.imports.instrument_candidatesr   r   r   r   r	   r
   r   r   r   "jarvis_finance.market_data.catalogr   r   jarvis_finance.storage.databaser   !jarvis_finance.storage.migrationsr   r   r+   rH   rT   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   <module>r      s5   " " " " " "        
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 W V V V V V V V : : : : : : > > > > > >     " " " "*8 8 8 8W W W W,= = = =: : : :} } } }u u u u
G 
G 
G 
GW W W Wg g g g@ @ @ @1 1 1 1:/ / / / / /r   