
    'j}                       U d 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
 ddlmZmZmZmZmZ  G d de          Z G d d	e          Z G d
 de          Z G d de          Z G d de          Z G d de          Z G d de          Z G d de          Z G d de          Z G d de          Z G d de          Z G d de          Z G d de          Z G d  d!e          Z G d" d#e          Z G d$ d%e          Z  G d& d'e          Z! G d( d)e          Z" G d* d+e          Z# G d, d-e          Z$ G d. d/e          Z% G d0 d1e          Z&eeeee e"e$e&gZ'd2e(d3<   ee
eeeeee e"e$e&f	          ed45          f         Z) ee)          Z*d6e(d7<   dud<Z+ G d= d>e          Z, G d? d@e          Z- G dA dBe          Z. G dC dDe          Z/ G dE dFe          Z0 G dG dHe          Z1 G dI dJe          Z2 G dK dLe          Z3 G dM dNe          Z4 G dO dPe          Z5 G dQ dRe          Z6 G dS dTe          Z7 G dU dVe          Z8 G dW dXe          Z9 G dY dZe          Z: G d[ d\e          Z; G d] d^e          Z<e-e/e1e3e5e8e:e<gZ=d_e(d`<   ee
e-e/e1e3e5e8e:e<f          ed45          f         Z> ee>          Z?dae(db<   dvddZ@de e'D             ZAdfe(dg<   dh e'D             ZBdie(dj<    eCdk e'D                       ZDdle(dm<   dn e=D             ZEdfe(do<   dp e=D             ZFdie(dq<   g drZGdse(dt<   dS )wu?  Chart type definitions, schemas, and colour palette for NL-to-SQL agents.

Single source of truth for all chart and highlight types.  Everything is
defined as a Pydantic model; the public exports below are derived from the
model registries so there is only one place to edit when adding a new type.

Public exports (backward-compatible):
  - CHART_TYPES              — ordered list of (key, description) pairs.
  - CHART_SCHEMAS            — prompt schema string per chart type.
  - KEY_HIGHLIGHT_TYPES      — ordered list of (key, description) for highlight blocks.
  - KEY_HIGHLIGHT_SCHEMAS    — prompt schema string per highlight type.
  - CHART_COLORS             — standard hex colours for series / slices.
  - VALID_CHART_KEYS         — O(1) validation set.
  - AnyChartContent          — discriminated Union for runtime validation.
  - AnyHighlightContent      — discriminated Union for runtime validation.
  - validate_chart_content   — parse & validate a raw dict against the correct chart model.
  - validate_highlight_content — parse & validate a raw dict against the correct highlight model.

To add a new chart type:
  1. Define item model(s) and a chart model subclassing _ChartBase.
  2. Append it to _CHART_MODEL_REGISTRY.
  3. Add it to the AnyChartContent Union.
  All other exports update automatically.
    )annotationsN)	AnnotatedAnyClassVarLiteralOptionalUnion)	BaseModel
ConfigDictFieldTypeAdaptermodel_validatorc                  R    e Zd ZU  ed          ZdZded<   dZded<   dZded<   dS )	
_ChartBaseignoreextra ClassVar[str]	CHART_KEYCHART_DESCRIPTIONPROMPT_SCHEMAN)	__name__
__module____qualname__r   model_configr   __annotations__r   r        N/var/www/html/ai-enterprise-brain/backend/chanakya/nl2sql_core/chart_config.pyr   r   $   sX         :H---L!I!!!!')))))#%M%%%%%%r   r   c                  R    e Zd ZU  ed          ZdZded<   dZded<   dZded<   dS )	_HighlightBaser   r   r   r   HIGHLIGHT_KEYHIGHLIGHT_DESCRIPTIONr   N)	r   r   r   r   r   r#   r   r$   r   r   r   r    r"   r"   +   sX         :H---L#%M%%%%+-----#%M%%%%%%r   r"   c                  v    e Zd ZU dZ ed          ZdZded<    ed          e	dd                        Z
dS )_ItemWithAutoIdu   Base for chart item models that carry an `id` hit-zone key.

    Always assigns a server-generated UUID — the agent never produces `id`.
    r   r   r   stridbeforemodedatar   returnc                z    t          |t                    s|S t          t          j                              |d<   |S )Nr(   )
