
    mj3'                        d dl mZ d dlZd dlmc mZ d dlZd dl	m	Z	m
Z
 d dlZd dlmZmZ d dlmZmZ d dlmZmZmZmZmZmZmZmZm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)datetimetimezone)apply_migrationsget_schema_version)add_demo_migros_receiptrun_grocery_optimization)	AldiSuisseProviderCoopProviderDennerProviderLidlSchweizProviderMigrosProviderOttosProviderGroceryProviderRateLimitErrorcompare_product_prices search_and_store_product_matchesreturnsqlite3.Connectionc                 n    t          j        d          } t           j        | _        t	          |            | S )Nz:memory:)sqlite3connectRowrow_factoryr   )conns    V/home/agent/.hermes/repos/FinanceManager/tests/unit/test_grocery_price_providers_v1.pydbr      s.    ?:&&D{DTK    Nonec                    t                      } t          |           }d}||k    }|st          j        d|fd||f          dt	          j                    v st          j        t                    rt          j        t                    nddt	          j                    v st          j        |           rt          j        |           ndt          j        |          t          j        |          dz  }dd|iz  }t          t          j	        |                    d x}x}}d	 | 
                    d
                                          D             }h d}|j        } ||          }|sdt          j        |          t          j        |          dt	          j                    v st          j        |          rt          j        |          ndt          j        |          dz  }t          t          j	        |                    d x}x}}d S )N1   ==z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr   r   py0py1py3py6assert %(py8)spy8c                    h | ]
}|d          S )name ).0rows     r   	<setcomp>zFtest_schema_version_33_provider_cache_columns_exist.<locals>.<setcomp>!   s    oooCCKooor   z0PRAGMA table_info(grocery_product_details_cache)>   unitbrandsourcecurrency	image_url
confidencepromotion_textraw_result_jsonprice_decimal_textquality_flags_jsonavailability_statusunit_price_decimal_textzJassert %(py6)s
{%(py6)s = %(py3)s
{%(py3)s = %(py1)s.issubset
}(%(py4)s)
}cols)r&   r'   py4r(   )r   r   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationexecutefetchallissubset)r   @py_assert2@py_assert5@py_assert4@py_format7@py_format9r=   @py_assert0s           r   3test_schema_version_33_provider_cache_columns_existrP      s   44Dd##)r)#r))))))))))#r))))))))))))))))))))))))))))))d)))))d))))))#))))r)))))))))))))))))))))))))))oo4<<0b#c#c#l#l#n#noooD Q  Q  Q  `  Q  Z  `  Z  Z  [_  `  `  `  `  `  `  `  `  Q  `  `  `  `  Z  `  `  `  `  `  `  `  `  `  `  `  [_  `  `  `  `  `  [_  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `  `r   c                 	   t                      } t          d           }t          d           }|                    | ddd          }|d         d	         }d
}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|d         d         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|d         d         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|d         d         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|                    | ddd          }	|	d         d         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|	d         d         }|j	        }d}
 ||
          }|ssdt	          j        |          t	          j        |          t	          j        |
          t	          j        |          dz  }t          t	          j        |                    d x}x}x}
}|                    | ddd          d         }|d	         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|d         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}d S )Nc                    dS )Nz<article data-product><a href="/milch">Migros Milch 1L</a><span class="price">CHF 1.60</span><span class="unit-price">1.60/l</span><span class="brand">Migros</span></article>r-   urls    r   <lambda>zPtest_migros_and_coop_provider_search_parse_mock_html_and_cache.<locals>.<lambda>'   s
      1a r   fetcherc                    dS )Nz<article data-product><a href="/milch">Prix Garantie Milch 1L</a><span class="price">CHF 1.20</span><span class="unit-price">1.20/l</span><span class="brand">Prix Garantie</span></article>r-   rS   s    r   rU   zPtest_migros_and_coop_provider_search_parse_mock_html_and_cache.<locals>.<lambda>(   s
      -k r   milchF   )	use_cachemax_resultsr   retailerMigrosr!   z%(py1)s == %(py4)sr&   r>   assert %(py6)sr(   r9   z1.60r4   CHFr3   	web_fetchTcacheproduct_urlz/milchzJassert %(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.endswith
}(%(py5)s)
}r&   r'   py5py7Coopr<   1.20)
r   r   r   search_productsr?   r@   rD   rE   rF   endswith)r   migroscoopfirstrO   @py_assert3rJ   @py_format5rM   cachedrL   @py_assert6@py_format8coop_results                 r   >test_migros_and_coop_provider_search_parse_mock_html_and_cacherv   %   s   44D  %a  %a  b  b  bF  !k  !k  l  l  lD""4Eq"QQE8J+8+8++++++++++8+++++++++8+++++++++++++++++++++++++++8()3V3)V3333333333)V33333)3333V3333333333333333333333333338J(5(5((((((((((5(((((((((5(((((((((((((((((((((((((((8H,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,##D'Tq#QQF!9X)')'))))))))))')))))))))')))))))))))))))))))))))))))!9]#6#,6X6,,X66666666#6666,6666X6666666666666666666666666666&&tWST&UUVWXKz",f,"f,,,,,,,,,,"f,,,,,",,,,f,,,,,,,,,,,,,,,,,,,,,,,,,,,01;V;1V;;;;;;;;;;1V;;;;;1;;;;V;;;;;;;;;;;;;;;;;;;;;;;;;;;;;r   c                 h   t                      } dd}t          |          }|                    | dd          }|d	         d
         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j        |                    d x}x}}d}|d	         d         }||v }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j        |                    d x}x}}t                      t                      t                      t                      fD ]-}	|	                    | d          }
