
    :rjq                     p   d dl mZ d dlmZmZ  e       ZdedefdZ	dededdfd	Z
defd
ZdeiZ	 ddededefdZdeddfdZdZdd d gddgd d dd d gd dg dd d gd	dddiddigddddiddigdgd d!d"d#d"dd$d%Zd&ed'd(d)d*d+ed,d-d.d*d/ed0d1d2d*d3ed4d5d6d*d7ed8d9d:d*d;ed<d=d>d*d?ed@dAdBd*dCedDdEdFd*dGedHdIdJdKdLdMdNdOedPdQdRdSdLdTdNdUedVdWddXdYdZedZd[d\d*d]ed^d_d`d*daedbdcddd*deedfdgdhd*gZdddidddidjd ddkd ddkdJdRdg dldd d!dmddndoZi edpi edp   dqdrdsgd drggiiZh dtZdpdud idud idvdJidwiZdxdydzd{d|gd}d~dd)ddddJddd.ddddJddd2ddddKddd6ddddKddd>dg dddJdddFdg dddKddd:dd dddJdddBdd dddKddddd dddJddddd dddKdddddddddddddddddgZy)    )ProjectSessionSyncMode)OverlayTemplateProjectSessionsourcepathc                 $   | }|j                  d      D ]y  }t        |t              r>|j                         rt	        |      t        |      k\  rt        c S |t	        |         }Qt        |t              r||vrt        c S ||   }st        c S  |S )a  Dot-path get, same addressing convention control_fields' own `path` already
    uses ("score.games.1" -> source["score"]["games"][1]). Numeric segments index
    into a list; everything else is a dict key. Returns _UNSET (not None -- a
    mapped field could legitimately resolve to None/0/"") on any missing key,
    wrong type, or out-of-range index, so the caller can tell "absent" apart from
    "present but falsy" and skip it rather than writing a bogus null through..)split
isinstancelistisdigitintlen_UNSETdict)r   r   currentsegments       t/tmp/claude-1000/-home-jpaul-Escritorio-plevion/fa10384b-1e8a-4c6d-a034-5f1e4e17c6fc/scratchpad/field_mapping_poc.py_get_by_pathr   $   s     G::c? 
gt$??$GG(Dc'l+G&g%g&GM
 N    targetreturnNc                    |j                  d      }| }t        |dd       D ]  \  }}||dz      }|j                         }t        |t              r,||vst        ||   t        t
        f      s	|rg ni ||<   ||   }Zt        |t
              skt        |      }	t        |      |	k  r |j                  d       t        |      |	k  r t        ||	   t        t
        f      s	|rg ni ||	<   ||	   } |d   }
t        |t
              r?t        |
      }	t        |      |	k  r |j                  d       t        |      |	k  r |||	<   y|||
<   y)a|  Dot-path set, building intermediate dicts/lists on demand. A numeric
    segment past the current list's length pads with None up to that index --
    field_mappings entries aren't guaranteed to arrive/resolve in index order
    (e.g. games.1 before games.0 if only Team B changed), and target_path's
    shape is declared by the mapping, not inferred from what's resolved so far.r	   N   )	r
   	enumerater   r   r   r   r   r   append)r   r   valuesegmentsr   ir   next_segmentis_list_indexidxlasts              r   _set_by_pathr%   :   sC    zz#HG". #
7A$,,.gt$g%Z8H4QU,-W)62B g&G&g,Cg,#%t$ g,#%gclT4L9%2rclG# B<D'4 $i'lc!NN4  'lc!r   c                     t        | t              sg S t        |       D cg c]2  \  }}t        |t              rt        |      dk(  r|dz   |d   |d   d4 c}}S c c}}w )av  The one aggregate/reshape mapping kind, not a plain 1:1 path copy --
    ScoreboardScoreState.setHistory (frontend-overlays/interfaces/
    scoreboard.interface.ts) is `[number, number][]`, e.g. [[6,4],[3,6]]: a
    positional pair-per-set, team identity implicit in tuple order, set
    identity implicit in list position. Reshapes into
    [{"set_number": 1, "team_a_games": 6, "team_b_games": 4}, ...] -- named,
    1-indexed, self-describing without the reader needing to already know
    "index 0 of each pair is team A" or "list position N is set N+1".

    One shared entry per set, both teams' numbers together -- the exact fix
    flagged over the earlier "sets_win" split-per-team-list sketch: a
    completed set is one fact (two scores, one set_number), so it's written
    once here, not as two independent per-team appends that could drift out
    of sync with each other.   r   r   )
