+
    _9j                     <   ^ RI t ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIHt ^ RIHt ] P                  P                  R4      t] P                  P                  ]R4      t] P                  P                  ]R4      t] P                  P                  ]R4      t] P                  P                  ]R4      t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&/tR%tRtR'. RGOR(. RHOR). RIOR*. RJOR+. RKOR,. RLOR-. RMOR.. RNOR/R0R1.R2R3R4.R5. ROO/tR6 tR7 tR8 tR9 tR: tR; tR< tR= t]^ RR>3R? lt]R@8X  ds   ^ RItRA]P@                  9   ;'       g    RB]P@                  9   t!]! ]!RC7      t"]"'       d6   ]!'       g,   ]#! 4        ]#! RD4       ]#! RE4       ]#! 4        ]#! RF] 24       R# R# R# R# )P    N)datetime)Counterz	~/Desktopzworkorders.jsonzcomentarios.jsonlzmotor_pesquisa_visual.htmlzpuxar_banana.gifz$8b959fa9-92da-48af-83a8-8960b86e5d3ez	Por fazerz$08059026-de6e-8b90-bde1-4ae92f676643Suspensoz$32fbd621-0458-4e3e-968d-77ffc56c892azEm cursoz$d17964fa-1174-ed36-eb9c-f50d58980859u    A aguardar intervenção externaz$658ee047-7054-cae7-8e55-b9ab543b67c7zA aguardar materialz$df9e4a1c-4670-8242-3843-d0a3e8d175dau   A aguardar reparação da peçaz$7b419f98-ebc2-dcd4-8db2-312cdfbe8072u    A aguardar validação qualidadez$8240c123-1dd5-860b-6c08-041805b85dbfu!   A aguardar validação segurançaz$ecdfc42d-e39a-d2e4-1455-78fc9d35dc05zEm analise com o fabricantez$85c3cbee-2503-4bf5-8271-b13b41b02a08u
   Concluídaz$529a1f42-ebb9-0688-3523-54e7b4dd5783u   A aguardar documentaçãoz$1ee4fca1-325d-948d-af5d-bc5fefa53635zEm testez$0ef45d0d-1901-959e-8727-869a178e0f6bu    Reparação corretiva temporariaz$d67894ef-2d53-4e63-a840-71089d2cc851Anuladaz$ada41e02-5e6a-4dc7-9f5c-e6b14340f1a6	Encerradaznao arrancaerro
carregador
emergenciacarregamentovibracaofugaoleozar comprimido
pneumaticoz
pressao arbarulhoruidozsom estranhosobreaquecimentoc                 &   V '       g   R # \         P                  ! R\        V 4      4      p R P                  R V  4       4      p V P	                  4       p \
        P                  ! RRV 4      p \
        P                  ! RRV 4      P                  4       p V # ) NFKDc              3   b   "   T F%  p\         P                  ! V4      '       d   K!  Vx  K'  	  R # 5iN)unicodedata	combining).0cs   & 4C:\Users\ESI FRANCE\Desktop\motor_pesquisa_visual.py	<genexpr>_normalizar.<locals>.<genexpr>E   s     Eu!K,A,A!,DAAus   /
/z[^\w\s] z\s+)r   	normalizestrjoinlowerresubstrip)textos   &r   _normalizarr(   A   so    !!&#e*5EGGEuEEEKKMEFF:sE*EFF63&,,.EL    c                 b    V '       g   R # \         P                  ! RR V P                  4       4      # )r   z[A-Z]+$)r$   r%   upper)
asset_codes   &r   _familia_assetr-   L   s$    66*b*"2"2"455r)   c                     V '       g   . # . ROp\        4       pV P                  4       pV F1  p\        P                  ! WC4       F  pVP	                  V4       K  	  K3  	  \        V4      # )\b\d{3,4}[A-Z]?\b)r/   z\b[A-Z]{2,3}\d{1,3}[A-Z]?\bz\b[A-Z]\d{2,3}[A-Z]?\bz
\bP\d{3}\b)setr+   r$   findalladdsorted)r'   padroesencontradostexto_upperpms   &     r   _extrair_codigos_maquinar9   R   s[    	G %K++-KA+AOOA ,  +r)   c                     . p\         P                  4        F  w  r#W 9   g   K  VP                  V4       K   	  V'       d    V R ,           R P                  V4      ,           # V # )r   )	SINONIMOSitemsextendr"   )
texto_normextraschavealtss   &   r   _expandir_sinonimosrB   c   sP    F (MM$ ) C#((6"222r)   c                     \         P                  P                  \        4      '       g   \	        R \         R24       . # \        \        RR7      ;_uu_ 4       p \        P                  ! V 4      uuRRR4       #   + '       g   i     R# ; i)u   ❌ u    não encontrado.utf-8encodingN)ospathexistsWORKORDERS_FILEprintopenjsonload)fs    r   _carregar_workordersrP   p   sV    77>>/**_%%678		o	0	0Ayy| 
1	0	0	0s   A99B
	c                     \         P                  P                  \        4      '       g   \	        R \         R24       / # / p \        \        RR7      ;_uu_ 4       pV F?  pVP                  4       pV'       g   K   \        P                  ! V4      pW0VR,          &   KA  	  RRR4       V #   \         d     K[  i ; i  + '       g   i     T # ; i)   ⚠️ u#    não encontrado. Sem comentários.rD   rE   idN)