isinstancedictr'   uuiduuid4)clsr,   s     r    
_assign_idz_ItemWithAutoId._assign_id:   s7     $%% 	K&&T
r   N)r,   r   r-   r   )r   r   r   __doc__r   r   r(   r   r   classmethodr4   r   r   r    r&   r&   2   sw           :H---LBLLLL_(###   [ $#  r   r&   c                      e Zd ZU dZded<   ded<    ed          Zded<    ed	          Zded
<    ed          Zded<   dZ	ded<   dZ
ded<   dZded<   dZded<   dS )_StackedBarItemr   r'   r(   nameu%   Solid bar segment width — REQUIRED.descriptionfloatbaseu8   Full bar width and right-side label source — REQUIRED.totalu-   Shown to the right of every bar — REQUIRED.
totalLabelNOptional[str]abbreviationOptional[float]	variation	baseLabelvariationLabel)r   r   r   r(   r   r   r=   r>   r?   rA   rC   rD   rE   r   r   r    r8   r8   J   s         BLLLLIII%$KLLLDLLLL5%_```E````e(WXXXJXXXX"&L&&&&!%I%%%%#I####$(N((((((r   r8   c                  f    e Zd ZU  ed          Z ed          Zded<   dZded	<   dZ	ded
<   dS )_StackedBarTotalsr   r   uD   Shown in footer summary row — REQUIRED if a summary row is needed.r:   r<   r>   NrB   r=   rC   )
r   r   r   r   r   r   r>   r   r=   rC   r   r   r    rG   rG   U   se         :H---L5%klllEllll D    !%I%%%%%%r   rG   c                  @    e Zd ZU  ed          Zded<   dZded<   dS )_StackedBarDatar   r   zlist[_StackedBarItem]itemsNzOptional[_StackedBarTotals]totals)r   r   r   r   r   r   rK   r   r   r    rI   rI   [   sA         :H---L    *.F......r   rI   c                  N    e Zd ZU dZded<   dZded<   dZded<   ded	<   d
ed<   dS )StackedHorizontalBarChartzstacked-horizontal-bar-chartr   r   zHorizontal stacked bars showing a base value and a variation per entity (e.g. base contract vs approved variations). Use when comparing two additive components across contractors, packages, or work areas. Sort by total descending.r   uf	  { "type": "stacked-horizontal-bar-chart",
  "data": {
    "items": [
      // REQUIRED fields — chart breaks without these:
      { "name": str,           // shown as y-axis label
        "base": number,        // solid bar segment width (e.g. base contract amount)
        "total": number,       // full bar width (base + variation); drives right-side label
        "totalLabel": str,     // REQUIRED — shown to the right of every bar, e.g. "₹5.00Cr"

        // Strongly recommended — chart renders but is confusing without:
        "variation": number|null,    // lighter stacked segment; omit only when no variation exists
        "baseLabel": str|null,       // tooltip sublabel for base segment, e.g. "₹4.20Cr"
        "variationLabel": str|null,  // tooltip sublabel for variation, e.g. "₹0.80Cr"

        // Optional — not rendered in this chart:
        "abbreviation": str|null     // not rendered in this chart — can be omitted
      }
    ],
    "totals": { "total": number,              // REQUIRED if a footer summary row is needed
                "base": number|null,          // optional — not currently rendered
                "variation": number|null }|null // optional — not currently rendered
  }
}
CRITICAL — "data" wrapper is REQUIRED. Do NOT place "items" directly under the root.
CRITICAL — "base", "total", and "totalLabel" are REQUIRED on every item.
total = base + variation on every row (where variation exists).
"totals.total" = sum of ALL items' total — required for the portfolio footer row.

Concrete example (2 contractors, base contract vs variations):
  WRONG ❌ — items at root (missing "data" wrapper), totalLabel missing:
    {"type":"stacked-horizontal-bar-chart","items":[
      {"name":"CISDI","base":42000000,"variation":8000000,"total":50000000},
      {"name":"L&T",  "base":31500000,"variation":4500000,"total":36000000}]}

  CORRECT ✅ — items inside "data", required labels present:
    {"type":"stacked-horizontal-bar-chart","data":{"items":[
      {"name":"CISDI","base":42000000,"variation":8000000,"total":50000000,"totalLabel":"₹5.00Cr","baseLabel":"₹4.20Cr","variationLabel":"₹0.80Cr"},
      {"name":"L&T",  "base":31500000,"variation":4500000,"total":36000000,"totalLabel":"₹3.60Cr","baseLabel":"₹3.15Cr","variationLabel":"₹0.45Cr"}
    ],"totals":{"total":86000000,"base":73500000,"variation":12500000}}}
r   z'Literal['stacked-horizontal-bar-chart']typerI   r,   Nr   r   r   r   r   r   r   r   r   r    rM   rM   `   sx         =I====	|     ($M ( ( ( (T 2111r   rM   c                      e Zd ZU dZded<   ded<   dZded<    ed	          Zd
ed<    ed	          Zd
ed<    ed	          Z	d
ed<   dZ
ded<   dZded<   dS )_ProgressRaceItemr   r'   r(   r9   Nr@   rA   u>   GLOBAL MAX — MAX(total) across ALL items. Same on EVERY row.r:   r<   r=   z(THIS entity's own value. Varies per row.r>   z/round((total / base) * 100, 1). Top item = 100.
percentagerD   r?   )r   r   r   r(   r   rA   r   r=   r>   rR   rD   r?   r   r   r    rQ   rQ      s         BLLLLIII"&L&&&&%$deeeDeeee5%OPPPEPPPP*[\\\J\\\\#I#### $J$$$$$$r   rQ   c                  z    e Zd ZU dZded<   dZded<   dZded<   ded	<   d
ed<    ed          dd            ZdS )ProgressRaceChartzprogress-race-chartr   r   u   Horizontal bars showing each entity's progress toward a shared target as a percentage. Use for 'who is on track vs falling behind?' — milestone completion, budget consumption, tasks closed.r   u  { "type": "progress-race-chart",
  "items": [{ "name": str, "abbreviation": str|null,
              "base": number,       // GLOBAL MAX — MAX(value) across ALL items. Same on EVERY row.
              "total": number,      // THIS entity's own value (bar length). Varies per row.
              "percentage": number, // round((total / base) * 100, 1). Top item = 100, rest < 100.
              "baseLabel": str|null,  // Formatted global max, e.g. "₹6.00Cr"
              "totalLabel": str|null  // Formatted entity value, e.g. "₹3.15Cr"
            }]
}
CRITICAL — base is NOT the entity's own value. It is the shared ceiling for ALL bars — set to MAX(total) on every row.
r   zLiteral['progress-race-chart']rN   zlist[_ProgressRaceItem]rJ   afterr*   r-   c                    t          | j                  dk     r| S d | j        D             }t          |          dk    r#t          dt          |           d| d          | S )N   c                    h | ]	}|j         
S r   )r=   .0items     r    	<setcomp>z=ProgressRaceChart._base_must_be_global_max.<locals>.<setcomp>   s    222t222r      z;'base' must be the same global max on every row, but found z distinct values: z6. Set base = MAX(total) across all items on every row.)lenrJ   
ValueError)selfbasess     r    _base_must_be_global_maxz*ProgressRaceChart._base_must_be_global_max   s    tz??QK22tz222u::>>Gu::G G16G G G  
 r   N)r-   rT   )	r   r   r   r   r   r   r   r   rb   r   r   r    rT   rT      s         4I4444	s     $M     )(((""""_'"""
 
 
 #"
 
 
r   rT   c                  <    e Zd ZU  ed          Zded<   ded<   dS )_SeverityBandr   r   r'   severityintcountNr   r   r   r   r   r   r   r   r    rd   rd      s4         :H---LMMMJJJJJr   rd   c                  \    e Zd ZU dZded<   dZded<   dZded<   ded	<   d
ed<   dZded<   dS )ProportionalBandChartzproportional-band-chartr   r   u   Proportional horizontal bands from highest to lowest severity. Use for risk or quality distribution — 'how many critical vs major vs minor issues?'r   u]  { "type": "proportional-band-chart",
  "severities": [{ "severity": str, "count": number }],
  "title": str|null
}
CRITICAL — "severities" MUST be ordered most severe → least severe: "Critical" → "High" → "Medium" → "Low" → "Observation".
Omit levels with 0 count. Do NOT add a "pct" field — the component computes widths from counts.
r   z"Literal['proportional-band-chart']rN   zlist[_SeverityBand]
severitiesNr@   title)r   r   r   r   r   r   r   rl   r   r   r    rj   rj      s         8I8888	a     $M     -,,,####Er   rj   c                      e Zd ZU  edd          Zded<    ed          Zded<    ed	          Zd
ed<   dZded<   dS )_RadialFanItemr   u)   Unique hit-zone key — server-generated.defaultr;   r'   r(   u)   Leaf label and tooltip text — REQUIRED.r:   r9   u3   Arc width, dot size, and label source — REQUIRED.r<   rg   Nr@   rA   )	r   r   r   r   r(   r   r9   rg   rA   r   r   r    rn   rn      s         eB,WXXXBXXXX"MNNNDNNNN5%Z[[[E[[[["&L&&&&&&r   rn   c                      e Zd ZU dZded<   dZded<   dZded<   ded	<    ed
          Zded<    ed          Z	ded<   ded<    e
