コード例 #1
0
ファイル: main.py プロジェクト: devolt5/ntg
def manuscript_full_json(passage_or_id, hs_hsnr_id):
    """Endpoint.  Serve information about a manuscript.

    :param string hs_hsnr_id: The hs, hsnr or id of the manuscript.

    """

    auth()

    hs_hsnr_id = request.args.get('ms_id') or hs_hsnr_id

    with current_app.config.dba.engine.begin() as conn:
        passage = Passage(conn, passage_or_id)
        ms = Manuscript(conn, hs_hsnr_id)
        rg_id = passage.request_rg_id(request)

        if ms.ms_id is None:
            return cache(
                make_json_response(None, 400,
                                   'Bad request: No such manuscript.'))

        json = ms.to_json()
        json['length'] = ms.get_length(rg_id)

        # Get the attestation(s) of the manuscript (may be uncertain eg. a/b/c)
        res = execute(
            conn, """
        SELECT labez, clique, labez_clique, certainty
        FROM apparatus_view_agg
        WHERE ms_id = :ms_id AND pass_id = :pass_id
        """, dict(parameters, ms_id=ms.ms_id, pass_id=passage.pass_id))

        row = res.fetchone()
        if row is not None:
            json['labez'], json['clique'], json['labez_clique'], json[
                'certainty'] = row

        # Get the affinity of the manuscript to all manuscripts
        res = execute(
            conn, """
        SELECT avg (a.affinity) as aa,
        percentile_cont(0.5) WITHIN GROUP (ORDER BY a.affinity) as ma
        FROM affinity a
        WHERE a.ms_id1 = :ms_id1 AND a.rg_id = :rg_id
        """, dict(parameters, ms_id1=ms.ms_id, rg_id=rg_id))

        json['aa'], json['ma'] = 0.0, 0.0
        row = res.fetchone()
        if row is not None:
            json['aa'], json['ma'] = row

        # Get the affinity of the manuscript to MT
        #
        # For a description of mt and mtp see the comment in
        # ActsMsListValPh3.pl and
        # http://intf.uni-muenster.de/cbgm/actsPh3/guide_en.html#Ancestors

        res = execute(
            conn, """
        SELECT a.affinity as mt, a.equal::float / c.length as mtp
        FROM affinity a
        JOIN ms_ranges c
          ON (a.ms_id1, a.rg_id) = (c.ms_id, c.rg_id)
        WHERE a.ms_id1 = :ms_id1 AND a.ms_id2 = 2 AND a.rg_id = :rg_id
        """, dict(parameters, ms_id1=ms.ms_id, rg_id=rg_id))

        json['mt'], json['mtp'] = 0.0, 0.0
        row = res.fetchone()
        if row is not None:
            json['mt'], json['mtp'] = row

        return cache(make_json_response(json))