rG   rH   rI   COMENTARIOS_FILErK   rL   r&   rM   loads	Exception)	resultadorO   linhaobjs       r   _carregar_comentariosrZ   x   s    77>>*++())LMN	I		1	1QEKKMEjj''*#d)$  
2    
2	1 s0    B<;!B*B<*B9	5B<8B9	9B<<C	c                     \         P                  P                  \        4      '       g   \	        R\         R24       R#  \        \        R4      ;_uu_ 4       p V P                  4       pRRR4       \        P                  ! X4      P                  R4      p\        \        V4      R,          ^4      p\	        RV R	24       R
V 2#   + '       g   i     Le; i  \         d   p\	        RT 24        Rp?R# Rp?ii ; i)uP   Lê o GIF de loading e devolve como data URI base64.
Devolve "" se não existir.rR   u-    não encontrado — será usado spinner CSS.r   rbNasciii   u   🎨 GIF de loading carregado (z KB)zdata:image/gif;base64,u   ⚠️ Erro ao ler GIF: )rG   rH   rI   GIF_FILErK   rL   readbase64	b64encodedecoderoundlenrV   )rO   dadosb64
tamanho_kbes        r   _carregar_gif_base64ri      s     77>>(##z!NOP	(D!!QFFHE "u%,,W53u:,a0
/
|4@A'u-- "!  (,-s1    C C)AC C	C C=$C88C=c                 R   \        R \        V 4       R24       . pV  EF  pVP                  R4      pVP                  V/ 4      pVP                  RR4      VP                  RR4      VP                  RR4      VP                  RR4      VP                  RR4      VP                  R	R4      VP                  R
R4      VP                  R4      ;'       g    R.pVP                  R. 4       F$  pVP                  VP                  RR4      4       K&  	  RP	                  R V 4       4      p\        V4      p	\        V	4      p	. p
VP                  R. 4       Fl  pT
P                  RVP                  R4      ;'       g    RRVP                  R4      ;'       g    RRVP                  R4      ;'       g    RR,          /4       Kn  	  VP                  / RVbRVP                  R4      bRVP                  R4      bRVP                  R4      bR\        VP                  RR4      4      bRVP                  R4      bRVP                  R4      bR	VP                  R	4      bR
VP                  R
4      bRVP                  R4      bRVP                  R4      bRVP                  R4      bRVP                  R4      bRVP                  R4      bR\        P                  VP                  R4      R4      bRVP                  R4      bRV	bRV
/C4       EK  	  \        R\        V4       R24       V# )u   📚 A indexar z OTs...rS   coder   name	assetCode	assetNamebrand
brandModelserialNumberremedycommentstextr   c              3   J   "   T F  q'       g   K  \        V4      x  K  	  R # 5ir   )r!   )r   r7   s   & r   r   $_construir_indice.<locals>.<genexpr>   s     ;v!vs1vvvs   	##authorDesconhecidodate:Ni  NassetFamily
technicianurgencycriticalitybeginDateTimestateId	stateNamer>   comentariosu   ✅ Índice pronto: z documentos)	rK   rd   getappendr"   r(   rB   r-   ESTADOS)
workordersr   docswoot_idcompartesr   texto_brutor>   comentarios_simpless   &&         r   _construir_indicer      s)   	OC
O,G
45DtooeR( FF62FF62FF;#FF;#FF7BFF<$FF>2&FF8""	
 R(AMM!%%+, ) hh;v;; -
(4
 R(A&&!%%/;;^f++v,,"e4(  ) 	 
%
BFF6N
 BFF6N
 ,	

 >"&&b*AB
 ,
 RVVG_
 "&&.
 BFF>2
 "&&.
 rvvi(
 266-0
 RVVO4
 rvvi(
 RVVI%6;
  bffX&!
" *#
$ .%
 	; d 
 T;
78Kr)   Fc                    \        R V  24       \        4       pV'       g   \        R4       R# V'       d   VRV p\        4       p\        R\        V4       R24       \	        WE4      p\        4       pV'       d   RV R2pRV R2p	MRpRp	\        V4      p
\        R	 V 4       4      p\        R
 V 4       4      p\        P                  ! 4       P                  R4      p\        4       pV FY  pVP                  R4      ;'       g    RP                  4       P                  4       pV'       g   KD  VV;;,          ^,          uu&   K[  	  VP                  ^4       UUu. uF  w  ppVNK
  	  ppp. pV F  pVP                  / RVR,          bRVR,          bRVR,          bRVR,          bRVR,          bRVR,          bRVR,          bRVR,          bRVR,          bRVR,          bRVR,          bRVR,          bRVR,          bRVR,          bRVR,          bRVR,          bRVR,          b4       K  	  \         P"                  ! RVR\$        R \&        R!\(        R"\*        R#V/RR$7      pRpV'       d   V^ 8  d   R%V R&2pR'V R(V R)V	 R*V R+V
 R,V R-V R.\        V4       R/V R02p \,        P.                  ! \,        P0                  P3                  V 4      R1R27       \5        V R3R4R57      ;_uu_ 4       pVP7                  V4       RRR4       \9        \,        P0                  P;                  V 4      RA,          ^4      p\        R64       \        R7V  24       \        R8V R924       \        R:V
 R;V R<V R=24       T'       d%   \        R?4       \B        P4                  ! R@T  24       R1# u uppi   + '       g   i     L; i  \<         d.   p\        R>T 24       ^ RIpTPA                  4         Rp?R# Rp?ii ; i)Bu'   📝 A gerar motor de pesquisa visual: u!   ❌ Sem workorders. Nada a gerar.FNu   💬 u    OTs com comentários em cachez
<img src="z$" alt="loading" class="loading-gif">z,<div class="loading-spinner-fallback"></div>c              3   V   "   T F  qP                  R 4      '       g   K  ^x  K!  	  R# 5i)rr   Nr   r   ds   & r   r   %gerar_motor_visual.<locals>.<genexpr>   s     81hQQ   )
)c              3   V   "   T F  qP                  R 4      '       g   K  ^x  K!  	  R# 5i)r   Nr   r   s   & r   r   r      s     BTUU=-A!!Tr   z%d/%m/%Y %H:%M:%Srm   r   rS   rk   rl   rz   rn   ro   rp   rq   r{   r|   r}   r~   r   r   rr   r   r   	sinonimosestadosestadoEncerradaestadoConcluidamaquinas)ensure_asciiz$<meta http-equiv="refresh" content="z">z<!DOCTYPE html>
<html lang="pt">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
u=,  
<title>🔍 Motor de Pesquisa de Avarias</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
    min-height: 100vh;
    padding: 20px;
}
.container { max-width: 1200px; margin: 0 auto; }

/* ============ LOADING OVERLAY (inicial) ============ */
#loadingOverlay {
    position: fixed;
    inset: 0;
    background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
    z-index: 9999;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: white;
    transition: opacity 0.5s ease;
}
#loadingOverlay.esconder {
    opacity: 0;
    pointer-events: none;
}
#loadingOverlay .loading-gif {
    width: 140px;
    height: 140px;
    object-fit: contain;
    margin-bottom: 22px;
    filter: drop-shadow(0 0 25px rgba(245, 87, 108, 0.4));
}
#loadingOverlay .loading-spinner-fallback {
    width: 80px;
    height: 80px;
    border: 6px solid rgba(255,255,255,0.15);
    border-top: 6px solid #f5576c;
    border-radius: 50%;
    animation: spinnerSpin 1s linear infinite;
    margin-bottom: 22px;
}