set_numberteam_a_gamesteam_b_games)r   r   r   r   )pairsr    pairs      r   _resolve_set_historyr-   [   se     eT"	 !'AtdD!c$i1n 1ud1gtAwO  s   7Aset_historysubscriber_templatesource_configc                 f   i }| j                   D ]  }t        |d         t        |      k7  r||d   |vr(t        ||d         }|t        u r@|j	                  d      }|t        |   |      }n.|j	                  d      }||j	                  t        |      |      }t        ||d   |        |S )u0  The actual cross-template contract: every field_mappings entry whose
    source_template_id matches the *live* paired session's own template (not just
    "any pack sibling" -- a mapping is declared per source template on purpose,
    since two scoreboard variants in the same pack could shape `score`
    differently) is walked off source_config and written into a fresh derived
    dict at its target_path.

    selected_keys: None (default) resolves every matching entry in the
    template's catalog, same as before this parameter existed. A set narrows
    that down to only the entries whose own `key` is a member -- this is the
    per-CONNECTION selection (ProjectSession.mapped_fields, see below), not a
    per-template one: the template can declare 20 possible fields, and two
    different sessions paired to the same source can each resolve a
    different subset of those 20 without the template itself changing.
    Unlike source_path/UNSET above, an empty selected_keys set is NOT the
    same as None here -- the caller (fan_out_to_subscribers) is responsible
    for turning "session declared no selection" into None before calling
    this, since {} legitimately means "resolve nothing" if a session were
    ever explicitly configured that way.

    Two ways a mapping entry can transform the raw value on its way to
    target_path, both optional and mutually exclusive (a mapping declares at
    most one):
    - `value_map`: a plain {"<raw string>": "<mapped value>"} dict for
      enum-shaped fields -- e.g. ScoreboardScoreState.server.team is a raw
      0/1 TeamIndex; {"0": "team_a", "1": "team_b"} turns it into the same
      named key the `teams` section itself uses, so a consumer never needs
      to know "0 means team A" out of band. Looked up by str(value) since
      JSON/dict keys from admin-authored field_mappings are always strings
      even when the live value is an int.
    - `kind`: names a whole-value reshape function (see _RESHAPE_KINDS) for
      mappings that aren't a positional 1:1 copy at all -- set_history is
      the one example so far (many raw values -> one named list). A mapping
      with `kind` set ignores `value_map` (the reshape function owns the
      entire transform).

    Deliberately builds a brand-new dict rather than mutating the subscriber's
    persisted config -- mirrors deriveFromScoreboardConfig's own contract
    (frontend-overlays/lib/match-info-rules.ts): this is *only* the derived
    subset, merged by the caller against whatever the subscriber's own
    config/defaults already have, same read-path precedence SPEC_OVERLAY_PACKS.md
    §2.3 already established. A path that fails to resolve is silently skipped,
    not defaulted to None here -- same reasoning, the caller's own precedence
    chain is what should decide the fallback, not this function guessing one.
    source_template_idkeysource_pathkind	value_maptarget_path)field_mappingsstrr   r   get_RESHAPE_KINDSr%   )	r/   r2   r0   selected_keysderivedmappingr   r5   r6   s	            r   resolve_mapped_configr?   x   s    ` G&55 =w+,-5G1HH$})L]GM,BCF?{{6""4(/EK0I$!c%j%8Wgm4e<#=$ Nr   source_sessionc           	         t         j                  j                  | t        j                        j                  d      }|D ]  }t        |j                  j                  | j                  j                  | j                  t        |j                        xs d      }|s^i |j                  ||_
        |j                  ddg       dd	lm}  |d
|j                    d|j                          y)a  Call this right after a source session's config is persisted (today:
    ProjectSessionViewSet.state, right where it already does
    `broadcast_group_event(f'overlay_session_{session.id}', 'overlay_state',
    session.config)` for the source's OWN primary render connection -- this is
    the same event, fanned out to every AUTOMATIC subscriber's group too,
    instead of leaving each subscriber's render page to open its own second WS
    connection back to this session and derive client-side).

    Only ever touches OTHER sessions' config -- source_session's own row/config
    is untouched here, this function has nothing to do with it beyond reading
    from it.

    select_related('project__template') because resolve_mapped_config needs
    each subscriber's own template.field_mappings -- N+1 otherwise, one query
    per subscriber in a pack that could plausibly have several.
    )subscribed_session	sync_modeproject__templateN)r2   r0   r<   config
updated_at)update_fieldsr   )broadcast_group_eventoverlay_session_overlay_state)r   objectsfilterr   	AUTOMATICselect_relatedr?   projecttemplatetemplate_idrE   setmapped_fieldssaveapps.common.ws.consumersrH   id)r@   subscribers
subscriberr=   rH   s        r   fan_out_to_subscribersrY      s    " !((//)(22 0  n() 
 " f
'''-55AA(// j667?4

  =z00<G<
x&>?B 0@/S]SdSde3fr   z$00000000-0000-0000-0000-000000000001padel1530)teamplayerF)	gamespointsserversetsWon
deuceCycle
isTiebreak
setHistorymatchWinnernextServerPlayerByTeamz#6290C3nametestzTeam A 2)colorplayersz#C4D600sssszTeam B 2   doubleAdvantageT)bestOf	deuceRulefinalSetTiebreak)enabledtournamentPoint)sportscoreteamsformatsituationalMessageteam_a_player_1zteams.0.players.0.namezteams.team_a.player_1zTeam A - Player 1)r3   r2   r4   r7   labelteam_a_player_2zteams.0.players.1.namezteams.team_a.player_2zTeam A - Player 2team_b_player_1zteams.1.players.0.namezteams.team_b.player_1zTeam B - Player 1team_b_player_2zteams.1.players.1.namezteams.team_b.player_2zTeam B - Player 2r)   zscore.games.0zscore.team_a.gameszTeam A Gamesteam_a_pointszscore.points.0zscore.team_a.pointszTeam A Pointsr*   zscore.games.1zscore.team_b.gameszTeam B Gamesteam_b_pointszscore.points.1zscore.team_b.pointszTeam B Points
serve_teamzscore.server.teamzscore.serve.teamteam_ateam_b)01zServing Team)r3   r2   r4   r7   r6   rz   serve_playerzscore.server.playerzscore.serve.playerplayer_1player_2zServing Playercompleted_setszscore.setHistoryz
score.setszCompleted Sets)r3   r2   r4   r7   r5   rz   rt   zmetadata.sportSportformat_best_ofzformat.bestOfzmetadata.format.best_ofzBest Offormat_deuce_rulezformat.deuceRulezmetadata.format.deuce_rulez
Deuce Ruletournament_pointz"situationalMessage.tournamentPointzmetadata.tournament_pointzTournament Point)r   r   )r   r   )r_   r`   )r   r   servesets)best_of
deuce_rule)rt   rw   r   )rv   ru   metadataru   re         >   r   r)   r*   r_   r]   )r   r   r   
playerModezPlayer ModeselectsinglesdoublesSetup)r   rz   
input_typeoptionsgroupzelements.1.contenttext    Names)r   rz   r   
max_lengthr   subgroupzelements.2.contentzelements.3.contentzelements.4.contentzelements.5.contentcounter)r   r[   r\   40AdScore)r   rz   r   labelsr   r   zelements.6.contentzelements.7.content   )r   rz   r   minmaxr   r   zelements.8.contentzelements.9.contentzTeam A Sets   zelements.10.contentzTeam B Setszelements.0.srcLogoimage
Appearance)r   rz   r   r   
backgroundzBackground Colorrj   zelements.11.itemszCommentary Tickerstring_list)r   rz   r   	max_itemsr   )N)apps.overlays.choicesr   apps.overlays.modelsr   r   objectr   r   r9   r   r%   r   r-   r;   r?   rY   PADEL_SCOREBOARD_TEMPLATE_IDSOURCE_CONFIG_EXAMPLEMATCH_STATE_FIELD_MAPPINGSRESOLVED_OUTPUT_EXAMPLE!SOURCE_CONFIG_WITH_COMPLETED_SETSCONNECTION_SELECTED_KEYS"CONNECTION_RESOLVED_OUTPUT_EXAMPLE-PADEL_SCOREBOARD_CONTROL_FIELDS_WITH_SUBGROUP r   r   <module>r      s  < 9 @	 S , S D B4 2 ' bfC(CMQC	Cd/f> /fd /fV  F  Q,*q6 #$a& &&)9FJ;O(PQ&&)9FJ;O(PQ ):PTU&*uE/ B !:/.$ !:/.$ !:/.$ !:/.$ :&+ :',  :&+ :',  :*)#(3 :,+%J7!  :)#!" :'  :&0 #:)3 ":;2#]u x  &:>%:>
 .."j9	 /@A! (%%O%g.O1v1v>NO% !6 J  A,A,(#& "b y) %$ %$ %$ %$ % / % / % % % & !	 #	 $$#Uq1 -r   