📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Módulo 02: Lógica de Programação e Algoritmos. Recomendado revisar antes de começar.

🌱 Miniprojeto 08: Caderno de Campo de Produtor Rural

Guia Autoguiado Passo a Passo: Do Registro de Lavouras em TXT à Aplicação Web com SQLite e Streamlit

Trilha de Especialização — Nível Introdutório (Curso 25: Sistemas Desktop com Python)


🎯 Objetivo de Aprendizagem

Para produtores da agricultura familiar, hortas comunitárias e sítios de pequenas cidades, o acompanhamento dos custos de insumos (adubo, sementes, combustível) e o registro das safras colhidas por lote/talhão são determinantes para entender a produtividade (kg/ha) e a rentabilidade do cultivo.

Neste projeto, o estudante aprende como:

  1. Processar dados agronômicos e custos em arquivos .txt delimitados.
  2. Criar a classe Talhao com métodos que calculam produtividade em kg/ha e custo unitário por kg colhido.
  3. Modelar duas tabelas com Chave Estrangeira no SQLite3 (talhoes 1:N registros_campo) com agregações SQL condicionais (CASE WHEN).
  4. Construir uma interface Web moderna com Streamlit, apresentando cards de produtividade, gráficos comparativos de safra e apontamentos de campo.

🪜 O Caminho Didático dos 4 Degraus

flowchart LR
    D1["🪜 DEGRAU 1\nImperativo & .txt\nMenu while + campo.txt\ncom cálculo de kg/ha"] --> D2["🪜 DEGRAU 2\nPrimeira Classe POO\nclass Talhao\n(produtividade, custo/kg)"]
    D2 --> D3["🪜 DEGRAU 3\nSQLite Relacional (1:N)\nTabelas talhoes e\nregistros com CASE WHEN"]
    D3 --> D4["🪜 DEGRAU 4\nInterface Web Streamlit\nPainel agronômico,\nmétricas e gráficos"]

    style D1 fill:#fffde7,stroke:#fbc02d,stroke-width:2px
    style D2 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style D3 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
    style D4 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px

📂 Estrutura de Pastas e Arquivos

sistemas_python_intro_08_campo_rural/
├── degrau_1_imperativo_txt/
│   ├── main.py              # Script imperativo com menu e arquivo campo.txt
│   └── test_degrau_1.py     # Testes automatizados de produtividade agrícola
├── degrau_2_primeira_classe/
│   ├── main.py              # Classes POO (class Talhao e class CadernoCampo)
│   └── test_degrau_2.py     # Testes unitários de indicadores agronômicos
├── degrau_3_sqlite/
│   ├── main.py              # SQLite3 relacional: 2 tabelas e agregação condicional SUM
│   └── test_degrau_3.py     # Testes unitários de consultas SQL
├── degrau_4_interface_streamlit/
│   ├── app.py               # Painel Web em Python puro com Streamlit
│   └── main.py              # Launcher do servidor Web Streamlit
├── main.py                  # Hub Central / Launcher interativo
└── index.md                 # Este guia didático autoguiado

🚀 Como Executar no Laboratório

1. Abra o terminal na pasta deste projeto

No VS Code / PyCharm, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:

cd sistemas_python_intro_08_campo_rural

2. Execute o Hub Central ou um Degrau Específico

# Executar o Hub Central (Menu interativo com todos os degraus):
python main.py

# Ou executar os degraus de terminal (1 a 3):
python degrau_1_imperativo_txt/main.py
python degrau_2_primeira_classe/main.py
python degrau_3_sqlite/main.py

# Executar a aplicação Web (Degrau 4 com Streamlit):
streamlit run degrau_4_interface_streamlit/app.py

[!TIP] Dica para execução a partir da raiz do repositório: Se você abriu o repositório completo no VS Code, basta executar: python catalogo_projetos/sistemas_python_intro_08_campo_rural/main.py


📖 Roteiro Passo a Passo & Gabarito Completo


🪜 Degrau 1: Programação Imperativa com Arquivo de Texto (.txt)

