fertpy 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. fertpy/__init__.py +15 -0
  2. fertpy/cli/parametros_cli.py +33 -0
  3. fertpy/core/domain/classe.py +21 -0
  4. fertpy/core/domain/criterio.py +47 -0
  5. fertpy/core/domain/exceptions.py +25 -0
  6. fertpy/core/domain/intervalo.py +41 -0
  7. fertpy/core/domain/modelo_agronomico.py +112 -0
  8. fertpy/core/domain/recomendacao.py +38 -0
  9. fertpy/core/engine/avaliador.py +73 -0
  10. fertpy/core/engine/calculo_calagem.py +37 -0
  11. fertpy/correcao_solo/__init__.py +3 -0
  12. fertpy/correcao_solo/calagem.py +46 -0
  13. fertpy/infra/loaders/adubacao_loader.py +36 -0
  14. fertpy/infra/loaders/correcao_loader.py +29 -0
  15. fertpy/infra/parsing/condicao.py +27 -0
  16. fertpy/infra/parsing/intervalo.py +72 -0
  17. fertpy/infra/parsing/modelo_agronomico.py +58 -0
  18. fertpy/knowledge/boletim_100/adubacao/milho/__init__.py +0 -0
  19. fertpy/knowledge/boletim_100/adubacao/milho/graos/fosforo.yaml +101 -0
  20. fertpy/knowledge/boletim_100/adubacao/milho/graos/nitrogenio.yaml +65 -0
  21. fertpy/knowledge/boletim_100/adubacao/milho/graos/potassio.yaml +90 -0
  22. fertpy/knowledge/boletim_100/adubacao/milho/silagem/fosforo.yaml +101 -0
  23. fertpy/knowledge/boletim_100/adubacao/milho/silagem/nitrogenio.yaml +65 -0
  24. fertpy/knowledge/boletim_100/adubacao/milho/silagem/potassio.yaml +79 -0
  25. fertpy/knowledge/boletim_100/calagem/milho.yaml +17 -0
  26. fertpy/knowledge/embrapa/solo/textura.yaml +51 -0
  27. fertpy/nutrientes/__init__.py +9 -0
  28. fertpy/nutrientes/fosforo.py +54 -0
  29. fertpy/nutrientes/nitrogenio.py +58 -0
  30. fertpy/nutrientes/potassio.py +54 -0
  31. fertpy/services/validacao_parametros.py +77 -0
  32. fertpy/utils/parametros.py +49 -0
  33. fertpy-0.2.1.dist-info/METADATA +588 -0
  34. fertpy-0.2.1.dist-info/RECORD +38 -0
  35. fertpy-0.2.1.dist-info/WHEEL +5 -0
  36. fertpy-0.2.1.dist-info/licenses/AUTHORS.md +36 -0
  37. fertpy-0.2.1.dist-info/licenses/LICENSE +206 -0
  38. fertpy-0.2.1.dist-info/top_level.txt +1 -0