|
d	         d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j        |                    d x}x}}|
d	         d
         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j        |                    d x}x}}/d S )N_urlstrr   c                     t          d          )Nrate_limited)r   rx   s    r   r{   zItest_provider_rate_limit_and_skeleton_future_status.<locals>.rate_limited9   s    +N;;;r   rV   rY   Fr[   r   statusneeds_reviewr!   r_   r`   ra   r(   r{   quality_flagsinz%(py1)s in %(py4)sfuture_statusskeleton_provider_not_live)rx   ry   r   ry   )r   r   rk   r?   r@   rD   rE   rF   r
   r   r   r   )r   r{   providerresultrO   rp   rJ   rq   rM   skeletonrowss              r   3test_provider_rate_limit_and_skeleton_future_statusr   7   sU   44D< < < <l333H%%dGu%EEF!9X0.0.0000000000.000000000.0000000000000000000000000007VAY77>77777777777>777777>77777777777777777777777777777777'))+>+@+@.BRBRTaTcTcd 3 3''g66Aw'G+GG'+GGGGGGGGGGG'+GGGGGG'GGGG+GGGGGGGGGGGGGGGGGGGGGGGGGGGGAwx 2N2 N2222222222 N22222 2222N22222222222222222222222222223 3r   c                    t                      } t          | dddddg          }g t          fd          }t          | |d	         |g|d
         d         d         gdd          }sedddt	          j                    v st          j                  rt          j                  ndiz  }t          t          j
        |                    |d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }	t          t          j
        |	                    d x}x}}|                     d                                          }
|
d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }	t          t          j
        |	                    d x}x}}|
d         }|j        }d} ||          }|ssdt          j        |          t          j        |          t          j        |          t          j        |          dz  }t          t          j
        |                    d x}x}x}}|
d          }|s:d!d"t          j        |          iz  }t          t          j
        |                    d }d S )#N
2026-05-19Migros TestMilch 1L1.50raw_product_nametotal_price_textpurchase_date
store_nameitemsc                2                         |           pdS Nz<article data-product><a href="/milch">Prix Garantie Milch 1L</a><span class="price">CHF 1.20</span><span class="unit-price">1.20/l</span></article>appendrT   callss    r   rU   zctest_search_and_store_product_matches_uses_sources_and_no_provider_call_on_render.<locals>.<lambda>I        S0A0A  1\  F\ r   rV   
receipt_idr   r   product_item_id   F)	providersincluded_product_item_idsmax_productsr[   zassert %(py0)sr%   r   provider_callsr!   r_   r`   ra   r(   z%SELECT * FROM grocery_product_matchesr3   rc   candidate_urlhttps://Lassert %(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.startswith
}(%(py5)s)
}rf   
fetched_atassert %(py1)sr&   )r   r   r   r   rA   rB   r?   rC   rD   rE   rF   r@   rG   fetchone
startswith)r   receiptr   matches@py_format1rO   rp   rJ   rq   rM   storedrL   rs   rt   @py_format2r   s                  @r   Qtest_search_and_store_product_matches_uses_sources_and_no_provider_call_on_renderr   E   s   44D%d,S`  I  _e  jf  jf  ig  h  h  hGE  %\  %\  %\  %\  ]  ]  ]H.tW\5JW_V`  ~E  FM  ~N  OP  ~Q  Rc  ~d  }e  tu  AF  G  G  GGLLLLLLLLLLLL5LLLLL5LLLLLLLLLLLLLLLL#$))$))))))))))$)))))$)))))))))))))))))))))))))))))))\\ABBKKMMF(*{*{**********{*********{***************************/"9"-9j9--j99999999"9999-9999j9999999999999999999999999999,r   c                 	   t          ddddddg          } | d         }d	}||u }|slt          j        d
|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t	          t          j        |                    d x}x}}| d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t	          t          j        |                    d x}x}}| d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t	          t          j        |                    d x}x}}t          ddd d ddg          }|d         }d	}||u }|slt          j        d
|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t	          t          j        |                    d x}x}}d}|d         }||v }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t	          t          j        |                    d x}x}}t          ddddg          }|d         }d}||u }|slt          j        d
|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t	          t          j        |                    d x}x}}|d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t	          t          j        |                    d x}x}}t          dd dd!ddg          }|d         }d}||u }|slt          j        d
|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t	          t          j        |                    d x}x}}d"}|d         }||v }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t	          t          j        |                    d x}x}}d S )#Nr   rj   1.50/lz1.20/lrb   close_match)original_price_textcandidate_price_textoriginal_unit_price_textcandidate_unit_price_textr4   r   can_calculate_savingsTisz%(py1)s is %(py4)sr`   ra   r(   savings_text0.30r!   r_   basis
unit_pricez3.00z2.50exact_matchpackage_price_fallbackr   r   r   r   )r   r   r4   r   Fz0.002.00z2.00/lnot_cheaper)r   r?   r@   rD   rE   rF   )	saferO   rp   rJ   rq   rM   packageunsafe	expensives	            r   Gtest_price_comparison_prefers_unit_price_and_excludes_uncertain_matchesr   S   s   !fSYt|  Ya  lq  BO  AP  Q  Q  QD'(0D0(D0000000000(D00000(0000D000000000000000000000000000)6)6))))))))))6)))))))))6)))))))))))))))))))))))))))=(L(=L((((((((((=L(((((=((((L((((((((((((((((((((((((((($V\w{  X\  gl  }J  |K  L  L  LG*+3t3+t3333333333+t33333+3333t333333333333333333333333333#?w'??#'???????????#'??????#????'????????????????????????????#U[fk  }K  |L  M  M  MF)*3e3*e3333333333*e33333*3333e333333333333333333333333333.!+V+!V++++++++++!V+++++!++++V+++++++++++++++++++++++++++&6X^  zB  ^f  qv  GT  FU  V  V  VI,-66-6666666666-66666-66666666666666666666666666666666Io66=66666666666=666666=6666666666666666666666666666666666r   c            	        t                      } t          | ddddddg          }t          d 	          }t          | |d
         |g|d         d         d         gd          }|                     d                                          d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j
        |                    d x}x}}|                     d                                          }	|	d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j
        |                    d x}x}}|	d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j
        |                    d x}x}}d S )Nr   r   r   r   r   r   unit_price_textr   r   c                    dS )Nz<article data-product><a href="/milch">Milch 1L</a><span class="price">CHF 2.00</span><span class="unit-price">2.00/l</span></article>r-   r|   s    r   rU   zEtest_higher_priced_provider_result_is_not_suggested.<locals>.<lambda>f   s
      2z r   rV   r   r   r   r   Fr   r   r[   z1SELECT COUNT(*) AS c FROM grocery_product_matchescr   r!   r_   r`   ra   r(   RSELECT status,candidate_price_text,quality_flags_json FROM grocery_product_matchesr~   r   r   r   )r   r   r   r   rG   r   r?   r@   rD   rE   rF   )
r   r   r   r   rO   rp   rJ   rq   rM   r   s
             r   3test_higher_priced_provider_result_is_not_suggestedr   c   s   44D%d,S`  I  ^f  |B  jC  jC  iD  E  E  EG  %z  %z  {  {  {H-dGL4IV^U_  }D  EL  }M  NO  }P  Qb  }c  |d  pu  v  v  vF<<KLLUUWWX[\a`aa\`aaaaaaaaaaa\`aaaaaa\aaaa`aaaaaaaaaaaaaaaaaaaaaaaaaaaa\\nooxxzzF(-~-~----------~---------~---------------------------()3V3)V3333333333)V33333)3333V33333333333333333333333333333r   c                    t                      } t          | dddddg          }g t          fd          }t          | |d	         |g|d
         d         d         gd          }|d         d         d         }t          | |d	         |g|d
         d         d         gd          }t	                    }d}||k    }|st          j        d|fd||f          dt          j                    v st          j	        t                    rt          j
        t                    nddt          j                    v st          j	                  rt          j
                  ndt          j
        |          t          j
        |          dz  }	dd|	iz  }