d          dd            ZdS )RadialFanTreeChartzradial-fan-tree-chartr   r   u   Radial fan tree where arc width encodes proportion of the parent total. Use for hierarchical data where relative branch size is the insight (e.g. package → subpackage counts).r   uI  { "type": "radial-fan-tree-chart",
  "total": number,     // MUST equal the sum of ALL items' count values
  "totalLabel": str,   // REQUIRED — formatted root total, e.g. "£8.70M" or "284 items"
  "items": [{ "name": str,            // REQUIRED — leaf label and tooltip text
               "count": number,        // REQUIRED — arc width, dot size, leaf label
               "abbreviation": str|null // optional — falls back to name[:6]
            }]
}
CRITICAL — "total" MUST equal sum of all item "count" values. "totalLabel" is REQUIRED (e.g. "284 items" or "£8.70M").
r   z Literal['radial-fan-tree-chart']rN   z-Must equal the sum of all items' count valuesr:   r<   r>   u"   Formatted root total — REQUIRED.r'   r?   zlist[_RadialFanItem]rJ   rU   r*   r-   c                    d | j         D             }|s| S t          |          }t          t          |          dz  d          }t          | j        |z
            |k    rt          d| j         d| d          | S )Nc                *    g | ]}|j         	|j         S N)rg   rY   s     r    
