Create a mapping of equivalent residue positions to consensus.

    Build a dict-of-dicts::

        {consensus-posn: {id: equiv-posn, id: equiv-posn, ...}, ...}

    The first sequence in the alignment is assumed to be the (gapless) consensus
    sequence.

def get_equivalent_positions(block):
    """Create a mapping of equivalent residue positions to consensus.

    Build a dict-of-dicts::

        {consensus-posn: {id: equiv-posn, id: equiv-posn, ...}, ...}

    The first sequence in the alignment is assumed to be the (gapless) consensus
    sequence.
    """
    consensus = block['sequences'][0]['seq']
    rest = block['sequences'][1:]

    # Validation
    if '-' in consensus or '.' in consensus:
        raise ValueError("First sequence (consensus?) contains gaps")
    # Check for duplicate sequence IDs
    seen = set()
    dupes = set()
    for rec in rest:
        if rec['id'] in seen:
            dupes.add(rec['id'])
        else:
            seen.add(rec['id'])
    if dupes:
        raise ValueError("Duplicate sequences:\n" + '\n'.join(dupes))

    curr_shift = {}
    curr_resn = {}
    # NB: consensus doesn't have head/tail, but other sequences may
    for rec in rest:
        # Count inserts seen so far -- shift string indexes by this far ahead to
        # get the "equivalent" location in the sequence string
        #   - as in, how far ahead in the current seq do we need to jump to get
        #     to a position equivalent to what's in the consensus?
        #   - can this ever be less than 0 (==consensus)?  No, because that's
        #     where gaps come from. Good.
        curr_shift[rec['id']] = 0
        # Residue number in the actual sequence at the current (shifted)
        # location
        #   curr_posn[id] = current equivalent res.num in `id` to cons[i]
        curr_resn[rec['id']] = rec['head_len']

    equivalencies = dict((i+1, {}) for i in xrange(len(consensus)))
    # Map each character position i in the consensus sequence
    # to equivalent residues in each of the other sequences
    #   i = index in the consensus string (== consensus res.num - 1)
    for i, char in enumerate(consensus):
        assert char.isupper()
        for rec in rest:
            rid = rec['id']
            strposn = i + curr_shift[rid]
            if rec['seq'][strposn].isupper():
                # Match
                curr_resn[rid] += 1
            elif rec['seq'][strposn].islower():
                # Insert
                while rec['seq'][strposn].islower():
                    # Count the whole insert size
                    curr_shift[rid] += 1
                    curr_resn[rid] += 1
                    strposn += 1
                curr_resn[rid] += 1 # Count the next match, too
            else:
                # Deletion / gap
                assert rec['seq'][strposn] in '.-'
                continue
            equivalencies[i+1][rid] = curr_resn[rid]

    return equivalencies