├── .gitignore
├── README.md
├── code-generator
├── README.md
├── extensions
│ ├── style.css
│ └── templates.umd.js
├── main.py
├── prompts.py
├── pyproject.toml
├── static
│ ├── README.md
│ ├── custom.css
│ ├── custom.js
│ └── favicon.png
└── ui.json
├── finance-content-checker
├── .wf
│ ├── components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl
│ ├── components-root.jsonl
│ ├── components-workflows_root.jsonl
│ └── metadata.json
├── README.md
├── main.py
├── prompts.py
├── pyproject.toml
├── static
│ ├── README.md
│ ├── Writer_Logo_black.svg
│ ├── custom.css
│ └── favicon.png
└── utils.py
├── finance-dashboard
├── .env.example
├── .wf
│ ├── components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl
│ ├── components-root.jsonl
│ ├── components-workflows_root.jsonl
│ └── metadata.json
├── README.md
├── charts.py
├── daily_APPLE.csv
├── daily_IBM.csv
├── earnings-data.json
├── main.py
├── prompts.py
├── pyproject.toml
├── static
│ ├── README.md
│ ├── custom.css
│ └── favicon.png
└── stock_data.py
├── financial-tools-chat
├── .env.example
├── .wf
│ ├── components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl
│ ├── components-root.jsonl
│ ├── components-workflows_root.jsonl
│ └── metadata.json
├── README.md
├── chat_tools.py
├── earnings-data.json
├── earnings-prompt.txt
├── main.py
├── prompts.py
├── pyproject.toml
└── static
│ ├── README.md
│ ├── custom.css
│ ├── custom.js
│ └── favicon.png
├── hacker-news-social-listener
├── .env.example
├── .gitignore
├── .wf
│ ├── components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl
│ ├── components-root.jsonl
│ ├── components-workflows_root.jsonl
│ └── metadata.json
├── README.md
├── main.py
├── prompts.py
├── pyproject.toml
└── static
│ ├── README.md
│ ├── custom.css
│ └── favicon.png
├── hf-spaces-starter
├── Dockerfile
├── README.md
└── images
│ ├── finance-dashboard-hf.png
│ └── new_space.png
├── localized-promo-dashboard
├── README.md
├── data
│ ├── default_segments.csv
│ └── segments.csv
├── main.py
├── prompts.py
├── pyproject.toml
├── static
│ ├── README.md
│ └── favicon.png
└── ui.json
├── palmyra-comparison
├── .env.example
├── .wf
│ ├── components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl
│ ├── components-root.jsonl
│ ├── components-workflows_root.jsonl
│ ├── components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl
│ └── metadata.json
├── README.md
├── main.py
├── pyproject.toml
└── static
│ ├── README.md
│ ├── custom.css
│ └── favicon.png
├── patient-portal
├── README.md
├── main.py
├── prompts.py
├── pyproject.toml
├── static
│ ├── README.md
│ └── favicon.png
└── ui.json
├── pd-embedded-chat
├── README.md
├── main.py
├── pyproject.toml
├── static
│ ├── README.md
│ ├── beverage-descriptions.csv
│ ├── candy-descriptions.csv
│ └── favicon.png
└── ui.json
├── pdp-generator
├── README.md
├── data
│ ├── output-html.html
│ └── running_shoes.xlsx
├── html_template.py
├── main.py
├── prompts.py
├── pyproject.toml
├── static
│ ├── README.md
│ ├── custom.css
│ ├── favicon.png
│ └── writer_logo.png
└── ui.json
├── portfolio-rebalance
├── README.md
├── example_data
│ ├── portfolio_data.pdf
│ └── portfolio_data.xlsx
├── main.py
├── prompts.py
├── pyproject.toml
├── static
│ ├── README.md
│ ├── favicon.png
│ └── writer_logo.png
└── ui.json
├── prescribing-info-app
├── .env.example
├── .wf
│ ├── components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl
│ ├── components-page-1-181cq1mpgw34i8dl.jsonl
│ ├── components-root.jsonl
│ ├── components-workflows_root.jsonl
│ ├── components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl
│ └── metadata.json
├── README.md
├── main.py
├── prompts.py
├── pyproject.toml
├── static
│ ├── README.md
│ ├── custom.css
│ └── favicon.png
└── utils.py
└── release-notes-generator
├── README.md
├── end
├── README.md
├── data
│ ├── output-html.html
│ └── test-data.csv
├── html_template.py
├── main.py
├── prompts.py
├── pyproject.toml
├── sample-input
│ └── test-data.csv
├── static
│ ├── README.md
│ ├── Writer_Logo_black.svg
│ ├── custom.css
│ └── favicon.png
└── ui.json
└── start
├── README.md
├── data
└── test-data.csv
├── html_template.py
├── main.py
├── prompts.py
├── pyproject.toml
├── sample-input
└── test-data.csv
├── static
├── README.md
├── Writer_Logo_black.svg
├── custom.css
└── favicon.png
└── ui.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Python
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # Poetry
7 | .venv/
8 | poetry.lock
9 |
10 | # macOS
11 | .DS_Store
12 |
13 | # IDEs and editors
14 | .idea/
15 | .vscode/
16 | *.swp
17 | *.swo
18 |
19 | # Distribution / packaging
20 | dist/
21 | build/
22 | *.egg-info/
23 | *.egg
24 |
25 | # Unit test / coverage reports
26 | htmlcov/
27 | .coverage
28 | .pytest_cache/
29 |
30 | # Environments
31 | .env
32 | .venv
33 | env/
34 | venv/
35 |
36 | # Logs
37 | *.log
38 |
39 | # Miscellaneous
40 | *.bak
41 | *.tmp
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Writer Framework tutorials
2 | This is a repo for Writer Frameowrk tutorial and sample applications. When applicable, an application will contain a `start` and `end` folder.
3 |
4 | ## What is Writer Framework?
5 |
6 | Writer Framework is an open-source framework for creating AI applications. Build user interfaces using a visual editor; write the backend code in Python.
7 |
8 | Writer Framework is fast and flexible with a clean, easily-testable syntax. It provides separation of concerns between UI and business logic, enabling more complex applications.
9 |
10 | ## Installation and Quickstart
11 |
12 | Getting started with Writer Framework is easy. It works on Linux, Mac and Windows.
13 |
14 | ```sh
15 | pip install writer
16 | ```
17 |
18 | We recommend using a virtual environment.
19 |
20 | ## Documentation
21 |
22 | Full documentation, including how to use Writer's AI module and deployment options, is available at [Writer](https://dev.writer.com/framework?utm_source=github&utm_medium=readme&utm_campaign=framework).
23 |
24 | ## About Writer
25 |
26 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework).
27 |
--------------------------------------------------------------------------------
/code-generator/README.md:
--------------------------------------------------------------------------------
1 | # Code generator
2 | Create React components in TypeScript based on user input using the [Writer Framework](https://dev.writer.com/framework).
3 |
4 | ## How to use
5 |
6 | 1. Input your app idea in the text input field.
7 | 2. The app will generate a React component based on your request.
8 | 3. The generated component will be displayed in the [CodeSandbox](https://codesandbox.io/).
9 | 4. You can modify the generated code as needed or click on the "Open Sandbox" button to open the code in CodeSandbox.
10 |
11 | ## Running the application
12 | First, install Writer Frameowrk:
13 |
14 | ```sh
15 | pip install writer
16 | ```
17 |
18 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create an API key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`:
19 |
20 | ```sh
21 | export WRITER_API_KEY=your-api-key
22 | ```
23 |
24 | Then, navigate to this folder and run:
25 |
26 | ```sh
27 | writer edit .
28 | ```
29 |
30 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction).
31 |
32 | ## About Writer
33 |
34 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework).
--------------------------------------------------------------------------------
/code-generator/extensions/style.css:
--------------------------------------------------------------------------------
1 | .CodeViewer[data-v-6b847114]{height:70vh}
2 |
--------------------------------------------------------------------------------
/code-generator/main.py:
--------------------------------------------------------------------------------
1 | import writer as wf
2 | from prompts import prompt
3 | from writer.ai import Conversation
4 |
5 |
6 | def generate_app_code(state, payload):
7 | state["conversation"] += {"role": "user", "content": payload}
8 |
9 | state["app_code"] = ""
10 |
11 | for chunk in state["conversation"].stream_complete():
12 | state["app_code"] += chunk["content"]
13 | clear_code_from_wrapping(state)
14 | state.call_frontend_function("scripts", "scrollToLastLine", [])
15 |
16 | state["conversation"] += {"role": "assistant", "content": state["app_code"]}
17 |
18 |
19 | def clear_code_from_wrapping(state):
20 | mapping = {"typescript": "", "javascript": "", "tsx": "", "jsx": "", "```": ""}
21 | for k, v in mapping.items():
22 | state["app_code"] = state["app_code"].replace(k, v)
23 |
24 |
25 | initial_state = wf.init_state(
26 | {
27 | "my_app": {"title": "Code generator"},
28 | "app_code": """
29 | import React from 'react';
30 |
31 | function App() {
32 | return (
33 |
34 |
Hello World!
35 |
36 | );
37 | }
38 |
39 | export default App;
40 | """,
41 | "conversation": Conversation(
42 | prompt_or_history=[{"role": "system", "content": prompt}],
43 | config={"model": "palmyra-x-004"},
44 | ),
45 | }
46 | )
47 |
48 | initial_state.import_stylesheet("style", "/static/custom.css?6")
49 | initial_state.import_frontend_module("scripts", "/static/custom.js?1")
50 |
--------------------------------------------------------------------------------
/code-generator/prompts.py:
--------------------------------------------------------------------------------
1 | prompt = """
2 | # CONTEXT #
3 | You are an expert front-end React developer who is fantastic at UI/UX design.
4 | You create expertly written React components that are both visually appealing and highly functional.
5 | You always think carefully about the code you write to ensure it is clean, efficient, and easy to maintain.
6 |
7 | # INSTRUCTIONS #
8 | Create an expertly written React component for whatever the user asks for.
9 |
10 | # ADDITIONAL GUIDELINES #
11 | - Ensure the component can be run in a standalone environment using a default export.
12 | - Use TypeScript for the component.
13 | - You may modify previous code if you are asked to do so.
14 | - Ensure the component is interactive and functional by creating state and handling events.
15 | - If you use any React imports such as useState or useEffect, ensure they are imported correctly.
16 | - For styling the component use appropriate spacing and padding and
17 | a consistent and visually appealing color scheme. Consider to use only inline styles.
18 | - Do not use any uninstalled libraries and packages.
19 | E.g. if you don't have "axios" installed do not import and try to use it.
20 | - If the user asks for a dashboard, graph, or chart, use the recharts library for this purpose.
21 |
22 | # USER REQUEST SAMPLE#
23 | Here is the user request sample:
24 | "Create a dashboard that shows sales data with sample data."
25 |
26 | # RESPONSE FORMAT #
27 | Write the code for the React component. Do not use a code-block for the response, i.e.,
28 | do not enclose the code in backticks (```). Do not preamble or provide anything else.
29 | """
30 |
--------------------------------------------------------------------------------
/code-generator/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "writer-framework-default"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Your Name "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.11"
10 | writer = "^0.7.4"
11 | flake8 = "^7.1.1"
12 | isort = "^5.13.2"
13 | black = "^24.8.0"
14 | pre-commit = "^3.8.0"
15 |
16 |
17 | [build-system]
18 | requires = ["poetry-core"]
19 | build-backend = "poetry.core.masonry.api"
20 |
--------------------------------------------------------------------------------
/code-generator/static/README.md:
--------------------------------------------------------------------------------
1 | # Serving static files
2 |
3 | You can use this folder to store files which will be served statically in the "/static" route.
4 |
5 | This is useful to store images and other files which will be served directly to the user of your application.
6 |
7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg".
8 | You can use this relative route as the source in an Image component.
9 |
--------------------------------------------------------------------------------
/code-generator/static/custom.css:
--------------------------------------------------------------------------------
1 | .ideas-input{
2 | max-width: 100% !important;
3 | }
4 |
--------------------------------------------------------------------------------
/code-generator/static/custom.js:
--------------------------------------------------------------------------------
1 | export function scrollToLastLine(){
2 | const lines = document.getElementsByClassName("cm-line")
3 | if(lines.length > 0)
4 | lines[lines.length - 1].scrollIntoView();
5 | }
6 |
--------------------------------------------------------------------------------
/code-generator/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/code-generator/static/favicon.png
--------------------------------------------------------------------------------
/code-generator/ui.json:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": {
3 | "writer_version": "0.7.4"
4 | },
5 | "components": {
6 | "root": {
7 | "id": "root",
8 | "type": "root",
9 | "content": {
10 | "appName": "Code generator"
11 | },
12 | "isCodeManaged": false,
13 | "position": 0,
14 | "handlers": {}
15 | },
16 | "c0f99a9e-5004-4e75-a6c6-36f17490b134": {
17 | "id": "c0f99a9e-5004-4e75-a6c6-36f17490b134",
18 | "type": "page",
19 | "content": {
20 | "pageMode": "compact"
21 | },
22 | "isCodeManaged": false,
23 | "position": 0,
24 | "parentId": "root",
25 | "handlers": {}
26 | },
27 | "bebc5fe9-63a7-46a7-b0fa-62303555cfaf": {
28 | "id": "bebc5fe9-63a7-46a7-b0fa-62303555cfaf",
29 | "type": "header",
30 | "content": {
31 | "text": "Code generator"
32 | },
33 | "isCodeManaged": false,
34 | "position": 0,
35 | "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134",
36 | "handlers": {}
37 | },
38 | "wr3d3ksx98oph32v": {
39 | "id": "wr3d3ksx98oph32v",
40 | "type": "custom_codeviewer",
41 | "content": {
42 | "code": "@{app_code}"
43 | },
44 | "isCodeManaged": false,
45 | "position": 2,
46 | "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134",
47 | "handlers": {}
48 | },
49 | "4ymyn6cupdlc2gp3": {
50 | "id": "4ymyn6cupdlc2gp3",
51 | "type": "textinput",
52 | "content": {
53 | "label": "",
54 | "placeholder": "Input app ideas...",
55 | "cssClasses": "ideas-input"
56 | },
57 | "isCodeManaged": false,
58 | "position": 1,
59 | "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134",
60 | "handlers": {
61 | "wf-change-finish": "generate_app_code"
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/finance-content-checker/.wf/components-root.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "root", "type": "root", "content": {"appName": "Finance Copy Compliance Checker"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
--------------------------------------------------------------------------------
/finance-content-checker/.wf/components-workflows_root.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
--------------------------------------------------------------------------------
/finance-content-checker/.wf/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "writer_version": "0.8.2"
3 | }
--------------------------------------------------------------------------------
/finance-content-checker/README.md:
--------------------------------------------------------------------------------
1 | # Finance copy compliance checker
2 | This application allows the user to paste in text and analyze it based on compliance guidelines.
3 |
4 | Note: the prompts in `prompts.py` are tightly coupled to the formatting of the sample data. If you want to use a different format, be sure to update the prompts accordingy.
5 |
6 | ## Running the application
7 | First, install Writer Frameowrk:
8 |
9 | ```sh
10 | pip install writer
11 | ```
12 |
13 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create an API key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`:
14 |
15 | ```sh
16 | export WRITER_API_KEY=your-api-key
17 | ```
18 |
19 | Then, navigate to this folder and run:
20 |
21 | ```sh
22 | writer edit .
23 | ```
24 |
25 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction).
26 |
27 | ## About Writer
28 |
29 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework).
--------------------------------------------------------------------------------
/finance-content-checker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "writer-framework-default"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Your Name "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.10.0"
10 | writer = "0.8.2"
11 | python-dotenv = "^1.0.1"
12 |
13 |
14 | [build-system]
15 | requires = ["poetry-core"]
16 | build-backend = "poetry.core.masonry.api"
17 |
--------------------------------------------------------------------------------
/finance-content-checker/static/README.md:
--------------------------------------------------------------------------------
1 | # Serving static files
2 |
3 | You can use this folder to store files which will be served statically in the "/static" route.
4 |
5 | This is useful to store images and other files which will be served directly to the user of your application.
6 |
7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg".
8 | You can use this relative route as the source in an Image component.
9 |
--------------------------------------------------------------------------------
/finance-content-checker/static/Writer_Logo_black.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/finance-content-checker/static/custom.css:
--------------------------------------------------------------------------------
1 |
2 | .description{
3 | padding-bottom: 1em;
4 | padding-top: 1em;
5 | margin-left: 3em;
6 | color:darkgray
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/finance-content-checker/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/finance-content-checker/static/favicon.png
--------------------------------------------------------------------------------
/finance-content-checker/utils.py:
--------------------------------------------------------------------------------
1 | import json
2 | import re
3 | from prompts import _we_pronoun_prompt, _outcome_language_prompt, _hyperbole_prompt
4 |
5 | # Map data dictionary into output boxes
6 | def set_page_output(data,state,rule):
7 | state["output"][rule]["text"]= data["text"]
8 | state["output"][rule]["description"] = data["description"]
9 | state["output"][rule]["guideline"] = data["guideline"]
10 | state["output"][rule]["suggestion"] = data["suggestion"]
11 |
12 |
13 | def highlight_string(state,rule):
14 | state["html_content"] = state["content"].replace('\n\n', '
')
15 | html_paragraph = state["html_content"]
16 | search_string = state["output"][rule]["text"]
17 | highlighted_html = highlight_string_in_html(html_paragraph, search_string)
18 | state["html_content"] = highlighted_html
19 |
20 |
21 | def convert_string_to_dict_list(input_string):
22 | try:
23 | # # Properly format the string to make it a valid JSON array
24 | input_string = input_string.replace("```json", "").replace("```", "")
25 | # Convert the formatted string into a list of dictionaries
26 | data_list = json.loads(input_string)
27 | return data_list
28 | except json.JSONDecodeError as e:
29 | print(e)
30 | return []
31 |
32 |
33 | def highlight_string_in_html(html_paragraph, search_string):
34 | # Escape special characters in the search string to safely use it in a regular expression
35 | escaped_search_string = re.escape(search_string)
36 |
37 | # Define the replacement pattern, wrapping the search string in a span with a light orange background
38 | replacement_pattern = f'{search_string}'
39 |
40 | # Use re.sub() to replace occurrences of the search string with the replacement pattern
41 | highlighted_html = re.sub(escaped_search_string, replacement_pattern, html_paragraph, flags=re.IGNORECASE)
42 |
43 | return highlighted_html
44 |
--------------------------------------------------------------------------------
/finance-dashboard/.env.example:
--------------------------------------------------------------------------------
1 | WRITER_API_KEY=your-api-key
2 |
--------------------------------------------------------------------------------
/finance-dashboard/.wf/components-root.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "root", "type": "root", "content": {"appName": "Finance Dashboard"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
--------------------------------------------------------------------------------
/finance-dashboard/.wf/components-workflows_root.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
--------------------------------------------------------------------------------
/finance-dashboard/.wf/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "writer_version": "0.8.3rc2"
3 | }
--------------------------------------------------------------------------------
/finance-dashboard/README.md:
--------------------------------------------------------------------------------
1 | # Financial research dashboard
2 | This application pulls in realtime stock data and analyzes it with Palmyra.
3 |
4 | ## Running the application
5 | First, install Writer Frameowrk:
6 |
7 | ```sh
8 | pip install writer
9 | ```
10 |
11 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create a Framework API key. To pass your API key to the Writer Framework, you'll need to make a copy of `.env.example`, remove the `.example` extension, and update `WRITER_API_KEY`:
12 |
13 | ```
14 | WRITER_API_KEY=your-api-key
15 | ```
16 |
17 | Then, navigate to this folder and run:
18 |
19 | ```sh
20 | writer edit .
21 | ```
22 |
23 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction).
24 |
25 | ## About Writer
26 |
27 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework).
28 |
--------------------------------------------------------------------------------
/finance-dashboard/charts.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import plotly.express as px
3 | import plotly.graph_objects as go
4 | from plotly.subplots import make_subplots
5 |
6 |
7 | def handle_click(state, context: dict, ui):
8 | # Resetting the classes for active button
9 | if state["active_button"]:
10 | active_button = ui.find(state["active_button"])
11 | active_button.content["cssClasses"] = ""
12 |
13 | event_target = context["target"]
14 | button = ui.find(event_target)
15 |
16 | # Storing the clicked button ID in state
17 | state["active_button"] = event_target
18 |
19 | button_text = button.content["text"]
20 | _handle_time_period(state, button_text)
21 | button.content["cssClasses"] = "button-click"
22 |
23 | button_max = ui.find("e13teponreio9yyz")
24 | button_max.content["cssClasses"] = ""
25 |
26 | ui.component_tree.updated = True
27 |
28 |
29 | def _handle_time_period(state, period):
30 | state["main_df_subset"] = state["main_df"]
31 | if period == "5D":
32 | state["main_df_subset"] = state["main_df_subset"][:5]
33 | elif period == "1M":
34 | state["main_df_subset"] = state["main_df_subset"][:30]
35 | elif period == "3M":
36 | state["main_df_subset"] = state["main_df_subset"][:90]
37 | elif period == "1Y":
38 | state["main_df_subset"] = state["main_df_subset"][:360]
39 | elif period == "5Y":
40 | state["main_df_subset"] = state["main_df_subset"][:1800]
41 | elif period == "Max":
42 | # No need to slice, already has the full data
43 | pass
44 | update_scatter_chart(state)
45 |
46 |
47 | def update_scatter_chart(state):
48 | fig = px.line(state["main_df_subset"], x="Date", y="Open", height=400)
49 |
50 | df1 = state["main_df_subset"]
51 | df2 = state["another_df"]
52 | df2 = df2.head(len(df1))
53 |
54 | # Add a new column to each dataframe to identify the source
55 | df1["Source"] = "Main_DF"
56 | df2["Source"] = "Another_DF"
57 |
58 | # Concatenate the dataframes
59 | combined_df = pd.concat([df1, df2])
60 | state["main_df_subset"] = combined_df
61 |
62 | # Plot the lines
63 | fig = make_subplots(specs=[[{"secondary_y": True}]])
64 |
65 | # Add traces for the primary y-axis (Main_DF)
66 | fig.add_trace(
67 | go.Scatter(x=df1["Date"], y=df1["Open"], name=state["symbol"], mode="lines"),
68 | secondary_y=False,
69 | )
70 |
71 | # Add traces for the secondary y-axis (Another_DF)
72 | fig.add_trace(
73 | go.Scatter(x=df2["Date"], y=df2["Open"], name="S&P 500", mode="lines"),
74 | secondary_y=True,
75 | )
76 |
77 | # Set axis titles
78 | fig.update_yaxes(title_text=f"{state['symbol']} Stock Price", secondary_y=False)
79 | fig.update_yaxes(title_text="S&P 500", secondary_y=True)
80 |
81 | # Update layout
82 | fig.update_layout(
83 | height=550,
84 | title_text=f"{state['symbol']} Stock vs the S&P 500",
85 | title_x=0.5,
86 | title_y=0.9,
87 | legend=dict(
88 | orientation="h",
89 | yanchor="top",
90 | y=-0.2, # Adjust this value as needed
91 | xanchor="center",
92 | x=0.5,
93 | ),
94 | )
95 |
96 | state["scatter_chart"] = fig
97 |
98 |
99 | def update_bar_graph(state):
100 | fig = px.line(state["main_df_subset"], x="Date", y="Open", height=400)
101 |
102 | df = state["income_statement_df"]
103 | selected_metrics = ["Total Revenue", "Net Income", "Operating Income"]
104 | df_filtered = df.loc[selected_metrics]
105 |
106 | # Transpose the DataFrame for easier plotting
107 | df_transposed = df_filtered.transpose().reset_index()
108 | df_transposed = df_transposed.melt(
109 | id_vars=["index"], var_name="Metric", value_name="Value"
110 | )
111 |
112 | # Create the bar graph using Plotly Express
113 | fig = px.bar(
114 | df_transposed,
115 | x="index",
116 | y="Value",
117 | color="Metric",
118 | barmode="group",
119 | labels={"index": "", "Value": ""},
120 | title="Summary of Quarterly Income Statement",
121 | )
122 |
123 | fig.update_layout(
124 | legend=dict(orientation="h", yanchor="top", y=-0.2, xanchor="center", x=0.5)
125 | )
126 |
127 | state["bar_graph"] = fig
128 |
--------------------------------------------------------------------------------
/finance-dashboard/daily_APPLE.csv:
--------------------------------------------------------------------------------
1 | timestamp,open,high,low,close,volume
2 | 2024-06-17,213.3700,218.9500,212.7200,216.6700,93728300
3 | 2024-06-14,213.8500,215.1700,211.3000,212.4900,70122748
4 | 2024-06-13,214.7400,216.7500,211.6000,214.2400,97862729
5 | 2024-06-12,207.3700,220.2000,206.9000,213.0700,198134293
6 | 2024-06-11,193.6500,207.1600,193.6300,207.1500,172373296
7 | 2024-06-10,196.9000,197.3000,192.1500,193.1200,97262077
8 | 2024-06-07,194.6500,196.9400,194.1400,196.8900,53103912
9 | 2024-06-06,195.6850,196.5000,194.1700,194.4800,41181753
10 | 2024-06-05,195.4000,196.9000,194.8700,195.8700,54156785
11 | 2024-06-04,194.6350,195.3200,193.0342,194.3500,47471445
12 | 2024-06-03,192.9000,194.9900,192.5200,194.0300,50080539
13 | 2024-05-31,191.4400,192.5700,189.9100,192.2500,75158277
14 | 2024-05-30,190.7600,192.1800,190.6300,191.2900,49947941
15 | 2024-05-29,189.6100,192.2470,189.5100,190.2900,53068016
16 | 2024-05-28,191.5100,193.0000,189.1000,189.9900,52280051
17 | 2024-05-24,188.8200,190.5800,188.0404,189.9800,36326975
18 | 2024-05-23,190.9800,191.0000,186.6250,186.8800,51005924
19 | 2024-05-22,192.2650,192.8231,190.2700,190.9000,34648547
20 | 2024-05-21,191.0900,192.7300,190.9201,192.3500,42309401
21 | 2024-05-20,189.3250,191.9199,189.0100,191.0400,44361275
22 | 2024-05-17,189.5100,190.8100,189.1800,189.8700,41282925
23 | 2024-05-16,190.4700,191.0950,189.6601,189.8400,52845230
24 | 2024-05-15,187.9100,190.6500,187.3700,189.7200,70399988
25 | 2024-05-14,187.5100,188.3000,186.2900,187.4300,52393619
26 | 2024-05-13,185.4350,187.1000,184.6200,186.2800,72044809
27 | 2024-05-10,184.9000,185.0900,182.1300,183.0500,50759496
28 | 2024-05-09,182.5600,184.6600,182.1100,184.5700,48982972
29 | 2024-05-08,182.8500,183.0700,181.4500,182.7400,45057087
30 | 2024-05-07,183.4500,184.9000,181.3200,182.4000,77305771
31 | 2024-05-06,182.3540,184.2000,180.4200,181.7100,78569667
32 | 2024-05-03,186.6450,187.0000,182.6600,183.3800,163224109
33 | 2024-05-02,172.5100,173.4150,170.8900,173.0300,94214915
34 | 2024-05-01,169.5800,172.7050,169.1100,169.3000,50383147
35 | 2024-04-30,173.3300,174.9900,170.0000,170.3300,65934776
36 | 2024-04-29,173.3700,176.0300,173.1000,173.5000,68169419
37 | 2024-04-26,169.8800,171.3400,169.1800,169.3000,44838354
38 | 2024-04-25,169.5250,170.6100,168.1511,169.8900,50558329
39 | 2024-04-24,166.5400,169.3000,166.2100,169.0200,48251835
40 | 2024-04-23,165.3500,167.0500,164.9200,166.9000,49537761
41 | 2024-04-22,165.5150,167.2600,164.7700,165.8400,48116443
42 | 2024-04-19,166.2100,166.4000,164.0750,165.0000,68149377
43 | 2024-04-18,168.0300,168.6400,166.5500,167.0400,43122903
44 | 2024-04-17,169.6100,170.6500,168.0000,168.0000,50901210
45 | 2024-04-16,171.7500,173.7600,168.2700,169.3800,73711235
46 | 2024-04-15,175.3600,176.6300,172.5000,172.6900,73531773
47 | 2024-04-12,174.2600,178.3600,174.2100,176.5500,101670886
48 | 2024-04-11,168.3400,175.4600,168.1600,175.0400,91070275
49 | 2024-04-10,168.8000,169.0900,167.1100,167.7800,49709336
50 | 2024-04-09,168.7000,170.0800,168.3500,169.6700,42231444
51 | 2024-04-08,169.0300,169.2000,168.2400,168.4500,37216858
52 | 2024-04-05,169.5900,170.3900,168.9500,169.5800,41975776
53 | 2024-04-04,170.2900,171.9200,168.8200,168.8200,53355055
54 | 2024-04-03,168.7900,170.6800,168.5800,169.6500,45571129
55 | 2024-04-02,169.0800,169.3400,168.2302,168.8400,49013991
56 | 2024-04-01,171.1900,171.2500,169.4750,170.0300,43772506
57 | 2024-03-28,171.7500,172.2300,170.5100,171.4800,65672690
58 | 2024-03-27,170.4100,173.6000,170.1100,173.3100,60273265
59 | 2024-03-26,170.0000,171.4200,169.5800,169.7100,57388449
60 | 2024-03-25,170.5650,171.9400,169.4500,170.8500,54288328
61 | 2024-03-22,171.7600,173.0500,170.0600,172.2800,71160138
62 | 2024-03-21,177.0500,177.4900,170.8400,171.3700,106181270
63 | 2024-03-20,175.7200,178.6700,175.0900,178.6700,53423102
64 | 2024-03-19,174.3400,176.6050,173.0300,176.0800,55215244
65 | 2024-03-18,175.5700,177.7100,173.5200,173.7200,75604184
66 | 2024-03-15,171.1700,172.6200,170.2850,172.6200,121752699
67 | 2024-03-14,172.9100,174.3078,172.0500,173.0000,72571635
68 | 2024-03-13,172.7700,173.1850,170.7600,171.1300,51948951
69 | 2024-03-12,173.1500,174.0300,171.0100,173.2300,59544927
70 | 2024-03-11,172.9400,174.3800,172.0500,172.7500,58929918
71 | 2024-03-08,169.0000,173.7000,168.9400,170.7300,76267041
72 | 2024-03-07,169.1500,170.7300,168.4900,169.0000,71765061
73 | 2024-03-06,171.0600,171.2400,168.6800,169.1200,68587707
74 | 2024-03-05,170.7600,172.0400,169.6200,170.1200,95132355
75 | 2024-03-04,176.1500,176.9000,173.7900,175.1000,81510101
76 | 2024-03-01,179.5500,180.5300,177.3800,179.6600,73563082
77 | 2024-02-29,181.2700,182.5700,179.5300,180.7500,136682597
78 | 2024-02-28,182.5100,183.1200,180.1300,181.4200,48953939
79 | 2024-02-27,181.1000,183.9225,179.5600,182.6300,54318851
80 | 2024-02-26,182.2400,182.7600,180.6500,181.1600,40867421
81 | 2024-02-23,185.0100,185.0400,182.2300,182.5200,45119677
82 | 2024-02-22,183.4800,184.9550,182.4600,184.3700,52292208
83 | 2024-02-21,181.9400,182.8888,180.6600,182.3200,41529674
84 | 2024-02-20,181.7900,182.4300,180.0000,181.5600,53665553
85 | 2024-02-16,183.4200,184.8500,181.6650,182.3100,49752465
86 | 2024-02-15,183.5500,184.4900,181.3500,183.8600,65434496
87 | 2024-02-14,185.3200,185.5300,182.4400,184.1500,54630517
88 | 2024-02-13,185.7700,186.2100,183.5128,185.0400,56529529
89 | 2024-02-12,188.4150,188.6700,186.7900,187.1500,41781934
90 | 2024-02-09,188.6500,189.9900,188.0000,188.8500,45155216
91 | 2024-02-08,189.3850,189.5350,187.3500,188.3200,40962046
92 | 2024-02-07,190.6400,191.0500,188.6100,189.4100,53438955
93 | 2024-02-06,186.8600,189.3100,186.7695,189.3000,43490759
94 | 2024-02-05,188.1500,189.2500,185.8400,187.6800,69668820
95 | 2024-02-02,179.8600,187.3300,179.2500,185.8500,102551680
96 | 2024-02-01,183.9850,186.9500,183.8200,186.8600,64885408
97 | 2024-01-31,187.0400,187.0950,184.3500,184.4000,55467803
98 | 2024-01-30,190.9400,191.8000,187.4700,188.0400,55859370
99 | 2024-01-29,192.0100,192.2000,189.5800,191.7300,47145622
100 | 2024-01-26,194.2700,194.7600,191.9400,192.4200,44594011
101 | 2024-01-25,195.2200,196.2675,193.1125,194.1700,54822126
102 |
--------------------------------------------------------------------------------
/finance-dashboard/daily_IBM.csv:
--------------------------------------------------------------------------------
1 | timestamp,open,high,low,close,volume
2 | 2024-06-17,168.7600,169.7200,167.5000,169.5000,3239815
3 | 2024-06-14,168.2900,169.4700,167.2300,169.2100,2777717
4 | 2024-06-13,169.0100,169.5900,168.3350,169.1200,3525717
5 | 2024-06-12,171.3500,172.4700,168.1010,169.0000,3522698
6 | 2024-06-11,169.9800,170.0000,166.8100,169.3200,2951251
7 | 2024-06-10,169.5500,170.7600,168.8800,170.3800,3444684
8 | 2024-06-07,168.1800,171.3050,168.0600,170.0100,3475495
9 | 2024-06-06,167.3800,168.4400,166.8000,168.2000,2207263
10 | 2024-06-05,166.4100,167.7900,165.7800,167.3800,3049377
11 | 2024-06-04,164.6000,166.4000,163.8800,165.8100,2594203
12 | 2024-06-03,166.5400,166.7800,163.5300,165.2800,2776058
13 | 2024-05-31,165.7000,166.9700,163.8400,166.8500,4905002
14 | 2024-05-30,165.5600,166.7300,164.2300,165.6300,3852963
15 | 2024-05-29,168.0000,168.6300,166.2100,167.0500,4206576
16 | 2024-05-28,170.4400,171.0850,168.6500,169.6600,2629645
17 | 2024-05-24,171.4800,172.0100,170.2100,170.8900,2587829
18 | 2024-05-23,175.3900,175.4600,170.4350,170.6700,3341335
19 | 2024-05-22,173.3900,174.9900,172.7600,173.6900,3294900
20 | 2024-05-21,169.9400,174.9700,169.9400,173.4700,6459800
21 | 2024-05-20,169.0000,170.1600,168.3800,169.9200,2726261
22 | 2024-05-17,168.9700,169.1100,167.3300,169.0300,2956387
23 | 2024-05-16,168.2600,169.6300,167.7900,168.9700,3492267
24 | 2024-05-15,167.9400,168.3500,167.3400,168.2600,4468823
25 | 2024-05-14,167.8600,168.1300,166.4800,167.3600,2600967
26 | 2024-05-13,167.5000,168.0600,166.7600,167.5600,2414859
27 | 2024-05-10,167.1300,168.0700,166.3200,167.1500,2255370
28 | 2024-05-09,167.5000,167.5500,165.8800,166.2700,4266616
29 | 2024-05-08,168.0100,170.2600,167.9000,169.9000,3522011
30 | 2024-05-07,169.0000,169.2900,167.9400,168.3800,3155260
31 | 2024-05-06,166.5000,168.6700,166.3800,168.6100,4222266
32 | 2024-05-03,165.0000,166.6100,164.9200,165.7100,3400405
33 | 2024-05-02,164.3500,164.8800,162.6200,164.6900,3829853
34 | 2024-05-01,165.6900,166.2700,164.3000,164.4300,4030960
35 | 2024-04-30,166.4900,166.7600,165.2605,166.2000,6011634
36 | 2024-04-29,167.4000,168.2200,166.2250,167.4300,5263342
37 | 2024-04-26,167.5000,167.8700,165.7300,167.1300,8983796
38 | 2024-04-25,168.2000,172.4500,165.6600,168.9100,16702150
39 | 2024-04-24,183.1700,184.2900,181.4000,184.1000,7616643
40 | 2024-04-23,182.7300,184.6800,179.0000,182.1900,5950229
41 | 2024-04-22,182.4500,183.3150,180.4500,181.9000,3076451
42 | 2024-04-19,182.4300,182.8000,180.5700,181.5800,3037990
43 | 2024-04-18,182.3500,183.4600,180.1700,181.4700,2886733
44 | 2024-04-17,184.1600,184.6700,181.7800,183.1000,3003033
45 | 2024-04-16,185.5900,185.7100,182.8600,183.7500,4473654
46 | 2024-04-15,185.5700,187.4800,180.8800,181.2500,3528140
47 | 2024-04-12,184.0000,185.1699,181.6850,182.2700,3547378
48 | 2024-04-11,186.0400,186.7950,184.5800,185.9000,2861736
49 | 2024-04-10,187.4200,187.9150,185.5200,186.0400,3081915
50 | 2024-04-09,190.5400,191.2500,186.6600,189.3100,2790673
51 | 2024-04-08,189.2400,190.2400,188.9118,189.8200,2673611
52 | 2024-04-05,188.5900,190.3200,188.0200,189.1400,2012428
53 | 2024-04-04,192.0000,193.2800,187.3400,187.9400,2924438
54 | 2024-04-03,188.6000,191.3500,188.4850,190.9000,2818910
55 | 2024-04-02,189.1400,189.8000,187.6000,188.8800,2689711
56 | 2024-04-01,190.0000,190.4600,188.5200,189.8300,2362586
57 | 2024-03-28,190.9400,191.9299,190.3400,190.9600,3742169
58 | 2024-03-27,189.6000,190.9600,188.6000,190.8000,3693305
59 | 2024-03-26,189.0200,190.0000,188.5000,188.5000,4229535
60 | 2024-03-25,190.2600,190.8200,188.7500,188.7900,3718289
61 | 2024-03-22,192.0000,192.9850,190.5100,190.8400,3988398
62 | 2024-03-21,193.0000,193.3700,190.0100,191.9000,6013561
63 | 2024-03-20,192.8700,193.9800,191.3100,193.9600,3238643
64 | 2024-03-19,191.4900,193.5800,190.2800,193.3400,5317341
65 | 2024-03-18,191.7000,193.2300,190.3200,191.6900,5410562
66 | 2024-03-15,191.9900,193.0573,190.7000,191.0700,8828184
67 | 2024-03-14,196.9500,197.7480,192.1200,193.4300,4102202
68 | 2024-03-13,197.5500,198.1000,195.3200,196.7000,3960737
69 | 2024-03-12,192.4600,199.1800,192.1500,197.7800,5862512
70 | 2024-03-11,195.0900,195.3800,190.8800,191.7300,4712688
71 | 2024-03-08,196.0600,197.7700,194.3800,195.9500,3943113
72 | 2024-03-07,197.5800,198.7300,196.1400,196.5400,4604458
73 | 2024-03-06,193.5000,198.1300,192.9600,196.1600,6945818
74 | 2024-03-05,192.0000,193.9400,190.5700,191.9500,5653641
75 | 2024-03-04,187.7600,193.8980,187.6000,193.0600,7938266
76 | 2024-03-01,185.4900,188.3800,185.1800,188.2000,4018354
77 | 2024-02-29,186.1500,186.8495,184.6900,185.0300,6458487
78 | 2024-02-28,184.6300,185.3700,183.5500,185.3000,3216345
79 | 2024-02-27,184.1600,185.1300,182.6200,184.8700,3641378
80 | 2024-02-26,185.6000,186.1250,184.0600,184.1300,4620815
81 | 2024-02-23,184.9000,186.4550,184.5700,185.7200,3433800
82 | 2024-02-22,182.4500,184.5500,181.9300,184.2100,5078398
83 | 2024-02-21,182.5600,183.0300,178.7500,179.7000,4728473
84 | 2024-02-20,187.6400,188.7700,183.0600,183.4400,4247181
85 | 2024-02-16,186.6300,188.9500,185.9452,187.6400,4842840
86 | 2024-02-15,183.6200,186.9800,183.6200,186.8700,4714301
87 | 2024-02-14,185.0000,185.0000,182.2600,183.5700,3173391
88 | 2024-02-13,184.2800,184.7700,182.3600,183.7000,4290453
89 | 2024-02-12,185.9000,186.4800,184.0300,186.1600,4724021
90 | 2024-02-09,184.4400,187.1800,183.8500,186.3400,5064641
91 | 2024-02-08,182.6300,184.5500,181.4900,184.3600,5161185
92 | 2024-02-07,183.3400,184.0200,182.6250,183.7400,4841188
93 | 2024-02-06,183.5500,184.6800,183.0400,183.4100,3338196
94 | 2024-02-05,185.5100,185.7800,183.2550,183.4200,4379602
95 | 2024-02-02,187.1000,187.3900,185.6150,185.7900,4055411
96 | 2024-02-01,183.6300,187.5100,182.7100,186.9000,4669444
97 | 2024-01-31,187.0500,187.6500,183.1400,183.6600,8876055
98 | 2024-01-30,187.7100,188.6500,186.7700,187.8700,4575058
99 | 2024-01-29,187.4600,189.4600,186.0500,187.1400,6107908
100 | 2024-01-26,191.3100,192.3896,186.1600,187.4200,9895941
101 | 2024-01-25,184.9600,196.9000,184.8300,190.4300,29596239
102 |
--------------------------------------------------------------------------------
/finance-dashboard/prompts.py:
--------------------------------------------------------------------------------
1 | # BASE PROMPTS (TAILORED TO EACH COMPANY)
2 |
3 | # base_prompts = {}
4 |
5 | stock_prompts = """
6 |
7 | Variables:
8 |
9 | {language}, {stock_name}, {words}, {stock_data}
10 |
11 | ************************
12 |
13 | Prompt:
14 | You will be acting as a stock market analyst. When I provide the stock data in the specified format
15 | between the tags, your task is to carefully review the data and provide insights,
16 | trends, and financial analysis for the stock specified in the {stock_name} variable.
17 |
18 |
19 | {stock_data}
20 |
21 |
22 | Please analyze the stock data concisely in {words} words using paragraphs, considering the following
23 | steps:
24 |
25 | 1. Identify any notable trends in the stock's price, volume, or other key metrics over the given
26 | time period. Discuss potential reasons behind these trends.
27 |
28 | 2. Compare the stock's performance and financial metrics to industry averages and key competitors.
29 | Discuss how the stock stacks up against its peers.
30 |
31 | 3. Based on your analysis, provide an overall assessment of the stock's current position and future
32 | prospects. Consider factors such as growth potential, risk level, and market sentiment.
33 |
34 | Before giving your final recommendation, please provide detailed reasoning and analysis to support
35 | your conclusions inside tags.
36 |
37 | Finally, offer a clear recommendation inside tags on whether to buy, hold, or sell
38 | {stock_name}, taking into account both the stock's current valuation and its long-term potential.
39 |
40 | Remember to base your analysis and recommendation solely on the provided stock data for
41 | {stock_name}. If there is insufficient information to draw a conclusion, state this limitation in
42 | your response.
43 |
44 | Output the analysis in {language}.
45 |
46 |
47 | """
48 |
49 | income_prompts = """
50 |
51 | Variables:
52 |
53 | {income_statement_data}, {stock_name}
54 |
55 | ************************
56 |
57 | Prompt:
58 | You are a financial analyst tasked with analyzing the income statement of a
59 | company. To assist in your analysis, you have been provided with the following data:
60 |
61 |
62 | {stock_name}
63 |
64 |
65 |
66 | {income_statement_data}
67 |
68 |
69 | Using the provided data, please conduct an executive summary analysis in 100 words, using paragraphs, of the company's financial health and
70 | future prospects. Your analysis should include:
71 |
72 |
73 | - Analyze key financial metrics from the income statement, such as revenue growth, profit margins,
74 | and expenses
75 | - Assess the company's profitability and efficiency based on the income statement data
76 | - Identify any potential risks or opportunities for the company based on the financial data
77 | - Consider industry trends and market conditions that may impact the company's performance
78 |
79 |
80 | After conducting your analysis, please provide your findings and conclusions in the following
81 | format:
82 |
83 |
84 | Income Statement Analysis:
85 | - [Discuss the company's revenue growth and profitability based on the income statement data]
86 | - [Identify any significant changes or trends in expenses or profit margins]
87 | - [Assess the company's overall financial health and efficiency based on the income statement
88 | metrics]
89 |
90 | Risks and Opportunities:
91 | - [Identify any potential risks or challenges the company may face based on the financial data]
92 | - [Discuss any opportunities for growth or improvement based on the analysis]
93 |
94 | Conclusion:
95 | - [Provide a summary of your overall assessment of the company's financial performance and future
96 | prospects]
97 | - [Include any recommendations or insights for investors or stakeholders]
98 |
99 |
100 | Please ensure that your analysis is clear, concise, and well-supported by the provided financial
101 | data. Use specific examples and figures from the stock data and income statement to support your
102 | conclusions.
103 |
104 | """
105 |
106 | earnings_prompt = """
107 |
108 | Variables:
109 |
110 | {earnings_transcript}
111 |
112 | ************************
113 |
114 | Prompt:
115 | You are an expert at analyzing quarterly earnings reports. Please carefully review the following earnings call transcript:
116 |
117 |
118 | {earnings_transcript}
119 |
120 |
121 | After reading through the transcript, take some time to think through the key insights and takeaways
122 | in a . Consider the following aspects:
123 | - Financial performance: How did the company perform financially this quarter? Were revenue,
124 | profits, margins, etc. up or down compared to prior periods? Did they meet, exceed or fall short of
125 | expectations?
126 | - Future outlook: What is the company's outlook for upcoming quarters? Are they optimistic or
127 | cautious? What are their projections for key metrics?
128 | - Strategic initiatives: Did the company discuss any major strategic initiatives, partnerships, new
129 | products, expansion plans or other projects? What is the rationale and potential impact?
130 | - Significant changes: Were there any notable leadership changes, restructurings, pivots in strategy
131 | or other significant developments disclosed?
132 |
133 |
134 | Once you've thought through the main points, please provide a summary of the key insights from the
135 | earnings call. The summary should concisely hit on the major takeaways around
136 | financial performance, outlook, strategy and changes while including specific details that support
137 | the main points. Aim for around 4-6 paragraphs.
138 |
139 | Remember, your goal is to extract and communicate the most important information from the earnings
140 | call in a clear, insightful executive summary. Focus on the high-level story and don't get too in
141 | the weeds with minor details. Put yourself in the shoes of an analyst or investor and highlight what
142 | you think they would care about most.
143 |
144 | """
145 |
--------------------------------------------------------------------------------
/finance-dashboard/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "writer-framework-default"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Your Name "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.10.0"
10 | writer = "^0.8.3rc2"
11 | python-dotenv = "^1.0.1"
12 | pandas = "^2.2.3"
13 | plotly = "^5.24.1"
14 | yfinance = "0.2.42"
15 | pre-commit = "^4.0.1"
16 | flake8 = "^7.1.1"
17 | black = "^24.10.0"
18 | isort = "^5.13.2"
19 |
20 | [build-system]
21 | requires = ["poetry-core"]
22 | build-backend = "poetry.core.masonry.api"
23 |
--------------------------------------------------------------------------------
/finance-dashboard/static/README.md:
--------------------------------------------------------------------------------
1 | # Serving static files
2 |
3 | You can use this folder to store files which will be served statically in the "/static" route.
4 |
5 | This is useful to store images and other files which will be served directly to the user of your application.
6 |
7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg".
8 | You can use this relative route as the source in an Image component.
9 |
--------------------------------------------------------------------------------
/finance-dashboard/static/custom.css:
--------------------------------------------------------------------------------
1 | .link {
2 | text-align: right !important;
3 | }
4 |
5 | .button-click {
6 | background-color: #5551ff !important;
7 | color: white !important;
8 | }
9 |
10 | .custom-iframe {
11 | height: 700px !important;
12 | }
13 |
--------------------------------------------------------------------------------
/finance-dashboard/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/finance-dashboard/static/favicon.png
--------------------------------------------------------------------------------
/finance-dashboard/stock_data.py:
--------------------------------------------------------------------------------
1 | import json
2 | from datetime import datetime
3 |
4 | import pandas as pd
5 | import yfinance as yf
6 | from charts import update_bar_graph
7 | from dotenv import load_dotenv
8 |
9 | load_dotenv()
10 |
11 |
12 | # Download stock data and format into a DataFrame
13 | def download_data(state):
14 | df = yf.download(state["symbol"], period="max", interval="1d")
15 | df = df.reset_index()
16 | if type(df.columns) is pd.MultiIndex:
17 | df.columns = df.columns.droplevel(1)
18 | df = df.sort_values(by="Date", ascending=False)
19 | df = df.round({"Open": 2, "High": 2, "Low": 2, "Close": 2, "Adj Close": 2})
20 | df["Date"] = pd.to_datetime(df["Date"])
21 | state["main_df_subset"] = df
22 | state["main_df"] = state["main_df_subset"]
23 |
24 |
25 | # Download S&P 500 data and format into a DataFrame
26 | def download_sp500(state):
27 | df = yf.download(tickers="^GSPC", period="max", interval="1d")
28 | df = df.reset_index()
29 | if type(df.columns) is pd.MultiIndex:
30 | df.columns = df.columns.droplevel(1)
31 | df = df.sort_values(by="Date", ascending=False)
32 | df = df.round({"Open": 2, "High": 2, "Low": 2, "Close": 2, "Adj Close": 2})
33 | df["Date"] = pd.to_datetime(df["Date"])
34 | state["another_df"] = df
35 |
36 |
37 | # Retrieve latest stock news
38 | def stock_news(state):
39 | msft = yf.Ticker(state["symbol"])
40 | articles = {}
41 | data = msft.news
42 | latest_articles = sorted(
43 | data, key=lambda x: x["providerPublishTime"], reverse=True
44 | )[:4]
45 |
46 | for item in latest_articles:
47 | provider_publish_time = item.get("providerPublishTime", "")
48 | if provider_publish_time:
49 | # Convert the timestamp to a readable date
50 | formatted_date = datetime.fromtimestamp(provider_publish_time)
51 | readable_date = formatted_date.strftime("%B %d, %Y at %H:%M")
52 | else:
53 | readable_date = "Date not available"
54 |
55 | title = item.get("title", "No Title")
56 | articles[title] = {
57 | "source": item.get("publisher", ""),
58 | "published_at": readable_date,
59 | "url": item.get("link", ""),
60 | }
61 | state["articles"] = articles
62 |
63 |
64 | # Retrieve income statement data
65 | def income_statement(state):
66 | quarterly_income_stmt = yf.Ticker(state["symbol"]).quarterly_income_stmt
67 | df = pd.DataFrame(quarterly_income_stmt)
68 | df.columns = pd.to_datetime(df.columns).strftime("%Y-%m-%d")
69 | state["income_statement_df"] = df
70 | update_bar_graph(state)
71 | show_fin_metrics(state)
72 |
73 |
74 | # Show financial metrics
75 | def show_fin_metrics(state):
76 | stock = yf.Ticker(state["symbol"])
77 | operating_margins = stock.info["operatingMargins"]
78 | gross_margin = stock.info["grossMargins"]
79 | ebitda_margin = stock.info["ebitdaMargins"]
80 |
81 | state["operating_margin"] = f"{operating_margins * 100:.2f}%"
82 | state["gross_margin"] = f"{gross_margin * 100:.2f}%"
83 | state["ebitda_margin"] = f"{ebitda_margin * 100:.2f}%"
84 |
85 |
86 | def _one_day_data(state):
87 | state["last_24_hours_open"] = round(state["main_df"]["Open"].iloc[0], 2)
88 | state["last_24_hours_high"] = round(state["main_df"]["High"].iloc[0], 2)
89 | state["last_24_hours_low"] = round(state["main_df"]["Low"].iloc[0], 2)
90 |
91 |
92 | # Retrieve earnings call transcript from JSON file
93 | # You could replace this with a call to an API like Financial Modeling Prep
94 | def earnings_calls(state):
95 | ticker = state["symbol"]
96 | with open("earnings-data.json", "r") as file:
97 | earnings_transcript = json.load(file)
98 | if earnings_transcript:
99 | for item in earnings_transcript:
100 | if item["symbol"] == ticker:
101 | state["earnings_transcript"] = item["content"]
102 | else:
103 | print("No earnings transcript found.")
104 |
--------------------------------------------------------------------------------
/financial-tools-chat/.env.example:
--------------------------------------------------------------------------------
1 | WRITER_API_KEY=your_writer_api_key
2 | EARNINGS_ANALYSIS_APP_ID=your_zero_code_app_id
3 |
--------------------------------------------------------------------------------
/financial-tools-chat/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "type": "page", "content": {"pageMode": "compact"}, "handlers": {}, "isCodeManaged": false, "parentId": "root", "position": 0}
2 | {"id": "4kukoixkdb66ixah", "type": "header", "content": {"text": "Financial tools chat"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 0}
3 | {"id": "kldz702eixpb8xnq", "type": "columns", "content": {"cssClasses": "column-container"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 1}
4 | {"id": "4nklk9e1wqetdhln", "type": "column", "content": {"width": "45"}, "handlers": {}, "isCodeManaged": false, "parentId": "kldz702eixpb8xnq", "position": 0}
5 | {"id": "pi01zfrzcoxpiqxu", "type": "chatbot", "content": {"avatarBackgroundColor": "#000000", "buttonColor": "#000000", "conversation": "@{conversation}", "cssClasses": "chat", "useMarkdown": "yes"}, "handlers": {"wf-chatbot-message": "message_handler"}, "isCodeManaged": false, "parentId": "4nklk9e1wqetdhln", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
6 | {"id": "52rbj5jd79undjij", "type": "column", "content": {"width": "50"}, "handlers": {}, "isCodeManaged": false, "parentId": "kldz702eixpb8xnq", "position": 1, "visible": {"binding": "visual_block_visible", "expression": "custom", "reversed": false}}
7 | {"id": "e8iafahde157xsgr", "type": "tabs", "content": {"cssClasses": "visualization"}, "handlers": {}, "isCodeManaged": false, "parentId": "52rbj5jd79undjij", "position": 0}
8 | {"id": "stock", "type": "tab", "content": {"cssClasses": "", "name": "Stock"}, "handlers": {}, "isCodeManaged": false, "parentId": "e8iafahde157xsgr", "position": 0, "visible": {"binding": "", "expression": "custom", "reversed": false}}
9 | {"id": "er0t7muikaatylvt", "type": "plotlygraph", "content": {"cssClasses": "stock", "spec": "@{stock_chart}"}, "handlers": {}, "isCodeManaged": false, "parentId": "stock", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
10 | {"id": "4n2cykskypjd6qo4", "type": "text", "content": {"text": "@{stock_analysis}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "stock", "position": 1}
11 | {"id": "income", "type": "tab", "content": {"cssClasses": "", "name": "Income"}, "handlers": {}, "isCodeManaged": false, "parentId": "e8iafahde157xsgr", "position": 1, "visible": {"binding": "", "expression": "custom", "reversed": false}}
12 | {"id": "rqbqheyr0o5hs4fj", "type": "plotlygraph", "content": {"cssClasses": "income", "spec": "@{income_chart}"}, "handlers": {}, "isCodeManaged": false, "parentId": "income", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
13 | {"id": "ssw8zcojyuup2aqn", "type": "text", "content": {"text": "@{income_analysis}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "income", "position": 1, "visible": {"binding": "", "expression": "custom", "reversed": false}}
14 | {"id": "earnings", "type": "tab", "content": {"cssClasses": "", "name": "Earnings"}, "handlers": {}, "isCodeManaged": false, "parentId": "e8iafahde157xsgr", "position": 2, "visible": {"binding": "", "expression": "custom", "reversed": false}}
15 | {"id": "3um69zhvgxb6efbh", "type": "text", "content": {"text": "@{earnings_analysis}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "earnings", "position": 0}
16 | {"id": "qwoll83rzy43r3rr", "type": "column", "content": {"cssClasses": "buttons", "width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "kldz702eixpb8xnq", "position": 2, "visible": {"binding": "", "expression": "custom", "reversed": false}}
17 | {"id": "19f02wo3koxaipwp", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#000000", "cssClasses": "", "icon": "arrow_back_ios", "text": ""}, "handlers": {"wf-click": "open_visualization"}, "isCodeManaged": false, "parentId": "qwoll83rzy43r3rr", "position": 0, "visible": {"binding": "visual_block_visible", "expression": "custom", "reversed": true}}
18 | {"id": "72yjtz3kkrb5zr16", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "cssClasses": "", "icon": "arrow_forward_ios", "text": ""}, "handlers": {"wf-click": "close_visualization"}, "isCodeManaged": false, "parentId": "qwoll83rzy43r3rr", "position": 1, "visible": {"binding": "visual_block_visible", "expression": "custom", "reversed": false}}
19 | {"id": "kphyjxow4ppazmw1", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#000000", "icon": "delete", "text": ""}, "handlers": {"wf-click": "clear_visualization"}, "isCodeManaged": false, "parentId": "qwoll83rzy43r3rr", "position": 2}
20 |
--------------------------------------------------------------------------------
/financial-tools-chat/.wf/components-root.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "root", "type": "root", "content": {"appName": "Financial tools chat"}, "handlers": {}, "isCodeManaged": false, "position": 0}
--------------------------------------------------------------------------------
/financial-tools-chat/.wf/components-workflows_root.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
--------------------------------------------------------------------------------
/financial-tools-chat/.wf/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "writer_version": "0.8.1"
3 | }
--------------------------------------------------------------------------------
/financial-tools-chat/README.md:
--------------------------------------------------------------------------------
1 | # Financial Tools Chat
2 |
3 | A conversational interface for financial research tools. This application was created using Writer Framework and uses the Palmyra Financial model.
4 |
5 | ## Description
6 |
7 | Financial Tools Chat is an interactive chat application that helps users with various financial research tools. Users can interact with the application through a chat interface to research and analyze:
8 | - Stock price data
9 | - Income statements
10 | - Earnings reports (Note: Earnings reports are stored locally in `earnings-data.json`. Feel free to swap this out with a call to an API that provides earnings data.)
11 |
12 | ## Prerequisites
13 | First, ensure you have Poetry installed. Then, in the project directory, install the dependencies by running:
14 |
15 | ```sh
16 | poetry install
17 | ```
18 |
19 | ### Sign up for Writer AI Studio
20 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new API Key.
21 |
22 | To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`:
23 | ```sh
24 | export WRITER_API_KEY=your-api-key
25 | ```
26 |
27 | You can also set the `WRITER_API_KEY` in the `.env` file.
28 |
29 | ### Create a no-code text generation application
30 | You'll also need to [create a no-code text generation application](https://dev.writer.com/no-code/building-a-text-generation-app) in Writer AI Studio and add the Palmyra Financial model to it to run the earnings call analysis. Add two fields to the application: `name` (for the symbol) and `data` (for the earnings report). You can use the `earnings-prompt.txt` file as a prompt for the earnings call analysis.
31 |
32 | ## Edit and run the application
33 | To make changes or edit the application, navigate to root folder and use the following command:
34 |
35 |
36 | ```sh
37 | writer edit .
38 | ```
39 |
40 | Once you're ready to run the application, execute:
41 |
42 | ```sh
43 | writer run .
44 | ```
45 |
46 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction).
47 |
48 | ## About Writer
49 |
50 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework).
51 |
--------------------------------------------------------------------------------
/financial-tools-chat/chat_tools.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | from io import StringIO
4 |
5 | import pandas as pd
6 | import plotly.express as px
7 | import plotly.graph_objects as go
8 | import yfinance as yf
9 | from dotenv import load_dotenv
10 | from plotly.subplots import make_subplots
11 | from prompts import income_prompt, stock_prompt
12 | from writer.ai import apps, complete
13 |
14 | load_dotenv()
15 |
16 |
17 | def _get_stock_analysis(symbol: str) -> str:
18 | config = {"model": "palmyra-fin-32k", "temperature": 0.7, "max_tokens": 8192}
19 | data = _get_stock_data(symbol)
20 | prompt = stock_prompt.format(name=symbol, data=data)
21 | stock_analysis = complete(prompt, config=config)
22 | return stock_analysis
23 |
24 |
25 | def _get_stock_data(symbol: str) -> str:
26 | df = yf.download(symbol, period="5y", interval="5d")
27 | df = df.reset_index()
28 | df = df.sort_values(by="Date", ascending=True)
29 | df = df.round({"Open": 2, "High": 2, "Low": 2, "Close": 2, "Adj Close": 2})
30 | df["Date"] = pd.to_datetime(df["Date"])
31 | return df.to_csv(index=False)
32 |
33 |
34 | def _get_income_analysis(symbol: str) -> str:
35 | config = {"model": "palmyra-fin-32k", "temperature": 0.7, "max_tokens": 8192}
36 | data = _get_income_data(symbol)
37 | prompt = income_prompt.format(name=symbol, data=data)
38 | income_analysis = complete(prompt, config=config)
39 | return income_analysis
40 |
41 |
42 | def _get_income_data(symbol: str) -> str:
43 | quarterly_income_stmt = yf.Ticker(symbol).quarterly_income_stmt
44 | df = pd.DataFrame(quarterly_income_stmt)
45 | df.columns = pd.to_datetime(df.columns).strftime("%Y-%m-%d")
46 | return df.to_csv()
47 |
48 |
49 | def _get_earnings_analysis(symbol: str) -> str:
50 | data = _get_earnings_data(symbol)
51 | earnings_analysis = apps.generate_content(
52 | application_id=os.getenv("EARNINGS_ANALYSIS_APP_ID", ""),
53 | input_dict={
54 | "name": symbol,
55 | "data": data,
56 | },
57 | )
58 | return earnings_analysis
59 |
60 |
61 | def _get_earnings_data(symbol: str) -> str:
62 | with open("earnings-data.json", "r") as file:
63 | earnings_transcript = json.load(file)
64 | if earnings_transcript:
65 | for item in earnings_transcript:
66 | if item["symbol"] == symbol:
67 | return item["content"]
68 | else:
69 | return "No earnings transcript found."
70 |
71 |
72 | def get_stock_chart(symbol: str) -> str:
73 | symbol_io = StringIO(_get_stock_data(symbol))
74 | sp500_io = StringIO(_get_stock_data("^GSPC"))
75 |
76 | symbol_df = pd.read_csv(symbol_io)
77 | sp500_df = pd.read_csv(sp500_io)
78 |
79 | fig = make_subplots(specs=[[{"secondary_y": True}]])
80 |
81 | fig.add_trace(
82 | go.Scatter(x=symbol_df["Date"], y=symbol_df["Open"], name=symbol, mode="lines"),
83 | secondary_y=False,
84 | )
85 |
86 | fig.add_trace(
87 | go.Scatter(
88 | x=sp500_df["Date"], y=sp500_df["Open"], name="S&P 500", mode="lines"
89 | ),
90 | secondary_y=True,
91 | )
92 |
93 | fig.update_yaxes(title_text=f"{symbol} Stock Price", secondary_y=False)
94 | fig.update_yaxes(title_text="S&P 500", secondary_y=True)
95 |
96 | fig.update_layout(
97 | height=450,
98 | title_text=f"{symbol} Stock vs the S&P 500",
99 | title_x=0.5,
100 | title_y=0.9,
101 | legend=dict(
102 | orientation="h",
103 | yanchor="top",
104 | y=-0.2,
105 | xanchor="center",
106 | x=0.5,
107 | ),
108 | )
109 |
110 | return fig.to_json()
111 |
112 |
113 | def get_income_chart(symbol: str) -> str:
114 | string_io = StringIO(_get_income_data(symbol))
115 |
116 | df = pd.read_csv(string_io, index_col=0)
117 | df_filtered = df.loc[["Total Revenue", "Net Income", "Operating Income"]]
118 |
119 | df_transposed = df_filtered.transpose().reset_index()
120 | df_transposed = df_transposed.melt(
121 | id_vars=["index"], var_name="Metric", value_name="Value"
122 | )
123 |
124 | fig = px.bar(
125 | df_transposed,
126 | x="index",
127 | y="Value",
128 | color="Metric",
129 | barmode="group",
130 | labels={"index": "", "Value": ""},
131 | title=f"Summary of Quarterly Income Statement for {symbol}",
132 | )
133 |
134 | fig.update_layout(
135 | height=400,
136 | legend=dict(orientation="h", yanchor="top", y=-0.2, xanchor="center", x=0.5),
137 | )
138 |
139 | return fig.to_json()
140 |
141 |
142 | stock_analysis_tool = {
143 | "type": "function",
144 | "name": "get_stock_analysis",
145 | "callable": _get_stock_analysis,
146 | "parameters": {
147 | "symbol": {
148 | "type": "string",
149 | "description": "Symbol to compose stock data analysis.",
150 | },
151 | },
152 | }
153 |
154 |
155 | income_analysis_tool = {
156 | "type": "function",
157 | "name": "get_income_analysis",
158 | "callable": _get_income_analysis,
159 | "parameters": {
160 | "symbol": {
161 | "type": "string",
162 | "description": "Symbol to compose income statement analysis.",
163 | },
164 | },
165 | }
166 |
167 |
168 | earnings_analysis_tool = {
169 | "type": "function",
170 | "name": "get_earnings_analysis",
171 | "callable": _get_earnings_analysis,
172 | "parameters": {
173 | "symbol": {
174 | "type": "string",
175 | "description": "Symbol to fetch earnings data and compose analysis.",
176 | },
177 | },
178 | }
179 |
180 |
181 | tools = [
182 | stock_analysis_tool,
183 | income_analysis_tool,
184 | earnings_analysis_tool,
185 | ]
186 |
--------------------------------------------------------------------------------
/financial-tools-chat/earnings-prompt.txt:
--------------------------------------------------------------------------------
1 | # CONTEXT #
2 | You are an expert at analyzing quarterly earnings reports. You are creating financial analysis report based on earning statements of specific symbol, provided at data section. Furthermore, you are acting really carefully, review the data and provide insights, trends, and financial analysis.
3 |
4 | # INSTRUCTIONS #
5 | Create an expertly written financial analysis.
6 |
7 | # DATA #
8 | Symbol of earnings data: {name}
9 | Earnings report: {data}
10 |
11 | # ADDITIONAL GUIDELINES #
12 | - Say a few words about financial performance: How did the company perform financially this quarter? Were revenue, profits, margins, etc. up or down compared to prior periods? Did they meet, exceed, or fall short of expectations?
13 | - Provide future outlook: What is the company's outlook for upcoming quarters? Are they optimistic or cautious? What are their projections for key metrics?
14 | - Compose some strategic initiatives: Did the company discuss any major strategic initiatives, partnerships, new products, expansion plans or other projects? What is the rationale and potential impact?
15 | - Outline significant changes: Were there any notable leadership changes, restructurings, pivots in strategy or other significant developments disclosed?
16 | - Use some numbers from provided data in your analysis report.
17 | - Remember to base your analysis and recommendation solely on the provided earnings report for {name}. If there is insufficient information to draw a conclusion, state this limitation in your response.
18 |
19 | # USER REQUEST SAMPLE #
20 | Here is the user request sample: "What you can say about IBM doings based on their earnings report?"
21 |
22 | # RESPONSE FORMAT #
23 | Highlight headers, topics, main metrics and ideas. Use .md markup to style your text.
--------------------------------------------------------------------------------
/financial-tools-chat/main.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 |
4 | import plotly.express as px
5 | import writer
6 | from chat_tools import get_income_chart, get_stock_chart, tools
7 | from dotenv import load_dotenv
8 | from writer.ai import Conversation
9 |
10 | load_dotenv()
11 |
12 | writer.ai.init(os.getenv("WRITER_API_KEY", ""))
13 |
14 |
15 | def message_handler(payload, state):
16 | try:
17 | state.call_frontend_function("scripts", "enableDisableTextarea", ["true"])
18 | state["conversation"] += payload
19 |
20 | response = state["conversation"].stream_complete(tools=tools)
21 |
22 | for chunk in response:
23 | state["conversation"] += chunk
24 |
25 | for tool_call in _get_tool_call_results(state).values():
26 | _visualize_response(state, tool_call)
27 |
28 | state.call_frontend_function("scripts", "enableDisableTextarea", ["false"])
29 | except Exception as e:
30 | state["conversation"] += {
31 | "role": "assistant",
32 | "content": "Sorry, we faced unexpected error. Please try again!",
33 | }
34 | state.call_frontend_function("scripts", "enableDisableTextarea", ["false"])
35 | raise e
36 |
37 |
38 | def _get_tool_call_results(state):
39 | tool_calls = {}
40 | for message in reversed(state["conversation"].messages):
41 | try:
42 | if message["role"] == "tool":
43 | tool_calls.update({message["name"]: message})
44 | elif message["role"] == "assistant" and message["tool_calls"]:
45 | for tool_call in message["tool_calls"]:
46 | tool_name = tool_call["function"]["name"]
47 | tool_args = tool_call["function"]["arguments"]
48 | tool_calls[tool_name].update({"arguments": json.loads(tool_args)})
49 | elif message["role"] == "user":
50 | break
51 | except KeyError as e:
52 | raise KeyError("Key error during fetching tool calls") from e
53 | return tool_calls
54 |
55 |
56 | def _visualize_response(state, tool_call):
57 | _update_text(state, tool_call["name"].split("_")[1], tool_call["content"])
58 | _update_graphic(
59 | state, tool_call["name"].split("_")[1], tool_call["arguments"]["symbol"]
60 | )
61 | state.call_frontend_function(
62 | "scripts", "focusOnTab", [tool_call["name"].split("_")[1]]
63 | )
64 | state["visual_block_visible"] = True
65 |
66 |
67 | def _update_text(state, property_name, text):
68 | state[property_name + "_analysis"] = text
69 |
70 |
71 | def _update_graphic(state, property_name, symbol_name):
72 | graphic = ""
73 | if property_name == "stock":
74 | graphic = get_stock_chart(symbol_name)
75 | elif property_name == "income":
76 | graphic = get_income_chart(symbol_name)
77 | state[property_name + "_chart"] = graphic
78 |
79 |
80 | def _get_chart_placeholder():
81 | df = px.data.stocks()
82 | df.columns.values[1] = "TEST"
83 | fig = px.line(df, x="date", y="TEST")
84 |
85 | fig.update_layout(
86 | height=400,
87 | title_x=0.5,
88 | title_y=0.9,
89 | legend=dict(
90 | orientation="h",
91 | yanchor="top",
92 | y=-0.2,
93 | xanchor="center",
94 | x=0.5,
95 | ),
96 | )
97 |
98 | return fig.to_json()
99 |
100 |
101 | def clear_visualization(state):
102 | tab_names = ["stock", "income", "earnings"]
103 |
104 | for tab_name in tab_names:
105 | state[tab_name + "_analysis"] = "Analysis will appear here."
106 | state[tab_name + "_chart"] = _get_chart_placeholder()
107 |
108 | state["visual_block_visible"] = False
109 |
110 |
111 | def close_visualization(state):
112 | state["visual_block_visible"] = False
113 |
114 |
115 | def open_visualization(state):
116 | state["visual_block_visible"] = True
117 |
118 |
119 | initial_state = writer.init_state(
120 | {
121 | "conversation": Conversation(
122 | [
123 | {"role": "assistant", "content": "Hi there. I can analyze stock price data, income statements, and earnings reports. How can I help?"},
124 | ],
125 | ),
126 | "visual_block_visible": False,
127 | "stock_analysis": "Analysis will appear here.",
128 | "stock_chart": _get_chart_placeholder(),
129 | "income_analysis": "Analysis will appear here.",
130 | "income_chart": _get_chart_placeholder(),
131 | "earnings_analysis": "Analysis will appear here.",
132 | }
133 | )
134 |
135 | initial_state.import_stylesheet("style", "/static/custom.css")
136 | initial_state.import_frontend_module("scripts", "/static/custom.js")
137 |
--------------------------------------------------------------------------------
/financial-tools-chat/prompts.py:
--------------------------------------------------------------------------------
1 | stock_prompt = """
2 | # CONTEXT #
3 | You are an expert of a financial analysis. You are creating financial analysis report based on stock
4 | data of specific symbol, provided at data section. Furthermore, you are acting really carefully, review
5 | the data and provide insights, trends, and financial analysis for the stock.
6 |
7 | # INSTRUCTIONS #
8 | Create an expertly written financial analysis.
9 |
10 | # DATA #
11 | Symbol of stock data: {name}
12 | Stock data: {data}
13 |
14 | # ADDITIONAL GUIDELINES #
15 | - Do not include any additional data in your response. Use only provided data.
16 | - Identify any notable trends in the stock's price, volume, or other key metrics over
17 | the given time period. Discuss potential reasons behind these trends.
18 | - Compare the stock's performance and financial metrics to industry averages and key
19 | competitors. Discuss how the stock stacks up against its peers.
20 | - Based on your analysis, provide an overall assessment of the stock's current position
21 | and future prospects. Consider factors such as growth potential, risk level, and market sentiment.
22 | - Use some numbers from provided data in your analysis report.
23 | - Remember to base your analysis and recommendation solely on the provided stock data for {name}.
24 | If there is insufficient information to draw a conclusion, state this limitation in your response.
25 |
26 | # USER REQUEST SAMPLE #
27 | Here is the user request sample: "Tell me about Tesla stock"
28 |
29 | # RESPONSE FORMAT #
30 | Highlight headers, topics, main metrics and ideas. Use .md markup to style your text.
31 | """
32 |
33 | income_prompt = """
34 | # CONTEXT #
35 | You are an expert of a financial analysis. You are creating financial analysis report based on income
36 | statement data of specific symbol, provided at data section. You are acting really carefully, review
37 | the data and provide insights, trends, and financial analysis for the incomes.
38 |
39 | # INSTRUCTIONS #
40 | Create an expertly written financial analysis.
41 |
42 | # DATA #
43 | Symbol of income statements data: {name}
44 | Income statements data: {data}
45 |
46 | # ADDITIONAL GUIDELINES #
47 | - Do not include any additional data in your response. Use only provided data.
48 | - Analyze key financial metrics from the income statement, such as revenue growth, profit margins, and expenses.
49 | - Assess the company's profitability and efficiency based on the income statement data.
50 | - Identify any potential risks or opportunities for the company based on the financial data.
51 | - Consider industry trends and market conditions that may impact the company's performance.
52 | - Use some numbers from provided data in your analysis report.
53 | - Remember to base your analysis and recommendation solely on the provided income data for {name}.
54 | If there is insufficient information to draw a conclusion, state this limitation in your response.
55 |
56 | # USER REQUEST SAMPLE #
57 | Here is the user request sample: "Hey, I want to know something about apple incomes"
58 |
59 | # RESPONSE FORMAT #
60 | Highlight headers, topics, main metrics and ideas. Use .md markup to style your text.
61 | """
62 |
--------------------------------------------------------------------------------
/financial-tools-chat/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "writer-framework-default"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Your Name "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.11"
10 | writer = "0.8.2"
11 | openai = "1.51.2"
12 | pre-commit = "^3.8.0"
13 | flake8 = "7.1.1"
14 | black = "^24.8.0"
15 | isort = "^5.13.2"
16 | markdown = "^3.7"
17 | python-dotenv = "1.0.1"
18 | writer-sdk = "^1.2.0"
19 | annotated-types = "0.7.0"
20 | anyio = "4.6.0"
21 | authlib = "1.3.2"
22 | beautifulsoup4 = "4.12.3"
23 | certifi = "2024.8.30"
24 | cffi = "1.17.1"
25 | charset-normalizer = "3.4.0"
26 | click = "8.1.7"
27 | cryptography = "43.0.1"
28 | distro = "1.9.0"
29 | fastapi = "0.115.0"
30 | frozendict = "2.4.5"
31 | gitignore-parser = "0.1.11"
32 | h11 = "0.14.0"
33 | html5lib = "1.1"
34 | httpcore = "1.0.6"
35 | httpx = "0.27.2"
36 | idna = "3.10"
37 | jiter = "0.6.1"
38 | lxml = "5.3.0"
39 | markupsafe = "3.0.1"
40 | mccabe = "0.7.0"
41 | multitasking = "0.0.11"
42 | mypy-extensions = "1.0.0"
43 | numpy = "1.26.4"
44 | packaging = "24.1"
45 | pandas = "2.2.3"
46 | peewee = "3.17.6"
47 | platformdirs = "4.3.6"
48 | plotly = "5.24.1"
49 | pyarrow = "15.0.2"
50 | pycodestyle = "2.12.1"
51 | pycparser = "2.22"
52 | pydantic = "2.9.2"
53 | pydantic-core = "2.23.4"
54 | pyflakes = "3.2.0"
55 | python-dateutil = "2.9.0.post0"
56 | python-multipart = "0.0.12"
57 | pytz = "2024.2"
58 | requests = "2.32.3"
59 | six = "1.16.0"
60 | sniffio = "1.3.1"
61 | soupsieve = "2.6"
62 | starlette = "0.38.6"
63 | tenacity = "9.0.0"
64 | tqdm = "4.66.5"
65 | typing-extensions = "4.12.2"
66 | tzdata = "2024.2"
67 | urllib3 = "2.2.3"
68 | uvicorn = "0.31.0"
69 | watchdog = "3.0.0"
70 | webencodings = "0.5.1"
71 | websockets = "12.0"
72 | writerai = "3.4.0"
73 | yfinance = "0.2.44"
74 |
75 |
76 | [build-system]
77 | requires = ["poetry-core"]
78 | build-backend = "poetry.core.masonry.api"
79 |
--------------------------------------------------------------------------------
/financial-tools-chat/static/README.md:
--------------------------------------------------------------------------------
1 | # Serving static files
2 |
3 | You can use this folder to store files which will be served statically in the "/static" route.
4 |
5 | This is useful to store images and other files which will be served directly to the user of your application.
6 |
7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg".
8 | You can use this relative route as the source in an Image component.
9 |
--------------------------------------------------------------------------------
/financial-tools-chat/static/custom.css:
--------------------------------------------------------------------------------
1 | .visualization{
2 | height: 80vh !important;
3 | overflow-y: auto !important;
4 | }
5 |
6 | .column-container{
7 | width: 71vw !important;
8 | }
9 |
--------------------------------------------------------------------------------
/financial-tools-chat/static/custom.js:
--------------------------------------------------------------------------------
1 | const chat = document.getElementsByClassName("chat")
2 |
3 | export function enableDisableTextarea(disable){
4 | const isDisabled = (disable === 'true')
5 |
6 | chat["0"].getElementsByTagName("textarea")["0"].disabled = isDisabled
7 | chat["0"].getElementsByTagName("button")["0"].disabled = isDisabled
8 | }
9 |
10 |
11 |
12 | export function focusOnTab(tabId){
13 | const tabDivs = document.querySelectorAll('[data-writer-id]');
14 | for (const tabDivElement of tabDivs)
15 | if(tabDivElement.getAttribute("data-writer-id") === tabId) {
16 | const button = tabDivElement.getElementsByTagName("button")[0];
17 | console.log(button)
18 | button.click();
19 | break;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/financial-tools-chat/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/financial-tools-chat/static/favicon.png
--------------------------------------------------------------------------------
/hacker-news-social-listener/.env.example:
--------------------------------------------------------------------------------
1 | WRITER_API_KEY=your_writer_api_key
2 | GRAPH_ID=your_graph_id
3 | HACKERNEWS_API_URL=https://hacker-news.firebaseio.com/v0
4 |
--------------------------------------------------------------------------------
/hacker-news-social-listener/.gitignore:
--------------------------------------------------------------------------------
1 | static/hackernews_comments.csv
2 | static/hackernews_posts.csv
3 |
--------------------------------------------------------------------------------
/hacker-news-social-listener/.wf/components-root.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "root", "type": "root", "content": {"appName": "Hacker News Listener"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
--------------------------------------------------------------------------------
/hacker-news-social-listener/.wf/components-workflows_root.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
--------------------------------------------------------------------------------
/hacker-news-social-listener/.wf/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "writer_version": "0.8.2"
3 | }
--------------------------------------------------------------------------------
/hacker-news-social-listener/README.md:
--------------------------------------------------------------------------------
1 | # Hacker News Listener
2 | This application is built using the Writer Framework and is designed to scrape the top posts and comments from Hacker News. It processes the data, uploads it to a Writer Knowledge Graph for further analysis, and generates AI-powered insights based on the content of the posts.
3 |
4 | ## Usage
5 |
6 | 1. Select the number of items you wish to process.
7 | 2. The application will generate raw data with analysis of posts and comments.
8 | 3. Ask specific questions using the Knowledge Graph chat.
9 | 4. Generate a detailed report from the processed data using the Prepared Report feature.
10 |
11 | ## Running the application
12 | First, ensure you have Poetry installed. Then, in the project directory, install the dependencies by running:
13 |
14 | ```sh
15 | poetry install
16 | ```
17 |
18 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel), create a new API Key and Knowledge Graph. To pass your API key and Knowledge Graph to the Writer Framework, you'll need to set an environment variables called `WRITER_API_KEY` and `GRAPH_ID`:
19 | ```sh
20 | export WRITER_API_KEY=your-api-key
21 | ```
22 | ```sh
23 | export GRAPH_ID=your-graph-id
24 | ```
25 |
26 | You can also set the `WRITER_API_KEY` and `GRAPH_ID` in the `.env` file.
27 |
28 | To make changes or edit the application, navigate to root folder and use the following command:
29 |
30 |
31 | ```sh
32 | writer edit .
33 | ```
34 |
35 | Once you're ready to run the application, execute:
36 |
37 | ```sh
38 | writer run .
39 | ```
40 |
41 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction).
42 |
43 | ## About Writer
44 |
45 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework).
46 |
--------------------------------------------------------------------------------
/hacker-news-social-listener/prompts.py:
--------------------------------------------------------------------------------
1 | def report_prompt(posts, comments):
2 | return f"""
3 | # CONTEXT #
4 | You are an expert at analyzing large amounts of posts and comments
5 | at social network for software developers called HackerNews. You are creating
6 | summary reports of provided data. Furthermore, you are acting
7 | really carefully outlining main trends, top posts and comments,
8 | most famous topics and development approaches.
9 |
10 | # INSTRUCTIONS #
11 | Create an expertly written summary report.
12 |
13 | # DATA #
14 | Here are the posts and comments you will use to create the report.
15 |
16 | Posts:
17 | {posts}
18 |
19 | Comments:
20 | {comments}
21 |
22 | # ADDITIONAL GUIDELINES #
23 | - Reflect only top posts and comments. DO NOT reflect all data in your
24 | report.
25 | - FIT your report in 10-15 paragraphs. This is also very IMPORTANT.
26 | - Say a few words about posts reflected at report.
27 | - Provide some analysis of trends you are surveying: if users consider
28 | theme useful or not, if they are pleased with it and so on.
29 | - Outline most interesting, discussed and high rated comments and posts.
30 |
31 | # RESPONSE FORMAT #
32 |
33 | Highlight headers, topics, main ideas. Use .md markup to style your text.
34 | """
35 |
--------------------------------------------------------------------------------
/hacker-news-social-listener/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "writer-framework-default"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Your Name "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.10.0"
10 | writer = "^0.8.2"
11 | praw = "^7.7.1"
12 | black = "^24.8.0"
13 | flake8 = "^7.1.1"
14 | isort = "^5.13.2"
15 | pre-commit = "^3.8.0"
16 | python-dotenv = "^1.0.1"
17 | aiohttp = "^3.10.10"
18 | asyncio = "^3.4.3"
19 |
20 |
21 | [build-system]
22 | requires = ["poetry-core"]
23 | build-backend = "poetry.core.masonry.api"
24 |
--------------------------------------------------------------------------------
/hacker-news-social-listener/static/README.md:
--------------------------------------------------------------------------------
1 | # Serving static files
2 |
3 | You can use this folder to store files which will be served statically in the "/static" route.
4 |
5 | This is useful to store images and other files which will be served directly to the user of your application.
6 |
7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg".
8 | You can use this relative route as the source in an Image component.
9 |
--------------------------------------------------------------------------------
/hacker-news-social-listener/static/custom.css:
--------------------------------------------------------------------------------
1 | .file{
2 | height: 60vh !important;
3 | }
4 |
5 | .file-text{
6 | height: 51vh !important;
7 | overflow-y: auto !important;
8 | }
9 |
10 | .files-list{
11 | height: 79vh !important;
12 | overflow-y: auto !important;
13 | }
14 |
15 | .CoreSection__title h3{
16 | font-size: 1.05rem !important;
17 | }
18 |
--------------------------------------------------------------------------------
/hacker-news-social-listener/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/hacker-news-social-listener/static/favicon.png
--------------------------------------------------------------------------------
/hf-spaces-starter/Dockerfile:
--------------------------------------------------------------------------------
1 | # Build stage
2 | FROM python:3.11-slim-buster AS Build
3 |
4 | # Set environment variables for Python and Poetry
5 | ENV PYTHONUNBUFFERED=1 \
6 | PIP_NO_CACHE_DIR=1 \
7 | POETRY_NO_INTERACTION=1 \
8 | POETRY_VIRTUALENVS_CREATE=false \
9 | POETRY_VERSION=1.7.1
10 |
11 | # Set the working directory in the container
12 | WORKDIR /app
13 |
14 | # Copy the dependencies file to the working directory
15 | COPY ./pyproject.toml /app/
16 |
17 | # Update, install dependencies, and prepare the Python environment
18 | RUN apt-get update && \
19 | apt-get install -y gcc g++ unixodbc-dev && \
20 | pip install "poetry==$POETRY_VERSION" && \
21 | poetry export --without-hashes --format requirements.txt --output requirements.txt && \
22 | python3 -m pip wheel --no-cache-dir --no-deps -w /app/wheels -r requirements.txt
23 |
24 | # Runtime stage
25 | FROM python:3.11-slim-buster AS Run
26 |
27 | # Set environment variables for Python and Poetry
28 | ENV HOME=/home/user \
29 | PATH=/home/user/.local/bin:$PATH
30 |
31 | # Create a non-root user
32 | RUN useradd -m -u 1000 user
33 |
34 | # Switch to the non-root user
35 | USER user
36 |
37 | # Copy wheel files from the build stage
38 | COPY --from=build /app/wheels $HOME/app/wheels
39 |
40 | # Set the working directory to where the wheels are
41 | WORKDIR $HOME/app/wheels
42 |
43 | # Install the wheel files
44 | RUN pip3 --no-cache-dir install *.whl
45 |
46 | # Copy the application files to the working directory (change to your app name)
47 | COPY --chown=user ./your-app-name $HOME/app
48 |
49 | # Set the working directory to the application files
50 | WORKDIR $HOME/app
51 |
52 | # Specify the command to run the application
53 | ENTRYPOINT [ "writer", "run" ]
54 |
55 | # Expose the port the app runs on
56 | EXPOSE 8080
57 |
58 | # Set the default command to run the app
59 | CMD [ ".", "--port", "8080", "--host", "0.0.0.0" ]
--------------------------------------------------------------------------------
/hf-spaces-starter/images/finance-dashboard-hf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/hf-spaces-starter/images/finance-dashboard-hf.png
--------------------------------------------------------------------------------
/hf-spaces-starter/images/new_space.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/hf-spaces-starter/images/new_space.png
--------------------------------------------------------------------------------
/localized-promo-dashboard/README.md:
--------------------------------------------------------------------------------
1 | First presented during AILF London on May 30th, 2024.
--------------------------------------------------------------------------------
/localized-promo-dashboard/data/default_segments.csv:
--------------------------------------------------------------------------------
1 | City,Segment,Revenue
2 | Germany,School,8234
3 | Germany,University,7321
4 | Germany,Work,10294
5 | Germany,Retired,5893
6 | Brazil,School,9321
7 | Brazil,University,7214
8 | Brazil,Work,12321
9 | Brazil,Retired,7821
10 |
--------------------------------------------------------------------------------
/localized-promo-dashboard/data/segments.csv:
--------------------------------------------------------------------------------
1 | City,Segment,Revenue
2 | UK,School,1075
3 | UK,University,5276
4 | UK,Work,4600
5 | UK,Retired,1051
6 | Australia,School,9916
7 | Australia,University,6063
8 | Australia,Work,9892
9 | Australia,Retired,5828
10 | US,School,36324
11 | US,University,4732
12 | US,Work,35848
13 | US,Retired,22992
14 | Spain,School,2932
15 | Spain,University,1819
16 | Spain,Work,7322
17 | Spain,Retired,3672
18 | Poland,School,1932
19 | Poland,University,2819
20 | Poland,Work,3322
21 | Poland,Retired,4672
22 |
--------------------------------------------------------------------------------
/localized-promo-dashboard/main.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | import pandas as pd
4 | import plotly.express as px
5 | import writer as wf
6 | import writer.ai
7 | from prompts import base_prompt
8 |
9 |
10 | def handle_file_upload_click(payload, state):
11 | _save_file(state, payload[0])
12 | _generate_output_data(state, state["file"]["file_path"])
13 |
14 |
15 | def _save_file(state, file):
16 | name = file.get("name")
17 | state["file"]["name"] = name
18 | state["file"]["file_path"] = f"data/{name}"
19 | file_data = file.get("data")
20 | with open(f"data/{name}", "wb") as file_handle:
21 | file_handle.write(file_data)
22 |
23 |
24 | def _generate_output_data(state, file_path):
25 | state["data_frame"] = pd.read_csv(file_path)
26 | _set_default_input(state)
27 | _generate_segments_chart(state)
28 | _generate_promo_materials(state)
29 |
30 |
31 | def _set_default_input(state):
32 | if not state["data_frame"].empty:
33 | state["input_data"]["location"] = state["data_frame"].at[0, "City"]
34 | state["input_data"]["segment"] = state["data_frame"].at[0, "Segment"]
35 |
36 |
37 | def handle_chart_click(payload, state):
38 | point_number = payload[0]["pointNumber"]
39 | fig = state["generated_charts"]["segments"]
40 | point_id = fig.data[0]["ids"][point_number]
41 | point_segments = point_id.split("/")
42 |
43 | if len(point_segments) < 3:
44 | return
45 | _all, location, segment = point_segments
46 |
47 | if (
48 | state["input_data"]["location"] == location
49 | and state["input_data"]["segment"] == segment
50 | ):
51 | return
52 |
53 | state["input_data"]["location"] = location
54 | state["input_data"]["segment"] = segment
55 |
56 | _generate_promo_materials(state)
57 |
58 |
59 | def _generate_promo_materials(state):
60 | state["available"] = False
61 | state["message"] = "% Generating promotional material..."
62 |
63 | social_post = _generate_post(
64 | state["base_prompt"].format(**state["input_data"].to_dict())
65 | )
66 | hashtags = _generate_post_hashtags(social_post)
67 | promo_email = _generate_promo_email(social_post)
68 | blog_post = _generate_blog_post(social_post)
69 |
70 | state["generated_data"]["social_post"] = social_post
71 | state["generated_data"]["hashtags"] = {
72 | item: item for item in re.findall(r"#\w+", hashtags)
73 | }
74 | state["generated_data"]["promo_email"] = promo_email
75 | state["generated_data"]["blog_post"] = blog_post
76 |
77 | state["message"] = ""
78 | state["available"] = True
79 |
80 |
81 | def _generate_post(prompt):
82 | social_post = writer.ai.complete(prompt)
83 | return social_post
84 |
85 |
86 | def _generate_post_hashtags(social_post):
87 | prompt = (
88 | f"Based on the following social media post, output ten hashtags in the language of the post, "
89 | f"for example #dogs #cats #food #pets etc:\n{social_post}"
90 | )
91 | hashtags = writer.ai.complete(prompt)
92 | return hashtags
93 |
94 |
95 | def _generate_promo_email(social_post):
96 | prompt = f"Based on the following social media post, create a promotional email:\n{social_post}"
97 | promo_email = writer.ai.complete(prompt)
98 | return promo_email
99 |
100 |
101 | def _generate_blog_post(social_post):
102 | prompt = (
103 | f"Based on the following social media post, create a blog post:\n{social_post}"
104 | )
105 | blog_post = writer.ai.complete(prompt)
106 | return blog_post
107 |
108 |
109 | def _generate_segments_chart(state):
110 | colors = [
111 | "#FFCFC2",
112 | "#B5EEEE",
113 | "#E4E9FF",
114 | "#FFD0F7",
115 | "#CECFFF",
116 | "#F2FFA2",
117 | "#B1EFDD",
118 | "#E5D1FA",
119 | "#FFE3FA",
120 | ]
121 | fig = px.treemap(
122 | state["data_frame"],
123 | path=[px.Constant("all"), "City", "Segment"],
124 | values="Revenue",
125 | hover_data=["City", "Segment"],
126 | color_discrete_sequence=colors,
127 | )
128 | state["generated_charts"]["segments"] = fig.update_layout(
129 | margin=dict(l=0, r=0, t=0, b=0), paper_bgcolor="#F5F5F9"
130 | )
131 |
132 |
133 | def _init_default_output_fields(state):
134 | _generate_output_data(state, "data/default_segments.csv")
135 | _set_default_input(state)
136 |
137 |
138 | initial_state = wf.init_state(
139 | {
140 | "base_prompt": base_prompt,
141 | "file": {"name": "", "file_path": ""},
142 | "input_data": {
143 | "location": "",
144 | "segment": "",
145 | "product_description": "Nomnom the cereal bar made in London, UK, crafted only with natural ingredients.",
146 | },
147 | "generated_data": {"social_post": "", "promo_email": "", "blog_post": ""},
148 | "generated_charts": {
149 | "segments": None,
150 | },
151 | "message": "",
152 | "available": False,
153 | }
154 | )
155 |
156 |
157 | _init_default_output_fields(initial_state)
158 |
--------------------------------------------------------------------------------
/localized-promo-dashboard/prompts.py:
--------------------------------------------------------------------------------
1 | base_prompt = """
2 | Output in the language of the location {location}.
3 |
4 | Write a promotional social media post for the product presented below the dashes. Include emojis.
5 |
6 | Target, specifically, the segment "{segment}".
7 |
8 | The market segments are as follows:
9 | - School: Grade school or high school students
10 | - University: University students
11 | - Work: People of working age
12 | - Retired: People who are retired
13 |
14 | Connect to the audience by using very strong references to {location}, but don't change its
15 | place of origin nor mention product features not present in the product description below.
16 |
17 | Use markdown syntax for the output.
18 |
19 | -----
20 | {product_description}
21 | """
22 |
--------------------------------------------------------------------------------
/localized-promo-dashboard/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "writer-framework-default"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Your Name "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.10.0"
10 | writer = {version = "^0.6.0rc5"}
11 | flake8 = "^7.1.1"
12 | black = "^24.8.0"
13 | isort = "^5.13.2"
14 | pre-commit = "^3.8.0"
15 |
16 |
17 | [build-system]
18 | requires = ["poetry-core"]
19 | build-backend = "poetry.core.masonry.api"
20 |
--------------------------------------------------------------------------------
/localized-promo-dashboard/static/README.md:
--------------------------------------------------------------------------------
1 | # Serving static files
2 |
3 | You can use this folder to store files which will be served statically in the "/static" route.
4 |
5 | This is useful to store images and other files which will be served directly to the user of your application.
6 |
7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg".
8 | You can use this relative route as the source in an Image component.
9 |
--------------------------------------------------------------------------------
/localized-promo-dashboard/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/localized-promo-dashboard/static/favicon.png
--------------------------------------------------------------------------------
/palmyra-comparison/.env.example:
--------------------------------------------------------------------------------
1 | WRITER_API_KEY=your Writer api key
--------------------------------------------------------------------------------
/palmyra-comparison/.wf/components-root.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "root", "type": "root", "content": {"appName": "Palmyra Comparison"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
--------------------------------------------------------------------------------
/palmyra-comparison/.wf/components-workflows_root.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
--------------------------------------------------------------------------------
/palmyra-comparison/.wf/components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "lfltcky7l1fsm6j2", "type": "workflows_workflow", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "workflows_root", "position": 0}
2 |
--------------------------------------------------------------------------------
/palmyra-comparison/.wf/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "writer_version": "0.8.2"
3 | }
--------------------------------------------------------------------------------
/palmyra-comparison/README.md:
--------------------------------------------------------------------------------
1 | This app was created using Writer Framework.
2 |
3 | To learn more about it, visit https://dev.writer.com/framework
4 |
--------------------------------------------------------------------------------
/palmyra-comparison/main.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import writer as wf
4 | from dotenv import load_dotenv
5 | from writer import WriterState
6 | from writerai import AsyncWriter
7 | from writerai.types.chat_chat_params import Message
8 |
9 | load_dotenv()
10 |
11 | def handle_user_input(payload: str, state: WriterState) -> None:
12 | asyncio.run(_ask_models(payload, state))
13 |
14 |
15 | def handle_prompt_button(context: dict, state: WriterState) -> None:
16 | state["user-prompt"] = state[context["target"]+'-full']
17 | asyncio.run(_ask_models(state[context["target"]+'-full'], state))
18 |
19 |
20 | def handle_dropdown_choice(context: dict, payload: str, state: WriterState) -> None:
21 | if "right" in context["target"]:
22 | state["palmyra-description-right"] = state["palmyra-models-description"][payload]
23 | else:
24 | state["palmyra-description-left"] = state["palmyra-models-description"][payload]
25 |
26 |
27 | async def _ask_models(prompt: str, state: WriterState) -> None:
28 | state["palmyra-left-conversation"].append(Message(role="user", content=prompt))
29 | state["palmyra-right-conversation"].append(Message(role="user", content=prompt))
30 |
31 | async_writer_client = AsyncWriter()
32 |
33 | await asyncio.gather(
34 | _perform_async_streaming(async_writer_client, state["palmyra-left-model"], "left", state),
35 | _perform_async_streaming(async_writer_client, state["palmyra-right-model"], "right", state),
36 | )
37 |
38 |
39 | async def _perform_async_streaming(client: AsyncWriter, model: str, side: str, state: WriterState) -> None:
40 | try:
41 | response = await client.chat.chat(
42 | model=model,
43 | messages=state[f"palmyra-{side}-conversation"],
44 | stream=True,
45 | max_tokens=state[f"palmyra-{side}-max-tokens"],
46 | temperature=state[f"palmyra-{side}-temperature"],
47 | )
48 |
49 | response_message = ""
50 | state[f"palmyra-{side}-response"] = ""
51 |
52 | async for message in response:
53 | content = message.choices[0].delta.content
54 | content = content if content is not None else ""
55 | response_message += content
56 | state[f"palmyra-{side}-response"] += content
57 |
58 | state[f"palmyra-{side}-conversation"].append(Message(role="assistant", content=response_message))
59 |
60 | except Exception as e:
61 | response_message = "Something went wrong. Please, try again."
62 | state[f"palmyra-{side}-conversation"].append(Message(role="assistant", content=response_message))
63 | raise e
64 |
65 | palmyra_models_description = {
66 | "palmyra-x-004": "**Palmyra X 004** is our newest and most advanced language model with a large context window of up to 128,000 tokens. This model excels in processing and understanding complex tasks, making it ideal for workflow automation, coding tasks, and data analysis.",
67 | "palmyra-med": "**Palmyra Medical** is the latest version of our healthcare model and the most accurate in the market. The Writer full-stack generative AI platform is used by the world’s leading Fortune 50 healthcare companies to help improve patient outcomes with powerful AI that are infused with deep medical knowledge.",
68 | "palmyra-fin-32k": "**Palmyra Financial** is Writer’s specialized language model for the finance industry, designed to support critical financial workflows with precision in terminology and document analysis. Palmyra Fin empowers financial organizations to streamline processes and make data-driven decisions confidently.",
69 | "palmyra-creative": "**Palmyra Creative** is Writer's purpose-built language model, engineered to elevate creative thinking and writing across diverse professional contexts. With capabilities that amplify originality and adaptability, it caters to industries and teams where innovation drives success. ",
70 | }
71 |
72 | initial_state = wf.init_state(
73 | {
74 | "palmyra-models": {
75 | "palmyra-x-004": "Palmyra X 004",
76 | "palmyra-med": "Palmyra Medical",
77 | "palmyra-fin-32k": "Palmyra Financial",
78 | "palmyra-creative": "Palmyra Creative",
79 | },
80 | "palmyra-models-description": palmyra_models_description,
81 | "palmyra-left-conversation": [],
82 | "palmyra-left-response": "Model response will appear here...",
83 | "palmyra-left-model": "palmyra-x-004",
84 | "palmyra-left-temperature": 0.7,
85 | "palmyra-left-max-tokens": 16384,
86 | "palmyra-description-left": palmyra_models_description["palmyra-x-004"],
87 | "palmyra-right-conversation": [],
88 | "palmyra-right-response": "Model response will appear here...",
89 | "palmyra-right-model": "palmyra-creative",
90 | "palmyra-right-temperature": 0.7,
91 | "palmyra-right-max-tokens": 16384,
92 | "palmyra-description-right": palmyra_models_description["palmyra-creative"],
93 | "user-prompt": "",
94 | "prompt-left-button": "Brainstorm bakery strategies",
95 | "prompt-left-button-full": "Imagine you're a struggling small-town bakery competing with a chain that opened across the street. Brainstorm unconventional strategies to win over customers without lowering prices.",
96 | "prompt-center-button": "Explain AI to a high schooler",
97 | "prompt-center-button-full": "Write a guide for a programmer who wants to explain their AI side project to a high schooler. The explanation must be engaging, simple, and use humorous analogies, while avoiding technical jargon.",
98 | "prompt-right-button": "Zero gravity game",
99 | "prompt-right-button-full": "Design a game that could only exist in zero gravity.",
100 | }
101 | )
102 |
103 | initial_state.import_stylesheet("style", "/static/custom.css")
104 |
--------------------------------------------------------------------------------
/palmyra-comparison/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "writer-framework-default"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Your Name "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.11"
10 | writer = "0.8.2"
11 | python-dotenv = "^1.0.1"
12 |
13 |
14 | [build-system]
15 | requires = ["poetry-core"]
16 | build-backend = "poetry.core.masonry.api"
17 |
--------------------------------------------------------------------------------
/palmyra-comparison/static/README.md:
--------------------------------------------------------------------------------
1 | # Serving static files
2 |
3 | You can use this folder to store files which will be served statically in the "/static" route.
4 |
5 | This is useful to store images and other files which will be served directly to the user of your application.
6 |
7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg".
8 | You can use this relative route as the source in an Image component.
9 |
--------------------------------------------------------------------------------
/palmyra-comparison/static/custom.css:
--------------------------------------------------------------------------------
1 | .model-response {
2 | height: 55vh !important;
3 | overflow-y: auto !important;
4 | }
5 |
6 | .prompt-input{
7 | max-width: 90vw !important;
8 | }
9 |
10 | .prompt-button{
11 | width: 100vw !important;
12 | }
13 |
14 | .WdsDropdownInput{
15 | width: 100vw !important;
16 | }
--------------------------------------------------------------------------------
/palmyra-comparison/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/palmyra-comparison/static/favicon.png
--------------------------------------------------------------------------------
/patient-portal/README.md:
--------------------------------------------------------------------------------
1 | # Patient interaction portal
2 | This application simulates a chat between a doctor and a patient. Clicking the generate button allows the doctor to generate SOAP notes and extract ICD-10 codes using Palmyra-Med.
3 |
4 | The main functionality of the app is in `main.py`. Prompts are in `prompts.py`.
5 |
6 | ## Running the application
7 | First, install Writer Frameowrk:
8 |
9 | ```sh
10 | pip install writer
11 | ```
12 |
13 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create an API key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`:
14 |
15 | ```sh
16 | export WRITER_API_KEY=your-api-key
17 | ```
18 |
19 | Then, navigate to this folder and run:
20 |
21 | ```sh
22 | writer edit .
23 | ```
24 |
25 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction).
26 |
27 | ## About Writer
28 |
29 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework).
--------------------------------------------------------------------------------
/patient-portal/main.py:
--------------------------------------------------------------------------------
1 | import writer as wf
2 | import writer.ai
3 | from prompts import soap_notes_prompt, icd_codes_prompt
4 |
5 | # Chat event handler
6 | def handle_simple_message(state, payload):
7 | state["conversation"] += payload
8 |
9 | for chunk in state["conversation"].stream_complete():
10 | state["conversation"] += chunk
11 |
12 | # Generate button event handler
13 | def handle_generate_button(state):
14 | state["message"] = "% Generating SOAP notes..."
15 | conversation_history = state["conversation"].serialized_messages
16 | notes_prompt = soap_notes_prompt(conversation_history)
17 | state["notes"] = writer.ai.complete(notes_prompt, config={"model": "palmyra-med-32k", "max_tokens": 1000})
18 |
19 | state["message"] = "% Generating ICD-10 codes..."
20 | codes_prompt = icd_codes_prompt(conversation_history, state["notes"])
21 | state["icd_codes"] = writer.ai.complete(codes_prompt, config={"model": "palmyra-med-32k", "max_tokens": 1000})
22 | state["message"] = None
23 |
24 | # Sample chat history between doctor and patient
25 | history = [
26 | {"role": "user", "content": "Hello. Please tell me about your syptoms."},
27 | {"role": "assistant", "content": "Hello doctor, I've been having some severe lower back pain for a few days now."},
28 | {"role": "user", "content": "I'm sorry to hear about your discomfort. Can you tell me if the pain is localized or does it spread to other areas?"},
29 | {"role": "assistant", "content": "It's mostly in the lower back, but sometimes I feel it shooting down my left leg."},
30 | {"role": "user", "content": "Does anything in particular seem to trigger the pain or make it worse?"},
31 | {"role": "assistant", "content": "It gets worse when I'm sitting for long periods or when I try to stand up."},
32 | {"role": "user", "content": "Have you tried any treatments at home, like applying heat or taking any over-the-counter medications?"},
33 | {"role": "assistant", "content": "I've used a heating pad and taken ibuprofen, but they only provide temporary relief."},
34 | {"role": "user", "content": "Given the nature of your symptoms, it sounds like it could be related to sciatica, especially with the pain radiating down your leg. I'm going to prescribe a medication called gabapentin to help manage the pain and nerve symptoms."},
35 | {"role": "assistant", "content": "Thank you, doctor. How often should I take the medication?"},
36 | {"role": "user", "content": "I'll start you on a low dose, and we can adjust it based on how you respond. You'll take it three times a day. It's important to take it exactly as prescribed to manage your symptoms effectively."},
37 | {"role": "assistant", "content": "Understood. Should I continue with the heating pad and ibuprofen as well?"},
38 | {"role": "user", "content": "Yes, you can continue those as needed for additional relief. We'll review everything during your follow-up visit."},
39 | {"role": "assistant", "content": "I appreciate your help. I'll start the medication and keep you updated on how I feel."}
40 | ]
41 |
42 | # Initialize the state with the conversation history and initial values
43 | wf.init_state({
44 | "my_app": {
45 | "title": "PATIENT INTERACTION PORTAL"
46 | },
47 | "conversation": writer.ai.Conversation(history, config={"model": "palmyra-med-32k"}),
48 | "notes": "SOAP notes from patient interaction will appear here.",
49 | "icd_codes": "Extracted ICD-10 codes will appear here.",
50 | "message": None
51 | })
52 |
--------------------------------------------------------------------------------
/patient-portal/prompts.py:
--------------------------------------------------------------------------------
1 | def soap_notes_prompt(history):
2 | return f'''
3 | System: You are an expert at reviewing medical conversations and generating SOAP format medical notes summarizing those conversations. You will be given a conversation between a doctor and their patient. Review the conversation and create SOAP notes summarizing the encounter.
4 |
5 | Here are examples of SOAP notes. Your SOAP notes must be in the same exact format as these examples:
6 |
7 |
8 | ### Lower back pain
9 | Patient experiencing severe lower back pain, which occasionally radiates down the left leg, indicative of possible sciatica. The symptoms have been present for a few days. The pain intensifies when sitting for prolonged periods or when attempting to stand. Home remedies such as a heating pad and ibuprofen have been tried, providing only temporary relief.\n\n
10 |
11 | Home care reviewed including continuing the use of a heating pad and over-the-counter ibuprofen for pain management. Advised to maintain moderate physical activity as tolerated and avoid prolonged sitting or standing in one position.\n\n\n
12 |
13 | #### Prescription sent \n
14 | Gabapentin \n
15 | Take 1 capsule three times a day \n
16 | Disp: #90 capsules \n
17 | Refills: 1 \n\n
18 |
19 | Patient is advised to monitor symptoms and report any increase in pain or new symptoms. A follow-up visit is recommended to assess the effectiveness of the treatment and make any necessary adjustments.
20 |
21 |
22 |
23 | ### Adult Sinus
24 | Patient experiencing worsening sinus congestion, drainage, and headache for the past 5 days. The patient reports pain around the forehead and cheeks, particularly when bending over, but no fever is present. Home remedies including saline nasal spray and over-the-counter decongestants have been tried with minimal relief.\n\n
25 |
26 | Home care reviewed including continuing the use of saline nasal spray and maintaining hydration. Advised to avoid allergens and pollutants where possible and to use steam inhalation to help alleviate congestion.\n\n\n
27 |
28 | #### Prescriptions sent \n\n
29 | Amoxicillin 500 mg \n
30 | Take 1 capsule three times a day \n
31 | Disp: #30 capsules \n
32 | Refills: 0\n\n
33 |
34 | Fluticasone Propionate Nasal Spray \n
35 | Use two sprays in each nostril once a day \n
36 | Disp: #1 bottle \n
37 | Refills: 1\n\n
38 |
39 | Patient is advised to complete the full course of antibiotics and to monitor symptoms closely. A follow-up is recommended if symptoms persist beyond the treatment period or worsen.
40 |
41 |
42 | Here is the conversation you need to review:
43 |
44 |
45 | {history}
46 |
47 |
48 | Only give me the notes. Do not give me a title nor start your notes with "SOAP Notes". Do use Markdown formatting for your notes, including line breaks and an empty line between each paragraph.
49 |
50 | You must only use information from the conversation to write your SOAP notes. Do not make up any information. Use the same exact format as the examples provided for your SOAP notes, starting with a category as a Markdown header (e.g. "### Lower back pain" or "### Adult Sinus"). If the doctor orders medication(s) within the conversation, list that at the end of the SOAP notes, exactly like the examples. ONLY INCLUDE A MEDICATION IF IT WAS ORDERED - JUST BECAUSE A MEDICATION IS MENTIONED DOES NOT MEAN IT WAS ORDERED.
51 |
52 | If the patient's age is not mentioned in the conversation, then do not mention their age in your SOAP notes.
53 |
54 | Give me the SOAP notes now:
55 | '''
56 |
57 | def icd_codes_prompt(history, notes):
58 | return f'''
59 | System: You are an expert at extracting ICD-10 codes from SOAP format medical notes. You will be given a conversation between a doctor and their patient, the resulting SOAP notes, and asked to extract all relevant ICD-10 codes.
60 |
61 | Here is the conversation you need to review:
62 | {history}
63 |
64 | Here are the SOAP notes you need to review:
65 | {notes}
66 |
67 | Extract all relevant ICD-10 codes from the conversation and SOAP notes.
68 | '''
--------------------------------------------------------------------------------
/patient-portal/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "writer-framework-default"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Your Name "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.10.0"
10 | writer = {version = "^0.6.0"}
11 |
12 |
13 | [build-system]
14 | requires = ["poetry-core"]
15 | build-backend = "poetry.core.masonry.api"
16 |
--------------------------------------------------------------------------------
/patient-portal/static/README.md:
--------------------------------------------------------------------------------
1 | # Serving static files
2 |
3 | You can use this folder to store files which will be served statically in the "/static" route.
4 |
5 | This is useful to store images and other files which will be served directly to the user of your application.
6 |
7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg".
8 | You can use this relative route as the source in an Image component.
9 |
--------------------------------------------------------------------------------
/patient-portal/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/patient-portal/static/favicon.png
--------------------------------------------------------------------------------
/pd-embedded-chat/README.md:
--------------------------------------------------------------------------------
1 | # Embedded Knowledge Graph chat
2 | This Writer Framework application lets you upload files to a Knowledge Graph and then use an embedded no-code chat application to query them with graph-based RAG. The sample data included in the `static` folder is for product descriptions, but you could use this application in any other domain.
3 |
4 | The main functionality of the app is in `main.py`. To run this application, you will need to do three things:
5 |
6 | 1. Create a Knowledge Graph, either in using AI Studio [no-code tools](https://support.writer.com/article/242-how-to-create-and-manage-a-knowledge-graph#How-to-create-a-new-Knowledge-Graph-dlpSX) or [using the API](https://dev.writer.com/api-guides/knowledge-graph).
7 | 2. Create a no-code chat application in AI Studio. You can [follow this guide on our docs](https://dev.writer.com/no-code/building-a-chat-app). Be sure to toggle "Knowledge Graph mode" in the app.
8 | 3. In the "Deploy" tab for the chat application, toggle the "Embed" option for the application and retrieve the embed URL. Check out [this document](https://dev.writer.com/no-code/deploying-an-app) to learn how to do this. The embed URL will go in the `iframe` component's `src` input.
9 |
10 | You may also want to check out the [Knowledge Graph API reference](https://dev.writer.com/api-guides/api-reference/kg-api/create-graph) and the [File API reference](https://dev.writer.com/api-guides/api-reference/file-api/upload-files).
11 |
12 | ## Running the application
13 | First, install Writer Frameowrk:
14 |
15 | ```sh
16 | pip install writer
17 | ```
18 |
19 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create an API key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`:
20 |
21 | ```sh
22 | export WRITER_API_KEY=your-api-key
23 | ```
24 |
25 | Then, navigate to this folder and run:
26 |
27 | ```sh
28 | writer edit .
29 | ```
30 |
31 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction).
32 |
33 | ## About Writer
34 |
35 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework).
--------------------------------------------------------------------------------
/pd-embedded-chat/main.py:
--------------------------------------------------------------------------------
1 | import writer as wf
2 | import writer.ai
3 | import time
4 |
5 | # Replace your Knowledge Graph ID here
6 | GRAPH_ID = "your-kg-id-here"
7 |
8 | # Adjust this timeout and sleep time as needed to avoid spamming the API
9 | MAX_TIMEOUT = 90
10 | SLEEP_TIME = 0.6
11 |
12 | # Load the files and let the user know
13 | def handle_file_upload(state, payload):
14 | state["files"] = payload
15 | if (len(state["files"]) == 1):
16 | state["message"] = f'File {state["files"][0]["name"]} ready to upload.'
17 | else:
18 | state["message"] = f'{len(state["files"])} files ready to upload.'
19 |
20 | # Upload and add file(s) to Knowledge Graph
21 | def handle_add_to_graph(state):
22 | # Retrieve the graph
23 | graph = writer.ai.retrieve_graph(GRAPH_ID)
24 | current_completed = graph.file_status.completed
25 | new_completed = current_completed + len(state["files"])
26 |
27 | # Upload the files
28 | for file in state["files"]:
29 | state["message"] = f'% Kicking off upload of {file["name"]}...'
30 | uploaded_file = writer.ai.upload_file(data=file["data"], name=file["name"], type=file["type"])
31 | graph.add_file(uploaded_file)
32 |
33 | # Poll the graph until all files are indexed and marked as completed
34 | state["message"] = "% Processing and indexing files in Knowledge Graph..."
35 | start_time = time.time()
36 | while (time.time() - start_time) < MAX_TIMEOUT:
37 | try:
38 | updated_graph = writer.ai.retrieve_graph(GRAPH_ID)
39 | if updated_graph.file_status.completed == new_completed:
40 | state["message"] = "+ File(s) successfully added to graph."
41 | return True
42 | except Exception as e:
43 | print(f"Error occurred: {e}")
44 | time.sleep(SLEEP_TIME)
45 | state["message"] = "- Indexing timed out. Please try again."
46 |
47 | # Initialise the state
48 | wf.init_state({
49 | "my_app": {
50 | "title": "Chat with product descriptions"
51 | },
52 | "message": "",
53 | "files": []
54 | })
--------------------------------------------------------------------------------
/pd-embedded-chat/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "writer-framework-default"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Your Name "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.10.0"
10 | writer = {version = "^0.6.0"}
11 |
12 |
13 | [build-system]
14 | requires = ["poetry-core"]
15 | build-backend = "poetry.core.masonry.api"
16 |
--------------------------------------------------------------------------------
/pd-embedded-chat/static/README.md:
--------------------------------------------------------------------------------
1 | # Serving static files
2 |
3 | You can use this folder to store files which will be served statically in the "/static" route.
4 |
5 | This is useful to store images and other files which will be served directly to the user of your application.
6 |
7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg".
8 | You can use this relative route as the source in an Image component.
9 |
--------------------------------------------------------------------------------
/pd-embedded-chat/static/beverage-descriptions.csv:
--------------------------------------------------------------------------------
1 | Name,Description,Feature 1,Feature 2,Feature 3,Key Ingredients
2 | Crimson Cherry Cooler,"Sip on the vibrant taste of Crimson Cherry Cooler, a refreshing blend of tart cherries and sparkling water. Perfect for a sunny day refreshment.",Tart cherry flavor,Sparkling water,Refreshing and vibrant,Cherries, carbonated water, sugar
3 | Azure Apple Fizz,"Dive into the crisp and bubbly Azure Apple Fizz. This sparkling apple cider offers a sweet, refreshing taste with every effervescent sip.",Crisp apple flavor,Effervescent bubbles,Sweet and refreshing,Apple juice, carbonated water, sugar
4 | Mango Melody Mix,"Tropical flavors await in Mango Melody Mix, a smooth blend of ripe mangoes and a hint of coconut. It's like a beach vacation in a bottle.",Ripe mango flavor,Hint of coconut,Smooth tropical blend,Mango puree, coconut water, sugar
5 | Lemon Lime Leap,"Experience the zesty burst of Lemon Lime Leap, a citrus-packed beverage that energizes and refreshes with its tangy flavor.",Zesty citrus flavor,Energizing and refreshing,Tangy taste,Lemon juice, lime juice, sugar, water
6 | Berry Bliss Brew,"Indulge in the sweet symphony of Berry Bliss Brew, a mixed berry tea that combines blueberries, raspberries, and strawberries in a delightful drink.",Mixed berry blend,Sweet and indulgent,Delightful and refreshing,Blueberry, raspberry, strawberry, water
7 | Ginger Gold Rush,"Spice up your day with Ginger Gold Rush, a bold ginger beer that packs a punch with its strong, spicy flavor and a hint of sweetness.",Strong ginger flavor,Spicy with a sweet hint,Bold and invigorating,Ginger, sugar, carbonated water
8 | Peach Sparkle Soda,"Fizz up your life with Peach Sparkle Soda, where juicy peaches meet bubbly soda in this sweet, sparkling treat.",Juicy peach flavor,Bubbly soda,Sweet and sparkling,Peach juice, carbonated water, sugar
9 | Cool Cucumber Quencher,"Refresh and hydrate with Cool Cucumber Quencher, a light and soothing drink infused with cucumber and a splash of mint.",Cucumber infusion,Light and soothing,Splash of mint,Cucumber, mint, water, sugar
10 | Vanilla Velvet Cream,"Savor the rich, creamy taste of Vanilla Velvet Cream, a luxurious vanilla bean milkshake that's smooth and decadently delicious.",Rich vanilla bean flavor,Creamy and luxurious,Smooth and delicious,Vanilla beans, milk, sugar
11 | Tropical Tangerine Twist,"Twist up your routine with Tropical Tangerine Twist, a vibrant blend of tangerine and tropical fruits that offers a refreshing escape.",Vibrant tangerine flavor,Blend of tropical fruits,Refreshing and vibrant,Tangerine, tropical fruits, water, sugar
12 | Pineapple Paradise Punch,"Escape to paradise with Pineapple Paradise Punch, a sweet and tangy pineapple drink that's perfect for sipping under the sun.",Sweet pineapple flavor,Tangy and refreshing,Perfect for sunny days,Pineapple juice, water, sugar
13 | Raspberry Ripple Refresher,"Enjoy the playful swirl of Raspberry Ripple Refresher, a raspberry lemonade that combines sweet raspberries with a tart lemon twist.",Sweet raspberries,Tart lemon twist,Playful and refreshing,Raspberry, lemon juice, sugar, water
14 | Mint Mojito Mocktail,"Cool down with a Mint Mojito Mocktail, a non-alcoholic version of the classic that's just as refreshing with mint, lime, and sparkling water.",Minty freshness,Non-alcoholic,Just as refreshing,Mint, lime, sugar, carbonated water
15 | Spiced Apple Cider,"Warm up with Spiced Apple Cider, a cozy beverage infused with cinnamon and nutmeg, perfect for chilly evenings.",Warm spiced flavor,Cozy and comforting,Perfect for chilly evenings,Apple cider, cinnamon, nutmeg
16 | Blackberry Bramble Blast,"Blast off with Blackberry Bramble Blast, a blackberry infused energy drink that boosts your energy with natural flavors and caffeine.",Blackberry infusion,Energy boosting,Natural flavors and caffeine,Blackberry juice, caffeine, sugar, water
17 | Kiwi Kale Smoothie,"Blend up a healthy treat with Kiwi Kale Smoothie, a nutrient-packed drink combining kiwi, kale, and a hint of honey for sweetness.",Nutrient-packed,Healthy and delicious,Hint of honey,Kiwi, kale, honey, water
18 | Iced Coconut Coffee,"Chill out with Iced Coconut Coffee, a cool blend of robust coffee and creamy coconut, perfect for coffee lovers looking for a tropical twist.",Robust coffee flavor,Creamy coconut blend,Cool and refreshing,Coffee, coconut milk, ice
19 | Strawberry Sunset Slush,"Watch the sunset with a Strawberry Sunset Slush, a frozen strawberry treat that cools and delights with its icy texture and sweet flavor.",Frozen strawberry treat,Icy texture,Sweet and cooling,Strawberry, ice, sugar
20 | Blueberry Basil Booster,"Boost your day with Blueberry Basil Booster, a unique blend of blueberries and basil that revitalizes and refreshes.",Unique blueberry and basil blend,Revitalizing and refreshing,Flavorful and unique,Blueberries, basil, water, sugar
21 | Pomegranate Power Potion,"Power up with Pomegranate Power Potion, a pomegranate-based energy drink that invigorates with antioxidants and a zesty flavor.",Pomegranate base,Antioxidant-rich,Invigorating and zesty,Pomegranate juice, caffeine, sugar
22 | Caramel Latte Luxe,"Luxuriate in Caramel Latte Luxe, a creamy latte with a rich caramel swirl, combining coffee's boldness with the sweetness of caramel.",Creamy latte,Rich caramel swirl,Luxurious and bold,Coffee, milk, caramel
23 | Saffron Sizzle Soda,"Experience the exotic with Saffron Sizzle Soda, a unique soda infused with saffron and a hint of lemon, offering a vibrant and unusual taste.",Saffron infusion,Unique and vibrant,Hint of lemon,Saffron, lemon, sugar, carbonated water
24 | Green Tea Glimmer,"Glimmer through your day with Green Tea Glimmer, a light and refreshing green tea enhanced with jasmine and a touch of honey.",Light green tea,Jasmine enhanced,Touch of honey,Green tea, jasmine, honey
25 | Watermelon Wave Water,"Ride the wave with Watermelon Wave Water, a hydrating watermelon water that quenches thirst with a hint of mint for extra freshness.",Hydrating watermelon water,Quenches thirst,Hint of mint,Watermelon, mint, water
26 | Grapefruit Grove Glimpse,"Get a glimpse of the grove with Grapefruit Grove Glimpse, a grapefruit juice blend that's tart, refreshing, and perfect for grapefruit lovers.",Tart grapefruit flavor,Refreshing juice blend,Perfect for grapefruit lovers,Grapefruit, water, sugar
27 | Almond Amaretto Affair,"Indulge in an Almond Amaretto Affair, a sweet almond-flavored coffee that combines the rich taste of amaretto for a decadent drink.",Sweet almond flavor,Rich amaretto taste,Decadent and indulgent,Coffee, almond flavor, amaretto
28 | Banana Berry Blast,"Go bananas with Banana Berry Blast, a smoothie that combines bananas and mixed berries for a fruity explosion of flavor.",Bananas and mixed berries,Fruity explosion,Smooth and delicious,Bananas, mixed berries, yogurt
29 | Cinnamon Citrus Cyclone,"Get swept away by Cinnamon Citrus Cyclone, a spiced citrus drink that energizes with its bold flavors and a dash of cinnamon.",Spiced citrus flavor,Energizes with bold flavors,Dash of cinnamon,Citrus juice, cinnamon, sugar, water
30 | Pear Peppermint Punch,"Freshen up with Pear Peppermint Punch, a pear-infused drink with a refreshing peppermint twist, perfect for a hot day.",Pear-infused,Refreshing peppermint twist,Perfect for hot days,Pear juice, peppermint, water
31 | Hibiscus Harmony Tea,"Find your harmony with Hibiscus Harmony Tea, a soothing herbal tea made from hibiscus flowers, offering a tart flavor and a floral aroma.",Soothing herbal tea,Tart flavor,Floral aroma,Hibiscus flowers, water, sugar
--------------------------------------------------------------------------------
/pd-embedded-chat/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/pd-embedded-chat/static/favicon.png
--------------------------------------------------------------------------------
/pd-embedded-chat/ui.json:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": {
3 | "writer_version": "0.6.2rc3"
4 | },
5 | "components": {
6 | "root": {
7 | "id": "root",
8 | "type": "root",
9 | "content": {
10 | "appName": "Product chat"
11 | },
12 | "isCodeManaged": false,
13 | "position": 0,
14 | "handlers": {},
15 | "visible": true
16 | },
17 | "c0f99a9e-5004-4e75-a6c6-36f17490b134": {
18 | "id": "c0f99a9e-5004-4e75-a6c6-36f17490b134",
19 | "type": "page",
20 | "content": {
21 | "pageMode": "compact"
22 | },
23 | "isCodeManaged": false,
24 | "position": 0,
25 | "parentId": "root",
26 | "handlers": {},
27 | "visible": true
28 | },
29 | "bebc5fe9-63a7-46a7-b0fa-62303555cfaf": {
30 | "id": "bebc5fe9-63a7-46a7-b0fa-62303555cfaf",
31 | "type": "header",
32 | "content": {
33 | "text": "@{my_app.title}"
34 | },
35 | "isCodeManaged": false,
36 | "position": 0,
37 | "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134",
38 | "handlers": {},
39 | "visible": true
40 | },
41 | "ejpasds0o8qyjs1n": {
42 | "id": "ejpasds0o8qyjs1n",
43 | "type": "section",
44 | "content": {
45 | "title": ""
46 | },
47 | "isCodeManaged": false,
48 | "position": 1,
49 | "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134",
50 | "handlers": {},
51 | "visible": true
52 | },
53 | "8j5hpsp5vf1qas41": {
54 | "id": "8j5hpsp5vf1qas41",
55 | "type": "iframe",
56 | "content": {
57 | "src": ""
58 | },
59 | "isCodeManaged": false,
60 | "position": 3,
61 | "parentId": "ejpasds0o8qyjs1n",
62 | "handlers": {},
63 | "visible": true
64 | },
65 | "jaxy6w53bb8k58v6": {
66 | "id": "jaxy6w53bb8k58v6",
67 | "type": "fileinput",
68 | "content": {
69 | "label": "Please add your product description files.",
70 | "allowMultipleFiles": "yes"
71 | },
72 | "isCodeManaged": false,
73 | "position": 1,
74 | "parentId": "ejpasds0o8qyjs1n",
75 | "handlers": {
76 | "wf-file-change": "handle_file_upload"
77 | },
78 | "visible": true
79 | },
80 | "lllmwrrajtpi5yr9": {
81 | "id": "lllmwrrajtpi5yr9",
82 | "type": "button",
83 | "content": {
84 | "text": "Upload and add to Knowledge Graph",
85 | "icon": "upload"
86 | },
87 | "isCodeManaged": false,
88 | "position": 2,
89 | "parentId": "ejpasds0o8qyjs1n",
90 | "handlers": {
91 | "wf-click": "handle_add_to_graph"
92 | },
93 | "visible": true
94 | },
95 | "xqgibumseiw6k736": {
96 | "id": "xqgibumseiw6k736",
97 | "type": "message",
98 | "content": {
99 | "message": "@{message}"
100 | },
101 | "isCodeManaged": false,
102 | "position": 0,
103 | "parentId": "ejpasds0o8qyjs1n",
104 | "handlers": {},
105 | "visible": "message"
106 | }
107 | }
108 | }
--------------------------------------------------------------------------------
/pdp-generator/README.md:
--------------------------------------------------------------------------------
1 | # Product description page generator and translator
2 | This Writer Framework application lets you upload product description data to generate formatted or translated product description page copy.
3 |
4 | ## Usage
5 |
6 | 1. Upload the data sample from the data folder of the app. Alternatively, you can upload your own data once the column names are the same as those in the sample file.
7 | 2. The application will iterate through the data set, rewrite the long description, and combine and format the features and specification fields.
8 | 3. The output is provided in downloadable HTML and Excel spreadsheets.
9 | 4. There are three languages currently configured for translation: Spanish, French and Hindi. Once the translation button is clicked the app will translate the Excel spreadsheet to all three.
10 |
11 | ## Running the application
12 | First, install Writer Frameowrk:
13 |
14 | ```sh
15 | pip install writer
16 | ```
17 |
18 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create an API key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`:
19 |
20 | ```sh
21 | export WRITER_API_KEY=your-api-key
22 | ```
23 |
24 | Then, navigate to this folder and run:
25 |
26 | ```sh
27 | writer edit .
28 | ```
29 |
30 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction).
31 |
32 | ## About Writer
33 |
34 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework).
35 |
--------------------------------------------------------------------------------
/pdp-generator/data/output-html.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Apex phoneix 39
5 |
6 |
Description: Lace up and conquer your runs with Apex Phoenix 39. Lightweight, durable, and oh-so-comfy, these shoes are your daily training essentials.
7 |
8 |
Product #: DA8725-001
9 |
10 |
Features
11 |
12 |
Lightweight
13 |
Durable rubber outsole
14 |
Cushioned midsole
15 |
Breathable mesh
16 |
17 |
18 |
Specifications
19 |
20 |
Weight : 255g
21 |
Heel Drop : 10mm
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
Terra support 22
30 |
31 |
Description: Experience plush, responsive runs with Terra Support 22. Its knit upper and boosting midsole provide long-distance comfort.
32 |
33 |
Product #: GZ0128
34 |
35 |
Features
36 |
37 |
Comfortable
38 |
Rubber outsole
39 |
Boost midsole
40 |
Primeknit upper
41 |
42 |
43 |
Specifications
44 |
45 |
Weight : 310g
46 |
Heel Drop : 10mm
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
Fly spirit 28
55 |
56 |
Description: Fly Spirit 28: Stability and comfort for overpronators.
57 |
58 |
Product #: 1011B189.001
59 |
60 |
Features
61 |
62 |
Lightweight
63 |
Plus outsole
64 |
Engineered mesh
65 |
66 |
67 |
Specifications
68 |
69 |
Weight : 298g
70 |
Heel Drop : 11mm
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
Instinct Ghost 14
79 |
80 |
Description: Run naturally with Instinct Ghost 14. Smooth transitions and plush cushioning make every stride effortless.
81 |
82 |
Product #: M860A11
83 |
84 |
Features
85 |
86 |
Eco-friendly
87 |
DNA cushioning
88 |
3D Fit Print
89 |
90 |
91 |
Specifications
92 |
93 |
Weight : 300g
94 |
Heel Drop : 12mm
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
Rush One 8
103 |
104 |
Description: Rush One 8: Lightweight, responsive running shoes for a smooth ride.
105 |
106 |
Product #: S20650-10
107 |
108 |
Features
109 |
110 |
Responsive foam midsole
111 |
Breathable mesh upper
112 |
113 |
114 |
Specifications
115 |
116 |
Weight : 234g
117 |
Heel Drop : 13mm
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
Balance 860
126 |
127 |
Description: Glide through your runs with the Balance 860. Experience reliable support and cushioning, mile after mile.
128 |
129 |
Product #: GZ0311
130 |
131 |
Features
132 |
133 |
Blown rubber outsole
134 |
Engineered mesh upper
135 |
136 |
137 |
Specifications
138 |
139 |
Weight : 319g
140 |
Heel Drop : 14mm
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
Ride 14
149 |
150 |
Description: Ride the miles in comfort and responsiveness with Ride 14.
151 |
152 |
Product #: GR54T
153 |
154 |
Features
155 |
156 |
Cushioned
157 |
Cushioning
158 |
FormFit upper
159 |
160 |
161 |
Specifications
162 |
163 |
Weight : 240g
164 |
Heel Drop : 15mm
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
Terra support 19
173 |
174 |
Description: Experience premium comfort and energy return with Terra Support 19.
175 |
176 |
Product #: FRT123
177 |
178 |
Features
179 |
180 |
Durable
181 |
Boosting midsole
182 |
Primeknit upper
183 |
184 |
185 |
Specifications
186 |
187 |
Weight : 190g
188 |
Heel Drop : 16mm
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
Apex Free RN 5.0
197 |
198 |
Description: Feel the freedom of barefoot running with Apex Free RN 5.0. Its flexible design lets you move naturally with every stride.
18 |
19 | """
20 | return html
21 |
--------------------------------------------------------------------------------
/pdp-generator/prompts.py:
--------------------------------------------------------------------------------
1 | def _get_features_prompt(features):
2 | prompt = f"""
3 | You are a Product description page writer and formatter. Take in the following inputs which describe the product's
4 | features and output a HTML bulleted list of the features.
5 |
6 | Instructions:
7 |
8 | - Do not output any Headings for anything else other than the bulleted list of features.
9 | - Do not make up anything. Just reformat the contents of the input into the feature list.
10 | - The Output MUST be formatted as a bulleted list. You will be penalized if you do not include bullets.
11 | - If any of the features are "nan" ignore those lines and do not include them in the output.
12 |
13 | Input: {features}
14 |
15 | Look at the example below to understand the required format of the output
16 |
17 | Example Output :
18 |
19 | - Simplifies the running experience with lightweight, easy-to-use design.
20 | - Enhances performance with features optimized for better energy return and cushioning.
21 | - Adapts to a wide variety of running conditions, providing versatility and comfort on different terrains.
22 |
23 | Output:
24 |
25 | Output:
26 | """.format(
27 | features=features
28 | )
29 | return prompt
30 |
31 |
32 | def _get_specifications_prompt(specifications):
33 | prompt = f"""
34 | You are a Product description page writer and formatter. Take in the following inputs which describe the product's
35 | specifications and output a List of the specifications with the Specification title wrapped in HTML bold tags
36 | and the specification value next to it. Do not output any Headings for anything else other than
37 | the list of specifications. Do not make up anything. Just reformat the contents of the input into the specification
38 | list.
39 |
40 | Input: {specifications}
41 |
42 | Look at the example below to understand the required format of the output. Notice that the format is "Label:
43 | specification value". Ensure there is a colon between the label and the value. There must be a new line space
44 | "\n" between each item.
45 |
46 | You can format the output as a bulleted list.
47 |
48 | Example output:
49 |
50 | - Suitable for a variety of conditions: Road running, trail running, treadmill \n
51 | - Shoe Type: Neutral, Stability, Motion Control \n
52 | - Size Range: US 7-14 \n
53 | - Available in multiple widths: Narrow, Regular, Wide, Extra Wide \n
54 | - Ideal for: Long-distance running, daily training, competitive racing
55 |
56 | Output:
57 | """.format(
58 | specifications=specifications
59 | )
60 | return prompt
61 |
62 |
63 | def _get_description_prompt(desc):
64 | prompt = f"""
65 | You are a Product description page writer and formatter. Take in the following long description for a product and
66 | slightly rewrite it to sound more like the examples below.
67 |
68 | Instructions:
69 |
70 | - Do not output any Headings for anything else other than the modified product description
71 | - Do not make up anything. Make sure none of the facts about the product are changed
72 | - The output length should be less than 150 characters
73 | - Learn the voice and structure from the examples below but do not copy any of the content into your output.
74 | Only reference the voice and structure of the examples.
75 |
76 | Examples:
77 |
78 | 1. Elevate your running game with the StrideMax Pro. Designed for seamless comfort and performance,
79 | these shoes make every run feel effortless. With adaptive cushioning, you’ll feel the difference
80 | from the very first step.
81 |
82 | 2. Take your daily jogs to the next level with SwiftRun 360. Engineered for both beginners and pros, these shoes offer
83 | a perfect blend of support and flexibility. Slip them on, and experience a smooth ride right out of the box.
84 |
85 | 3. Transform your training with the SpeedFlex Elite. Built for endurance and speed, these shoes provide optimal support
86 | in every stride. Easy to break in, they’ll have you hitting your personal bests in no time.
87 |
88 | Input: {desc}
89 |
90 | Output:
91 |
92 | Output:
93 | """.format(
94 | desc=desc
95 | )
96 | return prompt
97 |
98 |
99 | def _get_translation_prompt(text, language):
100 | prompt = f"""
101 | You are a translation service that translates materials for multiple global markets.
102 | You translate text exactly as is, word for word. You do not paraphrase or rewrite anything
103 | with your translation.
104 |
105 | Translate the source provided to language without altering any of the formatting, including hyperlinks,
106 | lists, italicized text, and section headers.
107 |
108 |
109 | 1. DO NOT skip any sections and DO NOT paraphrase anything.
110 | 2. Make sure to retain the spacing formatting of the output and use the same headers and paragraphs in the output.
111 | If bullets, headers, section titles or specific spacing is used you MUST retain the same formatting in the output
112 | including line spaces and bullet points. Do not retain the Bold formatting.
113 | 3. DO NOT make up any information in the output. You will be heavily penalized for including details in your output
114 | that are not present in the original text.
115 | 4. DO not output instructions, only give the translated output of the source text from the input below.
116 | 5. For short pieces of text such as Names of shoes, do not include any additional text aside from the Name
117 | of the shoe. You will get a $200 tip if you follow this rule.
118 |
119 |
120 | source: {text}
121 | language: {language}
122 |
123 |
124 | Output:
125 | """.format(
126 | text=text, language=language
127 | )
128 | return prompt
129 |
--------------------------------------------------------------------------------
/pdp-generator/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "writer-framework-default"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Your Name "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.10.0"
10 | writer = {version = "0.6.2rc2"}
11 | openpyxl = "^3.1.5"
12 |
13 |
14 | [build-system]
15 | requires = ["poetry-core"]
16 | build-backend = "poetry.core.masonry.api"
17 |
--------------------------------------------------------------------------------
/pdp-generator/static/README.md:
--------------------------------------------------------------------------------
1 | # Serving static files
2 |
3 | You can use this folder to store files which will be served statically in the "/static" route.
4 |
5 | This is useful to store images and other files which will be served directly to the user of your application.
6 |
7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg".
8 | You can use this relative route as the source in an Image component.
9 |
--------------------------------------------------------------------------------
/pdp-generator/static/custom.css:
--------------------------------------------------------------------------------
1 | .category{
2 | font-size: larger;
3 | padding-bottom: 1em;
4 | padding-top: 1em;
5 | font-weight: bold;
6 | }
7 |
8 | .summary{
9 | padding-top: 1em;
10 | padding-bottom: 1em;
11 | margin-left: 1em;
12 | }
13 |
14 | .description{
15 | margin-left: 3em;
16 | color:gray
17 | }
18 |
19 | .styled-table {
20 | width: 70%;
21 | max-width: 100%;
22 | margin: 20px auto;
23 | border-collapse: collapse;
24 | font-family: 'Arial', sans-serif;
25 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
26 | }
27 |
28 | /* Styling for table headers */
29 | .styled-table thead th {
30 | background-color: #009879;
31 | color: #ffffff;
32 | padding: 12px 15px;
33 | text-align: left;
34 | }
35 |
36 | /* Styling for table cells */
37 | .styled-table tbody td {
38 | border: 1px solid #dddddd;
39 | padding: 12px 15px;
40 | color: #333333;
41 | }
42 |
43 | /* Alternate row styling for better readability */
44 | .styled-table tbody tr:nth-child(even) {
45 | background-color: #f3f3f3;
46 | }
47 |
48 | /* Hover effect for rows */
49 | .styled-table tbody tr:hover {
50 | background-color: #f1f1f1;
51 | }
52 |
53 | /* Styling for the scrolling container */
54 | .scroll-container {
55 | overflow-y: auto;
56 | height: 300px; /* Adjust height as needed */
57 | }
58 |
59 | .headerCell{
60 | white-space: nowrap !important;
61 | }
62 |
--------------------------------------------------------------------------------
/pdp-generator/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/pdp-generator/static/favicon.png
--------------------------------------------------------------------------------
/pdp-generator/static/writer_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/pdp-generator/static/writer_logo.png
--------------------------------------------------------------------------------
/portfolio-rebalance/README.md:
--------------------------------------------------------------------------------
1 | # Portfolio rebalancing recommendation app
2 |
3 | This Writer Framework application uses the powerful Palmyra-Fin model to analyze portfolio data and provide recommendations for rebalancing an investment portfolio. You can also download the analysis as a text file and clear results to start over. This app supports PDF or Excel files.
4 |
5 | ## Usage
6 |
7 | 1. Upload the data sample from the `example_data` folder of the app. Alternatively, you can upload your own portfolio data in Excel (.xls, .xlsx) or PDF format. Note that you may need adjust the prompts in `prompts.py` to achieve the best results.
8 | 2. Once the file is uploaded, the application analyzes your portfolio data, highlighting both positive and negative aspects such as stock selection, sector weightings, and more.
9 | 3. Based on this analysis, the app generates recommendations for rebalancing your portfolio to optimize performance.
10 |
11 | ## Running the application
12 |
13 | First, ensure you have Poetry installed. Then, in the project directory, install the dependencies by running:
14 |
15 | ```sh
16 | poetry install
17 | ```
18 |
19 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new API Key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`:
20 |
21 | ```sh
22 | export WRITER_API_KEY=your-api-key
23 | ```
24 |
25 | To make changes or edit the application, navigate to root folder and use the following command:
26 |
27 |
28 | ```sh
29 | writer edit .
30 | ```
31 |
32 | Depending on how your environment is set up, you may need to run `writer` with `poetry run` like this:
33 |
34 | ```sh
35 | poetry run writer edit .
36 | ```
37 |
38 | Once you're ready to run the application, execute:
39 |
40 | ```sh
41 | writer run .
42 | ```
43 |
44 | To learn more, check out the [full documentation for Writer Framework](https://dev.writer.com/framework/introduction).
45 |
46 | ## About Writer
47 |
48 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework).
49 |
--------------------------------------------------------------------------------
/portfolio-rebalance/example_data/portfolio_data.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/portfolio-rebalance/example_data/portfolio_data.pdf
--------------------------------------------------------------------------------
/portfolio-rebalance/example_data/portfolio_data.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/portfolio-rebalance/example_data/portfolio_data.xlsx
--------------------------------------------------------------------------------
/portfolio-rebalance/main.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import os
3 | import shutil
4 |
5 | import pandas as pd
6 | import writer as wf
7 | import writer.ai
8 | from dotenv import load_dotenv
9 |
10 | from prompts import (generate_negative_concatenation_prompt,
11 | generate_negative_impacts_prompt,
12 | generate_negative_stock_selection_prompt,
13 | generate_positive_concatenation_prompt,
14 | generate_positive_impacts_prompt,
15 | generate_positive_sector_weighting_prompt,
16 | generate_positive_stock_selection_prompt,
17 | generate_rebalance_recommendation_prompt)
18 |
19 | load_dotenv()
20 |
21 | pd.options.mode.chained_assignment = None
22 |
23 |
24 | def handle_file_on_change(state, payload):
25 | clear_results(state)
26 | _save_file(state, payload[0])
27 |
28 | file_extension = state["file"]["name"].split(".")[-1].lower()
29 |
30 | match file_extension:
31 | case "xlsx" | "xls":
32 | df = _read_excel(state)
33 | case "pdf":
34 | text_data = _read_pdf(state)
35 | case _:
36 | state["processing-message"] = "Unsupported file type"
37 | return
38 |
39 | state["processing-message"] = "Analyzing positive and negative impacts..."
40 | prompt = analyze_data(
41 | df if file_extension in ["xlsx", "xls"] else text_data
42 | )
43 |
44 | state["processing-message"] = "Generating rebalancing recommendation..."
45 | state["analysis-result"] = ""
46 |
47 | # Using Palmyra-Fin model
48 | # for chunk in writer.ai.stream_complete(prompt, {"model": "palmyra-fin-32k", "max_tokens": 2048, "temperature": 0.7}):
49 | # state["analysis-result"] += chunk
50 |
51 | # Using Palmyra X 004 model
52 | conversation = writer.ai.Conversation([{"role": "user", "content": prompt}], {"model": "palmyra-x-004", "max_tokens": 2048, "temperature": 0.7})
53 | for chunk in conversation.stream_complete():
54 | if chunk.get("content"):
55 | state["analysis-result"] += chunk.get("content")
56 |
57 | state["visual_block_visible"] = True
58 | state["processing-message"] = ""
59 |
60 |
61 | def clear_results(state):
62 | state["analysis-result"] = "Your analysis will appear here."
63 | _delete_all_files(state)
64 | state["visual_block_visible"] = False
65 |
66 |
67 | def _save_file(state, file):
68 | name = file.get("name")
69 | state["file"]["name"] = name
70 | state["file"]["file_path"] = f"data/{name}"
71 | state["processing-message"] = f"File {name} saved."
72 | file_data = file.get("data")
73 | with open(f"data/{name}", "wb") as file_handle:
74 | file_handle.write(file_data)
75 |
76 |
77 | def _delete_all_files(state):
78 | directory = "data"
79 |
80 | if os.path.exists(directory):
81 | shutil.rmtree(directory)
82 |
83 | os.makedirs(directory)
84 |
85 | state["file"]["name"] = ""
86 | state["file"]["file_path"] = ""
87 | state["processing-message"] = "All files have been deleted."
88 |
89 |
90 | def _read_excel(state):
91 | data = pd.read_excel(state["file"]["file_path"])
92 | df = pd.DataFrame(data)
93 | return df
94 |
95 |
96 | def _read_pdf(state):
97 | from PyPDF2 import PdfReader
98 |
99 | reader = PdfReader(state["file"]["file_path"])
100 | text = ""
101 | for page in reader.pages:
102 | text += page.extract_text()
103 | return text
104 |
105 | # Create async tasks for each prompt and then generate the final prompt
106 | async def gather_results_and_generate_rebalance_prompt(data: str):
107 | async def complete_async(prompt):
108 | return await asyncio.to_thread(writer.ai.complete, prompt, {"model": "palmyra-fin-32k", "max_tokens": 3048, "temperature": 0.7})
109 |
110 | positive_stock_selection_task = complete_async(
111 | generate_positive_stock_selection_prompt(data)
112 | )
113 |
114 | positive_sector_weighting_task = complete_async(
115 | generate_positive_sector_weighting_prompt(data)
116 | )
117 |
118 | positive_concatenation_task = complete_async(
119 | generate_positive_concatenation_prompt(data)
120 | )
121 |
122 | positive_impacts_task = complete_async(generate_positive_impacts_prompt(data))
123 |
124 | negative_stock_selection_task = complete_async(
125 | generate_negative_stock_selection_prompt(data)
126 | )
127 |
128 | negative_concatenation_task = complete_async(
129 | generate_negative_concatenation_prompt(data)
130 | )
131 |
132 | negative_impacts_task = complete_async(generate_negative_impacts_prompt(data))
133 |
134 | (
135 | positive_stock_selection,
136 | positive_sector_weighting,
137 | positive_concatenation,
138 | positive_impacts,
139 | negative_stock_selection,
140 | negative_concatenation,
141 | negative_impacts,
142 | ) = await asyncio.gather(
143 | positive_stock_selection_task,
144 | positive_sector_weighting_task,
145 | positive_concatenation_task,
146 | positive_impacts_task,
147 | negative_stock_selection_task,
148 | negative_concatenation_task,
149 | negative_impacts_task,
150 | )
151 |
152 | final_prompt = generate_rebalance_recommendation_prompt(
153 | positive_stock_selection,
154 | positive_sector_weighting,
155 | positive_concatenation,
156 | positive_impacts,
157 | negative_stock_selection,
158 | negative_concatenation,
159 | negative_impacts,
160 | )
161 |
162 | return final_prompt
163 |
164 |
165 | def analyze_data(data):
166 | if isinstance(data, pd.DataFrame):
167 | data_str = data.to_string()
168 | else:
169 | data_str = data
170 |
171 | return asyncio.run(
172 | gather_results_and_generate_rebalance_prompt(data_str)
173 | )
174 |
175 |
176 | def handle_file_download(state):
177 | analysis_result = state["analysis-result"]
178 |
179 | if analysis_result:
180 | with open("data/analysis_result.txt", "w") as file_handle:
181 | file_handle.write(analysis_result)
182 |
183 | file_data = wf.pack_file("data/analysis_result.txt", "text/plain")
184 | state.file_download(file_data, "analysis_result.txt")
185 |
186 |
187 | initial_state = wf.init_state(
188 | {
189 | "image-path": "static/writer_logo.png",
190 | "app": {"title": "Recommendations for Rebalancing Portfolio"},
191 | "file": {"name": "", "file_path": ""},
192 | "analysis-result": "Your recommendations will appear here.",
193 | "processing-message": "",
194 | "visual_block_visible": False
195 | }
196 | )
197 |
198 |
199 | initial_state.import_stylesheet("style", "/static/custom.css?")
200 |
--------------------------------------------------------------------------------
/portfolio-rebalance/prompts.py:
--------------------------------------------------------------------------------
1 | def generate_positive_stock_selection_prompt(past_quarter_results):
2 | prompt = f"""
3 | For the ClearBridge Large Cap Growth Fund, when compared to the benchmark of Russell 1000 Growth, which stock sectors had the highest Selection + Interaction? Do not provide more than two sectors.
4 |
5 | Please only list the names of the sectors. Do not list more than two and do not put them in an ordered list.
6 |
7 | Use this document to help answer your question: {past_quarter_results}
8 | """
9 | return prompt
10 |
11 |
12 | def generate_positive_sector_weighting_prompt(past_quarter_results):
13 | prompt = f"""
14 | For the ClearBridge Large Cap Growth Fund, which two sectors had the highest Total Effect?
15 |
16 | Please only list the names of the sectors. Do not provide more than two.
17 |
18 | Use this document to help answer your question: {past_quarter_results}
19 | """
20 | return prompt
21 |
22 |
23 | def generate_positive_concatenation_prompt(past_quarter_results):
24 | prompt = f"""
25 | Stock sectors: {past_quarter_results}
26 |
27 | For the two stock sectors mentioned above, format a response as: "stock selection in the X sector and Y sector contributed to the performance", where X is the first sector mentioned above and Y is the second sector mentioned above. Do not capitalize the first word of the sentence.
28 | """
29 | return prompt
30 |
31 |
32 | def generate_negative_stock_selection_prompt(past_quarter_results):
33 | prompt = f"""
34 | For the ClearBridge Large Cap Growth Fund, when compared to the benchmark of Russell 1000 Growth, which stock sectors had the lowest Selection + Interaction? Do not provide more than two sectors.
35 |
36 | Please only list the names of the sectors. Do not list more than two.
37 |
38 | Use this document to help answer your question: {past_quarter_results}
39 | """
40 | return prompt
41 |
42 |
43 | def generate_negative_concatenation_prompt(negative_stock_selection):
44 | prompt = f"""
45 | Stock sectors: {negative_stock_selection}
46 |
47 | For the two stock sectors mentioned above, format a response as: "stock selection in the X sector and Y sector detracted from performance", where X is the first sector mentioned above and Y is the second sector mentioned above. Do not capitalize the first word of the sentence.
48 | """
49 | return prompt
50 |
51 |
52 | def generate_positive_impacts_prompt(past_quarter_results):
53 | prompt = f"""
54 | Give an answer to the question: What individual stocks contributed the most to positive returns? Your answer should be in the format: "In terms of individual stocks, the greatest contributors to returns included the Portfolios' positions in [Company A], [Company B], [Company C], [Company D], and [Company E]."
55 |
56 | Here's the data sheet that can be used to answer that question: {past_quarter_results}
57 |
58 | Three things to keep in mind:
59 |
60 | 1. The data sheet has the full names of companies, but you only need to report on its short name. For example, instead of reporting as London Stock Exchange Group plc, you should just say London Stock Exchange Group.
61 | 2. You should only list a maximum of five individual stocks.
62 | 3. The examples above should strictly be used for format advice. Only use stocks mentioned in the data sheet in your response.
63 |
64 | Please provide your answer in this format.
65 | """
66 | return prompt
67 |
68 |
69 | def generate_negative_impacts_prompt(past_quarter_results):
70 | prompt = f"""
71 | Give an answer to the question: What individual stocks detracted the most from returns? Your answer should be in the format: "In terms of individual stocks, the greatest detractors from returns included the Portfolios' positions in [Company A], [Company B], [Company C], [Company D], and [Company E]."
72 |
73 | Here's the data sheet that can be used to answer that question: {past_quarter_results}
74 |
75 | Three things to keep in mind:
76 |
77 | 1. The data sheet has the full names of companies, but you only need to report its short name. For example, instead of reporting as London Stock Exchange Group plc, you should just say London Stock Exchange Group.
78 | 2. You should only list a maximum of five individual stocks.
79 | 3. The examples above should strictly be used for format advice. Only use stocks mentioned in the data sheet in your response.
80 |
81 | Please provide your answer in this format.
82 | """
83 | return prompt
84 |
85 |
86 | def generate_rebalance_recommendation_prompt(
87 | positive_stock_selection,
88 | positive_sector_weighting,
89 | positive_concatenation,
90 | positive_impacts,
91 | negative_stock_selection,
92 | negative_concatenation,
93 | negative_impacts,
94 | ):
95 |
96 | prompt = f"""
97 | Based on the past results from last quarter, please write a step-by-step analysis of how to rebalance the portfolio.
98 |
99 | Here are the results:
100 |
101 | Positive Stock Selection:
102 | For the last quarter relative to the benchmark, the highest positive stock selection was in the following sectors: {positive_stock_selection}.
103 |
104 | Positive Sector Weighting:
105 | The sectors with the highest total effect were: {positive_sector_weighting}.
106 |
107 | Positive Impacts:
108 | Stock selection in {positive_concatenation} contributed to the performance.
109 | In terms of individual stocks, the greatest contributors to returns included: {positive_impacts}.
110 |
111 | Negative Stock Selection:
112 | The sectors with the lowest stock selection and interaction were: {negative_stock_selection}.
113 |
114 | Negative Impacts:
115 | Stock selection in {negative_concatenation} detracted from the performance.
116 | In terms of individual stocks, the greatest detractors from returns included: {negative_impacts}.
117 |
118 | Based on this analysis, please provide your positive impacts, negative impacts and recommendations for rebalancing the portfolio. In your recommendation, be sure to name specific stocks as well as industries.Do not include any extraneous information around general portfolio rebalancing process or additional general resources.
119 | """
120 |
121 | return prompt
122 |
--------------------------------------------------------------------------------
/portfolio-rebalance/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "writer-framework-default"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Your Name "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.10"
10 | writer = "0.7.5"
11 | openpyxl = "3.1.5"
12 | pandas = "2.2.3"
13 | pypdf2 = "3.0.1"
14 | python-dotenv = "1.0.1"
15 | python-docx = "1.1.2"
16 | asyncio = "^3.4.3"
17 |
18 |
19 | [tool.poetry.group.dev.dependencies]
20 | flake8 = "^7.1.1"
21 | black = "^24.8.0"
22 | isort = "^5.13.2"
23 |
24 | [build-system]
25 | requires = ["poetry-core"]
26 | build-backend = "poetry.core.masonry.api"
27 |
--------------------------------------------------------------------------------
/portfolio-rebalance/static/README.md:
--------------------------------------------------------------------------------
1 | # Serving static files
2 |
3 | You can use this folder to store files which will be served statically in the "/static" route.
4 |
5 | This is useful to store images and other files which will be served directly to the user of your application.
6 |
7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg".
8 | You can use this relative route as the source in an Image component.
9 |
--------------------------------------------------------------------------------
/portfolio-rebalance/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/portfolio-rebalance/static/favicon.png
--------------------------------------------------------------------------------
/portfolio-rebalance/static/writer_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/portfolio-rebalance/static/writer_logo.png
--------------------------------------------------------------------------------
/prescribing-info-app/.env.example:
--------------------------------------------------------------------------------
1 | WRITER_API_KEY=your_writer_api_key
2 | GRAPH_ID=your_writer_graph_id
3 |
--------------------------------------------------------------------------------
/prescribing-info-app/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "type": "page", "content": {"key": "drug-info", "pageMode": "compact"}, "handlers": {}, "isCodeManaged": false, "parentId": "root", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
2 | {"id": "f0jnnqbgaq9egozo", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 0}
3 | {"id": "5l0szzvnaaxlt8tl", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "f0jnnqbgaq9egozo", "position": 0}
4 | {"id": "se73vd8nc8w057x4", "type": "header", "content": {"text": "Prescribing Information"}, "handlers": {}, "isCodeManaged": false, "parentId": "5l0szzvnaaxlt8tl", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
5 | {"id": "zh10210i1bawmqp3", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "f0jnnqbgaq9egozo", "position": 1}
6 | {"id": "820nw2mf596ezam3", "type": "horizontalstack", "content": {"contentHAlign": "right"}, "isCodeManaged": false, "parentId": "zh10210i1bawmqp3", "position": 0}
7 | {"id": "medicine", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "icon": "", "text": "Drug info \ud83d\udc8a"}, "handlers": {"wf-click": "$goToPage_drug-info"}, "isCodeManaged": false, "parentId": "820nw2mf596ezam3", "position": 0}
8 | {"id": "chat", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "icon": "", "text": "Chat \ud83d\udcac"}, "handlers": {"wf-click": "$goToPage_chat"}, "isCodeManaged": false, "parentId": "820nw2mf596ezam3", "position": 1}
9 | {"id": "c5f7bp0fjv7ew40w", "type": "section", "content": {"title": "Availiable drugs"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 1}
10 | {"id": "bdts4z8blerl33fw", "type": "tags", "content": {"tags": "{\n \"Xeljanz\": \"Xeljanz\",\n \"Stelara\": \"Stelara\",\n \"Dupixent\": \"Dupixent\",\n \"Lucentis\": \"Lucentis\",\n \"Bavencio\": \"Bavencio\",\n \"Trulicity\": \"Trulicity\",\n \"Entresto\": \"Entresto\",\n \"Humira\": \"Humira\",\n \"Tagrisso\": \"Tagrisso\"\n}"}, "handlers": {"wf-tag-click": "drug_switch"}, "isCodeManaged": false, "parentId": "c5f7bp0fjv7ew40w", "position": 0}
11 | {"id": "mhhffykuw46c3xgi", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 2}
12 | {"id": "0mpz722m992c8h06", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "mhhffykuw46c3xgi", "position": 0}
13 | {"id": "fm9r15fyiysf5fhl", "type": "section", "content": {"title": "Adverse reactions"}, "handlers": {}, "isCodeManaged": false, "parentId": "0mpz722m992c8h06", "position": 0}
14 | {"id": "fdvy87wyzk0pyg27", "type": "text", "content": {"cssClasses": "adv-reactions-scroller", "text": "@{adverse_reactions}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "fm9r15fyiysf5fhl", "position": 0}
15 | {"id": "693ecrbqbc942f6x", "type": "column", "content": {"width": "3"}, "handlers": {}, "isCodeManaged": false, "parentId": "mhhffykuw46c3xgi", "position": 1}
16 | {"id": "xpjwnx9jurnxuy4z", "type": "tabs", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "693ecrbqbc942f6x", "position": 0}
17 | {"id": "y1x1a8xb2cdwfg77", "type": "tab", "content": {"cssClasses": "", "name": "Full prescribing info"}, "handlers": {}, "isCodeManaged": false, "parentId": "xpjwnx9jurnxuy4z", "position": 0}
18 | {"id": "4eg5lxq1vle8m9h4", "type": "text", "content": {"cssClasses": "prescr-info-scroller", "text": "@{raw_prescribing_info}\n", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "y1x1a8xb2cdwfg77", "position": 0}
19 | {"id": "3pb0e6kmi4soloz8", "type": "tab", "content": {"cssClasses": "", "name": "Summary of prescribing info"}, "handlers": {}, "isCodeManaged": false, "parentId": "xpjwnx9jurnxuy4z", "position": 1}
20 | {"id": "jz845bowxcjm606y", "type": "text", "content": {"cssClasses": "prescr-info-scroller", "text": "@{prescribing_info_summary}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "3pb0e6kmi4soloz8", "position": 0}
21 | {"id": "ob300350xax1esrg", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "mhhffykuw46c3xgi", "position": 2}
22 | {"id": "b0sg45pzixq3ra3w", "type": "section", "content": {"cssClasses": "pubmed-scroller", "title": "Related PubMed articles"}, "handlers": {}, "isCodeManaged": false, "parentId": "ob300350xax1esrg", "position": 0}
23 | {"id": "hnmadv68pz0jeak8", "type": "repeater", "content": {"keyVariable": "itemId", "repeaterObject": "@{related_pubmed_articles}", "valueVariable": "item"}, "handlers": {}, "isCodeManaged": false, "parentId": "b0sg45pzixq3ra3w", "position": 0}
24 | {"id": "vowbh61n9cbzv8ko", "type": "tags", "content": {"seed": "0", "tags": "{\n \"1\": \"@{item.Publication Date}\"\n}"}, "handlers": {}, "isCodeManaged": false, "parentId": "hnmadv68pz0jeak8", "position": 0}
25 | {"id": "i4sm8m3k228y7faw", "type": "text", "content": {"text": "@{item.Title}"}, "handlers": {}, "isCodeManaged": false, "parentId": "hnmadv68pz0jeak8", "position": 1}
26 | {"id": "drihhplzfxomn2ga", "type": "link", "content": {"primaryTextColor": "#5551ff", "target": "", "text": "PubMed", "url": "@{item.Article Link}"}, "handlers": {}, "isCodeManaged": false, "parentId": "hnmadv68pz0jeak8", "position": 2}
27 |
--------------------------------------------------------------------------------
/prescribing-info-app/.wf/components-page-1-181cq1mpgw34i8dl.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "181cq1mpgw34i8dl", "type": "page", "content": {"key": "chat", "pageMode": "compact"}, "handlers": {}, "isCodeManaged": false, "parentId": "root", "position": 1, "visible": {"binding": "", "expression": true, "reversed": false}}
2 | {"id": "b6cillcjhtpg71tk", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "181cq1mpgw34i8dl", "position": 0}
3 | {"id": "5dxznhfw18bkp88o", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "b6cillcjhtpg71tk", "position": 0}
4 | {"id": "66m0u100zicdr0j3", "type": "header", "content": {"text": "Chat with Prescribing Information"}, "handlers": {}, "isCodeManaged": false, "parentId": "5dxznhfw18bkp88o", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
5 | {"id": "ptn01trkgrzmc11v", "type": "column", "content": {"width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "b6cillcjhtpg71tk", "position": 1}
6 | {"id": "y1x6gru1icypiguh", "type": "horizontalstack", "content": {"contentHAlign": "right"}, "isCodeManaged": false, "parentId": "ptn01trkgrzmc11v", "position": 0}
7 | {"id": "medicine1", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "icon": "", "text": "Drug info \ud83d\udc8a"}, "handlers": {"wf-click": "$goToPage_drug-info"}, "isCodeManaged": false, "parentId": "y1x6gru1icypiguh", "position": 0}
8 | {"id": "chat1", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "icon": "", "text": "Chat \ud83d\udcac"}, "handlers": {"wf-click": "$goToPage_chat"}, "isCodeManaged": false, "parentId": "y1x6gru1icypiguh", "position": 1}
9 | {"id": "3yv1o3158qk6ewrp", "type": "section", "content": {"title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "181cq1mpgw34i8dl", "position": 1}
10 | {"id": "0na7dihjazzdlbjf", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "3yv1o3158qk6ewrp", "position": 0}
11 | {"id": "yl0x2lefilp1xnl6", "type": "column", "content": {"width": "13"}, "handlers": {}, "isCodeManaged": false, "parentId": "0na7dihjazzdlbjf", "position": 0}
12 | {"id": "t76ns9nd93w1aaac", "type": "chatbot", "content": {"conversation": "@{conversation}", "useMarkdown": "yes"}, "handlers": {"wf-chatbot-message": "user_message"}, "isCodeManaged": false, "parentId": "yl0x2lefilp1xnl6", "position": 0}
13 | {"id": "4dyb1959ehmz6hoh", "type": "text", "content": {"text": "@{contributing_sources_button_text}"}, "handlers": {"wf-click": "contributing_sources_change_vis"}, "isCodeManaged": false, "parentId": "yl0x2lefilp1xnl6", "position": 1}
14 | {"id": "6gunwa2x9r6fw6c4", "type": "column", "content": {"title": "Contributing sources", "width": "7"}, "handlers": {}, "isCodeManaged": false, "parentId": "0na7dihjazzdlbjf", "position": 1, "visible": {"binding": "contributing_sources_vis", "expression": "custom", "reversed": false}}
15 | {"id": "rfpjjy0krwyn5cfo", "type": "columns", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "6gunwa2x9r6fw6c4", "position": 0}
16 | {"id": "h8gtll4cw37fh3hf", "type": "column", "content": {"contentPadding": "0px 5px 0px 0px", "cssClasses": "files-list", "width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "rfpjjy0krwyn5cfo", "position": 0}
17 | {"id": "gq06l9kpvbeqkee1", "type": "repeater", "content": {"keyVariable": "index", "repeaterObject": "@{contributing_sources}", "valueVariable": "file"}, "handlers": {}, "isCodeManaged": false, "parentId": "h8gtll4cw37fh3hf", "position": 0}
18 | {"id": "0ggjzzhqd8vgttkg", "type": "section", "content": {"cssClasses": "@{file.file_css}", "isCollapsible": "", "startCollapsed": "", "title": ""}, "handlers": {}, "isCodeManaged": false, "parentId": "gq06l9kpvbeqkee1", "position": 0}
19 | {"id": "avu7ft9wkylm5mho", "type": "text", "content": {"text": "#### @{file.name}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "0ggjzzhqd8vgttkg", "position": 0}
20 | {"id": "nnirvi5yma8wsswp", "type": "text", "content": {"cssClasses": "@{file.content_css}", "text": "@{file.content}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "0ggjzzhqd8vgttkg", "position": 1}
21 |
--------------------------------------------------------------------------------
/prescribing-info-app/.wf/components-root.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "root", "type": "root", "content": {"appName": "Prescribing Information App"}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
--------------------------------------------------------------------------------
/prescribing-info-app/.wf/components-workflows_root.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
--------------------------------------------------------------------------------
/prescribing-info-app/.wf/components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl:
--------------------------------------------------------------------------------
1 | {"id": "lfltcky7l1fsm6j2", "type": "workflows_workflow", "content": {}, "handlers": {}, "isCodeManaged": false, "parentId": "workflows_root", "position": 0}
2 |
--------------------------------------------------------------------------------
/prescribing-info-app/.wf/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "writer_version": "0.8.2"
3 | }
--------------------------------------------------------------------------------
/prescribing-info-app/README.md:
--------------------------------------------------------------------------------
1 | # Prescribing Information App
2 |
3 | This is a Python application built with [Writer Framework](https://github.com/writer/writer-framework) that provides detailed prescribing information, side effects, and related PubMed articles for medications. The app uses the OpenFDA API and PubMed database to gather comprehensive drug information and present it in an easily digestible format. This application uses [Palmyra Med](https://huggingface.co/Writer/Palmyra-Med-70B) to summarize the prescribing information and our [graph-based RAG tool](https://dev.writer.com/api-guides/kg-chat) powered by Palmyra X 004 to enable users to ask questions about the fetched information.
4 |
5 | To learn more about Writer Framework, check out our [comprehensive docs](https://dev.writer.com/framework/introduction).
6 |
7 | ## Features
8 |
9 | - Fetch and display full prescribing information from OpenFDA API
10 | - Show drug side effects and adverse reactions
11 | - Search and display related PubMed articles
12 | - Automatic summarization of prescribing information using Palmyra Med
13 | - Interactive chat interface for drug-related queries using our graph-based RAG tool
14 | - Display of contributing sources for information
15 |
16 | ## Prerequisites
17 |
18 | - Python 3.11 or higher
19 | - Poetry for dependency management
20 | - Writer Framework API Key (follow the [Writer Framework Quickstart guide](https://dev.writer.com/framework/quickstart) to get one)
21 | - Knowledge Graph ID (you can create a Knowledge Graph [through AI Studio](https://support.writer.com/article/242-how-to-create-and-manage-a-knowledge-graph) or [through the API](https://dev.writer.com/api-guides/knowledge-graph))
22 |
23 | ## Installation
24 |
25 | 1. Clone the repository and navigate to the project directory:
26 |
27 | ```sh
28 | cd prescribing-info-app
29 | ```
30 |
31 | 2. Install dependencies using Poetry:
32 |
33 | ```sh
34 | poetry install
35 | ```
36 |
37 | 3. Create a `.env` file in the project root and add your credentials:
38 |
39 | ```sh
40 | WRITER_API_KEY=your-api-key
41 | GRAPH_ID=your-graph-id
42 | ```
43 |
44 | ## Usage
45 |
46 | 1. Activate the Poetry virtual environment:
47 |
48 | ```sh
49 | poetry shell
50 | ```
51 |
52 | 2. Run the application:
53 |
54 | ```sh
55 | writer run .
56 | ```
57 |
58 | 3. To make changes or edit the application:
59 |
60 | ```sh
61 | writer edit .
62 | ```
63 |
64 | ## Project Structure
65 |
66 | - `main.py` - Core application logic and Writer Framework integration
67 | - `utils.py` - Utility functions for API interactions and data processing
68 | - `prompts.py` - Prompt templates
69 | - `static/` - Static files and CSS
70 | - `.env` - Environment variables (not tracked in git)
71 |
72 | ## About Writer
73 |
74 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=devrel).
--------------------------------------------------------------------------------
/prescribing-info-app/main.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import json
3 | import os
4 | from datetime import datetime
5 | from typing import Optional
6 |
7 | import writer as wf
8 | from dotenv import load_dotenv
9 | from prompts import prescribing_summary_prompt
10 | from utils import (
11 | _get_drug_side_effects,
12 | _get_full_prescribing_info,
13 | _get_pubmed_sheets,
14 | _parse_prescribing_info,
15 | _upload_file_and_add_to_graph,
16 | )
17 | from writer import WriterState
18 | from writer.ai import Conversation, File, retrieve_file, retrieve_graph, stream_complete
19 |
20 | load_dotenv()
21 |
22 | GRAPH_ID = os.getenv("GRAPH_ID", "")
23 |
24 |
25 | def drug_switch(state: WriterState, payload: str) -> None:
26 | update_drug_data(state, payload)
27 |
28 |
29 | def update_drug_data(state: WriterState, drug_name: str) -> None:
30 | prescribing_info, adverse_reactions = asyncio.run(
31 | _get_drug_prescribing_info(drug_name)
32 | )
33 | state["raw_prescribing_info"] = _parse_prescribing_info(prescribing_info[0])
34 | state["adverse_reactions"] = adverse_reactions
35 | state["related_pubmed_articles"] = _get_pubmed_sheets(drug_name)
36 |
37 | state["prescribing_info_summary"] = ""
38 |
39 | for chunk in stream_complete(
40 | initial_text=prescribing_summary_prompt.format(
41 | prescribing_details=state["raw_prescribing_info"]
42 | ),
43 | config={"model": "palmyra-med"}
44 | ):
45 | state["prescribing_info_summary"] += chunk
46 |
47 | asyncio.run(_upload_drug_data_into_graph(state, drug_name))
48 |
49 |
50 | async def _get_drug_prescribing_info(drug_name: str) -> (dict, str):
51 | return await asyncio.gather(
52 | _get_full_prescribing_info(drug_name), _get_drug_side_effects(drug_name)
53 | )
54 |
55 |
56 | async def _upload_drug_data_into_graph(state: WriterState, drug_name: str) -> None:
57 | input_params = [
58 | {
59 | "file_data": state["prescribing_info_summary"],
60 | "file_name": f"prescribing_info_summary_{drug_name}_{str(datetime.timestamp(datetime.now()))}",
61 | "graph_id": GRAPH_ID,
62 | },
63 | {
64 | "file_data": state["raw_prescribing_info"],
65 | "file_name": f"raw_prescribing_info_{drug_name}_{str(datetime.timestamp(datetime.now()))}",
66 | "graph_id": GRAPH_ID,
67 | },
68 | {
69 | "file_data": state["adverse_reactions"],
70 | "file_name": f"adverse_reactions_{drug_name}_{str(datetime.timestamp(datetime.now()))}",
71 | "graph_id": GRAPH_ID,
72 | },
73 | {
74 | "file_data": json.dumps(state["related_pubmed_articles"], default=str),
75 | "file_name": f"pubmed_articles_{drug_name}_{str(datetime.timestamp(datetime.now()))}",
76 | "graph_id": GRAPH_ID,
77 | },
78 | ]
79 | coroutines = [_upload_file_and_add_to_graph(**params) for params in input_params]
80 | await asyncio.gather(*coroutines)
81 |
82 |
83 | def _handle_contributing_sources(state: WriterState, graph_data: dict) -> None:
84 | sources = graph_data.get("sources")
85 | contributing_sources = {}
86 | if sources:
87 | for index, source in enumerate(sources):
88 | source_file = _get_file_from_graph(source["file_id"])
89 | source_snippet = source["snippet"]
90 |
91 | raw_filename = f"📄 {source_file.name}"
92 | row_length = 30
93 | filename = " ".join(
94 | [
95 | raw_filename[i : i + row_length]
96 | for i in range(0, len(raw_filename), row_length)
97 | ]
98 | )
99 |
100 | contributing_sources.update(
101 | {
102 | str(index): {
103 | "name": filename,
104 | "file_css": "file",
105 | "content": source_snippet,
106 | "content_css": "file-text",
107 | }
108 | }
109 | )
110 |
111 | state["contributing_sources"] = contributing_sources
112 | state["contributing_sources_vis"] = True
113 | state["contributing_sources_button_text"] = "View contributing sources ▸"
114 |
115 |
116 | def _get_file_from_graph(file_id: str) -> Optional[File]:
117 | try:
118 | return retrieve_file(file_id=file_id)
119 | except Exception as e:
120 | print(f"An error while file obtainment occurred: {str(e)}")
121 | return None
122 |
123 |
124 | def user_message(state: WriterState, payload: dict) -> None:
125 | try:
126 | state["conversation"] += payload
127 | graph = retrieve_graph(GRAPH_ID)
128 | response = state["conversation"].stream_complete(
129 | tools=graph,
130 | )
131 |
132 | for chunk in response:
133 | state["conversation"] += chunk
134 |
135 | graph_data = state["conversation"].messages[-1].get("graph_data")
136 | if graph_data:
137 | _handle_contributing_sources(state, graph_data)
138 |
139 | except Exception as e:
140 | state["conversation"] += {
141 | "role": "assistant",
142 | "content": "Something went wrong. Please try again!",
143 | }
144 | raise e
145 |
146 |
147 | def contributing_sources_change_vis(state: WriterState) -> None:
148 | state["contributing_sources_vis"] = not state["contributing_sources_vis"]
149 | if state["contributing_sources_vis"]:
150 | state["contributing_sources_button_text"] = "View contributing sources ▸"
151 | else:
152 | state["contributing_sources_button_text"] = "View contributing sources ◂"
153 |
154 |
155 | initial_state = wf.init_state(
156 | {
157 | "adverse_reactions": "Drug adverse reactions will appear here...",
158 | "prescribing_info_summary": "Prescribing info summary will appear here...",
159 | "raw_prescribing_info": "Prescribing info will appear here...",
160 | "related_pubmed_articles": {},
161 | "contributing_sources_button_text": "View contributing sources ◂",
162 | "contributing_sources_vis": False,
163 | "contributing_sources": {},
164 | "conversation": Conversation(
165 | [
166 | {
167 | "role": "assistant",
168 | "content": "Hi! I can answer your questions "
169 | "about the selected medication "
170 | "based on its prescribing information "
171 | "and related Pubmed articles.",
172 | },
173 | ],
174 | ),
175 | }
176 | )
177 |
178 | initial_state.import_stylesheet("style", "/static/custom.css")
179 |
--------------------------------------------------------------------------------
/prescribing-info-app/prompts.py:
--------------------------------------------------------------------------------
1 | prescribing_summary_prompt = """
2 |
3 | Variables:
4 |
5 | {prescribing_details}
6 |
7 | ************************
8 |
9 | Prompt:
10 | Here is the prescribing details text to summarize:
11 |
12 |
13 | {prescribing_details}
14 |
15 |
16 | Please summarize the prescribing details text.
17 |
18 | """
19 |
--------------------------------------------------------------------------------
/prescribing-info-app/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "writer-framework-default"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Your Name "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.11"
10 | writer = "^0.8.2"
11 | bio = "^1.7.1"
12 | bs4 = "^0.0.2"
13 | python-dotenv = "^1.0.1"
14 | flake8 = "^7.1.1"
15 | isort = "^5.13.2"
16 | black = "^24.10.0"
17 | pre-commit = "^4.0.1"
18 | asyncio = "^3.4.3"
19 | aiohttp = "^3.11.11"
20 |
21 |
22 | [build-system]
23 | requires = ["poetry-core"]
24 | build-backend = "poetry.core.masonry.api"
25 |
--------------------------------------------------------------------------------
/prescribing-info-app/static/README.md:
--------------------------------------------------------------------------------
1 | # Serving static files
2 |
3 | You can use this folder to store files which will be served statically in the "/static" route.
4 |
5 | This is useful to store images and other files which will be served directly to the user of your application.
6 |
7 | For example, if you store an image named "myimage.jpg" in this folder, it'll be accessible as "static/myimage.jpg".
8 | You can use this relative route as the source in an Image component.
9 |
--------------------------------------------------------------------------------
/prescribing-info-app/static/custom.css:
--------------------------------------------------------------------------------
1 | .adv-reactions-scroller{
2 | height: 60.47vh !important;
3 | overflow-y: auto !important;
4 | }
5 |
6 | .prescr-info-scroller{
7 | height: 60vh !important;
8 | overflow-y: auto !important;
9 | }
10 |
11 | .pubmed-scroller{
12 | height: 69.96vh !important;
13 | overflow-y: auto !important;
14 | }
15 |
16 | .file{
17 | overflow-x: auto !important;
18 | }
19 |
20 | .file-text{
21 | height: 40vh !important;
22 | overflow-y: auto !important;
23 | }
24 |
25 | .files-list{
26 | height: 79vh !important;
27 | overflow-y: auto !important;
28 | }
29 |
--------------------------------------------------------------------------------
/prescribing-info-app/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/writer/framework-tutorials/7d52c1465f481e29e884be6a32205cb04f2f87fc/prescribing-info-app/static/favicon.png
--------------------------------------------------------------------------------
/release-notes-generator/README.md:
--------------------------------------------------------------------------------
1 | # Release notes generator
2 | This application allows the user to upload a CSV of ticket data and labels and create nicely formatted release notes.
3 |
4 | Note: the prompts in `prompts.py` are tightly coupled to the formatting of the sample data. If you want to use a different CSV file with a different format, be sure to update the prompts accordingy.
5 |
6 | To build this application, you'll need to sign up for [Writer AI Studio](https://app.writer.com/aistudio/signup?utm_campaign=devrel) and create a new Framework application. This will create an API key. To pass your API key to the Writer Framework, you'll need to set an environment variable called `WRITER_API_KEY`:
7 |
8 | ```sh
9 | export WRITER_API_KEY=your-api-key
10 | ```
11 |
12 | Read the [full tutorial](https://dev.writer.com/framework/release-description-generator) to learn how to build this application.
13 |
14 | ## About Writer
15 |
16 | Writer is the full-stack generative AI platform for enterprises. Quickly and easily build and deploy generative AI apps with a suite of developer tools fully integrated with our platform of LLMs, graph-based RAG tools, AI guardrails, and more. Learn more at [writer.com](https://www.writer.com?utm_source=github&utm_medium=readme&utm_campaign=framework).
--------------------------------------------------------------------------------
/release-notes-generator/end/README.md:
--------------------------------------------------------------------------------
1 | This app was created using Writer Framework.
2 |
3 | To learn more about it, visit https://dev.writer.com/framework
4 |
--------------------------------------------------------------------------------
/release-notes-generator/end/data/output-html.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | New Feature
4 |
5 |
6 | New JS Challenge action for L7 DDoS auto mitigation
7 |
8 |
L7 DDoS now supports JavaScript Challenge as a new mitigation option in addition to blocking.
9 |
This provides customers with more flexibility in choosing how to mitigate volumetric DDoS attacks.
10 |
The JavaScript Challenge action can be configured in the EnableDDoSDetectionSetting configuration.
11 |
Once enabled, Akar will never create FastAcl even if blocking by IP, and will instead create an L7Acl rule with the JavaScript Challenge action.
12 |
13 |
14 |
15 |
16 |
17 | Detailed Events in Synthetic Monitoring Service
18 |
19 |
Synthetic monitoring customers can now view the verbose output/response of their synthetic monitor checks in the `Events` table. This will help them understand what issues and outages their end users are facing when using their applications.