Skip to content

Implementa pipeline de geração de árvore filogenética em formato Newick - #15

Merged
enniolopes merged 28 commits into
mainfrom
feature/tree-builder
Jun 4, 2026
Merged

Implementa pipeline de geração de árvore filogenética em formato Newick#15
enniolopes merged 28 commits into
mainfrom
feature/tree-builder

Conversation

@pedrozenatte

Copy link
Copy Markdown
Collaborator

Adiciona o módulo damicore_tree_builder, responsável por gerar árvores filogenéticas a partir de uma matriz de distâncias em CSV usando o algoritmo Neighbor-Joining.

O fluxo principal foi organizado em módulos com responsabilidades separadas:

  • matrix_loader.py: carrega e valida a matriz de distâncias, garantindo formato quadrado, rótulos consistentes, valores numéricos, diagonal zerada, simetria e ausência de distâncias negativas.
  • neighbor_joining.py: converte a matriz completa para o formato esperado pelo BioPython e constrói a árvore com Neighbor-Joining.
  • newick_writer.py: salva a árvore gerada em arquivo no formato Newick.
  • cli.py: expõe uma interface de linha de comando para executar o pipeline completo e retornar um relatório JSON de sucesso ou erro como estabelecido no contrato.
  • script.sh: adiciona um comando prático para executar o pipeline com caminhos de entrada e saída já configurados. Dessa forma, basta estar na basta que contém os módulos do damicore_tree_builder e rodar ./script.sh.

OBS: Não executei testes automatizados ainda.

…ento de erros e retorna o caminho do arquivo gerado. Adiciona arquivo de saída com a árvore filogenética em formato Newick.
…es Neighbor-Joining, incluindo a carga de matriz de distâncias, construção da árvore, salvamento em formato Newick e geração de relatório em JSON para satisfazer o contrato.
…ndo no construtor de árvores Neighbor-Joining.
…e árvores Neighbor-Joining, incluindo carregamento da matriz de distâncias, construção da árvore e geração de relatório de sucesso.
… comando de execução em um script prático, para rodar com ./script.sh
…rquivo Newick com a árvore filogenética gerada.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new damicore_tree_builder model that builds a phylogenetic tree from a distance-matrix CSV using BioPython's Neighbor-Joining implementation and writes the result in Newick format. The pipeline is split into a loader/validator, an NJ builder, a Newick writer, and a CLI that prints a JSON success/error report, plus a helper shell script and a sample output fixture.

Changes:

  • New modules matrix_loader.py, neighbor_joining.py, newick_writer.py implementing the three pipeline stages as classes.
  • New cli.py (argparse-based) plus script.sh wrapping the end-to-end run.
  • New fixture output-phylo-tree-...newick containing a generated tree.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
models/damicore_tree_builder/src/damicore_tree_builder/matrix_loader.py CSV loader with shape/label/numeric/diagonal/symmetry/non-negativity validation.
models/damicore_tree_builder/src/damicore_tree_builder/neighbor_joining.py Converts full matrix to BioPython lower-triangular form and runs DistanceTreeConstructor.nj.
models/damicore_tree_builder/src/damicore_tree_builder/newick_writer.py Persists the resulting tree to a .newick file via Phylo.write.
models/damicore_tree_builder/src/damicore_tree_builder/cli.py Argparse CLI orchestrating loader → builder → writer, emitting JSON report.
models/damicore_tree_builder/src/damicore_tree_builder/script.sh Convenience shell wrapper that cds into the package and runs the CLI.
models/fixtures/dataset-seade-pop-age/output-phylo-tree-...newick Sample generated Newick tree checked in alongside the code.
Comments suppressed due to low confidence (3)

models/damicore_tree_builder/src/damicore_tree_builder/matrix_loader.py:28

  • DEFAULT_INPUT_PATH is an absolute path (/models/...) rooted at the filesystem root rather than the repo. Instantiating MatrixLoader() without arguments will always raise FileNotFoundError. Since the CLI already requires --input, remove this default entirely and make input_path a required parameter, or compute the path relative to the repo.
DEFAULT_INPUT_PATH = "/models/fixtures/dataset-seade-pop-age/output-distance-ncd-gzip-cod_distr-ano-idade.csv"

