{"id":13594118,"url":"https://github.com/lethain/systems","last_synced_at":"2026-04-03T15:00:57.794Z","repository":{"id":53849027,"uuid":"148081148","full_name":"lethain/systems","owner":"lethain","description":"systems is a set of tools for describing, running and visualizing systems diagrams.","archived":false,"fork":false,"pushed_at":"2025-05-10T02:25:46.000Z","size":189,"stargazers_count":396,"open_issues_count":8,"forks_count":22,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-12-01T21:58:04.259Z","etag":null,"topics":["system-modeling","systems-dynamics","systems-thinking"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lethain.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-10T01:12:44.000Z","updated_at":"2025-12-01T09:18:19.000Z","dependencies_parsed_at":"2022-08-23T08:01:47.385Z","dependency_job_id":null,"html_url":"https://github.com/lethain/systems","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lethain/systems","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lethain%2Fsystems","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lethain%2Fsystems/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lethain%2Fsystems/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lethain%2Fsystems/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lethain","download_url":"https://codeload.github.com/lethain/systems/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lethain%2Fsystems/sbom","scorecard":{"id":585858,"data":{"date":"2025-08-11","repo":{"name":"github.com/lethain/systems","commit":"80411c85e24a346a39cc0100c0a2ce9552f0a5cc"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 1/28 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":"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":"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":"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":"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":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"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 4 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"}}]},"last_synced_at":"2025-08-20T20:29:23.954Z","repository_id":53849027,"created_at":"2025-08-20T20:29:23.954Z","updated_at":"2025-08-20T20:29:23.954Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31359068,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T14:24:47.623Z","status":"ssl_error","status_checked_at":"2026-04-03T14:24:32.380Z","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":["system-modeling","systems-dynamics","systems-thinking"],"created_at":"2024-08-01T16:01:28.893Z","updated_at":"2026-04-03T15:00:57.485Z","avatar_url":"https://github.com/lethain.png","language":"HTML","funding_links":[],"categories":["HTML"],"sub_categories":[],"readme":"\n# Systems\n\n`systems` is a set of tools for describing, running and visualizing\n[systems diagrams](https://lethain.com/systems-thinking/).\n\n\nInstallation directions are below, and then get started by [working through the tutorial](./docs/tutorial.md)\nor reading through the [Jupyter notebook example](./notebooks/hiring.ipynb) example.\n\nFor a more in-depth look at the system syntax, please read [the syntax specification](./docs/spec.md).\n\n## Quickest start\n\nFollow the installation instructions below, then write a system definition\nsuch as:\n\n    Start(10)\n    Start  \u003e Middle @ 2\n    Middle \u003e End\n\nYou can then evaluate your system (use `--csv` for an importable format):\n\n    cat tmp.txt | systems-run -r 3\n\n            Start   Middle  End\n    0       10      0       0\n    1       8       2       0\n    2       6       3       1\n    3       4       4       2\n\nSee [the tutorial](./docs/tutorial.md) for more detailed starting information.\n\n## Running in code\n\nIt's also possible to write code to run your model, rather than rely on the command line tool.\nFor example:\n\n    from systems.parse import parse\n\n    def results_for_spec(spec, rounds):\n        model = parse(spec)\n        results = model.run(rounds=rounds)\n        return model, results\n\n    spec = \"\"\"Start(10)\n    Start  \u003e Middle @ 2\n    Middle \u003e End\"\"\"\n    \n    model, results = results_for_spec(spec, 10)\n    print(results)\n    # outputs: [{'Start': 10, 'Middle': 0, 'End': 0}, {'Start': 8, 'Middle': 2, 'End': 0}, ...]\n\nThis pattern is particularly useful when running from inside of a Jupyter Notebook,\nsuch as the examples in [`lethain/eng-strategy-models`](https://github.com/lethain/eng-strategy-models).\n\n\n## Installation\n\nTo install via PyPi:\n\n    pip install systems\n\nTo install for local development:\n\n    git clone https://github.com/lethain/systems.git\n    cd systems\n    python3 -m venv ./env\n    source ./env/bin/activate\n    python setup.py develop\n\nRun tests via:\n\n    python3 -m unittest tests/test_*.py\n\nOr run a single test via:\n\n    python3 tests/test_parse.py TestParse.test_parse_complex_formula\n\nPlease open an Github issue if you run into any problems!\n\n## Jupyter notebooks\n\nLikely the easiest way to iterate on a model is within a Jupyter notebook.\nSee an [example notebook here](./notebooks/hiring.ipynb).\nTo install, follow the installation steps above, and followed by:\n\n    # install graphviz\n    brew install graphviz\n\n    # install these additional python packages\n    pip install jupyter pandas matplotlib\n\n\n\n## Using the command line tools\n\nThere are four command line tools that you'll use when creating and debugging\nsystems/\n\n`systems-run` is used to run models:\n\n    $ cat examples/hiring.txt | systems-run -r 3\n    PhoneScreens    Onsites Offers  Hires   Employees       Departures\n    0       0               0       0       0       5               0\n    1       25              0       0       0       5               0\n    2       25              12      0       0       5               0\n    3       25              12      6       0       5               0\n\n`systems-viz` is used to visualize models into [Graphviz](https://www.graphviz.org/):\n\n    $ cat examples/hiring.txt | systems-viz\n    // Parsed\n    digraph {\n      rankdir=LR\n      0 [label=Candidates]\n      1 [label=PhoneScreens]\n      // etc, etc, some other stuff\n    }\n\nTypically you'll pipe the output of `systems-viz` into `dot`, for example\n\n    $ cat examples/hiring.txt | systems-viz | dot -Tpng -o tmp.png\n\n`systems-format` reads in a model, tokenizes it and formats the tokens\ninto properly formatted results. This is similar to `gofmt`, and could\nbe used for ensuring a consistent house formatting style for your diagrams.\n(It was primarily implemented to support generating human readable error\nmessages instead of surfacing the tokens to humans when errors arise.)\n\n    $ cat examples/hiring.txt | systems-fmt\n    [Candidates] \u003e PhoneScreens @ 25\n    PhoneScreens \u003e Onsites @ 0.5\n    # etc etc\n\n`systems-lex` generates the tokens for a given system file.\nThis is typically most useful when you're extending the lexer\nto support new types of functionality, but can also be useful\nfor other kinds of debugging:\n\n    $ cat examples/hiring.txt | systems-lex\n    ('lines',\n       [('line',\n         1,\n         [('comment', '# wrap with [] to indicate an infinite stock that')]),\n        ('line', 2, [('comment', \"# isn't included in each table\")]),\n\t('line', 3, [('comment', '# integers are implicitly steady rates')]),\n\t('line',\n\t 4,\n         [('infinite_stock', 'Candidates', ('params', [])),\n\t  ('flow_direction', '\u003e'),\n          ('stock', 'PhoneScreens', ('params', ())),\n          ('flow_delimiter', '@'),\n          ('flow', '', ('params', (('formula', [('whole', '25')]),)))]),\n\t...\n      ]\n    )\n\n\n## Error messages\n\nThe parser will do its best to give you a useful error message.\nFor example, if you're missing delimiters:\n\n    cat examples/no_delim.txt | systems-run\n    line 1 is missing delimiter '\u003e': \"[a] \u003c b @ 25\"\n\nAt worst, it will give you the line number and line that is\ncreating an issue:\n\n    cat examples/invalid_flow.txt | systems-run\n    line 1 could not be parsed: \"a \u003e b @ 0..2\"\n\n## Uploading distribution\n\nIf you are trying to install this on PyPi, the steps are roughly:\n\n    python3 -m pip install --user --upgrade pip\n    python3 -m pip install --user --upgrade wheel\n    python3 -m pip install --user --upgrade twine\n    python3 setup.py sdist bdist_wheel\n    twine upload --repository-url https://upload.pypi.org/legacy/ dist/*\n\nThat should more or less work. :)\n\n\n\n## Syntax Specification\n\nThe full the syntax specification is available in [./docs/spec.md](./docs/spec.md),\nand is replicated here to make this library easier to drive with an LLM.\n\n---\n\nThis file specifies the language used for describing systems in `systems`.\nThere are three primary kinds of objects to specify:\n\n* `stocks` hold values, and\n* `flows` transition values from one stock to another.\n* finally, `formula` are used to describe initial and maximum values for stocks,\n    and the magnitude of flows.\n\n\n## Specifying stocks\n\nStocks are specified on their own line, or implicitly in flow declarations:\n\n    MyStock\n\nThis would create a stock named `MyStock` with an initial value of zero and\na maximum value of infinity:\n\n    OtherStock(10)\n\nYou can also specify maximum values:\n\n    ThirdStock(0, 10)\n\nThis would create `ThirdStock` with an initial value of zero, and a maximum value of ten.\n\nGoing back to `OtherStock` for a moment, you can also use the special literal `inf`\nto explicitly specify its maximum value:\n\n    OtherStock(10, inf)\n\nThis is a more explicit way to specify a stock with an infinite maximum.\nGenerally it's a strange indicator if you're using the `inf` literal directly,\nand instead you'd use the special syntax for infinite flows:\n\n    [InfiniteFlow]\n\nThis `InfiniteFlow` would have initial and maximum values of infinity.\n\nWithout going too far into the details, initial and maximums can be specified using any\nlegal formula, more on formulas below:\n\n    Managers(2)\n    Engineers(Managers * 4, Managers * 8)\n\nIn many cases, though, you'll end up specifying your stocks inline in your\nflows, as opposed to doing them on their own lines, but the syntax\nis the same.\n\n## Flows\n\nFor example, this would have both `a` and `b` would initialize at zero,\nand both would have infinite maximum values, in addition there would be\na flow of one unit per round from `a` to `b` (assuming that `a` is above zero):\n\n    a \u003e b @ 1\n\nIn the above example, `a` has an initial value of zero, so it would never\ndo anything. Most working systems address that problem by starting with\nan infinite stock:\n\n    [a] \u003e  b  @ 5\n     b  \u003e [c] @ 3\n\nIn the above, `a` and `c` would be infinite, and `b` would start\nwith a value of zero. You can also solve the empty start problem\nby specifying non-zero initial values for your stocks:\n\n    a(10) \u003e b(3)  @ 5\n    b     \u003e c(12) @ 1\n    c     \u003e a\n\nIn this example, `a` is initialized at 10, `b` at 3, and `c` at 12.\nNote that you don't have to set the value at first reference. It is legal\nto initialize a value at a later definition of a stock, e.g. this is fine:\n\n    a(1) \u003e b @ 5\n    b(2) \u003e c @ 3\n    c(3) \u003e a @ 1\n\nHowever, it *is* illegal to initialize the same stock multiple times.\n\n    a(1) \u003e b(2) @ 1\n    b(3) \u003e a    @ 1\n\nThis will throw an error, because you can't initialize `b` twice with different values!\n\n## Rates, Conversions and Leaks\n\nEach line specifies two nodes and the link between them. Links are described\nfollowing the `@` character. The most common type of flow is a `rate`, which\nis a fixed transfer of values in one stock to another.\n\nFor example, moving two units per round between `a` and `b`:\n\n    # these are equivalent\n    a \u003e b @ 2\n    a \u003e b @ Rate(2)\n\nUp to two units will be transfered from `a` to `b` each round.\n\nAnother common kind of flow is the `conversion` flow, which takes\nthe entire contents of the source stock and multiplies that value\nagainst the conversion rate, adding the result to the next flow.\n\n    # these are equivalent\n    a(10) \u003e b @ 0.5\n    a(10) \u003e b @ Conversion(0.5)\n\nThe above would multiple `0.5` against `10` and move `5` units to `b`,\nwith the other `5` units being lost to the conversion rate (e.g. disappearing).\nA common example of a conversion rate would be the offer acceptance rate\nin a [hiring funnel](https://lethain.com/hiring-funnel/).\n\nThe third kind of flow is the `leak`, which combines properties of the\n`rate` and `conversion` flows. It moves a fixed percentage of the source\nflow into the destination flow, while leaving the remainder intact.\n\n    a(10) \u003e b @ Leak(0.2)\n\nConsidering the difference between the `conversion` and `leak`, if the above\nwere a `conversion`, then the value of `a` after one round would  be `0`, but if it's\na `leak`, then the value would be `8`.\n\n## Formulas\n\nAny flow value, initial value and maximum value can be a formula:\n\n    Recruiters(3)\n    Engineers(Managers * 4, Managers * 8)\n    [Candidates] \u003e Engineers @ Recruiters * 6\n    [Candidates] \u003e Managers  @ Recruiters * 3\n\nThe above system shows that `Engineers` has an initial value of `Managers * 4`,\na maximum value of `Managers * 8` and then shows that both `Engineers` and `Managers`\ngrow at multiples of the value of the `Recruiters` stock.\n\nThis is also a good example of using the `Recruiters` stock as\na variable, as it doesn't' actually change over time.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flethain%2Fsystems","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flethain%2Fsystems","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flethain%2Fsystems/lists"}