Index: trunk/BNC/src/ssrQc.cpp
===================================================================
--- trunk/BNC/src/ssrQc.cpp	(revision 11087)
+++ 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()) {