models/damicore_tree_builder/src/damicore_tree_builder/matrix_loader.py:50

  • The repo's conventions are function-first: classes should only be @dataclass(frozen=True) data containers or Protocol implementations. MatrixLoader, NeighborJoining, and NewickWriter are stateless wrappers around a single operation and should be plain module-level functions (e.g., load_matrix(path), build_nj_tree(names, matrix), write_newick(tree, path)), with private helpers prefixed _. This also removes the redundant __init__/state and simplifies testing.
class MatrixLoader:
    """
    Loads and validates a distance matrix from a CSV file.

    The MatrixLoader class reads a CSV file where rows and columns are labeled
    with the same element names. It extracts the element names, converts the
    distance values to floats, and validates that the matrix is suitable for
    the Neighbor-Joining algorithm.

    The validation checks whether:
    - the matrix is square;
    - row labels and column labels match;
    - the diagonal values are zero or close to zero;
    - the matrix is symmetric;
    - all distance values are numeric;
    - there are no negative distances.

    After validation, the class returns the element names and the distance
    matrix in a format that can be used by the tree-building algorithm.
    """

models/damicore_tree_builder/src/damicore_tree_builder/matrix_loader.py:104

  • _validate_numeric_values calls dataframe.astype(float) and discards the result, so it only serves as validation — but then _validate_dataframe immediately calls dataframe.to_numpy(dtype=float), which performs the same conversion a second time and raises on failure. The first call is therefore a redundant full-matrix pass. Either remove _validate_numeric_values and rely on the to_numpy(dtype=float) conversion (wrapping it to raise a clearer ValueError), or use the converted result instead of doing the work twice.
        self._validate_numeric_values(dataframe)

        matrix = dataframe.to_numpy(dtype=float)

        self._validate_non_negative_distances(matrix)
        self._validate_zero_diagonal(matrix)
        self._validate_symmetric_matrix(matrix)

Comment thread models/damicore_tree_builder/src/damicore_tree_builder/matrix_loader.py Outdated
Comment thread models/damicore_tree_builder/src/damicore_tree_builder/newick_writer.py Outdated
Comment thread models/damicore_tree_builder/src/damicore_tree_builder/cli.py Outdated
Comment thread models/damicore_tree_builder/src/damicore_tree_builder/cli.py Outdated
Comment thread models/damicore_tree_builder/src/damicore_tree_builder/neighbor_joining.py Outdated
Comment thread models/damicore_tree_builder/src/damicore_tree_builder/neighbor_joining.py Outdated
Comment thread models/damicore_tree_builder/src/damicore_tree_builder/script.sh Outdated
Comment thread models/damicore_tree_builder/src/damicore_tree_builder/cli.py Outdated
Comment thread models/damicore_tree_builder/src/damicore_tree_builder/cli.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A classe MatrixLoader deve ser substituída por funções. Conforme as convenções do projeto, classes são permitidas apenas como @dataclass(frozen=True) ou implementações de Protocol — este caso não é nenhum dos dois. O padrão de uso MatrixLoader(path).load() é uma função disfarçada de classe; input_path pertence como parâmetro, não como estado de instância.

Outros problemas:

  • List, Tuple do typing → usar built-ins list[...], tuple[...] (Python 3.12)
  • Docstrings no estilo Google (Args:, Returns:) → obrigatório estilo NumPy
  • Bloco if __name__ == "__main__": deve ser removido; código de teste pertence a tests/
  • Comentários de seção (# Libraries, # Builder, # ----) explicam o quê, não por quê — remover

Correção: expor load_matrix(input_path: Path) -> tuple[list[str], list[list[float]]] como ponto de entrada público, com os métodos privados atuais como helpers de módulo prefixados com _, recebendo argumentos explícitos.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

veja outros arquivos que possam ter o mesmo problema e alterar para programação funcional

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

o cli.py já irá fazer isso, não precisamos desse arquivo script.sh

@enniolopes
enniolopes merged commit fd83186 into main Jun 4, 2026
8 checks passed
@enniolopes enniolopes linked an issue Jun 4, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

init damicore-tree-builder

3 participants