@@ -576,6 +576,10 @@ class TOPPIsobaricWorkflow :
576576
577577 channel_extractor.registerChannelsInOutputMap (cmap, mz_file);
578578
579+ // Collect peptide IDs without corresponding MS3 spectra (thread-safe collection)
580+ // Note: unassigned_pep_ids is shared across threads; push_back is protected by critical section
581+ PeptideIdentificationList unassigned_pep_ids;
582+
579583 #pragma omp parallel for /* num_threads(inner_threads)*/
580584 for (int64_t pep_idx = 0 ; pep_idx < static_cast <int64_t >(pep_ids.size ()); ++pep_idx)
581585 {
@@ -589,6 +593,23 @@ class TOPPIsobaricWorkflow :
589593 std::vector<std::pair<double ,unsigned >> channel_qc (quant_method->getNumberOfChannels (), std::make_pair (std::numeric_limits<double >::quiet_NaN (), 0 ));
590594
591595 auto [quant_spec_idx, id_spec_idx, ms1_spec_idx] = getSpecIdxs_ (ms2spec_it->second , exp, has_ms3);
596+
597+ // Check if MS3 spectrum is missing for MS2 spectrum
598+ if (has_ms3 && quant_spec_idx == -1 )
599+ {
600+ // Store peptide ID with file association for unassigned IDs
601+ PeptideIdentification unassigned_pep = pep;
602+ unassigned_pep.setIdentifier (ID_RUN_NAME_);
603+ unassigned_pep.setMetaValue (Constants::UserParam::ID_MERGE_INDEX, i);
604+ #pragma omp critical(unassigned_pep_ids_collection)
605+ {
606+ OpenMS_Log_warn << " MS2 spectrum " << spec_ref << " at index " << ms2spec_it->second
607+ << " does not have a corresponding MS3 spectrum. Skipping quantification and adding to unassigned.\n " ;
608+ unassigned_pep_ids.push_back (std::move (unassigned_pep));
609+ }
610+ continue ;
611+ }
612+
592613 auto [quant_purity, id_purity] = getPurities_ (quant_spec_idx, id_spec_idx, ms1_spec_idx, exp, has_ms3, max_precursor_isotope_deviation, calc_id_purity, interpolate_precursor_purity);
593614
594615 if (has_ms3 && exp[quant_spec_idx].getMSLevel () != 3 )
@@ -628,6 +649,17 @@ class TOPPIsobaricWorkflow :
628649 }
629650 channel_extractor.printStatsWithMissing (qc);
630651
652+ // Add peptide IDs without MS3 spectra to unassigned IDs
653+ if (!unassigned_pep_ids.empty ())
654+ {
655+ OPENMS_LOG_INFO << " Adding " << unassigned_pep_ids.size ()
656+ << " peptide identifications without MS3 spectra to unassigned IDs." << std::endl;
657+ auto & cmap_unassigned = cmap.getUnassignedPeptideIdentifications ();
658+ cmap_unassigned.insert (cmap_unassigned.end (),
659+ std::make_move_iterator (unassigned_pep_ids.begin ()),
660+ std::make_move_iterator (unassigned_pep_ids.end ()));
661+ }
662+
631663 // TODO if we want to support normalization, we either need to replace the quantifier with corrector and normalizer separately
632664 // or init the normalizer from the quantifier settings.
633665 // But honestly, most downstream software can do it better, so I would not bother. Just export to mzTab and do it in R/python.
0 commit comments