ó
    6lj•  ã            
      ó*  • % S r SSKJr  SSKrSSKJrJrJrJrJ	r	  \	\
\
\\
\\   4   rSr\R                  " S\R                  5      S4\R                  " S\R                  5      S	4\R                  " S
\R                  5      S4\R                  " S\R                  5      S4\R                  " S\R                  5      S4\R                  " S\R                  5      S4\R                  " S\R                  5      S4/rS\S'   \R                  " S5      r\R                  " S\R                  5      S4\R                  " S\R                  5      S4\R                  " S\R                  5      S4\R                  " S\R                  5      S4\R                  " S\R                  5      S4\R                  " S \R                  5      S4/rS\S!'   S)S" jr          S*S# jrS+S$ jrS,S% jr            S-S& jr          S.S' jrS/S( jrg)0u@  Filing metadata: what a document is, derived and displayed.

A document carries filing_type, year, period and version as columns. Nothing
displayable is stored beyond them: the label a card, the workspace header or
an export filename shows is derived here at read time, so grooming the
metadata corrects every label at once.

`infer` pre-fills the metadata from a filename. It is a convenience and never
authoritative â€” the upload form shows what it guessed and any field the user
touches is pinned. The same rules live in `ui/src/metadata.js` for the form;
`tests/playground/test_metadata.py` runs one case list through both so the
two implementations cannot drift apart silently.

