{"id":37084337,"url":"https://github.com/grandrew/taskjuggler-python","last_synced_at":"2026-01-14T10:19:01.066Z","repository":{"id":52705944,"uuid":"106134820","full_name":"grandrew/taskjuggler-python","owner":"grandrew","description":"Python interface to TaskJuggler 3.6 to import, run, export the plan","archived":false,"fork":false,"pushed_at":"2021-04-20T17:04:06.000Z","size":800,"stargazers_count":38,"open_issues_count":9,"forks_count":2,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-09-29T19:30:37.066Z","etag":null,"topics":["api-wrapper","planner","project-management","task-manager","taskjuggler","taskjuggler-python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/grandrew.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-07T22:31:09.000Z","updated_at":"2024-07-15T09:54:15.000Z","dependencies_parsed_at":"2022-08-21T19:40:17.794Z","dependency_job_id":null,"html_url":"https://github.com/grandrew/taskjuggler-python","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/grandrew/taskjuggler-python","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grandrew%2Ftaskjuggler-python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grandrew%2Ftaskjuggler-python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grandrew%2Ftaskjuggler-python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grandrew%2Ftaskjuggler-python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/grandrew","download_url":"https://codeload.github.com/grandrew/taskjuggler-python/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grandrew%2Ftaskjuggler-python/sbom","scorecard":{"id":443495,"data":{"date":"2025-08-11","repo":{"name":"github.com/grandrew/taskjuggler-python","commit":"4697ceb8a7b674cc3fbe8d1c5539ff12d198f4f2"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Code-Review","score":0,"reason":"Found 1/29 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"40 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2023-135 / GHSA-xqr8-7jwr-rhp7","Warn: Project is vulnerable to: PYSEC-2022-42991 / GHSA-v3c5-jqr6-7qm8","Warn: Project is vulnerable to: PYSEC-2024-60 / GHSA-jjg7-2v4v-x38h","Warn: Project is vulnerable to: PYSEC-2019-217 / GHSA-462w-v97r-4m45","Warn: Project is vulnerable to: GHSA-cpwx-vrp4-4pq7","Warn: Project is vulnerable to: PYSEC-2021-66 / GHSA-g3rq-g295-4j3m","Warn: Project is vulnerable to: GHSA-h5c8-rqwp-cp95","Warn: Project is vulnerable to: GHSA-h75v-3vvj-5mfj","Warn: Project is vulnerable to: GHSA-q2x7-8rv6-6q7h","Warn: Project is vulnerable to: PYSEC-2020-92 / GHSA-hj5v-574p-mj7c","Warn: Project is vulnerable to: PYSEC-2022-42969","Warn: Project is vulnerable to: PYSEC-2021-140 / GHSA-9w8r-397f-prfh","Warn: Project is vulnerable to: PYSEC-2023-117 / GHSA-mrwq-x4v8-fh7p","Warn: Project is vulnerable to: PYSEC-2021-141 / GHSA-pq64-v7f5-gqh8","Warn: Project is vulnerable to: PYSEC-2020-175 / GHSA-7fcj-pq9j-wh2r","Warn: Project is vulnerable to: PYSEC-2023-292 / GHSA-9w2p-rh8c-v9g5","Warn: Project is vulnerable to: PYSEC-2021-142 / GHSA-8q59-q68h-6hv4","Warn: Project is vulnerable to: PYSEC-2018-49 / GHSA-rprw-h62v-c2w7","Warn: Project is vulnerable to: GHSA-9hjg-9r4m-mvj7","Warn: Project is vulnerable to: GHSA-9wx4-h78v-vm56","Warn: Project is vulnerable to: PYSEC-2023-74 / GHSA-j8r2-6x86-q33q","Warn: Project is vulnerable to: PYSEC-2018-28 / GHSA-x84v-xcm2-53pg","Warn: Project is vulnerable to: GHSA-753j-mpmx-qq6g","Warn: Project is vulnerable to: GHSA-7cx3-6m66-7c5m","Warn: Project is vulnerable to: GHSA-8w49-h785-mj3c","Warn: Project is vulnerable to: PYSEC-2023-75 / GHSA-hj3f-6gcp-jg8j","Warn: Project is vulnerable to: GHSA-qppv-j76h-2rpx","Warn: Project is vulnerable to: GHSA-w235-7p84-xx57","Warn: Project is vulnerable to: GHSA-g7vv-2v7x-gj9p","Warn: Project is vulnerable to: GHSA-34jh-p97f-mpxf","Warn: Project is vulnerable to: PYSEC-2023-212 / GHSA-g4mx-q9vg-27p4","Warn: Project is vulnerable to: PYSEC-2023-207 / GHSA-gwvm-45gx-3cf8","Warn: Project is vulnerable to: PYSEC-2019-133 / GHSA-mh33-7rrq-662w","Warn: Project is vulnerable to: GHSA-pq67-6m6q-mj2v","Warn: Project is vulnerable to: PYSEC-2019-132 / GHSA-r64q-w8jr-g9qp","Warn: Project is vulnerable to: PYSEC-2023-192 / GHSA-v845-jxx5-vc9f","Warn: Project is vulnerable to: PYSEC-2020-148 / GHSA-wqvq-5m8c-6g24","Warn: Project is vulnerable to: PYSEC-2018-32 / GHSA-www2-v7xj-xrc6","Warn: Project is vulnerable to: PYSEC-2021-108","Warn: Project is vulnerable to: PYSEC-2022-43017 / GHSA-qwmp-2cf2-g9g6"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-19T06:08:09.684Z","repository_id":52705944,"created_at":"2025-08-19T06:08:09.685Z","updated_at":"2025-08-19T06:08:09.685Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28416880,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T10:18:03.274Z","status":"ssl_error","status_checked_at":"2026-01-14T10:16:11.865Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-wrapper","planner","project-management","task-manager","taskjuggler","taskjuggler-python"],"created_at":"2026-01-14T10:19:00.290Z","updated_at":"2026-01-14T10:19:01.061Z","avatar_url":"https://github.com/grandrew.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Unix: [![Unix Build Status](https://img.shields.io/travis/grandrew/taskjuggler-python/master.svg)](https://travis-ci.org/grandrew/taskjuggler-python) Windows: [![Windows Build Status](https://img.shields.io/appveyor/ci/grandrew/taskjuggler-python/master.svg)](https://ci.appveyor.com/project/grandrew/taskjuggler-python)\u003cbr\u003eMetrics: [![Coverage Status](https://img.shields.io/coveralls/grandrew/taskjuggler-python/master.svg)](https://coveralls.io/r/grandrew/taskjuggler-python) [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/grandrew/taskjuggler-python.svg)](https://scrutinizer-ci.com/g/grandrew/taskjuggler-python/?branch=master)\u003cbr\u003eUsage: [![PyPI Version](https://img.shields.io/pypi/v/taskjuggler_python.svg)](https://pypi.python.org/pypi/taskjuggler_python)\n\n![example screencast](assets/at_example.gif)\n\n# Rationale\n\nIt's **[whatever current year]** and still most of the tasks/project management tools lack support for any means of automated planning. This library helps to integrate automated planner that's been available for over a decade, with a shot of suporting different front-ends, complex scheduling strategies and potentially different planners.\n\n![example logic plan](assets/plan_logic.png)\n\nRealize your craziest time management dreams!\n\n- Current focus is on personal planning and small teams (hence no support for multiple resource yet)\n- No configuration is required to start\n- Utility airtable API example provided with advanced planning strategy (see below)\n\n# Overview\n\n`python_taskjuggler` module provides python interfaces to TaskJuggler 3 planner. It is a set of base classes that provide object abstractions that TaskJuggler internally uses. The module comes with example implementation of JSON project description parser.\n\nIt is still work in progress and currently supports:\n\n- generating taskjuggler project file\n- runnig the planner\n- importing task bookings\n- working with single default resource `\"me\"`\n\nThe package comes with an example command line utility `tjp-client` that provides automatic planning for\ntasks defined as records in [airtable](https://airtable.com) table. \nWorking with google sheets, jira, trello, todoist, smartsheet and others could be implemented the same way.\n\nThe utility allows to immediately re-schedule to reflect any changes to the plans that may arise due to new fixed appointments, dependencies, priority amendments or required efforts re-evaluation.\n\n## Command-line utility usage:\n\n```\n$ tjp-client -a airtable -k \u003cairtable_api_key\u003e -b \u003cairtable_database\u003e -t \u003ctable_name\u003e -v \u003cview_name\u003e\n```\n\n### Preparation\n\n1. Create an [airtable](https://airtable.com) database by copying [this example base](https://airtable.com/shrivEunRhLcBm79R) or create a new database with table named \"Tasks\" with the following columns (**case sensitive**): \n\n```\n\n1. id           - the integer (number) field used as task ID. \"Auto Number\" type recommended.\n2. summary      - single line text: task summary / title\n3. effort       - integer number: task effort duration measured in hours. Default value of 1 recommended\n4. priority     - single select: field with values \"CRITICAL\", \"High\", \"Low\", \"info\"\n5. preference   - integer number: optional additional number 0-99 for higher granularity of priorities\n6. depends      - single line text: with dependencies listed as id's, like: 2,3,4\n7. appointment  - date field with time: the fixed tasks or appointments that can not be moved\n8. deadline     - date: the desired deadline value. Current strategy will use it to emphasize priority if missed.\n9. booking      - date+time: this is where output will be written to. Sort your table by this column\n\nOther columns are just ignored.\n\nYou can add nice calculations to the table like time difference between deadline and calculated booking.\n\n```\n2. Create a view called `Work` with all the tasks with status \"Done\" filtered out *(it is left as an exercize for the reader to create a new column and a filter for it)*\n3. Create a calendar view with `booking` field\n4. Add some tasks and appointments. Beware not to add impossible scenarios - those are not supported yet (see console output to check)\n5. Get `API key`, `database ID`, note your table name and view name should be `Tasks` and `Work` respectively\n6. Execute in terminal (change base name and key respectively):\n\n```sh\n$ tjp-client -a airtable -k keyAnIuVcuhhkeAkc -b appA8ZtLosVV7HGXy -t Tasks -v Work\n```\n7. Enjoy the show of `taskjuggler_python` moving your tasks around!\n\nNow try changing priorities, adding appointments and re-scheduling the plan.\n\n# Setup\n\n## Requirements\n\n* Python 2.7+\n* TaskJuggler 3.6+\n\n## Installation\n\nInstall TaskJuggler with gem:\n\n```sh\n$ gem install taskjuggler\n```\n\nInstall taskjuggler_python with pip:\n\n```sh\n$ pip install taskjuggler_python\n```\n\nor directly from the source code:\n\n```sh\n$ git clone https://github.com/grandrew/taskjuggler-python.git\n$ cd taskjuggler-python\n$ python setup.py install\n```\n\n\n# Usage\n\nBasic usage concepts include:\n\n1. A `Task`, referred to as `issue` throughout the code\n    1. Task's `id` which is used to identify and map the tasks - a property of `JugglerTask` instance\n    2. Task's `effort` measured in units set as `UNIT` class attribute of `JugglerTaskEffort`\n    3. Task's dependencies\n    4. Task's `start` date (a.k.a. fixed appointment)\n    5. Task's `priority` measured as interger `0-1000` to set scheduling preference. No priority is scheduled always first.\n2. Bookings - the taskjuggler execution result written as `JugglerTask`'s property object(s)\n\nThe minimal invocation will look like:\n\n```python\n\u003e\u003e\u003e from taskjuggler_python import juggler\n\u003e\u003e\u003e jg = juggler.GenericJuggler()\n\u003e\u003e\u003e t = juggler.JugglerTask()\n\u003e\u003e\u003e t.set_property(juggler.JugglerTaskEffort(1)) # hours by default\n\u003e\u003e\u003e jg.add_task(t)\n\u003e\u003e\u003e jg.run()\n\u003e\u003e\u003e t.walk(juggler.JugglerBooking)[0].decode()\n[datetime.datetime(2017, 10, 12, 13, 0, tzinfo=\u003cUTC\u003e), datetime.datetime(2017, 10, 12, 14, 0, tzinfo=\u003cUTC\u003e)]\n```\n\n## JSON tasks loading:\n\n```sh\n$ python\n\u003e\u003e\u003e from taskjuggler_python import jsonjuggler\n\u003e\u003e\u003e my_tasks = \"\"\"[\n  {\n    \"id\": 2,\n    \"depends\": [\n      1\n    ],\n    \"allocate\": \"me\",\n    \"effort\": 1.2\n  },\n  {\n    \"id\": 1,\n    \"effort\": 3,\n    \"allocate\": \"me\",\n    \"summary\": \"test\"\n  }\n]\"\"\"\n\u003e\u003e\u003e jg = jsonjuggler.JsonJuggler(my_tasks)\n\u003e\u003e\u003e jg.run()\n\u003e\u003e\u003e jg.toJSON()\n[\n    {\n        \"allocate\": \"me\",\n        \"booking\": \"2017-10-10T11:00:00+00:00\",\n        \"depends\": [\n            1\n        ],\n        \"effort\": 1.2,\n        \"id\": 2\n    },\n    {\n        \"allocate\": \"me\",\n        \"booking\": \"2017-10-10T08:00:00+00:00\",\n        \"effort\": 3,\n        \"id\": 1,\n        \"summary\": \"test\"\n    }\n]\n```\n\n## Python interface usage example\n\nAs an example, let's create interface to automatically schedule tasks that are defined as airtable records\nusing [Airtable API wrapper](https://github.com/gtalarico/airtable-python-wrapper):\n\nWe are using the fact that airtable's API already emits nicely formatted JSON in `fields` field. \nWe only have to name the table columns with correct field names that [jsonjuggler](https://github.com/grandrew/taskjuggler-python/blob/master/taskjuggler_python/jsonjuggler.py) example wrapper expects\n\n```python\nfrom airtable import Airtable\nfrom taskjuggler_python import juggler, jsonjuggler\n\nairtable = Airtable(\"appA8ZtLosVV7HGXy\", \"Tasks\", api_key=\"keyAnIuVcuhhkeAkc\")\n\n# use DictJuggler example wrapper from jsonjuggler module, directly feed what the API emits in \"fields\"\nJUGGLER = jsonjuggler.DictJuggler([x[\"fields\"] for x in airtable.get_all(view=\"Work\")])\n\n# run taskjuggler and calculate bookings\nJUGGLER.run() \n\n# walk through all tasks objects\nfor t in JUGGLER.walk(juggler.JugglerTask): \n    airtable.update_by_field(\"id\", t.get_id(), \n        {\"booking\": t.walk(juggler.JugglerBooking)[0].decode()[0].isoformat()})\n# the last line finds first booking in this task, decodes it to datatime list and encodes to isoformat\n```\n\nAfter executing this code you should have time assigned to all of your tasks, none of them overlapping,\nrespecting dependencies, taking into account default time shifts, appointments and no overwork allowed.\n\n## Advanced booking strategies example\n\nImagine that you want your older tasks to increase their percieved priority so that every task with \nany priority level gets a chance to be scheduled in the foreseeable future:\n\n```python\n# recalculate JSON issue priorities based on deadlines\n\nfor rec in json_issues:\n    if \"priority\" in rec and \"deadline\" in rec and not rec[\"priority\"] \u003e= 300:\n            \n            diff_days = (datetime.datetime.now() - dateutil.parser.parse(rec[\"deadline\"])).days\n            \n            if diff_days \u003c 0: diff_days = 0                 # deny lowering priority\n            rec[\"priority\"] += diff_days * 3                # after 30 days priority is up by 90\n            if rec[\"priority\"] \u003e= 250:                      # limit maximum percieved priority\n                rec[\"priority\"] = 250                       #    \u003e 300 is critical in our strategy\n```\n\nYou can find the fully working example [here](https://github.com/grandrew/taskjuggler-python/blob/master/taskjuggler_python/tjpy_client.py).\n\n## Writing your own interface\n\nSee code for more examples of how to use the interfaces.\n\n# TODO\n\n- **documentation!!**\n\n## TaskJuggler support\n\n- general error reporting support (capture stderr and decode id's)\n- emit warnings if e.g. unable to start appointed event due to slipped schedule\n- working hours, shifts\n- exporting of tjp file; generating reports, gantt charts, etc.\n- *deadline (date) - is a check that the task is not scheduled after this date [not in planner - this is a check and can not be enforced]*\n- task grouping\n- limits dailymax, etc.\n- fixed stat time/end time (ALAP/ASAP strats)\n    - period for appointments\n- non-splittable tasks (`X = effort; limits { maximum Xh }` ??), split punishing\n- extensive timezone support\n- mark tasks as done / decouple depends\n\n## General enhancements\n\n- Enable pylint with configuration that allows the check to pass (pylint.ini)\n- Loading scheduling results\n    - export back to json\n- Make ID management transparent in the API\n- Extensive testing including safe strings checks\n- TaskJuggler task identifier full path, subtask and validation\n- Bookings and timesheets support\n- Monte-carlo simulations\n- Provide JSONEncoder and JSONDecoder interfaces for jsonjuggler module\n    - https://stackoverflow.com/questions/3768895/how-to-make-a-class-json-serializable \n- Data collection, analytics and prediction (e.g. average task error)\n    - Store bookings and do automatic progress analytics\n- Different backend support (e.g. OptaPlanner/Drools; rename to `python-planner` package?)\n    - produce multivariare pareto-optimal solutions\n    - export to MiniZinc / FlatZinc for generic CP solvers\n    - GPU-accelerated CP solvers?\n    - QCL (Quantum Computation Language) export\n\n## Thoughts\n\n- Use logging to predict average performance per day\n- Create a universal API middleware with human interface to help with life planning and performance measurements\n    - Solve more advanded problems \n        - like \"allocate some time during the day to low-priority tasks if no critical-priority task exist\"\n        - or \"adjust (increase) priority of low-priority tasks according to their age\"","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgrandrew%2Ftaskjuggler-python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgrandrew%2Ftaskjuggler-python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgrandrew%2Ftaskjuggler-python/lists"}