コード例 #2
0
ファイル: main.py プロジェクト: devolt5/ntg
def relatives_csv(passage_or_id, hs_hsnr_id):
    """Output a table of the nearest relatives of a manuscript.

    Output a table of the nearest relatives/ancestors/descendants of a
    manuscript and what they attest.

    """

    auth()

    type_ = request.args.get('type') or 'rel'
    limit = int(request.args.get('limit') or 0)
    labez = request.args.get('labez') or 'all'
    mode = request.args.get('mode') or 'sim'
    include = request.args.getlist('include[]') or []
    fragments = request.args.getlist('fragments[]') or []

    view = 'affinity_view' if mode == 'rec' else 'affinity_p_view'

    where = ''
    if type_ == 'anc':
        where = ' AND older < newer'
    if type_ == 'des':
        where = ' AND older >= newer'

    if labez == 'all':
        where += " AND labez !~ '^z'"
    elif labez == 'all+lac':
        pass
    else:
        where += " AND labez = '%s'" % labez

    if 'fragments' in fragments:
        frag_where = ''
    else:
        frag_where = 'AND aff.common > aff.ms1_length / 2'

    limit = '' if limit == 0 else ' LIMIT %d' % limit

    with current_app.config.dba.engine.begin() as conn:

        passage = Passage(conn, passage_or_id)
        ms = Manuscript(conn, hs_hsnr_id)
        rg_id = passage.request_rg_id(request)

        exclude = get_excluded_ms_ids(conn, include)

        # Get the X most similar manuscripts and their attestations
        res = execute(
            conn, """
        /* get the LIMIT closest ancestors for this node */
        WITH ranks AS (
          SELECT ms_id1, ms_id2,
            rank () OVER (ORDER BY affinity DESC, common, older, newer DESC, ms_id2) AS rank,
            affinity
          FROM {view} aff
          WHERE ms_id1 = :ms_id1 AND aff.rg_id = :rg_id AND ms_id2 NOT IN :exclude
            AND newer > older {frag_where}
          ORDER BY affinity DESC
        )

        SELECT r.rank,
               aff.ms_id2 as ms_id,
               ms.hs,
               ms.hsnr,
               aff.ms2_length,
               aff.common,
               aff.equal,
               aff.older,
               aff.newer,
               aff.unclear,
               aff.common - aff.equal - aff.older - aff.newer - aff.unclear as norel,
               CASE WHEN aff.newer < aff.older THEN ''
                    WHEN aff.newer = aff.older THEN '-'
                    ELSE '>'
               END as direction,
               aff.affinity,
               a.labez,
               a.certainty
        FROM
          {view} aff
        JOIN apparatus_view_agg a
          ON aff.ms_id2 = a.ms_id
        JOIN manuscripts ms
          ON aff.ms_id2 = ms.ms_id
        LEFT JOIN ranks r
          ON r.ms_id2 = aff.ms_id2
        WHERE aff.ms_id2 NOT IN :exclude AND aff.ms_id1 = :ms_id1
              AND aff.rg_id = :rg_id AND aff.common > 0
              AND a.pass_id = :pass_id {where} {frag_where}
        ORDER BY affinity DESC, r.rank, newer DESC, older DESC, hsnr
        {limit}
        """,
            dict(parameters,
                 where=where,
                 frag_where=frag_where,
                 ms_id1=ms.ms_id,
                 hsnr=ms.hsnr,
                 pass_id=passage.pass_id,
                 rg_id=rg_id,
                 limit=limit,
                 view=view,
                 exclude=exclude))

        Relatives = collections.namedtuple(
            'Relatives',
            'rank ms_id hs hsnr length common equal older newer unclear norel direction affinity labez certainty'
        )
        return csvify(Relatives._fields, list(map(Relatives._make, res)))