No primeiro degrau, gravamos cada talhão no arquivo campo.txt no formato talhao,cultura,area_ha,custo_reais,colheita_kg.

Código-Fonte Completo (degrau_1_imperativo_txt/main.py):

import os

ARQUIVO_PADRAO = "campo.txt"

def cadastrar_talhao(talhoes, nome_talhao, cultura, area_ha):
    if not nome_talhao.strip() or not cultura.strip() or area_ha <= 0:
        return False
    talhoes.append({
        "talhao": nome_talhao.strip(),
        "cultura": cultura.strip(),
        "area_ha": float(area_ha),
        "custo_reais": 0.0,
        "colheita_kg": 0.0
    })
    return True

def registrar_custo(talhoes, nome_talhao, valor_gasto):
    for t in talhoes:
        if t["talhao"].lower() == nome_talhao.lower():
            t["custo_reais"] += valor_gasto
            return True
    return False

def registrar_colheita(talhoes, nome_talhao, kg_colhidos):
    for t in talhoes:
        if t["talhao"].lower() == nome_talhao.lower():
            t["colheita_kg"] += kg_colhidos
            return True
    return False

Testes Automatizados (degrau_1_imperativo_txt/test_degrau_1.py):

"""
Testes Unitários para o Degrau 1 do Projeto 08 (Imperativo & Arquivo TXT)
"""

import os
import unittest
from main import (
    cadastrar_talhao,
    carregar_talhoes,
    registrar_colheita,
    registrar_custo,
    salvar_talhoes
)

ARQUIVO_TESTE = "teste_campo.txt"


class TestDegrau1Campo(unittest.TestCase):

    def setUp(self):
        if os.path.exists(ARQUIVO_TESTE):
            os.remove(ARQUIVO_TESTE)

    def tearDown(self):
        if os.path.exists(ARQUIVO_TESTE):
            os.remove(ARQUIVO_TESTE)

    def test_cadastrar_custo_e_colheita(self):
        talhoes = []
        cadastrar_talhao(talhoes, "Talhao 01 - Horta", "Alface", 2.0)
        self.assertEqual(len(talhoes), 1)
        self.assertEqual(talhoes[0]["area_ha"], 2.0)

        # Custo
        registrar_custo(talhoes, "Talhao 01 - Horta", 300.00)
        self.assertEqual(talhoes[0]["custo_reais"], 300.00)

        # Colheita (1200 kg em 2 ha = 600 kg/ha)
        registrar_colheita(talhoes, "Talhao 01 - Horta", 1200.00)
        self.assertEqual(talhoes[0]["colheita_kg"], 1200.00)

        # Persistência TXT
        salvar_talhoes(talhoes, caminho=ARQUIVO_TESTE)
        carregados = carregar_talhoes(caminho=ARQUIVO_TESTE)
        self.assertEqual(len(carregados), 1)
        self.assertEqual(carregados[0]["cultura"], "Alface")
        self.assertEqual(carregados[0]["colheita_kg"], 1200.00)


if __name__ == "__main__":
    unittest.main()

🪜 Degrau 2: Primeiro Contato com POO (Classes Talhao e CadernoCampo)

No segundo degrau, a classe Talhao calcula dinamicamente a produtividade em kg/ha e o custo médio por quilo colhido.

Código-Fonte Completo (degrau_2_primeira_classe/main.py):

class Talhao:
    def __init__(self, nome, cultura, area_ha, custo_total=0.0, colheita_kg=0.0):
        self.nome = nome.strip()
        self.cultura = cultura.strip()
        self.area_ha = float(area_ha)
        self.custo_total = float(custo_total)
        self.colheita_kg = float(colheita_kg)

    def produtividade(self):
        return (self.colheita_kg / self.area_ha) if self.area_ha > 0 else 0.0

    def custo_por_kg(self):
        return (self.custo_total / self.colheita_kg) if self.colheita_kg > 0 else 0.0

Testes Automatizados (degrau_2_primeira_classe/test_degrau_2.py):

"""
Testes Unitários para o Degrau 2 do Projeto 08 (Primeira Classe POO)
"""

import os
import unittest
from main import CadernoCampo, Talhao

