Index: /trunk/BNC/src/bnchelp.html
===================================================================
--- /trunk/BNC/src/bnchelp.html	(revision 11090)
+++ /trunk/BNC/src/bnchelp.html	(revision 11091)
@@ -5138,9 +5138,10 @@
       signal (or to another common signal, if the stream lacks it), are compared with
       <ul>
-        <li>each other AC separately (one vote per AC, own AC left out, combinations not voting), with the RMS of
-          the differences and the number of DCBs with opposite sign (only DCBs of at least 0.3 m are used for the
-          sign). A stream is reported to use the opposite sign if it has the opposite sign for most DCBs to more than
-          half of at least two other ACs - with only two ACs, it cannot be decided which one is wrong. Satellites
-          with DCBs deviating from the median of at least 3 other ACs are listed,</li>
+        <li>each other solution separately (see 'CONSENSUS' below; also the other solutions of the own AC,
+          combinations not voting), with the RMS of the differences and the number of DCBs with opposite sign (only
+          DCBs of at least 0.3 m are used for the sign). A stream is reported to use the opposite sign if it has the
+          opposite sign for most DCBs to more than half of at least two solutions of other ACs - with only two ACs, it
+          cannot be decided which one is wrong. Satellites with DCBs deviating from the median of at least 3
+          solutions of other ACs are listed,</li>
         <li>the selected 'QC Reference' stream instead, if it is not 'CONSENSUS',</li>
         <li>the 'QC Bias File', if specified.</li>
@@ -5152,6 +5153,6 @@
       gets errors of twice the biases, e.g.
       <pre>
-  G code biases compared with the other ACs (DCBs to C1W): BKG RMS 2.17 m, opposite sign 48/49; CNE RMS 2.44 m, opposite sign 98/101
-  G code biases with the opposite sign of 2 of 2 other ACs (BKG CNE) - SINEX sign convention instead of the SSR one?
+  G code biases compared with the other solutions (DCBs to C1W): BKG00 RMS 2.17 m, opposite sign 48/49; CNE00 RMS 2.44 m, opposite sign 98/101
+  G code biases with the opposite sign of 2 of 2 solutions of other ACs (BKG00 CNE00) - SINEX sign convention instead of the SSR one?
   G code biases compared with Bias SINEX file CAS1MGXRAP_01D_01D_DCB.BSX (DCBs to C1W, 32 satellites, 141 DCBs): RMS of the differences 2.401 m, opposite sign for 106 of 107 DCBs (|DCB| &gt;= 0.3 m)
 </pre>
@@ -5191,8 +5192,11 @@
   </p>
   <ul>
-    <li>'CONSENSUS' (default): per satellite the median of the other analysis centers (at least two). All decoded
-      streams contribute, also those not selected under 'QC Streams' (except the excluded ones). An AC with several
-      streams (IGS-SSR and RTCM-SSR format, APC and CoM) has one vote, the median of its streams; the AC is derived
-      from the stream name (e.g. 'CNE' from 'SSRA00CNE1'). A stream is compared without the streams of its own AC.
+    <li>'CONSENSUS' (default): per satellite the median of the solutions of the other analysis centers (at least
+      two). All decoded streams contribute, also those not selected under 'QC Streams' (except the excluded ones). A
+      solution is the AC and the two digits of the stream name, e.g. 'GFZ00' for SSRA00GFZ0, SSRA00GFZ1, SSRC00GFZ0
+      and SSRC00GFZ1, and 'GFZ01' for SSRA01GFZ0 (OSB streams like OSBC00WHU1 belong to the solution 'WHU00'). The
+      streams of a solution (IGS-SSR and RTCM-SSR format, APC and CoM) have one vote, the median of its streams;
+      different solutions of an AC vote separately. A stream is compared without all solutions of its own AC, as
+      these are not independent (same software and models).
       Combination products ('INTERNAL' and the IGS combinations '...IGS.') do not vote, as they are derived from the
       ACs, but are compared with the consensus. A single erroneous AC does not affect the consensus, and no
Index: /trunk/BNC/src/ssrQc.cpp
===================================================================
--- /trunk/BNC/src/ssrQc.cpp	(revision 11090)
+++ /trunk/BNC/src/ssrQc.cpp	(revision 11091)
@@ -172,10 +172,28 @@
 
 // Analysis center of a stream: the 3 characters of the AC in mountpoints
-// like SSRA00CNE1, otherwise the stream name itself
+// like SSRA00CNE1 or OSBC00WHU1, otherwise the stream name itself
 ////////////////////////////////////////////////////////////////////////////
 QString t_ssrQc::acOf(const QString& staID) {
-  static const QRegularExpression re("^SSR[AC][0-9]{2}([A-Z0-9]{3})[0-9]$");
+  static const QRegularExpression re("^(?:SSR|OSB)[AC][0-9]{2}([A-Z0-9]{3})[0-9]$");
   QRegularExpressionMatch mm = re.match(staID);
   return mm.hasMatch() ? mm.captured(1) : staID;
+}
+
+// Solution of an AC: AC and the two digits of the mountpoint, e.g. GFZ00 for
+// SSRA00GFZ0/SSRC00GFZ1 and GFZ01 for SSRA01GFZ0; the streams of a solution
+// (formats, APC/CoM) have one vote in the consensus
+////////////////////////////////////////////////////////////////////////////
+QString t_ssrQc::solOf(const QString& staID) {
+  static const QRegularExpression re("^(?:SSR|OSB)[AC]([0-9]{2})([A-Z0-9]{3})[0-9]$");
+  QRegularExpressionMatch mm = re.match(staID);
+  return mm.hasMatch() ? mm.captured(2) + mm.captured(1) : staID;
+}
+
+// AC of a solution (GFZ01 -> GFZ)
+////////////////////////////////////////////////////////////////////////////
+QString t_ssrQc::acOfSol(const QString& sol) {
+  static const QRegularExpression re("^([A-Z0-9]{3})[0-9]{2}$");
+  QRegularExpressionMatch mm = re.match(sol);
+  return mm.hasMatch() ? mm.captured(1) : sol;
 }
 