t          t          j        |
                    d x}x}}|d         }d}||k    }|slt          j        d|fd||f          t          j
        |          t          j
        |          dz  }dd|iz  }	t          t          j        |	                    d x}x}}|d         d         d          }d!}||k    }|slt          j        d|fd"||f          t          j
        |          t          j
        |          dz  }dd|iz  }	t          t          j        |	                    d x}x}}|d         d         d         }||k    }|st          j        d|fd#||f          t          j
        |          d$t          j                    v st          j	        |          rt          j
        |          nd$d%z  }d&d'|iz  }t          t          j        |                    d x}}d S )(Nr   r   r   r   r   r   c                2                         |           pdS r   r   r   s    r   rU   zUtest_search_and_store_preserves_provider_cache_and_source_timestamp.<locals>.<lambda>r   r   r   rV   r   r   r   r   Fr   r   source_fetched_atTr   r!   r#   lenr   r$   r)   r*   
cache_hits)>=)z%(py1)s >= %(py4)sr`   ra   r(   r3   rd   r_   )z%(py1)s == %(py3)sfirst_timestamp)r&   r'   zassert %(py5)srg   )r   r   r   r   r   r?   r@   rA   rB   rC   rD   rE   rF   )r   r   r   ro   r   secondrJ   rK   rL   rM   rN   rO   rp   rq   @py_format4@py_format6r   s                   @r   Ctest_search_and_store_preserves_provider_cache_and_source_timestampr   n   s   44D%d,S`  I  _e  jf  jf  ig  h  h  hGE  %\  %\  %\  %\  ]  ]  ]H,T7<3HU]T^  |C  DK  |L  MN  |O  Pa  |b  {c  ot  u  u  uEI&q)*=>O-dGL4IV^U_  }D  EL  }M  NO  }P  Qb  }c  |d  pt  u  u  uFu:::?:33uu:,$1$1$$$$$$$$$$1$$$$$$$$$1$$$$$$$$$$$$$$$$$$$$$$$$$$$)Q)4W4)W4444444444)W44444)4444W444444444444444444444444444)Q 34G4GGGGGGGGGG4GGGGG4GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGr   c                    t                      } t          | dddddg          }t          d           }t          | |d	         |g|d
         d         d         gd          }|d         d         d         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|d         d         d         }d }||u }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|d         d         d         }|s:ddt	          j        |          iz  }	t          t	          j        |	                    d }| 	                    d          
                                }
