Py.Cafe

ferreira_ntg/

horse-data-explorer

Horse Data Explorer

DocsPricing
  • app.py
  • requirements.txt
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import streamlit as st
import requests

# --- CONFIG ---
URL = "https://atzpjotneawiovkeyklp.supabase.co"
KEY = "sb_publishable_VfIZcHkZ_wQcw4Bx0vbR9A_V3Eu3OCQ"
HEADERS = {"apikey": KEY, "Authorization": f"Bearer {KEY}"}

st.set_page_config(page_title="Horse Registry Pro", layout="wide")

# --- CSS: UPDATED PARENT COLORS & GRID ---
st.markdown("""
    <style>
    div.stButton > button {
        background-color: white !important; border: 1px solid #d1d5db !important;
        border-radius: 12px !important; min-height: 180px !important;
        width: 100% !important; display: flex !important;
        flex-direction: column !important; white-space: pre-wrap !important;
    }
    .detail-container {
        background-color: #ffffff; padding: 25px; border-radius: 12px;
        border: 1px solid #e5e7eb; box-shadow: 0 10px 15px rgba(0,0,0,0.1);
    }
    .data-label { font-weight: bold; color: #6b7280; font-size: 0.75rem; text-transform: uppercase; margin: 0; }
    .data-value { font-weight: 600; color: #111827; font-size: 0.95rem; margin-bottom: 15px; display: block; }

    /* Parent Info Boxes with specific colors */
    .info-pai { background-color: #f0f9ff; border-left: 5px solid #0ea5e9; padding: 20px; border-radius: 8px; margin-bottom: 10px; }
    .info-mae { background-color: #fff1f2; border-left: 5px solid #f43f5e; padding: 20px; border-radius: 8px; margin-bottom: 10px; }
    </style>
""", unsafe_allow_html=True)

# --- CALLBACKS & UTILS ---
def open_horse(horse_data): st.session_state.selected_horse = horse_data
def close_horse(): st.session_state.selected_horse = None
def to_bold(text):
    chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
    bold_chars = "𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵"
    return text.translate(str.maketrans(chars, bold_chars))

if 'selected_horse' not in st.session_state: st.session_state.selected_horse = None

