📚 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.
Trilha de Especialização — Nível Introdutório (Curso 25: Sistemas Desktop com Python)
- Visão Geral dos 10 Miniprojetos: 🌱 Guia Pedagógico de Miniprojetos
- Catálogo Geral: 🏗️ Catálogo de Projetos
- ➡️ Implementação Prática: Miniprojeto 08 (Código Completo em 4 Degraus)
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:
.txt delimitados.Talhao com métodos que calculam produtividade em kg/ha e custo unitário por kg colhido.talhoes 1:N registros_campo) com agregações SQL condicionais (CASE WHEN).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
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
No VS Code / PyCharm, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:
cd sistemas_python_intro_08_campo_rural
# 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
.txt)No primeiro degrau, gravamos cada talhão no arquivo campo.txt no formato talhao,cultura,area_ha,custo_reais,colheita_kg.
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
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()
Talhao e CadernoCampo)No segundo degrau, a classe Talhao calcula dinamicamente a produtividade em kg/ha e o custo médio por quilo colhido.
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
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()
No terceiro degrau, criamos as tabelas talhoes e registros_campo no SQLite, usando SUM(CASE WHEN...) para consolidar custos e colheitas.
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()
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()
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.
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")
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()
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.
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()
# 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)
A imagem abaixo ilustra a esteira evolutiva dos 4 degraus didáticos do miniprojeto:
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
| 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 |
| 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 |
| 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 |
| 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 |
| 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 ✅ |