|
d         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}|
d         }d }||u }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d x}x}}d S )Nr   r   r   r   r   r   c                    dS )NzJ<article data-product><a href="/milch">Coop Milch ohne Preis</a></article>r-   r|   s    r   rU   zItest_missing_provider_price_is_not_stored_as_zero_match.<locals>.<lambda>   s    1} r   rV   r   r   r   r   Fr   r   r~   r   r!   r_   r`   ra   r(   
price_textr   r   match_idr   r&   r   r   )r   r   r   r   r?   r@   rD   rE   rF   rG   r   )r   r   r   r   rO   rp   rJ   rq   rM   r   r   s              r   7test_missing_provider_price_is_not_stored_as_zero_matchr   |   s   44D%d,S`  I  _e  jf  jf  ig  h  h  hG$}$}~~~H-dGL4IV^U_  }D  EL  }M  NO  }P  Qb  }c  |d  pu  v  v  vF)Q);^;)^;;;;;;;;;;)^;;;;;);;;;^;;;;;;;;;;;;;;;;;;;;;;;;;;;)Q-55-5555555555-55555-5555555555555555555555555555555)Q
+++++++++++++++++++++++++\\nooxxzzF(-~-~----------~---------~---------------------------()1T1)T1111111111)T11111)1111T11111111111111111111111111111r   c                 &   t                      } t          d           }|                    | dd          }|                     d           |                                  |j        }|                    | ddd	          }|j        }d
}||z   }||k    }|st          j        d|fd||f          dt          j	                    v st          j
        |          rt          j        |          ndt          j        |          dt          j	                    v st          j
        |          rt          j        |          ndt          j        |          dz  }dd|iz  }	t          t          j        |	                    d x}x}x}}|d         d         }
