text: import click
  import logging
  
  from followthemoney import model
  from followthemoney.dedupe import Linker, Match
  from followthemoney.cli.cli import cli
  from followthemoney.cli.util import read_entities, write_object
  
  log = logging.getLogger(__name__)
  
  
  @cli.command("link", help="Apply matches from a deduplication match file")  # noqa
  @click.option("-i", "--infile", type=click.File("r"), default="-")  # noqa
  @click.option("-o", "--outfile", type=click.File("w"), default="-")  # noqa
  @click.option("-m", "--matches", type=click.File("r"), required=True)  # noqa
  def link(infile, outfile, matches):
      try:
          linker = Linker(model)
          for match in Match.from_file(model, matches):
              linker.add(match)
          log.info("Linker: %s clusters.", len(linker.lookup))
          for entity in read_entities(infile):
              entity = linker.apply(entity)
              write_object(outfile, entity)
      except BrokenPipeError:
          raise click.Abort()
  
  
  @cli.command(
      "match-decide", help="Generate match decisions based purely on score"
  )  # noqa
  @click.option("-i", "--infile", type=click.File("r"), default="-")  # noqa
  @click.option("-o", "--outfile", type=click.File("w"), default="-")  # noqa
  @click.option("-t", "--threshold", type=float, default=0.8)
  def match_decide(infile, outfile, threshold):
      try:
          for match in Match.from_file(model, infile):
              if match.decision is None:
                  if match.score is not None and match.score > threshold:
                      match.decision = True
              write_object(outfile, match)
      except BrokenPipeError:
          raise click.Abort()
  
  
  @cli.command("match-entities", help="Unnests matches into entities")
  @click.option("-i", "--infile", type=click.File("r"), default="-")  # noqa
  @click.option("-o", "--outfile", type=click.File("w"), default="-")  # noqa
  @click.option(
      "-a", "--all", is_flag=True, default=False, help="Unnest non-positive matches"
  )  # noqa
  def match_entities(infile, outfile, all):
      try:
          for match in Match.from_file(model, infile):
              if not all and match.decision is not True:
                  continue
              if match.canonical is not None:
                  write_object(outfile, match.canonical)
              if match.entity is not None:
                  write_object(outfile, match.entity)
      except BrokenPipeError:
          raise click.Abort()
  