@@ -753,13 +753,41 @@ void WorkflowHelpers::injectAODWriter(WorkflowSpec& workflow, ConfigContext cons
753753 // create DataOutputDescriptor
754754 std::shared_ptr<DataOutputDirector> dod = AnalysisSupportHelpers::getDataOutputDirector (ctx);
755755
756+ auto hasWritableAOD = std::ranges::any_of (dec.outputsInputs , [](InputSpec const & spec) {
757+ return DataSpecUtils::partialMatch (spec, writableAODOrigins);
758+ });
759+ auto hasTFNumber = std::ranges::any_of (dec.outputsInputs , [](InputSpec const & spec) {
760+ return DataSpecUtils::partialMatch (spec, header::DataOrigin{" TFN" }) &&
761+ DataSpecUtils::partialMatch (spec, header::DataDescription{" TFNumber" });
762+ });
763+ auto hasTFFileName = std::ranges::any_of (dec.outputsInputs , [](InputSpec const & spec) {
764+ return DataSpecUtils::partialMatch (spec, header::DataOrigin{" TFF" }) &&
765+ DataSpecUtils::partialMatch (spec, header::DataDescription{" TFFilename" });
766+ });
767+ // AMD metadata is part of AODOrigins, but metadata alone is not enough to
768+ // run the AOD writer unless the same topology can provide TF bookkeeping.
769+ auto canWriteMetadataOnly = hasTFNumber && hasTFFileName;
770+
756771 // select outputs of type AOD which need to be saved
757772 dec.outputsInputsAOD .clear ();
758773 for (auto ii = 0u ; ii < dec.outputsInputs .size (); ii++) {
759- if (DataSpecUtils::partialMatch (dec.outputsInputs [ii], AODOrigins)) {
760- auto ds = dod->getDataOutputDescriptors (dec.outputsInputs [ii]);
761- if (ds.size () > 0 || dec.isDangling [ii]) {
762- dec.outputsInputsAOD .emplace_back (dec.outputsInputs [ii]);
774+ auto const & input = dec.outputsInputs [ii];
775+ if (!DataSpecUtils::partialMatch (input, AODOrigins)) {
776+ continue ;
777+ }
778+ // Avoid injecting an AOD writer in detector-only subworkflows where the
779+ // only AOD-like output is AMD metadata from the META collector.
780+ if (DataSpecUtils::partialMatch (input, header::DataOrigin{" AMD" }) && !hasWritableAOD && !canWriteMetadataOnly) {
781+ continue ;
782+ }
783+ auto ds = dod->getDataOutputDescriptors (input);
784+ if (ds.size () > 0 || dec.isDangling [ii]) {
785+ dec.outputsInputsAOD .emplace_back (input);
786+ if (DataSpecUtils::partialMatch (input, header::DataOrigin{" AMD" })) {
787+ // Metadata collected from META outputs is republished as AMD sporadic data.
788+ // Keep the AOD writer input sporadic so it can consume it together with
789+ // the timeframe AMD metadata emitted directly by AOD producers.
790+ dec.outputsInputsAOD .back ().lifetime = Lifetime::Sporadic;
763791 }
764792 }
765793 }
@@ -772,15 +800,18 @@ void WorkflowHelpers::injectAODWriter(WorkflowSpec& workflow, ConfigContext cons
772800 auto fileSink = AnalysisSupportHelpers::getGlobalAODSink (ctx);
773801 workflow.push_back (fileSink);
774802
775- auto it = std::find_if (dec.outputsInputs .begin (), dec.outputsInputs .end (), [](InputSpec const & spec) -> bool {
776- return DataSpecUtils::partialMatch (spec, o2::header::DataOrigin (" TFN" ));
777- });
778- dec.isDangling [std::distance (dec.outputsInputs .begin (), it)] = false ;
779-
780- it = std::find_if (dec.outputsInputs .begin (), dec.outputsInputs .end (), [](InputSpec const & spec) -> bool {
781- return DataSpecUtils::partialMatch (spec, o2::header::DataOrigin (" TFF" ));
782- });
783- dec.isDangling [std::distance (dec.outputsInputs .begin (), it)] = false ;
803+ // TFN/TFF are added to the writer inputs above, but they may be absent in
804+ // metadata-only topologies. Only mark them as non-dangling when present.
805+ auto markAsMatched = [&](header::DataOrigin origin) {
806+ auto it = std::find_if (dec.outputsInputs .begin (), dec.outputsInputs .end (), [&](InputSpec const & spec) -> bool {
807+ return DataSpecUtils::partialMatch (spec, origin);
808+ });
809+ if (it != dec.outputsInputs .end ()) {
810+ dec.isDangling [std::distance (dec.outputsInputs .begin (), it)] = false ;
811+ }
812+ };
813+ markAsMatched (header::DataOrigin{" TFN" });
814+ markAsMatched (header::DataOrigin{" TFF" });
784815 }
785816}
786817
0 commit comments