# --- VIEW: HORSE RECORD ---
if st.session_state.selected_horse:
    h = st.session_state.selected_horse
    
    # 1. Header with Name and RP
    c_title, c_close = st.columns([10, 1])
    rp_display = h.get('rp') or '-'
    c_title.title(f"🐎 {h['nome']} (RP: {rp_display})")
    c_close.button("✖️", on_click=close_horse)

    st.markdown('<div class="detail-container">', unsafe_allow_html=True)
    
    # 2. Dados Genéricos Section
    st.subheader("📝 Dados Genéricos")
    
    # Custom Field Map as per your request
    grid_layout = [
        [("SBB", "sbb"), ("Nascimento", "nascimento"), ("Confirmação", "confirmacao"), ("Data Morte", "data_morte")],
        [("SBB Alternativo", "sbb_alternativo"), ("Sexo", "sexo"), ("Status", "status"), ("Confirmação", "confirmacao")],
        [("Pelagem", "pelagem"), ("Situação", "situacao"), ("Reg. Méritos", "registro_de_meritos"), ("Res Domínio", "res_dominio")],
        [("Últ. Transf.", "ultima_transferencia"), ("Castrado", "castra"), ("NMGC", "nmgc"), ("Altura", "altura")],
        [("Tórax", "torax"), ("Canela", "canela"), ("Restrição", "animal_com_restricao"), ("RP", "rp")]
    ]

    for row in grid_layout:
        cols = st.columns(4)
        for idx, (label, key) in enumerate(row):
            val = h.get(key)
            if key == "sexo": val = "Fêmea" if val == "F" else "Macho"
            cols[idx].markdown(f'<p class="data-label">{label}</p><p class="data-value">{val or "-"}</p>', unsafe_allow_html=True)

    st.divider()
    
    # 3. Tabs (Fixed table names based on Supabase hints)
    tabs = st.tabs(["👨‍👩‍👦 Parentes", "🏠 Criador/Prop.", "🏅 Méritos", "🏆 Premiações", "🧬 Padreação"])

    with tabs[0]: # Parentes
        col_pai, col_mae = st.columns(2)
        with col_pai:
            st.markdown('<div class="info-pai"><h3>♂️ Pai</h3>', unsafe_allow_html=True)
            p_c1, p_c2 = st.columns(2)
            p_c1.markdown(f'<p class="data-label">SBB</p><p class="data-value">{h.get("pai_sbb") or "-"}</p>', unsafe_allow_html=True)
            p_c2.markdown(f'<p class="data-label">Nome</p><p class="data-value">{h.get("pai_nome") or "-"}</p>', unsafe_allow_html=True)
            p_c1.markdown(f'<p class="data-label">RP</p><p class="data-value">{h.get("pai_rp") or "-"}</p>', unsafe_allow_html=True)
            p_c2.markdown(f'<p class="data-label">Pelagem</p><p class="data-value">{h.get("pai_pelagem") or "-"}</p>', unsafe_allow_html=True)
            st.markdown('</div>', unsafe_allow_html=True)

        with col_mae:
            st.markdown('<div class="info-mae"><h3>♀️ Mãe</h3>', unsafe_allow_html=True)
            m_c1, m_c2 = st.columns(2)
            m_c1.markdown(f'<p class="data-label">SBB</p><p class="data-value">{h.get("mae_sbb") or "-"}</p>', unsafe_allow_html=True)
            m_c2.markdown(f'<p class="data-label">Nome</p><p class="data-value">{h.get("mae_nome") or "-"}</p>', unsafe_allow_html=True)
            m_c1.markdown(f'<p class="data-label">RP</p><p class="data-value">{h.get("mae_rp") or "-"}</p>', unsafe_allow_html=True)
            m_c2.markdown(f'<p class="data-label">Pelagem</p><p class="data-value">{h.get("mae_pelagem") or "-"}</p>', unsafe_allow_html=True)
            st.markdown('</div>', unsafe_allow_html=True)

    with tabs[1]: # Criador/Proprietário
        cp1, cp2 = st.columns(2)
        cp1.markdown(f'**Criador:** {h.get("criador_nome") or "-"}\n\n**Cidade/UF:** {h.get("criador_cidade") or "-"}/{h.get("criador_uf") or "-"}')
        cp2.markdown(f'**Proprietário:** {h.get("proprietario_nome") or "-"}\n\n**Cidade/UF:** {h.get("prop_cidade") or "-"}/{h.get("prop_uf") or "-"}')

    # Mapping based on hints: sbb_meritos, sbb_premiacao, sbb_padreacao
    # If this still fails, double-check your Supabase Table Editor for exact names
    table_map = {
        2: "sbb_meritos",
        3: "sbb_premiacao",
        4: "sbb_padreacao"
    }

    for idx, table in table_map.items():
        with tabs[idx]:
            try:
                res = requests.get(f"{URL}/rest/v1/{table}?sbb=eq.{h['sbb']}", headers=HEADERS).json()
                if isinstance(res, list) and len(res) > 0:
                    st.table(res)
                else:
                    st.info(f"Nenhum registro encontrado na tabela '{table}'.")
            except:
                st.error(f"Erro ao acessar {table}.")

    st.markdown('</div>', unsafe_allow_html=True)

# --- VIEW: SEARCH ---
else:
    st.title("🐎 Consulta de Registro")
    c1, c2, c3 = st.columns([3, 2, 2])
    h_name = c1.text_input("Nome")
    h_sbb = c2.text_input("SBB")
    h_sex = c3.selectbox("Sexo", ["Todos", "M", "F"])

    query = f"raw_details?select=*&limit=30"
    if h_name: query += f"&nome=ilike.*{h_name.replace(' ', '*')}*"
    if h_sbb: query += f"&sbb=eq.{h_sbb}"
    if h_sex != "Todos": query += f"&sexo=eq.{h_sex}"
    
    data = requests.get(f"{URL}/rest/v1/{query}", headers=HEADERS).json()
    if isinstance(data, list):
        for i in range(0, len(data), 5):
            cols = st.columns(5)
            for j in range(5):
                if i + j < len(data):
                    item = data[i+j]
                    sex_lbl = "Macho" if item['sexo'] == 'M' else "Fêmea"
                    lbl = f"{to_bold(item['nome'])}\nSBB: {item['sbb']}\n{item['nascimento']}\n{sex_lbl}"
                    cols[j].button(lbl, key=f"btn_{item['sbb']}", on_click=open_horse, args=(item,))