{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Working with MANTO data\n",
    "\n",
    "<div class=\"alert alert-warning\" style=\"margin: 1em 2em\">\n",
    "    <h4>Rate limit on JSON requests to MANTO server</h4>\n",
    "    <p>Note that MANTO's API limits JSON requests to 1000 / 15 minutes from a given IP. This should be <strong>more than sufficient</strong> for use with DICES, since MANTO entities are cached locally after the first download. If, however, you do exceed the limit, then the call to MANTO will return an error and the DICES client will create a dummy MANTO entity with no data.</p>\n",
    "    <p>I've introduced the <code>cache_empty=False</code> default for <code>getMantoID()</code> and <code>getMantoChar()</code>, which specifies that an entity returned with an empty <code>data</code> attribute should not be cached, forcing an automatic re-download the next time it's used. If, however, you expect <code>data</code> to be empty, you can set <code>cache_empty=True</code> to prevent unnecessary calls to the MANTO server.</p>\n",
    "</div>\n",
    "\n",
    "### Preliminaries\n",
    "\n",
    "Import statements. Our MANTO tools are in the `manto` module."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from dicesapi import DicesAPI, SpeechGroup\n",
    "from dicesapi.jupyter import NotebookPBar\n",
    "from dicesapi import manto\n",
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Create a connection to DICES"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "api=DicesAPI(progress_class=NotebookPBar, logfile='dices.log')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Instantiate a DICES character to get started"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "char = api.getCharacters(name='Telemachus')[0]\n",
    "print(char)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Retrieving MANTO entities\n",
    "\n",
    "Let’s create a new MANTO entity by retrieving Telemachus’ ID from MANTO."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "manto_ent = manto.getMantoChar(char)\n",
    "print(manto_ent)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Using MANTO ties to find relatives\n",
    "\n",
    "This code is still in development and subject to change. But for now, this is how we search MANTO for Telemachus' parents. Under the hood, we're checking two different MANTO ties, `31764`, \"son of\", and `31765`, \"daughter of.\"\n",
    "\n",
    "This function currently returns MANTO Entity objects."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parents = manto_ent.getParents()\n",
    "print(parents)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's look for grandparents. Note that when sources disagree about parentage, MANTO returns an inclusive list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for p in parents:\n",
    "    print(f'Grandparents on {p.name}’s side:')\n",
    "    for gp in p.getParents():\n",
    "        print(' - ' + gp.name)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Boolean relationship checker\n",
    "\n",
    "#### For MANTO Entities\n",
    "\n",
    "Sometimes we just want a yes/no answer as to parentage. For MANTO Entities, methods like `isChildOf()` will take a second Manto entity as argument and return `True` or `False`. This can be useful, for example, for filtering a list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# a list of character pairs, by MANTO ID\n",
    "pairs = [\n",
    "    ('8182179', '8182084'),  # Anchises and Aeneas\n",
    "    ('8188282', '8190182'),  # Eteocles and Oedipus\n",
    "    ('8189802', '8182035'),  # Menelaus and Agamemnon\n",
    "]\n",
    "\n",
    "for a, b in pairs:\n",
    "    ent_a = manto.getMantoID(a)\n",
    "    ent_b = manto.getMantoID(b)\n",
    "    print(f'Is {ent_a.name} the child of {ent_b.name}? ', ent_a.isChildOf(ent_b))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### For DICES Characters\n",
    "\n",
    "For convenience, there are also wrapper functions that perform the same checks on DICES Characters by resolving the characters to MANTO entities first, then calling the boolean methods above. If you pass a CharacterInstance instead of a Character, the function will try to determine the MANTO ID of the underlying Character.\n",
    "\n",
    "If passed a Character with no MANTO ID, or an anonymous CharacterInstance with no Character, these functions return `None`. You can use the optional `err_val` parameter to set a different fallback value; for example, if you're using the function in a filter and want to ensure it always returns a Boolean, set this to `False`.\n",
    "\n",
    "For these next examples, let's start with a list of Telemachus' interlocutors."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# download all speeches by Telemachus\n",
    "speeches = api.getSpeeches(spkr_id=char.id, progress=True)\n",
    "print(f'{len(speeches)} speeches')\n",
    "\n",
    "# extract the list of addressees\n",
    "addressees = speeches.getAddrs(flatten=True)\n",
    "print(f'{len(addressees)} unique addressees.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this block, we use call `charIsChild()` on the speaker and his addressees to test parentage using MANTO's data. Where the function returns `None`, we know data was missing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for addr in addressees:\n",
    "    check = manto.charIsChild(char, addr)\n",
    "    if check is not None:\n",
    "        print(f'Is {char.name} the child of {addr.name}?', check)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Or we can use MANTO ties in a custom filter. In this case I'll silence the debugging messages and force `charIsChild()` to return `False` if no MANTO ID is found."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# filter a CharacterInstanceGroup based on MANTO ties:\n",
    "parent_addrs = addressees.advancedFilter(lambda addr: manto.charIsChild(char, addr, debug=False, err_val=False))\n",
    "\n",
    "print(f'Parent addressees of {char.name}:')\n",
    "for p in parent_addrs:\n",
    "    print(p.name)\n",
    "print()\n",
    "    \n",
    "# now use the filtered list of CharacterInstances to filter the original SpeechGroup\n",
    "parent_speeches = speeches.filterAddrInstances(parent_addrs)\n",
    "\n",
    "print(f'Speeches in which {char.name} addresses a parent:\\n')\n",
    "for s in parent_speeches:\n",
    "    print(f'{s.author.name} {s.work.title} {s.l_range}\\t{s.getAddrString()}')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}