@@ -505,5 +523,9 @@
     }
     if (_reference == "CONSENSUS") {
-      if (!votes(sta) || acOf(sta) == acOf(staID) || (!onlyAC.isEmpty() && acOf(sta) != onlyAC)) {
+      if (!votes(sta)) {
+        continue;
+      }
+      if (onlyAC.isEmpty() ? (acOf(sta) == acOf(staID))
+                           : (solOf(sta) != onlyAC || solOf(sta) == solOf(staID))) {
         continue;
       }
@@ -521,5 +543,5 @@
     for (auto it = bb.constBegin(); it != bb.constEnd(); ++it) {
       if (it.key() != base) {
-        acDcbs[acOf(sta)][it.key()].append(bb[base] - it.value());
+        acDcbs[solOf(sta)][it.key()].append(bb[base] - it.value());
       }
     }
@@ -668,6 +690,6 @@
     QSet<QString> acs;
     for (auto itS = _cbLast.constBegin(); itS != _cbLast.constEnd(); ++itS) {
-      if (itS.key() != staID && votes(itS.key()) && acOf(itS.key()) != acOf(staID)) {
-        acs.insert(acOf(itS.key()));
+      if (itS.key() != staID && votes(itS.key()) && solOf(itS.key()) != solOf(staID)) {
+        acs.insert(solOf(itS.key()));
       }
     }
@@ -683,22 +705,25 @@
         continue;
       }
-      numACs++;
       bases.unite(cc.bases);
       items << QString("%1 RMS %2 m, opposite sign %3/%4").arg(ac).arg(cc.rms(), 0, 'f', 2)
                                                          .arg(cc.numOpp).arg(cc.numSign);
-      if (cc.opposite()) {
-        oppACs << ac;
-      }
-    }
-    if (numACs > 0) {
+      // the sign rule counts the solutions of other ACs only
+      if (acOfSol(ac) != acOf(staID)) {
+        numACs++;
+        if (cc.opposite()) {
+          oppACs << ac;
+        }
+      }
+    }
+    if (!items.isEmpty()) {
       QStringList ll = bases.values();
       ll.sort();
-      lines << QString("code biases compared with the other ACs (DCBs to %1): %2").arg(ll.join("/")).arg(items.join("; "));
+      lines << QString("code biases compared with the other solutions (DCBs to %1): %2").arg(ll.join("/")).arg(items.join("; "));
       if (numACs >= 2 && oppACs.size() > numACs / 2) {
-        lines << QString("code biases with the opposite sign of %1 of %2 other ACs (%3) - "
+        lines << QString("code biases with the opposite sign of %1 of %2 solutions of other ACs (%3) - "
                          "SINEX sign convention instead of the SSR one?")
                  .arg(oppACs.size()).arg(numACs).arg(oppACs.join(" "));
       }
-      deviating(compare(1, QString()), QString("the median of the other ACs (at least %1 ACs)").arg(MIN_VOTE));
+      deviating(compare(1, QString()), QString("the median of the other ACs (at least %1 solutions)").arg(MIN_VOTE));
     }
   }
@@ -828,5 +853,5 @@
         if (!votes(oo) || !crd[oo].contains(prn) || !crd[oo][prn].clkOK) continue;
         if (useOff && !off.contains(oo)) continue;
-        perAC[acOf(oo)] << crd[oo][prn].clk - (useOff ? off[oo] : 0.0);
+        perAC[solOf(oo)] << crd[oo][prn].clk - (useOff ? off[oo] : 0.0);
       }
       QMap<QString, double> res;
@@ -879,5 +904,5 @@
           while (itA.hasNext()) {
             itA.next();
-            if (itA.key() != ownAC) clks << itA.value();
+            if (acOfSol(itA.key()) != ownAC) clks << itA.value();
           }
           // orbits: per AC the median of its streams with the same reference point
@@ -887,5 +912,5 @@
             const t_satCrd& rc = crd[oo][prn];
             if (rc.refPoint != sc.refPoint || sc.refPoint == '?') continue;
-            for (int ii = 0; ii < 3; ii++) perAC[ii][acOf(oo)] << rc.xx[ii];
+            for (int ii = 0; ii < 3; ii++) perAC[ii][solOf(oo)] << rc.xx[ii];
           }
           for (const QString& ac : perAC[0].keys()) {
Index: /trunk/BNC/src/ssrQc.h
===================================================================
--- /trunk/BNC/src/ssrQc.h	(revision 11090)
+++ /trunk/BNC/src/ssrQc.h	(revision 11091)
@@ -86,4 +86,6 @@
   bool           votes(const QString& staID) const;
   static QString acOf(const QString& staID);
+  static QString solOf(const QString& staID);   // AC solution, e.g. GFZ00, GFZ01
+  static QString acOfSol(const QString& sol);
 
   // Stream comparison: every CMP_SAMPL seconds, the orbits and clocks of each