<listcomp>zFRadialFanTreeChart._total_must_equal_sum_of_counts.<locals>.<listcomp>   s!    NNNtz7M$*7M7M7Mr   g{Gz?g      ?z	'total' (z1) must equal the sum of all item 'count' values (z8). Recompute: total = sum(item.count for item in items).)rJ   summaxabsr>   r_   )r`   countsexpected	tolerances       r    _total_must_equal_sum_of_countsz2RadialFanTreeChart._total_must_equal_sum_of_counts   s    NNNNN 	Kv;;H,c22	tzH$%%	11WDJ W WW W W   r   N)r-   rr   )r   r   r   r   r   r   r   r   r>   r?   r   r}   r   r   r    rr   rr      s         6I6666	t     
$M 
 
 
 
 +***5%TUUUEUUUUe(LMMMJMMMM_'"""   #"  r   rr   c                      e Zd ZU dZded<   dZded<   dZded<   ded	<    ed
d          Zded<    ed
d          Z	ded<   ded<    e
d          dd            ZdS )SemiCircularGaugeChartzsemi-circular-gauge-chartr   r   u   Semi-circular gauge for a single headline rate or completion KPI (e.g. NCE confirmation rate). Pass raw counts — the component computes the percentage.r   u|  { "type": "semi-circular-gauge-chart",
  "confirmed": number, // Raw numerator — e.g. 15 confirmed NCEs
  "total":     number, // Raw denominator — e.g. 25 total NCEs
  "label":     str     // Context shown beneath dial, e.g. "NCEs confirmed as compensation events"
}
CRITICAL — "confirmed" and "total" are RAW COUNTS, not percentages. Never pass a pre-computed percentage.
r   z$Literal['semi-circular-gauge-chart']rN   r   zRaw numerator count)ger;   r<   	confirmedu%   Raw denominator count — must be > 0)gtr;   r>   r'   labelrU   r*   r-   'SemiCircularGaugeChart'c                f    | j         | j        k    r t          d| j          d| j         d          | S )Nz'confirmed' (z) cannot exceed 'total' (z).)r   r>   r_   )r`   s    r     _confirmed_must_not_exceed_totalz7SemiCircularGaugeChart._confirmed_must_not_exceed_total#  sD    >DJ&&WWWWWW   r   N)r-   r   )r   r   r   r   r   r   r   r   r   r>   r   r   r   r   r    r   r     s         :I::::	E     $M     /...u/DEEEIEEEE5A+RSSSESSSSJJJ_'"""   #"  r   r   c                      e Zd ZU  edd          Zded<    ed          Zded<   d	Zd
ed<    ed          Zded<    ed          Z	ded<   d	S )_SegmentedSplitItemr   u'   Unique identifier — server-generated.ro   r'   r(   z#Full display name shown in tooltipsr:   r9   Nr@   rA   z)Count for the primary segment (green bar)rf   implementedz+Count for the secondary segment (amber bar)unimplemented)
r   r   r   r   r(   r   r9   rA   r   r   r   r   r    r   r   .  s         eB,UVVVBVVVV"GHHHDHHHH"&L&&&&u)TUUUKUUUU+XYYYMYYYYYYr   r   c                  x    e Zd ZU dZded<   dZded<   dZded<   ded	<   d
Zded<   d
Zded<   d
Z	ded<   ded<   d
S )SegmentedSplitBarChartzsegmented-split-bar-chartr   r   zSplit bars dividing each entity into two complementary segments (done vs remaining, closed vs open). Use for 'what portion is complete vs outstanding?' across a list of entities.r   uK  { "type": "segmented-split-bar-chart",
  "labelA": str|null, // Label for the primary segment, e.g. "Closed", "Paid", "Implemented"
  "labelB": str|null, // Label for the secondary segment, e.g. "Open", "Outstanding", "Unimplemented"
  "unit":   str|null, // Noun for tooltip total line, e.g. "variations", "items", "NCEs"
  "items": [{ "name": str, "abbreviation": str|null,
              "implemented":   number, // REQUIRED — Segment A count — done/closed/paid
              "unimplemented": number  // REQUIRED — Segment B count — remaining/open/outstanding
           }]
}
CRITICAL — item fields are ALWAYS "implemented" and "unimplemented" regardless of domain (done/closed/paid → "implemented"; remaining/open/outstanding → "unimplemented").
Set labelA/labelB to domain names. Do NOT add "pct". Sort least complete first.
r   z$Literal['segmented-split-bar-chart']rN   Nr@   labelAlabelBunitzlist[_SegmentedSplitItem]rJ   )
r   r   r   r   r   r   r   r   r   r   r   r   r    r   r   5  s         :I::::	X     $M     /... F     F    D$$$$$$r   r   c                      e Zd ZU  ed          Z ed          Zded<    ed          Zded	<    ed
          Z	ded<   dS )