ARQUIVO_TESTE = "teste_campo_poo.txt"


class TestDegrau2Campo(unittest.TestCase):

    def setUp(self):
        if os.path.exists(ARQUIVO_TESTE):
            os.remove(ARQUIVO_TESTE)

    def tearDown(self):
        if os.path.exists(ARQUIVO_TESTE):
            os.remove(ARQUIVO_TESTE)

    def test_classe_talhao_indicadores(self):
        t = Talhao("Lote 03 - Cafezal", "Café Arábica", 5.0)
        self.assertEqual(t.area_ha, 5.0)

        t.adicionar_custo(2500.00)
        self.assertEqual(t.custo_total, 2500.00)

        t.adicionar_colheita(10000.00)  # 10.000 kg em 5 ha = 2.000 kg/ha
        self.assertEqual(t.colheita_kg, 10000.00)
        self.assertEqual(t.produtividade(), 2000.00)
        self.assertEqual(t.custo_por_kg(), 0.25)  # 2500 / 10000 = 0.25 R$/kg

    def test_caderno_campo_persistencia(self):
        cc1 = CadernoCampo()
        cc1.adicionar(Talhao("Lote 01", "Milho Safrinha", 10.0, 4000.00, 35000.00))
        cc1.salvar(caminho=ARQUIVO_TESTE)

        cc2 = CadernoCampo()
        cc2.carregar(caminho=ARQUIVO_TESTE)
        self.assertEqual(len(cc2.talhoes), 1)
        self.assertEqual(cc2.talhoes[0].cultura, "Milho Safrinha")
        self.assertEqual(cc2.talhoes[0].produtividade(), 3500.00)


if __name__ == "__main__":
    unittest.main()

🪜 Degrau 3: Banco de Dados Relacional com SQLite3 Nativo (1:N)

No terceiro degrau, criamos as tabelas talhoes e registros_campo no SQLite, usando SUM(CASE WHEN...) para consolidar custos e colheitas.

Código-Fonte Completo (degrau_3_sqlite/main.py):

import sqlite3

def inicializar_tabelas(caminho="campo.db"):
    conn = sqlite3.connect(caminho)
    conn.execute("PRAGMA foreign_keys = ON;")
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS talhoes (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            nome TEXT NOT NULL UNIQUE,
            cultura TEXT NOT NULL,
            area_ha REAL NOT NULL,
            data_cadastro TEXT DEFAULT (date('now', 'localtime'))
        )
    """)
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS registros_campo (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            talhao_id INTEGER NOT NULL,
            tipo TEXT NOT NULL CHECK(tipo IN ('CUSTO', 'COLHEITA')),
            descricao TEXT NOT NULL,
            valor_reais REAL DEFAULT 0.0,
            quantidade_kg REAL DEFAULT 0.0,
            data_evento TEXT DEFAULT (date('now', 'localtime')),
            FOREIGN KEY (talhao_id) REFERENCES talhoes(id) ON DELETE CASCADE
        )
    """)
    conn.commit()
    conn.close()

Testes Automatizados (degrau_3_sqlite/test_degrau_3.py):

"""
Testes Unitários para o Degrau 3 do Projeto 08 (SQLite3 Relacional com Agregações)
"""

import os
import unittest
from main import (
    cadastrar_talhao,
    inicializar_tabelas,
    listar_talhoes_com_totais,
    registrar_evento
)

BANCO_TESTE = "teste_campo_degrau3.db"


