@@ -130,15 +130,42 @@ def rank(
130130 # Step 3: Generate peptides
131131 click .echo ("🔬 Generating mutant peptides..." )
132132 mhci_lens = [int (x ) for x in mhci_lengths .split ("," )]
133- # (Simplified — would use protein DB in full pipeline)
134133 click .echo (f" Peptide lengths: MHC-I { mhci_lens } " )
135134
136- # Step 4: Ranking (placeholder candidates for direct VCF mode)
135+ # NOTE: full peptide generation requires a canine protein FASTA database.
136+ # In the full pipeline (Snakemake), this is handled by the alignment steps.
137+ from dogneo .core .peptides import ProteinDatabase , generate_peptides
138+ from dogneo .core .binding import BindingPrediction
139+ from dogneo .core .ranking import build_candidates
140+
141+ protein_db = ProteinDatabase ()
142+ # TODO: accept --protein-db CLI flag for standalone usage
143+ peptides_by_variant : dict [str , list ] = {}
144+ predictions_by_peptide : dict [str , list ] = {}
145+
146+ for v in coding :
147+ peps = generate_peptides (v , protein_db , lengths = mhci_lens )
148+ if peps :
149+ peptides_by_variant [v .variant_id ] = peps
150+
151+ if not peptides_by_variant :
152+ click .secho (
153+ "⚠️ No peptides generated — this likely means no canine protein DB "
154+ "was loaded. Use the full Snakemake pipeline (dogneo run) or provide "
155+ "a pre-built candidates JSON to the report command." ,
156+ fg = "yellow" ,
157+ )
158+
159+ # Step 4: Ranking
137160 click .echo ("📊 Scoring and ranking candidates..." )
138- # In a full implementation, this would chain through binding prediction
139- # For now, we create candidates from variant data
140- candidates : list [NeoantigenCandidate ] = []
141- click .echo (f" { len (candidates )} candidates ranked" )
161+ candidates = build_candidates (coding , peptides_by_variant , predictions_by_peptide )
162+
163+ if allele_list and candidates :
164+ ranked = rank_candidates (candidates )
165+ click .echo (f" { len (ranked )} candidates ranked" )
166+ else :
167+ ranked = candidates
168+ click .echo (f" { len (ranked )} candidates (unranked — no alleles or binding data)" )
142169
143170 # Step 5: Export
144171 if "tsv" in format_list :
@@ -183,9 +210,41 @@ def report(input_path: str, fmt: str, output: str, llm_tier: str) -> None:
183210 with open (input_path ) as f :
184211 data = _json .load (f )
185212
186- # Reconstruct candidates (simplified)
187- click .echo (f"📄 Generating { fmt } report..." )
188- click .echo (f" Input: { data .get ('metadata' , {}).get ('total_candidates' , '?' )} candidates" )
213+ total = data .get ("metadata" , {}).get ("total_candidates" , "?" )
214+ sample_id = data .get ("metadata" , {}).get ("sample_id" , "UNKNOWN" )
215+ click .echo (f"📄 Generating { fmt } report from { total } candidates..." )
216+
217+ from dogneo .report .generator import ReportGenerator
218+ from dogneo .config import LLMConfig
219+ from dogneo .llm .router import LLMRouter
220+
221+ llm_router = None
222+ if llm_tier != "none" :
223+ llm_config = LLMConfig (default_tier = llm_tier )
224+ llm_router = LLMRouter (config = llm_config )
225+
226+ gen = ReportGenerator (llm_router = llm_router )
227+ output_path = Path (output )
228+
229+ # The JSON's "candidates" list already has serialized candidate dicts
230+ candidate_dicts = data .get ("candidates" , [])
231+
232+ if fmt == "html" :
233+ gen .generate_html (
234+ [], sample_id ,
235+ parameters = data .get ("metadata" , {}).get ("parameters" , {}),
236+ alleles = data .get ("metadata" , {}).get ("alleles" , []),
237+ output_path = output_path ,
238+ pre_rendered_candidates = candidate_dicts ,
239+ )
240+ else :
241+ gen .generate_markdown (
242+ [], sample_id ,
243+ parameters = data .get ("metadata" , {}).get ("parameters" , {}),
244+ output_path = output_path ,
245+ pre_rendered_candidates = candidate_dicts ,
246+ )
247+
189248 click .secho (f"✅ Report written to: { output } " , fg = "green" )
190249
191250
0 commit comments