0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonで関係グラフの参照整合性を検査する: 7つの合成テストと人によるレビュー

0
Posted at

A relationship map is a small graph, but a polished drawing can hide ordinary data mistakes. A connection can point to an absent person, the same identifier can occur twice, or an imported edge can carry no source note. These are useful problems for a small Python inspection function because they can be described precisely without attempting to decide whether a family story is true.

This tutorial implements a deliberately narrow, metadata-only review pass. It uses synthetic identifiers and fictional source labels, with no names, health details, dates, or real family records. The code below was executed locally against the seven included unittest fixtures. That test result concerns this example only: it is not a security audit, a clinical validation, or a test of any hosted product's internal storage or AI behavior.

The article is prepared on behalf of the Genogram Maker project with AI assistance. The implementation is an independent teaching example, not an official API client, an export converter, or a claim that the project's application uses this schema. A diagram and this checker cannot diagnose conditions, infer biological relationships, or establish the reliability of a source.

Original fictional relationship illustration

Original illustration with fictional people, not a product screenshot or a clinical record.

Keep the intermediate format deliberately small

The input consists of a list of person identifiers and a list of edge dictionaries. An edge has a source identifier, a destination identifier, a relationship kind, and an evidence label. The evidence label is a reference to a separate note, not the note itself. This separation keeps the example easy to inspect and avoids requiring sensitive narrative material in every test fixture.

The direction of an edge remains explicit. A parent edge from one person to another differs from the reverse direction. The function does not automatically reverse a partner edge or infer the gender, legal status, or household role of either endpoint. Those choices belong to the surrounding application's documented data model. If your application treats some relationships as undirected, normalize them deliberately before inspection and test that policy separately.

For this example, the supported kinds are parent, partner, caregiver, and unspecified. This vocabulary is intentionally incomplete. It is a small teaching contract, not a complete genogram notation standard. An unsupported kind produces a warning rather than being silently changed into a familiar value. That behavior preserves the distinction between recognizing a label and deciding what a relationship should mean.

Return warnings instead of mutating records

Each warning contains an entity type, its zero-based position in the corresponding input list, and a machine-readable code. Positions are local to this inspection result, not permanent record identifiers. Returning a list makes it possible to show several related problems in one review session instead of requiring a repeated import-and-fail cycle that reveals only the first issue each time.

The function does not edit the input. Automatically dropping a duplicate edge could hide the fact that two source notes disagree. Automatically replacing a missing person would be even worse because the program would invent an entity to satisfy its own structural expectations. A warning should help a human locate the original record, consult the separate note, and decide which correction is justified.

The output intentionally omits the actual identifiers and evidence text. This reduces accidental disclosure if a developer prints a warning while experimenting. It does not make the input anonymous, guarantee safe logging, or solve access control. Even a synthetic-looking identifier can be identifying when another system maps it back to a person. Treat a production implementation's storage and observability design as separate work.

Distinguish reference integrity from factual accuracy

An unknown endpoint is a structural issue: at least one endpoint does not appear in the supplied people list. A duplicate person identifier is another structural issue because subsequent references cannot unambiguously identify distinct records. Neither warning tells the reviewer which record is historically correct. A misspelled identifier and a genuinely absent person can produce the same structural symptom while requiring different corrections.

Similarly, a nonempty evidence label is not proof that the linked note exists or supports the assertion. The checker verifies that a label is present, not that an archive contains the note or that its author had direct knowledge. A fabricated but well-formed evidence label would pass this particular check. That limitation is central to the example and should remain visible in any user interface built around it.

Self-connections and repeated edges are also reported, but the function avoids stronger family-history rules. It does not search for cycles, infer chronology, distinguish biological and legal parenthood, or decide whether multiple caregivers are plausible. More validation is not automatically better when a rule would encode a social or clinical assumption that the input format never promised to represent.

Explain the implementation's boundaries

The outer arguments are expected to be finite iterables representing the two lists described above. Within those lists, the function checks person identifiers and the minimum edge fields. Blank or nonstring person identifiers generate an invalid identifier warning. An edge that is not a dictionary, or lacks nonblank string endpoint and kind fields, receives an invalid edge warning and is not inspected further.

That early continuation is deliberate. It prevents the remaining checks from treating malformed containers as ordinary identifiers or using unhashable values as keys. Validly shaped edges can receive several warnings, such as an unknown endpoint and missing evidence. Warning order follows input order and the explicit sequence of checks, which makes the expected behavior straightforward to test without sorting away useful context.

For valid identifiers and edges, the sets used for membership and duplicate detection provide expected linear processing time in the number of records, with memory proportional to the unique identifiers and edge keys. This is not a performance benchmark. Extremely large or adversarial inputs need explicit size limits, schema validation, resource controls, and testing beyond the small local fixtures shown here.

Use a visual workspace without confusing the schemas

