diff --git a/README.md b/README.md index c4f8bc5..03e1eab 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,107 @@ -# A script to make you proud +# Project management tool for a translation agency -This repository contains a small Python program that shows that I have learned Python in this semester. +## Introduction +Every translation agency needs to keep track of its past and ongoing projects, just like any company. However, when projects start to accumulate, it may become rather difficult to keep an overview. In addition, large tables or endless database entries do not make for comfortable reading. This tool enables project managers to quickly get an overview of their translation projects and all their information, as a user-friendly and informative text, as well as retrieving specific information. -The code has been developed by Mariana Montes. +## Files in this repository +- `README.md` (this file): Gives an overview of the repository, the files it contains and how to use them. +- `project-management-tool_basic.py`: A basic version of the project management tool, which can be run from the command line rather than importing it to use it. +- `project-management-tool_expanded.ipynb`: An interactive version of the script, with more features than the basic version, but which needs to be imported and opened and cannot be run from the command line. +- `project-management-tool_expanded.md`: The markdown version of the file above. +- `projects_db.json`: A sample project database, containing a list of dictionaries with all the information for each project, namely: + - `title` (a string) indicates the project's title (typically the title of the source document, or the overall title the translator gave the project if there's more than one document to be translated); + - `client` (a string) indicates the client who ordered the translation; + - `source` (a string) indicates the language of the source document (document to be translated); + - `target` (a string) indicates the language of the target document (translation); + - `words` (an integer) indicates the word count of the source document; + - `start` (a string) indicates the project's start date in ISO format (YYYY-MM-DD); + - `deadline` (a string) indicates the project's deadline in ISO format (YYYY-MM-DD); + - `price` (a float) indicates the total price invoiced to the client (excl. VAT); + - `tm` (a boolean) indicates whether or not a translation memory is available for this project; + - `translator` (a string) indicates the freelancer assigned to translate this project (using their unique reference inside the agency's database) or is left empty to indicate that an agency collaborator translated the project; + - `reviewer` (a string) indicates the freelancer assigned to review this project (using their unique reference inside the agency's database) or is left empty to indicate that an agency collaborator reviewed the project; + - `status` (a string) indicates the project's status in the agency workflow; + - `domain` (a string) indicates the overall domain to which the project belongs. +- `guide_bruxelles.json`, `handboek.json`, `rhumatismes_inflammatoires.json` and `user_guide.json`: Separate files containing the same information as `projects_db.json`, but with one file per project. +- `freelancers_db.json`: A sample database (again, as a list of dictionaries), with an overview of three freelancers' information, namely: + - `name` (a string): The freelancer's name in Firstname Lastname format; + - `email`(a string): The freelancer's email address; + - `phone`(a string): The freelancer's phone number (international version, so starting with "+"); + - `task` (a set of strings): The task(s) the freelancer usually performs for the agency, namely whether they're a translator or a reviewer; + - `language`(a set of strings): The freelancer's working language(s). +- `add-on_database-generator.ipynb` and `add-on_database-generator.md`: An add-on not integrated in the project management tool itself, but which you can use to generate the freelancer/project information in the dictionary format you may need for the script (i.e. same format as the json-files mentioned above). +- `add-on_TM-generator.ipynb` and `add-on_TM-generator.md`: An add-on not integrated in the project management tool itself, but which you can use to generate a translation memory based on a source and target text, so you can turn your project information from `TM = false` into `TM = true`. +- `python_en.txt` and `python_en.txt`: A sample source and target text to try out the TM generator. -## Installation and usage +## Composition of the project management tool -Clone this repository with the following git command in the console (or download the ZIP file via GitHub): +### Basic version +The tool consists of a Python class called `Project`. That class creates objects with the following **attributes**: +- 13 instance attributes, which are the same as dictionary keys in the project information dictionaries. The last four attributes are optional and have the following default values: + - `translator`: "internal", meaning that an agency collaborator was assigned to the project in this role; + - `reviewer`: "internal", meaning that an agency collaborator was assigned to the project in this role; + - `status`: "created"; + - `domain`: An empty string, meaning that no domain was provided for this project (so it's probably a general text). +- 7 computed attributes, namely: + - `st`: A conversion of the start date from a string into an ISO-formatted date using the `datetime` module. + - `dl`: A conversion of the deadline from a string into an ISO-formatted date using the `datetime` module. + - `today`: The date of the day on which the tool is used, computed using the `datetime` module. + - `daysleft`: The number of days left until the project deadline, computed by substracting the current date (`today`) from the project deadline (`dl`). + - `length`: The total number of days foreseen for the project (weekends and holidays included), computed by subtracting the start date (`st`) from the deadline (`dl`). + - `rate`: The rate per word the client pays for the project, computed by dividing the project price by the word count. + - `efficiency`: The number of words to translate or revise per work day to meet the deadline, computed by dividing the word count by 5/7th of the total project length. Does not take into account holidays. -```sh -git clone git@github.com:montesmariana/intro_machine_learning_using_python -``` +The class also has three **methods**: +- `days_left`: Prints a message indicating the number of days left until the project deadline if the deadline is in the past or present, and indicating that the deadline has been exceeded already if it is in the past. +- `project_length`: Prints the total number of days (weekends included) foreseen for the project (as calling that attribute would otherwise return a datetime timedelta, which isn't very clear for most users). +- a `printing method`, which displays the project information using the following text template: + - "`title` is a translation for`client` from `source` into `target`. + - Both the translator and the reviewer are agency collaborators. (Or "The translator is `translator` and the reviewer is `reviewer`." if they're freelancers.) + - The domain is `domain`(or "unspecified" if no domain was provided). + - It's `words` words long, with a rate of `rate` € per word. + - It started on `st` and is due on `dl`, so `length` days are foreseen for it, of which `daysleft` left. To meet the deadline,`efficiency` words need to be translated or revised per day. + - There is a translation memory. (Or "no" if there's none.) + - The project is currently `status`." -You can import the script as a module by adding the repository to your path in a Python script or interactive notebook and then calling `import`. +The script is documented with **docstrings** and has an **argparse parser**, so it can be run directly from the command line. -```python -import sys -sys.path.append('/path/to/intro_machine_learning_using_python') -import script as s -``` +### Expanded version +The expanded version of the script has all the features the basic version has, except for the argparse parser (due to time constraints). In addition, it has some extra features that the basic version does not have. -Check out `tutorial.md` to see an example of the different functionalities of the script! +Next to the `Project` class, the expanded tool has a `Freelancer` class to turn the entries of the freelancer database found in `freelancers_db.json` into objects. The **attributes** of that class are the same as the keys in that list of dictionaries. The class also has two **methods**, namely: +- A method to import arguments using keyword arguments (`kwargs`), rather than manually inputting each attribute; +- A `printing method`, which displays the freelancer information using the following text template: + - "`name` can be contacted via e-mail (`email`) or via phone (`phone`). + - `name`'s reference in our database is `ref`. + - `name` works as a `task` for `language`." -You can also run the script directly with the following code in a console: +Due to time constraints, this class is **not documented with docstrings**. -```sh -python script.py -``` +This added `Freelancer` class allows project managers to not only get an overview of their project's information, but to link the project database and the freelancer database to easily look up information about the freelancers assigned to a project (say, if they're late to deliver or something changes in the project organisation and the PM wants to contact them via e-mail). -Or in Jupyter notebook with: +## How to use the project management tool -```python -%run script.py -``` +### Basic version +You can run the basic version of the script directly from the command line and manually feed it the project information, using: -In both cases `example.json` stands for the `filename` argument that the script needs. You can use [the file in this repository](example.json) or a similar file of yours. Find more information on how this script works with: +`python project-management-tool_basic.py "title" "client" "source" "target" words "YYYY-MM-DD" "YYYY-MM-DD" price tm` -```sh -python script.py --help -``` +(The placeholders need to be replaced by the actual project information, check above that you input all the information as the right variable type, otherwise you will get an error message asking you to use the correct type.) -If you run this script, you become proud of yourself. +### Expanded version +Because no argparse parser was programmed yet for this version of the tool, it needs to be opened rather than being run from the command line. You can open `project-management-tool_expanded.ipynb` with Jupyter Notebooks, or `project-management-tool_expanded.md` with, for example, VS Code. For more information about how to use these files, open them and go through the tutorial in them. Unlike with the basic version run from the command line, this version of the tool allows files as input to instantiate objects. It is possible to print the information of all the projects in one go, using `projects_db.json`, or to link the `Freelancer` and `Project` class for a single project, using `freelancers_db.json` and one of the separate project files. + +## Future features +Due to time constraints, several features could not be implemented, which will be added in future updates: +- An argparse parser for the expanded version, which will enable the user to run it from the command line rather than having to open the script. Said parser will also work with files, rather than forcing the user to manually input every attribute, which will result in a significant time gain and make use of the tool easier. +- Validation and docstrings in the `Freelancer` class. +- Validation of the project deadline: ensure that the deadline comes *after* the start date, not before. +- Fine-tuning the `start` date and `deadline`, so the PM can either specify at what time of the day the project starts/needs to be delivered, or not specify it (in which case, the default start/end time is midnight for the start and 11:59 pm for the deadline). +- Differentiate between a rush assignment (`efficiency` calculated on 7/7 of the project lenth instead of 5/7) and a regular assignment (`efficiency` calculated on 5/7 of the project length). +- Fine-tuning of the `efficiency` by taking into account weekends and holidays (ex. so a regular four-days project is counted as four full work days if it spans from Monday to Thursday, but only two full work days if it spans from Friday to Monday, rather than calculating 5/7 of the full project length in both cases). +- A `Client` class with all the necessary client information (contact, VAT-number, bank account number, domain, unique reference inside the agency database...) This class will be linked to the `Project` class just like `Freelancer`, facilitating retrieval of client information. +- The possibility to loop through all the separate project files even when linking `Project` and `Freelancer` objects, so you don't have to run the script for each project separately (which is feasible for four projects, but far too time-consuming in a real agency setting). +- An extension of the recognised file formats, from json or csv to xml, excel-documents... +- Fine-tuning the `efficiency` attribute by creating a specific attribute for translation and for review (for example 2/3 of the time for translation and 1/3 for review). +- Fine-tuning the `rate` and `price` attributes by linking the rate to a `rate` entry in the freelancer database (since each freelancer has their own rates, and rate is not applicable to agency collaborators, who receive a fixed salary per month rather than being paid per project) and calculating the agency's margin after substraction of the freelancer fees from the total project price. +- Ensuring standardisation of the dates in a certain time zone, to avoid calculation errors if the start date and deadline were fixed in a different time zone from that of the person using the script (since their time zone determines the computation of "today"). diff --git a/add-on_TM-generator.ipynb b/add-on_TM-generator.ipynb new file mode 100644 index 0000000..6369480 --- /dev/null +++ b/add-on_TM-generator.ipynb @@ -0,0 +1,270 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "45eba7f4", + "metadata": {}, + "source": [ + "# Source and target text aligner\n", + "Sometimes, you still have some translations left over from a time where you didn't use CAT-tools and you'd like to feed them into your translation memory. Some CAT-tools have built-in text aligners, but not all of them, so how do you go from two separate text documents to an aligned bilingual (csv-)file ready to be fed into your TM?" + ] + }, + { + "cell_type": "markdown", + "id": "cd681b03", + "metadata": {}, + "source": [ + "## Step one: Prepare the source and target text\n", + "The easiest file format to start from is a pure txt-file... and since for a TM only the pure text is of interest, converting a Word-, PowerPoint- or whatever file to a txt-file isn't an issue. So, we'll take the original source and target document and export them to a txt-format (with utf-8 encoding).\n", + "## Step two: Store the two (continuous) texts into variables" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4ccbdff0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Introduction to Machine Learning with Python.\\n\\nThis module provides an introduction to the basic concepts and use of the Python programming language in support of translation. Focus lies on the main concepts that include Natural Language Processing, automation, text analysis and machine learning.\\n'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with open('python_en.txt', encoding = 'utf-8') as f:\n", + " st_1 = f.read()\n", + "st_1" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "01e779fd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Introduction au machine learning à l’aide de Python.\\n\\nCe module offre une introduction aux concepts de base et à l’utilisation du langage de programmation Python comme aide à la traduction. L’accent est mis sur le traitement du langage naturel (NLP), l’automatisation, l’analyse de texte et le machine learning.\\n'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with open('python_fr.txt', encoding = 'utf-8') as f:\n", + " tt_1 = f.read()\n", + "tt_1" + ] + }, + { + "cell_type": "markdown", + "id": "7c32d752", + "metadata": {}, + "source": [ + "## Step three: Split the single text string into list of sentences\n", + "Since most TMs (and CAT-tools) use sentence segmentation, the source and target text need to be split up into sentences. So, each text becomes a list of separate sentences.\n", + "\n", + "For this, we use `nltk tokenizer`, which functions with English and French (and many other languages, but English and French are the ones that interest us right now)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "fa4734fa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Introduction to Machine Learning with Python.',\n", + " 'This module provides an introduction to the basic concepts and use of the Python programming language in support of translation.',\n", + " 'Focus lies on the main concepts that include Natural Language Processing, automation, text analysis and machine learning.']" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from nltk.tokenize import sent_tokenize\n", + "split_st_1 = sent_tokenize(st_1, language = 'english')\n", + "split_st_1" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "afdef22f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Introduction au machine learning à l’aide de Python.',\n", + " 'Ce module offre une introduction aux concepts de base et à l’utilisation du langage de programmation Python comme aide à la traduction.',\n", + " 'L’accent est mis sur le traitement du langage naturel (NLP), l’automatisation, l’analyse de texte et le machine learning.']" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from nltk.tokenize import sent_tokenize\n", + "split_tt_1 = sent_tokenize(tt_1, language = 'french')\n", + "split_tt_1" + ] + }, + { + "cell_type": "markdown", + "id": "a554c888", + "metadata": {}, + "source": [ + "## Step four: Aligning those lists and exporting the tuples list to a csv-file\n", + "Writing a csv-file looks fairly similar to reading a txt-file, like we did at the start of the process, except this time we use `f.write` instead of `f.read`.\n", + "A csv-file consists of rows, often a first header row with the label of each column, followed by the actual content of the file. Both are defined separately.\n", + "- The content of the header row (defined in the first indented line) simply consists of the language codes of the source and target language.\n", + "- The content of the next rows (defined in the next indented lines) contains our texts.\n", + " - The `zip()`-function aligns the first sentence of the source text with the first sentence of the target text, the second with the second... and so on in x, y tuples.\n", + " - Next, those tuples are put inside an f-string that separates x and y with a tab and places makes a new line start after each y." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c07d868f", + "metadata": {}, + "outputs": [], + "source": [ + "with open(\"translation_memory.csv\", \"w\", encoding = \"utf-8\") as f: # open file to overwrite\n", + " f.write(\"EN\\tFR\\n\") # write heading\n", + " for x, y in zip(split_st_1, split_tt_1): # go through each pair of lines\n", + " f.write(f\"{x}\\t{y}\\n\") # write the line" + ] + }, + { + "cell_type": "markdown", + "id": "b3ffb77f", + "metadata": {}, + "source": [ + "## Step five: Admiring our work\n", + "Using pandas, we can read the newly created csv-file." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f1a8de6c", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2f5e0b81", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ENFR
0Introduction to Machine Learning with Python.Introduction au machine learning à l’aide de P...
1This module provides an introduction to the ba...Ce module offre une introduction aux concepts ...
2Focus lies on the main concepts that include N...L’accent est mis sur le traitement du langage ...
\n", + "
" + ], + "text/plain": [ + " EN \\\n", + "0 Introduction to Machine Learning with Python. \n", + "1 This module provides an introduction to the ba... \n", + "2 Focus lies on the main concepts that include N... \n", + "\n", + " FR \n", + "0 Introduction au machine learning à l’aide de P... \n", + "1 Ce module offre une introduction aux concepts ... \n", + "2 L’accent est mis sur le traitement du langage ... " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "read_tm = pd.read_csv('translation_memory.csv', sep = '\\t')\n", + "read_tm.head()" + ] + } + ], + "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.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/add-on_TM-generator.md b/add-on_TM-generator.md new file mode 100644 index 0000000..f69b006 --- /dev/null +++ b/add-on_TM-generator.md @@ -0,0 +1,148 @@ +# Source and target text aligner +Sometimes, you still have some translations left over from a time where you didn't use CAT-tools and you'd like to feed them into your translation memory. Some CAT-tools have built-in text aligners, but not all of them, so how do you go from two separate text documents to an aligned bilingual (csv-)file ready to be fed into your TM? + +## Step one: Prepare the source and target text +The easiest file format to start from is a pure txt-file... and since for a TM only the pure text is of interest, converting a Word-, PowerPoint- or whatever file to a txt-file isn't an issue. So, we'll take the original source and target document and export them to a txt-format (with utf-8 encoding). +## Step two: Store the two (continuous) texts into variables + + +```python +with open('python_en.txt', encoding = 'utf-8') as f: + st_1 = f.read() +st_1 +``` + + + + + 'Introduction to Machine Learning with Python.\n\nThis module provides an introduction to the basic concepts and use of the Python programming language in support of translation. Focus lies on the main concepts that include Natural Language Processing, automation, text analysis and machine learning.\n' + + + + +```python +with open('python_fr.txt', encoding = 'utf-8') as f: + tt_1 = f.read() +tt_1 +``` + + + + + 'Introduction au machine learning à l’aide de Python.\n\nCe module offre une introduction aux concepts de base et à l’utilisation du langage de programmation Python comme aide à la traduction. L’accent est mis sur le traitement du langage naturel (NLP), l’automatisation, l’analyse de texte et le machine learning.\n' + + + +## Step three: Split the single text string into list of sentences +Since most TMs (and CAT-tools) use sentence segmentation, the source and target text need to be split up into sentences. So, each text becomes a list of separate sentences. + +For this, we use `nltk tokenizer`, which functions with English and French (and many other languages, but English and French are the ones that interest us right now). + + +```python +from nltk.tokenize import sent_tokenize +split_st_1 = sent_tokenize(st_1, language = 'english') +split_st_1 +``` + + + + + ['Introduction to Machine Learning with Python.', + 'This module provides an introduction to the basic concepts and use of the Python programming language in support of translation.', + 'Focus lies on the main concepts that include Natural Language Processing, automation, text analysis and machine learning.'] + + + + +```python +from nltk.tokenize import sent_tokenize +split_tt_1 = sent_tokenize(tt_1, language = 'french') +split_tt_1 +``` + + + + + ['Introduction au machine learning à l’aide de Python.', + 'Ce module offre une introduction aux concepts de base et à l’utilisation du langage de programmation Python comme aide à la traduction.', + 'L’accent est mis sur le traitement du langage naturel (NLP), l’automatisation, l’analyse de texte et le machine learning.'] + + + +## Step four: Aligning those lists and exporting the tuples list to a csv-file +Writing a csv-file looks fairly similar to reading a txt-file, like we did at the start of the process, except this time we use `f.write` instead of `f.read`. +A csv-file consists of rows, often a first header row with the label of each column, followed by the actual content of the file. Both are defined separately. +- The content of the header row (defined in the first indented line) simply consists of the language codes of the source and target language. +- The content of the next rows (defined in the next indented lines) contains our texts. + - The `zip()`-function aligns the first sentence of the source text with the first sentence of the target text, the second with the second... and so on in x, y tuples. + - Next, those tuples are put inside an f-string that separates x and y with a tab and places makes a new line start after each y. + + +```python +with open("translation_memory.csv", "w", encoding = "utf-8") as f: # open file to overwrite + f.write("EN\tFR\n") # write heading + for x, y in zip(split_st_1, split_tt_1): # go through each pair of lines + f.write(f"{x}\t{y}\n") # write the line +``` + +## Step five: Admiring our work +Using pandas, we can read the newly created csv-file. + + +```python +import pandas as pd +``` + + +```python +read_tm = pd.read_csv('translation_memory.csv', sep = '\t') +read_tm.head() +``` + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ENFR
0Introduction to Machine Learning with Python.Introduction au machine learning à l’aide de P...
1This module provides an introduction to the ba...Ce module offre une introduction aux concepts ...
2Focus lies on the main concepts that include N...L’accent est mis sur le traitement du langage ...
+
+ + diff --git a/add-on_database-generator.ipynb b/add-on_database-generator.ipynb new file mode 100644 index 0000000..3fad348 --- /dev/null +++ b/add-on_database-generator.ipynb @@ -0,0 +1,288 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e3769b22", + "metadata": {}, + "source": [ + "# Introduction\n", + "This script is an add-on to the project management tool. Most translation agencies already have their own databases (or apps) and would therefore not need an extra script to generate/export them. However, for agencies which do not yet have a handy way to record their freelancer and project information in a structured way and/or export it to a usable file format, here is a script to list the freelancer and project information as dictionaries exported to json-files." + ] + }, + { + "cell_type": "markdown", + "id": "7b4bbba8", + "metadata": {}, + "source": [ + "# Freelancers\n", + "The entire freelancers database will be exported as a single file containing a list of dictionaries." + ] + }, + { + "cell_type": "markdown", + "id": "efe751b4", + "metadata": {}, + "source": [ + "## Creating a list of dictionaries\n", + "The information of each freelancer is recorded in a single dictionary. Said information comprises:\n", + "- the freelancer's full `name` (in First name Last name format), a string,\n", + "- the freelancer's `email` address, a string,\n", + "- the freelancer's `phone`number, a string,\n", + "- the freelancer's `reference`, which is their unique identifier in the agency's database (this reference is also the name of the dictionary with that freelancer's information), a string,\n", + "- the freelancer's `task(s)`, so whether they are a translator and/or a reviewer, a set,\n", + "- the freelancer's `languages`, a set." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c90216bc", + "metadata": {}, + "outputs": [], + "source": [ + "sdw = {\n", + " 'name' : 'Sibylle de Woot',\n", + " 'email' : 'sdewoot@email.be',\n", + " 'phone' : '+32 485 12 34 56',\n", + " 'ref' : 'sdw',\n", + " 'task' : [\n", + " 'translator',\n", + " 'reviewer'\n", + " ],\n", + " 'language' : [\n", + " 'FR',\n", + " 'NL',\n", + " 'EN',\n", + " 'DE'\n", + " ]\n", + "}\n", + "mm = {\n", + " 'name' : 'Mariana Montes',\n", + " 'email' : 'mariana.montes@company.com',\n", + " 'phone' : '+32 487 98 76 54',\n", + " 'ref' : 'mm',\n", + " 'task' : [\n", + " 'reviewer'\n", + " ],\n", + " 'language' : [\n", + " 'ES',\n", + " 'EN'\n", + " ]\n", + "}\n", + "evdl = {\n", + " 'name' : 'Emily van der Londen',\n", + " 'email' : 'evdl@translation.net',\n", + " 'phone' : '+32 486 19 28 37',\n", + " 'ref' : 'evdl',\n", + " 'task' : [\n", + " 'translator'\n", + " ],\n", + " 'language' : [\n", + " 'NL',\n", + " 'EN',\n", + " 'FR'\n", + " ]\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "af21cd42", + "metadata": {}, + "source": [ + "The dictionaries are then assembled together into a list:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2bdb15d0", + "metadata": {}, + "outputs": [], + "source": [ + "freelancers = [sdw, mm, evdl]" + ] + }, + { + "cell_type": "markdown", + "id": "db81bdc2", + "metadata": {}, + "source": [ + "## Creating the file\n", + "This list is then dumped into (= exported as) a json-file called `freelancers_db.json`:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "eab0e236", + "metadata": {}, + "outputs": [], + "source": [ + "import json # to generate a json-file, the json-module is necessary\n", + "with open('freelancers_db.json', 'w', encoding='utf-8') as f:\n", + " json.dump(freelancers, f)" + ] + }, + { + "cell_type": "markdown", + "id": "74525c2f", + "metadata": {}, + "source": [ + "# Projects\n", + "Unlike the freelancers' information, each project's information will also be exported into a separate file (as we need one file per project for the project management tool to work, see main script)." + ] + }, + { + "cell_type": "markdown", + "id": "34707a69", + "metadata": {}, + "source": [ + "## Creating the dictionaries\n", + "The method is the same as for the freelancer database, only the information is obviously different:\n", + "- `title` (a string, ) indicates the project's title (typically the title of the source document, or the overall title the translator gave the project if there's more than one document to be translated);\n", + "- `client` (a string) indicates the client who ordered the translation;\n", + "- `source` (a string) indicates the language of the source document (document to be translated);\n", + "- `target` (a string) indicates the language of the target document (translation);\n", + "- `words` (an integer) indicates the word count of the source document;\n", + "- `start` (a string) indicates the project's start date in ISO format (YYYY-MM-DD);\n", + "- `deadline` (a string) indicates the project's deadline in ISO format (YYYY-MM-DD);\n", + "- `price` (a float) indicates the total price invoiced to the client (excl. VAT);\n", + "- `tm` (a boolean) indicates whether or not a translation memory is available for this project;\n", + "- `translator`(a string, optional, defaults to \"internal\") gives the internal reference of the freelance translator assigned to the project, or remains empty if the project was assigned to an internal translator;\n", + "- `reviewer`(a string, optional, defaults to \"internal\") gives the internal reference of the freelance reviewer assigned to the project, or remains empty if the project was assigned to an internal reviewer;\n", + "- `status`(a string, optional, defaults to \"created\") indicated the project status in the agency workflow, choosing from \"created\", \"in translation\", \"in revision\", \"delivered\", \"delayed\", or \"cancel(l)ed\";\n", + "- `domain` (a string, optional, defaults to empty string) indicates the overall domain to which the project belongs." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2b030111", + "metadata": {}, + "outputs": [], + "source": [ + "rhumatismes_inflammatoires = {\n", + " 'title' : 'La polyarthrite rhumatoïde et autres rhumatismes inflammatoires',\n", + " 'client' : 'Reuma vzw',\n", + " 'source' : 'FR',\n", + " 'target' : 'NL',\n", + " 'words' : 2142,\n", + " 'start' : '2020-10-02',\n", + " 'deadline' : '2020-10-15',\n", + " 'price' : 715.00,\n", + " 'tm' : False,\n", + " 'translator' : '',\n", + " 'reviewer' : '',\n", + " 'status' : 'delivered',\n", + " 'domain' : 'healthcare'\n", + "}\n", + "handboek = {\n", + " 'title' : 'Handboek voor studentenvertegenwoordigers',\n", + " 'client' : 'KU Leuven',\n", + " 'source' : 'NL',\n", + " 'target' : 'EN',\n", + " 'words' : 7237,\n", + " 'start' : '2023-02-21',\n", + " 'deadline' : '2023-03-07',\n", + " 'price' : 2680.00,\n", + " 'tm' : True,\n", + " 'translator' : 'sdw',\n", + " 'reviewer' : '',\n", + " 'status' : 'delayed', \n", + " 'domain' : 'education'\n", + "}\n", + "user_guide = {\n", + " 'title' : 'User Guide MFPs',\n", + " 'client' : 'UGent',\n", + " 'source' : 'EN',\n", + " 'target' : 'NL',\n", + " 'words' : 1852,\n", + " 'start' : '2023-04-12',\n", + " 'deadline' : '2023-04-15',\n", + " 'price' : 740.00,\n", + " 'tm' : True,\n", + " 'translator' : '',\n", + " 'reviewer' : 'mm',\n", + " 'status' : 'cancelled',\n", + " 'domain' : ''\n", + "}\n", + "guide_bruxelles = {\n", + " 'title' : 'Guide de Bruxelles',\n", + " 'client' : 'Foodies',\n", + " 'source' : 'NL',\n", + " 'target' : 'FR',\n", + " 'words' : 11500,\n", + " 'start' : '2023-05-06',\n", + " 'deadline' : '2023-06-30',\n", + " 'price' : 4025.00,\n", + " 'tm' : False,\n", + " 'translator' : 'evdl',\n", + " 'reviewer' : 'sdw',\n", + " 'status' : 'in revision', \n", + " 'domain' : ''\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d6460ed3", + "metadata": {}, + "outputs": [], + "source": [ + "projects = [rhumatismes_inflammatoires, handboek, user_guide, guide_bruxelles]" + ] + }, + { + "cell_type": "markdown", + "id": "92084ac1", + "metadata": {}, + "source": [ + "## Creating the files\n", + "Unlike the freelancers database, each project is also exported separately into one json-file:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e9ff8b20", + "metadata": {}, + "outputs": [], + "source": [ + "with open('projects_db.json', 'w', encoding='utf-8') as f:\n", + " json.dump(projects, f)\n", + "\n", + "with open('rhumatismes_inflammatoires.json', 'w', encoding='utf-8') as f:\n", + " json.dump(rhumatismes_inflammatoires, f)\n", + "with open('handboek.json', 'w', encoding='utf-8') as f:\n", + " json.dump(handboek, f)\n", + "with open('user_guide.json', 'w', encoding='utf-8') as f:\n", + " json.dump(user_guide, f)\n", + "with open('guide_bruxelles.json', 'w', encoding='utf-8') as f:\n", + " json.dump(guide_bruxelles, f)" + ] + } + ], + "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.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/add-on_database-generator.md b/add-on_database-generator.md new file mode 100644 index 0000000..d1bf077 --- /dev/null +++ b/add-on_database-generator.md @@ -0,0 +1,184 @@ +# Introduction +This script is an add-on to the project management tool. Most translation agencies already have their own databases (or apps) and would therefore not need an extra script to generate/export them. However, for agencies which do not yet have a handy way to record their freelancer and project information in a structured way and/or export it to a usable file format, here is a script to list the freelancer and project information as dictionaries exported to json-files. + +# Freelancers +The entire freelancers database will be exported as a single file containing a list of dictionaries. + +## Creating a list of dictionaries +The information of each freelancer is recorded in a single dictionary. Said information comprises: +- the freelancer's full `name` (in First name Last name format), a string, +- the freelancer's `email` address, a string, +- the freelancer's `phone`number, a string, +- the freelancer's `reference`, which is their unique identifier in the agency's database (this reference is also the name of the dictionary with that freelancer's information), a string, +- the freelancer's `task(s)`, so whether they are a translator and/or a reviewer, a set, +- the freelancer's `languages`, a set. + + +```python +sdw = { + 'name' : 'Sibylle de Woot', + 'email' : 'sdewoot@email.be', + 'phone' : '+32 485 12 34 56', + 'ref' : 'sdw', + 'task' : [ + 'translator', + 'reviewer' + ], + 'language' : [ + 'FR', + 'NL', + 'EN', + 'DE' + ] +} +mm = { + 'name' : 'Mariana Montes', + 'email' : 'mariana.montes@company.com', + 'phone' : '+32 487 98 76 54', + 'ref' : 'mm', + 'task' : [ + 'reviewer' + ], + 'language' : [ + 'ES', + 'EN' + ] +} +evdl = { + 'name' : 'Emily van der Londen', + 'email' : 'evdl@translation.net', + 'phone' : '+32 486 19 28 37', + 'ref' : 'evdl', + 'task' : [ + 'translator' + ], + 'language' : [ + 'NL', + 'EN', + 'FR' + ] +} +``` + +The dictionaries are then assembled together into a list: + + +```python +freelancers = [sdw, mm, evdl] +``` + +## Creating the file +This list is then dumped into (= exported as) a json-file called `freelancers_db.json`: + + +```python +import json # to generate a json-file, the json-module is necessary +with open('freelancers_db.json', 'w', encoding='utf-8') as f: + json.dump(freelancers, f) +``` + +# Projects +Unlike the freelancers' information, each project's information will also be exported into a separate file (as we need one file per project for the project management tool to work, see main script). + +## Creating the dictionaries +The method is the same as for the freelancer database, only the information is obviously different: +- `title` (a string, ) indicates the project's title (typically the title of the source document, or the overall title the translator gave the project if there's more than one document to be translated); +- `client` (a string) indicates the client who ordered the translation; +- `source` (a string) indicates the language of the source document (document to be translated); +- `target` (a string) indicates the language of the target document (translation); +- `words` (an integer) indicates the word count of the source document; +- `start` (a string) indicates the project's start date in ISO format (YYYY-MM-DD); +- `deadline` (a string) indicates the project's deadline in ISO format (YYYY-MM-DD); +- `price` (a float) indicates the total price invoiced to the client (excl. VAT); +- `tm` (a boolean) indicates whether or not a translation memory is available for this project; +- `translator`(a string, optional, defaults to "internal") gives the internal reference of the freelance translator assigned to the project, or remains empty if the project was assigned to an internal translator; +- `reviewer`(a string, optional, defaults to "internal") gives the internal reference of the freelance reviewer assigned to the project, or remains empty if the project was assigned to an internal reviewer; +- `status`(a string, optional, defaults to "created") indicated the project status in the agency workflow, choosing from "created", "in translation", "in revision", "delivered", "delayed", or "cancel(l)ed"; +- `domain` (a string, optional, defaults to empty string) indicates the overall domain to which the project belongs. + + +```python +rhumatismes_inflammatoires = { + 'title' : 'La polyarthrite rhumatoïde et autres rhumatismes inflammatoires', + 'client' : 'Reuma vzw', + 'source' : 'FR', + 'target' : 'NL', + 'words' : 2142, + 'start' : '2020-10-02', + 'deadline' : '2020-10-15', + 'price' : 715.00, + 'tm' : False, + 'translator' : '', + 'reviewer' : '', + 'status' : 'delivered', + 'domain' : 'healthcare' +} +handboek = { + 'title' : 'Handboek voor studentenvertegenwoordigers', + 'client' : 'KU Leuven', + 'source' : 'NL', + 'target' : 'EN', + 'words' : 7237, + 'start' : '2023-02-21', + 'deadline' : '2023-03-07', + 'price' : 2680.00, + 'tm' : True, + 'translator' : 'sdw', + 'reviewer' : '', + 'status' : 'delayed', + 'domain' : 'education' +} +user_guide = { + 'title' : 'User Guide MFPs', + 'client' : 'UGent', + 'source' : 'EN', + 'target' : 'NL', + 'words' : 1852, + 'start' : '2023-04-12', + 'deadline' : '2023-04-15', + 'price' : 740.00, + 'tm' : True, + 'translator' : '', + 'reviewer' : 'mm', + 'status' : 'cancelled', + 'domain' : '' +} +guide_bruxelles = { + 'title' : 'Guide de Bruxelles', + 'client' : 'Foodies', + 'source' : 'NL', + 'target' : 'FR', + 'words' : 11500, + 'start' : '2023-05-06', + 'deadline' : '2023-06-30', + 'price' : 4025.00, + 'tm' : False, + 'translator' : 'evdl', + 'reviewer' : 'sdw', + 'status' : 'in revision', + 'domain' : '' +} +``` + + +```python +projects = [rhumatismes_inflammatoires, handboek, user_guide, guide_bruxelles] +``` + +## Creating the files +Unlike the freelancers database, each project is also exported separately into one json-file: + + +```python +with open('projects_db.json', 'w', encoding='utf-8') as f: + json.dump(projects, f) + +with open('rhumatismes_inflammatoires.json', 'w', encoding='utf-8') as f: + json.dump(rhumatismes_inflammatoires, f) +with open('handboek.json', 'w', encoding='utf-8') as f: + json.dump(handboek, f) +with open('user_guide.json', 'w', encoding='utf-8') as f: + json.dump(user_guide, f) +with open('guide_bruxelles.json', 'w', encoding='utf-8') as f: + json.dump(guide_bruxelles, f) +``` diff --git a/example.json b/example.json deleted file mode 100644 index e69de29..0000000 diff --git a/freelancers_db.json b/freelancers_db.json new file mode 100644 index 0000000..05bebbe --- /dev/null +++ b/freelancers_db.json @@ -0,0 +1 @@ +[{"name": "Sibylle de Woot", "email": "sdewoot@email.be", "phone": "+32 485 12 34 56", "ref": "sdw", "task": ["translator", "reviewer"], "language": ["FR", "NL", "EN", "DE"]}, {"name": "Mariana Montes", "email": "mariana.montes@company.com", "phone": "+32 487 98 76 54", "ref": "mm", "task": ["reviewer"], "language": ["ES", "EN"]}, {"name": "Emily van der Londen", "email": "evdl@translation.net", "phone": "+32 486 19 28 37", "ref": "evdl", "task": ["translator"], "language": ["NL", "EN", "FR"]}] \ No newline at end of file diff --git a/guide_bruxelles.json b/guide_bruxelles.json new file mode 100644 index 0000000..78ed310 --- /dev/null +++ b/guide_bruxelles.json @@ -0,0 +1 @@ +{"title": "Guide de Bruxelles", "client": "Foodies", "source": "NL", "target": "FR", "words": 11500, "start": "2023-05-06", "deadline": "2023-06-30", "price": 4025.0, "tm": false, "translator": "evdl", "reviewer": "sdw", "status": "in revision", "domain": ""} \ No newline at end of file diff --git a/handboek.json b/handboek.json new file mode 100644 index 0000000..7b36710 --- /dev/null +++ b/handboek.json @@ -0,0 +1 @@ +{"title": "Handboek voor studentenvertegenwoordigers", "client": "KU Leuven", "source": "NL", "target": "EN", "words": 7237, "start": "2023-02-21", "deadline": "2023-03-07", "price": 2680.0, "tm": true, "translator": "sdw", "reviewer": "", "status": "delayed", "domain": "education"} \ No newline at end of file diff --git a/project-management-tool_basic.py b/project-management-tool_basic.py new file mode 100644 index 0000000..3c96ad4 --- /dev/null +++ b/project-management-tool_basic.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +import datetime + +"""This script gives a translation agency an overview of all its projects. +""" + +import argparse + +class Project: + + def __init__( + self, + title, + client, + source, + target, + words, + start, + deadline, + price, + tm, + translator = 'internal', + reviewer = 'internal', + status = 'created', + domain = ''): + """Initialises an object of the Project class, a class that represents a translation project of a translation agency. + + Args: + title (str): Title of the project (typically the title of the source document, or the overall title the agency gave the project if there's more than one document to be translated). + client (str): Client who ordered the translation. + source (str): Language of the source document(s) (document(s) to be translated). + target (str): Language of the target document(s) (translation(s)). + words (int): Word count of the source document(s). + start (str): Project's start date in ISO format (YYYY-MM-DD). + deadline (str): Project's deadline in ISO format (YYYY-MM-DD). + price (float): Total price invoiced to the client (excl. VAT). + tm (bool): Whether a translation memory is available for this project. + translator (string, optional): Translator assigned to the project. Defaults to 'internal'. + reviewer (string, optional): Reviewer assigned to the project. Defaults to 'internal'. + status (string, optional): Current project status inside the agency's workflow. Defaults to 'created'. + domain (str, optional): Overall domain to which the project belongs. Defaults to an empty string. + + Attributes: + title (str): Title of the project (typically the title of the source document, or the overall title the agency gave the project if there's more than one document to be translated). + client (str): Client who ordered the translation. + source (str): Language of the source document(s) (document(s) to be translated). + target (str): Language of the target document(s) (translation(s)). + words (int): Word count of the source document(s). + start (str): Project's start date in ISO format (YYYY-MM-DD). + deadline (str): Project's deadline in ISO format (YYYY-MM-DD). + price (float): Total price invoiced to the client (excl. VAT). + tm (bool): Whether a translation memory is available for this project. + translator (string, optional): Translator assigned to the project. + reviewer (string, optional): Reviewer assigned to the project. + status (string, optional): Current project status inside the agency's workflow. + domain (str): Overall domain to which the project belongs. + today (date): Date of the day where the script is run. + st (date): Project's start date, turned from an ISO-formatted string into a date. + dl (date): Project's deadline, turned from an ISO-formatted string into a date. + daysleft (timedelta): Time left until the project's deadline. + length (timedelta): Total time allocated to the project. + rate (float): Project's word rate. + efficiency (float): Number of words to be translated or revised per day to meet the deadline (assuming a five-day workweek). + + Raises: + TypeError: If 'title' is not a string. + TypeError: If 'client' is not a string. + TypeError: If 'source' is not a string. + TypeError: If 'target' is not a string. + TypeError: If 'words' is not an integer. + TypeError: If 'start' is not a string. + ValueError: If 'start' does not follow the pattern 4 digits-2 digits-2 digits. + TypeError: If 'deadline' is not a string. + ValueError: If 'deadline' does not follow the pattern 4 digits-2 digits-2 digits. + TypeError: If 'price' is not a float. + TypeError: If 'tm' is not a boolean. + TypeError: If 'translator' is not a string. + TypeError: If 'reviewer' is not a string. + TypeError: If 'status' is not a string. + ValueError: If 'status' is not a label in the agency workflow: created, in translation, in revision, delivered, delayed, cancel(l)ed. + TypeError: If 'domain' is not a string. + """ + if type(title) != str: + raise TypeError("The title must be a string.") + else: + self.title = title + if type(client) != str: + raise TypeError("The client name must be a string.") + else: + self.client = client + if type(source) != str: + raise TypeError("The source language must be a string.") + else: + self.source = source + if type(target) != str: + raise TypeError("The target langague must be a string.") + else: + self.target = target + if type(words) != int: + raise TypeError("The word count must be an integer.") + else: + self.words = words + if type(start) != str: + raise TypeError("The start date must be provided as a string.") + try: + self.st = datetime.date.fromisoformat(start) + except: + raise TypeError("The start date must be provided in ISOFormat") + else: + self.start = start + if type(deadline) != str: + raise TypeError("The deadline must be provided as a string.") + try: + self.dl = datetime.date.fromisoformat(deadline) + except: + raise TypeError("The start deadline must be provided in ISOFormat") + else: + self.deadline = deadline + if type(price) != float: + raise TypeError("The price must be a float.") + else: + self.price = price + if type(tm) != bool: + raise TypeError("The TM availability must be a boolean.") + else: + self.tm = tm + if type(translator) != str: + raise TypeError("The translator's name must be a string.") + elif translator == '': + self.translator = "internal" + else: + self.translator = translator + if type(reviewer) != str: + raise TypeError("The reviewer's name must be a string.") + elif reviewer == '': + self.reviewer = "internal" + else: + self.reviewer = reviewer + if type(status) != str: + raise TypeError("The status must be a string.") + elif status == '': + self.status = "created" + elif not status.lower() in ["created", + "in translation", + "in revision", + "delivered", + "delayed", + "cancelled", + "canceled"]: + raise ValueError("Please pick a status from the workflow: created, in translation, in revision, delivered, delayed or cancelled.") + else: + self.status = status + if type(domain) != str: + raise TypeError("The domain must be a string.") + else: + self.domain = domain + + today = datetime.date.today() + self.daysleft = self.dl - today + self.length = self.dl - self.st + self.rate = self.price/self.words + self.efficiency = words/((5/7)*self.length.days) + + def days_left(self): + """Displays how many days remain until the project deadline. + + Args: + None + + Returns: + str: A message informing the user that the deadline has been exceeded already if the deadline is in the past. + str: A message informing the user of the number of days left until the deadline if the deadline is not in the past. + """ + if self.dl < datetime.date.today(): + return f"The deadline has been exceeded already." + else: + return f"There are {self.daysleft.days} days left until the deadline." + + def project_length(self): + """Displays the total number of days allocated to the project. + + Args: + None + + Returns: + str: The total number of days allocated to the project. + """ + return f"{self.length.days} days" + + def __str__(self): + # defines the print behaviour: returns a text providing the main information about the project + sent_1 = f"{self.title} is a translation for {self.client} from {self.source} into {self.target}." + if self.translator.lower() == "internal" and self.reviewer.lower() == "internal": + sent_2 = f"Both the translator and the reviewer are agency collaborators." + elif self.translator.lower() == "internal" and self.reviewer.lower() != "internal": + sent_2 = f"The translator is an agency collaborator and the reviewer is {self.reviewer}." + elif self.translator.lower() != "internal" and self.reviewer.lower() == "internal": + sent_2 = f"The translator is {self.translator} and the reviewer is an agency collaborator." + else: + sent_2 = f"The translator is {self.translator} and the reviewer is {self.reviewer}." + # this if-statement considers whether a domain was added + if len(self.domain) > 0: + sent_3 = f"The domain is: {self.domain}." + else: + sent_3 = "The domain is unspecified." # if no domain was added, the text mentions it + sent_4 = f"It's {self.words} words long, with a rate of {round(self.rate, 2)} € per word." #the word rate is rounded to two decimal places to avoid cumbersomely long numbers + # this if-statement considers whether the deadline is in the past + if self.dl < datetime.date.today(): + sent_5 = f"It started on {self.st} and was due on {self.dl}, so {self.length.days} days were foreseen for it. To meet the deadline, {round(self.efficiency)} words needed to be translated or revised per day." # the efficiency is rounded to units because you can't translate a fraction of a word anyway + else: + sent_5 = f"It started on {self.st} and is due on {self.dl}, so {self.length.days} days are foreseen for it, of which {self.daysleft.days} left. To meet the deadline, {round(self.efficiency)} words need to be translated or revised per day." + # this if-statement considers whether there is a translation memory for the project + sent_6 = f"There is {'a' if self.tm else 'no'} translation memory." + sent_7 = f"The project is currently {self.status}." + # print each sentence in a different line + return "\n".join([sent_1, sent_2, sent_3, sent_4, sent_5, sent_6, sent_7]) + +if __name__ == "__main__": + parser = argparse.ArgumentParser("project") + parser.add_argument("title", type=str, help="Title of the project") + parser.add_argument("client", type=str, help="Client who ordered the translation") + parser.add_argument("source", type=str, help="Source language") + parser.add_argument("target", type=str, help="Target language") + parser.add_argument("words", type=int, help="Project word count") + parser.add_argument("start", type=str, help="Project start date") + parser.add_argument("deadline", type=str, help="Project deadline") + parser.add_argument("price", type=float, help="Total price project") + parser.add_argument("tm", type=bool, help="Whether a translation memory is available") + parser.add_argument("--translator", type=str, default = "internal", help="Translator assigned to the project") + parser.add_argument("--revisor", type=str, default = "internal",help="Revisor assigned to the project") + parser.add_argument("--status", type=str, default = "created", + choices = ["created", "in translation", "in revision", "delivered", "delayed", "cancelled", "canceled"], help="Project status in the agency workflow") + parser.add_argument("--domain", type=str, default = "", help="Overall domain of the text") + args = parser.parse_args() + proj = Project(args.title, args.client, args.source, args.target, args.words, args.start, args.deadline, args.price, args.tm, args.translator, args.revisor, args.status, args.domain) + proj.days_left() + proj.project_length() + print(proj) + diff --git a/project-management-tool_expanded.ipynb b/project-management-tool_expanded.ipynb new file mode 100644 index 0000000..5dc3b83 --- /dev/null +++ b/project-management-tool_expanded.ipynb @@ -0,0 +1,1683 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "14eadce4", + "metadata": {}, + "source": [ + "# The two classes\n", + "Below, you'll find the two classes described in `README.md`. Make their respective cells run to initialise the classes." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2480b610", + "metadata": {}, + "outputs": [], + "source": [ + "import datetime\n", + "\n", + "\"\"\"This script gives a translation agency an overview of all its projects.\n", + "\"\"\"\n", + "\n", + "class Project:\n", + "\n", + " def __init__(\n", + " self,\n", + " title,\n", + " client,\n", + " source,\n", + " target,\n", + " words,\n", + " start,\n", + " deadline,\n", + " price,\n", + " tm,\n", + " translator = 'internal',\n", + " reviewer = 'internal',\n", + " status = 'created',\n", + " domain = ''):\n", + " \"\"\"Initialises an object of the Project class, a class that represents a translation project of a translation agency.\n", + " \n", + " Args:\n", + " title (str): Title of the project (typically the title of the source document, or the overall title the agency gave the project if there's more than one document to be translated).\n", + " client (str): Client who ordered the translation.\n", + " source (str): Language of the source document(s) (document(s) to be translated).\n", + " target (str): Language of the target document(s) (translation(s)).\n", + " words (int): Word count of the source document(s).\n", + " start (str): Project's start date in ISO format (YYYY-MM-DD).\n", + " deadline (str): Project's deadline in ISO format (YYYY-MM-DD).\n", + " price (float): Total price invoiced to the client (excl. VAT).\n", + " tm (bool): Whether a translation memory is available for this project.\n", + " translator (string, optional): Translator assigned to the project. Defaults to 'internal'.\n", + " reviewer (string, optional): Reviewer assigned to the project. Defaults to 'internal'.\n", + " status (string, optional): Current project status inside the agency's workflow. Defaults to 'created'.\n", + " domain (str, optional): Overall domain to which the project belongs. Defaults to an empty string.\n", + " \n", + " Attributes:\n", + " title (str): Title of the project (typically the title of the source document, or the overall title the agency gave the project if there's more than one document to be translated).\n", + " client (str): Client who ordered the translation.\n", + " source (str): Language of the source document(s) (document(s) to be translated).\n", + " target (str): Language of the target document(s) (translation(s)).\n", + " words (int): Word count of the source document(s).\n", + " start (str): Project's start date in ISO format (YYYY-MM-DD).\n", + " deadline (str): Project's deadline in ISO format (YYYY-MM-DD).\n", + " price (float): Total price invoiced to the client (excl. VAT).\n", + " tm (bool): Whether a translation memory is available for this project.\n", + " translator (string, optional): Translator assigned to the project.\n", + " reviewer (string, optional): Reviewer assigned to the project.\n", + " status (string, optional): Current project status inside the agency's workflow.\n", + " domain (str): Overall domain to which the project belongs.\n", + " today (date): Date of the day where the script is run.\n", + " st (date): Project's start date, turned from an ISO-formatted string into a date.\n", + " dl (date): Project's deadline, turned from an ISO-formatted string into a date.\n", + " daysleft (timedelta): Time left until the project's deadline.\n", + " length (timedelta): Total time allocated to the project.\n", + " rate (float): Project's word rate.\n", + " efficiency (float): Number of words to be translated or revised per day to meet the deadline (assuming a five-day workweek).\n", + " \n", + " Raises:\n", + " TypeError: If 'title' is not a string.\n", + " TypeError: If 'client' is not a string.\n", + " TypeError: If 'source' is not a string.\n", + " TypeError: If 'target' is not a string.\n", + " TypeError: If 'words' is not an integer.\n", + " TypeError: If 'start' is not a string.\n", + " ValueError: If 'start' does not follow the pattern 4 digits-2 digits-2 digits.\n", + " TypeError: If 'deadline' is not a string.\n", + " ValueError: If 'deadline' does not follow the pattern 4 digits-2 digits-2 digits.\n", + " TypeError: If 'price' is not a float.\n", + " TypeError: If 'tm' is not a boolean.\n", + " TypeError: If 'translator' is not a string.\n", + " TypeError: If 'reviewer' is not a string.\n", + " TypeError: If 'status' is not a string.\n", + " ValueError: If 'status' is not a label in the agency workflow: created, in translation, in revision, delivered, delayed, cancel(l)ed.\n", + " TypeError: If 'domain' is not a string.\n", + " \"\"\"\n", + " if type(title) != str:\n", + " raise TypeError(\"The title must be a string.\")\n", + " else:\n", + " self.title = title\n", + " if type(client) != str:\n", + " raise TypeError(\"The client name must be a string.\")\n", + " else:\n", + " self.client = client\n", + " if type(source) != str:\n", + " raise TypeError(\"The source language must be a string.\")\n", + " else:\n", + " self.source = source\n", + " if type(target) != str:\n", + " raise TypeError(\"The target langague must be a string.\")\n", + " else:\n", + " self.target = target\n", + " if type(words) != int:\n", + " raise TypeError(\"The word count must be an integer.\")\n", + " else:\n", + " self.words = words\n", + " if type(start) != str:\n", + " raise TypeError(\"The start date must be provided as a string.\")\n", + " try:\n", + " self.st = datetime.date.fromisoformat(start)\n", + " except:\n", + " raise TypeError(\"The start date must be provided in ISO format\")\n", + " else:\n", + " self.start = start\n", + " if type(deadline) != str:\n", + " raise TypeError(\"The deadline must be provided as a string.\")\n", + " try:\n", + " self.dl = datetime.date.fromisoformat(deadline)\n", + " except:\n", + " raise TypeError(\"The deadline must be provided in ISO format\")\n", + " else:\n", + " self.deadline = deadline\n", + " if type(price) != float:\n", + " raise TypeError(\"The price must be a float.\")\n", + " else:\n", + " self.price = price\n", + " if type(tm) != bool:\n", + " raise TypeError(\"The TM availability must be a boolean.\")\n", + " else:\n", + " self.tm = tm\n", + " if type(translator) != str and type(translator) != Freelancer:\n", + " raise TypeError(\"The translator's name must be a string or an entry in our freelancer database.\")\n", + " elif translator == '':\n", + " self.translator = \"internal\"\n", + " else:\n", + " self.translator = translator\n", + " if type(reviewer) != str and type(reviewer) != Freelancer:\n", + " raise TypeError(\"The reviewer's name must be a string or an entry in our freelancer database.\")\n", + " elif reviewer == '':\n", + " self.reviewer = \"internal\"\n", + " else:\n", + " self.reviewer = reviewer\n", + " if type(status) != str:\n", + " raise TypeError(\"The status must be a string.\")\n", + " elif status == '':\n", + " self.status = \"created\"\n", + " elif not status.lower() in [\"created\",\n", + " \"in translation\",\n", + " \"in revision\",\n", + " \"delivered\",\n", + " \"delayed\",\n", + " \"cancelled\",\n", + " \"canceled\"]:\n", + " raise ValueError(\"Please pick a status from the workflow: created, in translation, in revision, delivered, delayed or cancelled.\")\n", + " else:\n", + " self.status = status\n", + " if type(domain) != str:\n", + " raise TypeError(\"The domain must be a string.\")\n", + " else:\n", + " self.domain = domain\n", + " \n", + " today = datetime.date.today()\n", + " self.daysleft = self.dl - today\n", + " self.length = self.dl - self.st\n", + " self.rate = self.price/self.words\n", + " self.efficiency = words/((5/7)*self.length.days)\n", + "\n", + " def days_left(self):\n", + " \"\"\"Displays how many days remain until the project deadline.\n", + " \n", + " Args:\n", + " None\n", + " \n", + " Returns:\n", + " str: A message informing the user that the deadline has been exceeded already if the deadline is in the past.\n", + " str: A message informing the user of the number of days left until the deadline if the deadline is not in the past.\n", + " \"\"\"\n", + " if self.dl < datetime.date.today():\n", + " return f\"The deadline has been exceeded already.\"\n", + " else:\n", + " return f\"There are {self.daysleft.days} days left until the deadline.\"\n", + " \n", + " def project_length(self):\n", + " \"\"\"Displays the total number of days allocated to the project.\n", + " \n", + " Args:\n", + " None\n", + " \n", + " Returns:\n", + " str: The total number of days allocated to the project.\n", + " \"\"\"\n", + " return f\"{self.length.days} days\"\n", + " \n", + " def __str__(self):\n", + " # defines the print behaviour: returns a text providing the main information about the project\n", + " sent_1 = f\"{self.title} is a translation for {self.client} from {self.source} into {self.target}.\"\n", + " if self.translator.lower() == \"internal\" and self.reviewer.lower() == \"internal\":\n", + " sent_2 = f\"Both the translator and the reviewer are agency collaborators.\"\n", + " elif self.translator.lower() == \"internal\" and self.reviewer.lower() != \"internal\":\n", + " sent_2 = f\"The translator is an agency collaborator and the reviewer is {self.reviewer}.\"\n", + " elif self.translator.lower() != \"internal\" and self.reviewer.lower() == \"internal\":\n", + " sent_2 = f\"The translator is {self.translator} and the reviewer is an agency collaborator.\"\n", + " else:\n", + " sent_2 = f\"The translator is {self.translator} and the reviewer is {self.reviewer}.\"\n", + " # this if-statement considers whether a domain was added\n", + " if len(self.domain) > 0:\n", + " sent_3 = f\"The domain is: {self.domain}.\"\n", + " else:\n", + " sent_3 = \"The domain is unspecified.\" # if no domain was added, the text mentions it\n", + " sent_4 = f\"It's {self.words} words long, with a rate of {round(self.rate, 2)} € per word.\" #the word rate is rounded to two decimal places to avoid cumbersomely long numbers\n", + " # this if-statement considers whether the deadline is in the past\n", + " if self.dl < datetime.date.today():\n", + " sent_5 = f\"It started on {self.st} and was due on {self.dl}, so {self.length.days} days were foreseen for it. To meet the deadline, {round(self.efficiency)} words needed to be translated or revised per day.\" # the efficiency is rounded to units because you can't translate a fraction of a word anyway\n", + " else:\n", + " sent_5 = f\"It started on {self.st} and is due on {self.dl}, so {self.length.days} days are foreseen for it, of which {self.daysleft.days} left. To meet the deadline, {round(self.efficiency)} words need to be translated or revised per day.\"\n", + " # this if-statement considers whether there is a translation memory for the project\n", + " sent_6 = f\"There is {'a' if self.tm else 'no'} translation memory.\"\n", + " sent_7 = f\"The project is currently {self.status}.\"\n", + " # print each sentence in a different line\n", + " return \"\\n\".join([sent_1, sent_2, sent_3, sent_4, sent_5, sent_6, sent_7])" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "14fba222", + "metadata": {}, + "outputs": [], + "source": [ + "class Freelancer():\n", + "\n", + " def __init__(self, name, email, phone, ref, task, language):\n", + " self.name = name\n", + " self.email = email\n", + " self.phone = phone\n", + " self.ref = ref\n", + " self.task = task\n", + " self.language = language\n", + " \n", + " def function_with_kwargs(**kwargs):\n", + " if \"name\" in kwargs:\n", + " print(kwargs[\"name\"])\n", + " else:\n", + " print(\"no name found\")\n", + " \n", + " def __str__(self):\n", + " sent_1 = f\"{self.name} can be contacted via e-mail ({self.email}) or via phone ({self.phone}).\"\n", + " sent_2 = f\"{self.name}'s reference in our database is {self.ref}.\"\n", + " sent_3 = f\"{self.name} works as a {', '.join(self.task)} for {', '.join(self.language)}.\"\n", + " return \"\\n\".join([sent_1, sent_2, sent_3])" + ] + }, + { + "cell_type": "markdown", + "id": "ea7762ef", + "metadata": {}, + "source": [ + "# Creating and using objects of the Project class" + ] + }, + { + "cell_type": "markdown", + "id": "336998ee", + "metadata": {}, + "source": [ + "## Manually\n", + "It is possible to manually define objects of each class, meaning that you write out each mandatory attribute (and any optional attribute you want to make deviate from the default value), for example:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "306f2ab3", + "metadata": {}, + "outputs": [], + "source": [ + "project1 = Project(\"The project title\", \"some client\", \"FR\", \"EN\", 10000, \"2023-06-17\", \"2023-06-30\", 3600.00, True)" + ] + }, + { + "cell_type": "markdown", + "id": "e9b9334d", + "metadata": {}, + "source": [ + "In this case, the name of the object is \"project1\" and no optional attribute differs from the default value. But you can also change all the optional attributes:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "bcfe2da8", + "metadata": {}, + "outputs": [], + "source": [ + "project2 = Project(\"A second project title\", \"another client\", \"NL\", \"DE\", 7500, \"2023-06-16\", \"2023-06-28\", 3200.00, True, \"Sibylle de Woot\", \"Mariana Montes\", \"in translation\", \"education\")" + ] + }, + { + "cell_type": "markdown", + "id": "109a2949", + "metadata": {}, + "source": [ + "Or only some of them:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "dbddfd92", + "metadata": {}, + "outputs": [], + "source": [ + "project3 = Project(\"Some other project title\", \"new client\", \"EN\", \"NL\", 12000, \"2023-06-15\", \"2023-06-30\", 3800.00, True, reviewer = \"Sibylle de Woot\", domain = \"healthcare\")" + ] + }, + { + "cell_type": "markdown", + "id": "3f3586f5", + "metadata": {}, + "source": [ + "Once an object has been created, you can use the various methods to\n", + "- Print an overview of all the project information:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "7390c272", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The project title is a translation for some client from FR into EN.\n", + "Both the translator and the reviewer are agency collaborators.\n", + "The domain is unspecified.\n", + "It's 10000 words long, with a rate of 0.36 € per word.\n", + "It started on 2023-06-17 and is due on 2023-06-30, so 13 days are foreseen for it, of which 12 left. To meet the deadline, 1077 words need to be translated or revised per day.\n", + "There is a translation memory.\n", + "The project is currently created.\n" + ] + } + ], + "source": [ + "print(project1)\n", + "# The printing method is the only one that doesn't require you to specify that it's specific to the Project class, you simply need the name of the object" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "4c36e4ee", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A second project title is a translation for another client from NL into DE.\n", + "The translator is Sibylle de Woot and the reviewer is Mariana Montes.\n", + "The domain is: education.\n", + "It's 7500 words long, with a rate of 0.43 € per word.\n", + "It started on 2023-06-16 and is due on 2023-06-28, so 12 days are foreseen for it, of which 10 left. To meet the deadline, 875 words need to be translated or revised per day.\n", + "There is a translation memory.\n", + "The project is currently in translation.\n" + ] + } + ], + "source": [ + "print(project2)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "80347a4a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Some other project title is a translation for new client from EN into NL.\n", + "The translator is an agency collaborator and the reviewer is Sibylle de Woot.\n", + "The domain is: healthcare.\n", + "It's 12000 words long, with a rate of 0.32 € per word.\n", + "It started on 2023-06-15 and is due on 2023-06-30, so 15 days are foreseen for it, of which 12 left. To meet the deadline, 1120 words need to be translated or revised per day.\n", + "There is a translation memory.\n", + "The project is currently created.\n" + ] + } + ], + "source": [ + "print(project3)" + ] + }, + { + "cell_type": "markdown", + "id": "eda324f2", + "metadata": {}, + "source": [ + "- Find out how much time is left until the deadline:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "de5fc1f5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'There are 12 days left until the deadline.'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Project.days_left(project1)\n", + "# To use any other class method, you need to use the format \"Class.method(object)\"" + ] + }, + { + "cell_type": "markdown", + "id": "28055bbe", + "metadata": {}, + "source": [ + "- Find out how many days are foreseen in total for the project:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "9b0ac32b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'13 days'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Project.project_length(project1)" + ] + }, + { + "cell_type": "markdown", + "id": "0ecd4c69", + "metadata": {}, + "source": [ + "Attributes can also be retrieved separately, with `object.attribute`, for example" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ceaf14b8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'The project title'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "project1.title" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a4c670b8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'2023-06-17'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "project1.start" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "6531970a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "datetime.timedelta(days=13)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "project1.length" + ] + }, + { + "cell_type": "markdown", + "id": "a87fe061", + "metadata": {}, + "source": [ + "As you can see, the value of the attribute \"length\" isn't very user-friendly, as it is a timedelta computed with the datetime module. That is the reason why the method `project_length` was created, to display the attribute in a more readable way. Another user-friendely ways to display the project length is by printing the attribute, rather than simply retrieving it:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "12f49f53", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "13 days, 0:00:00\n" + ] + } + ], + "source": [ + "print(project1.length)" + ] + }, + { + "cell_type": "markdown", + "id": "3c848675", + "metadata": {}, + "source": [ + "As you can see, the project length is calculated to the second here. For now, that's not useful, but when a future update allows PMs to specify the start and end time, it will be." + ] + }, + { + "cell_type": "markdown", + "id": "3d34827c", + "metadata": {}, + "source": [ + "So, feeding the project information into an object of the `Project` class makes for easy information retrieval and allows the PM to generate a clear global overview of the project in full sentences. However, especially with manual information input, issues can occur when the user makes a typo, or inputs the wrong type of variable. To counter those issues, validation was integrated into the `Project` class. Any wrong variable type raises an error, and the user is asked to use the right variable type.\n", + "\n", + "Say, for example, that I forget that the price needs to be a float (i.e. a decimal number):" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "f56b92bc", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "The price must be a float.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m project4 \u001b[38;5;241m=\u001b[39m \u001b[43mProject\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mYet another title\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43manother client\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mFR\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mNL\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m8000\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m2023-06-16\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m2023-06-28\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m3300\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mSibylle de Woot\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mMariana Montes\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43min translation\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mtechnical\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[1], line 117\u001b[0m, in \u001b[0;36mProject.__init__\u001b[0;34m(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain)\u001b[0m\n\u001b[1;32m 115\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdeadline \u001b[38;5;241m=\u001b[39m deadline\n\u001b[1;32m 116\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mtype\u001b[39m(price) \u001b[38;5;241m!=\u001b[39m \u001b[38;5;28mfloat\u001b[39m:\n\u001b[0;32m--> 117\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mThe price must be a float.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 118\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 119\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mprice \u001b[38;5;241m=\u001b[39m price\n", + "\u001b[0;31mTypeError\u001b[0m: The price must be a float." + ] + } + ], + "source": [ + "project4 = Project(\"Yet another title\", \"another client\", \"FR\", \"NL\", 8000, \"2023-06-16\", \"2023-06-28\", 3300, True, \"Sibylle de Woot\", \"Mariana Montes\", \"in translation\", \"technical\")" + ] + }, + { + "cell_type": "markdown", + "id": "7de66ade", + "metadata": {}, + "source": [ + "As you can see, the script informs me that the price must be a float. Now, let's say that my numbers are correct, but that I didn't write my dates in ISO format:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "6e08020c", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "The start date must be provided in ISO format", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[1], line 103\u001b[0m, in \u001b[0;36mProject.__init__\u001b[0;34m(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain)\u001b[0m\n\u001b[1;32m 102\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 103\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mst \u001b[38;5;241m=\u001b[39m \u001b[43mdatetime\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdate\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfromisoformat\u001b[49m\u001b[43m(\u001b[49m\u001b[43mstart\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 104\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m:\n", + "\u001b[0;31mValueError\u001b[0m: Invalid isoformat string: '23-06-16'", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[16], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m project4 \u001b[38;5;241m=\u001b[39m \u001b[43mProject\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mYet another title\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43manother client\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mFR\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mNL\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m8000\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m23-06-16\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m23-06-28\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m3300.00\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mSibylle de Woot\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mMariana Montes\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43min translation\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mtechnical\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[1], line 105\u001b[0m, in \u001b[0;36mProject.__init__\u001b[0;34m(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain)\u001b[0m\n\u001b[1;32m 103\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mst \u001b[38;5;241m=\u001b[39m datetime\u001b[38;5;241m.\u001b[39mdate\u001b[38;5;241m.\u001b[39mfromisoformat(start)\n\u001b[1;32m 104\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m:\n\u001b[0;32m--> 105\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mThe start date must be provided in ISO format\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 106\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 107\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mstart \u001b[38;5;241m=\u001b[39m start\n", + "\u001b[0;31mTypeError\u001b[0m: The start date must be provided in ISO format" + ] + } + ], + "source": [ + "project4 = Project(\"Yet another title\", \"another client\", \"FR\", \"NL\", 8000, \"23-06-16\", \"23-06-28\", 3300.00, True, \"Sibylle de Woot\", \"Mariana Montes\", \"in translation\", \"technical\")" + ] + }, + { + "cell_type": "markdown", + "id": "c9c92274", + "metadata": {}, + "source": [ + "While both dates are wrong, only the error with the start date is raised. That's because the script stops at the first detected error. However, if I only correct one and not the other, it will raise the error in the deadline:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "6d4f884f", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "The deadline must be provided in ISO format", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[1], line 111\u001b[0m, in \u001b[0;36mProject.__init__\u001b[0;34m(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain)\u001b[0m\n\u001b[1;32m 110\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 111\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdl \u001b[38;5;241m=\u001b[39m \u001b[43mdatetime\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdate\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfromisoformat\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdeadline\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 112\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m:\n", + "\u001b[0;31mValueError\u001b[0m: Invalid isoformat string: '23-06-28'", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[17], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m project4 \u001b[38;5;241m=\u001b[39m \u001b[43mProject\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mYet another title\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43manother client\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mFR\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mNL\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m8000\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m2023-06-16\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m23-06-28\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m3300.00\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mSibylle de Woot\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mMariana Montes\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43min translation\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mtechnical\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[1], line 113\u001b[0m, in \u001b[0;36mProject.__init__\u001b[0;34m(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain)\u001b[0m\n\u001b[1;32m 111\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdl \u001b[38;5;241m=\u001b[39m datetime\u001b[38;5;241m.\u001b[39mdate\u001b[38;5;241m.\u001b[39mfromisoformat(deadline)\n\u001b[1;32m 112\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m:\n\u001b[0;32m--> 113\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mThe deadline must be provided in ISO format\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 114\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 115\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdeadline \u001b[38;5;241m=\u001b[39m deadline\n", + "\u001b[0;31mTypeError\u001b[0m: The deadline must be provided in ISO format" + ] + } + ], + "source": [ + "project4 = Project(\"Yet another title\", \"another client\", \"FR\", \"NL\", 8000, \"2023-06-16\", \"23-06-28\", 3300.00, True, \"Sibylle de Woot\", \"Mariana Montes\", \"in translation\", \"technical\")" + ] + }, + { + "cell_type": "markdown", + "id": "71845103", + "metadata": {}, + "source": [ + "The way the script detects whether or not the date format is correct, is by trying to turn the string I inputted into an actual date with the datetime module. Because if that, it can also catch some errors if I provide the date in YYYY-DD-MM format, rather than YYYY-MM-DD:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "95d045ba", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "The start date must be provided in ISO format", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[1], line 103\u001b[0m, in \u001b[0;36mProject.__init__\u001b[0;34m(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain)\u001b[0m\n\u001b[1;32m 102\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 103\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mst \u001b[38;5;241m=\u001b[39m \u001b[43mdatetime\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdate\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfromisoformat\u001b[49m\u001b[43m(\u001b[49m\u001b[43mstart\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 104\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m:\n", + "\u001b[0;31mValueError\u001b[0m: month must be in 1..12", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[18], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m project4 \u001b[38;5;241m=\u001b[39m \u001b[43mProject\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mYet another title\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43manother client\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mFR\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mNL\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m8000\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m2023-16-06\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m2023-06-28\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m3300.00\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mSibylle de Woot\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mMariana Montes\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43min translation\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mtechnical\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[1], line 105\u001b[0m, in \u001b[0;36mProject.__init__\u001b[0;34m(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain)\u001b[0m\n\u001b[1;32m 103\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mst \u001b[38;5;241m=\u001b[39m datetime\u001b[38;5;241m.\u001b[39mdate\u001b[38;5;241m.\u001b[39mfromisoformat(start)\n\u001b[1;32m 104\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m:\n\u001b[0;32m--> 105\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mThe start date must be provided in ISO format\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 106\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 107\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mstart \u001b[38;5;241m=\u001b[39m start\n", + "\u001b[0;31mTypeError\u001b[0m: The start date must be provided in ISO format" + ] + } + ], + "source": [ + "project4 = Project(\"Yet another title\", \"another client\", \"FR\", \"NL\", 8000, \"2023-16-06\", \"2023-06-28\", 3300.00, True, \"Sibylle de Woot\", \"Mariana Montes\", \"in translation\", \"technical\")" + ] + }, + { + "cell_type": "markdown", + "id": "a2ea9317", + "metadata": {}, + "source": [ + "When trying to convert the string into a date, the script runs into a problem, as there is no sixteenth month in the year, which leads it to ask for an ISO-formatted date. This is handy, but has its limitations for dates until the twelvth of the month, in which day and month can me inverted and still yield a valid ISO-formatted date. Validation is a useful feature to weed out many mistakes, but it's no miracle solution and in the end it's up to the user to ensure that they use correct information." + ] + }, + { + "cell_type": "markdown", + "id": "0d5f6b5a", + "metadata": {}, + "source": [ + "A last \"special\" type of validation used in the `Project` class concerns the attribute `status`. To make sure everyone is on the same page concerning project progress, only labels from the agency workflow are allowed for this attribute, namely:\n", + "- created,\n", + "- in translation,\n", + "- in revision,\n", + "- delivered,\n", + "- delayed,\n", + "- cancel(l)ed.\n", + "\n", + "If a user tries to invent their own labels, they will receive a request to use a status in the agency workflow:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "48ee762b", + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "Please pick a status from the workflow: created, in translation, in revision, delivered, delayed or cancelled.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[19], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m project4 \u001b[38;5;241m=\u001b[39m \u001b[43mProject\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mYet another title\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43manother client\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mFR\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mNL\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m8000\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m2023-06-16\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m2023-06-28\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m3300.00\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mSibylle de Woot\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mMariana Montes\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mongoing\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mtechnical\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[1], line 147\u001b[0m, in \u001b[0;36mProject.__init__\u001b[0;34m(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain)\u001b[0m\n\u001b[1;32m 139\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mstatus \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcreated\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 140\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m status\u001b[38;5;241m.\u001b[39mlower() \u001b[38;5;129;01min\u001b[39;00m [\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcreated\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 141\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124min translation\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 142\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124min revision\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 145\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcancelled\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 146\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcanceled\u001b[39m\u001b[38;5;124m\"\u001b[39m]:\n\u001b[0;32m--> 147\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mPlease pick a status from the workflow: created, in translation, in revision, delivered, delayed or cancelled.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 148\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 149\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mstatus \u001b[38;5;241m=\u001b[39m status\n", + "\u001b[0;31mValueError\u001b[0m: Please pick a status from the workflow: created, in translation, in revision, delivered, delayed or cancelled." + ] + } + ], + "source": [ + "project4 = Project(\"Yet another title\", \"another client\", \"FR\", \"NL\", 8000, \"2023-06-16\", \"2023-06-28\", 3300.00, True, \"Sibylle de Woot\", \"Mariana Montes\", \"ongoing\", \"technical\")" + ] + }, + { + "cell_type": "markdown", + "id": "5d6d8b8d", + "metadata": {}, + "source": [ + "A last point you may have noticed is that, while the default value when no domain is provided is an empty string, the default value for `translator` and `reviewer` is \"internal\" and even an empty string is turned into \"internal\", even though it does not appear as such in the printed text with project information (and an empty string can be turned into \"an agency collaborator\" just as easily as a string containing \"internal\"). The reason is that, if the user wants to retrieve the value of the attribute `translator`, \"internal\" is much clearer than an empty string, which looks like missing information and which requires more reflexion on the part of the user to understand that it's empty because the project was assigned to an agency collaborator." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "0351fbd6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'internal'" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "project1.translator" + ] + }, + { + "cell_type": "markdown", + "id": "f468f379", + "metadata": {}, + "source": [ + "## Using files" + ] + }, + { + "cell_type": "markdown", + "id": "17d73d19", + "metadata": {}, + "source": [ + "Since creating each instance of the `Project` class manually would be both cumbersome and *very* time-consuming, a way to speed up the process is to export the project information to a file and use that file to feed the information into the `Project` class. In this example, all the project information was exported as a list of dictionaries (one dictionary per project) in the json-file called `projects_db.json`. Bear in mind that, for the system to find the file, it needs to be in the folder you're running the script from (otherwise you need to use the full path to the file, but we'll not go into that here).\n", + "\n", + "If all you want is an overview of all the projects printed as text, you can go through the dictionaries one by one and print their information, without actually creating `Project` objects:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "df01de23", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "La polyarthrite rhumatoïde et autres rhumatismes inflammatoires is a translation for Reuma vzw from FR into NL.\n", + "Both the translator and the reviewer are agency collaborators.\n", + "The domain is: healthcare.\n", + "It's 2142 words long, with a rate of 0.33 € per word.\n", + "It started on 2020-10-02 and was due on 2020-10-15, so 13 days were foreseen for it. To meet the deadline, 231 words needed to be translated or revised per day.\n", + "There is no translation memory.\n", + "The project is currently delivered.\n", + "----\n", + "Handboek voor studentenvertegenwoordigers is a translation for KU Leuven from NL into EN.\n", + "The translator is sdw and the reviewer is an agency collaborator.\n", + "The domain is: education.\n", + "It's 7237 words long, with a rate of 0.37 € per word.\n", + "It started on 2023-02-21 and was due on 2023-03-07, so 14 days were foreseen for it. To meet the deadline, 724 words needed to be translated or revised per day.\n", + "There is a translation memory.\n", + "The project is currently delayed.\n", + "----\n", + "User Guide MFPs is a translation for UGent from EN into NL.\n", + "The translator is an agency collaborator and the reviewer is mm.\n", + "The domain is unspecified.\n", + "It's 1852 words long, with a rate of 0.4 € per word.\n", + "It started on 2023-04-12 and was due on 2023-04-15, so 3 days were foreseen for it. To meet the deadline, 864 words needed to be translated or revised per day.\n", + "There is a translation memory.\n", + "The project is currently cancelled.\n", + "----\n", + "Guide de Bruxelles is a translation for Foodies from NL into FR.\n", + "The translator is evdl and the reviewer is sdw.\n", + "The domain is unspecified.\n", + "It's 11500 words long, with a rate of 0.35 € per word.\n", + "It started on 2023-05-06 and is due on 2023-06-30, so 55 days are foreseen for it, of which 12 left. To meet the deadline, 293 words need to be translated or revised per day.\n", + "There is no translation memory.\n", + "The project is currently in revision.\n", + "----\n" + ] + } + ], + "source": [ + "import json # we'll need the json module to use the file\n", + "# store the file's content in a variable:\n", + "projects = 'projects_db.json' # assign filename to a string variable\n", + "with open(projects, encoding = 'utf-8') as f:\n", + " # open file and use json to parse it\n", + " projects = json.load(f) # projects is now a list of dictionaries. \n", + "\n", + "# go through each of the items in the list\n", + "for project in projects:\n", + " # create a Project instance with title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status and domain\n", + " my_project = Project(project['title'], project['client'], project['source'], project['target'], project['words'], project['start'], project['deadline'], project['price'], project['tm'], project['translator'], project['reviewer'], project['status'], project['domain'])\n", + " # print the project information\n", + " print(my_project)\n", + " \n", + " # print a separating line between translations\n", + " print('----')" + ] + }, + { + "cell_type": "markdown", + "id": "acd1dcc3", + "metadata": {}, + "source": [ + "If we don't only want a printed overview of all the projects, but want to be able to retrieve information from specific attributes or use the other methods, we'll need to create objects of the `Project` class. Before we come to that, however, you might have noticed that instead of a name in Firstname Lastname format, freelancers are now referred to with abbreviations. Those are their references in the agency freelancer database and they are used to make the link between the `Project` and the `Freelancer` classes. So, let's first have a look at `Freelancer`." + ] + }, + { + "cell_type": "markdown", + "id": "35c2d175", + "metadata": {}, + "source": [ + "# Creating and using objects of the Freelancer class" + ] + }, + { + "cell_type": "markdown", + "id": "6a1897d3", + "metadata": {}, + "source": [ + "## Manually\n", + "While the class contents (described in `README.md`) differ from `Projects`, the `Freelancer` class works the same way. Objects can be instantiated manually, for example:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "a65c0362", + "metadata": {}, + "outputs": [], + "source": [ + "freelancer1 = Freelancer(\"Sibylle de Woot\", \"sdewoot@mail.be\", \"+32 486 12 34 56\", \"sdw\", [\"translator\", \"reviewer\"], [\"FR\", \"NL\", \"EN\", \"DE\"])" + ] + }, + { + "cell_type": "markdown", + "id": "11376c1a", + "metadata": {}, + "source": [ + "Again, you can then print a text displaying all the information:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "b543aedf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sibylle de Woot can be contacted via e-mail (sdewoot@mail.be) or via phone (+32 486 12 34 56).\n", + "Sibylle de Woot's reference in our database is sdw.\n", + "Sibylle de Woot works as a translator, reviewer for FR, NL, EN, DE.\n" + ] + } + ], + "source": [ + "print(freelancer1)" + ] + }, + { + "cell_type": "markdown", + "id": "a7932f6b", + "metadata": {}, + "source": [ + "Or retrieve specific information through the attributes:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "7e130be3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Sibylle de Woot'" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "freelancer1.name" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "b6851f59", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['FR', 'NL', 'EN', 'DE']" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "freelancer1.language" + ] + }, + { + "cell_type": "markdown", + "id": "181931df", + "metadata": {}, + "source": [ + "One limitation of the Freelancer class is its lack of validation. As a result, I can input virtually anything in any attribute: I could mix up information (for example exchange name and phone number), input the phone number as an integer rather than a string, not use the right format (for example not use the international version of the phone number), only give the freelancer's first name... Anything goes:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "fb01bd9f", + "metadata": {}, + "outputs": [], + "source": [ + "freelancer2 = Freelancer(\"Sibylle\", 486123456, \"sdewoot@email.be\", \"sdw\", \"translator & reviewer\", \"French, Dutch, English and German\")" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "ff4f4147", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sibylle can be contacted via e-mail (486123456) or via phone (sdewoot@email.be).\n", + "Sibylle's reference in our database is sdw.\n", + "Sibylle works as a t, r, a, n, s, l, a, t, o, r, , &, , r, e, v, i, e, w, e, r for F, r, e, n, c, h, ,, , D, u, t, c, h, ,, , E, n, g, l, i, s, h, , a, n, d, , G, e, r, m, a, n.\n" + ] + } + ], + "source": [ + "print(freelancer2)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "69e2ae3d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "486123456" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "freelancer2.email" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "5176310c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'French, Dutch, English and German'" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "freelancer2.language" + ] + }, + { + "cell_type": "markdown", + "id": "3f3c3675", + "metadata": {}, + "source": [ + "As you can see, the result is useless, unstructured and completely nonsensical. Hence why validation is a priority in the coming updates." + ] + }, + { + "cell_type": "markdown", + "id": "94b6a7a3", + "metadata": {}, + "source": [ + "## Using files\n", + "Like the `Project` class, the Freelancer class can go through a list (in this example, based on the file `freelancers_db.json`) and print each freelancer's information:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "3e2a1e76", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sibylle de Woot can be contacted via e-mail (sdewoot@email.be) or via phone (+32 485 12 34 56).\n", + "Sibylle de Woot's reference in our database is sdw.\n", + "Sibylle de Woot works as a translator, reviewer for FR, NL, EN, DE.\n", + "----\n", + "Mariana Montes can be contacted via e-mail (mariana.montes@company.com) or via phone (+32 487 98 76 54).\n", + "Mariana Montes's reference in our database is mm.\n", + "Mariana Montes works as a reviewer for ES, EN.\n", + "----\n", + "Emily van der Londen can be contacted via e-mail (evdl@translation.net) or via phone (+32 486 19 28 37).\n", + "Emily van der Londen's reference in our database is evdl.\n", + "Emily van der Londen works as a translator for NL, EN, FR.\n", + "----\n" + ] + } + ], + "source": [ + "freelancers = 'freelancers_db.json'\n", + "with open(freelancers, encoding = 'utf-8') as f:\n", + " freelancers = json.load(f)\n", + "\n", + "for freelancer in freelancers:\n", + " my_freelancer = Freelancer(freelancer['name'], freelancer['email'], freelancer['phone'], freelancer['ref'], freelancer['task'], freelancer['language'])\n", + " print(my_freelancer)\n", + " \n", + " print('----')" + ] + }, + { + "cell_type": "markdown", + "id": "6ca712c1", + "metadata": {}, + "source": [ + "But separate overviews of projects and freelancers aren't what interests us. What we're looking for, is to merge both in usable Python objects. So, let's see how that works." + ] + }, + { + "cell_type": "markdown", + "id": "19f9821c", + "metadata": {}, + "source": [ + "# Linking the Project and Freelancer class\n", + "For this, we need to slightly edit the `Project` class to make sure that the freelancer's name is displayed in the printed out project information, rather than the full text containing the freelancer information. We replace `{self.translator}` and `{self.reviewer}` by `{self.translator.name}` and `{self.reviewer.name}`." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "0f76b171", + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"This script gives a translation agency an overview of all its projects.\n", + "\"\"\"\n", + "\n", + "class Project:\n", + "\n", + " def __init__(\n", + " self,\n", + " title,\n", + " client,\n", + " source,\n", + " target,\n", + " words,\n", + " start,\n", + " deadline,\n", + " price,\n", + " tm,\n", + " translator = 'internal',\n", + " reviewer = 'internal',\n", + " status = 'created',\n", + " domain = ''):\n", + " \"\"\"Initialises an object of the Project class, a class that represents a translation project of a translation agency.\n", + " \n", + " Args:\n", + " title (str): Title of the project (typically the title of the source document, or the overall title the agency gave the project if there's more than one document to be translated).\n", + " client (str): Client who ordered the translation.\n", + " source (str): Language of the source document(s) (document(s) to be translated).\n", + " target (str): Language of the target document(s) (translation(s)).\n", + " words (int): Word count of the source document(s).\n", + " start (str): Project's start date in ISO format (YYYY-MM-DD).\n", + " deadline (str): Project's deadline in ISO format (YYYY-MM-DD).\n", + " price (float): Total price invoiced to the client (excl. VAT).\n", + " tm (bool): Whether a translation memory is available for this project.\n", + " translator (string, optional): Translator assigned to the project. Defaults to 'internal'.\n", + " reviewer (string, optional): Reviewer assigned to the project. Defaults to 'internal'.\n", + " status (string, optional): Current project status inside the agency's workflow. Defaults to 'created'.\n", + " domain (str, optional): Overall domain to which the project belongs. Defaults to an empty string.\n", + " \n", + " Attributes:\n", + " title (str): Title of the project (typically the title of the source document, or the overall title the agency gave the project if there's more than one document to be translated).\n", + " client (str): Client who ordered the translation.\n", + " source (str): Language of the source document(s) (document(s) to be translated).\n", + " target (str): Language of the target document(s) (translation(s)).\n", + " words (int): Word count of the source document(s).\n", + " start (str): Project's start date in ISO format (YYYY-MM-DD).\n", + " deadline (str): Project's deadline in ISO format (YYYY-MM-DD).\n", + " price (float): Total price invoiced to the client (excl. VAT).\n", + " tm (bool): Whether a translation memory is available for this project.\n", + " translator (string, optional): Translator assigned to the project.\n", + " reviewer (string, optional): Reviewer assigned to the project.\n", + " status (string, optional): Current project status inside the agency's workflow.\n", + " domain (str): Overall domain to which the project belongs.\n", + " today (date): Date of the day where the script is run.\n", + " st (date): Project's start date, turned from an ISO-formatted string into a date.\n", + " dl (date): Project's deadline, turned from an ISO-formatted string into a date.\n", + " daysleft (timedelta): Time left until the project's deadline.\n", + " length (timedelta): Total time allocated to the project.\n", + " rate (float): Project's word rate.\n", + " efficiency (float): Number of words to be translated or revised per day to meet the deadline (assuming a five-day workweek).\n", + " \n", + " Raises:\n", + " TypeError: If 'title' is not a string.\n", + " TypeError: If 'client' is not a string.\n", + " TypeError: If 'source' is not a string.\n", + " TypeError: If 'target' is not a string.\n", + " TypeError: If 'words' is not an integer.\n", + " TypeError: If 'start' is not a string.\n", + " ValueError: If 'start' does not follow the pattern 4 digits-2 digits-2 digits.\n", + " TypeError: If 'deadline' is not a string.\n", + " ValueError: If 'deadline' does not follow the pattern 4 digits-2 digits-2 digits.\n", + " TypeError: If 'price' is not a float.\n", + " TypeError: If 'tm' is not a boolean.\n", + " TypeError: If 'translator' is not a string.\n", + " TypeError: If 'reviewer' is not a string.\n", + " TypeError: If 'status' is not a string.\n", + " ValueError: If 'status' is not a label in the agency workflow: created, in translation, in revision, delivered, delayed, cancel(l)ed.\n", + " TypeError: If 'domain' is not a string.\n", + " \"\"\"\n", + " if type(title) != str:\n", + " raise TypeError(\"The title must be a string.\")\n", + " else:\n", + " self.title = title\n", + " if type(client) != str:\n", + " raise TypeError(\"The client name must be a string.\")\n", + " else:\n", + " self.client = client\n", + " if type(source) != str:\n", + " raise TypeError(\"The source language must be a string.\")\n", + " else:\n", + " self.source = source\n", + " if type(target) != str:\n", + " raise TypeError(\"The target langague must be a string.\")\n", + " else:\n", + " self.target = target\n", + " if type(words) != int:\n", + " raise TypeError(\"The word count must be an integer.\")\n", + " else:\n", + " self.words = words\n", + " if type(start) != str:\n", + " raise TypeError(\"The start date must be provided as a string.\")\n", + " try:\n", + " self.st = datetime.date.fromisoformat(start)\n", + " except:\n", + " raise TypeError(\"The start date must be provided in ISO format\")\n", + " else:\n", + " self.start = start\n", + " if type(deadline) != str:\n", + " raise TypeError(\"The deadline must be provided as a string.\")\n", + " try:\n", + " self.dl = datetime.date.fromisoformat(deadline)\n", + " except:\n", + " raise TypeError(\"The deadline must be provided in ISO format\")\n", + " else:\n", + " self.deadline = deadline\n", + " if type(price) != float:\n", + " raise TypeError(\"The price must be a float.\")\n", + " else:\n", + " self.price = price\n", + " if type(tm) != bool:\n", + " raise TypeError(\"The TM availability must be a boolean.\")\n", + " else:\n", + " self.tm = tm\n", + " if type(translator) != str and type(translator) != Freelancer:\n", + " raise TypeError(\"The translator's name must be a string or an entry in our freelancer database.\")\n", + " elif translator == '':\n", + " self.translator = \"internal\"\n", + " else:\n", + " self.translator = translator\n", + " if type(reviewer) != str and type(reviewer) != Freelancer:\n", + " raise TypeError(\"The reviewer's name must be a string or an entry in our freelancer database.\")\n", + " elif reviewer == '':\n", + " self.reviewer = \"internal\"\n", + " else:\n", + " self.reviewer = reviewer\n", + " if type(status) != str:\n", + " raise TypeError(\"The status must be a string.\")\n", + " elif status == '':\n", + " self.status = \"created\"\n", + " elif not status.lower() in [\"created\",\n", + " \"in translation\",\n", + " \"in revision\",\n", + " \"delivered\",\n", + " \"delayed\",\n", + " \"cancelled\",\n", + " \"canceled\"]:\n", + " raise ValueError(\"Please pick a status from the workflow: created, in translation, in revision, delivered, delayed or cancelled.\")\n", + " else:\n", + " self.status = status\n", + " if type(domain) != str:\n", + " raise TypeError(\"The domain must be a string.\")\n", + " else:\n", + " self.domain = domain\n", + " \n", + " today = datetime.date.today()\n", + " self.daysleft = self.dl - today\n", + " self.length = self.dl - self.st\n", + " self.rate = self.price/self.words\n", + " self.efficiency = words/((5/7)*self.length.days)\n", + "\n", + " def days_left(self):\n", + " \"\"\"Displays how many days remain until the project deadline.\n", + " \n", + " Args:\n", + " None\n", + " \n", + " Returns:\n", + " str: A message informing the user that the deadline has been exceeded already if the deadline is in the past.\n", + " str: A message informing the user of the number of days left until the deadline if the deadline is not in the past.\n", + " \"\"\"\n", + " if self.dl < datetime.date.today():\n", + " return f\"The deadline has been exceeded already.\"\n", + " else:\n", + " return f\"There are {self.daysleft.days} days left until the deadline.\"\n", + " \n", + " def project_length(self):\n", + " \"\"\"Displays the total number of days allocated to the project.\n", + " \n", + " Args:\n", + " None\n", + " \n", + " Returns:\n", + " str: The total number of days allocated to the project.\n", + " \"\"\"\n", + " return f\"{self.length.days} days\"\n", + " \n", + " def __str__(self):\n", + " # defines the print behaviour: returns a text providing the main information about the project\n", + " sent_1 = f\"{self.title} is a translation for {self.client} from {self.source} into {self.target}.\"\n", + " if self.translator == \"internal\" and self.reviewer == \"internal\":\n", + " sent_2 = f\"Both the translator and the reviewer are agency collaborators.\"\n", + " elif self.translator == \"internal\" and self.reviewer != \"internal\":\n", + " sent_2 = f\"The translator is an agency collaborator and the reviewer is {self.reviewer.name}.\"\n", + " elif self.translator != \"internal\" and self.reviewer == \"internal\":\n", + " sent_2 = f\"The translator is {self.translator.name} and the reviewer is an agency collaborator.\"\n", + " else:\n", + " sent_2 = f\"The translator is {self.translator.name} and the reviewer is {self.reviewer.name}.\"\n", + " # this if-statement considers whether a domain was added\n", + " if len(self.domain) > 0:\n", + " sent_3 = f\"The domain is: {self.domain}.\"\n", + " else:\n", + " sent_3 = \"The domain is unspecified.\" # if no domain was added, the text mentions it\n", + " sent_4 = f\"It's {self.words} words long, with a rate of {round(self.rate, 2)} € per word.\" #the word rate is rounded to two decimal places to avoid cumbersomely long numbers\n", + " # this if-statement considers whether the deadline is in the past\n", + " if self.dl < datetime.date.today():\n", + " sent_5 = f\"It started on {self.st} and was due on {self.dl}, so {self.length.days} days were foreseen for it. To meet the deadline, {round(self.efficiency)} words needed to be translated or revised per day.\" # the efficiency is rounded to units because you can't translate a fraction of a word anyway\n", + " else:\n", + " sent_5 = f\"It started on {self.st} and is due on {self.dl}, so {self.length.days} days are foreseen for it, of which {self.daysleft.days} left. To meet the deadline, {round(self.efficiency)} words need to be translated or revised per day.\"\n", + " # this if-statement considers whether there is a translation memory for the project\n", + " sent_6 = f\"There is {'a' if self.tm else 'no'} translation memory.\"\n", + " sent_7 = f\"The project is currently {self.status}.\"\n", + " # print each sentence in a different line\n", + " return \"\\n\".join([sent_1, sent_2, sent_3, sent_4, sent_5, sent_6, sent_7])" + ] + }, + { + "cell_type": "markdown", + "id": "0b55972d", + "metadata": {}, + "source": [ + "First, we'll retrieve the freelancers' information from the json-file and save it as a list of dictionaries. Granted, we've already done so above, but as some people may have skipped directly to this part of the tutorial, we'll do it again here." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "1674b222", + "metadata": {}, + "outputs": [], + "source": [ + "with open('freelancers_db.json', 'r') as f:\n", + " all_freelancers = {x['ref'] : x for x in json.load(f)} # the name of each dictionary is the value of the key \"ref\", so the freelancer's unique internal reference in the agency database" + ] + }, + { + "cell_type": "markdown", + "id": "b9e75768", + "metadata": {}, + "source": [ + "Then, we also read and save the project information as a dictionary. As mentioned above, the value of the \"translator\" and \"reviewer\" key are now internal references rather than names, which we can use to check whether the value of those keys (or attributes, once we instantiates objects of the `Project` class) correspond to a dictionary in our list `all_freelancers` (i.e. an entry in our freelancer database). If it is, the value of the attribute `translator` of our `Project` instance will not be a string, but an instance of the `Freelancer` class." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "825ceadd", + "metadata": {}, + "outputs": [], + "source": [ + "with open('guide_bruxelles.json', 'r') as f2:\n", + " guide_bruxelles_dict = json.load(f2)\n", + " if 'translator' in guide_bruxelles_dict and guide_bruxelles_dict['translator'] in all_freelancers:\n", + " guide_bruxelles_dict['translator'] = Freelancer(**all_freelancers[guide_bruxelles_dict['translator']])\n", + " if 'reviewer' in guide_bruxelles_dict and guide_bruxelles_dict['reviewer'] in all_freelancers:\n", + " guide_bruxelles_dict['reviewer'] = Freelancer(**all_freelancers[guide_bruxelles_dict['reviewer']])\n", + " guide_bruxelles = Project(**guide_bruxelles_dict)\n", + "\n", + "with open('handboek.json', 'r') as f3:\n", + " handboek_dict = json.load(f3)\n", + " if 'translator' in handboek_dict and handboek_dict['translator'] in all_freelancers:\n", + " handboek_dict['translator'] = Freelancer(**all_freelancers[handboek_dict['translator']])\n", + " if 'reviewer' in handboek_dict and handboek_dict['reviewer'] in all_freelancers:\n", + " handboek_dict['reviewer'] = Freelancer(**all_freelancers[handboek_dict['reviewer']])\n", + " handboek = Project(**handboek_dict)\n", + "\n", + "with open('rhumatismes_inflammatoires.json', 'r') as f4:\n", + " rhumatismes_inflammatoires_dict = json.load(f4)\n", + " if 'translator' in rhumatismes_inflammatoires_dict and rhumatismes_inflammatoires_dict['translator'] in all_freelancers:\n", + " rhumatismes_inflammatoires_dict['translator'] = Freelancer(**all_freelancers[rhumatismes_inflammatoires_dict['translator']])\n", + " if 'reviewer' in rhumatismes_inflammatoires_dict and rhumatismes_inflammatoires_dict['reviewer'] in all_freelancers:\n", + " rhumatismes_inflammatoires_dict['reviewer'] = Freelancer(**all_freelancers[rhumatismes_inflammatoires_dict['reviewer']])\n", + " rhumatismes_inflammatoires = Project(**rhumatismes_inflammatoires_dict)\n", + "\n", + "with open('user_guide.json', 'r') as f5:\n", + " user_guide_dict = json.load(f5)\n", + " if 'translator' in user_guide_dict and user_guide_dict['translator'] in all_freelancers:\n", + " user_guide_dict['translator'] = Freelancer(**all_freelancers[user_guide_dict['translator']])\n", + " if 'reviewer' in user_guide_dict and user_guide_dict['reviewer'] in all_freelancers:\n", + " user_guide_dict['reviewer'] = Freelancer(**all_freelancers[user_guide_dict['reviewer']])\n", + " user_guide = Project(**user_guide_dict)" + ] + }, + { + "cell_type": "markdown", + "id": "0032d9e8", + "metadata": {}, + "source": [ + "Once that is done, we can print both the project information:" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "0d75f947", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Guide de Bruxelles is a translation for Foodies from NL into FR.\n", + "The translator is Emily van der Londen and the reviewer is Sibylle de Woot.\n", + "The domain is unspecified.\n", + "It's 11500 words long, with a rate of 0.35 € per word.\n", + "It started on 2023-05-06 and is due on 2023-06-30, so 55 days are foreseen for it, of which 12 left. To meet the deadline, 293 words need to be translated or revised per day.\n", + "There is no translation memory.\n", + "The project is currently in revision.\n", + "----\n", + "Handboek voor studentenvertegenwoordigers is a translation for KU Leuven from NL into EN.\n", + "The translator is Sibylle de Woot and the reviewer is an agency collaborator.\n", + "The domain is: education.\n", + "It's 7237 words long, with a rate of 0.37 € per word.\n", + "It started on 2023-02-21 and was due on 2023-03-07, so 14 days were foreseen for it. To meet the deadline, 724 words needed to be translated or revised per day.\n", + "There is a translation memory.\n", + "The project is currently delayed.\n", + "----\n", + "La polyarthrite rhumatoïde et autres rhumatismes inflammatoires is a translation for Reuma vzw from FR into NL.\n", + "Both the translator and the reviewer are agency collaborators.\n", + "The domain is: healthcare.\n", + "It's 2142 words long, with a rate of 0.33 € per word.\n", + "It started on 2020-10-02 and was due on 2020-10-15, so 13 days were foreseen for it. To meet the deadline, 231 words needed to be translated or revised per day.\n", + "There is no translation memory.\n", + "The project is currently delivered.\n", + "----\n", + "User Guide MFPs is a translation for UGent from EN into NL.\n", + "The translator is an agency collaborator and the reviewer is Mariana Montes.\n", + "The domain is unspecified.\n", + "It's 1852 words long, with a rate of 0.4 € per word.\n", + "It started on 2023-04-12 and was due on 2023-04-15, so 3 days were foreseen for it. To meet the deadline, 864 words needed to be translated or revised per day.\n", + "There is a translation memory.\n", + "The project is currently cancelled.\n" + ] + } + ], + "source": [ + "print(guide_bruxelles)\n", + "print('----')\n", + "print(handboek)\n", + "print('----')\n", + "print(rhumatismes_inflammatoires)\n", + "print('----')\n", + "print (user_guide)" + ] + }, + { + "cell_type": "markdown", + "id": "0d354535", + "metadata": {}, + "source": [ + "The freelancer information can then be printed separately by asking to print the value of the `translator`/`reviewer` attribute." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "0250fbb3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Emily van der Londen can be contacted via e-mail (evdl@translation.net) or via phone (+32 486 19 28 37).\n", + "Emily van der Londen's reference in our database is evdl.\n", + "Emily van der Londen works as a translator for NL, EN, FR.\n" + ] + } + ], + "source": [ + "print(guide_bruxelles.translator)" + ] + }, + { + "cell_type": "markdown", + "id": "14e0c7c7", + "metadata": {}, + "source": [ + "The value of other `Project` attributes can be retrieved the usual way:" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "b0c41e69", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Guide de Bruxelles'" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "guide_bruxelles.title" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "07036a48", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "292.72727272727275" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "guide_bruxelles.efficiency" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "de05a880", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'55 days'" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Project.project_length(guide_bruxelles)" + ] + }, + { + "cell_type": "markdown", + "id": "d822691f", + "metadata": {}, + "source": [ + "And the freelancers' information can also be retrieved through the `Project` instance:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "f8cab93d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Emily van der Londen'" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "guide_bruxelles.translator.name" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "cecba6bb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'sdewoot@email.be'" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "guide_bruxelles.reviewer.email" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "de2c768a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['translator']" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "guide_bruxelles.translator.task" + ] + }, + { + "cell_type": "markdown", + "id": "3d69f7cd", + "metadata": {}, + "source": [ + "# Conclusion\n", + "Hopefully, this script will make project management easier for many translation agencies. Of course, this is only version 1.0 and many updates, improvements and extra features are foreseen in the near to medium future, as you can see in the last section of `README.md`." + ] + } + ], + "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.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/project-management-tool_expanded.md b/project-management-tool_expanded.md new file mode 100644 index 0000000..f1aa7bc --- /dev/null +++ b/project-management-tool_expanded.md @@ -0,0 +1,1186 @@ +# The two classes +Below, you'll find the two classes described in `README.md`. Make their respective cells run to initialise the classes. + + +```python +import datetime + +"""This script gives a translation agency an overview of all its projects. +""" + +class Project: + + def __init__( + self, + title, + client, + source, + target, + words, + start, + deadline, + price, + tm, + translator = 'internal', + reviewer = 'internal', + status = 'created', + domain = ''): + """Initialises an object of the Project class, a class that represents a translation project of a translation agency. + + Args: + title (str): Title of the project (typically the title of the source document, or the overall title the agency gave the project if there's more than one document to be translated). + client (str): Client who ordered the translation. + source (str): Language of the source document(s) (document(s) to be translated). + target (str): Language of the target document(s) (translation(s)). + words (int): Word count of the source document(s). + start (str): Project's start date in ISO format (YYYY-MM-DD). + deadline (str): Project's deadline in ISO format (YYYY-MM-DD). + price (float): Total price invoiced to the client (excl. VAT). + tm (bool): Whether a translation memory is available for this project. + translator (string, optional): Translator assigned to the project. Defaults to 'internal'. + reviewer (string, optional): Reviewer assigned to the project. Defaults to 'internal'. + status (string, optional): Current project status inside the agency's workflow. Defaults to 'created'. + domain (str, optional): Overall domain to which the project belongs. Defaults to an empty string. + + Attributes: + title (str): Title of the project (typically the title of the source document, or the overall title the agency gave the project if there's more than one document to be translated). + client (str): Client who ordered the translation. + source (str): Language of the source document(s) (document(s) to be translated). + target (str): Language of the target document(s) (translation(s)). + words (int): Word count of the source document(s). + start (str): Project's start date in ISO format (YYYY-MM-DD). + deadline (str): Project's deadline in ISO format (YYYY-MM-DD). + price (float): Total price invoiced to the client (excl. VAT). + tm (bool): Whether a translation memory is available for this project. + translator (string, optional): Translator assigned to the project. + reviewer (string, optional): Reviewer assigned to the project. + status (string, optional): Current project status inside the agency's workflow. + domain (str): Overall domain to which the project belongs. + today (date): Date of the day where the script is run. + st (date): Project's start date, turned from an ISO-formatted string into a date. + dl (date): Project's deadline, turned from an ISO-formatted string into a date. + daysleft (timedelta): Time left until the project's deadline. + length (timedelta): Total time allocated to the project. + rate (float): Project's word rate. + efficiency (float): Number of words to be translated or revised per day to meet the deadline (assuming a five-day workweek). + + Raises: + TypeError: If 'title' is not a string. + TypeError: If 'client' is not a string. + TypeError: If 'source' is not a string. + TypeError: If 'target' is not a string. + TypeError: If 'words' is not an integer. + TypeError: If 'start' is not a string. + ValueError: If 'start' does not follow the pattern 4 digits-2 digits-2 digits. + TypeError: If 'deadline' is not a string. + ValueError: If 'deadline' does not follow the pattern 4 digits-2 digits-2 digits. + TypeError: If 'price' is not a float. + TypeError: If 'tm' is not a boolean. + TypeError: If 'translator' is not a string. + TypeError: If 'reviewer' is not a string. + TypeError: If 'status' is not a string. + ValueError: If 'status' is not a label in the agency workflow: created, in translation, in revision, delivered, delayed, cancel(l)ed. + TypeError: If 'domain' is not a string. + """ + if type(title) != str: + raise TypeError("The title must be a string.") + else: + self.title = title + if type(client) != str: + raise TypeError("The client name must be a string.") + else: + self.client = client + if type(source) != str: + raise TypeError("The source language must be a string.") + else: + self.source = source + if type(target) != str: + raise TypeError("The target langague must be a string.") + else: + self.target = target + if type(words) != int: + raise TypeError("The word count must be an integer.") + else: + self.words = words + if type(start) != str: + raise TypeError("The start date must be provided as a string.") + try: + self.st = datetime.date.fromisoformat(start) + except: + raise TypeError("The start date must be provided in ISO format") + else: + self.start = start + if type(deadline) != str: + raise TypeError("The deadline must be provided as a string.") + try: + self.dl = datetime.date.fromisoformat(deadline) + except: + raise TypeError("The deadline must be provided in ISO format") + else: + self.deadline = deadline + if type(price) != float: + raise TypeError("The price must be a float.") + else: + self.price = price + if type(tm) != bool: + raise TypeError("The TM availability must be a boolean.") + else: + self.tm = tm + if type(translator) != str and type(translator) != Freelancer: + raise TypeError("The translator's name must be a string or an entry in our freelancer database.") + elif translator == '': + self.translator = "internal" + else: + self.translator = translator + if type(reviewer) != str and type(reviewer) != Freelancer: + raise TypeError("The reviewer's name must be a string or an entry in our freelancer database.") + elif reviewer == '': + self.reviewer = "internal" + else: + self.reviewer = reviewer + if type(status) != str: + raise TypeError("The status must be a string.") + elif status == '': + self.status = "created" + elif not status.lower() in ["created", + "in translation", + "in revision", + "delivered", + "delayed", + "cancelled", + "canceled"]: + raise ValueError("Please pick a status from the workflow: created, in translation, in revision, delivered, delayed or cancelled.") + else: + self.status = status + if type(domain) != str: + raise TypeError("The domain must be a string.") + else: + self.domain = domain + + today = datetime.date.today() + self.daysleft = self.dl - today + self.length = self.dl - self.st + self.rate = self.price/self.words + self.efficiency = words/((5/7)*self.length.days) + + def days_left(self): + """Displays how many days remain until the project deadline. + + Args: + None + + Returns: + str: A message informing the user that the deadline has been exceeded already if the deadline is in the past. + str: A message informing the user of the number of days left until the deadline if the deadline is not in the past. + """ + if self.dl < datetime.date.today(): + return f"The deadline has been exceeded already." + else: + return f"There are {self.daysleft.days} days left until the deadline." + + def project_length(self): + """Displays the total number of days allocated to the project. + + Args: + None + + Returns: + str: The total number of days allocated to the project. + """ + return f"{self.length.days} days" + + def __str__(self): + # defines the print behaviour: returns a text providing the main information about the project + sent_1 = f"{self.title} is a translation for {self.client} from {self.source} into {self.target}." + if self.translator.lower() == "internal" and self.reviewer.lower() == "internal": + sent_2 = f"Both the translator and the reviewer are agency collaborators." + elif self.translator.lower() == "internal" and self.reviewer.lower() != "internal": + sent_2 = f"The translator is an agency collaborator and the reviewer is {self.reviewer}." + elif self.translator.lower() != "internal" and self.reviewer.lower() == "internal": + sent_2 = f"The translator is {self.translator} and the reviewer is an agency collaborator." + else: + sent_2 = f"The translator is {self.translator} and the reviewer is {self.reviewer}." + # this if-statement considers whether a domain was added + if len(self.domain) > 0: + sent_3 = f"The domain is: {self.domain}." + else: + sent_3 = "The domain is unspecified." # if no domain was added, the text mentions it + sent_4 = f"It's {self.words} words long, with a rate of {round(self.rate, 2)} € per word." #the word rate is rounded to two decimal places to avoid cumbersomely long numbers + # this if-statement considers whether the deadline is in the past + if self.dl < datetime.date.today(): + sent_5 = f"It started on {self.st} and was due on {self.dl}, so {self.length.days} days were foreseen for it. To meet the deadline, {round(self.efficiency)} words needed to be translated or revised per day." # the efficiency is rounded to units because you can't translate a fraction of a word anyway + else: + sent_5 = f"It started on {self.st} and is due on {self.dl}, so {self.length.days} days are foreseen for it, of which {self.daysleft.days} left. To meet the deadline, {round(self.efficiency)} words need to be translated or revised per day." + # this if-statement considers whether there is a translation memory for the project + sent_6 = f"There is {'a' if self.tm else 'no'} translation memory." + sent_7 = f"The project is currently {self.status}." + # print each sentence in a different line + return "\n".join([sent_1, sent_2, sent_3, sent_4, sent_5, sent_6, sent_7]) +``` + + +```python +class Freelancer(): + + def __init__(self, name, email, phone, ref, task, language): + self.name = name + self.email = email + self.phone = phone + self.ref = ref + self.task = task + self.language = language + + def function_with_kwargs(**kwargs): + if "name" in kwargs: + print(kwargs["name"]) + else: + print("no name found") + + def __str__(self): + sent_1 = f"{self.name} can be contacted via e-mail ({self.email}) or via phone ({self.phone})." + sent_2 = f"{self.name}'s reference in our database is {self.ref}." + sent_3 = f"{self.name} works as a {', '.join(self.task)} for {', '.join(self.language)}." + return "\n".join([sent_1, sent_2, sent_3]) +``` + +# Creating and using objects of the Project class + +## Manually +It is possible to manually define objects of each class, meaning that you write out each mandatory attribute (and any optional attribute you want to make deviate from the default value), for example: + + +```python +project1 = Project("The project title", "some client", "FR", "EN", 10000, "2023-06-17", "2023-06-30", 3600.00, True) +``` + +In this case, the name of the object is "project1" and no optional attribute differs from the default value. But you can also change all the optional attributes: + + +```python +project2 = Project("A second project title", "another client", "NL", "DE", 7500, "2023-06-16", "2023-06-28", 3200.00, True, "Sibylle de Woot", "Mariana Montes", "in translation", "education") +``` + +Or only some of them: + + +```python +project3 = Project("Some other project title", "new client", "EN", "NL", 12000, "2023-06-15", "2023-06-30", 3800.00, True, reviewer = "Sibylle de Woot", domain = "healthcare") +``` + +Once an object has been created, you can use the various methods to +- Print an overview of all the project information: + + +```python +print(project1) +# The printing method is the only one that doesn't require you to specify that it's specific to the Project class, you simply need the name of the object +``` + + The project title is a translation for some client from FR into EN. + Both the translator and the reviewer are agency collaborators. + The domain is unspecified. + It's 10000 words long, with a rate of 0.36 € per word. + It started on 2023-06-17 and is due on 2023-06-30, so 13 days are foreseen for it, of which 12 left. To meet the deadline, 1077 words need to be translated or revised per day. + There is a translation memory. + The project is currently created. + + + +```python +print(project2) +``` + + A second project title is a translation for another client from NL into DE. + The translator is Sibylle de Woot and the reviewer is Mariana Montes. + The domain is: education. + It's 7500 words long, with a rate of 0.43 € per word. + It started on 2023-06-16 and is due on 2023-06-28, so 12 days are foreseen for it, of which 10 left. To meet the deadline, 875 words need to be translated or revised per day. + There is a translation memory. + The project is currently in translation. + + + +```python +print(project3) +``` + + Some other project title is a translation for new client from EN into NL. + The translator is an agency collaborator and the reviewer is Sibylle de Woot. + The domain is: healthcare. + It's 12000 words long, with a rate of 0.32 € per word. + It started on 2023-06-15 and is due on 2023-06-30, so 15 days are foreseen for it, of which 12 left. To meet the deadline, 1120 words need to be translated or revised per day. + There is a translation memory. + The project is currently created. + + +- Find out how much time is left until the deadline: + + +```python +Project.days_left(project1) +# To use any other class method, you need to use the format "Class.method(object)" +``` + + + + + 'There are 12 days left until the deadline.' + + + +- Find out how many days are foreseen in total for the project: + + +```python +Project.project_length(project1) +``` + + + + + '13 days' + + + +Attributes can also be retrieved separately, with `object.attribute`, for example + + +```python +project1.title +``` + + + + + 'The project title' + + + + +```python +project1.start +``` + + + + + '2023-06-17' + + + + +```python +project1.length +``` + + + + + datetime.timedelta(days=13) + + + +As you can see, the value of the attribute "length" isn't very user-friendly, as it is a timedelta computed with the datetime module. That is the reason why the method `project_length` was created, to display the attribute in a more readable way. Another user-friendely ways to display the project length is by printing the attribute, rather than simply retrieving it: + + +```python +print(project1.length) +``` + + 13 days, 0:00:00 + + +As you can see, the project length is calculated to the second here. For now, that's not useful, but when a future update allows PMs to specify the start and end time, it will be. + +So, feeding the project information into an object of the `Project` class makes for easy information retrieval and allows the PM to generate a clear global overview of the project in full sentences. However, especially with manual information input, issues can occur when the user makes a typo, or inputs the wrong type of variable. To counter those issues, validation was integrated into the `Project` class. Any wrong variable type raises an error, and the user is asked to use the right variable type. + +Say, for example, that I forget that the price needs to be a float (i.e. a decimal number): + + +```python +project4 = Project("Yet another title", "another client", "FR", "NL", 8000, "2023-06-16", "2023-06-28", 3300, True, "Sibylle de Woot", "Mariana Montes", "in translation", "technical") +``` + + + --------------------------------------------------------------------------- + + TypeError Traceback (most recent call last) + + Cell In[15], line 1 + ----> 1 project4 = Project("Yet another title", "another client", "FR", "NL", 8000, "2023-06-16", "2023-06-28", 3300, True, "Sibylle de Woot", "Mariana Montes", "in translation", "technical") + + + Cell In[1], line 117, in Project.__init__(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain) + 115 self.deadline = deadline + 116 if type(price) != float: + --> 117 raise TypeError("The price must be a float.") + 118 else: + 119 self.price = price + + + TypeError: The price must be a float. + + +As you can see, the script informs me that the price must be a float. Now, let's say that my numbers are correct, but that I didn't write my dates in ISO format: + + +```python +project4 = Project("Yet another title", "another client", "FR", "NL", 8000, "23-06-16", "23-06-28", 3300.00, True, "Sibylle de Woot", "Mariana Montes", "in translation", "technical") +``` + + + --------------------------------------------------------------------------- + + ValueError Traceback (most recent call last) + + Cell In[1], line 103, in Project.__init__(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain) + 102 try: + --> 103 self.st = datetime.date.fromisoformat(start) + 104 except: + + + ValueError: Invalid isoformat string: '23-06-16' + + + During handling of the above exception, another exception occurred: + + + TypeError Traceback (most recent call last) + + Cell In[16], line 1 + ----> 1 project4 = Project("Yet another title", "another client", "FR", "NL", 8000, "23-06-16", "23-06-28", 3300.00, True, "Sibylle de Woot", "Mariana Montes", "in translation", "technical") + + + Cell In[1], line 105, in Project.__init__(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain) + 103 self.st = datetime.date.fromisoformat(start) + 104 except: + --> 105 raise TypeError("The start date must be provided in ISO format") + 106 else: + 107 self.start = start + + + TypeError: The start date must be provided in ISO format + + +While both dates are wrong, only the error with the start date is raised. That's because the script stops at the first detected error. However, if I only correct one and not the other, it will raise the error in the deadline: + + +```python +project4 = Project("Yet another title", "another client", "FR", "NL", 8000, "2023-06-16", "23-06-28", 3300.00, True, "Sibylle de Woot", "Mariana Montes", "in translation", "technical") +``` + + + --------------------------------------------------------------------------- + + ValueError Traceback (most recent call last) + + Cell In[1], line 111, in Project.__init__(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain) + 110 try: + --> 111 self.dl = datetime.date.fromisoformat(deadline) + 112 except: + + + ValueError: Invalid isoformat string: '23-06-28' + + + During handling of the above exception, another exception occurred: + + + TypeError Traceback (most recent call last) + + Cell In[17], line 1 + ----> 1 project4 = Project("Yet another title", "another client", "FR", "NL", 8000, "2023-06-16", "23-06-28", 3300.00, True, "Sibylle de Woot", "Mariana Montes", "in translation", "technical") + + + Cell In[1], line 113, in Project.__init__(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain) + 111 self.dl = datetime.date.fromisoformat(deadline) + 112 except: + --> 113 raise TypeError("The deadline must be provided in ISO format") + 114 else: + 115 self.deadline = deadline + + + TypeError: The deadline must be provided in ISO format + + +The way the script detects whether or not the date format is correct, is by trying to turn the string I inputted into an actual date with the datetime module. Because if that, it can also catch some errors if I provide the date in YYYY-DD-MM format, rather than YYYY-MM-DD: + + +```python +project4 = Project("Yet another title", "another client", "FR", "NL", 8000, "2023-16-06", "2023-06-28", 3300.00, True, "Sibylle de Woot", "Mariana Montes", "in translation", "technical") +``` + + + --------------------------------------------------------------------------- + + ValueError Traceback (most recent call last) + + Cell In[1], line 103, in Project.__init__(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain) + 102 try: + --> 103 self.st = datetime.date.fromisoformat(start) + 104 except: + + + ValueError: month must be in 1..12 + + + During handling of the above exception, another exception occurred: + + + TypeError Traceback (most recent call last) + + Cell In[18], line 1 + ----> 1 project4 = Project("Yet another title", "another client", "FR", "NL", 8000, "2023-16-06", "2023-06-28", 3300.00, True, "Sibylle de Woot", "Mariana Montes", "in translation", "technical") + + + Cell In[1], line 105, in Project.__init__(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain) + 103 self.st = datetime.date.fromisoformat(start) + 104 except: + --> 105 raise TypeError("The start date must be provided in ISO format") + 106 else: + 107 self.start = start + + + TypeError: The start date must be provided in ISO format + + +When trying to convert the string into a date, the script runs into a problem, as there is no sixteenth month in the year, which leads it to ask for an ISO-formatted date. This is handy, but has its limitations for dates until the twelvth of the month, in which day and month can me inverted and still yield a valid ISO-formatted date. Validation is a useful feature to weed out many mistakes, but it's no miracle solution and in the end it's up to the user to ensure that they use correct information. + +A last "special" type of validation used in the `Project` class concerns the attribute `status`. To make sure everyone is on the same page concerning project progress, only labels from the agency workflow are allowed for this attribute, namely: +- created, +- in translation, +- in revision, +- delivered, +- delayed, +- cancel(l)ed. + +If a user tries to invent their own labels, they will receive a request to use a status in the agency workflow: + + +```python +project4 = Project("Yet another title", "another client", "FR", "NL", 8000, "2023-06-16", "2023-06-28", 3300.00, True, "Sibylle de Woot", "Mariana Montes", "ongoing", "technical") +``` + + + --------------------------------------------------------------------------- + + ValueError Traceback (most recent call last) + + Cell In[19], line 1 + ----> 1 project4 = Project("Yet another title", "another client", "FR", "NL", 8000, "2023-06-16", "2023-06-28", 3300.00, True, "Sibylle de Woot", "Mariana Montes", "ongoing", "technical") + + + Cell In[1], line 147, in Project.__init__(self, title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status, domain) + 139 self.status = "created" + 140 elif not status.lower() in ["created", + 141 "in translation", + 142 "in revision", + (...) + 145 "cancelled", + 146 "canceled"]: + --> 147 raise ValueError("Please pick a status from the workflow: created, in translation, in revision, delivered, delayed or cancelled.") + 148 else: + 149 self.status = status + + + ValueError: Please pick a status from the workflow: created, in translation, in revision, delivered, delayed or cancelled. + + +A last point you may have noticed is that, while the default value when no domain is provided is an empty string, the default value for `translator` and `reviewer` is "internal" and even an empty string is turned into "internal", even though it does not appear as such in the printed text with project information (and an empty string can be turned into "an agency collaborator" just as easily as a string containing "internal"). The reason is that, if the user wants to retrieve the value of the attribute `translator`, "internal" is much clearer than an empty string, which looks like missing information and which requires more reflexion on the part of the user to understand that it's empty because the project was assigned to an agency collaborator. + + +```python +project1.translator +``` + + + + + 'internal' + + + +## Using files + +Since creating each instance of the `Project` class manually would be both cumbersome and *very* time-consuming, a way to speed up the process is to export the project information to a file and use that file to feed the information into the `Project` class. In this example, all the project information was exported as a list of dictionaries (one dictionary per project) in the json-file called `projects_db.json`. Bear in mind that, for the system to find the file, it needs to be in the folder you're running the script from (otherwise you need to use the full path to the file, but we'll not go into that here). + +If all you want is an overview of all the projects printed as text, you can go through the dictionaries one by one and print their information, without actually creating `Project` objects: + + +```python +import json # we'll need the json module to use the file +# store the file's content in a variable: +projects = 'projects_db.json' # assign filename to a string variable +with open(projects, encoding = 'utf-8') as f: + # open file and use json to parse it + projects = json.load(f) # projects is now a list of dictionaries. + +# go through each of the items in the list +for project in projects: + # create a Project instance with title, client, source, target, words, start, deadline, price, tm, translator, reviewer, status and domain + my_project = Project(project['title'], project['client'], project['source'], project['target'], project['words'], project['start'], project['deadline'], project['price'], project['tm'], project['translator'], project['reviewer'], project['status'], project['domain']) + # print the project information + print(my_project) + + # print a separating line between translations + print('----') +``` + + La polyarthrite rhumatoïde et autres rhumatismes inflammatoires is a translation for Reuma vzw from FR into NL. + Both the translator and the reviewer are agency collaborators. + The domain is: healthcare. + It's 2142 words long, with a rate of 0.33 € per word. + It started on 2020-10-02 and was due on 2020-10-15, so 13 days were foreseen for it. To meet the deadline, 231 words needed to be translated or revised per day. + There is no translation memory. + The project is currently delivered. + ---- + Handboek voor studentenvertegenwoordigers is a translation for KU Leuven from NL into EN. + The translator is sdw and the reviewer is an agency collaborator. + The domain is: education. + It's 7237 words long, with a rate of 0.37 € per word. + It started on 2023-02-21 and was due on 2023-03-07, so 14 days were foreseen for it. To meet the deadline, 724 words needed to be translated or revised per day. + There is a translation memory. + The project is currently delayed. + ---- + User Guide MFPs is a translation for UGent from EN into NL. + The translator is an agency collaborator and the reviewer is mm. + The domain is unspecified. + It's 1852 words long, with a rate of 0.4 € per word. + It started on 2023-04-12 and was due on 2023-04-15, so 3 days were foreseen for it. To meet the deadline, 864 words needed to be translated or revised per day. + There is a translation memory. + The project is currently cancelled. + ---- + Guide de Bruxelles is a translation for Foodies from NL into FR. + The translator is evdl and the reviewer is sdw. + The domain is unspecified. + It's 11500 words long, with a rate of 0.35 € per word. + It started on 2023-05-06 and is due on 2023-06-30, so 55 days are foreseen for it, of which 12 left. To meet the deadline, 293 words need to be translated or revised per day. + There is no translation memory. + The project is currently in revision. + ---- + + +If we don't only want a printed overview of all the projects, but want to be able to retrieve information from specific attributes or use the other methods, we'll need to create objects of the `Project` class. Before we come to that, however, you might have noticed that instead of a name in Firstname Lastname format, freelancers are now referred to with abbreviations. Those are their references in the agency freelancer database and they are used to make the link between the `Project` and the `Freelancer` classes. So, let's first have a look at `Freelancer`. + +# Creating and using objects of the Freelancer class + +## Manually +While the class contents (described in `README.md`) differ from `Projects`, the `Freelancer` class works the same way. Objects can be instantiated manually, for example: + + +```python +freelancer1 = Freelancer("Sibylle de Woot", "sdewoot@mail.be", "+32 486 12 34 56", "sdw", ["translator", "reviewer"], ["FR", "NL", "EN", "DE"]) +``` + +Again, you can then print a text displaying all the information: + + +```python +print(freelancer1) +``` + + Sibylle de Woot can be contacted via e-mail (sdewoot@mail.be) or via phone (+32 486 12 34 56). + Sibylle de Woot's reference in our database is sdw. + Sibylle de Woot works as a translator, reviewer for FR, NL, EN, DE. + + +Or retrieve specific information through the attributes: + + +```python +freelancer1.name +``` + + + + + 'Sibylle de Woot' + + + + +```python +freelancer1.language +``` + + + + + ['FR', 'NL', 'EN', 'DE'] + + + +One limitation of the Freelancer class is its lack of validation. As a result, I can input virtually anything in any attribute: I could mix up information (for example exchange name and phone number), input the phone number as an integer rather than a string, not use the right format (for example not use the international version of the phone number), only give the freelancer's first name... Anything goes: + + +```python +freelancer2 = Freelancer("Sibylle", 486123456, "sdewoot@email.be", "sdw", "translator & reviewer", "French, Dutch, English and German") +``` + + +```python +print(freelancer2) +``` + + Sibylle can be contacted via e-mail (486123456) or via phone (sdewoot@email.be). + Sibylle's reference in our database is sdw. + Sibylle works as a t, r, a, n, s, l, a, t, o, r, , &, , r, e, v, i, e, w, e, r for F, r, e, n, c, h, ,, , D, u, t, c, h, ,, , E, n, g, l, i, s, h, , a, n, d, , G, e, r, m, a, n. + + + +```python +freelancer2.email +``` + + + + + 486123456 + + + + +```python +freelancer2.language +``` + + + + + 'French, Dutch, English and German' + + + +As you can see, the result is useless, unstructured and completely nonsensical. Hence why validation is a priority in the coming updates. + +## Using files +Like the `Project` class, the Freelancer class can go through a list (in this example, based on the file `freelancers_db.json`) and print each freelancer's information: + + +```python +freelancers = 'freelancers_db.json' +with open(freelancers, encoding = 'utf-8') as f: + freelancers = json.load(f) + +for freelancer in freelancers: + my_freelancer = Freelancer(freelancer['name'], freelancer['email'], freelancer['phone'], freelancer['ref'], freelancer['task'], freelancer['language']) + print(my_freelancer) + + print('----') +``` + + Sibylle de Woot can be contacted via e-mail (sdewoot@email.be) or via phone (+32 485 12 34 56). + Sibylle de Woot's reference in our database is sdw. + Sibylle de Woot works as a translator, reviewer for FR, NL, EN, DE. + ---- + Mariana Montes can be contacted via e-mail (mariana.montes@company.com) or via phone (+32 487 98 76 54). + Mariana Montes's reference in our database is mm. + Mariana Montes works as a reviewer for ES, EN. + ---- + Emily van der Londen can be contacted via e-mail (evdl@translation.net) or via phone (+32 486 19 28 37). + Emily van der Londen's reference in our database is evdl. + Emily van der Londen works as a translator for NL, EN, FR. + ---- + + +But separate overviews of projects and freelancers aren't what interests us. What we're looking for, is to merge both in usable Python objects. So, let's see how that works. + +# Linking the Project and Freelancer class +For this, we need to slightly edit the `Project` class to make sure that the freelancer's name is displayed in the printed out project information, rather than the full text containing the freelancer information. We replace `{self.translator}` and `{self.reviewer}` by `{self.translator.name}` and `{self.reviewer.name}`. + + +```python +"""This script gives a translation agency an overview of all its projects. +""" + +class Project: + + def __init__( + self, + title, + client, + source, + target, + words, + start, + deadline, + price, + tm, + translator = 'internal', + reviewer = 'internal', + status = 'created', + domain = ''): + """Initialises an object of the Project class, a class that represents a translation project of a translation agency. + + Args: + title (str): Title of the project (typically the title of the source document, or the overall title the agency gave the project if there's more than one document to be translated). + client (str): Client who ordered the translation. + source (str): Language of the source document(s) (document(s) to be translated). + target (str): Language of the target document(s) (translation(s)). + words (int): Word count of the source document(s). + start (str): Project's start date in ISO format (YYYY-MM-DD). + deadline (str): Project's deadline in ISO format (YYYY-MM-DD). + price (float): Total price invoiced to the client (excl. VAT). + tm (bool): Whether a translation memory is available for this project. + translator (string, optional): Translator assigned to the project. Defaults to 'internal'. + reviewer (string, optional): Reviewer assigned to the project. Defaults to 'internal'. + status (string, optional): Current project status inside the agency's workflow. Defaults to 'created'. + domain (str, optional): Overall domain to which the project belongs. Defaults to an empty string. + + Attributes: + title (str): Title of the project (typically the title of the source document, or the overall title the agency gave the project if there's more than one document to be translated). + client (str): Client who ordered the translation. + source (str): Language of the source document(s) (document(s) to be translated). + target (str): Language of the target document(s) (translation(s)). + words (int): Word count of the source document(s). + start (str): Project's start date in ISO format (YYYY-MM-DD). + deadline (str): Project's deadline in ISO format (YYYY-MM-DD). + price (float): Total price invoiced to the client (excl. VAT). + tm (bool): Whether a translation memory is available for this project. + translator (string, optional): Translator assigned to the project. + reviewer (string, optional): Reviewer assigned to the project. + status (string, optional): Current project status inside the agency's workflow. + domain (str): Overall domain to which the project belongs. + today (date): Date of the day where the script is run. + st (date): Project's start date, turned from an ISO-formatted string into a date. + dl (date): Project's deadline, turned from an ISO-formatted string into a date. + daysleft (timedelta): Time left until the project's deadline. + length (timedelta): Total time allocated to the project. + rate (float): Project's word rate. + efficiency (float): Number of words to be translated or revised per day to meet the deadline (assuming a five-day workweek). + + Raises: + TypeError: If 'title' is not a string. + TypeError: If 'client' is not a string. + TypeError: If 'source' is not a string. + TypeError: If 'target' is not a string. + TypeError: If 'words' is not an integer. + TypeError: If 'start' is not a string. + ValueError: If 'start' does not follow the pattern 4 digits-2 digits-2 digits. + TypeError: If 'deadline' is not a string. + ValueError: If 'deadline' does not follow the pattern 4 digits-2 digits-2 digits. + TypeError: If 'price' is not a float. + TypeError: If 'tm' is not a boolean. + TypeError: If 'translator' is not a string. + TypeError: If 'reviewer' is not a string. + TypeError: If 'status' is not a string. + ValueError: If 'status' is not a label in the agency workflow: created, in translation, in revision, delivered, delayed, cancel(l)ed. + TypeError: If 'domain' is not a string. + """ + if type(title) != str: + raise TypeError("The title must be a string.") + else: + self.title = title + if type(client) != str: + raise TypeError("The client name must be a string.") + else: + self.client = client + if type(source) != str: + raise TypeError("The source language must be a string.") + else: + self.source = source + if type(target) != str: + raise TypeError("The target langague must be a string.") + else: + self.target = target + if type(words) != int: + raise TypeError("The word count must be an integer.") + else: + self.words = words + if type(start) != str: + raise TypeError("The start date must be provided as a string.") + try: + self.st = datetime.date.fromisoformat(start) + except: + raise TypeError("The start date must be provided in ISO format") + else: + self.start = start + if type(deadline) != str: + raise TypeError("The deadline must be provided as a string.") + try: + self.dl = datetime.date.fromisoformat(deadline) + except: + raise TypeError("The deadline must be provided in ISO format") + else: + self.deadline = deadline + if type(price) != float: + raise TypeError("The price must be a float.") + else: + self.price = price + if type(tm) != bool: + raise TypeError("The TM availability must be a boolean.") + else: + self.tm = tm + if type(translator) != str and type(translator) != Freelancer: + raise TypeError("The translator's name must be a string or an entry in our freelancer database.") + elif translator == '': + self.translator = "internal" + else: + self.translator = translator + if type(reviewer) != str and type(reviewer) != Freelancer: + raise TypeError("The reviewer's name must be a string or an entry in our freelancer database.") + elif reviewer == '': + self.reviewer = "internal" + else: + self.reviewer = reviewer + if type(status) != str: + raise TypeError("The status must be a string.") + elif status == '': + self.status = "created" + elif not status.lower() in ["created", + "in translation", + "in revision", + "delivered", + "delayed", + "cancelled", + "canceled"]: + raise ValueError("Please pick a status from the workflow: created, in translation, in revision, delivered, delayed or cancelled.") + else: + self.status = status + if type(domain) != str: + raise TypeError("The domain must be a string.") + else: + self.domain = domain + + today = datetime.date.today() + self.daysleft = self.dl - today + self.length = self.dl - self.st + self.rate = self.price/self.words + self.efficiency = words/((5/7)*self.length.days) + + def days_left(self): + """Displays how many days remain until the project deadline. + + Args: + None + + Returns: + str: A message informing the user that the deadline has been exceeded already if the deadline is in the past. + str: A message informing the user of the number of days left until the deadline if the deadline is not in the past. + """ + if self.dl < datetime.date.today(): + return f"The deadline has been exceeded already." + else: + return f"There are {self.daysleft.days} days left until the deadline." + + def project_length(self): + """Displays the total number of days allocated to the project. + + Args: + None + + Returns: + str: The total number of days allocated to the project. + """ + return f"{self.length.days} days" + + def __str__(self): + # defines the print behaviour: returns a text providing the main information about the project + sent_1 = f"{self.title} is a translation for {self.client} from {self.source} into {self.target}." + if self.translator == "internal" and self.reviewer == "internal": + sent_2 = f"Both the translator and the reviewer are agency collaborators." + elif self.translator == "internal" and self.reviewer != "internal": + sent_2 = f"The translator is an agency collaborator and the reviewer is {self.reviewer.name}." + elif self.translator != "internal" and self.reviewer == "internal": + sent_2 = f"The translator is {self.translator.name} and the reviewer is an agency collaborator." + else: + sent_2 = f"The translator is {self.translator.name} and the reviewer is {self.reviewer.name}." + # this if-statement considers whether a domain was added + if len(self.domain) > 0: + sent_3 = f"The domain is: {self.domain}." + else: + sent_3 = "The domain is unspecified." # if no domain was added, the text mentions it + sent_4 = f"It's {self.words} words long, with a rate of {round(self.rate, 2)} € per word." #the word rate is rounded to two decimal places to avoid cumbersomely long numbers + # this if-statement considers whether the deadline is in the past + if self.dl < datetime.date.today(): + sent_5 = f"It started on {self.st} and was due on {self.dl}, so {self.length.days} days were foreseen for it. To meet the deadline, {round(self.efficiency)} words needed to be translated or revised per day." # the efficiency is rounded to units because you can't translate a fraction of a word anyway + else: + sent_5 = f"It started on {self.st} and is due on {self.dl}, so {self.length.days} days are foreseen for it, of which {self.daysleft.days} left. To meet the deadline, {round(self.efficiency)} words need to be translated or revised per day." + # this if-statement considers whether there is a translation memory for the project + sent_6 = f"There is {'a' if self.tm else 'no'} translation memory." + sent_7 = f"The project is currently {self.status}." + # print each sentence in a different line + return "\n".join([sent_1, sent_2, sent_3, sent_4, sent_5, sent_6, sent_7]) +``` + +First, we'll retrieve the freelancers' information from the json-file and save it as a list of dictionaries. Granted, we've already done so above, but as some people may have skipped directly to this part of the tutorial, we'll do it again here. + + +```python +with open('freelancers_db.json', 'r') as f: + all_freelancers = {x['ref'] : x for x in json.load(f)} # the name of each dictionary is the value of the key "ref", so the freelancer's unique internal reference in the agency database +``` + +Then, we also read and save the project information as a dictionary. As mentioned above, the value of the "translator" and "reviewer" key are now internal references rather than names, which we can use to check whether the value of those keys (or attributes, once we instantiates objects of the `Project` class) correspond to a dictionary in our list `all_freelancers` (i.e. an entry in our freelancer database). If it is, the value of the attribute `translator` of our `Project` instance will not be a string, but an instance of the `Freelancer` class. + + +```python +with open('guide_bruxelles.json', 'r') as f2: + guide_bruxelles_dict = json.load(f2) + if 'translator' in guide_bruxelles_dict and guide_bruxelles_dict['translator'] in all_freelancers: + guide_bruxelles_dict['translator'] = Freelancer(**all_freelancers[guide_bruxelles_dict['translator']]) + if 'reviewer' in guide_bruxelles_dict and guide_bruxelles_dict['reviewer'] in all_freelancers: + guide_bruxelles_dict['reviewer'] = Freelancer(**all_freelancers[guide_bruxelles_dict['reviewer']]) + guide_bruxelles = Project(**guide_bruxelles_dict) + +with open('handboek.json', 'r') as f3: + handboek_dict = json.load(f3) + if 'translator' in handboek_dict and handboek_dict['translator'] in all_freelancers: + handboek_dict['translator'] = Freelancer(**all_freelancers[handboek_dict['translator']]) + if 'reviewer' in handboek_dict and handboek_dict['reviewer'] in all_freelancers: + handboek_dict['reviewer'] = Freelancer(**all_freelancers[handboek_dict['reviewer']]) + handboek = Project(**handboek_dict) + +with open('rhumatismes_inflammatoires.json', 'r') as f4: + rhumatismes_inflammatoires_dict = json.load(f4) + if 'translator' in rhumatismes_inflammatoires_dict and rhumatismes_inflammatoires_dict['translator'] in all_freelancers: + rhumatismes_inflammatoires_dict['translator'] = Freelancer(**all_freelancers[rhumatismes_inflammatoires_dict['translator']]) + if 'reviewer' in rhumatismes_inflammatoires_dict and rhumatismes_inflammatoires_dict['reviewer'] in all_freelancers: + rhumatismes_inflammatoires_dict['reviewer'] = Freelancer(**all_freelancers[rhumatismes_inflammatoires_dict['reviewer']]) + rhumatismes_inflammatoires = Project(**rhumatismes_inflammatoires_dict) + +with open('user_guide.json', 'r') as f5: + user_guide_dict = json.load(f5) + if 'translator' in user_guide_dict and user_guide_dict['translator'] in all_freelancers: + user_guide_dict['translator'] = Freelancer(**all_freelancers[user_guide_dict['translator']]) + if 'reviewer' in user_guide_dict and user_guide_dict['reviewer'] in all_freelancers: + user_guide_dict['reviewer'] = Freelancer(**all_freelancers[user_guide_dict['reviewer']]) + user_guide = Project(**user_guide_dict) +``` + +Once that is done, we can print both the project information: + + +```python +print(guide_bruxelles) +print('----') +print(handboek) +print('----') +print(rhumatismes_inflammatoires) +print('----') +print (user_guide) +``` + + Guide de Bruxelles is a translation for Foodies from NL into FR. + The translator is Emily van der Londen and the reviewer is Sibylle de Woot. + The domain is unspecified. + It's 11500 words long, with a rate of 0.35 € per word. + It started on 2023-05-06 and is due on 2023-06-30, so 55 days are foreseen for it, of which 12 left. To meet the deadline, 293 words need to be translated or revised per day. + There is no translation memory. + The project is currently in revision. + ---- + Handboek voor studentenvertegenwoordigers is a translation for KU Leuven from NL into EN. + The translator is Sibylle de Woot and the reviewer is an agency collaborator. + The domain is: education. + It's 7237 words long, with a rate of 0.37 € per word. + It started on 2023-02-21 and was due on 2023-03-07, so 14 days were foreseen for it. To meet the deadline, 724 words needed to be translated or revised per day. + There is a translation memory. + The project is currently delayed. + ---- + La polyarthrite rhumatoïde et autres rhumatismes inflammatoires is a translation for Reuma vzw from FR into NL. + Both the translator and the reviewer are agency collaborators. + The domain is: healthcare. + It's 2142 words long, with a rate of 0.33 € per word. + It started on 2020-10-02 and was due on 2020-10-15, so 13 days were foreseen for it. To meet the deadline, 231 words needed to be translated or revised per day. + There is no translation memory. + The project is currently delivered. + ---- + User Guide MFPs is a translation for UGent from EN into NL. + The translator is an agency collaborator and the reviewer is Mariana Montes. + The domain is unspecified. + It's 1852 words long, with a rate of 0.4 € per word. + It started on 2023-04-12 and was due on 2023-04-15, so 3 days were foreseen for it. To meet the deadline, 864 words needed to be translated or revised per day. + There is a translation memory. + The project is currently cancelled. + + +The freelancer information can then be printed separately by asking to print the value of the `translator`/`reviewer` attribute. + + +```python +print(guide_bruxelles.translator) +``` + + Emily van der Londen can be contacted via e-mail (evdl@translation.net) or via phone (+32 486 19 28 37). + Emily van der Londen's reference in our database is evdl. + Emily van der Londen works as a translator for NL, EN, FR. + + +The value of other `Project` attributes can be retrieved the usual way: + + +```python +guide_bruxelles.title +``` + + + + + 'Guide de Bruxelles' + + + + +```python +guide_bruxelles.efficiency +``` + + + + + 292.72727272727275 + + + + +```python +Project.project_length(guide_bruxelles) +``` + + + + + '55 days' + + + +And the freelancers' information can also be retrieved through the `Project` instance: + + +```python +guide_bruxelles.translator.name +``` + + + + + 'Emily van der Londen' + + + + +```python +guide_bruxelles.reviewer.email +``` + + + + + 'sdewoot@email.be' + + + + +```python +guide_bruxelles.translator.task +``` + + + + + ['translator'] + + + +# Conclusion +Hopefully, this script will make project management easier for many translation agencies. Of course, this is only version 1.0 and many updates, improvements and extra features are foreseen in the near to medium future, as you can see in the last section of `README.md`. diff --git a/projects_db.json b/projects_db.json new file mode 100644 index 0000000..763aadd --- /dev/null +++ b/projects_db.json @@ -0,0 +1 @@ +[{"title": "La polyarthrite rhumato\u00efde et autres rhumatismes inflammatoires", "client": "Reuma vzw", "source": "FR", "target": "NL", "words": 2142, "start": "2020-10-02", "deadline": "2020-10-15", "price": 715.0, "tm": false, "translator": "", "reviewer": "", "status": "delivered", "domain": "healthcare"}, {"title": "Handboek voor studentenvertegenwoordigers", "client": "KU Leuven", "source": "NL", "target": "EN", "words": 7237, "start": "2023-02-21", "deadline": "2023-03-07", "price": 2680.0, "tm": true, "translator": "sdw", "reviewer": "", "status": "delayed", "domain": "education"}, {"title": "User Guide MFPs", "client": "UGent", "source": "EN", "target": "NL", "words": 1852, "start": "2023-04-12", "deadline": "2023-04-15", "price": 740.0, "tm": true, "translator": "", "reviewer": "mm", "status": "cancelled", "domain": ""}, {"title": "Guide de Bruxelles", "client": "Foodies", "source": "NL", "target": "FR", "words": 11500, "start": "2023-05-06", "deadline": "2023-06-30", "price": 4025.0, "tm": false, "translator": "evdl", "reviewer": "sdw", "status": "in revision", "domain": ""}] \ No newline at end of file diff --git a/python_en.txt b/python_en.txt new file mode 100644 index 0000000..93484cf --- /dev/null +++ b/python_en.txt @@ -0,0 +1 @@ +Introduction to Machine Learning with Python. This module provides an introduction to the basic concepts and use of the Python programming language in support of translation. Focus lies on the main concepts that include Natural Language Processing, automation, text analysis and machine learning. \ No newline at end of file diff --git a/python_fr.txt b/python_fr.txt new file mode 100644 index 0000000..c969aa6 --- /dev/null +++ b/python_fr.txt @@ -0,0 +1 @@ +Introduction au machine learning à l’aide de Python. Ce module offre une introduction aux concepts de base et à l’utilisation du langage de programmation Python comme aide à la traduction. L’accent est mis sur le traitement du langage naturel (NLP), l’automatisation, l’analyse de texte et le machine learning. \ No newline at end of file diff --git a/rhumatismes_inflammatoires.json b/rhumatismes_inflammatoires.json new file mode 100644 index 0000000..e7f81d9 --- /dev/null +++ b/rhumatismes_inflammatoires.json @@ -0,0 +1 @@ +{"title": "La polyarthrite rhumato\u00efde et autres rhumatismes inflammatoires", "client": "Reuma vzw", "source": "FR", "target": "NL", "words": 2142, "start": "2020-10-02", "deadline": "2020-10-15", "price": 715.0, "tm": false, "translator": "", "reviewer": "", "status": "delivered", "domain": "healthcare"} \ No newline at end of file diff --git a/script.py b/script.py deleted file mode 100644 index ac747da..0000000 --- a/script.py +++ /dev/null @@ -1,13 +0,0 @@ -#! /usr/bin/python - -"""Brief description of the script. -""" -import argparse - -# write here your function and class definitions -# don't forget to properly document them with docstrings!! - -if __name__ == "__main": - # write here what happens if the code is run instead of imported - # include argparse code! - \ No newline at end of file diff --git a/tutorial.md b/tutorial.md deleted file mode 100644 index 6add32e..0000000 --- a/tutorial.md +++ /dev/null @@ -1,3 +0,0 @@ -# How to use my script - -In this notebook you will learn how to use the functions and classes defined in `script.py`. diff --git a/user_guide.json b/user_guide.json new file mode 100644 index 0000000..f8bd4ee --- /dev/null +++ b/user_guide.json @@ -0,0 +1 @@ +{"title": "User Guide MFPs", "client": "UGent", "source": "EN", "target": "NL", "words": 1852, "start": "2023-04-12", "deadline": "2023-04-15", "price": 740.0, "tm": true, "translator": "", "reviewer": "mm", "status": "cancelled", "domain": ""} \ No newline at end of file