/* ============ OVERLAY DE PESQUISA ============ */
#pesquisaOverlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(26, 26, 46, 0.85);
    backdrop-filter: blur(4px);
    z-index: 9998;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: white;
    transition: opacity 0.25s ease;
}
#pesquisaOverlay.activo {
    display: flex;
}
#pesquisaOverlay.esconder {
    opacity: 0;
    pointer-events: none;
}
#pesquisaOverlay .loading-gif {
    width: 110px;
    height: 110px;
    object-fit: contain;
    margin-bottom: 18px;
    filter: drop-shadow(0 0 20px rgba(245, 87, 108, 0.5));
}
#pesquisaOverlay .loading-spinner-fallback {
    width: 70px;
    height: 70px;
    border: 5px solid rgba(255,255,255,0.15);
    border-top: 5px solid #f5576c;
    border-radius: 50%;
    animation: spinnerSpin 1s linear infinite;
    margin-bottom: 18px;
}

/* Spinner / dots (partilhados) */
@keyframes spinnerSpin {
    to { transform: rotate(360deg); }
}
.loading-titulo {
    font-size: 18px;
    font-weight: 600;
    margin-bottom: 8px;
    letter-spacing: 0.3px;
}
.loading-sub {
    font-size: 13px;
    opacity: 0.75;
}
.loading-dots::after {
    content: "";
    animation: dots 1.5s steps(4, end) infinite;
}
@keyframes dots {
    0%   { content: ""; }
    25%  { content: "."; }
    50%  { content: ".."; }
    75%  { content: "..."; }
    100% { content: ""; }
}

/* ============ HEADER ============ */
.header {
    background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
    color: white;
    padding: 28px 32px;
    border-radius: 20px;
    margin-bottom: 22px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.2);
    position: relative;
    overflow: hidden;
}
.header::before {
    content: '';
    position: absolute;
    top: -50%; right: -20%;
    width: 500px; height: 500px;
    background: rgba(255,255,255,0.05);
    border-radius: 50%;
}
.header h1 { font-size: 26px; margin-bottom: 6px; position: relative; z-index: 1; }
.header .subtitle { opacity: 0.85; font-size: 14px; position: relative; z-index: 1; }
.header .timestamp { opacity: 0.7; font-size: 12px; margin-top: 6px; position: relative; z-index: 1; }
.header .stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 12px;
    margin-top: 18px;
    position: relative; z-index: 1;
}
.stat-item {
    background: rgba(255,255,255,0.12);
    backdrop-filter: blur(10px);
    padding: 12px 14px;
    border-radius: 12px;
    text-align: center;
}
.stat-item .number { font-size: 22px; font-weight: bold; }
.stat-item .label { font-size: 11px; opacity: 0.85; margin-top: 4px; }

