product 10 min read

Czech Litigation Monitoring: Courts, Insolvency, and Enforcement

Automated monitoring of Czech court proceedings, ISIR insolvency registry, enforcement tracking, and litigation outcome prediction

Tomas Korcak (korczis) 446 words

Litigation history and active court proceedings are essential signals in due diligence. A company involved in significant litigation faces potential financial exposure, operational disruption, and reputational risk. In the Czech Republic, court data is distributed across multiple systems, each with its own access patterns and data structures. Prismatic integrates these systems into a unified litigation monitoring capability.

Czech Court System Data Sources

Three primary systems provide litigation data for Czech entities:

Justice.cz hosts the public court portal with access to proceedings across district, regional, and high courts. Data includes case numbers, parties, case type, filing dates, and published decisions. Access is public but requires structured querying.

ISIR (Insolvencni rejstrik) is the dedicated insolvency register, providing real-time data on insolvency petitions, proceedings, court decisions, creditor claims, and resolution plans. ISIR offers a SOAP API for automated access, making it the most machine-friendly Czech court data source.

CEDR and the Execution Registry track enforcement proceedings (exekuce), providing visibility into whether a company has unresolved debts being actively collected through court-ordered enforcement.

ISIR Integration

The ISIR adapter is Prismatic's most mature Czech court integration, leveraging the registry's structured SOAP API:

defmodule Prismatic.DD.Sources.ISIR do
  @moduledoc """
  Czech Insolvency Registry (ISIR) adapter.
  Queries insolvency proceedings by ICO or name.
  """

  @behaviour Prismatic.DD.Source

  @isir_endpoint "https://isir.justice.cz/isir/ws/IsirWsService"

  @impl true
  def fetch(%{ico: ico}, opts) when is_binary(ico) do
    timeout = Keyword.get(opts, :timeout, 15_000)

    soap_body = build_query_by_ico(ico)

    case send_soap_request(soap_body, timeout) do
      {:ok, response} ->
        proceedings = parse_insolvency_proceedings(response)

        {:ok, %{
          data: %{
            proceedings: proceedings,
            active_count: Enum.count(proceedings, & &1.is_active),
            total_count: length(proceedings),
            queried_at: DateTime.utc_now()
          },
          confidence: 0.95,
          source: :isir
        }}

      {:error, reason} ->
        {:error, {:isir_query_failed, reason}}
    end
  end

  @impl true
  def source_group, do: :court

  @impl true
  def rate_limit, do: {5, 60_000}

  @impl true
  def priority, do: 1

  defp parse_insolvency_proceedings(response) do
    response
    |> SweetXml.xpath(~x"//insolvencniRizeni"l)
    |> Enum.map(fn node ->
      %{
        case_number: SweetXml.xpath(node, ~x"./spisovaZnacka/text()"s),
        court: SweetXml.xpath(node, ~x"./soud/text()"s),
        filing_date: parse_date(SweetXml.xpath(node, ~x"./datumZalozeni/text()"s)),
        status: map_status(SweetXml.xpath(node, ~x"./stav/text()"s)),
        is_active: SweetXml.xpath(node, ~x"./stav/text()"s) not in ["KONC", "ZRUS"],
        debtor_role: SweetXml.xpath(node, ~x"./roleSubjektu/text()"s),
        proceedings_type: map_proceedings_type(
          SweetXml.xpath(node, ~x"./druhRizeni/text()"s)
        )
      }
    end)
  end

  defp map_status("NEVYR"), do: :pending
  defp map_status("MORAT"), do: :moratorium
  defp map_status("UPAD"), do: :bankruptcy_declared
  defp map_status("ODDL"), do: :debt_relief
  defp map_status("REOR"), do: :reorganization
  defp map_status("KONK"), do: :liquidation
  defp map_status("KONC"), do: :concluded
  defp map_status("ZRUS"), do: :cancelled
  defp map_status(other), do: {:unknown, other}
end

Litigation Event Processing

Raw court data requires processing to be useful for risk assessment. Prismatic normalizes litigation events into a standard structure and computes aggregate metrics:

defmodule Prismatic.DD.Litigation.Processor do
  @moduledoc """
  Litigation event normalization and aggregate risk computation.
  """

  @type litigation_summary :: %{
    total_cases: non_neg_integer(),
    active_cases: non_neg_integer(),
    as_plaintiff: non_neg_integer(),
    as_defendant: non_neg_integer(),
    total_exposure_czk: Decimal.t() | nil,
    insolvency_history: boolean(),
    active_enforcement: boolean(),
    severity_distribution: %{atom() => non_neg_integer()}
  }

  @spec summarize(entity_id :: binary(), [map()]) :: litigation_summary()
  def summarize(entity_id, raw_events) do
    events = Enum.map(raw_events, &normalize_event/1)

    %{
      total_cases: length(events),
      active_cases: Enum.count(events, & &1.is_active),
      as_plaintiff: Enum.count(events, &(&1.entity_role == :plaintiff)),
      as_defendant: Enum.count(events, &(&1.entity_role == :defendant)),
      total_exposure_czk: compute_total_exposure(events),
      insolvency_history: Enum.any?(events, &(&1.case_type == :insolvency)),
      active_enforcement: Enum.any?(events, fn e ->
        e.case_type == :enforcement and e.is_active
      end),
      severity_distribution: compute_severity_distribution(events),
      timeline: build_timeline(events)
    }
  end

  defp compute_total_exposure(events) do
    events
    |> Enum.filter(fn e -> e.is_active and e.entity_role == :defendant end)
    |> Enum.map(& &1.claimed_amount)
    |> Enum.reject(&is_nil/1)
    |> case do
      [] -> nil
      amounts -> Enum.reduce(amounts, Decimal.new(0), &Decimal.add/2)
    end
  end

  defp compute_severity_distribution(events) do
    events
    |> Enum.map(&classify_severity/1)
    |> Enum.frequencies()
  end

  defp classify_severity(%{case_type: :insolvency, is_active: true}), do: :critical
  defp classify_severity(%{case_type: :enforcement, is_active: true}), do: :high
  defp classify_severity(%{claimed_amount: amt}) when not is_nil(amt) do
    if Decimal.compare(amt, Decimal.new("10_000_000")) == :gt, do: :high, else: :medium
  end
  defp classify_severity(_event), do: :low
end

Continuous Monitoring

For entities under ongoing surveillance, Prismatic implements a polling-based monitoring system that checks for new court events at configurable intervals:

defmodule Prismatic.DD.Litigation.Monitor do
  @moduledoc """
  Continuous litigation monitoring with change detection.
  """

  use GenServer
  require Logger

  @default_interval :timer.hours(6)

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @impl true
  def init(opts) do
    interval = Keyword.get(opts, :interval, @default_interval)
    schedule_check(interval)
    {:ok, %{interval: interval, watched_entities: MapSet.new()}}
  end

  @impl true
  def handle_info(:check_updates, state) do
    state.watched_entities
    |> Enum.each(fn entity_id ->
      check_entity_updates(entity_id)
    end)

    schedule_check(state.interval)
    {:noreply, state}
  end

  defp check_entity_updates(entity_id) do
    entity = Prismatic.DD.Entities.get!(entity_id)
    last_check = entity.litigation_last_checked

    case fetch_new_events(entity, last_check) do
      {:ok, []} ->
        :ok

      {:ok, new_events} ->
        Logger.info("Found #{length(new_events)} new litigation events for #{entity_id}")

        Phoenix.PubSub.broadcast(
          Prismatic.PubSub,
          "dd:litigation:#{entity_id}",
          {:new_litigation_events, entity_id, new_events}
        )

        Prismatic.DD.Entities.update_litigation_check(entity_id, DateTime.utc_now())

      {:error, reason} ->
        Logger.warning("Litigation check failed for #{entity_id}: #{inspect(reason)}")
    end
  end

  defp schedule_check(interval) do
    Process.send_after(self(), :check_updates, interval)
  end
end

Outcome Analysis

Historical case outcomes provide context for assessing active cases. Prismatic tracks case dispositions and computes aggregate statistics per case type and court. For insolvency proceedings, the resolution rate, average duration, and creditor recovery percentages are all tracked. This data informs the risk scoring engine, allowing it to weight active cases not just by claimed amount but by the probability of an adverse outcome given historical patterns.

The litigation dimension contributes to the overall entity risk score with a default weight of 10%. However, for entities with active insolvency proceedings or enforcement actions, the scoring engine can escalate the litigation dimension dynamically, reflecting the outsized importance of these findings.

Integration Notes

All court data in the Czech Republic is public information, and automated access is permissible for legitimate purposes. However, rate limits must be respected. ISIR's API is reasonably reliable but has occasional downtime. Justice.cz is less structured and requires more robust error handling. The enforcement registry has the most limited programmatic access.

Prismatic handles source unavailability gracefully: if a court data source is down during a DD pipeline run, the investigation proceeds with available data and flags the missing source in the results. The analyst can trigger a targeted re-check once the source recovers.