d}|
|k    }|slt          j        d|fd|
|f          t          j        |
          t          j        |          dz  }dd|iz  }t          t          j        |                    d x}
x}}d S )Nc                    dS )Nzb<article data-product><a href="/milch">Coop Milch</a><span class="price">CHF 1.20</span></article>r-   r|   s    r   rU   z>test_stale_cache_is_not_used_without_refresh.<locals>.<lambda>   s
      2V r   rV   rY   Fr}   zOUPDATE grocery_product_details_cache SET fetched_at='2000-01-01T00:00:00+00:00'T<   )r[   max_age_secondsr   r!   )zA%(py2)s
{%(py2)s = %(py0)s.request_count
} == (%(py4)s + %(py6)s)r   calls_before)r%   py2r>   r(   zassert %(py9)spy9r   r3   rc   r_   r`   ra   r(   )r   r   rk   rG   commitrequest_countr?   r@   rA   rB   rC   rD   rE   rF   )r   r   r   r   @py_assert1rK   @py_assert7rp   rt   @py_format10rO   rJ   rq   rM   s                 r   ,test_stale_cache_is_not_used_without_refreshr      sm   44D  %V  %V  W  W  WH##D'U#CCDLLbcccKKMMM)L##D'TSU#VVD!5A5\A%55!%55555555555!%55555555555558555558555555!55555555555\55555\555555A5555555555555555555555555555578++++++++++++++++++++++++++++++++++++++++++++++++++r   c            	        t                      } t          | ddddddg          }t          d 	          }t          | |d
         |g|d         d         d         gd           t	          | |d
         dgd|d         d         d         g          }|d         d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }dd|iz  }t          t          j	        |                    d x}x}}|d         d         d         }|j
        }d}	 ||	          }
|
ssdt          j        |          t          j        |          t          j        |	          t          j        |
          dz  }t          t          j	        |                    d x}x}x}	}
d S ) Nr   r   r   r   r   r   r   c                    dS r   r-   rS   s    r   rU   zEtest_provider_matches_feed_optimizer_only_when_safe.<locals>.<lambda>   s
      1G r   rV   r   r   r   r   Fr   ri   r   )selected_retailersmax_store_countr   summaryestimated_savings_textr   r!   r_   r`   ra   r(   product_comparisons
source_urlr   r   rf   )r   r   r   r   r	   r?   r@   rD   rE   rF   r   )r   r   r   runrO   rp   rJ   rq   rM   rL   rs   rt   s               r   3test_provider_matches_feed_optimizer_only_when_safer      s   44D%d,S`  I  ^f  |B  jC  jC  iD  E  E  EG  %G  %G  H  H  HH$T7<+@XJsz  |C  tD  EF  tG  HY  tZ  s[  gl  m  m  m  m
"4)>TZS[mn  LS  T[  L\  ]^  L_  `q  Lr  Ks  t  t  tCy>23=v=3v==========3v=====3====v===========================$%a(6M6AM*MAA*MMMMMMMM6MMMMAMMMM*MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMr   )r   r   )r   r   )'
__future__r   builtinsrA   _pytest.assertion.rewrite	assertionrewriter?   r   r   r   pytest!jarvis_finance.storage.migrationsr   r   )jarvis_finance.services.grocery_optimizerr   r	   /jarvis_finance.services.grocery_price_providersr
   r   r   r   r   r   r   r   r   r   rP   rv   r   r   r   r   r   r   r   r   r-   r   r   <module>r     s   " " " " " "               ' ' ' ' ' ' ' '  R R R R R R R R g g g g g g g g
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
   ` ` ` `< < < <$3 3 3 3       7 7 7 7 4 4 4 4H H H H
2 
2 
2 
2	, 	, 	, 	,N N N N N Nr   