fertpy/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ from .nutrientes import (
2
+ Fosforo,
3
+ Nitrogenio,
4
+ Potassio,
5
+ )
6
+
7
+ from .correcao_solo import Calagem
8
+
9
+
10
+ __all__ = [
11
+ "Fosforo",
12
+ "Nitrogenio",
13
+ "Potassio",
14
+ "Calagem",
15
+ ]
@@ -0,0 +1,33 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ from fertpy.utils.parametros import extrair_parametros
15
+
16
+
17
+ def imprimir_parametros(estrutura: dict[str, dict[str, list[str]]]) -> None:
18
+ linhas = []
19
+
20
+ for boletim, culturas in estrutura.items():
21
+ linhas.append(f"\nboletim: {boletim}")
22
+
23
+ for cultura, nutrientes in culturas.items():
24
+ linhas.append(f" Cultura: {cultura}")
25
+
26
+ for nutriente in nutrientes:
27
+ linhas.append(f" - {nutriente}")
28
+
29
+ return "\n".join(linhas)
30
+
31
+ def imprimir_parametros_cli():
32
+ dados = extrair_parametros()
33
+ print(imprimir_parametros(dados))
@@ -0,0 +1,21 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ from dataclasses import dataclass
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class Classe:
19
+ nome: str
20
+ tipo: str = "faixa"
21
+ descricao: str | None = None
@@ -0,0 +1,47 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ from dataclasses import dataclass
15
+ from typing import Any
16
+
17
+ from fertpy.core.domain.intervalo import Intervalo
18
+ from fertpy.core.domain.classe import Classe
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class Criterio:
23
+
24
+ condicoes: dict[str, Intervalo | str]
25
+ classe: Classe | None
26
+ recomendacao: Any
27
+
28
+ observacoes: str | None = None
29
+
30
+ def __post_init__(self):
31
+
32
+ if not isinstance(self.condicoes, dict):
33
+ raise TypeError("Condições deve ser um dict")
34
+
35
+ if not self.condicoes:
36
+ raise ValueError("Criterio precisa de pelo menos uma condição")
37
+
38
+ for nome, condicao in self.condicoes.items():
39
+
40
+ if not isinstance(nome, str) or not nome.strip():
41
+ raise TypeError("Nome de condição inválido")
42
+
43
+ if not isinstance(condicao, (Intervalo, str)):
44
+ raise TypeError(f"Condição inválida para {nome}")
45
+
46
+ if self.classe is not None and not isinstance(self.classe, Classe):
47
+ raise TypeError("Classe deve ser Classe ou None")
@@ -0,0 +1,25 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ class ParametroInvalidoError(ValueError):
15
+
16
+ def __init__(self, parametro: str, valor: str, validos: list[str]):
17
+ mensagem = (
18
+ f"{parametro} inválido: '{valor}', "
19
+ f"Valores válidos: {', '.join(validos)}"
20
+ )
21
+ super().__init__(mensagem)
22
+ self.parametro = parametro
23
+ self.valor = valor
24
+ self.validos = validos
25
+
@@ -0,0 +1,41 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ from dataclasses import dataclass
15
+
16
+ @dataclass(frozen=True)
17
+ class Intervalo:
18
+ minimo: float | None
19
+ maximo: float | None
20
+ inclui_min: bool = True
21
+ inclui_max: bool = False
22
+
23
+ def __post_init__(self):
24
+ if self.minimo is not None and self.maximo is not None:
25
+ if self.minimo > self.maximo:
26
+ raise ValueError("Minimo não pode ser maior que o maximo")
27
+
28
+ def contem(self, valor:float) -> bool:
29
+ if self.minimo is not None:
30
+ if self.inclui_min and valor < self.minimo:
31
+ return False
32
+ if not self.inclui_min and valor <= self.minimo:
33
+ return False
34
+
35
+ if self.maximo is not None:
36
+ if self.inclui_max and valor > self.maximo:
37
+ return False
38
+ if not self.inclui_max and valor >= self.maximo:
39
+ return False
40
+
41
+ return True
@@ -0,0 +1,112 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ from dataclasses import dataclass
15
+ from collections import defaultdict
16
+
17
+ from fertpy.core.domain.criterio import Criterio
18
+ from fertpy.core.domain.exceptions import ParametroInvalidoError
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class ModeloAgronomico:
23
+ nutriente: str
24
+ unidade_saida: str
25
+
26
+ criterios: list[Criterio]
27
+
28
+ nutriente_recomendado: str | None = None
29
+ unidade_entrada: str | None = None
30
+ metodo_analitico: str | None = None
31
+ fonte_referencia: dict | None = None
32
+
33
+ def __post_init__(self):
34
+
35
+ if not self.nutriente:
36
+ raise ValueError("O modelo não possui nutriente declarado")
37
+
38
+ if not self.unidade_saida:
39
+ raise ValueError("O modelo não possui unidade de saida declarado")
40
+
41
+ if not self.criterios:
42
+ raise ValueError("Modelo precisa de critérios")
43
+
44
+ for c in self.criterios:
45
+ if not isinstance(c, Criterio):
46
+ raise TypeError("Todos os critérios devem ser instâncias de criterio")
47
+
48
+ @property
49
+ def variaveis_esperadas(self) -> set[str]:
50
+ variaveis = set()
51
+
52
+ for criterio in self.criterios:
53
+ variaveis.update(criterio.condicoes.keys())
54
+
55
+ return variaveis
56
+
57
+ def valores_discretos_por_variavel(self) -> dict[str, set]:
58
+
59
+ valores = defaultdict(set)
60
+
61
+ for criterio in self.criterios:
62
+ for var, cond in criterio.condicoes.items():
63
+ if isinstance(cond, str):
64
+ valores[var].add(cond)
65
+
66
+ return valores
67
+
68
+ def validar_contexto(self, contexto: dict):
69
+ faltando = self.variaveis_esperadas - contexto.keys()
70
+ if faltando:
71
+ raise ValueError(
72
+ f"Variáveis obrigatórias ausentes: {sorted(faltando)}"
73
+ )
74
+
75
+ extras = contexto.keys() - self.variaveis_esperadas
76
+ if extras:
77
+ raise ValueError(
78
+ f"Variáveis inesperadas: {sorted(extras)}"
79
+ )
80
+
81
+ for var, valor in contexto.items():
82
+
83
+ exemplos = [
84
+ criterio.condicoes[var]
85
+ for criterio in self.criterios
86
+ if var in criterio.condicoes
87
+ ]
88
+
89
+ if not exemplos:
90
+ continue
91
+
92
+ exemplo = exemplos[0]
93
+
94
+ if isinstance(exemplo, str):
95
+ valores_validos = {
96
+ c.condicoes[var]
97
+ for c in self.criterios
98
+ if isinstance(c.condicoes[var], str)
99
+ }
100
+
101
+ if valor not in valores_validos:
102
+ raise ParametroInvalidoError(
103
+ var,
104
+ valor,
105
+ sorted(valores_validos)
106
+ )
107
+
108
+ else:
109
+ if not isinstance(valor, (int, float)):
110
+ raise TypeError(
111
+ f"Variável '{var}' deve ser numérica"
112
+ )
@@ -0,0 +1,38 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ from dataclasses import dataclass
15
+
16
+ from fertpy.core.domain.intervalo import Intervalo
17
+ from fertpy.core.domain.classe import Classe
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class Recomendacao:
22
+ nutriente: str
23
+
24
+ dose: float | Intervalo | str
25
+ unidade: str
26
+
27
+ classe: Classe
28
+
29
+ observacoes: list[str] | None = None
30
+ fonte: dict | None = None
31
+
32
+ def __post_init__(self):
33
+
34
+ if not self.nutriente:
35
+ raise ValueError("Recomendação precisa do parâmetro nutriente")
36
+
37
+ if not self.unidade:
38
+ raise ValueError("Recomendação precisa do parâmetro unidade")
@@ -0,0 +1,73 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ from fertpy.core.domain.classe import Classe
15
+ from fertpy.core.domain.criterio import Criterio
16
+ from fertpy.core.domain.intervalo import Intervalo
17
+ from fertpy.core.domain.recomendacao import Recomendacao
18
+
19
+
20
+ class Avaliador:
21
+
22
+ @staticmethod
23
+ def avaliar(
24
+ criterios: list[Criterio],
25
+ contexto: dict[str, float],
26
+ nutriente: str,
27
+ unidade: str,
28
+ observacoes: list[str] | None = None,
29
+ fonte: dict | None = None
30
+ ) -> Recomendacao:
31
+
32
+ for criterio in criterios:
33
+ for variavel, intervalo in criterio.condicoes.items():
34
+
35
+ if variavel not in contexto:
36
+ break
37
+
38
+ valor = contexto[variavel]
39
+
40
+ if isinstance(intervalo, Intervalo):
41
+
42
+ if not isinstance(valor, (int, float)):
43
+ break
44
+
45
+ if not intervalo.contem(valor):
46
+ break
47
+ else:
48
+ if valor != intervalo:
49
+ break
50
+
51
+ else:
52
+ classe_final = criterio.classe or Classe(nome="dose")
53
+
54
+ obs_final = []
55
+
56
+ if observacoes:
57
+ obs_final.extend(observacoes)
58
+
59
+ if criterio.observacoes:
60
+ obs_final.append(criterio.observacoes)
61
+
62
+ obs_final = obs_final or None
63
+
64
+ return Recomendacao(
65
+ nutriente=nutriente,
66
+ dose=criterio.recomendacao,
67
+ unidade=unidade,
68
+ classe=classe_final,
69
+ observacoes=obs_final,
70
+ fonte=fonte
71
+ )
72
+
73
+ raise ValueError("Nenhum critério compatível encontrado.")
@@ -0,0 +1,37 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ class CalagemEngine:
15
+
16
+ @staticmethod
17
+ def calcular(
18
+ v1: float,
19
+ ctc: float,
20
+ v2: float,
21
+ prnt: float,
22
+ dose_maxima: float | None = None,
23
+ dose_minima: float | None = None
24
+ ) -> float:
25
+
26
+ nc = (ctc * (v2 - v1)) / (10 * prnt)
27
+
28
+ if nc <= 0:
29
+ return 0.0
30
+
31
+ if dose_maxima is not None and nc > dose_maxima:
32
+ nc = dose_maxima
33
+
34
+ if dose_minima is not None and nc < dose_minima:
35
+ nc = dose_minima
36
+
37
+ return round(nc, 2)
@@ -0,0 +1,3 @@
1
+ from .calagem import Calagem
2
+
3
+ __all__ = ["Calagem"]
@@ -0,0 +1,46 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ from fertpy.infra.loaders.correcao_loader import carregar_modelo_correcao
15
+ from fertpy.core.engine.calculo_calagem import CalagemEngine
16
+
17
+
18
+ class Calagem:
19
+
20
+ def __init__(self, cultura: str):
21
+
22
+ yaml = carregar_modelo_correcao(
23
+ "boletim_100",
24
+ cultura
25
+ )["calagem"]
26
+
27
+ self.modelo = yaml
28
+
29
+ def calcular(
30
+ self,
31
+ v_atual: float,
32
+ ctc: float,
33
+ prnt: float | None = None
34
+ ) -> float:
35
+
36
+ parametros = self.modelo["parametros"]
37
+ limites = self.modelo.get("limites", {})
38
+
39
+ return CalagemEngine.calcular(
40
+ v1=v_atual,
41
+ ctc=ctc,
42
+ v2=parametros["v2_desejado"],
43
+ prnt=prnt or parametros["prnt_padrao"],
44
+ dose_maxima=limites.get("dose_maxima"),
45
+ dose_minima=limites.get("dose_minima")
46
+ )
@@ -0,0 +1,36 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ from importlib import resources
15
+ import yaml
16
+
17
+
18
+ def carregar_modelo_adubacao(
19
+ boletim: str,
20
+ cultura: str,
21
+ finalidade: str,
22
+ nutriente: str
23
+ ):
24
+
25
+ pacote_base = (
26
+ f"fertpy.knowledge."
27
+ f"{boletim}.adubacao."
28
+ f"{cultura}.{finalidade}"
29
+ )
30
+
31
+ nome_arquivo = f"{nutriente}.yaml"
32
+
33
+ with resources.files(pacote_base).joinpath(nome_arquivo).open(
34
+ "r", encoding="utf-8"
35
+ ) as f:
36
+ return yaml.safe_load(f)
@@ -0,0 +1,29 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ from importlib import resources
15
+ import yaml
16
+
17
+
18
+ def carregar_modelo_correcao(
19
+ boletim: str,
20
+ cultura: str
21
+ ):
22
+
23
+ pacote_base = f"fertpy.knowledge.{boletim}.calagem"
24
+ nome_arquivo = f"{cultura}.yaml"
25
+
26
+ with resources.files(pacote_base).joinpath(nome_arquivo).open(
27
+ "r", encoding="utf-8"
28
+ ) as f:
29
+ return yaml.safe_load(f)
@@ -0,0 +1,27 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ from fertpy.infra.parsing.intervalo import parse_intervalo
15
+
16
+
17
+ def parse_condicao(valor):
18
+ if isinstance(valor, (int, float)):
19
+ return parse_intervalo(str(valor))
20
+
21
+ if isinstance(valor, str):
22
+ try:
23
+ return parse_intervalo(valor)
24
+ except ValueError:
25
+ return valor.strip()
26
+
27
+ return valor
@@ -0,0 +1,72 @@
1
+ # Copyright 2026 Luiz Henrique de Lima Peguin
2
+ # and Pedro Henrique Escaranaro Brasil
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ import re
15
+
16
+ from fertpy.core.domain.intervalo import Intervalo
17
+
18
+
19
+ def parse_intervalo(texto: str) -> Intervalo:
20
+ texto = texto.strip()
21
+
22
+ #===============================================
23
+ # Forma matematica: x <= a <= y, x < a < y, etc
24
+ #===============================================
25
+
26
+ padrao_duplo = re.compile(
27
+ r"^\s*(-?\d+\.?\d*)\s*(<=|<)\s*[a-zA-Z_]+\s*(<=|<)\s*(-?\d+\.?\d*)\s*$"
28
+ )
29
+
30
+ m = padrao_duplo.match(texto)
31
+
32
+ if m:
33
+ a = float(m.group(1))
34
+ limite_inferior = m.group(2)
35
+ limite_superior = m.group(3)
36
+ b = float(m.group(4))
37
+
38
+ if a >= b:
39
+ raise ValueError(f"intervalo inválido (a >= b): {texto}")
40
+
41
+ inc_min = limite_inferior == "<="
42
+ inc_max = limite_superior == "<="
43
+
44
+ return Intervalo(a, b, inc_min, inc_max)
45
+
46
+ #============================
47
+ # Intervalos abertos simples
48
+ #============================
49
+
50
+ if texto.startswith(">="):
51
+ return Intervalo(float(texto[2:].strip()), None, True, False)
52
+
53
+ if texto.startswith("<="):
54
+ return Intervalo(None, float(texto[2:].strip()), False, True)
55
+
56
+ if texto.startswith(">"):
57
+ return Intervalo(float(texto[1:].strip()), None, False, False)
58
+
59
+ if texto.startswith("<"):
60
+ return Intervalo(None, float(texto[1:].strip()), False, False)
61
+
62
+ #=============
63
+ # Valor exato
64
+ #=============
65
+
66
+ try:
67
+ v = float(texto)
68
+ return Intervalo(v, v, True, True)
69
+ except ValueError:
70
+ pass
71
+
72
+ raise ValueError(f"Intervalo inválido: {texto}")