The identity tuple (folder, filing_type, year, period, version) is what the
overwrite flow keys on. It only exists when filing_type, year and period are
all present, and it compares case-insensitively; `tuple_key` is that rule in
one place. `overlap_key` is the same tuple without the version â€” two versions
of one filing share it, which is exactly what the library's possible-duplicate
flag reports.
é    )ÚannotationsN)ÚAnyÚDictÚIterableÚOptionalÚTuple)ÚQ1ÚQ2ÚQ3ÚQ4ÚH1ÚH2ÚFYz$(?<!\d)([1-4])\s*q\s*(20\d{2})(?!\d)úq-yearz%(?<!\d)(20\d{2})[-_. ]?q([1-4])(?!\d)úyear-qz)(?<![a-z0-9])q([1-4])[-_. ]?(\d{2})(?!\d)úq-yyz(?<![a-z0-9])q([1-4])(?![0-9])Úqz(?<![a-z0-9])h([12])(?![0-9])Úhz(first|second)[-_ ]halfÚhalfz3(?<![a-z0-9])(fy|full[-_ ]?year|annual)(?![a-z0-9])Úfyz!list[tuple[re.Pattern[str], str]]Ú_PERIOD_PATTERNSz(?<!\d)(20\d{2})(?!\d)z"(?<![a-z0-9])10[-_ ]?k(?![a-z0-9])ú10-Kz"(?<![a-z0-9])10[-_ ]?q(?![a-z0-9])z10-Qz (?<![a-z0-9])99[-_. ]?1(?![0-9])z99-1z (?<![a-z0-9])99[-_. ]?2(?![0-9])z99-2zpress[-_ ]?releasez.supplemental|presentation|deck|slides|earningsÚ_TYPE_PATTERNSc                óŠ  ^• U R                  SS5      S   m[        R                  " SST5      m[        U4S j[         5       S5      nSnSn[
         GH$  u  pEUR                  T5      nU(       d  M   US:X  a.  S	UR                  S5       3[        UR                  S
5      5      p2OÑUS:X  a.  S	UR                  S
5       3[        UR                  S5      5      p2O�US:X  a1  S	UR                  S5       3S[        UR                  S
5      5      -   p2OfUS:X  a  S	UR                  S5       3nOKUS:X  a  SUR                  S5       3nO0US:X  a(  UR                  S5      R                  5       S:X  a  SOSnOSn  O   Uc6  [        R                  T5      nU(       a  [        UR                  S5      5      nUc  US:X  a  SnXUS.$ )u¼   Filing metadata guessed from a filename: {filing_type, year, period},
each None when nothing in the name says. A 10-K with no explicit period
defaults to FY â€” an annual report is annual.Ú/é   éÿÿÿÿz\.[^.]+$Ú c              3  ó\   >#   • U  H!  u  pUR                  T5      (       d  M  Uv •  M#     g 7f©N)Úsearch)Ú.0ÚrxÚlabelÚstems      €Ú7/home/mande/repo/quber/src/quber/playground/metadata.pyÚ	<genexpr>Úinfer.<locals>.<genexpr>B   s   øé € ÐQª~¡) "ÀÇÁÈ4ÇŸ™ª~ùs   ƒ,£	,Nr   ÚQé   r   r   iÐ  r   r   ÚHr   Úfirstr   r   r   r   )Úfiling_typeÚyearÚperiod)ÚrsplitÚreÚsubÚnextr   r   r!   ÚgroupÚintÚlowerÚ_YEAR_RE)Úfilenamer-   r/   r.   r#   ÚkindÚmr%   s          @r&   Úinferr;   ;   s–  ø€ ð �?‰?˜3 Ó" 2Ñ&€DÜ�6Š6�+˜r 4Ó(€DäÔQ­~ÓQÐSWÓX€Kà €FØ€Dß$Ð$‰ˆØ�I‰I�d‹OˆÞÙØ�8ÓØ˜qŸw™w q›z˜lÐ+¬S°·±¸³«_‘DØ�XÓØ˜qŸw™w q›z˜lÐ+¬S°·±¸³«_‘DØ�V‹^Ø˜qŸw™w q›z˜lÐ+¨T´C¸¿¹À»
³OÑ-C‘DØ�S‹[Ø˜Ÿ™ ›˜Ð%‰FØ�S‹[Ø˜Ÿ™ ›˜Ð%‰FØ�V‹^ØŸW™W Q›Z×-Ñ-Ó/°7Ó:‘TÀ‰FàˆFÙñ% %ð( �|Ü�O‰O˜DÓ!ˆÞÜ�q—w‘w˜q“z“?ˆDØ�~˜+¨Ó/ØˆØ&ÀÑGÐGó    c                óð   • / nU (       a  UR                  U 5        SR                  S X!4 5       5      nU(       a  UR                  U5        U(       a  UR                  SU 35        SR                  U5      $ )u’   The display label: `99-2 Â· Q1 2026 Â· v2` with missing parts omitted.
Empty when the metadata is entirely absent â€” the title then stands alone.Ú c              3  óH   #   • U  H  o(       d  M  [        U5      v •  M     g 7fr    )Ústr)r"   Úps     r&   r'   Úfiling_label.<locals>.<genexpr>n   s   é € Ð6¢.˜Q³A“&”#�a—&�&¢.ùs   ‚
"�"Úvu    Â· )ÚappendÚjoin)r-   r.   r/   ÚversionÚpartsÚpys         r&   Úfiling_labelrI   c   sa   € ð €EÞØ�‰�[Ô!Ø	�‰Ñ6 6¡.Ó6Ó	6€BÞ	Ø�‰�RÔÞØ�‰�q˜˜	�]Ô#Ø�;‰;�uÓÐr<   c                 óÜ   • U  Hf  nU(       d  M  UR                  5       (       d  M#  [        R                  " SSUR                  5       5      R                  S5      SS nU(       d  Md  Us  $    g)u€   A filename-safe stem from the first non-empty candidate â€” label, then
title, then original filename, per the label precedence.z
[^a-z0-9]+Ú-Né<   Údocument)Ústripr1   r2   r6   )Ú
candidatesÚcr%   s      r&   Úexport_stemrQ   v   sV   € ó ˆßˆ1�—‘—“Ü—6’6˜-¨¨a¯g©g«iÓ8×>Ñ>¸sÓCÀCÀRÐHˆDßˆtØ’ñ	 ð
 r<   c                óP   • U =(       d    SR                  5       R                  5       $ )Nr   )rN   r6   )Úss    r&   Ú_normrT   �   s   € Ø�G��?‰?Ó×"Ñ"Ó$Ð$r<   c                ó„   • U(       a  U(       a  U(       d  g[        U 5      [        U5      [        U5      [        U5      U4$ )u»   The identity tuple, normalized for comparison â€” or None when it does
not exist because filing_type, year or period is missing. Documents
without a tuple never collide and never replace.N)rT   r5   )Úfolderr-   r.   r/   rF   s        r&   Ú	tuple_keyrW   …   s0   € ö žD¦VØÜ�&‹Mœ5 Ó-¬s°4«y¼%À»-ÈÐQÐQr<   c                ó4   • [        XX#SS9nU(       a  USS $ S$ )ziThe tuple without its version: what v1/v2 siblings share, and what the
possible-duplicate flag groups by.N)rF   é   )rW   )rV   r-   r.   r/   Úkeys        r&   Úoverlap_keyr[   ”   s%   € ô �F¨¸tÑ
D€CÞˆ3ˆr�ˆ7Ð#˜tÐ#r<   c           	     óÞ  • [        U 5      n0 nU Hi  n[        UR                  S5      UR                  S5      UR                  S5      UR                  S5      5      nUc  MR  UR                  US5      S-   X$'   Mk     U Hk  n[        UR                  S5      UR                  S5      UR                  S5      UR                  S5      5      n[        USL=(       a    X$   S:„  5      US'   Mm     g)	zŒSet `overlap` on each document dict: True when another document shares
its versionless tuple. Documents with incomplete metadata never flag.rV   r-   r.   r/   Nr   r   Úoverlap)Úlistr[   ÚgetÚbool)ÚdocsÚrowsÚcountsÚdrZ   s        r&   Úannotate_overlapsre       sÍ   € ô �‹:€DØ35€FÛˆÜ˜!Ÿ%™% ›/¨1¯5©5°Ó+?ÀÇÁÀvÃÐPQ×PUÑPUÐV^ÓP_Ó`ˆØ‹?Ø Ÿ*™* S¨!Ó,¨qÑ0ˆF‹Kñ ó ˆÜ˜!Ÿ%™% ›/¨1¯5©5°Ó+?ÀÇÁÀvÃÐPQ×PUÑPUÐV^ÓP_Ó`ˆÜ˜C t˜O×?°±¸a±Ó@ˆˆ)‹ò r<   )r8   r@   ÚreturnzDict[str, Any])
r-   úOptional[str]r.   úOptional[int]r/   rg   rF   rh   rf   r@   )rO   rg   rf   r@   )rS   rg   rf   r@   )rV   rg   r-   rg   r.   rh   r/   rg   rF   rh   rf   zOptional[TupleKey])
rV   rg   r-   rg   r.   rh   r/   rg   rf   z#Optional[Tuple[str, str, int, str]])ra   zIterable[Dict[str, Any]]rf   ÚNone)Ú__doc__Ú
__future__r   r1   Útypingr   r   r   r   r   r@   r5   ÚTupleKeyÚPERIODSÚcompileÚIr   Ú__annotations__r7   r   r;   rI   rQ   rT   rW   r[   re   © r<   r&   Ú<module>rs      se  ðòõ* #ã 	ß 7Õ 7ð ��c˜3  X¨c¡]Ð2Ñ3€à
4€ð ‡Z‚ZÐ7¸¿¹Ó>ÀÐIØ‡Z‚ZÐ8¸"¿$¹$Ó?ÀÐJØ‡Z‚ZÐ<¸b¿d¹dÓCÀVÐLØ‡Z‚ZÐ1°2·4±4Ó8¸#Ð>Ø‡Z‚ZÐ0°"·$±$Ó7¸Ð=Ø‡Z‚ZÐ*¨B¯D©DÓ1°6Ð:Ø‡Z‚ZÐFÈÏÉÓMÈtÐTð7Ð Ð3ó ð �:Š:Ð/Ó0€ð
 ‡Z‚ZÐ5°r·t±tÓ<¸fÐEØ‡Z‚ZÐ5°r·t±tÓ<¸fÐEØ‡Z‚ZÐ3°R·T±TÓ:¸FÐCØ‡Z‚ZÐ3°R·T±TÓ:¸FÐCØ‡Z‚ZÐ% r§t¡tÓ,¨fÐ5Ø‡Z‚ZÐAÀ2Ç4Á4ÓHÈ&ÐQð5€Ð1ó ô%HðPØðà
ðð ðð ð	ð
 	ôô&ô%ðRØðRàðRð ðRð ð	Rð
 ðRð ôRð	$Øð	$àð	$ð ð	$ð ð		$ð
 )ô	$õAr<   