@Override
  public SensorModelResult likelihood(SensorModelSupportLibrary library, Context context) {

    VehicleState parentState = context.getParentState();
    VehicleState state = context.getState();
    Observation obs = context.getObservation();

    JourneyState js = state.getJourneyState();
    EVehiclePhase phase = js.getPhase();
    BlockState blockState = state.getBlockState();

    /** These probabilities only apply if are IN_PROGRESS and have a block state */
    if (phase != EVehiclePhase.IN_PROGRESS || blockState == null)
      return new SensorModelResult("pInProgress (n/a)");

    SensorModelResult result = new SensorModelResult("pInProgress");

    /** Rule: IN_PROGRESS => block location is close to gps location */
    double pBlockLocation = library.computeBlockLocationProbability(parentState, blockState, obs);
    result.addResultAsAnd("pBlockLocation", pBlockLocation);

    return result;
  }
  public DSC_STATE getDscState(final Context context) {
    final VehicleState state = context.getState();
    final Observation obs = context.getObservation();

    final JourneyState js = state.getJourneyState();
    EVehiclePhase phase = js.getPhase();

    final String observedDsc = obs.getLastValidDestinationSignCode();

    if (observedDsc == null || !obs.hasValidDsc() || obs.hasOutOfServiceDsc()) {
      /** If we haven't yet seen a valid DSC, or it's out of service */
      if (!obs.hasValidDsc()) {
        return DSC_STATE.DSC_NOT_VALID;
      } else if (EVehiclePhase.IN_PROGRESS == phase && obs.hasOutOfServiceDsc()) {
        return DSC_STATE.DSC_OOS_IP;
      } else {
        return DSC_STATE.DSC_OOS_NOT_IP;
      }
    } else {
      final BlockState bs = state.getBlockState();
      if (bs == null) {
        return DSC_STATE.DSC_IS_NO_BLOCK;
      } else {
        final Set<String> dscs = Sets.newHashSet();
        dscs.add(bs.getDestinationSignCode());
        final Set<AgencyAndId> routes = Sets.newHashSet();
        routes.add(bs.getBlockLocation().getActiveTrip().getTrip().getRouteCollection().getId());

        /*
         * dsc changes occur between parts of a block, so account for that
         */
        if (EVehiclePhase.LAYOVER_DURING == phase || EVehiclePhase.DEADHEAD_DURING == phase) {
          final BlockTripEntry nextTrip = bs.getBlockLocation().getActiveTrip().getNextTrip();

          if (nextTrip != null) {
            final String dsc =
                _destinationSignCodeService.getDestinationSignCodeForTripId(
                    nextTrip.getTrip().getId());
            if (dsc != null) dscs.add(dsc);
            routes.add(nextTrip.getTrip().getRouteCollection().getId());
          }
        }

        /*
         * Check if it's active, since deadhead-after/before's
         * can/should have o.o.s. dsc's, although they often don't.
         * TODO perhaps check the last non-o.o.s. dsc to give
         * higher weight to deadhead-after's that match (when
         * we have good run-info, perhaps).
         */
        if (!EVehiclePhase.isActiveAfterBlock(phase)
            && !EVehiclePhase.isActiveBeforeBlock(phase)
            && dscs.contains(observedDsc)) {
          if (EVehiclePhase.IN_PROGRESS == phase) return DSC_STATE.DSC_MATCH;
          else return DSC_STATE.DSC_DEADHEAD_MATCH;
        } else {
          /*
           * a dsc implies a route. even though the reported dsc may not match,
           * we expect the general route to be the same...
           */
          boolean routeMatch = false;
          for (final AgencyAndId dscRoute : obs.getDscImpliedRouteCollections()) {
            if (routes.contains(dscRoute)) {
              routeMatch = true;
              break;
            }
          }

          if (routeMatch) return DSC_STATE.DSC_ROUTE_MATCH;
          else return DSC_STATE.DSC_NO_ROUTE_MATCH;
        }
      }
    }
  }