_ScaleSider   r   z6Aggregate monetary/volume amount driving the beam tiltr:   r<   valuez+Number of contributing records on this siderf   rg   uG   Formatted display value shown on the pan, e.g. '£28.4M' or '₹5.00Cr'r'   r   N)
r   r   r   r   r   r   r   r   rg   r   r   r   r    r   r   R  s         :H---L5%]^^^E^^^^#PQQQEQQQQ#lmmmEmmmmmmr   r   c                  v    e Zd ZU dZded<   dZded<   dZded<   ded	<   d
ed<   d
ed<   d
ed<   ded<   ded<   dS )BalanceScaleChartzbalance-scale-chartr   r   u   Balance beam comparing two aggregate totals — the heavier side tilts the beam. Use when the question is 'which side outweighs the other?' (accepted vs submitted quotations, claims raised vs approved).r   uV  { "type": "balance-scale-chart",
  "leftTitle":  str,  // Name of the left pan, e.g. "Accepted", "Scope Added", "Claims Approved"
  "rightTitle": str,  // Name of the right pan, e.g. "Submitted", "Scope Removed", "Claims Raised"
  "unit":       str,  // Unit for the count on each side, e.g. "quotations", "claims", "NCEs"
  "left":  { "value": number, "count": number, "label": str },
  "right": { "value": number, "count": number, "label": str }
}
— "label" = formatted display value on the pan (e.g. "£28.4M") — NOT the side name.
The side with the higher "value" tilts the beam toward it.
r   zLiteral['balance-scale-chart']rN   r'   	leftTitle
rightTitler   r   leftrightNrO   r   r   r    r   r   X  s         4I4444	D     
$M 
 
 
 
 )(((NNNOOOIIIr   r   c                  T    e Zd ZU  ed          Z ed          Zded<   ded<   d	S )
_TrendPointr   r   zHPeriod label: '2024-W01' (week), 'Jan 2024' (month), 'Q1 2024' (quarter)r:   r'   weekr<   rg   N)r   r   r   r   r   r   r   r   r   r   r    r   r   t  sJ         :H---L"lmmmDmmmmLLLLLr   r   c                  h    e Zd ZU dZded<   dZded<   dZded<   ded	<    ed
d          Zded<   dS )	TrendViewz
trend-viewr   r   zTime-series area chart for a single metric over time. Use for 'how has X changed over time?' questions. Sort points chronologically oldest-first.r   u>  { "type": "trend-view",
  "points": [{ "week": str,    // Period label — "2024-W01" (week), "Jan 2024" (month), "Q1 2024" (quarter)
               "count": number // Metric value for this period
            }]
}
CRITICAL — field is "count", NOT "value". Sort points chronologically oldest-first. Minimum 2 points.
r   zLiteral['trend-view']rN   rW   z*Minimum 2 points required to render a line)
min_lengthr;   zlist[_TrendPoint]pointsN)	r   r   r   r   r   r   r   r   r   r   r   r    r   r   y  s         +I++++	f     $M       %@l m m mFmmmmmmr   r   c                      e Zd ZU dZded<   ded<   dZded<   dZded	<   dZded
<   dZded<   dZ	ded<   dZ
ded<   dZded<   dZded<   dS )_WeeklyFlowItemr   r'   r(   r9   Nr@   rA   rB   r=   rC   r>   rR   rD   rE   r?   )r   r   r   r(   r   rA   r=   rC   r>   rR   rD   rE   r?   r   r   r    r   r     s         BLLLLIII"&L&&&& D    !%I%%%%!E!!!!"&J&&&&#I####$(N(((( $J$$$$$$r   r   c                  N    e Zd ZU dZded<   dZded<   dZded<   ded	<   d
ed<   dS )
WeeklyFlowzweekly-flowr   r   u   Sankey flow diagram showing how volume moves through pipeline stages (submitted → reviewed → approved). Use for 'where does volume go at each stage?' questions.r   u  { "type": "weekly-flow",
  "items": [{ "name": str, "abbreviation": str|null,
              "base": number|null,        // Total input volume of the FIRST stage — same on every row
              "variation": number|null,   // Drop-off at this stage (negative = lost volume)
              "total": number|null,       // Volume remaining after this stage (base + variation)
              "percentage": number|null,  // round(total / items[0].total * 100, 1) — share of initial flow
              "baseLabel": str|null, "variationLabel": str|null, "totalLabel": str|null }]
}
Items are flow stages in order. First item: variation=0, total=base, percentage=100.
Each subsequent stage: variation=drop-off (negative), percentage=round(total/items[0].total*100,1).
r   zLiteral['weekly-flow']rN   zlist[_WeeklyFlowItem]rJ   NrO   r   r   r    r   r     sw         ,I,,,,	C     $M     !         r   r   zlist[type[_ChartBase]]_CHART_MODEL_REGISTRYrN   )discriminatorzTypeAdapter[AnyChartContent]_chart_adapterr,   r0   r-   AnyChartContentc                6    t                               |           S zKParse and validate a raw content dict; raises ValidationError on bad input.)r   validate_pythonr,   s    r    validate_chart_contentr     s    ))$///r   c                  J    e Zd ZU  ed          Zded<   ded<   dZded<   dS )		_StatItemr   r   r'   r   r   Nr@   icon)r   r   r   r   r   r   r   r   r   r    r   r     sG         :H---LJJJJJJDr   r   c                  N    e Zd ZU dZded<   dZded<   dZded<   ded	<   d
ed<   dS )StatsHighlightstatsr   r#   z2a few key KPI numbers (total count, sum, average).r$   u  { "type": "stats",
  "items": [{ "value": str, "label": str, "icon": str|null }]
}
icon rules — apply the first matching rule:
  "pound"          — value starts with £, $, or €  (e.g. "£342M")
  "percent"        — value contains %               (e.g. "47%")
  "shapes"         — everything else: plain numbers, decimals, day values, ranges, any value with a unit (e.g. "150", "45.7", "14 Days", "40-60 Days", "52.0 days"). Never use null.