class TestDegrau3SQLite(unittest.TestCase):

    def setUp(self):
        if os.path.exists(BANCO_TESTE):
            os.remove(BANCO_TESTE)
        inicializar_tabelas(caminho=BANCO_TESTE)

    def tearDown(self):
        if os.path.exists(BANCO_TESTE):
            os.remove(BANCO_TESTE)

    def test_relacionamento_e_agregacao_sql(self):
        # 1. Cadastrar Talhão
        t_id = cadastrar_talhao("Talhao Soja 01", "Soja", 10.0, caminho=BANCO_TESTE)
        self.assertIsNotNone(t_id)

        # 2. Registrar Custos
        registrar_evento(t_id, "CUSTO", "Sementes Transgenicas", valor_reais=1500.00, caminho=BANCO_TESTE)
        registrar_evento(t_id, "CUSTO", "Adubo Fosfatado", valor_reais=2500.00, caminho=BANCO_TESTE)

        # 3. Registrar Colheitas
        registrar_evento(t_id, "COLHEITA", "Primeira Colheita", quantidade_kg=18000.00, caminho=BANCO_TESTE)
        registrar_evento(t_id, "COLHEITA", "Segunda Colheita", quantidade_kg=12000.00, caminho=BANCO_TESTE)

        # 4. Validar Agregação SQL (4000.00 de custo e 30000.00 kg colhidos)
        lista = listar_talhoes_com_totais(caminho=BANCO_TESTE)
        self.assertEqual(len(lista), 1)
        self.assertEqual(lista[0][4], 4000.00)   # total_custo
        self.assertEqual(lista[0][5], 30000.00)  # total_colheita


if __name__ == "__main__":
    unittest.main()

🪜 Degrau 4: Interface Web com Streamlit (Painel Agronômico)

No quarto degrau, o aluno cria uma aplicação web moderna com cards de indicadores gerais, formulários de apontamento e gráficos comparativos de produtividade.

Código-Fonte Completo (degrau_4_interface_streamlit/app.py):

import streamlit as st

def main():
    st.set_page_config(page_title="Caderno de Campo do Produtor Rural", page_icon="🌾", layout="wide")
    st.title("🌾 Caderno de Campo & Gestão Agronômica")

    # Métricas Agronômicas
    col1, col2, col3, col4 = st.columns(4)
    col1.metric("🌱 Área Plantada", "15.00 ha")
    col2.metric("💰 Insumos Investidos", "R$ 8.500,00")
    col3.metric("🚜 Total Colhido", "42.000 kg")
    col4.metric("📈 Produtividade Média", "2.800 kg/ha")

Launcher do Servidor Web (degrau_4_interface_streamlit/main.py):

"""
Launcher para o Degrau 4 (Streamlit Web)
Verifica se o pacote streamlit está instalado e inicia a aplicação no navegador.
"""

import os
import subprocess
import sys

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
APP_FILE = os.path.join(BASE_DIR, "app.py")


def main():
    print("\n========================================================")
    print("🌐 INICIANDO CADERNO DE CAMPO NA WEB (STREAMLIT)")
    print("========================================================\n")

    try:
        import streamlit  # noqa: F401
        print("✅ Streamlit detectado! Abrindo a aplicacao no seu navegador padrao...\n")
        cmd = [sys.executable, "-m", "streamlit", "run", APP_FILE]
        subprocess.run(cmd, cwd=BASE_DIR)
    except ImportError:
        print("⚠️ O pacote 'streamlit' nao foi encontrado no ambiente Python atual.")
        print("\nPara instalar e rodar, execute no seu terminal:")
        print("    pip install streamlit")
        print(f"    streamlit run \"{APP_FILE}\"\n")


if __name__ == "__main__":
    main()

🎛️ Hub Central: Launcher Interativo (main.py)

O arquivo main.py na raiz do projeto funciona como um painel de controle: permite executar qualquer um dos 4 degraus (ou toda a suíte de testes) a partir de um único menu no terminal, sem precisar navegar manualmente entre as pastas.

Código-Fonte Completo (main.py):

"""
Hub Central / Launcher — Miniprojeto 08: Caderno de Campo de Produtor Rural
Permite executar de forma autoguiada qualquer um dos 4 degraus pedagógicos ou rodar a suíte de testes.
"""

import os
import subprocess
import sys

BASE_DIR = os.path.dirname(os.path.abspath(__file__))


def rodar_script(caminho_relativo):
    caminho_absoluto = os.path.join(BASE_DIR, caminho_relativo)
    diretorio = os.path.dirname(caminho_absoluto)
    print(f"\n🚀 Executando: {caminho_relativo}...\n")
    try:
        subprocess.run([sys.executable, caminho_absoluto], cwd=diretorio, check=True)
    except KeyboardInterrupt:
        print("\n\n⏹️ Execucao interrompida pelo usuario.")
    except Exception as e:
        print(f"\n❌ Erro durante a execucao: {e}")