A browser workspace such as the genogram maker free interface can help arrange editable people, relationships, and notes, with auto layout and optional AI drafting. The current free plan describes two projects, fifty people per project, five monthly AI credits after sign-in, and PNG export; AI creation or updates use one to five credits. Professional access adds PDF, SVG, GEDCOM, and editable JSON downloads. Confirm current plan details before choosing an export-dependent workflow.

The Python example does not consume those exports. Anyone building an actual adapter must first inspect the documented format, map its semantics intentionally, and preserve unsupported fields rather than quietly discarding them. A screenshot also cannot substitute for structured graph input. It is a reading copy, not an authoritative database from which this function can recover identifiers, direction, and evidence references.

Run the included tests and interpret them narrowly

Save the implementation and tests under the filenames shown below, in the same directory, then run the command in the final block. The seven fixtures exercise one valid graph, duplicate people, an absent endpoint, a self-connection, a repeated edge, an unsupported kind, and blank evidence. Every fixture uses invented identifiers and a fictional note label. The actual local run passed all seven tests.

A passing run means those specific assertions match the current implementation. It does not demonstrate complete coverage of malformed inputs, locale handling, concurrency, file parsing, or the behavior of an external application. Before adapting this example, add tests for your schema boundary, null values, mixed container types, multiple simultaneous warnings, intentionally repeated source claims, and any normalization policy that changes identifier spelling or relationship direction.

Keep review separate from publication

Use warning results as a queue for evidence review, not as an automatic permission to share a diagram. A record with no structural warnings may still contain inaccurate, contested, or unnecessarily sensitive information. Conversely, a warning may reflect an incomplete working draft that should remain incomplete until a source can be checked. Filling every gap is not the same as improving the document.

Before distributing a reading copy, review the intended audience, the symbol legend, uncertain assertions, and which notes should remain private. Keep revisions and source references separate from the image when appropriate. If health interpretation is involved, consult qualified professionals and relevant evidence. The modest value of this utility is that it makes a few ordinary consistency problems visible while leaving decisions about meaning, consent, and sharing where they belong.

Implementation: graph_review.py

"""Structural warnings for a small, explicitly synthetic graph."""
def inspect_graph(people, edges):
    issues, known, seen = [], set(), set()
    for index, person_id in enumerate(people):
        if not isinstance(person_id, str) or not person_id.strip():
            issues.append(("person", index, "invalid_id"))
        elif person_id in known:
            issues.append(("person", index, "duplicate_id"))
        else:
            known.add(person_id)
    for index, edge in enumerate(edges):
        if not isinstance(edge, dict) or not all(
            isinstance(edge.get(k), str) and edge[k].strip()
            for k in ("from", "to", "kind")
        ):
            issues.append(("edge", index, "invalid_edge"))
            continue
        source, target, kind = edge["from"], edge["to"], edge["kind"]
        if source not in known or target not in known:
            issues.append(("edge", index, "unknown_endpoint"))
        if source == target:
            issues.append(("edge", index, "self_connection"))
        if kind not in {"parent", "partner", "caregiver", "unspecified"}:
            issues.append(("edge", index, "unsupported_kind"))
        key = (source, target, kind)
        if key in seen:
            issues.append(("edge", index, "duplicate_edge"))
        seen.add(key)
        evidence = edge.get("evidence")
        if not isinstance(evidence, str) or not evidence.strip():
            issues.append(("edge", index, "missing_evidence"))
    return issues

Tests: test_graph_review.py

import unittest
from graph_review import inspect_graph

def edge(**updates):
    value = {"from": "p1", "to": "p2", "kind": "caregiver",
             "evidence": "fictional-note-1"}
    value.update(updates)
    return value

class GraphReviewTests(unittest.TestCase):
    def test_valid_synthetic_graph(self):
        self.assertEqual(inspect_graph(["p1", "p2"], [edge()]), [])

    def test_duplicate_person(self):
        self.assertEqual(inspect_graph(["p1", "p1"], []),
                         [("person", 1, "duplicate_id")])

    def test_unknown_endpoint(self):
        self.assertEqual(inspect_graph(["p1"], [edge()]),
                         [("edge", 0, "unknown_endpoint")])

    def test_self_connection(self):
        self.assertEqual(inspect_graph(["p1"], [edge(to="p1")]),
                         [("edge", 0, "self_connection")])

    def test_duplicate_edge(self):
        self.assertEqual(inspect_graph(["p1", "p2"], [edge(), edge()]),
                         [("edge", 1, "duplicate_edge")])

    def test_unsupported_kind(self):
        self.assertEqual(inspect_graph(["p1", "p2"], [edge(kind="guess")]),
                         [("edge", 0, "unsupported_kind")])

    def test_missing_evidence(self):
        self.assertEqual(inspect_graph(["p1", "p2"], [edge(evidence=" ")]),
                         [("edge", 0, "missing_evidence")])

if __name__ == "__main__":
    unittest.main()

Local execution

python3 -m unittest -v test_graph_review.py

Observed locally: seven tests passed. No production family records, external accounts, or hosted AI requests were used by this test suite.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?