/* ============ SEARCH BOX ============ */
.search-box {
    background: white;
    padding: 22px 26px;
    border-radius: 16px;
    box-shadow: 0 4px 15px rgba(0,0,0,0.08);
    margin-bottom: 20px;
    border-left: 6px solid #f5576c;
}
.search-box textarea {
    width: 100%;
    min-height: 70px;
    padding: 12px 14px;
    border: 2px solid #e0e0e0;
    border-radius: 10px;
    font-size: 15px;
    font-family: inherit;
    resize: vertical;
    transition: border-color 0.2s;
}
.search-box textarea:focus {
    outline: none;
    border-color: #f5576c;
}
.search-actions {
    display: flex;
    gap: 10px;
    margin-top: 12px;
    align-items: center;
    flex-wrap: wrap;
}
.btn {
    padding: 10px 22px;
    border: none;
    border-radius: 10px;
    cursor: pointer;
    font-weight: 600;
    font-size: 14px;
    transition: all 0.25s;
}
.btn-primary { background: #f5576c; color: white; }
.btn-primary:hover { background: #d6304a; }
.btn-primary:disabled { background: #bbb; cursor: not-allowed; }
.btn-clear {
    background: #f8f9fa; border: 2px solid #e0e0e0; color: #333;
}
.btn-clear:hover { background: #e9ecef; border-color: #f5576c; }
.btn-clear:disabled { opacity: 0.5; cursor: not-allowed; }

.opcoes {
    display: flex; gap: 16px; align-items: center;
    margin-left: auto; font-size: 13px; color: #555;
}
.opcoes label { display: flex; align-items: center; gap: 6px; font-weight: 600; }
.opcoes input[type="number"] {
    padding: 6px 10px; border: 2px solid #e0e0e0;
    border-radius: 8px; width: 80px; font-size: 13px;
}
.opcoes input[type="number"]:focus { outline: none; border-color: #f5576c; }

.sugestoes {
    margin-top: 10px;
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    align-items: center;
    font-size: 12px;
    color: #888;
}
.sugestao {
    background: #f8f9fa;
    border: 1px solid #e0e0e0;
    border-radius: 14px;
    padding: 4px 12px;
    cursor: pointer;
    font-size: 12px;
    transition: all 0.2s;
}
.sugestao:hover {
    background: #f5576c; color: white; border-color: #f5576c;
}

/* ============ RESULTADOS ============ */
.resultado-info {
    background: white;
    padding: 12px 20px;
    border-radius: 12px;
    margin-bottom: 16px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.06);
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    font-size: 14px;
    color: #555;
}
.resultado-info .maq-tag {
    background: #e3f2fd; color: #1976d2;
    padding: 3px 10px; border-radius: 12px;
    font-size: 12px; font-weight: 700;
    margin-left: 6px;
}

.solucoes {
    background: white;
    padding: 20px 24px;
    border-radius: 14px;
    margin-bottom: 20px;
    box-shadow: 0 4px 15px rgba(0,0,0,0.08);
}
.solucoes h3 {
    font-size: 16px; color: #1a1a2e;
    margin-bottom: 14px;
    display: flex; align-items: center; gap: 8px;
}
.solucao-item {
    display: grid;
    grid-template-columns: 70px 1fr auto;
    gap: 14px;
    align-items: center;
    padding: 10px 0;
    border-bottom: 1px solid #f0f0f0;
}
.solucao-item:last-child { border-bottom: none; }
.solucao-pct {
    font-weight: 700; font-size: 17px;
    color: #f5576c;
}
.solucao-bar-wrap {
    background: #f0f0f0; height: 10px; border-radius: 6px;
    overflow: hidden; margin-top: 6px;
}
.solucao-bar {
    height: 100%;
    background: linear-gradient(90deg, #f5576c, #f093fb);
    border-radius: 6px;
    transition: width 0.4s;
}
.solucao-texto {
    font-size: 13px; color: #333; line-height: 1.4;
    word-break: break-word;
}
.solucao-casos {
    font-size: 11px; color: #888; margin-top: 3px;
    white-space: nowrap;
}

.casos-header {
    font-size: 15px; color: #333;
    margin: 20px 0 12px;
    font-weight: 700;
}
.caso {
    background: white;
    border-radius: 14px;
    padding: 16px 20px;
    margin-bottom: 12px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.06);
    border-left: 6px solid #667eea;
    transition: all 0.25s;
    opacity: 0;
    animation: fadeInUp 0.4s ease forwards;
}
.caso:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 25px rgba(0,0,0,0.12);
}
.caso.score-alto { border-left-color: #28a745; }
.caso.score-medio { border-left-color: #ffc107; }
.caso.score-baixo { border-left-color: #dc3545; }

.caso-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 14px;
    flex-wrap: wrap;
}
.caso-code {
    font-family: monospace; font-size: 14px;
    font-weight: 700; color: #667eea;
}
.caso-score {
    font-size: 20px; font-weight: 800;
    padding: 4px 14px; border-radius: 12px;
    white-space: nowrap;
}
.score-alto .caso-score { background: #d4edda; color: #155724; }
.score-medio .caso-score { background: #fff3cd; color: #856404; }
.score-baixo .caso-score { background: #f8d7da; color: #721c24; }

.caso-title {
    font-size: 14px; color: #333;
    margin: 8px 0;
    font-weight: 600;
}
.caso-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    font-size: 12px;
    color: #666;
    margin: 10px 0;
}
.caso-meta span { display: flex; align-items: center; gap: 4px; }
.caso-remedy {
    background: #f8f9fa;
    border-left: 4px solid #667eea;
    border-radius: 8px;
    padding: 10px 14px;
    margin-top: 10px;
    font-size: 13px;
    color: #333;
    line-height: 1.5;
}
.caso-remedy strong { color: #667eea; }

.caso-comentarios-btn {
    background: none; border: none;
    color: #667eea; cursor: pointer;
    font-size: 12px; font-weight: 600;
    padding: 6px 0; margin-top: 8px;
}
.caso-comentarios-btn:hover { text-decoration: underline; }

.caso-comentarios {
    display: none;
    margin-top: 10px;
    border-top: 1px dashed #e0e0e0;
    padding-top: 10px;
}
.caso-comentarios.aberto { display: block; }
.caso-comentario {
    background: #f8f9fa;
    border-radius: 6px;
    padding: 8px 12px;
    margin-bottom: 6px;
    font-size: 12px;
    border-left: 3px solid #e0e0e0;
}
.caso-comentario .author { font-weight: 700; color: #667eea; }
.caso-comentario .date { color: #888; margin-left: 8px; font-size: 11px; }
.caso-comentario .text {
    margin-top: 4px; color: #333;
    white-space: pre-wrap; word-break: break-word;
}

.no-result {
    text-align: center;
    padding: 50px 20px;
    color: #666;
    background: white;
    border-radius: 14px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.06);
}
.no-result .icon { font-size: 56px; margin-bottom: 14px; }
.no-result h2 { font-size: 18px; margin-bottom: 8px; color: #333; }

.estado-badge {
    display: inline-block;
    padding: 2px 10px;
    border-radius: 12px;
    font-size: 11px;
    font-weight: 600;
    background: #e9ecef;
    color: #495057;
}
.estado-Encerrada { background: #e9ecef; color: #495057; }
.estado-Concluída { background: #d4edda; color: #155724; }
.estado-Em_curso { background: #fff3cd; color: #856404; }
.estado-Por_fazer { background: #f8d7da; color: #721c24; }
.estado-Suspenso { background: #ffe5d0; color: #7b4a1e; }

@keyframes fadeInUp {
    from { opacity: 0; transform: translateY(12px); }
    to { opacity: 1; transform: translateY(0); }
}

@media (max-width: 768px) {
    .solucao-item { grid-template-columns: 1fr; }
    .opcoes { width: 100%; margin-left: 0; justify-content: flex-start; }
    .header h1 { font-size: 20px; }
    .caso-header { flex-direction: column; gap: 6px; }
}
</style>
</head>
<body>

<!-- LOADING OVERLAY (inicial) -->
<div id="loadingOverlay">
    u   
    <div class="loading-titulo">🔍 A preparar o motor de pesquisa</div>
    <div class="loading-sub">A carregar índice de OTs e comentários<span class="loading-dots"></span></div>
</div>

<!-- OVERLAY DE PESQUISA -->
<div id="pesquisaOverlay">
    u  
    <div class="loading-titulo">🔎 A analisar casos...</div>
    <div class="loading-sub">A comparar com <span id="pesquisaTotal">0</span> OTs<span class="loading-dots"></span></div>
</div>

<div class="container">

    <div class="header">
        <h1>🔍 Motor de Pesquisa de Avarias</h1>
        <div class="subtitle">Encontra casos históricos semelhantes e soluções aplicadas</div>
        <div class="timestamp">📅 Índice gerado em zl</div>

        <div class="stats">
            <div class="stat-item">
                <div class="number">u   </div>
                <div class="label">📋 OTs indexadas</div>
            </div>
            <div class="stat-item">
                <div class="number">u   </div>
                <div class="label">💡 Com solução</div>
            </div>
            <div class="stat-item">
                <div class="number">u   </div>
                <div class="label">💬 Com comentários</div>
            </div>
            <div class="stat-item">
                <div class="number">uD  </div>
                <div class="label">🖥️ Máquinas</div>
            </div>
        </div>
    </div>

    <div class="search-box">
        <textarea id="pergunta" placeholder="Descreve a avaria... Ex: 754 erro emergencia carregador nao arranca" onkeydown="if(event.ctrlKey && event.key==='Enter') pesquisar()"></textarea>
        <div class="search-actions">
            <button class="btn btn-primary" onclick="pesquisar()">🔍 Pesquisar</button>
            <button class="btn btn-clear" onclick="limpar()">✖️ Limpar</button>
            <div class="opcoes">
                <label>Top <input type="number" id="topN" value="10" min="1" max="50"></label>
                <label>Score mín. <input type="number" id="minScore" value="30" min="0" max="100"></label>
            </div>
        </div>
        <div class="sugestoes" id="sugestoes">
            <span>💡 Exemplos:</span>
        </div>
    </div>

    <div id="resultados">
        <div class="no-result">
            <div class="icon">🔎</div>
            <h2>Escreve uma descrição e clica em Pesquisar</h2>
            <p>Ou prime Ctrl+Enter</p>
        </div>
    </div>
</div>

<script>
// ==================================================================
// Dados embutidos
// ==================================================================
const BUNDLE = u?  ;
const DOCS = BUNDLE.docs;
const SINONIMOS = BUNDLE.sinonimos;
const MAQUINAS_SUGERIDAS = BUNDLE.maquinas;
const ESTADO_ENCERRADA = BUNDLE.estadoEncerrada;
const ESTADO_CONCLUIDA = BUNDLE.estadoConcluida;

// ==================================================================
// Normalização
// ==================================================================
function normalizar(texto) {
    if (!texto) return "";
    return String(texto)
        .normalize("NFKD")
        .replace(/[\u0300-\u036f]/g, "")
        .toLowerCase()
        .replace(/[^\w\s]/g, " ")
        .replace(/\s+/g, " ")
        .trim();
}

function expandirSinonimos(textoNorm) {
    const extras = [];
    for (const chave in SINONIMOS) {
        if (textoNorm.indexOf(chave) !== -1) {
            extras.push.apply(extras, SINONIMOS[chave]);
        }
    }
    return extras.length ? textoNorm + " " + extras.join(" ") : textoNorm;
}

function extrairCodigosMaquina(texto) {
    if (!texto) return [];
    const upper = texto.toUpperCase();
    const padroes = [
        /\b\d{3,4}[A-Z]?\b/g,
        /\b[A-Z]{2,3}\d{1,3}[A-Z]?\b/g,
        /\b[A-Z]\d{2,3}[A-Z]?\b/g,
        /\bP\d{3}\b/g,
    ];
    const encontrados = {};
    for (const p of padroes) {
        const m = upper.match(p);
        if (m) for (const x of m) encontrados[x] = true;
    }
    return Object.keys(encontrados);
}

// ==================================================================
// Similaridade
// ==================================================================
function levenshtein(a, b) {
    if (a === b) return 0;
    if (!a.length) return b.length;
    if (!b.length) return a.length;
    const m = a.length, n = b.length;
    let prev = new Array(n + 1);
    let curr = new Array(n + 1);
    for (let j = 0; j <= n; j++) prev[j] = j;
    for (let i = 1; i <= m; i++) {
        curr[0] = i;
        for (let j = 1; j <= n; j++) {
            const cost = a[i - 1] === b[j - 1] ? 0 : 1;
            curr[j] = Math.min(
                curr[j - 1] + 1,
                prev[j] + 1,
                prev[j - 1] + cost
            );
        }
        const tmp = prev; prev = curr; curr = tmp;
    }
    return prev[n];
}

function ratio(a, b) {
    if (!a && !b) return 100;
    if (!a || !b) return 0;
    const d = levenshtein(a, b);
    const maxLen = Math.max(a.length, b.length);
    return (1 - d / maxLen) * 100;
}

function partialRatio(needle, haystack) {
    if (!needle || !haystack) return 0;
    if (needle.length > haystack.length) {
        const t = needle; needle = haystack; haystack = t;
    }
    if (haystack.indexOf(needle) !== -1) return 100;

    const winLen = needle.length;
    let best = 0;
    const step = Math.max(1, Math.floor(winLen / 4));
    for (let i = 0; i <= haystack.length - winLen; i += step) {
        const window = haystack.substring(i, i + winLen);
        const r = ratio(needle, window);
        if (r > best) {
            best = r;
            if (best >= 99) return 100;
        }
    }
    return best;
}

function tokenSetRatio(a, b) {
    const ta = a.split(" ").filter(Boolean);
    const tb = b.split(" ").filter(Boolean);
    const setA = new Set(ta);
    const setB = new Set(tb);
    const inter = [];
    const diffA = [];
    const diffB = [];
    for (const t of setA) {
        if (setB.has(t)) inter.push(t);
        else diffA.push(t);
    }
    for (const t of setB) {
        if (!setA.has(t)) diffB.push(t);
    }
    inter.sort();
    diffA.sort();
    diffB.sort();

    const sInter = inter.join(" ");
    const c1 = (sInter + " " + diffA.join(" ")).trim();
    const c2 = (sInter + " " + diffB.join(" ")).trim();

    return Math.max(
        ratio(sInter, c1),
        ratio(sInter, c2),
        ratio(c1, c2)
    );
}

// ==================================================================
// Scoring
// ==================================================================
function calcularScore(perguntaNorm, doc, maquinas) {
    let scoreMaq = 0;
    const assetCode = (doc.assetCode || "").toUpperCase();
    const familia = (doc.assetFamily || "").toUpperCase();

    if (maquinas.length > 0) {
        if (maquinas.indexOf(assetCode) !== -1) scoreMaq = 40;
        else if (familia && maquinas.indexOf(familia) !== -1) scoreMaq = 25;
        else if (maquinas.some(m => assetCode.indexOf(m) !== -1 || m.indexOf(assetCode) !== -1)) scoreMaq = 30;
    }

    const partes = [
        doc.code || "",
        doc.name || "",
        doc.assetCode || "",
        doc.assetName || "",
        doc.brand || "",
        doc.brandModel || "",
        doc.serialNumber || "",
        doc.remedy || "",
    ];
    if (doc.comentarios) {
        for (const c of doc.comentarios) partes.push(c.text || "");
    }
    let textoDocNorm = normalizar(partes.join(" "));
    textoDocNorm = expandirSinonimos(textoDocNorm);

    let scoreTxt = 0;
    if (perguntaNorm && textoDocNorm) {
        const s1 = partialRatio(perguntaNorm, textoDocNorm);
        const s2 = tokenSetRatio(perguntaNorm, textoDocNorm);
        scoreTxt = (s1 * 0.4 + s2 * 0.6) * 0.40;
    }

    const bonusRemedy = doc.remedy ? 10 : 0;
    const bonusEncerrada = doc.stateId === ESTADO_ENCERRADA ? 10 : 0;

    return scoreMaq + scoreTxt + bonusRemedy + bonusEncerrada;
}

// ==================================================================
// Pesquisa (com overlay de loading)
// ==================================================================
function pesquisar() {
    const pergunta = document.getElementById("pergunta").value.trim();
    if (!pergunta) {
        document.getElementById("pergunta").focus();
        return;
    }

    const topN = parseInt(document.getElementById("topN").value) || 10;
    const minScore = parseFloat(document.getElementById("minScore").value) || 30;

    // Mostrar overlay de pesquisa
    const overlay = document.getElementById("pesquisaOverlay");
    const totalEl = document.getElementById("pesquisaTotal");
    if (totalEl) totalEl.textContent = DOCS.length;
    overlay.classList.add("activo");
    overlay.classList.remove("esconder");

    // Desativar botões durante a pesquisa
    const btnPesquisar = document.querySelector(".btn-primary");
    const btnLimpar = document.querySelector(".btn-clear");
    if (btnPesquisar) btnPesquisar.disabled = true;
    if (btnLimpar) btnLimpar.disabled = true;

    // Dar tempo ao browser para pintar o overlay ANTES de bloquear com o cálculo
    setTimeout(function() {
        try {
            _executarPesquisa(pergunta, topN, minScore);
        } finally {
            // Esconder overlay com fade-out
            overlay.classList.add("esconder");
            setTimeout(function() {
                overlay.classList.remove("activo");
                overlay.classList.remove("esconder");
            }, 220);

            // Reactivar botões
            if (btnPesquisar) btnPesquisar.disabled = false;
            if (btnLimpar) btnLimpar.disabled = false;
        }
    }, 40);
}

function _executarPesquisa(pergunta, topN, minScore) {
    const maquinas = extrairCodigosMaquina(pergunta);
    let perguntaNorm = normalizar(pergunta);
    perguntaNorm = expandirSinonimos(perguntaNorm);

    const t0 = performance.now();

    const resultados = [];
    for (const doc of DOCS) {
        const s = calcularScore(perguntaNorm, doc, maquinas);
        if (s >= minScore) {
            resultados.push({ score: s, doc: doc });
        }
    }

    resultados.sort((a, b) => b.score - a.score);
    const top = resultados.slice(0, topN);

    const dt = (performance.now() - t0).toFixed(0);

    renderizar(pergunta, maquinas, top, resultados.length, dt);
}

function renderizar(pergunta, maquinas, top, totalResultados, dt) {
    const el = document.getElementById("resultados");

    let html = "";

    html += '<div class="resultado-info">';
    html += '<div>';
    html += '<strong>' + totalResultados + '</strong> caso(s) encontrado(s) · top ' + top.length + ' mostrado(s) · <span style="color:#888;">' + dt + ' ms</span>';
    if (maquinas.length > 0) {
        html += ' · Máquinas:';
        for (const m of maquinas) {
            html += ' <span class="maq-tag">' + escapeHtml(m) + '</span>';
        }
    }
    html += '</div>';
    html += '</div>';

    if (top.length === 0) {
        html += '<div class="no-result">';
        html += '<div class="icon">🤷</div>';
        html += '<h2>Sem resultados</h2>';
        html += '<p>Tenta baixar o score mínimo ou reformular a pergunta.</p>';
        html += '</div>';
        el.innerHTML = html;
        return;
    }

    // Agregação de soluções
    const contagem = {};
    for (const r of top) {
        const rem = r.doc.remedy;
        if (rem) contagem[rem] = (contagem[rem] || 0) + 1;
    }
    const totalComRemedy = Object.values(contagem).reduce((a, b) => a + b, 0);

    if (totalComRemedy > 0) {
        const solucoes = Object.entries(contagem)
            .map(([solucao, casos]) => ({
                solucao: solucao,
                casos: casos,
                prob: (casos / totalComRemedy) * 100,
            }))
            .sort((a, b) => b.prob - a.prob);

        html += '<div class="solucoes">';
        html += '<h3>📊 Soluções por probabilidade</h3>';
        for (const s of solucoes) {
            html += '<div class="solucao-item">';
            html += '  <div class="solucao-pct">' + s.prob.toFixed(1) + '%</div>';
            html += '  <div>';
            html += '    <div class="solucao-texto">' + escapeHtml(s.solucao) + '</div>';
            html += '    <div class="solucao-bar-wrap"><div class="solucao-bar" style="width:' + s.prob.toFixed(1) + '%"></div></div>';
            html += '  </div>';
            html += '  <div class="solucao-casos">' + s.casos + ' caso(s)</div>';
            html += '</div>';
        }
        html += '</div>';
    } else {
        html += '<div class="solucoes"><h3>📊 Soluções por probabilidade</h3>';
        html += '<p style="color:#888;font-size:13px;">⚠️ Nenhuma solução registada nos casos encontrados.</p>';
        html += '</div>';
    }

    // Casos
    html += '<div class="casos-header">🏆 Casos mais similares</div>';

    let idx = 0;
    for (const r of top) {
        idx++;
        const doc = r.doc;
        const score = r.score;

        let classe = "score-baixo";
        if (score >= 75) classe = "score-alto";
        else if (score >= 50) classe = "score-medio";

        const estadoNome = doc.stateName || "";
        const estadoClasse = estadoNome.replace(/\s+/g, "_");

        html += '<div class="caso ' + classe + '" style="animation-delay:' + (idx * 0.03) + 's">';
        html += '  <div class="caso-header">';
        html += '    <div>';
        html += '      <div class="caso-code">' + escapeHtml(doc.code || doc.id || '') + '</div>';
        html += '      <div style="font-size:11px;color:#888;margin-top:3px;">';
        html += '        🖥️ ' + escapeHtml(doc.assetCode || '?') + ' — ' + escapeHtml(doc.assetName || '');
        html += '      </div>';
        html += '    </div>';
        html += '    <div class="caso-score">' + score.toFixed(1) + '%</div>';
        html += '  </div>';

        html += '  <div class="caso-title">' + escapeHtml(doc.name || 'Sem título') + '</div>';

        html += '  <div class="caso-meta">';
        if (doc.technician) html += '<span>👤 ' + escapeHtml(doc.technician) + '</span>';
        if (doc.beginDateTime) html += '<span>📅 ' + escapeHtml(String(doc.beginDateTime).slice(0, 10)) + '</span>';
        if (doc.brand || doc.brandModel) html += '<span>🏷️ ' + escapeHtml((doc.brand || '') + ' ' + (doc.brandModel || '')) + '</span>';
        if (estadoNome) html += '<span class="estado-badge estado-' + estadoClasse + '">' + escapeHtml(estadoNome) + '</span>';
        html += '  </div>';

        if (doc.remedy) {
            html += '  <div class="caso-remedy"><strong>💡 Solução:</strong> ' + escapeHtml(doc.remedy) + '</div>';
        } else {
            html += '  <div class="caso-remedy" style="opacity:0.6;"><strong>💡 Solução:</strong> (sem solução registada)</div>';
        }

        if (doc.comentarios && doc.comentarios.length > 0) {
            const cid = 'com_' + idx;
            html += '  <button class="caso-comentarios-btn" onclick="toggleComentarios(\'' + cid + '\', this)">';
            html += '    💬 Ver ' + doc.comentarios.length + ' comentário(s)';
            html += '  </button>';
            html += '  <div class="caso-comentarios" id="' + cid + '">';
            const comentariosOrd = doc.comentarios.slice().sort((a, b) => {
                const da = new Date(a.date || 0).getTime() || 0;
                const db = new Date(b.date || 0).getTime() || 0;
                return db - da;
            });
            for (const c of comentariosOrd) {
                html += '<div class="caso-comentario">';
                html += '  <span class="author">👤 ' + escapeHtml(c.author || 'Desconhecido') + '</span>';
                if (c.date) html += '  <span class="date">📅 ' + escapeHtml(formatarData(c.date)) + '</span>';
                html += '  <div class="text">' + escapeHtml(c.text || '') + '</div>';
                html += '</div>';
            }
            html += '  </div>';
        }

        html += '</div>';
    }

    el.innerHTML = html;
}

function toggleComentarios(id, btn) {
    const el = document.getElementById(id);
    if (!el) return;
    el.classList.toggle("aberto");
    if (el.classList.contains("aberto")) {
        btn.innerHTML = btn.innerHTML.replace("Ver", "Ocultar");
    } else {
        btn.innerHTML = btn.innerHTML.replace("Ocultar", "Ver");
    }
}

function formatarData(iso) {
    if (!iso) return "";
    try {
        const d = new Date(iso);
        if (isNaN(d.getTime())) return String(iso).slice(0, 16);
        return d.toLocaleString("pt-PT", {
            day: "2-digit", month: "2-digit", year: "numeric",
            hour: "2-digit", minute: "2-digit"
        });
    } catch (e) {
        return String(iso).slice(0, 16);
    }
}

function escapeHtml(s) {
    if (s === null || s === undefined) return "";
    return String(s)
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;");
}

function limpar() {
    document.getElementById("pergunta").value = "";
    document.getElementById("pergunta").focus();
    document.getElementById("resultados").innerHTML =
        '<div class="no-result"><div class="icon">🔎</div><h2>Escreve uma descrição e clica em Pesquisar</h2><p>Ou prime Ctrl+Enter</p></div>';
}

// ==================================================================
// Sugestões
// ==================================================================
function initSugestoes() {
    const el = document.getElementById("sugestoes");
    const exemplos = [];

    const top3 = MAQUINAS_SUGERIDAS.slice(0, 3);
    for (const m of top3) {
        exemplos.push(m + " erro emergencia");
        exemplos.push(m + " nao arranca");
    }
    exemplos.push("carregador nao arranca");
    exemplos.push("erro emergencia");

    const unicos = Array.from(new Set(exemplos)).slice(0, 6);

    for (const ex of unicos) {
        const span = document.createElement("span");
        span.className = "sugestao";
        span.textContent = ex;
        span.onclick = function() {
            document.getElementById("pergunta").value = ex;
            pesquisar();
        };
        el.appendChild(span);
    }
}

// Esconder loading overlay inicial quando tudo estiver pronto
window.addEventListener("load", function() {
    const ov = document.getElementById("loadingOverlay");
    if (ov) {
        setTimeout(function() {
            ov.classList.add("esconder");
            setTimeout(function() {
                if (ov.parentNode) ov.remove();
            }, 600);
        }, 250);
    }
});

document.addEventListener("DOMContentLoaded", function() {
    initSugestoes();
    document.getElementById("pergunta").focus();
});
</script>
</body>
</html>T)exist_okwrD   rE   u   ✅ HTML gerado com sucesso!u      📄 u      📊 Tamanho: z MBu      📈 z OTs | u    com solução | u    com comentáriosu   ❌ Erro ao gerar HTML: u   
🌐 A abrir no browser...zfile://i   )"rK   rP   rZ   rd   r   ri   sumr   nowstrftimer   r   r&   r+   most_commonr   rM   dumpsr;   r   ESTADO_ENCERRADAESTADO_CONCLUIDArG   makedirsrH   dirnamerL   writerc   getsizerV   	traceback	print_exc
webbrowser)filenamerefresh_intervalmax_docsabrirr   r   r   gif_data_urigif_htmlgif_pesquisa_htmltotal
com_remedycom_comentariosagoramaquinas_counterr   codr8   _top_maquinasdocs_js
dados_jsonrefresh_metahtmlrO   
tamanho_mbrh   r   s   &&&&                        r   gerar_motor_visualr      sy   	3H:
>?%'J12	*
')K	E#k"##A
BCZ5D ()L~-QR(6Z[AJ IE888JBTBBOLLN##$78Eyuu[!''R..06683S!Q&!  #3">">r"BC"B$!QA"BLC G 
!D'
AfI
 AfI
 ;	

 1]+
 ;
 QwZ
 !L/
 An-
 !L/
 q|
 1]+
 Q/
 q|
 ;
  ak!
" 1]+#
 	 * Gw//	
 
J L,q0=>N=OrR

  xp J   
7 8=g >% &+G ,% &0L 1% &5$5 6% &))9%:$; "<D | QAQDf
BGGOOH-=(C'22aGGDM 3277??84DaH
,.
#$!*S12wzl2COCTTefg ,.'(,-e  DB  32  (,-	s8   N%%AN> 0N+A5N> +N;	6N> >O6	"O11O6__main__z--abrirz-a)r   u'   💡 Para abrir automaticamente, corre:z*   python motor_pesquisa_visual.py --abrirz   Ou abre manualmente: file://)znao ligaz	nao parteznao funcionazsem arranquez
nao iniciaparadaparou	desligada)alarmefalhaavariadefeitoproblema)alimentadoriemcaz
bar feedercharger)emerg	emergencyzbotao emergenciaseta)carregaalimentacaozintroducao de barra)vibratrepidatreme)	vazamentoescapeperda)lubrificantefluidozoleo hidraulico)aquecimentoquenteztemperatura alta)$rG   rM   r$   r   r   r`   r   collectionsr   rH   
expanduserDESKTOPr"   rJ   rT   	HTML_FILEr^   r   r   r   r;   r(   r-   r9   rB   rP   rZ   ri   r   r   __name__sysargvr   okrK    r)   r   <module>r      s   
  	     
''

[
)'',,w(9:77<<)<= GGLL">?	77<<!34 +K*J*J*,N*,A*,M*,N*,O*,I*L*,G*J*,N*I*K$ : 9   B
@CDE-
,
9lL1(E	&6"$*6x !*ATY vx! z!55TSXX%5E	%	(B	%78:;/	{;< r r)   