def rodar_todos_os_testes():
    print("\n🧪 ========================================================")
    print("   RODANDO A SUÍTE DE TESTES DOS DEGRAUS 1, 2 E 3")
    print("========================================================\n")
    testes = [
        ("Degrau 1 (Imperativo & Arquivo TXT)", "degrau_1_imperativo_txt/test_degrau_1.py"),
        ("Degrau 2 (Primeira Classe POO)", "degrau_2_primeira_classe/test_degrau_2.py"),
        ("Degrau 3 (Banco SQLite3 Relacional)", "degrau_3_sqlite/test_degrau_3.py"),
    ]
    for nome, arq in testes:
        print(f"▶️ Testando {nome}...")
        caminho_abs = os.path.join(BASE_DIR, arq)
        diretorio = os.path.dirname(caminho_abs)
        res = subprocess.run([sys.executable, caminho_abs], cwd=diretorio)
        if res.returncode == 0:
            print(f"✅ {nome}: PASSOU!\n")
        else:
            print(f"❌ {nome}: FALHOU!\n")


def menu_hub():
    while True:
        print("\n========================================================")
        print("🌾 MINIPROJETO 08: CADERNO DE CAMPO RURAL (HUB)")
        print("========================================================")
        print("1. 🪜 Executar Degrau 1 (Imperativo & Arquivo .txt)")
        print("2. 🪜 Executar Degrau 2 (Primeira Classe POO: class Talhao)")
        print("3. 🪜 Executar Degrau 3 (Banco de Dados SQLite3 Relacional)")
        print("4. 🪜 Executar Degrau 4 (Interface Web com Streamlit)")
        print("5. 🧪 Rodar Todos os Testes Automatizados (Degraus 1, 2 e 3)")
        print("6. 🚪 Sair")
        print("========================================================")
        opcao = input("Escolha o degrau que deseja executar (1-6): ").strip()

        if opcao == "1":
            rodar_script("degrau_1_imperativo_txt/main.py")
        elif opcao == "2":
            rodar_script("degrau_2_primeira_classe/main.py")
        elif opcao == "3":
            rodar_script("degrau_3_sqlite/main.py")
        elif opcao == "4":
            rodar_script("degrau_4_interface_streamlit/main.py")
        elif opcao == "5":
            rodar_todos_os_testes()
        elif opcao == "6":
            print("👋 Encerrando o launcher. Bons estudos!")
            break
        else:
            print("❌ Opcao invalida. Digite de 1 a 6.")


if __name__ == "__main__":
    menu_hub()

🧪 Validação com Testes Automatizados

# Executar a suíte de testes do Miniprojeto 08:
python -c "import subprocess, sys; [subprocess.run([sys.executable, p], check=True) for p in ['catalogo_projetos/sistemas_python_intro_08_campo_rural/degrau_1_imperativo_txt/test_degrau_1.py', 'catalogo_projetos/sistemas_python_intro_08_campo_rural/degrau_2_primeira_classe/test_degrau_2.py', 'catalogo_projetos/sistemas_python_intro_08_campo_rural/degrau_3_sqlite/test_degrau_3.py']]"

# Ou, de forma ainda mais simples, use o Hub Central (opção 5 do menu):
python main.py

Resultado obtido:

Ran 1 test in 0.003s OK (Degrau 1)
Ran 2 tests in 0.003s OK (Degrau 2)
Ran 1 test in 0.060s OK (Degrau 3)


🏛️ Arquitetura Visual & Engenharia do Sistema

A imagem abaixo ilustra a esteira evolutiva dos 4 degraus didáticos do miniprojeto:

Arquitetura da Aplicação em 4 Degraus