コード例 #3
0
ファイル: textflow.py プロジェクト: devolt5/ntg
def textflow(passage_or_id):
    """ Output a stemma of manuscripts. """

    labez = request.args.get('labez') or ''
    hyp_a = request.args.get('hyp_a') or 'A'
    connectivity = int(request.args.get('connectivity') or 10)
    width = float(request.args.get('width') or 0.0)
    fontsize = float(request.args.get('fontsize') or 10.0)
    mode = request.args.get('mode') or 'sim'

    include = request.args.getlist('include[]') or []
    fragments = request.args.getlist('fragments[]') or []
    checks = request.args.getlist('checks[]') or []
    var_only = request.args.getlist('var_only[]') or []
    cliques = request.args.getlist('cliques[]') or []

    fragments = 'fragments' in fragments
    checks = 'checks' in checks
    var_only = 'var_only' in var_only  # Panel: Coherence at Variant Passages (GraphViz)
    cliques = 'cliques' in cliques  # consider or ignore cliques
    leaf_z = 'Z' in include  # show leaf z nodes in global textflow?

    view = 'affinity_view' if mode == 'rec' else 'affinity_p_view'

    global_textflow = not ((labez != '') or var_only)
    rank_z = False  # include z nodes in ranking?

    if global_textflow:
        connectivity = 1
        rank_z = True
    if connectivity == 21:
        connectivity = 9999

    labez_where = ''
    frag_where = ''
    z_where = ''

    if labez != '':
        labez_where = 'AND app.cbgm AND app.labez = :labez'
        if hyp_a != 'A':
            labez_where = 'AND app.cbgm AND (app.labez = :labez OR (app.ms_id = 1 AND :hyp_a = :labez))'

    if not fragments:
        frag_where = 'AND a.common > a.ms1_length / 2'

    if not rank_z:
        z_where = "AND app.labez !~ '^z' AND app.certainty = 1.0"

    group_field = 'labez_clique' if cliques else 'labez'

    with current_app.config.dba.engine.begin() as conn:
        passage = Passage(conn, passage_or_id)
        rg_id = passage.request_rg_id(request)

        exclude = get_excluded_ms_ids(conn, include)

        # nodes query
        #
        # get all nodes or all nodes (hypothetically) attesting labez

        res = execute(
            conn, """
        SELECT ms_id
        FROM apparatus app
        WHERE pass_id = :pass_id AND ms_id NOT IN :exclude {labez_where} {z_where}
        """,
            dict(parameters,
                 exclude=exclude,
                 pass_id=passage.pass_id,
                 labez=labez,
                 hyp_a=hyp_a,
                 labez_where=labez_where,
                 z_where=z_where))

        nodes = {row[0] for row in res}
        if not nodes:
            nodes = {-1}  # avoid SQL syntax error

        # rank query
        #
        # query to get the closest ancestors for every node with rank <= connectivity

        query = """
        SELECT ms_id1, ms_id2, rank
        FROM (
          SELECT ms_id1, ms_id2, rank () OVER (PARTITION BY ms_id1
             ORDER BY affinity DESC, common, older, newer DESC, ms_id2) AS rank
          FROM {view} a
          WHERE ms_id1 IN :nodes AND a.rg_id = :rg_id AND ms_id2 NOT IN :exclude
            AND newer > older {frag_where}
        ) AS r
        WHERE rank <= :connectivity
        ORDER BY rank
        """

        res = execute(
            conn, query,
            dict(parameters,
                 nodes=tuple(nodes),
                 exclude=exclude,
                 rg_id=rg_id,
                 pass_id=passage.pass_id,
                 view=view,
                 labez=labez,
                 connectivity=connectivity,
                 frag_where=frag_where,
                 hyp_a=hyp_a))

        Ranks = collections.namedtuple('Ranks', 'ms_id1 ms_id2 rank')
        ranks = list(map(Ranks._make, res))

        # Initially build an unconnected graph with one node for each
        # manuscript.  We will connect the nodes later.  Finally we will remove
        # unconnected nodes.

        graph = nx.DiGraph()

        dest_nodes = {r.ms_id1 for r in ranks}
        src_nodes = {r.ms_id2 for r in ranks}

        res = execute(
            conn, """
        SELECT ms.ms_id, ms.hs, ms.hsnr, a.labez, a.clique, a.labez_clique, a.certainty
        FROM apparatus_view_agg a
        JOIN manuscripts ms USING (ms_id)
        WHERE pass_id = :pass_id AND ms_id IN :ms_ids
        """,
            dict(parameters,
                 ms_ids=tuple(src_nodes | dest_nodes | nodes),
                 pass_id=passage.pass_id))

        Mss = collections.namedtuple(
            'Mss', 'ms_id hs hsnr labez clique labez_clique certainty')
        mss = list(map(Mss._make, res))

        for ms in mss:
            attrs = {}
            attrs['hs'] = ms.hs
            attrs['hsnr'] = ms.hsnr
            attrs[
                'labez'] = ms.labez if ms.certainty == 1.0 else 'zw ' + ms.labez
            attrs['clique'] = ms.clique
            attrs[
                'labez_clique'] = ms.labez_clique if ms.certainty == 1.0 else 'zw ' + ms.labez_clique
            attrs['ms_id'] = ms.ms_id
            attrs['label'] = ms.hs
            attrs['certainty'] = ms.certainty
            attrs['clickable'] = '1'
            if ms.ms_id == 1 and hyp_a != 'A':
                attrs['labez'] = hyp_a[0]
                attrs['clique'] = ''
                attrs['labez_clique'] = hyp_a[0]
            # FIXME: attrs['shape'] = SHAPES.get (attrs['labez'], SHAPES['a'])
            graph.add_node(ms.ms_id, **attrs)

        # Connect the nodes
        #
        # Step 1: If the node has internal parents, keep only the top-ranked
        # internal parent.
        #
        # Step 2: If the node has no internal parents, keep the top-ranked
        # parents for each external attestation.
        #
        # Assumption: ranks are sorted top-ranked first

        def is_z_node(n):
            labez = n['labez']
            cert = n['certainty']
            return (labez[0] == 'z') or (cert < 1.0)

        tags = set()
        for step in (1, 2):
            for r in ranks:
                a1 = graph.nodes[r.ms_id1]
                if not r.ms_id2 in graph.nodes:
                    continue
                a2 = graph.nodes[r.ms_id2]
                if not (global_textflow) and is_z_node(a2):
                    # disregard zz / zw
                    continue
                if step == 1 and a1[group_field] != a2[group_field]:
                    # differing attestations are handled in step 2
                    continue
                if r.ms_id1 in tags:
                    # an ancestor of this node that lays within the node's
                    # attestation was already seen.  we need not look into other
                    # attestations
                    continue
                if str(r.ms_id1) + a2[group_field] in tags:
                    # an ancestor of this node that lays within this attestation
                    # was already seen.  we need not look into further nodes
                    continue
                # add a new parent
                if r.rank > 1:
                    graph.add_edge(r.ms_id2,
                                   r.ms_id1,
                                   rank=r.rank,
                                   headlabel=r.rank)
                else:
                    graph.add_edge(r.ms_id2, r.ms_id1)

                if a1[group_field] == a2[group_field]:
                    # tag: has ancestor node within the same attestation
                    tags.add(r.ms_id1)
                else:
                    # tag: has ancestor node with this other attestation
                    tags.add(str(r.ms_id1) + a2[group_field])

        if not leaf_z:
            remove_z_leaves(graph)

        # the if clause fixes #83
        graph.remove_nodes_from([
            n for n in nx.isolates(graph) if graph.nodes[n]['labez'] != labez
        ])

        if var_only:
            # Panel: Coherence at Variant Passages (GraphViz)
            #
            # if one predecessor is within the same attestation then remove all
            # other predecessors that are not within the same attestation
            for n in graph:
                within = False
                attestation_n = graph.nodes[n][group_field]
                for p in graph.predecessors(n):
                    if graph.nodes[p][group_field] == attestation_n:
                        within = True
                        break
                if within:
                    for p in graph.predecessors(n):
                        if graph.nodes[p][group_field] != attestation_n:
                            graph.remove_edge(p, n)

            # remove edges between nodes within the same attestation
            for u, v in list(graph.edges()):
                if graph.nodes[u][group_field] == graph.nodes[v][group_field]:
                    graph.remove_edge(u, v)

            # remove now isolated nodes
            graph.remove_nodes_from(list(nx.isolates(graph)))

            # unconstrain backward edges (yields a better GraphViz layout)
            for u, v in graph.edges():
                if graph.nodes[u][group_field] > graph.nodes[v][group_field]:
                    graph.adj[u][v]['constraint'] = 'false'

        else:
            for n in graph:
                # Use a different label if the parent's labez_clique differs from this
                # node's labez_clique.
                pred = list(graph.predecessors(n))
                attrs = graph.nodes[n]
                if not pred:
                    attrs['label'] = "%s: %s" % (attrs['labez_clique'],
                                                 attrs['hs'])
                for p in pred:
                    if attrs['labez_clique'] != graph.nodes[p]['labez_clique']:
                        attrs['label'] = "%s: %s" % (attrs['labez_clique'],
                                                     attrs['hs'])
                        graph.adj[p][n]['style'] = 'dashed'

        if checks:
            for rank in congruence(conn, passage):
                try:
                    graph.adj[rank.ms_id1][rank.ms_id2]['style'] = 'bold'
                except KeyError:
                    pass

    if var_only:
        dot = helpers.nx_to_dot_subgraphs(graph, group_field, width, fontsize)
    else:
        dot = helpers.nx_to_dot(graph, width, fontsize)
    return dot