NEVER set a contractor icon when value is a number, amount, or percentage.r   zLiteral['stats']rN   zlist[_StatItem]rJ   Nr   r   r   r#   r   r$   r   r   r   r    r   r     so         #*M****+_____$NM N N N N r   r   c                  J    e Zd ZU  ed          Zded<   ded<   dZded<   dS )	_RankedItemr   r   r'   r9   r   Nr@   kpiLabel)r   r   r   r   r   r   r   r   r   r    r   r     sG         :H---LIIIJJJ"H""""""r   r   c                  N    e Zd ZU dZded<   dZded<   dZded<   ded	<   d
ed<   dS )RankedHighlightrankedr   r#   z&a ranked list with values and colours.r$   zV{ "type": "ranked",
  "items": [{ "name": str, "value": str, "kpiLabel": str|null }]
}r   zLiteral['ranked']rN   zlist[_RankedItem]rJ   Nr   r   r   r    r   r     sj         #+M+++++SSSSS$M    
 r   r   c                  J    e Zd ZU  ed          Zded<   ded<   dZded<   dS )	_ProportionChipr   r   r'   r   r   Nr@   colorr   r   r   r   r   r   r   r   r   r    r   r   
  G         :H---LJJJJJJEr   r   c                      e Zd ZU dZded<   dZded<   dZded<   ded	<   d