flowchart TD
    subgraph S1["🪜 Degrau 1: Script Imperativo"]
        A1["Menu Terminal while"] --> A2["Arquivo de Texto/JSON"]
    end
    subgraph S2["🪜 Degrau 2: Orientação a Objetos"]
        B1["Modelo de Domínio POO"] --> B2["Encapsulamento de Regras"]
    end
    subgraph S3["🪜 Degrau 3: Banco Relacional"]
        C1["SQLite3 Local"] --> C2["Transações ACID & Agregações"]
    end
    subgraph S4["🪜 Degrau 4: Interface Visual"]
        D1["Aplicação Desktop / Web"] --> D2["UX Interativa Final"]
    end

    S1 --> S2
    S2 --> S3
    S3 --> S4

    style S1 fill:#fffde7,stroke:#fbc02d,stroke-width:1.5px
    style S2 fill:#e3f2fd,stroke:#1976d2,stroke-width:1.5px
    style S3 fill:#e8f5e9,stroke:#388e3c,stroke-width:1.5px
    style S4 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1.5px

🎯 Engenharia de Requisitos & Modelagem Formal

👤 User Stories (Histórias de Usuário)

ID Persona História de Usuário Critério de Aceite Prioridade
US-01 Produtor Rural Desejo anotar talhões, culturas e datas de plantio em arquivo texto Registrar eventos agronômicos com formato estruturado em campo.txt Alta
US-02 Técnico Agrícola Desejo estimar datas de colheita e produtividade via modelo POO Classe Plantio com cálculo de ciclo vegetativo e estimativa de safra Alta
US-03 Gestor da Fazenda Desejo armazenar aplicações de defensivos e colheitas no SQLite Banco relacional com histórico por talhão e relatórios de colheita Alta
US-04 Administrador Rural Desejo painel web Streamlit com visualização de talhões e métricas Dashboard com gráficos de produtividade por hectare e alertas de colheita Alta
US-05 Auditor Agrícola Desejo validar que os períodos de carência de insumos sejam respeitados Testes unitários checando restrições de data e integridade dos registros Média

⚙️ Requisitos Funcionais (RF)

ID Descrição do Requisito Criticidade
RF-01 Registro diário de atividades agrícolas em arquivo texto delimitado Alta
RF-02 Modelagem de classes com estimativa de produtividade e ciclo de culturas Alta
RF-03 Tabelas no SQLite para talhões, safras, insumos e apontamentos de colheita Alta
RF-04 Interface Web Streamlit com mapas de talhões, gráficos e métricas de produção Alta
RF-05 Hub central e suíte de validação automatizada de regras agrícolas Média

🛡️ Requisitos Não-Funcionais (RNF)

ID Especificação Arquitetural Criticidade
RNF-01 Capacidade de operação offline em terminais de campo sem conexão Alta
RNF-02 Consistência de dados temporais e validação de cronologia de datas Alta
RNF-03 Visualização leve e intuitiva adaptada para telas de tablets e notebooks Média
RNF-04 Uso estrito de recursos nativos nos degraus de base Alta
RNF-05 Arquitetura progressiva facilitando a assimilação dos 4 degraus Alta

🔗 Matriz de Rastreabilidade (RTM)

User Story Requisito Funcional Requisito Não-Funcional Módulo / Artefato de Código Status de Homologação
US-01 RF-01 RNF-01, RNF-04 degrau_1_imperativo_txt/main.py Testado
US-02 RF-02 RNF-02, RNF-05 degrau_2_primeira_classe/main.py Testado
US-03 RF-03 RNF-01, RNF-02 degrau_3_sqlite/main.py Testado
US-04 RF-04 RNF-03, RNF-05 degrau_4_interface_streamlit/app.py Testado
US-05 RF-05 RNF-01, RNF-05 main.py & test_degrau_*.py Testado

🏁 Checkpoints de Qualidade & Homologação

Checkpoint Escopo da Validação Artefato Verificado Status
CP-01 Script Imperativo & Persistência Inicial degrau_1_* PASS ✅
CP-02 Modelagem Orientada a Objetos degrau_2_* PASS ✅
CP-03 Integridade Relacional no SQLite degrau_3_* PASS ✅
CP-04 Interface Visual & Usabilidade degrau_4_* PASS ✅
CP-05 Hub Launcher Central & Suíte de Testes main.py + test_*.py PASS ✅