ed<   ded<   ded<   ded<   d
ed<   ded<   ded<   ded<   dZded<   dS )ProportionHighlight
proportionr   r#   z0two-part proportion split (e.g. open vs closed).r$   z{ "type": "proportion",
  "leftPct": number, "leftLabel": str, "leftValue": str, "leftColor": str,
  "rightPct": number, "rightLabel": str, "rightValue": str, "rightColor": str,
  "chips": [{ "value": str, "label": str, "color": str|null }]|null
}r   zLiteral['proportion']rN   r<   leftPctr'   	leftLabel	leftValue	leftColorrightPct
rightLabel
rightValue
rightColorNzOptional[list[_ProportionChip]]chipsr   r   r   r#   r   r$   r   r   r   r   r    r   r     s         #/M////+]]]]]$M      NNNNNNNNNNNNOOOOOOOOOOOO-1E111111r   r   c                  J    e Zd ZU  ed          Zded<   ded<   dZded<   dS )		_RingChipr   r   r'   r   r   Nr@   r   r   r   r   r    r   r   (  r   r   r   c                      e Zd ZU dZded<   dZded<   dZded<   ded	<    ed
d          Zded<   ded<   ded<   dZ	ded<   dS )RingHighlightringr   r#   u   a single percentage ring (e.g. commitment %). Accepts exactly ONE pct value (0–100 integer). Never pass two pct values or an array.r$   u  { "type": "ring",
  "pct": number,
  "label": str, "color": str,
  "chips": [{ "value": str, "label": str, "color": str|null }]|null
}
IMPORTANT: "pct" is a single integer 0–100 representing one percentage.
Do NOT send two pct fields, a second percentage, or an array. If the data has
two complementary percentages (e.g. 45% implemented, 55% pending), use
"proportion" instead of "ring".r   zLiteral['ring']rN   g        g      Y@)r   ler<   pctr'   r   r   NzOptional[list[_RingChip]]r   )
r   r   r   r#   r   r$   r   r   r   r   r   r   r    r   r   .  s         #)M))))	1     	$#M 	# 	# 	# 	# #%(((C((((JJJJJJ'+E++++++r   r   c                  <    e Zd ZU  ed          Zded<   ded<   dS )
_BadgeItemr   r   r'   textz Literal['red', 'amber', 'green']re   Nrh   r   r   r    r   r   H  s9         :H---LIII......r   r   c                  \    e Zd ZU dZded<   dZded<   dZded<   ded	<   d
ed<   dZded<   dS )BadgesHighlightbadgesr   r#   zseverity or status labels.r$   zp{ "type": "badges",
  "items": [{ "text": str, "severity": "red"|"amber"|"green" }],
  "textSize": number|null
}r   zLiteral['badges']rN   zlist[_BadgeItem]rJ   NrB   textSize)r   r   r   r#   r   r$   r   r   r   r   r    r   r   M  s{         #+M+++++GGGGG$M      $H$$$$$$r   r   c                  F    e Zd ZU  ed          Zded<   ded<   ded<   dS )	_DotStripDotr   r   r<   valr'   r   r9   Nrh   r   r   r    r   r   ]  s=         :H---LJJJJJJIIIIIr   r   c                  <    e Zd ZU  ed          Zded<   ded<   dS )_DotStripChipr   r   r'   r   r   Nrh   r   r   r    r   r   c  s4         :H---LJJJJJJJJr   r   c                  z    e Zd ZU dZded<   dZded<   dZded<   ded	<   d
ed<   d
ed<   ded<   ded<   dZded<   dS )DotStripHighlightz	dot-stripr   r#   z+a min/max range with named dots on a strip.r$   z{ "type": "dot-strip",
  "min": number, "max": number, "unit": str,
  "dots": [{ "val": number, "color": str, "name": str }],
  "chips": [{ "value": str, "label": str, }]|null
}r   zLiteral['dot-strip']rN   r<   minrx   r'   r   zlist[_DotStripDot]dotsNzOptional[list[_DotStripChip]]r   r   r   r   r    r   r   h  s         #.M....+XXXXX$M     JJJJJJIII+/E//////r   r   c                  z    e Zd ZU  ed          Zded<   ded<   ded<   ded<   d	Zd
ed<   d	Zded<   d	Zd
ed<   d	S )_ScorecardRowr   r   r'   r9   r   r<   r   r   Nr@   badgez*Optional[Literal['green', 'amber', 'red']]badgeSeveritysublabel)	r   r   r   r   r   r   r   r   r   r   r   r    r   r   |  s{         :H---LIIIJJJJJJJJJE@DMDDDD"H""""""r   r   c                  N    e Zd ZU dZded<   dZded<   dZded<   ded	<   d
ed<   dS )ScorecardRowsHighlightzscorecard-rowsr   r#   z2contractor/item scorecard with inline bar per row.r$   z{ "type": "scorecard-rows",
  "items": [{ "name": str, "value": str, "pct": number, "color": str,
               "badge": str|null, "badgeSeverity": "green"|"amber"|"red"|null,
               "sublabel": str|null }]
}r   zLiteral['scorecard-rows']rN   zlist[_ScorecardRow]rJ   Nr   r   r   r    r   r     sj         #3M3333+_____$M     $###r   r   c                  <    e Zd ZU  ed          Zded<   ded<   dS )_ComparisonRowr   r   r'   r   	list[str]cellsNrh   r   r   r    r   r     s9         :H---LJJJr   r   c                  X    e Zd ZU dZded<   dZded<   dZded<   ded	<   d
ed<   ded<   dS )ComparisonRowsHighlightzcomparison-rowsr   r#   z a side-by-side comparison table.r$   z_{ "type": "comparison-rows",
  "columns": [str],
  "rows": [{ "label": str, "cells": [str],}]
}r   zLiteral['comparison-rows']rN   r   columnszlist[_ComparisonRow]rowsNr   r   r   r    r   r     sv         #4M4444+MMMMM$M     %$$$r   r   zlist[type[_HighlightBase]]_HIGHLIGHT_MODEL_REGISTRYz TypeAdapter[AnyHighlightContent]_highlight_adapterAnyHighlightContentc                6    t                               |           S r   )r   r   r   s    r    validate_highlight_contentr     s    --d333r   c                *    g | ]}|j         |j        fS r   )r   r   rZ   ms     r    rv   rv     s/     & & &+,Q[!%&& & &r   zlist[tuple[str, str]]CHART_TYPESc                (    i | ]}|j         |j        S r   )r   r   r   s     r    
<dictcomp>r     s+     ! ! !%&AK! ! !r   zdict[str, str]CHART_SCHEMASc              #  $   K   | ]}|j         V  d S ru   )r   r   s     r    	<genexpr>r     s$      ,X,XQQ[,X,X,X,X,X,Xr   zfrozenset[str]VALID_CHART_KEYSc                *    g | ]}|j         |j        fS r   )r#   r$   r   s     r    rv   rv     s/     . . .34Q_a-.. . .r   KEY_HIGHLIGHT_TYPESc                (    i | ]}|j         |j        S r   )r#   r   r   s     r    r   r     s+     ) ) ))*AOQ_) ) )r   KEY_HIGHLIGHT_SCHEMAS)z#4C93D9z#EC772Az#5DA537z#818FF8z#EEBF3Br   CHART_COLORS)r,   r0   r-   r   )r,   r0   r-   r   )Hr5   
__future__r   r1   typingr   r   r   r   r   r	   pydanticr
   r   r   r   r   r   r"   r&   r8   rG   rI   rM   rQ   rT   rd   rj   rn   rr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   	frozensetr   r  r  r  r   r   r    <module>r
     s	    0 # " " " " "  E E E E E E E E E E E E E E E E O O O O O O O O O O O O O O& & & & & & & && & & & &Y & & &    i   0	) 	) 	) 	) 	)o 	) 	) 	)& & & & &	 & & &/ / / / /i / / /
1 1 1 1 1
 1 1 1l% % % % % % % %! ! ! ! !
 ! ! !L    I   
         J      ,' ' ' ' '_ ' ' '# # # # # # # #P    Z   >Z Z Z Z Z/ Z Z Z% % % % %Z % % %:n n n n n n n n    
   8    )   
n n n n n
 n n n*
% 
% 
% 
% 
%o 
% 
% 
%! ! ! ! ! ! ! !8 	1  	 	 	 	 	!	
 
E! 0;{?/K/K K K K K0 0 0 0    	       ^   &# # # # #) # # #	 	 	 	 	n 	 	 	         i      2 2 2 2 2. 2 2 20         	      , , , , ,N , , ,4/ / / / / / / /
% % % % %n % % %     9       I   
0 0 0 0 0 0 0 0(# # # # #I # # #    ^        Y   
    n   & 	9  	 	 	 	  		!	 
E
!  8C{CV7W7W  W W W W4 4 4 4& &0E& & &    ! !*?! ! !     $-9,X,XBW,X,X,X#X#X  X X X X. .8Q. . .     ) ).G) ) )      RQQ Q Q Q Q Q Qr   