diff --git a/capabilities/README.md b/capabilities/README.md new file mode 100644 index 00000000..27c585ad --- /dev/null +++ b/capabilities/README.md @@ -0,0 +1,19 @@ +# Claude Capabilities + +Welcome to the Capabilities section of the Claude Cookbooks! This directory contains a collection of guides that showcase specific capabilities where Claude excels. Each guide provides an in-depth exploration of a particular capability, discussing potential use cases, prompt engineering techniques to optimize results, and approaches for evaluating Claude's performance. + +## Guides + +- **[Classification with Claude](./classification/guide.ipynb)**: Discover how Claude can revolutionize classification tasks, especially in scenarios with complex business rules and limited training data. This guide walks you through data preparation, prompt engineering with retrieval-augmented generation (RAG), testing, and evaluation. + +- **[Retrieval Augmented Generation with Claude](./retrieval_augmented_generation/guide.ipynb)**: Learn how to enhance Claude's capabilities with domain-specific knowledge using RAG. This guide demonstrates how to build a RAG system from scratch, optimize its performance, and create an evaluation suite. You'll learn how techniques like summary indexing and re-ranking can significantly improve precision, recall, and overall accuracy in question-answering tasks. + +- **[Retrieval Augmented Generation with Contextual Embeddings](./contextual-embeddings/guide.ipynb)**: Learn how to use a new technique to improve the performance of your RAG system. In traditional RAG, documents are typically split into smaller chunks for efficient retrieval. While this approach works well for many applications, it can lead to problems when individual chunks lack sufficient context. Contextual Embeddings solve this problem by adding relevant context to each chunk before embedding. You'll learn how to use contextual embeddings with semantic search, BM25 search, and reranking to improve performance. + +- **[Summarization with Claude](./summarization/guide.ipynb)**: Explore Claude's ability to summarize and synthesize information from multiple sources. This guide covers a variety of summarization techniques, including multi-shot, domain-based, and chunking methods, as well as strategies for handling long-form content and multiple documents. We also explore evaluating summaries, which can be a balance of art, subjectivity, and the right approach! + +- **[Text-to-SQL with Claude](./text_to_sql/guide.ipynb)**: This guide covers how to generate complex SQL queries from natural language using prompting techniques, self-improvement, and RAG. We'll also explore how to evaluate and improve the accuracy of generated SQL queries, with evals that test for syntax, data correctness, row count, and more. + +## Getting Started + +To get started with these guides, simply navigate to the desired guide's directory and follow the instructions provided in the `guide.ipynb` file. Each guide is self-contained and includes all the necessary code, data, and evaluation scripts to reproduce the examples and experiments. \ No newline at end of file diff --git a/skills/classification/README.md b/capabilities/classification/README.md similarity index 100% rename from skills/classification/README.md rename to capabilities/classification/README.md diff --git a/skills/classification/data/results.csv b/capabilities/classification/data/results.csv similarity index 100% rename from skills/classification/data/results.csv rename to capabilities/classification/data/results.csv diff --git a/skills/classification/data/test.tsv b/capabilities/classification/data/test.tsv similarity index 100% rename from skills/classification/data/test.tsv rename to capabilities/classification/data/test.tsv diff --git a/skills/classification/data/train.tsv b/capabilities/classification/data/train.tsv similarity index 100% rename from skills/classification/data/train.tsv rename to capabilities/classification/data/train.tsv diff --git a/skills/classification/data/vector_db.pkl b/capabilities/classification/data/vector_db.pkl similarity index 100% rename from skills/classification/data/vector_db.pkl rename to capabilities/classification/data/vector_db.pkl diff --git a/skills/classification/evaluation/README.md b/capabilities/classification/evaluation/README.md similarity index 100% rename from skills/classification/evaluation/README.md rename to capabilities/classification/evaluation/README.md diff --git a/skills/classification/evaluation/dataset.csv b/capabilities/classification/evaluation/dataset.csv similarity index 100% rename from skills/classification/evaluation/dataset.csv rename to capabilities/classification/evaluation/dataset.csv diff --git a/skills/classification/evaluation/promptfooconfig.yaml b/capabilities/classification/evaluation/promptfooconfig.yaml similarity index 100% rename from skills/classification/evaluation/promptfooconfig.yaml rename to capabilities/classification/evaluation/promptfooconfig.yaml diff --git a/skills/classification/evaluation/prompts.py b/capabilities/classification/evaluation/prompts.py similarity index 100% rename from skills/classification/evaluation/prompts.py rename to capabilities/classification/evaluation/prompts.py diff --git a/skills/classification/evaluation/transform.py b/capabilities/classification/evaluation/transform.py similarity index 100% rename from skills/classification/evaluation/transform.py rename to capabilities/classification/evaluation/transform.py diff --git a/skills/classification/evaluation/vectordb.py b/capabilities/classification/evaluation/vectordb.py similarity index 100% rename from skills/classification/evaluation/vectordb.py rename to capabilities/classification/evaluation/vectordb.py diff --git a/skills/classification/guide.ipynb b/capabilities/classification/guide.ipynb similarity index 100% rename from skills/classification/guide.ipynb rename to capabilities/classification/guide.ipynb diff --git a/skills/contextual-embeddings/README.md b/capabilities/contextual-embeddings/README.md similarity index 100% rename from skills/contextual-embeddings/README.md rename to capabilities/contextual-embeddings/README.md diff --git a/skills/contextual-embeddings/contextual-rag-lambda-function/inference_adapter.py b/capabilities/contextual-embeddings/contextual-rag-lambda-function/inference_adapter.py similarity index 100% rename from skills/contextual-embeddings/contextual-rag-lambda-function/inference_adapter.py rename to capabilities/contextual-embeddings/contextual-rag-lambda-function/inference_adapter.py diff --git a/skills/contextual-embeddings/contextual-rag-lambda-function/lambda_function.py b/capabilities/contextual-embeddings/contextual-rag-lambda-function/lambda_function.py similarity index 100% rename from skills/contextual-embeddings/contextual-rag-lambda-function/lambda_function.py rename to capabilities/contextual-embeddings/contextual-rag-lambda-function/lambda_function.py diff --git a/skills/contextual-embeddings/contextual-rag-lambda-function/s3_adapter.py b/capabilities/contextual-embeddings/contextual-rag-lambda-function/s3_adapter.py similarity index 100% rename from skills/contextual-embeddings/contextual-rag-lambda-function/s3_adapter.py rename to capabilities/contextual-embeddings/contextual-rag-lambda-function/s3_adapter.py diff --git a/skills/contextual-embeddings/data/codebase_chunks.json b/capabilities/contextual-embeddings/data/codebase_chunks.json similarity index 100% rename from skills/contextual-embeddings/data/codebase_chunks.json rename to capabilities/contextual-embeddings/data/codebase_chunks.json diff --git a/skills/contextual-embeddings/data/evaluation_set.jsonl b/capabilities/contextual-embeddings/data/evaluation_set.jsonl similarity index 100% rename from skills/contextual-embeddings/data/evaluation_set.jsonl rename to capabilities/contextual-embeddings/data/evaluation_set.jsonl diff --git a/skills/contextual-embeddings/guide.ipynb b/capabilities/contextual-embeddings/guide.ipynb similarity index 92% rename from skills/contextual-embeddings/guide.ipynb rename to capabilities/contextual-embeddings/guide.ipynb index 6c1c9e65..5e1c0613 100644 --- a/skills/contextual-embeddings/guide.ipynb +++ b/capabilities/contextual-embeddings/guide.ipynb @@ -3,55 +3,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "# Enhancing RAG with Contextual Retrieval\n", - "\n", - "> Note: For more background information on Contextual Retrieval, including additional performance evaluations on various datasets, we recommend reading our accompanying [blog post](https://www.anthropic.com/news/contextual-retrieval).\n", - "\n", - "Retrieval Augmented Generation (RAG) enables Claude to leverage your internal knowledge bases, codebases, or any other corpus of documents when providing a response. Enterprises are increasingly building RAG applications to improve workflows in customer support, Q&A over internal company documents, financial & legal analysis, code generation, and much more.\n", - "\n", - "In a [separate guide](https://github.com/anthropics/anthropic-cookbook/blob/main/skills/retrieval_augmented_generation/guide.ipynb), we walked through setting up a basic retrieval system, demonstrated how to evaluate its performance, and then outlined a few techniques to improve performance. In this guide, we present a technique for improving retrieval performance: Contextual Embeddings.\n", - "\n", - "In traditional RAG, documents are typically split into smaller chunks for efficient retrieval. While this approach works well for many applications, it can lead to problems when individual chunks lack sufficient context. Contextual Embeddings solve this problem by adding relevant context to each chunk before embedding. This method improves the quality of each embedded chunk, allowing for more accurate retrieval and thus better overall performance. Averaged across all data sources we tested, Contextual Embeddings reduced the top-20-chunk retrieval failure rate by 35%.\n", - "\n", - "The same chunk-specific context can also be used with BM25 search to further improve retrieval performance. We introduce this technique in the “Contextual BM25” section.\n", - "\n", - "In this guide, we'll demonstrate how to build and optimize a Contextual Retrieval system using a dataset of 9 codebases as our knowledge base. We'll walk through:\n", - "\n", - "1) Setting up a basic retrieval pipeline to establish a baseline for performance.\n", - "\n", - "2) Contextual Embeddings: what it is, why it works, and how prompt caching makes it practical for production use cases.\n", - "\n", - "3) Implementing Contextual Embeddings and demonstrating performance improvements.\n", - "\n", - "4) Contextual BM25: improving performance with *contextual* BM25 hybrid search.\n", - "\n", - "5) Improving performance with reranking,\n", - "\n", - "### Evaluation Metrics & Dataset:\n", - "\n", - "We use a pre-chunked dataset of 9 codebases - all of which have been chunked according to a basic character splitting mechanism. Our evaluation dataset contains 248 queries - each of which contains a 'golden chunk.' We'll use a metric called Pass@k to evaluate performance. Pass@k checks whether or not the 'golden document' was present in the first k documents retrieved for each query. Contextual Embeddings in this case helped us to improve Pass@10 performance from ~87% --> ~95%.\n", - "\n", - "You can find the code files and their chunks in `data/codebase_chunks.json` and the evaluation dataset in `data/evaluation_set.jsonl`\n", - "\n", - "#### Additional Notes:\n", - "\n", - "Prompt caching is helpful in managing costs when using this retrieval method. This feature is currently available on Anthropic's 1P API, and is coming soon to our 3P partner environments in AWS Bedrock and GCP Vertex. We know that many of our customers leverage AWS Knowledge Bases and GCP Vertex AI APIs when building RAG solutions, and this method can be used on either platform with a bit of customization. Consider reaching out to Anthropic or your AWS/GCP account team for guidance on this!\n", - "\n", - "To make it easier to use this method on Bedrock, the AWS team has provided us with code that you can use to implement a Lambda function that adds context to each document. If you deploy this Lambda function, you can select it as a custom chunking option when configuring a [Bedrock Knowledge Base](https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-create.html). You can find this code in `contextual-rag-lambda-function`. The main lambda function code is in `lambda_function.py`.\n", - "\n", - "## Table of Contents\n", - "\n", - "1) Setup\n", - "\n", - "2) Basic RAG\n", - "\n", - "3) Contextual Embeddings\n", - "\n", - "4) Contextual BM25\n", - "\n", - "5) Reranking" - ] + "source": "# Enhancing RAG with Contextual Retrieval\n\n> Note: For more background information on Contextual Retrieval, including additional performance evaluations on various datasets, we recommend reading our accompanying [blog post](https://www.anthropic.com/news/contextual-retrieval).\n\nRetrieval Augmented Generation (RAG) enables Claude to leverage your internal knowledge bases, codebases, or any other corpus of documents when providing a response. Enterprises are increasingly building RAG applications to improve workflows in customer support, Q&A over internal company documents, financial & legal analysis, code generation, and much more.\n\nIn a [separate guide](https://github.com/anthropics/anthropic-cookbook/blob/main/capabilities/retrieval_augmented_generation/guide.ipynb), we walked through setting up a basic retrieval system, demonstrated how to evaluate its performance, and then outlined a few techniques to improve performance. In this guide, we present a technique for improving retrieval performance: Contextual Embeddings.\n\nIn traditional RAG, documents are typically split into smaller chunks for efficient retrieval. While this approach works well for many applications, it can lead to problems when individual chunks lack sufficient context. Contextual Embeddings solve this problem by adding relevant context to each chunk before embedding. This method improves the quality of each embedded chunk, allowing for more accurate retrieval and thus better overall performance. Averaged across all data sources we tested, Contextual Embeddings reduced the top-20-chunk retrieval failure rate by 35%.\n\nThe same chunk-specific context can also be used with BM25 search to further improve retrieval performance. We introduce this technique in the \"Contextual BM25\" section.\n\nIn this guide, we'll demonstrate how to build and optimize a Contextual Retrieval system using a dataset of 9 codebases as our knowledge base. We'll walk through:\n\n1) Setting up a basic retrieval pipeline to establish a baseline for performance.\n\n2) Contextual Embeddings: what it is, why it works, and how prompt caching makes it practical for production use cases.\n\n3) Implementing Contextual Embeddings and demonstrating performance improvements.\n\n4) Contextual BM25: improving performance with *contextual* BM25 hybrid search.\n\n5) Improving performance with reranking,\n\n### Evaluation Metrics & Dataset:\n\nWe use a pre-chunked dataset of 9 codebases - all of which have been chunked according to a basic character splitting mechanism. Our evaluation dataset contains 248 queries - each of which contains a 'golden chunk.' We'll use a metric called Pass@k to evaluate performance. Pass@k checks whether or not the 'golden document' was present in the first k documents retrieved for each query. Contextual Embeddings in this case helped us to improve Pass@10 performance from ~87% --> ~95%.\n\nYou can find the code files and their chunks in `data/codebase_chunks.json` and the evaluation dataset in `data/evaluation_set.jsonl`\n\n#### Additional Notes:\n\nPrompt caching is helpful in managing costs when using this retrieval method. This feature is currently available on Anthropic's 1P API, and is coming soon to our 3P partner environments in AWS Bedrock and GCP Vertex. We know that many of our customers leverage AWS Knowledge Bases and GCP Vertex AI APIs when building RAG solutions, and this method can be used on either platform with a bit of customization. Consider reaching out to Anthropic or your AWS/GCP account team for guidance on this!\n\nTo make it easier to use this method on Bedrock, the AWS team has provided us with code that you can use to implement a Lambda function that adds context to each document. If you deploy this Lambda function, you can select it as a custom chunking option when configuring a [Bedrock Knowledge Base](https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-create.html). You can find this code in `contextual-rag-lambda-function`. The main lambda function code is in `lambda_function.py`.\n\n## Table of Contents\n\n1) Setup\n\n2) Basic RAG\n\n3) Contextual Embeddings\n\n4) Contextual BM25\n\n5) Reranking" }, { "cell_type": "markdown", diff --git a/skills/retrieval_augmented_generation/README.md b/capabilities/retrieval_augmented_generation/README.md similarity index 100% rename from skills/retrieval_augmented_generation/README.md rename to capabilities/retrieval_augmented_generation/README.md diff --git a/skills/retrieval_augmented_generation/data/anthropic_docs.json b/capabilities/retrieval_augmented_generation/data/anthropic_docs.json similarity index 100% rename from skills/retrieval_augmented_generation/data/anthropic_docs.json rename to capabilities/retrieval_augmented_generation/data/anthropic_docs.json diff --git a/skills/retrieval_augmented_generation/data/anthropic_summary_indexed_docs.json b/capabilities/retrieval_augmented_generation/data/anthropic_summary_indexed_docs.json similarity index 100% rename from skills/retrieval_augmented_generation/data/anthropic_summary_indexed_docs.json rename to capabilities/retrieval_augmented_generation/data/anthropic_summary_indexed_docs.json diff --git a/skills/retrieval_augmented_generation/data/end_to_end_results.json b/capabilities/retrieval_augmented_generation/data/end_to_end_results.json similarity index 100% rename from skills/retrieval_augmented_generation/data/end_to_end_results.json rename to capabilities/retrieval_augmented_generation/data/end_to_end_results.json diff --git a/skills/retrieval_augmented_generation/data/retrieval_results.json b/capabilities/retrieval_augmented_generation/data/retrieval_results.json similarity index 100% rename from skills/retrieval_augmented_generation/data/retrieval_results.json rename to capabilities/retrieval_augmented_generation/data/retrieval_results.json diff --git a/skills/retrieval_augmented_generation/evaluation/README.md b/capabilities/retrieval_augmented_generation/evaluation/README.md similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/README.md rename to capabilities/retrieval_augmented_generation/evaluation/README.md diff --git a/skills/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed.csv b/capabilities/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed.csv similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed.csv rename to capabilities/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed.csv diff --git a/skills/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed_level_three.csv b/capabilities/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed_level_three.csv similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed_level_three.csv rename to capabilities/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed_level_three.csv diff --git a/skills/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed_level_two.csv b/capabilities/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed_level_two.csv similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed_level_two.csv rename to capabilities/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed_level_two.csv diff --git a/skills/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed_one.csv b/capabilities/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed_one.csv similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed_one.csv rename to capabilities/retrieval_augmented_generation/evaluation/csvs/evaluation_results_detailed_one.csv diff --git a/skills/retrieval_augmented_generation/evaluation/docs_evaluation_dataset.json b/capabilities/retrieval_augmented_generation/evaluation/docs_evaluation_dataset.json similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/docs_evaluation_dataset.json rename to capabilities/retrieval_augmented_generation/evaluation/docs_evaluation_dataset.json diff --git a/skills/retrieval_augmented_generation/evaluation/eval_end_to_end.py b/capabilities/retrieval_augmented_generation/evaluation/eval_end_to_end.py similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/eval_end_to_end.py rename to capabilities/retrieval_augmented_generation/evaluation/eval_end_to_end.py diff --git a/skills/retrieval_augmented_generation/evaluation/eval_retrieval.py b/capabilities/retrieval_augmented_generation/evaluation/eval_retrieval.py similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/eval_retrieval.py rename to capabilities/retrieval_augmented_generation/evaluation/eval_retrieval.py diff --git a/skills/retrieval_augmented_generation/evaluation/json_results/evaluation_results_level_three.json b/capabilities/retrieval_augmented_generation/evaluation/json_results/evaluation_results_level_three.json similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/json_results/evaluation_results_level_three.json rename to capabilities/retrieval_augmented_generation/evaluation/json_results/evaluation_results_level_three.json diff --git a/skills/retrieval_augmented_generation/evaluation/json_results/evaluation_results_level_two.json b/capabilities/retrieval_augmented_generation/evaluation/json_results/evaluation_results_level_two.json similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/json_results/evaluation_results_level_two.json rename to capabilities/retrieval_augmented_generation/evaluation/json_results/evaluation_results_level_two.json diff --git a/skills/retrieval_augmented_generation/evaluation/json_results/evaluation_results_one.json b/capabilities/retrieval_augmented_generation/evaluation/json_results/evaluation_results_one.json similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/json_results/evaluation_results_one.json rename to capabilities/retrieval_augmented_generation/evaluation/json_results/evaluation_results_one.json diff --git a/skills/retrieval_augmented_generation/evaluation/promptfoo_datasets/end_to_end_dataset.csv b/capabilities/retrieval_augmented_generation/evaluation/promptfoo_datasets/end_to_end_dataset.csv similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/promptfoo_datasets/end_to_end_dataset.csv rename to capabilities/retrieval_augmented_generation/evaluation/promptfoo_datasets/end_to_end_dataset.csv diff --git a/skills/retrieval_augmented_generation/evaluation/promptfoo_datasets/retrieval_dataset.csv b/capabilities/retrieval_augmented_generation/evaluation/promptfoo_datasets/retrieval_dataset.csv similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/promptfoo_datasets/retrieval_dataset.csv rename to capabilities/retrieval_augmented_generation/evaluation/promptfoo_datasets/retrieval_dataset.csv diff --git a/skills/retrieval_augmented_generation/evaluation/promptfooconfig_end_to_end.yaml b/capabilities/retrieval_augmented_generation/evaluation/promptfooconfig_end_to_end.yaml similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/promptfooconfig_end_to_end.yaml rename to capabilities/retrieval_augmented_generation/evaluation/promptfooconfig_end_to_end.yaml diff --git a/skills/retrieval_augmented_generation/evaluation/promptfooconfig_retrieval.yaml b/capabilities/retrieval_augmented_generation/evaluation/promptfooconfig_retrieval.yaml similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/promptfooconfig_retrieval.yaml rename to capabilities/retrieval_augmented_generation/evaluation/promptfooconfig_retrieval.yaml diff --git a/skills/retrieval_augmented_generation/evaluation/prompts.py b/capabilities/retrieval_augmented_generation/evaluation/prompts.py similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/prompts.py rename to capabilities/retrieval_augmented_generation/evaluation/prompts.py diff --git a/skills/retrieval_augmented_generation/evaluation/provider_retrieval.py b/capabilities/retrieval_augmented_generation/evaluation/provider_retrieval.py similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/provider_retrieval.py rename to capabilities/retrieval_augmented_generation/evaluation/provider_retrieval.py diff --git a/skills/retrieval_augmented_generation/evaluation/vectordb.py b/capabilities/retrieval_augmented_generation/evaluation/vectordb.py similarity index 100% rename from skills/retrieval_augmented_generation/evaluation/vectordb.py rename to capabilities/retrieval_augmented_generation/evaluation/vectordb.py diff --git a/skills/retrieval_augmented_generation/guide.ipynb b/capabilities/retrieval_augmented_generation/guide.ipynb similarity index 100% rename from skills/retrieval_augmented_generation/guide.ipynb rename to capabilities/retrieval_augmented_generation/guide.ipynb diff --git a/skills/summarization/README.md b/capabilities/summarization/README.md similarity index 100% rename from skills/summarization/README.md rename to capabilities/summarization/README.md diff --git a/skills/summarization/data/Sample Sublease Agreement.pdf b/capabilities/summarization/data/Sample Sublease Agreement.pdf similarity index 100% rename from skills/summarization/data/Sample Sublease Agreement.pdf rename to capabilities/summarization/data/Sample Sublease Agreement.pdf diff --git a/skills/summarization/data/multiple_subleases.py b/capabilities/summarization/data/multiple_subleases.py similarity index 100% rename from skills/summarization/data/multiple_subleases.py rename to capabilities/summarization/data/multiple_subleases.py diff --git a/skills/summarization/data/results.csv b/capabilities/summarization/data/results.csv similarity index 100% rename from skills/summarization/data/results.csv rename to capabilities/summarization/data/results.csv diff --git a/skills/summarization/data/sample-lease1-summary.txt b/capabilities/summarization/data/sample-lease1-summary.txt similarity index 100% rename from skills/summarization/data/sample-lease1-summary.txt rename to capabilities/summarization/data/sample-lease1-summary.txt diff --git a/skills/summarization/data/sample-lease1.txt b/capabilities/summarization/data/sample-lease1.txt similarity index 100% rename from skills/summarization/data/sample-lease1.txt rename to capabilities/summarization/data/sample-lease1.txt diff --git a/skills/summarization/data/sample-lease2-summary.txt b/capabilities/summarization/data/sample-lease2-summary.txt similarity index 100% rename from skills/summarization/data/sample-lease2-summary.txt rename to capabilities/summarization/data/sample-lease2-summary.txt diff --git a/skills/summarization/data/sample-lease2.txt b/capabilities/summarization/data/sample-lease2.txt similarity index 100% rename from skills/summarization/data/sample-lease2.txt rename to capabilities/summarization/data/sample-lease2.txt diff --git a/skills/summarization/data/sample-lease3-summary.txt b/capabilities/summarization/data/sample-lease3-summary.txt similarity index 100% rename from skills/summarization/data/sample-lease3-summary.txt rename to capabilities/summarization/data/sample-lease3-summary.txt diff --git a/skills/summarization/data/sample-lease3.txt b/capabilities/summarization/data/sample-lease3.txt similarity index 100% rename from skills/summarization/data/sample-lease3.txt rename to capabilities/summarization/data/sample-lease3.txt diff --git a/skills/summarization/data/sample-lease4-summary.txt b/capabilities/summarization/data/sample-lease4-summary.txt similarity index 100% rename from skills/summarization/data/sample-lease4-summary.txt rename to capabilities/summarization/data/sample-lease4-summary.txt diff --git a/skills/summarization/data/sample-lease4.txt b/capabilities/summarization/data/sample-lease4.txt similarity index 100% rename from skills/summarization/data/sample-lease4.txt rename to capabilities/summarization/data/sample-lease4.txt diff --git a/skills/summarization/data/sample-lease5-summary.txt b/capabilities/summarization/data/sample-lease5-summary.txt similarity index 100% rename from skills/summarization/data/sample-lease5-summary.txt rename to capabilities/summarization/data/sample-lease5-summary.txt diff --git a/skills/summarization/data/sample-lease5.txt b/capabilities/summarization/data/sample-lease5.txt similarity index 100% rename from skills/summarization/data/sample-lease5.txt rename to capabilities/summarization/data/sample-lease5.txt diff --git a/skills/summarization/data/sample-lease6-summary.txt b/capabilities/summarization/data/sample-lease6-summary.txt similarity index 100% rename from skills/summarization/data/sample-lease6-summary.txt rename to capabilities/summarization/data/sample-lease6-summary.txt diff --git a/skills/summarization/data/sample-lease6.txt b/capabilities/summarization/data/sample-lease6.txt similarity index 100% rename from skills/summarization/data/sample-lease6.txt rename to capabilities/summarization/data/sample-lease6.txt diff --git a/skills/summarization/data/sample-lease7-summary.txt b/capabilities/summarization/data/sample-lease7-summary.txt similarity index 100% rename from skills/summarization/data/sample-lease7-summary.txt rename to capabilities/summarization/data/sample-lease7-summary.txt diff --git a/skills/summarization/data/sample-lease7.txt b/capabilities/summarization/data/sample-lease7.txt similarity index 100% rename from skills/summarization/data/sample-lease7.txt rename to capabilities/summarization/data/sample-lease7.txt diff --git a/skills/summarization/data/sample-lease8-summary.txt b/capabilities/summarization/data/sample-lease8-summary.txt similarity index 100% rename from skills/summarization/data/sample-lease8-summary.txt rename to capabilities/summarization/data/sample-lease8-summary.txt diff --git a/skills/summarization/data/sample-lease8.txt b/capabilities/summarization/data/sample-lease8.txt similarity index 100% rename from skills/summarization/data/sample-lease8.txt rename to capabilities/summarization/data/sample-lease8.txt diff --git a/skills/summarization/data/sample-lease9-summary.txt b/capabilities/summarization/data/sample-lease9-summary.txt similarity index 100% rename from skills/summarization/data/sample-lease9-summary.txt rename to capabilities/summarization/data/sample-lease9-summary.txt diff --git a/skills/summarization/data/sample-lease9.txt b/capabilities/summarization/data/sample-lease9.txt similarity index 100% rename from skills/summarization/data/sample-lease9.txt rename to capabilities/summarization/data/sample-lease9.txt diff --git a/skills/summarization/evaluation/README.md b/capabilities/summarization/evaluation/README.md similarity index 100% rename from skills/summarization/evaluation/README.md rename to capabilities/summarization/evaluation/README.md diff --git a/skills/summarization/evaluation/custom_evals/bleu_eval.py b/capabilities/summarization/evaluation/custom_evals/bleu_eval.py similarity index 100% rename from skills/summarization/evaluation/custom_evals/bleu_eval.py rename to capabilities/summarization/evaluation/custom_evals/bleu_eval.py diff --git a/skills/summarization/evaluation/custom_evals/llm_eval.py b/capabilities/summarization/evaluation/custom_evals/llm_eval.py similarity index 100% rename from skills/summarization/evaluation/custom_evals/llm_eval.py rename to capabilities/summarization/evaluation/custom_evals/llm_eval.py diff --git a/skills/summarization/evaluation/custom_evals/rouge_eval.py b/capabilities/summarization/evaluation/custom_evals/rouge_eval.py similarity index 100% rename from skills/summarization/evaluation/custom_evals/rouge_eval.py rename to capabilities/summarization/evaluation/custom_evals/rouge_eval.py diff --git a/skills/summarization/evaluation/promptfooconfig.yaml b/capabilities/summarization/evaluation/promptfooconfig.yaml similarity index 100% rename from skills/summarization/evaluation/promptfooconfig.yaml rename to capabilities/summarization/evaluation/promptfooconfig.yaml diff --git a/skills/summarization/evaluation/prompts.py b/capabilities/summarization/evaluation/prompts.py similarity index 100% rename from skills/summarization/evaluation/prompts.py rename to capabilities/summarization/evaluation/prompts.py diff --git a/skills/summarization/evaluation/tests.yaml b/capabilities/summarization/evaluation/tests.yaml similarity index 100% rename from skills/summarization/evaluation/tests.yaml rename to capabilities/summarization/evaluation/tests.yaml diff --git a/skills/summarization/guide.ipynb b/capabilities/summarization/guide.ipynb similarity index 100% rename from skills/summarization/guide.ipynb rename to capabilities/summarization/guide.ipynb diff --git a/skills/text_to_sql/README.md b/capabilities/text_to_sql/README.md similarity index 100% rename from skills/text_to_sql/README.md rename to capabilities/text_to_sql/README.md diff --git a/skills/text_to_sql/data/data.db b/capabilities/text_to_sql/data/data.db similarity index 100% rename from skills/text_to_sql/data/data.db rename to capabilities/text_to_sql/data/data.db diff --git a/skills/text_to_sql/data/results.csv b/capabilities/text_to_sql/data/results.csv similarity index 100% rename from skills/text_to_sql/data/results.csv rename to capabilities/text_to_sql/data/results.csv diff --git a/skills/text_to_sql/data/vector_db.pkl b/capabilities/text_to_sql/data/vector_db.pkl similarity index 100% rename from skills/text_to_sql/data/vector_db.pkl rename to capabilities/text_to_sql/data/vector_db.pkl diff --git a/skills/text_to_sql/evaluation/README.md b/capabilities/text_to_sql/evaluation/README.md similarity index 100% rename from skills/text_to_sql/evaluation/README.md rename to capabilities/text_to_sql/evaluation/README.md diff --git a/skills/text_to_sql/evaluation/promptfooconfig.yaml b/capabilities/text_to_sql/evaluation/promptfooconfig.yaml similarity index 100% rename from skills/text_to_sql/evaluation/promptfooconfig.yaml rename to capabilities/text_to_sql/evaluation/promptfooconfig.yaml diff --git a/skills/text_to_sql/evaluation/prompts.py b/capabilities/text_to_sql/evaluation/prompts.py similarity index 100% rename from skills/text_to_sql/evaluation/prompts.py rename to capabilities/text_to_sql/evaluation/prompts.py diff --git a/skills/text_to_sql/evaluation/tests/test_above_average_salary.py b/capabilities/text_to_sql/evaluation/tests/test_above_average_salary.py similarity index 100% rename from skills/text_to_sql/evaluation/tests/test_above_average_salary.py rename to capabilities/text_to_sql/evaluation/tests/test_above_average_salary.py diff --git a/skills/text_to_sql/evaluation/tests/test_average_salary.py b/capabilities/text_to_sql/evaluation/tests/test_average_salary.py similarity index 100% rename from skills/text_to_sql/evaluation/tests/test_average_salary.py rename to capabilities/text_to_sql/evaluation/tests/test_average_salary.py diff --git a/skills/text_to_sql/evaluation/tests/test_budget_allocation.py b/capabilities/text_to_sql/evaluation/tests/test_budget_allocation.py similarity index 100% rename from skills/text_to_sql/evaluation/tests/test_budget_allocation.py rename to capabilities/text_to_sql/evaluation/tests/test_budget_allocation.py diff --git a/skills/text_to_sql/evaluation/tests/test_employee_count.py b/capabilities/text_to_sql/evaluation/tests/test_employee_count.py similarity index 100% rename from skills/text_to_sql/evaluation/tests/test_employee_count.py rename to capabilities/text_to_sql/evaluation/tests/test_employee_count.py diff --git a/skills/text_to_sql/evaluation/tests/test_employee_details.py b/capabilities/text_to_sql/evaluation/tests/test_employee_details.py similarity index 100% rename from skills/text_to_sql/evaluation/tests/test_employee_details.py rename to capabilities/text_to_sql/evaluation/tests/test_employee_details.py diff --git a/skills/text_to_sql/evaluation/tests/test_hierarchical_query.py b/capabilities/text_to_sql/evaluation/tests/test_hierarchical_query.py similarity index 100% rename from skills/text_to_sql/evaluation/tests/test_hierarchical_query.py rename to capabilities/text_to_sql/evaluation/tests/test_hierarchical_query.py diff --git a/skills/text_to_sql/evaluation/tests/test_simple_query.py b/capabilities/text_to_sql/evaluation/tests/test_simple_query.py similarity index 100% rename from skills/text_to_sql/evaluation/tests/test_simple_query.py rename to capabilities/text_to_sql/evaluation/tests/test_simple_query.py diff --git a/skills/text_to_sql/evaluation/tests/utils.py b/capabilities/text_to_sql/evaluation/tests/utils.py similarity index 100% rename from skills/text_to_sql/evaluation/tests/utils.py rename to capabilities/text_to_sql/evaluation/tests/utils.py diff --git a/skills/text_to_sql/evaluation/vectordb.py b/capabilities/text_to_sql/evaluation/vectordb.py similarity index 100% rename from skills/text_to_sql/evaluation/vectordb.py rename to capabilities/text_to_sql/evaluation/vectordb.py diff --git a/skills/text_to_sql/guide.ipynb b/capabilities/text_to_sql/guide.ipynb similarity index 100% rename from skills/text_to_sql/guide.ipynb rename to capabilities/text_to_sql/guide.ipynb diff --git a/skills/.claude/hooks/pre-bash.sh b/skills/.claude/hooks/pre-bash.sh new file mode 100755 index 00000000..138da667 --- /dev/null +++ b/skills/.claude/hooks/pre-bash.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# PreToolUse Hook - Bash Safety Check +# Prevents dangerous commands and provides helpful reminders + +set -e + +TOOL_NAME="$1" +COMMAND="$2" + +# Only run for Bash tool +if [[ "$TOOL_NAME" != "Bash" ]]; then + exit 0 +fi + +# Check for potentially dangerous commands +if [[ "$COMMAND" == *"rm -rf outputs"* ]] || [[ "$COMMAND" == *"rm -rf sample_data"* ]]; then + echo "⚠️ WARNING: Attempting to delete important directory!" + echo "Command: $COMMAND" + echo "These directories contain generated files and sample data." + # Allow but warn +fi + +# Warn about pip install without using requirements.txt +if [[ "$COMMAND" == *"pip install"* ]] && [[ "$COMMAND" != *"requirements.txt"* ]]; then + echo "ℹ️ Installing package directly. Consider updating requirements.txt" +fi + +# Remind about kernel restart after SDK reinstall +if [[ "$COMMAND" == *"pip install"* ]] && [[ "$COMMAND" == *"anthropic"* ]]; then + echo "ℹ️ Remember: Restart Jupyter kernel after SDK installation!" +fi + +# Warn if trying to start jupyter/servers +if [[ "$COMMAND" == *"jupyter notebook"* ]] || [[ "$COMMAND" == *"jupyter lab"* ]]; then + echo "ℹ️ Starting Jupyter. Make sure to select the venv kernel in notebooks." +fi + +exit 0 diff --git a/skills/.claude/hooks/pre-write.sh b/skills/.claude/hooks/pre-write.sh new file mode 100755 index 00000000..5341aeb9 --- /dev/null +++ b/skills/.claude/hooks/pre-write.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# PreToolUse Hook - Write Safety Check +# Prevents accidental overwrites of key files + +set -e + +TOOL_NAME="$1" +FILE_PATH="$2" + +# Only run for Write tool +if [[ "$TOOL_NAME" != "Write" ]]; then + exit 0 +fi + +# Protected files - should never be overwritten without explicit user request +PROTECTED_FILES=( + ".env" + "requirements.txt" +) + +for protected in "${PROTECTED_FILES[@]}"; do + if [[ "$FILE_PATH" == *"$protected"* ]]; then + echo "⚠️ WARNING: Attempting to write to protected file: $FILE_PATH" + echo "This file should rarely be modified. Proceeding with caution..." + # Allow but warn - don't block + fi +done + +# Warn if writing to notebooks/ without .ipynb extension +if [[ "$FILE_PATH" == *"notebooks/"* ]] && [[ "$FILE_PATH" != *".ipynb" ]]; then + echo "⚠️ Writing non-notebook file to notebooks/ directory: $FILE_PATH" +fi + +# Warn if writing to sample_data/ +if [[ "$FILE_PATH" == *"sample_data/"* ]]; then + echo "ℹ️ Modifying sample data: $FILE_PATH" +fi + +exit 0 diff --git a/skills/.claude/hooks/session-start.sh b/skills/.claude/hooks/session-start.sh new file mode 100755 index 00000000..afed4095 --- /dev/null +++ b/skills/.claude/hooks/session-start.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# SessionStart Hook - Skills Cookbook Environment Check +# This hook runs at the start of each Claude Code session to verify environment setup + +set -e + +echo "🔍 Skills Cookbook - Environment Check" +echo "======================================" + +# Check if we're in a virtual environment +if [[ -z "$VIRTUAL_ENV" ]]; then + echo "⚠️ WARNING: No virtual environment detected!" + echo " Run: source venv/bin/activate" + echo "" +fi + +# Check if Anthropic SDK is installed and get version +if python -c "import anthropic" 2>/dev/null; then + SDK_VERSION=$(python -c "import anthropic; print(anthropic.__version__)" 2>/dev/null || echo "unknown") + echo "✅ Anthropic SDK: $SDK_VERSION" + # Check for minimum version for Skills support + if [[ "$SDK_VERSION" < "0.71.0" ]]; then + echo "⚠️ SDK version $SDK_VERSION may be too old (minimum 0.71.0 for Skills support)" + echo " Run: pip install anthropic>=0.71.0" + echo "" + fi +else + echo "❌ Anthropic SDK not installed" + echo " Run: pip install -r requirements.txt" + echo "" +fi + +# Check for API key +if [[ -f ".env" ]]; then + if grep -q "^ANTHROPIC_API_KEY=sk-" .env 2>/dev/null; then + echo "✅ API key configured in .env" + else + echo "⚠️ .env exists but API key may not be set" + echo " Check ANTHROPIC_API_KEY in .env" + echo "" + fi +else + echo "⚠️ .env file not found" + echo " Run: cp .env.example .env" + echo " Then add your ANTHROPIC_API_KEY" + echo "" +fi + +# Check outputs directory +if [[ -d "outputs" ]]; then + FILE_COUNT=$(find outputs -type f 2>/dev/null | wc -l | tr -d ' ') + echo "✅ outputs/ directory exists ($FILE_COUNT files)" +else + echo "ℹ️ Creating outputs/ directory..." + mkdir -p outputs +fi + +# Show current status from plan +if [[ -f "docs/skills_cookbook_plan.md" ]]; then + echo "" + echo "📊 Current Status:" + PHASE_STATUS=$(grep -A1 "^**Phase:**" docs/skills_cookbook_plan.md 2>/dev/null | tail -1 || echo "Unknown") + echo " $PHASE_STATUS" +fi + +echo "" +echo "======================================" +echo "Ready to work on Skills Cookbook! 🚀" +echo "" +echo "Quick commands:" +echo " - jupyter notebook # Launch notebooks" +echo " - ls outputs/ # View generated files" +echo " - cat CLAUDE.md # View project guide" diff --git a/skills/.claude/settings.json b/skills/.claude/settings.json new file mode 100644 index 00000000..c9f6f2e1 --- /dev/null +++ b/skills/.claude/settings.json @@ -0,0 +1,30 @@ +{ + "hooks": { + "SessionStart": { + "command": ".claude/hooks/session-start.sh", + "description": "Verify Skills cookbook environment setup (SDK version, API key, directories)" + }, + "PreToolUse": [ + { + "command": ".claude/hooks/pre-write.sh", + "description": "Warn before overwriting protected files (whl, plan, sample data)", + "toolFilter": ["Write"] + }, + { + "command": ".claude/hooks/pre-bash.sh", + "description": "Safety checks for bash commands (rm, pip install, jupyter)", + "toolFilter": ["Bash"] + } + ] + }, + "contextFiles": [ + "CLAUDE.md", + "docs/skills_cookbook_plan.md" + ], + "projectInfo": { + "name": "Skills Cookbook", + "type": "jupyter-notebooks", + "language": "python", + "description": "Educational cookbook for Claude Skills API with progressive notebooks" + } +} diff --git a/skills/.env.example b/skills/.env.example new file mode 100644 index 00000000..36f4c811 --- /dev/null +++ b/skills/.env.example @@ -0,0 +1,14 @@ +# Claude Skills Cookbook Configuration +# Copy this file to .env and add your actual API key + +# Required: Your Anthropic API key from https://console.anthropic.com/ +ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +# Model selection (Skills require Claude 4.5 Sonnet or newer) +ANTHROPIC_MODEL=claude-sonnet-4-5-20250929 + +# Optional: Custom skills storage directory (default: ./custom_skills) +SKILLS_STORAGE_PATH=./custom_skills + +# Optional: Output directory for generated files (default: ./outputs) +OUTPUT_PATH=./outputs diff --git a/skills/.gitignore b/skills/.gitignore new file mode 100644 index 00000000..baa46fb7 --- /dev/null +++ b/skills/.gitignore @@ -0,0 +1,38 @@ +# Environment and secrets +.env +*.env + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +*.egg-info/ +dist/ +build/ + +# Jupyter +.ipynb_checkpoints/ +*.ipynb_checkpoints + +# Generated outputs +outputs/ +*.xlsx +*.pptx +*.pdf +*.docx + +# Documentation sources - internal only +docs/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/skills/CLAUDE.md b/skills/CLAUDE.md new file mode 100644 index 00000000..2c326d02 --- /dev/null +++ b/skills/CLAUDE.md @@ -0,0 +1,235 @@ +# Skills Cookbook - Claude Code Guide + +## Project Overview + +This is a comprehensive Jupyter notebook cookbook demonstrating Claude's Skills feature for document generation (Excel, PowerPoint, PDF). It's designed for developers learning to integrate Skills into their applications. + +## Quick Start Commands + +### Environment Setup +```bash +# Create and activate virtual environment +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate + +# Install dependencies (MUST use local whl for Skills support) +pip install -r requirements.txt + +# Configure API key +cp .env.example .env +# Edit .env and add your ANTHROPIC_API_KEY +``` + +### Running Notebooks +```bash +# Launch Jupyter +jupyter notebook + +# Or use VSCode with Jupyter extension +# Make sure to select the venv kernel in VSCode: Cmd+Shift+P → "Python: Select Interpreter" +``` + +### Testing & Verification +```bash +# Verify environment and SDK version +python -c "import anthropic; print(f'SDK Version: {anthropic.__version__}')" + +# Check outputs directory for generated files +ls -lh outputs/ +``` + +## Architecture Overview + +### Directory Structure +``` +skills/ +├── notebooks/ # 3 progressive Jupyter notebooks +│ ├── 01_skills_introduction.ipynb +│ ├── 02_skills_financial_applications.ipynb # WIP +│ └── 03_skills_custom_development.ipynb # WIP +├── sample_data/ # Financial datasets for examples +├── custom_skills/ # Custom skill development area +├── outputs/ # Generated files (xlsx, pptx, pdf) +├── file_utils.py # Files API helper functions +└── docs/ # Implementation tracking +``` + +### Key Technical Details + +**Beta API Requirements:** +- All Skills functionality uses `client.beta.*` namespace +- Required beta headers: `code-execution-2025-08-25`, `files-api-2025-04-14`, `skills-2025-10-02` +- Must use `client.beta.messages.create()` with `container` parameter +- Code execution tool (`code_execution_20250825`) is REQUIRED + +**Files API Integration:** +- Skills generate files and return `file_id` attributes +- Must use `client.beta.files.download()` to download files +- Must use `client.beta.files.retrieve_metadata()` to get file info +- Helper functions in `file_utils.py` handle extraction and download + +**Built-in Skills:** +- `xlsx` - Excel workbooks with formulas and charts +- `pptx` - PowerPoint presentations +- `pdf` - PDF documents +- `docx` - Word documents + +## Development Gotchas + +### 1. SDK Version +**Important**: Ensure you have the Anthropic SDK version 0.71.0 or later with Skills support +```bash +pip install anthropic>=0.71.0 +# Restart Jupyter kernel after installation if upgrading! +``` + +### 2. Beta Namespace Required +**Problem**: `container` parameter not recognized, files API fails +**Solution**: Use `client.beta.messages.create()` and `client.beta.files.*` +```python +# ❌ Wrong +response = client.messages.create(container={...}) +content = client.files.content(file_id) + +# ✅ Correct +response = client.beta.messages.create(container={...}) +content = client.beta.files.content(file_id) +``` + +### 3. Beta Headers Placement +**Problem**: Setting Skills beta in default_headers requires code_execution on ALL requests +**Solution**: Use `betas` parameter per-request instead +```python +# ❌ Wrong (affects all requests) +client = Anthropic(default_headers={"anthropic-beta": "skills-2025-10-02"}) + +# ✅ Correct (per-request) +response = client.beta.messages.create( + betas=["code-execution-2025-08-25", "files-api-2025-04-14", "skills-2025-10-02"], + ... +) +``` + +### 4. File ID Extraction +**Problem**: Response structure differs from standard Messages API +**Solution**: File IDs in `bash_code_execution_tool_result.content.content[0].file_id` +```python +# Use file_utils.extract_file_ids() - handles beta response structure +from file_utils import extract_file_ids, download_all_files +file_ids = extract_file_ids(response) +``` + +### 5. Files API Response Objects +**Problem**: `'BinaryAPIResponse' object has no attribute 'content'`, `'FileMetadata' object has no attribute 'size'` +**Solution**: Use `.read()` for file content and `.size_bytes` for file size +```python +# ❌ Wrong +file_content = client.beta.files.download(file_id) +with open(path, 'wb') as f: + f.write(file_content.content) # No .content attribute! + +# ✅ Correct +file_content = client.beta.files.download(file_id) +with open(path, 'wb') as f: + f.write(file_content.read()) # Use .read() + +# FileMetadata fields: id, filename, size_bytes (not size), mime_type, created_at, type, downloadable +metadata = client.beta.files.retrieve_metadata(file_id) +print(f"Size: {metadata.size_bytes} bytes") # Use size_bytes, not size +``` + +### 6. Jupyter Kernel Selection +**Problem**: Wrong Python interpreter = wrong dependencies +**Solution**: Always select venv kernel in VSCode/Jupyter +- VSCode: Cmd+Shift+P → "Python: Select Interpreter" → select venv +- Jupyter: Kernel → Change Kernel → select venv + +### 7. Module Reload Required +**Problem**: Changes to `file_utils.py` not reflected in running notebooks +**Solution**: Restart kernel or reload module +```python +import importlib +importlib.reload(file_utils) +``` + +### 8. Document Generation Times +**Problem**: File creation takes longer than typical API calls, users may think cell is frozen +**Actual Observed Times:** +- Excel: ~2 minutes +- PowerPoint: ~1-2 minutes (simple 2-3 slide presentations) +- PDF: ~1-2 minutes + +**Solution**: Add clear timing expectations before file creation cells +```markdown +**⏱️ Note**: Excel generation typically takes 1-2 minutes. +Be patient - the cell will show [*] while running! +``` +**Important**: Keep examples simple and focused. Generation times are consistent at 1-2 minutes for well-scoped examples. + +## Common Tasks + +### Adding a New Notebook Section +1. Follow existing structure in `01_skills_introduction.ipynb` +2. Include setup cell with imports and beta headers +3. Show API call, response handling, file download +4. Add error handling examples +5. Update `docs/skills_cookbook_plan.md` checklist + +### Creating Sample Data +1. Add realistic financial data to `sample_data/` +2. Use CSV for tabular, JSON for structured +3. Include headers and proper formatting +4. Reference in notebook with pandas +5. Keep file sizes reasonable (<100KB) + +### Testing File Download +1. Run notebook cell to generate file +2. Check response for file_id +3. Use `download_all_files()` helper +4. Verify file in `outputs/` directory +5. Open file in native app to validate + +**Note**: Files are overwritten by default. You'll see `[overwritten]` in the download summary when a file already existed. Set `overwrite=False` to prevent this. + +### Debugging API Errors +1. Check SDK version: `anthropic.__version__` should be `0.69.0` +2. Verify beta headers are passed per-request +3. Ensure code_execution tool is included +4. Check response structure with `print(response)` +5. Look for error details in `response.stop_reason` + +## Testing Checklist + +Before committing notebook changes: +- [ ] Restart kernel and run all cells +- [ ] Verify all file downloads work +- [ ] Check outputs/ for generated files +- [ ] Validate files open correctly in native apps +- [ ] Update skills_cookbook_plan.md checklist +- [ ] Test in fresh virtual environment + +## Resources + +- **API Reference**: https://docs.claude.com/en/api/messages +- **Files API**: https://docs.claude.com/en/api/files-content +- **Skills Documentation**: https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview + +## Project-Specific Notes + +- **Focus Domain**: Finance & Analytics with practical business applications +- **Target Audience**: Intermediate developers and business analysts +- **Notebook 1**: Complete and tested (file downloads working) +- **Notebook 2**: Financial Applications - next priority +- **Notebook 3**: Custom Skills Development - after Notebook 2 + +## Environment Variables + +Required in `.env`: +```bash +ANTHROPIC_API_KEY=your-api-key-here +``` + +Optional (for advanced examples): +```bash +ANTHROPIC_BASE_URL=https://api.anthropic.com # If using proxy +``` diff --git a/skills/README.md b/skills/README.md index 868e7142..12ecac6d 100644 --- a/skills/README.md +++ b/skills/README.md @@ -1,19 +1,329 @@ -# Claude Skills +# Claude Skills Cookbook 🚀 -Welcome to the Skills section of the Claude Cookbooks! This directory contains a collection of guides that showcase specific skills and capabilities where Claude excels. Each guide provides an in-depth exploration of a particular skill, discussing potential use cases, prompt engineering techniques to optimize results, and approaches for evaluating Claude's performance. +A comprehensive guide to using Claude's Skills feature for document generation, data analysis, and business automation. This cookbook demonstrates how to leverage Claude's built-in skills for Excel, PowerPoint, and PDF creation, as well as how to build custom skills for specialized workflows. -## Guides +> **🎯 See Skills in Action:** Check out **[Claude Creates Files](https://www.anthropic.com/news/create-files)** to see how these Skills power Claude's ability to create and edit documents directly in Claude.ai and the desktop app! -- **[Classification with Claude](./classification/guide.ipynb)**: Discover how Claude can revolutionize classification tasks, especially in scenarios with complex business rules and limited training data. This guide walks you through data preparation, prompt engineering with retrieval-augmented generation (RAG), testing, and evaluation. +## What are Skills? -- **[Retrieval Augmented Generation with Claude](./retrieval_augmented_generation/guide.ipynb)**: Learn how to enhance Claude's capabilities with domain-specific knowledge using RAG. This guide demonstrates how to build a RAG system from scratch, optimize its performance, and create an evaluation suite. You'll learn how techniques like summary indexing and re-ranking can significantly improve precision, recall, and overall accuracy in question-answering tasks. +Skills are organized packages of instructions, executable code, and resources that give Claude specialized capabilities for specific tasks. Think of them as "expertise packages" that Claude can discover and load dynamically to: -- **[Retrieval Augmented Generation with Contextual Embeddings](./contextual-embeddings/guide.ipynb)**: Learn how to use a new technique to improve the performance of your RAG system. In traditional RAG, documents are typically split into smaller chunks for efficient retrieval. While this approach works well for many applications, it can lead to problems when individual chunks lack sufficient context. Contextual Embeddings solve this problem by adding relevant context to each chunk before embedding. You'll learn how to use contextual embeddings with semantic search, BM25 search, and reranking to improve performance. +- Create professional documents (Excel, PowerPoint, PDF, Word) +- Perform complex data analysis and visualization +- Apply company-specific workflows and branding +- Automate business processes with domain expertise -- **[Summarization with Claude](./summarization/guide.ipynb)**: Explore Claude's ability to summarize and synthesize information from multiple sources. This guide covers a variety of summarization techniques, including multi-shot, domain-based, and chunking methods, as well as strategies for handling long-form content and multiple documents. We also explore evaluating summaries, which can be a balance of art, subjectivity, and the right approach! +📖 Read our engineering blog post on [Equipping agents for the real world with Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills) -- **[Text-to-SQL with Claude](./text_to_sql/guide.ipynb)**: This guide covers how to generate complex SQL queries from natural language using prompting techniques, self-improvement, and RAG. We'll also explore how to evaluate and improve the accuracy of generated SQL queries, with evals that test for syntax, data correctness, row count, and more. +## Key Features -## Getting Started +✨ **Progressive Disclosure Architecture** - Skills load only when needed, optimizing token usage +📊 **Financial Focus** - Real-world examples for finance and business analytics +🔧 **Custom Skills Development** - Learn to build and deploy your own skills +🎯 **Production-Ready Examples** - Code you can adapt for immediate use -To get started with the Skills guides, simply navigate to the desired guide's directory and follow the instructions provided in the `guide.ipynb` file. Each guide is self-contained and includes all the necessary code, data, and evaluation scripts to reproduce the examples and experiments. \ No newline at end of file +## Cookbook Structure + +### 📚 [Notebook 1: Introduction to Skills](notebooks/01_skills_introduction.ipynb) +Learn the fundamentals of Claude's Skills feature with quick-start examples. +- Understanding Skills architecture +- Setting up the API with beta headers +- Creating your first Excel spreadsheet +- Generating PowerPoint presentations +- Exporting to PDF format + +### 💼 [Notebook 2: Financial Applications](notebooks/02_skills_financial_applications.ipynb) +Explore powerful business use cases with real financial data. +- Building financial dashboards with charts and pivot tables +- Portfolio analysis and investment reporting +- Cross-format workflows: CSV → Excel → PowerPoint → PDF +- Token optimization strategies + +### 🔧 [Notebook 3: Custom Skills Development](notebooks/03_skills_custom_development.ipynb) +Master the art of creating your own specialized skills. +- Building a financial ratio calculator +- Creating company brand guidelines skill +- Advanced: Financial modeling suite +- [Best practices](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices) and security considerations + +## Quick Start + +### Prerequisites + +- Python 3.8 or higher +- Anthropic API key ([get one here](https://console.anthropic.com/)) +- Jupyter Notebook or JupyterLab + +### Installation + +1. **Clone the repository** +```bash +git clone https://github.com/anthropics/claude-cookbooks.git +cd claude-cookbooks/skills +``` + +2. **Create virtual environment** (recommended) +```bash +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +``` + +3. **Install dependencies** +```bash +pip install -r requirements.txt +``` + +4. **Configure API key** +```bash +cp .env.example .env +# Edit .env and add your ANTHROPIC_API_KEY +``` + +5. **Launch Jupyter** +```bash +jupyter notebook +``` + +6. **Start with Notebook 1** +Open `notebooks/01_skills_introduction.ipynb` and follow along! + +## Sample Data + +The cookbook includes realistic financial datasets in `sample_data/`: + +- 📊 **financial_statements.csv** - Quarterly P&L, balance sheet, and cash flow data +- 💰 **portfolio_holdings.json** - Investment portfolio with performance metrics +- 📋 **budget_template.csv** - Department budget with variance analysis +- 📈 **quarterly_metrics.json** - KPIs and operational metrics + +## Project Structure + +``` +skills/ +├── notebooks/ # Jupyter notebooks +│ ├── 01_skills_introduction.ipynb +│ ├── 02_skills_financial_applications.ipynb +│ └── 03_skills_custom_development.ipynb +├── sample_data/ # Financial datasets +│ ├── financial_statements.csv +│ ├── portfolio_holdings.json +│ ├── budget_template.csv +│ └── quarterly_metrics.json +├── custom_skills/ # Your custom skills +│ ├── financial_analyzer/ +│ ├── brand_guidelines/ +│ └── report_generator/ +├── outputs/ # Generated files +├── docs/ # Documentation +├── requirements.txt # Python dependencies +├── .env.example # Environment template +└── README.md # This file +``` + +## API Configuration + +Skills require specific beta headers. The notebooks handle this automatically, but here's what's happening behind the scenes: + +```python +from anthropic import Anthropic + +client = Anthropic( + api_key="your-api-key", + default_headers={ + "anthropic-beta": "code-execution-2025-08-25,files-api-2025-04-14,skills-2025-10-02" + } +) +``` + +**Required Beta Headers:** +- `code-execution-2025-08-25` - Enables code execution for Skills +- `files-api-2025-04-14` - Required for downloading generated files +- `skills-2025-10-02` - Enables Skills feature + +## Working with Generated Files + +When Skills create documents (Excel, PowerPoint, PDF, etc.), they return `file_id` attributes in the response. You must use the **Files API** to download these files. + +### How It Works + +1. **Skills create files** during code execution +2. **Response includes file_ids** for each created file +3. **Use Files API** to download the actual file content +4. **Save locally** or process as needed + +### Example: Creating and Downloading an Excel File + +```python +from anthropic import Anthropic + +client = Anthropic(api_key="your-api-key") + +# Step 1: Use a skill to create a file +response = client.messages.create( + model="claude-sonnet-4-5-20250929", + max_tokens=4096, + container={ + "skills": [ + {"type": "anthropic", "skill_id": "xlsx", "version": "latest"} + ] + }, + tools=[{"type": "code_execution_20250825", "name": "code_execution"}], + messages=[{ + "role": "user", + "content": "Create an Excel file with a simple budget spreadsheet" + }] +) + +# Step 2: Extract file_id from the response +file_id = None +for block in response.content: + if block.type == "tool_result" and hasattr(block, 'output'): + # Look for file_id in the tool output + if 'file_id' in str(block.output): + file_id = extract_file_id(block.output) # Parse the file_id + break + +# Step 3: Download the file using Files API +if file_id: + file_content = client.beta.files.download(file_id=file_id) + + # Step 4: Save to disk + with open("outputs/budget.xlsx", "wb") as f: + f.write(file_content.read()) + + print(f"✅ File downloaded: budget.xlsx") +``` + +### Files API Methods + +```python +# Download file content (binary) +content = client.beta.files.download(file_id="file_abc123...") +with open("output.xlsx", "wb") as f: + f.write(content.read()) # Use .read() not .content + +# Get file metadata +info = client.beta.files.retrieve_metadata(file_id="file_abc123...") +print(f"Filename: {info.filename}, Size: {info.size_bytes} bytes") # Use size_bytes not size + +# List all files +files = client.beta.files.list() +for file in files.data: + print(f"{file.filename} - {file.created_at}") + +# Delete a file +client.beta.files.delete(file_id="file_abc123...") +``` + +**Important Notes:** +- Files are stored temporarily on Anthropic's servers +- Downloaded files should be saved to your local `outputs/` directory +- The Files API uses the same API key as the Messages API +- All notebooks include helper functions for file download +- **Files are overwritten by default** - rerunning cells will replace existing files (you'll see `[overwritten]` in the output) + +See the [Files API documentation](https://docs.claude.com/en/api/files-content) for complete details. + +## Built-in Skills Reference + +Claude comes with these pre-built skills: + +| Skill | ID | Description | +|-------|-----|-------------| +| Excel | `xlsx` | Create and manipulate Excel workbooks with formulas, charts, and formatting | +| PowerPoint | `pptx` | Generate professional presentations with slides, charts, and transitions | +| PDF | `pdf` | Create formatted PDF documents with text, tables, and images | +| Word | `docx` | Generate Word documents with rich formatting and structure | + +## Creating Custom Skills + +Custom skills follow this structure: + +``` +my_skill/ +├── SKILL.md # Required: Instructions for Claude +├── scripts/ # Optional: Python/JS code +│ └── processor.py +└── resources/ # Optional: Templates, data + └── template.xlsx +``` + +Learn more in [Notebook 3](notebooks/03_skills_custom_development.ipynb). + +## Common Use Cases + +### Financial Reporting +- Automated quarterly reports +- Budget variance analysis +- Investment performance dashboards + +### Data Analysis +- Excel-based analytics with complex formulas +- Pivot table generation +- Statistical analysis and visualization + +### Document Automation +- Branded presentation generation +- Report compilation from multiple sources +- Cross-format document conversion + +## Performance Tips + +1. **Use Progressive Disclosure**: Skills load in stages to minimize token usage +2. **Batch Operations**: Process multiple files in a single conversation +3. **Skill Composition**: Combine multiple skills for complex workflows +4. **Cache Reuse**: Use container IDs to reuse loaded skills + +## Troubleshooting + +### Common Issues + +**API Key Not Found** +``` +ValueError: ANTHROPIC_API_KEY not found +``` +→ Make sure you've copied `.env.example` to `.env` and added your key + +**Skills Beta Header Missing** +``` +Error: Skills feature requires beta header +``` +→ Ensure you're using the correct beta headers as shown in the notebooks + +**Token Limit Exceeded** +``` +Error: Request exceeds token limit +``` +→ Break large operations into smaller chunks or use progressive disclosure + +## Resources + +### Documentation +- 📖 [Claude API Documentation](https://docs.anthropic.com/en/api/messages) +- 🔧 [Skills Documentation](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview) + +### Support Articles +- 📚 [Teach Claude your way of working using Skills](https://support.claude.com/en/articles/12580051-teach-claude-your-way-of-working-using-skills) - User guide for working with Skills +- 🛠️ [How to create a skill with Claude through conversation](https://support.claude.com/en/articles/12599426-how-to-create-a-skill-with-claude-through-conversation) - Interactive skill creation guide + +### Community & Support +- 💬 [Claude Support](https://support.claude.com) +- 🐙 [GitHub Issues](https://github.com/anthropics/claude-cookbooks/issues) + +## Contributing + +We welcome contributions! Please see [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines. + +## License + +This cookbook is provided under the MIT License. See [LICENSE](../LICENSE) for details. + +## Acknowledgments + +Special thanks to the Anthropic team for developing the Skills feature and providing the SDK. + +--- + +**Questions?** Check the [FAQ](docs/FAQ.md) or open an issue. + +**Ready to start?** Open [Notebook 1](notebooks/01_skills_introduction.ipynb) and let's build something amazing! 🎉 \ No newline at end of file diff --git a/skills/assets/not-just-markdown.png b/skills/assets/not-just-markdown.png new file mode 100644 index 00000000..cfe4c3b2 Binary files /dev/null and b/skills/assets/not-just-markdown.png differ diff --git a/skills/assets/prog-disc-1.png b/skills/assets/prog-disc-1.png new file mode 100644 index 00000000..f2c28131 Binary files /dev/null and b/skills/assets/prog-disc-1.png differ diff --git a/skills/assets/prog-disc-2.png b/skills/assets/prog-disc-2.png new file mode 100644 index 00000000..0d3d4f57 Binary files /dev/null and b/skills/assets/prog-disc-2.png differ diff --git a/skills/assets/skills-bundled-files.png b/skills/assets/skills-bundled-files.png new file mode 100644 index 00000000..eaa8bbbd Binary files /dev/null and b/skills/assets/skills-bundled-files.png differ diff --git a/skills/assets/skills-conceptual-diagram.png b/skills/assets/skills-conceptual-diagram.png new file mode 100644 index 00000000..bccd6c0e Binary files /dev/null and b/skills/assets/skills-conceptual-diagram.png differ diff --git a/skills/custom_skills/analyzing-financial-statements/SKILL.md b/skills/custom_skills/analyzing-financial-statements/SKILL.md new file mode 100644 index 00000000..646f77f0 --- /dev/null +++ b/skills/custom_skills/analyzing-financial-statements/SKILL.md @@ -0,0 +1,69 @@ +--- +name: Analyzing Financial Statements +description: This skill calculates key financial ratios and metrics from financial statement data for investment analysis +--- + +# Financial Ratio Calculator Skill + +This skill provides comprehensive financial ratio analysis for evaluating company performance, profitability, liquidity, and valuation. + +## Capabilities + +Calculate and interpret: +- **Profitability Ratios**: ROE, ROA, Gross Margin, Operating Margin, Net Margin +- **Liquidity Ratios**: Current Ratio, Quick Ratio, Cash Ratio +- **Leverage Ratios**: Debt-to-Equity, Interest Coverage, Debt Service Coverage +- **Efficiency Ratios**: Asset Turnover, Inventory Turnover, Receivables Turnover +- **Valuation Ratios**: P/E, P/B, P/S, EV/EBITDA, PEG +- **Per-Share Metrics**: EPS, Book Value per Share, Dividend per Share + +## How to Use + +1. **Input Data**: Provide financial statement data (income statement, balance sheet, cash flow) +2. **Select Ratios**: Specify which ratios to calculate or use "all" for comprehensive analysis +3. **Interpretation**: The skill will calculate ratios and provide industry-standard interpretations + +## Input Format + +Financial data can be provided as: +- CSV with financial line items +- JSON with structured financial statements +- Text description of key financial figures +- Excel files with financial statements + +## Output Format + +Results include: +- Calculated ratios with values +- Industry benchmark comparisons (when available) +- Trend analysis (if multiple periods provided) +- Interpretation and insights +- Excel report with formatted results + +## Example Usage + +"Calculate key financial ratios for this company based on the attached financial statements" + +"What's the P/E ratio if the stock price is $50 and annual earnings are $2.50 per share?" + +"Analyze the liquidity position using the balance sheet data" + +## Scripts + +- `calculate_ratios.py`: Main calculation engine for all financial ratios +- `interpret_ratios.py`: Provides interpretation and benchmarking + +## Best Practices + +1. Always validate data completeness before calculations +2. Handle missing values appropriately (use industry averages or exclude) +3. Consider industry context when interpreting ratios +4. Include period comparisons for trend analysis +5. Flag unusual or concerning ratios + +## Limitations + +- Requires accurate financial data +- Industry benchmarks are general guidelines +- Some ratios may not apply to all industries +- Historical data doesn't guarantee future performance \ No newline at end of file diff --git a/skills/custom_skills/analyzing-financial-statements/calculate_ratios.py b/skills/custom_skills/analyzing-financial-statements/calculate_ratios.py new file mode 100644 index 00000000..2b6a06d8 --- /dev/null +++ b/skills/custom_skills/analyzing-financial-statements/calculate_ratios.py @@ -0,0 +1,320 @@ +""" +Financial ratio calculation module. +Provides functions to calculate key financial metrics and ratios. +""" + +import json +from typing import Dict, Any, Optional, List + + +class FinancialRatioCalculator: + """Calculate financial ratios from financial statement data.""" + + def __init__(self, financial_data: Dict[str, Any]): + """ + Initialize with financial statement data. + + Args: + financial_data: Dictionary containing income_statement, balance_sheet, + cash_flow, and market_data + """ + self.income_statement = financial_data.get('income_statement', {}) + self.balance_sheet = financial_data.get('balance_sheet', {}) + self.cash_flow = financial_data.get('cash_flow', {}) + self.market_data = financial_data.get('market_data', {}) + self.ratios = {} + + def safe_divide(self, numerator: float, denominator: float, default: float = 0.0) -> float: + """Safely divide two numbers, returning default if denominator is zero.""" + if denominator == 0: + return default + return numerator / denominator + + def calculate_profitability_ratios(self) -> Dict[str, float]: + """Calculate profitability ratios.""" + ratios = {} + + # ROE (Return on Equity) + net_income = self.income_statement.get('net_income', 0) + shareholders_equity = self.balance_sheet.get('shareholders_equity', 0) + ratios['roe'] = self.safe_divide(net_income, shareholders_equity) + + # ROA (Return on Assets) + total_assets = self.balance_sheet.get('total_assets', 0) + ratios['roa'] = self.safe_divide(net_income, total_assets) + + # Gross Margin + revenue = self.income_statement.get('revenue', 0) + cogs = self.income_statement.get('cost_of_goods_sold', 0) + gross_profit = revenue - cogs + ratios['gross_margin'] = self.safe_divide(gross_profit, revenue) + + # Operating Margin + operating_income = self.income_statement.get('operating_income', 0) + ratios['operating_margin'] = self.safe_divide(operating_income, revenue) + + # Net Margin + ratios['net_margin'] = self.safe_divide(net_income, revenue) + + return ratios + + def calculate_liquidity_ratios(self) -> Dict[str, float]: + """Calculate liquidity ratios.""" + ratios = {} + + current_assets = self.balance_sheet.get('current_assets', 0) + current_liabilities = self.balance_sheet.get('current_liabilities', 0) + + # Current Ratio + ratios['current_ratio'] = self.safe_divide(current_assets, current_liabilities) + + # Quick Ratio (Acid Test) + inventory = self.balance_sheet.get('inventory', 0) + quick_assets = current_assets - inventory + ratios['quick_ratio'] = self.safe_divide(quick_assets, current_liabilities) + + # Cash Ratio + cash = self.balance_sheet.get('cash_and_equivalents', 0) + ratios['cash_ratio'] = self.safe_divide(cash, current_liabilities) + + return ratios + + def calculate_leverage_ratios(self) -> Dict[str, float]: + """Calculate leverage/solvency ratios.""" + ratios = {} + + total_debt = self.balance_sheet.get('total_debt', 0) + shareholders_equity = self.balance_sheet.get('shareholders_equity', 0) + + # Debt-to-Equity Ratio + ratios['debt_to_equity'] = self.safe_divide(total_debt, shareholders_equity) + + # Interest Coverage Ratio + ebit = self.income_statement.get('ebit', 0) + interest_expense = self.income_statement.get('interest_expense', 0) + ratios['interest_coverage'] = self.safe_divide(ebit, interest_expense) + + # Debt Service Coverage Ratio + net_operating_income = self.income_statement.get('operating_income', 0) + total_debt_service = interest_expense + self.balance_sheet.get('current_portion_long_term_debt', 0) + ratios['debt_service_coverage'] = self.safe_divide(net_operating_income, total_debt_service) + + return ratios + + def calculate_efficiency_ratios(self) -> Dict[str, float]: + """Calculate efficiency/activity ratios.""" + ratios = {} + + revenue = self.income_statement.get('revenue', 0) + total_assets = self.balance_sheet.get('total_assets', 0) + + # Asset Turnover + ratios['asset_turnover'] = self.safe_divide(revenue, total_assets) + + # Inventory Turnover + cogs = self.income_statement.get('cost_of_goods_sold', 0) + inventory = self.balance_sheet.get('inventory', 0) + ratios['inventory_turnover'] = self.safe_divide(cogs, inventory) + + # Receivables Turnover + accounts_receivable = self.balance_sheet.get('accounts_receivable', 0) + ratios['receivables_turnover'] = self.safe_divide(revenue, accounts_receivable) + + # Days Sales Outstanding + ratios['days_sales_outstanding'] = self.safe_divide(365, ratios['receivables_turnover']) + + return ratios + + def calculate_valuation_ratios(self) -> Dict[str, float]: + """Calculate valuation ratios.""" + ratios = {} + + share_price = self.market_data.get('share_price', 0) + shares_outstanding = self.market_data.get('shares_outstanding', 0) + market_cap = share_price * shares_outstanding + + # P/E Ratio + net_income = self.income_statement.get('net_income', 0) + eps = self.safe_divide(net_income, shares_outstanding) + ratios['pe_ratio'] = self.safe_divide(share_price, eps) + ratios['eps'] = eps + + # P/B Ratio + book_value = self.balance_sheet.get('shareholders_equity', 0) + book_value_per_share = self.safe_divide(book_value, shares_outstanding) + ratios['pb_ratio'] = self.safe_divide(share_price, book_value_per_share) + ratios['book_value_per_share'] = book_value_per_share + + # P/S Ratio + revenue = self.income_statement.get('revenue', 0) + ratios['ps_ratio'] = self.safe_divide(market_cap, revenue) + + # EV/EBITDA + ebitda = self.income_statement.get('ebitda', 0) + total_debt = self.balance_sheet.get('total_debt', 0) + cash = self.balance_sheet.get('cash_and_equivalents', 0) + enterprise_value = market_cap + total_debt - cash + ratios['ev_to_ebitda'] = self.safe_divide(enterprise_value, ebitda) + + # PEG Ratio (if growth rate available) + earnings_growth = self.market_data.get('earnings_growth_rate', 0) + if earnings_growth > 0: + ratios['peg_ratio'] = self.safe_divide(ratios['pe_ratio'], earnings_growth * 100) + + return ratios + + def calculate_all_ratios(self) -> Dict[str, Any]: + """Calculate all financial ratios.""" + return { + 'profitability': self.calculate_profitability_ratios(), + 'liquidity': self.calculate_liquidity_ratios(), + 'leverage': self.calculate_leverage_ratios(), + 'efficiency': self.calculate_efficiency_ratios(), + 'valuation': self.calculate_valuation_ratios() + } + + def interpret_ratio(self, ratio_name: str, value: float) -> str: + """Provide interpretation for a specific ratio.""" + interpretations = { + 'current_ratio': lambda v: ( + "Strong liquidity" if v > 2 else + "Adequate liquidity" if v > 1.5 else + "Potential liquidity concerns" if v > 1 else + "Liquidity issues" + ), + 'debt_to_equity': lambda v: ( + "Low leverage" if v < 0.5 else + "Moderate leverage" if v < 1 else + "High leverage" if v < 2 else + "Very high leverage" + ), + 'roe': lambda v: ( + "Excellent returns" if v > 0.20 else + "Good returns" if v > 0.15 else + "Average returns" if v > 0.10 else + "Below average returns" if v > 0 else + "Negative returns" + ), + 'pe_ratio': lambda v: ( + "Potentially undervalued" if 0 < v < 15 else + "Fair value" if 15 <= v < 25 else + "Growth premium" if 25 <= v < 40 else + "High valuation" if v >= 40 else + "N/A (negative earnings)" if v <= 0 else "N/A" + ) + } + + if ratio_name in interpretations: + return interpretations[ratio_name](value) + return "No interpretation available" + + def format_ratio(self, name: str, value: float, format_type: str = "ratio") -> str: + """Format ratio value for display.""" + if format_type == "percentage": + return f"{value * 100:.2f}%" + elif format_type == "times": + return f"{value:.2f}x" + elif format_type == "days": + return f"{value:.1f} days" + elif format_type == "currency": + return f"${value:.2f}" + else: + return f"{value:.2f}" + + +def calculate_ratios_from_data(financial_data: Dict[str, Any]) -> Dict[str, Any]: + """ + Main function to calculate all ratios from financial data. + + Args: + financial_data: Dictionary with financial statement data + + Returns: + Dictionary with calculated ratios and interpretations + """ + calculator = FinancialRatioCalculator(financial_data) + ratios = calculator.calculate_all_ratios() + + # Add interpretations + interpretations = {} + for category, category_ratios in ratios.items(): + interpretations[category] = {} + for ratio_name, value in category_ratios.items(): + interpretations[category][ratio_name] = { + 'value': value, + 'formatted': calculator.format_ratio(ratio_name, value), + 'interpretation': calculator.interpret_ratio(ratio_name, value) + } + + return { + 'ratios': ratios, + 'interpretations': interpretations, + 'summary': generate_summary(ratios) + } + + +def generate_summary(ratios: Dict[str, Any]) -> str: + """Generate a text summary of the financial analysis.""" + summary_parts = [] + + # Profitability summary + prof = ratios.get('profitability', {}) + if prof.get('roe', 0) > 0: + summary_parts.append(f"ROE of {prof['roe']*100:.1f}% indicates {'strong' if prof['roe'] > 0.15 else 'moderate'} shareholder returns.") + + # Liquidity summary + liq = ratios.get('liquidity', {}) + if liq.get('current_ratio', 0) > 0: + summary_parts.append(f"Current ratio of {liq['current_ratio']:.2f} suggests {'good' if liq['current_ratio'] > 1.5 else 'potential'} liquidity {'position' if liq['current_ratio'] > 1.5 else 'concerns'}.") + + # Leverage summary + lev = ratios.get('leverage', {}) + if lev.get('debt_to_equity', 0) >= 0: + summary_parts.append(f"Debt-to-equity of {lev['debt_to_equity']:.2f} indicates {'conservative' if lev['debt_to_equity'] < 0.5 else 'moderate' if lev['debt_to_equity'] < 1 else 'high'} leverage.") + + # Valuation summary + val = ratios.get('valuation', {}) + if val.get('pe_ratio', 0) > 0: + summary_parts.append(f"P/E ratio of {val['pe_ratio']:.1f} suggests the stock is trading at {'a discount' if val['pe_ratio'] < 15 else 'fair value' if val['pe_ratio'] < 25 else 'a premium'}.") + + return " ".join(summary_parts) if summary_parts else "Insufficient data for summary." + + +# Example usage +if __name__ == "__main__": + # Sample financial data + sample_data = { + 'income_statement': { + 'revenue': 1000000, + 'cost_of_goods_sold': 600000, + 'operating_income': 200000, + 'ebit': 180000, + 'ebitda': 250000, + 'interest_expense': 20000, + 'net_income': 150000 + }, + 'balance_sheet': { + 'total_assets': 2000000, + 'current_assets': 800000, + 'cash_and_equivalents': 200000, + 'accounts_receivable': 150000, + 'inventory': 250000, + 'current_liabilities': 400000, + 'total_debt': 500000, + 'current_portion_long_term_debt': 50000, + 'shareholders_equity': 1500000 + }, + 'cash_flow': { + 'operating_cash_flow': 180000, + 'investing_cash_flow': -100000, + 'financing_cash_flow': -50000 + }, + 'market_data': { + 'share_price': 50, + 'shares_outstanding': 100000, + 'earnings_growth_rate': 0.10 + } + } + + results = calculate_ratios_from_data(sample_data) + print(json.dumps(results, indent=2)) \ No newline at end of file diff --git a/skills/custom_skills/analyzing-financial-statements/interpret_ratios.py b/skills/custom_skills/analyzing-financial-statements/interpret_ratios.py new file mode 100644 index 00000000..c25a4c63 --- /dev/null +++ b/skills/custom_skills/analyzing-financial-statements/interpret_ratios.py @@ -0,0 +1,364 @@ +""" +Financial ratio interpretation module. +Provides industry benchmarks and contextual analysis. +""" + +from typing import Dict, Any, List, Optional + + +class RatioInterpreter: + """Interpret financial ratios with industry context.""" + + # Industry benchmark ranges (simplified for demonstration) + BENCHMARKS = { + 'technology': { + 'current_ratio': {'excellent': 2.5, 'good': 1.8, 'acceptable': 1.2, 'poor': 1.0}, + 'debt_to_equity': {'excellent': 0.3, 'good': 0.5, 'acceptable': 1.0, 'poor': 2.0}, + 'roe': {'excellent': 0.25, 'good': 0.18, 'acceptable': 0.12, 'poor': 0.08}, + 'gross_margin': {'excellent': 0.70, 'good': 0.50, 'acceptable': 0.35, 'poor': 0.20}, + 'pe_ratio': {'undervalued': 15, 'fair': 25, 'growth': 35, 'expensive': 50} + }, + 'retail': { + 'current_ratio': {'excellent': 2.0, 'good': 1.5, 'acceptable': 1.0, 'poor': 0.8}, + 'debt_to_equity': {'excellent': 0.5, 'good': 0.8, 'acceptable': 1.5, 'poor': 2.5}, + 'roe': {'excellent': 0.20, 'good': 0.15, 'acceptable': 0.10, 'poor': 0.05}, + 'gross_margin': {'excellent': 0.40, 'good': 0.30, 'acceptable': 0.20, 'poor': 0.10}, + 'pe_ratio': {'undervalued': 12, 'fair': 18, 'growth': 25, 'expensive': 35} + }, + 'financial': { + 'current_ratio': {'excellent': 1.5, 'good': 1.2, 'acceptable': 1.0, 'poor': 0.8}, + 'debt_to_equity': {'excellent': 1.0, 'good': 2.0, 'acceptable': 4.0, 'poor': 6.0}, + 'roe': {'excellent': 0.15, 'good': 0.12, 'acceptable': 0.08, 'poor': 0.05}, + 'pe_ratio': {'undervalued': 10, 'fair': 15, 'growth': 20, 'expensive': 30} + }, + 'manufacturing': { + 'current_ratio': {'excellent': 2.2, 'good': 1.7, 'acceptable': 1.3, 'poor': 1.0}, + 'debt_to_equity': {'excellent': 0.4, 'good': 0.7, 'acceptable': 1.2, 'poor': 2.0}, + 'roe': {'excellent': 0.18, 'good': 0.14, 'acceptable': 0.10, 'poor': 0.06}, + 'gross_margin': {'excellent': 0.35, 'good': 0.25, 'acceptable': 0.18, 'poor': 0.12}, + 'pe_ratio': {'undervalued': 14, 'fair': 20, 'growth': 28, 'expensive': 40} + }, + 'healthcare': { + 'current_ratio': {'excellent': 2.3, 'good': 1.8, 'acceptable': 1.4, 'poor': 1.0}, + 'debt_to_equity': {'excellent': 0.3, 'good': 0.6, 'acceptable': 1.0, 'poor': 1.8}, + 'roe': {'excellent': 0.22, 'good': 0.16, 'acceptable': 0.11, 'poor': 0.07}, + 'gross_margin': {'excellent': 0.65, 'good': 0.45, 'acceptable': 0.30, 'poor': 0.20}, + 'pe_ratio': {'undervalued': 18, 'fair': 28, 'growth': 40, 'expensive': 55} + } + } + + def __init__(self, industry: str = 'general'): + """ + Initialize interpreter with industry context. + + Args: + industry: Industry sector for benchmarking + """ + self.industry = industry.lower() + self.benchmarks = self.BENCHMARKS.get(self.industry, self._get_general_benchmarks()) + + def _get_general_benchmarks(self) -> Dict[str, Any]: + """Get general industry-agnostic benchmarks.""" + return { + 'current_ratio': {'excellent': 2.0, 'good': 1.5, 'acceptable': 1.0, 'poor': 0.8}, + 'debt_to_equity': {'excellent': 0.5, 'good': 1.0, 'acceptable': 1.5, 'poor': 2.5}, + 'roe': {'excellent': 0.20, 'good': 0.15, 'acceptable': 0.10, 'poor': 0.05}, + 'gross_margin': {'excellent': 0.40, 'good': 0.30, 'acceptable': 0.20, 'poor': 0.10}, + 'pe_ratio': {'undervalued': 15, 'fair': 22, 'growth': 30, 'expensive': 45} + } + + def interpret_ratio(self, ratio_name: str, value: float) -> Dict[str, Any]: + """ + Interpret a single ratio with context. + + Args: + ratio_name: Name of the ratio + value: Calculated ratio value + + Returns: + Dictionary with interpretation details + """ + interpretation = { + 'value': value, + 'rating': 'N/A', + 'message': '', + 'recommendation': '', + 'benchmark_comparison': {} + } + + if ratio_name in self.benchmarks: + benchmark = self.benchmarks[ratio_name] + interpretation['benchmark_comparison'] = benchmark + + # Determine rating based on benchmarks + if ratio_name in ['current_ratio', 'roe', 'gross_margin']: + # Higher is better + if value >= benchmark['excellent']: + interpretation['rating'] = 'Excellent' + interpretation['message'] = f"Performance significantly exceeds industry standards" + elif value >= benchmark['good']: + interpretation['rating'] = 'Good' + interpretation['message'] = f"Above average performance for {self.industry} industry" + elif value >= benchmark['acceptable']: + interpretation['rating'] = 'Acceptable' + interpretation['message'] = f"Meets industry standards" + else: + interpretation['rating'] = 'Poor' + interpretation['message'] = f"Below industry standards - attention needed" + + elif ratio_name == 'debt_to_equity': + # Lower is better + if value <= benchmark['excellent']: + interpretation['rating'] = 'Excellent' + interpretation['message'] = f"Very conservative capital structure" + elif value <= benchmark['good']: + interpretation['rating'] = 'Good' + interpretation['message'] = f"Healthy leverage level" + elif value <= benchmark['acceptable']: + interpretation['rating'] = 'Acceptable' + interpretation['message'] = f"Moderate leverage" + else: + interpretation['rating'] = 'Poor' + interpretation['message'] = f"High leverage - potential risk" + + elif ratio_name == 'pe_ratio': + # Context-dependent + if value > 0: + if value < benchmark['undervalued']: + interpretation['rating'] = 'Potentially Undervalued' + interpretation['message'] = f"Trading below typical {self.industry} multiples" + elif value < benchmark['fair']: + interpretation['rating'] = 'Fair Value' + interpretation['message'] = f"In line with industry averages" + elif value < benchmark['growth']: + interpretation['rating'] = 'Growth Premium' + interpretation['message'] = f"Market pricing in growth expectations" + else: + interpretation['rating'] = 'Expensive' + interpretation['message'] = f"High valuation relative to industry" + + # Add specific recommendations + interpretation['recommendation'] = self._get_recommendation(ratio_name, interpretation['rating']) + + return interpretation + + def _get_recommendation(self, ratio_name: str, rating: str) -> str: + """Generate actionable recommendations based on ratio and rating.""" + recommendations = { + 'current_ratio': { + 'Poor': "Consider improving working capital management, reducing short-term debt, or increasing liquid assets", + 'Acceptable': "Monitor liquidity closely and consider building additional cash reserves", + 'Good': "Maintain current liquidity management practices", + 'Excellent': "Strong liquidity position - consider productive use of excess cash" + }, + 'debt_to_equity': { + 'Poor': "High leverage increases financial risk - consider debt reduction strategies", + 'Acceptable': "Monitor debt levels and ensure adequate interest coverage", + 'Good': "Balanced capital structure - maintain current approach", + 'Excellent': "Conservative leverage - may consider strategic use of debt for growth" + }, + 'roe': { + 'Poor': "Focus on improving operational efficiency and profitability", + 'Acceptable': "Explore opportunities to enhance returns through operational improvements", + 'Good': "Solid returns - continue current strategies", + 'Excellent': "Outstanding performance - ensure sustainability of high returns" + }, + 'pe_ratio': { + 'Potentially Undervalued': "May present buying opportunity if fundamentals are solid", + 'Fair Value': "Reasonably priced relative to industry peers", + 'Growth Premium': "Ensure growth prospects justify premium valuation", + 'Expensive': "Consider valuation risk - ensure fundamentals support high multiple" + } + } + + if ratio_name in recommendations and rating in recommendations[ratio_name]: + return recommendations[ratio_name][rating] + + return "Continue monitoring this metric" + + def analyze_trend(self, ratio_name: str, values: List[float], periods: List[str]) -> Dict[str, Any]: + """ + Analyze trend in a ratio over time. + + Args: + ratio_name: Name of the ratio + values: List of ratio values + periods: List of period labels + + Returns: + Trend analysis dictionary + """ + if len(values) < 2: + return {'trend': 'Insufficient data', 'message': 'Need at least 2 periods for trend analysis'} + + # Calculate trend + first_value = values[0] + last_value = values[-1] + change = last_value - first_value + pct_change = (change / abs(first_value)) * 100 if first_value != 0 else 0 + + # Determine trend direction + if abs(pct_change) < 5: + trend = 'Stable' + elif pct_change > 0: + trend = 'Improving' if ratio_name != 'debt_to_equity' else 'Deteriorating' + else: + trend = 'Deteriorating' if ratio_name != 'debt_to_equity' else 'Improving' + + return { + 'trend': trend, + 'change': change, + 'pct_change': pct_change, + 'message': f"{ratio_name} has {'increased' if change > 0 else 'decreased'} by {abs(pct_change):.1f}% from {periods[0]} to {periods[-1]}", + 'values': list(zip(periods, values)) + } + + def generate_report(self, ratios: Dict[str, Any]) -> str: + """ + Generate a comprehensive interpretation report. + + Args: + ratios: Dictionary of calculated ratios + + Returns: + Formatted report string + """ + report_lines = [ + f"Financial Analysis Report - {self.industry.title()} Industry Context", + "=" * 70, + "" + ] + + for category, category_ratios in ratios.items(): + report_lines.append(f"\n{category.upper()} ANALYSIS") + report_lines.append("-" * 40) + + for ratio_name, value in category_ratios.items(): + if isinstance(value, (int, float)): + interpretation = self.interpret_ratio(ratio_name, value) + report_lines.append(f"\n{ratio_name.replace('_', ' ').title()}:") + report_lines.append(f" Value: {value:.2f}") + report_lines.append(f" Rating: {interpretation['rating']}") + report_lines.append(f" Analysis: {interpretation['message']}") + report_lines.append(f" Action: {interpretation['recommendation']}") + + return "\n".join(report_lines) + + +def perform_comprehensive_analysis( + ratios: Dict[str, Any], + industry: str = 'general', + historical_data: Optional[Dict[str, Any]] = None +) -> Dict[str, Any]: + """ + Perform comprehensive ratio analysis with interpretations. + + Args: + ratios: Calculated financial ratios + industry: Industry sector for benchmarking + historical_data: Optional historical ratio data for trend analysis + + Returns: + Complete analysis with interpretations and recommendations + """ + interpreter = RatioInterpreter(industry) + analysis = { + 'current_analysis': {}, + 'trend_analysis': {}, + 'overall_health': {}, + 'recommendations': [] + } + + # Analyze current ratios + for category, category_ratios in ratios.items(): + analysis['current_analysis'][category] = {} + for ratio_name, value in category_ratios.items(): + if isinstance(value, (int, float)): + analysis['current_analysis'][category][ratio_name] = interpreter.interpret_ratio(ratio_name, value) + + # Perform trend analysis if historical data provided + if historical_data: + for ratio_name, historical_values in historical_data.items(): + if 'values' in historical_values and 'periods' in historical_values: + analysis['trend_analysis'][ratio_name] = interpreter.analyze_trend( + ratio_name, + historical_values['values'], + historical_values['periods'] + ) + + # Generate overall health assessment + analysis['overall_health'] = _assess_overall_health(analysis['current_analysis']) + + # Generate key recommendations + analysis['recommendations'] = _generate_key_recommendations(analysis) + + # Add formatted report + analysis['report'] = interpreter.generate_report(ratios) + + return analysis + + +def _assess_overall_health(current_analysis: Dict[str, Any]) -> Dict[str, str]: + """Assess overall financial health based on ratio analysis.""" + ratings = [] + for category, category_analysis in current_analysis.items(): + for ratio_name, ratio_analysis in category_analysis.items(): + if 'rating' in ratio_analysis: + ratings.append(ratio_analysis['rating']) + + # Simple scoring system + score_map = { + 'Excellent': 4, + 'Good': 3, + 'Acceptable': 2, + 'Poor': 1, + 'Fair Value': 3, + 'Potentially Undervalued': 3, + 'Growth Premium': 2, + 'Expensive': 1 + } + + scores = [score_map.get(rating, 2) for rating in ratings] + avg_score = sum(scores) / len(scores) if scores else 0 + + if avg_score >= 3.5: + health = "Excellent" + message = "Company shows strong financial health across most metrics" + elif avg_score >= 2.5: + health = "Good" + message = "Overall healthy financial position with some areas for improvement" + elif avg_score >= 1.5: + health = "Fair" + message = "Mixed financial indicators - attention needed in several areas" + else: + health = "Poor" + message = "Significant financial challenges requiring immediate attention" + + return { + 'status': health, + 'message': message, + 'score': f"{avg_score:.1f}/4.0" + } + + +def _generate_key_recommendations(analysis: Dict[str, Any]) -> List[str]: + """Generate prioritized recommendations based on analysis.""" + recommendations = [] + + # Check for critical issues + for category, category_analysis in analysis['current_analysis'].items(): + for ratio_name, ratio_analysis in category_analysis.items(): + if ratio_analysis.get('rating') == 'Poor': + recommendations.append(f"Priority: Address {ratio_name.replace('_', ' ')} - {ratio_analysis.get('recommendation', '')}") + + # Add trend-based recommendations + for ratio_name, trend in analysis.get('trend_analysis', {}).items(): + if trend.get('trend') == 'Deteriorating': + recommendations.append(f"Monitor: {ratio_name.replace('_', ' ')} showing negative trend") + + # Add general recommendations if healthy + if not recommendations: + recommendations.append("Continue current financial management practices") + recommendations.append("Consider strategic growth opportunities") + + return recommendations[:5] # Return top 5 recommendations \ No newline at end of file diff --git a/skills/custom_skills/applying-brand-guidelines/REFERENCE.md b/skills/custom_skills/applying-brand-guidelines/REFERENCE.md new file mode 100644 index 00000000..c453dcaa --- /dev/null +++ b/skills/custom_skills/applying-brand-guidelines/REFERENCE.md @@ -0,0 +1,137 @@ +# Brand Guidelines Reference + +## Quick Reference Card + +### Must-Have Elements +✅ Company logo on first page/slide +✅ Correct brand colors (no variations) +✅ Approved fonts only +✅ Consistent formatting throughout +✅ Professional tone of voice + +### Never Use +❌ Competitor logos or references +❌ Unapproved colors or gradients +❌ Decorative or script fonts +❌ Pixelated or stretched logos +❌ Informal language or slang + +## Color Codes Reference + +### For Digital (RGB/Hex) +| Color Name | Hex Code | RGB | Usage | +|------------|----------|-----|-------| +| Acme Blue | #0066CC | 0, 102, 204 | Primary headers, CTAs | +| Acme Navy | #003366 | 0, 51, 102 | Body text, secondary | +| Success Green | #28A745 | 40, 167, 69 | Positive values | +| Warning Amber | #FFC107 | 255, 193, 7 | Warnings, attention | +| Error Red | #DC3545 | 220, 53, 69 | Errors, negative | +| Neutral Gray | #6C757D | 108, 117, 125 | Muted text | +| Light Gray | #F8F9FA | 248, 249, 250 | Backgrounds | + +### For Print (CMYK) +| Color Name | CMYK | Pantone | +|------------|------|---------| +| Acme Blue | 100, 50, 0, 20 | 2935 C | +| Acme Navy | 100, 50, 0, 60 | 2965 C | + +## Document Templates + +### Email Signature +``` +[Name] +[Title] +Acme Corporation | Innovation Through Excellence +[Phone] | [Email] +www.acmecorp.example +``` + +### Slide Footer +``` +© 2025 Acme Corporation | Confidential | Page [X] +``` + +### Report Header +``` +[Logo] [Document Title] Page [X] of [Y] +``` + +## Accessibility Standards + +### Color Contrast +- Text on white background: Use Acme Navy (#003366) +- Text on blue background: Use white (#FFFFFF) +- Minimum contrast ratio: 4.5:1 for body text +- Minimum contrast ratio: 3:1 for large text + +### Font Sizes +- Minimum body text: 11pt (print), 14px (digital) +- Minimum caption text: 9pt (print), 12px (digital) + +## File Naming Conventions + +### Standard Format +``` +YYYY-MM-DD_DocumentType_Version_Status.ext +``` + +### Examples +- `2025-01-15_QuarterlyReport_v2_FINAL.pptx` +- `2025-01-15_BudgetAnalysis_v1_DRAFT.xlsx` +- `2025-01-15_Proposal_v3_APPROVED.pdf` + +## Common Mistakes to Avoid + +1. **Wrong Blue**: Using generic blue instead of Acme Blue #0066CC +2. **Stretched Logo**: Always maintain aspect ratio +3. **Too Many Colors**: Stick to the approved palette +4. **Inconsistent Fonts**: Don't mix font families +5. **Missing Logo**: Always include on first page +6. **Wrong Date Format**: Use "Month DD, YYYY" +7. **Decimal Places**: Be consistent (currency: 2, percentage: 1) + +## Department-Specific Guidelines + +### Finance +- Always right-align numbers in tables +- Use parentheses for negative values: ($1,234) +- Include data source citations + +### Marketing +- Can use full secondary color palette +- May include approved imagery +- Follow social media specific guidelines when applicable + +### Legal +- Use numbered sections (1.0, 1.1, 1.2) +- Include document control information +- Apply "Confidential" watermark when needed + +## International Considerations + +### Date Formats by Region +- **US**: Month DD, YYYY (January 15, 2025) +- **UK**: DD Month YYYY (15 January 2025) +- **ISO**: YYYY-MM-DD (2025-01-15) + +### Currency Display +- **USD**: $1,234.56 +- **EUR**: €1.234,56 +- **GBP**: £1,234.56 + +## Version History + +| Version | Date | Changes | +|---------|------|---------| +| 2.0 | Jan 2025 | Added digital color codes | +| 1.5 | Oct 2024 | Updated font guidelines | +| 1.0 | Jan 2024 | Initial brand guidelines | + +## Contact for Questions + +**Brand Team** +Email: brand@acmecorp.example +Slack: #brand-guidelines + +**For Exceptions** +Submit request to brand team with business justification \ No newline at end of file diff --git a/skills/custom_skills/applying-brand-guidelines/SKILL.md b/skills/custom_skills/applying-brand-guidelines/SKILL.md new file mode 100644 index 00000000..ca2cfc95 --- /dev/null +++ b/skills/custom_skills/applying-brand-guidelines/SKILL.md @@ -0,0 +1,171 @@ +--- +name: Applying Brand Guidelines +description: This skill applies consistent corporate branding and styling to all generated documents including colors, fonts, layouts, and messaging +--- + +# Corporate Brand Guidelines Skill + +This skill ensures all generated documents adhere to corporate brand standards for consistent, professional communication. + +## Brand Identity + +### Company: Acme Corporation +**Tagline**: "Innovation Through Excellence" +**Industry**: Technology Solutions + +## Visual Standards + +### Color Palette + +**Primary Colors**: +- **Acme Blue**: #0066CC (RGB: 0, 102, 204) - Headers, primary buttons +- **Acme Navy**: #003366 (RGB: 0, 51, 102) - Text, accents +- **White**: #FFFFFF - Backgrounds, reverse text + +**Secondary Colors**: +- **Success Green**: #28A745 (RGB: 40, 167, 69) - Positive metrics +- **Warning Amber**: #FFC107 (RGB: 255, 193, 7) - Cautions +- **Error Red**: #DC3545 (RGB: 220, 53, 69) - Negative values +- **Neutral Gray**: #6C757D (RGB: 108, 117, 125) - Secondary text + +### Typography + +**Primary Font Family**: Segoe UI, system-ui, -apple-system, sans-serif + +**Font Hierarchy**: +- **H1**: 32pt, Bold, Acme Blue +- **H2**: 24pt, Semibold, Acme Navy +- **H3**: 18pt, Semibold, Acme Navy +- **Body**: 11pt, Regular, Acme Navy +- **Caption**: 9pt, Regular, Neutral Gray + +### Logo Usage + +- Position: Top-left corner on first page/slide +- Size: 120px width (maintain aspect ratio) +- Clear space: Minimum 20px padding on all sides +- Never distort, rotate, or apply effects + +## Document Standards + +### PowerPoint Presentations + +**Slide Templates**: +1. **Title Slide**: Company logo, presentation title, date, presenter +2. **Section Divider**: Section title with blue background +3. **Content Slide**: Title bar with blue background, white content area +4. **Data Slide**: For charts/graphs, maintain color palette + +**Layout Rules**: +- Margins: 0.5 inches all sides +- Title position: Top 15% of slide +- Bullet indentation: 0.25 inches per level +- Maximum 6 bullet points per slide +- Charts use brand colors exclusively + +### Excel Spreadsheets + +**Formatting Standards**: +- **Headers**: Row 1, Bold, White text on Acme Blue background +- **Subheaders**: Bold, Acme Navy text +- **Data cells**: Regular, Acme Navy text +- **Borders**: Thin, Neutral Gray +- **Alternating rows**: Light gray (#F8F9FA) for readability + +**Chart Defaults**: +- Primary series: Acme Blue +- Secondary series: Success Green +- Gridlines: Neutral Gray, 0.5pt +- No 3D effects or gradients + +### PDF Documents + +**Page Layout**: +- **Header**: Company logo left, document title center, page number right +- **Footer**: Copyright notice left, date center, classification right +- **Margins**: 1 inch all sides +- **Line spacing**: 1.15 +- **Paragraph spacing**: 12pt after + +**Section Formatting**: +- Main headings: Acme Blue, 16pt, bold +- Subheadings: Acme Navy, 14pt, semibold +- Body text: Acme Navy, 11pt, regular + +## Content Guidelines + +### Tone of Voice + +- **Professional**: Formal but approachable +- **Clear**: Avoid jargon, use simple language +- **Active**: Use active voice, action-oriented +- **Positive**: Focus on solutions and benefits + +### Standard Phrases + +**Opening Statements**: +- "At Acme Corporation, we..." +- "Our commitment to innovation..." +- "Delivering excellence through..." + +**Closing Statements**: +- "Thank you for your continued partnership." +- "We look forward to serving your needs." +- "Together, we achieve excellence." + +### Data Presentation + +**Numbers**: +- Use comma separators for thousands +- Currency: $X,XXX.XX format +- Percentages: XX.X% (one decimal) +- Dates: Month DD, YYYY + +**Tables**: +- Headers in brand blue +- Alternating row colors +- Right-align numbers +- Left-align text + +## Quality Standards + +### Before Finalizing + +Always ensure: +1. Logo is properly placed and sized +2. All colors match brand palette exactly +3. Fonts are consistent throughout +4. No typos or grammatical errors +5. Data is accurately presented +6. Professional tone maintained + +### Prohibited Elements + +Never use: +- Clip art or stock photos without approval +- Comic Sans, Papyrus, or decorative fonts +- Rainbow colors or gradients +- Animations or transitions (unless specified) +- Competitor branding or references + +## Application Instructions + +When creating any document: +1. Start with brand colors and fonts +2. Apply appropriate template structure +3. Include logo on first page/slide +4. Use consistent formatting throughout +5. Review against brand standards +6. Ensure professional appearance + +## Scripts + +- `apply_brand.py`: Automatically applies brand formatting to documents +- `validate_brand.py`: Checks documents for brand compliance + +## Notes + +- These guidelines apply to all external communications +- Internal documents may use simplified formatting +- Special projects may have exceptions (request approval) +- Brand guidelines updated quarterly - check for latest version \ No newline at end of file diff --git a/skills/custom_skills/applying-brand-guidelines/apply_brand.py b/skills/custom_skills/applying-brand-guidelines/apply_brand.py new file mode 100644 index 00000000..c97a0f78 --- /dev/null +++ b/skills/custom_skills/applying-brand-guidelines/apply_brand.py @@ -0,0 +1,485 @@ +""" +Brand application module for corporate document styling. +Applies consistent branding to Excel, PowerPoint, and PDF documents. +""" + +from typing import Dict, Any, List, Optional + + +class BrandFormatter: + """Apply corporate brand guidelines to documents.""" + + # Brand color definitions + COLORS = { + 'primary': { + 'acme_blue': {'hex': '#0066CC', 'rgb': (0, 102, 204)}, + 'acme_navy': {'hex': '#003366', 'rgb': (0, 51, 102)}, + 'white': {'hex': '#FFFFFF', 'rgb': (255, 255, 255)} + }, + 'secondary': { + 'success_green': {'hex': '#28A745', 'rgb': (40, 167, 69)}, + 'warning_amber': {'hex': '#FFC107', 'rgb': (255, 193, 7)}, + 'error_red': {'hex': '#DC3545', 'rgb': (220, 53, 69)}, + 'neutral_gray': {'hex': '#6C757D', 'rgb': (108, 117, 125)}, + 'light_gray': {'hex': '#F8F9FA', 'rgb': (248, 249, 250)} + } + } + + # Font definitions + FONTS = { + 'primary': 'Segoe UI', + 'fallback': ['system-ui', '-apple-system', 'sans-serif'], + 'sizes': { + 'h1': 32, + 'h2': 24, + 'h3': 18, + 'body': 11, + 'caption': 9 + }, + 'weights': { + 'regular': 400, + 'semibold': 600, + 'bold': 700 + } + } + + # Company information + COMPANY = { + 'name': 'Acme Corporation', + 'tagline': 'Innovation Through Excellence', + 'copyright': '© 2025 Acme Corporation. All rights reserved.', + 'website': 'www.acmecorp.example', + 'logo_path': 'assets/acme_logo.png' + } + + def __init__(self): + """Initialize brand formatter with standard settings.""" + self.colors = self.COLORS + self.fonts = self.FONTS + self.company = self.COMPANY + + def format_excel(self, workbook_config: Dict[str, Any]) -> Dict[str, Any]: + """ + Apply brand formatting to Excel workbook configuration. + + Args: + workbook_config: Excel workbook configuration dictionary + + Returns: + Branded workbook configuration + """ + branded_config = workbook_config.copy() + + # Apply header formatting + branded_config['header_style'] = { + 'font': { + 'name': self.fonts['primary'], + 'size': self.fonts['sizes']['body'], + 'bold': True, + 'color': self.colors['primary']['white']['hex'] + }, + 'fill': { + 'type': 'solid', + 'color': self.colors['primary']['acme_blue']['hex'] + }, + 'alignment': { + 'horizontal': 'center', + 'vertical': 'center' + }, + 'border': { + 'style': 'thin', + 'color': self.colors['secondary']['neutral_gray']['hex'] + } + } + + # Apply data cell formatting + branded_config['cell_style'] = { + 'font': { + 'name': self.fonts['primary'], + 'size': self.fonts['sizes']['body'], + 'color': self.colors['primary']['acme_navy']['hex'] + }, + 'alignment': { + 'horizontal': 'left', + 'vertical': 'center' + } + } + + # Apply alternating row colors + branded_config['alternating_rows'] = { + 'enabled': True, + 'color': self.colors['secondary']['light_gray']['hex'] + } + + # Chart color scheme + branded_config['chart_colors'] = [ + self.colors['primary']['acme_blue']['hex'], + self.colors['secondary']['success_green']['hex'], + self.colors['secondary']['warning_amber']['hex'], + self.colors['secondary']['neutral_gray']['hex'] + ] + + return branded_config + + def format_powerpoint(self, presentation_config: Dict[str, Any]) -> Dict[str, Any]: + """ + Apply brand formatting to PowerPoint presentation configuration. + + Args: + presentation_config: PowerPoint configuration dictionary + + Returns: + Branded presentation configuration + """ + branded_config = presentation_config.copy() + + # Slide master settings + branded_config['master'] = { + 'background_color': self.colors['primary']['white']['hex'], + 'title_area': { + 'font': self.fonts['primary'], + 'size': self.fonts['sizes']['h1'], + 'color': self.colors['primary']['acme_blue']['hex'], + 'bold': True, + 'position': {'x': 0.5, 'y': 0.15, 'width': 9, 'height': 1} + }, + 'content_area': { + 'font': self.fonts['primary'], + 'size': self.fonts['sizes']['body'], + 'color': self.colors['primary']['acme_navy']['hex'], + 'position': {'x': 0.5, 'y': 2, 'width': 9, 'height': 5} + }, + 'footer': { + 'show_slide_number': True, + 'show_date': True, + 'company_name': self.company['name'] + } + } + + # Title slide template + branded_config['title_slide'] = { + 'background': self.colors['primary']['acme_blue']['hex'], + 'title_color': self.colors['primary']['white']['hex'], + 'subtitle_color': self.colors['primary']['white']['hex'], + 'include_logo': True, + 'logo_position': {'x': 0.5, 'y': 0.5, 'width': 2} + } + + # Content slide template + branded_config['content_slide'] = { + 'title_bar': { + 'background': self.colors['primary']['acme_blue']['hex'], + 'text_color': self.colors['primary']['white']['hex'], + 'height': 1 + }, + 'bullet_style': { + 'level1': '•', + 'level2': '○', + 'level3': '▪', + 'indent': 0.25 + } + } + + # Chart defaults + branded_config['charts'] = { + 'color_scheme': [ + self.colors['primary']['acme_blue']['hex'], + self.colors['secondary']['success_green']['hex'], + self.colors['secondary']['warning_amber']['hex'], + self.colors['secondary']['neutral_gray']['hex'] + ], + 'gridlines': { + 'color': self.colors['secondary']['neutral_gray']['hex'], + 'width': 0.5 + }, + 'font': { + 'name': self.fonts['primary'], + 'size': self.fonts['sizes']['caption'] + } + } + + return branded_config + + def format_pdf(self, document_config: Dict[str, Any]) -> Dict[str, Any]: + """ + Apply brand formatting to PDF document configuration. + + Args: + document_config: PDF document configuration dictionary + + Returns: + Branded document configuration + """ + branded_config = document_config.copy() + + # Page layout + branded_config['page'] = { + 'margins': {'top': 1, 'bottom': 1, 'left': 1, 'right': 1}, + 'size': 'letter', + 'orientation': 'portrait' + } + + # Header configuration + branded_config['header'] = { + 'height': 0.75, + 'content': { + 'left': { + 'type': 'logo', + 'width': 1.5 + }, + 'center': { + 'type': 'text', + 'content': document_config.get('title', 'Document'), + 'font': self.fonts['primary'], + 'size': self.fonts['sizes']['body'], + 'color': self.colors['primary']['acme_navy']['hex'] + }, + 'right': { + 'type': 'page_number', + 'format': 'Page {page} of {total}' + } + } + } + + # Footer configuration + branded_config['footer'] = { + 'height': 0.5, + 'content': { + 'left': { + 'type': 'text', + 'content': self.company['copyright'], + 'font': self.fonts['primary'], + 'size': self.fonts['sizes']['caption'], + 'color': self.colors['secondary']['neutral_gray']['hex'] + }, + 'center': { + 'type': 'date', + 'format': '%B %d, %Y' + }, + 'right': { + 'type': 'text', + 'content': 'Confidential' + } + } + } + + # Text styles + branded_config['styles'] = { + 'heading1': { + 'font': self.fonts['primary'], + 'size': self.fonts['sizes']['h1'], + 'color': self.colors['primary']['acme_blue']['hex'], + 'bold': True, + 'spacing_after': 12 + }, + 'heading2': { + 'font': self.fonts['primary'], + 'size': self.fonts['sizes']['h2'], + 'color': self.colors['primary']['acme_navy']['hex'], + 'bold': True, + 'spacing_after': 10 + }, + 'heading3': { + 'font': self.fonts['primary'], + 'size': self.fonts['sizes']['h3'], + 'color': self.colors['primary']['acme_navy']['hex'], + 'bold': False, + 'spacing_after': 8 + }, + 'body': { + 'font': self.fonts['primary'], + 'size': self.fonts['sizes']['body'], + 'color': self.colors['primary']['acme_navy']['hex'], + 'line_spacing': 1.15, + 'paragraph_spacing': 12 + }, + 'caption': { + 'font': self.fonts['primary'], + 'size': self.fonts['sizes']['caption'], + 'color': self.colors['secondary']['neutral_gray']['hex'], + 'italic': True + } + } + + # Table formatting + branded_config['table_style'] = { + 'header': { + 'background': self.colors['primary']['acme_blue']['hex'], + 'text_color': self.colors['primary']['white']['hex'], + 'bold': True + }, + 'rows': { + 'alternating_color': self.colors['secondary']['light_gray']['hex'], + 'border_color': self.colors['secondary']['neutral_gray']['hex'] + } + } + + return branded_config + + def validate_colors(self, colors_used: List[str]) -> Dict[str, Any]: + """ + Validate that colors match brand guidelines. + + Args: + colors_used: List of color codes used in document + + Returns: + Validation results with corrections if needed + """ + results = { + 'valid': True, + 'corrections': [], + 'warnings': [] + } + + approved_colors = [] + for category in self.colors.values(): + for color in category.values(): + approved_colors.append(color['hex'].upper()) + + for color in colors_used: + color_upper = color.upper() + if color_upper not in approved_colors: + results['valid'] = False + # Find closest brand color + closest = self._find_closest_brand_color(color) + results['corrections'].append({ + 'original': color, + 'suggested': closest, + 'message': f"Non-brand color {color} should be replaced with {closest}" + }) + + return results + + def _find_closest_brand_color(self, color: str) -> str: + """Find the closest brand color to a given color.""" + # Simplified - in reality would calculate color distance + return self.colors['primary']['acme_blue']['hex'] + + def apply_watermark(self, document_type: str) -> Dict[str, Any]: + """ + Generate watermark configuration for documents. + + Args: + document_type: Type of document (draft, confidential, etc.) + + Returns: + Watermark configuration + """ + watermarks = { + 'draft': { + 'text': 'DRAFT', + 'color': self.colors['secondary']['neutral_gray']['hex'], + 'opacity': 0.1, + 'angle': 45, + 'font_size': 72 + }, + 'confidential': { + 'text': 'CONFIDENTIAL', + 'color': self.colors['secondary']['error_red']['hex'], + 'opacity': 0.1, + 'angle': 45, + 'font_size': 60 + }, + 'sample': { + 'text': 'SAMPLE', + 'color': self.colors['secondary']['warning_amber']['hex'], + 'opacity': 0.15, + 'angle': 45, + 'font_size': 72 + } + } + + return watermarks.get(document_type, watermarks['draft']) + + def get_chart_palette(self, num_series: int = 4) -> List[str]: + """ + Get color palette for charts. + + Args: + num_series: Number of data series + + Returns: + List of hex color codes + """ + palette = [ + self.colors['primary']['acme_blue']['hex'], + self.colors['secondary']['success_green']['hex'], + self.colors['secondary']['warning_amber']['hex'], + self.colors['secondary']['neutral_gray']['hex'], + self.colors['primary']['acme_navy']['hex'], + self.colors['secondary']['error_red']['hex'] + ] + + return palette[:num_series] + + def format_number(self, value: float, format_type: str = 'general') -> str: + """ + Format numbers according to brand standards. + + Args: + value: Numeric value to format + format_type: Type of formatting (currency, percentage, general) + + Returns: + Formatted string + """ + if format_type == 'currency': + return f"${value:,.2f}" + elif format_type == 'percentage': + return f"{value:.1f}%" + elif format_type == 'large_number': + if value >= 1_000_000: + return f"{value/1_000_000:.1f}M" + elif value >= 1_000: + return f"{value/1_000:.1f}K" + else: + return f"{value:.0f}" + else: + return f"{value:,.0f}" if value >= 1000 else f"{value:.2f}" + + +def apply_brand_to_document(document_type: str, config: Dict[str, Any]) -> Dict[str, Any]: + """ + Main function to apply branding to any document type. + + Args: + document_type: Type of document ('excel', 'powerpoint', 'pdf') + config: Document configuration + + Returns: + Branded configuration + """ + formatter = BrandFormatter() + + if document_type.lower() == 'excel': + return formatter.format_excel(config) + elif document_type.lower() in ['powerpoint', 'pptx']: + return formatter.format_powerpoint(config) + elif document_type.lower() == 'pdf': + return formatter.format_pdf(config) + else: + raise ValueError(f"Unsupported document type: {document_type}") + + +# Example usage +if __name__ == "__main__": + # Example Excel configuration + excel_config = { + 'title': 'Quarterly Report', + 'sheets': ['Summary', 'Details'] + } + + branded_excel = apply_brand_to_document('excel', excel_config) + print("Branded Excel Configuration:") + print(branded_excel) + + # Example PowerPoint configuration + ppt_config = { + 'title': 'Business Review', + 'num_slides': 10 + } + + branded_ppt = apply_brand_to_document('powerpoint', ppt_config) + print("\nBranded PowerPoint Configuration:") + print(branded_ppt) \ No newline at end of file diff --git a/skills/custom_skills/creating-financial-models/SKILL.md b/skills/custom_skills/creating-financial-models/SKILL.md new file mode 100644 index 00000000..d9c18526 --- /dev/null +++ b/skills/custom_skills/creating-financial-models/SKILL.md @@ -0,0 +1,173 @@ +--- +name: Creating Financial Models +description: This skill provides an advanced financial modeling suite with DCF analysis, sensitivity testing, Monte Carlo simulations, and scenario planning for investment decisions +--- + +# Financial Modeling Suite + +A comprehensive financial modeling toolkit for investment analysis, valuation, and risk assessment using industry-standard methodologies. + +## Core Capabilities + +### 1. Discounted Cash Flow (DCF) Analysis +- Build complete DCF models with multiple growth scenarios +- Calculate terminal values using perpetuity growth and exit multiple methods +- Determine weighted average cost of capital (WACC) +- Generate enterprise and equity valuations + +### 2. Sensitivity Analysis +- Test key assumptions impact on valuation +- Create data tables for multiple variables +- Generate tornado charts for sensitivity ranking +- Identify critical value drivers + +### 3. Monte Carlo Simulation +- Run thousands of scenarios with probability distributions +- Model uncertainty in key inputs +- Generate confidence intervals for valuations +- Calculate probability of achieving targets + +### 4. Scenario Planning +- Build best/base/worst case scenarios +- Model different economic environments +- Test strategic alternatives +- Compare outcome probabilities + +## Input Requirements + +### For DCF Analysis +- Historical financial statements (3-5 years) +- Revenue growth assumptions +- Operating margin projections +- Capital expenditure forecasts +- Working capital requirements +- Terminal growth rate or exit multiple +- Discount rate components (risk-free rate, beta, market premium) + +### For Sensitivity Analysis +- Base case model +- Variable ranges to test +- Key metrics to track + +### For Monte Carlo Simulation +- Probability distributions for uncertain variables +- Correlation assumptions between variables +- Number of iterations (typically 1,000-10,000) + +### For Scenario Planning +- Scenario definitions and assumptions +- Probability weights for scenarios +- Key performance indicators to track + +## Output Formats + +### DCF Model Output +- Complete financial projections +- Free cash flow calculations +- Terminal value computation +- Enterprise and equity value summary +- Valuation multiples implied +- Excel workbook with full model + +### Sensitivity Analysis Output +- Sensitivity tables showing value ranges +- Tornado chart of key drivers +- Break-even analysis +- Charts showing relationships + +### Monte Carlo Output +- Probability distribution of valuations +- Confidence intervals (e.g., 90%, 95%) +- Statistical summary (mean, median, std dev) +- Risk metrics (VaR, probability of loss) + +### Scenario Planning Output +- Scenario comparison table +- Probability-weighted expected values +- Decision tree visualization +- Risk-return profiles + +## Model Types Supported + +1. **Corporate Valuation** + - Mature companies with stable cash flows + - Growth companies with J-curve projections + - Turnaround situations + +2. **Project Finance** + - Infrastructure projects + - Real estate developments + - Energy projects + +3. **M&A Analysis** + - Acquisition valuations + - Synergy modeling + - Accretion/dilution analysis + +4. **LBO Models** + - Leveraged buyout analysis + - Returns analysis (IRR, MOIC) + - Debt capacity assessment + +## Best Practices Applied + +### Modeling Standards +- Consistent formatting and structure +- Clear assumption documentation +- Separation of inputs, calculations, outputs +- Error checking and validation +- Version control and change tracking + +### Valuation Principles +- Use multiple valuation methods for triangulation +- Apply appropriate risk adjustments +- Consider market comparables +- Validate against trading multiples +- Document key assumptions clearly + +### Risk Management +- Identify and quantify key risks +- Use probability-weighted scenarios +- Stress test extreme cases +- Consider correlation effects +- Provide confidence intervals + +## Example Usage + +"Build a DCF model for this technology company using the attached financials" + +"Run a Monte Carlo simulation on this acquisition model with 5,000 iterations" + +"Create sensitivity analysis showing impact of growth rate and WACC on valuation" + +"Develop three scenarios for this expansion project with probability weights" + +## Scripts Included + +- `dcf_model.py`: Complete DCF valuation engine +- `sensitivity_analysis.py`: Sensitivity testing framework + +## Limitations and Disclaimers + +- Models are only as good as their assumptions +- Past performance doesn't guarantee future results +- Market conditions can change rapidly +- Regulatory and tax changes may impact results +- Professional judgment required for interpretation +- Not a substitute for professional financial advice + +## Quality Checks + +The model automatically performs: +1. Balance sheet balancing checks +2. Cash flow reconciliation +3. Circular reference resolution +4. Sensitivity bound checking +5. Statistical validation of Monte Carlo results + +## Updates and Maintenance + +- Models use latest financial theory and practices +- Regular updates for market parameter defaults +- Incorporation of regulatory changes +- Continuous improvement based on usage patterns \ No newline at end of file diff --git a/skills/custom_skills/creating-financial-models/dcf_model.py b/skills/custom_skills/creating-financial-models/dcf_model.py new file mode 100644 index 00000000..7f48527f --- /dev/null +++ b/skills/custom_skills/creating-financial-models/dcf_model.py @@ -0,0 +1,545 @@ +""" +Discounted Cash Flow (DCF) valuation model. +Implements enterprise valuation using free cash flow projections. +""" + +import numpy as np +from typing import Dict, List, Any, Optional, Tuple + + +class DCFModel: + """Build and calculate DCF valuation models.""" + + def __init__(self, company_name: str = "Company"): + """ + Initialize DCF model. + + Args: + company_name: Name of the company being valued + """ + self.company_name = company_name + self.historical_financials = {} + self.projections = {} + self.assumptions = {} + self.wacc_components = {} + self.valuation_results = {} + + def set_historical_financials( + self, + revenue: List[float], + ebitda: List[float], + capex: List[float], + nwc: List[float], + years: List[int] + ): + """ + Set historical financial data. + + Args: + revenue: Historical revenue + ebitda: Historical EBITDA + capex: Historical capital expenditure + nwc: Historical net working capital + years: Historical years + """ + self.historical_financials = { + 'years': years, + 'revenue': revenue, + 'ebitda': ebitda, + 'capex': capex, + 'nwc': nwc, + 'ebitda_margin': [ebitda[i]/revenue[i] for i in range(len(revenue))], + 'capex_percent': [capex[i]/revenue[i] for i in range(len(revenue))] + } + + def set_assumptions( + self, + projection_years: int = 5, + revenue_growth: List[float] = None, + ebitda_margin: List[float] = None, + tax_rate: float = 0.25, + capex_percent: List[float] = None, + nwc_percent: List[float] = None, + terminal_growth: float = 0.03 + ): + """ + Set projection assumptions. + + Args: + projection_years: Number of years to project + revenue_growth: Annual revenue growth rates + ebitda_margin: EBITDA margins by year + tax_rate: Corporate tax rate + capex_percent: Capex as % of revenue + nwc_percent: NWC as % of revenue + terminal_growth: Terminal growth rate + """ + if revenue_growth is None: + revenue_growth = [0.10] * projection_years # Default 10% growth + + if ebitda_margin is None: + # Use historical average if available + if self.historical_financials: + avg_margin = np.mean(self.historical_financials['ebitda_margin']) + ebitda_margin = [avg_margin] * projection_years + else: + ebitda_margin = [0.20] * projection_years # Default 20% margin + + if capex_percent is None: + capex_percent = [0.05] * projection_years # Default 5% of revenue + + if nwc_percent is None: + nwc_percent = [0.10] * projection_years # Default 10% of revenue + + self.assumptions = { + 'projection_years': projection_years, + 'revenue_growth': revenue_growth, + 'ebitda_margin': ebitda_margin, + 'tax_rate': tax_rate, + 'capex_percent': capex_percent, + 'nwc_percent': nwc_percent, + 'terminal_growth': terminal_growth + } + + def calculate_wacc( + self, + risk_free_rate: float, + beta: float, + market_premium: float, + cost_of_debt: float, + debt_to_equity: float, + tax_rate: Optional[float] = None + ) -> float: + """ + Calculate Weighted Average Cost of Capital (WACC). + + Args: + risk_free_rate: Risk-free rate (e.g., 10-year treasury) + beta: Equity beta + market_premium: Equity market risk premium + cost_of_debt: Pre-tax cost of debt + debt_to_equity: Debt-to-equity ratio + tax_rate: Tax rate (uses assumption if not provided) + + Returns: + WACC as decimal + """ + if tax_rate is None: + tax_rate = self.assumptions.get('tax_rate', 0.25) + + # Calculate cost of equity using CAPM + cost_of_equity = risk_free_rate + beta * market_premium + + # Calculate weights + equity_weight = 1 / (1 + debt_to_equity) + debt_weight = debt_to_equity / (1 + debt_to_equity) + + # Calculate WACC + wacc = (equity_weight * cost_of_equity + + debt_weight * cost_of_debt * (1 - tax_rate)) + + self.wacc_components = { + 'risk_free_rate': risk_free_rate, + 'beta': beta, + 'market_premium': market_premium, + 'cost_of_equity': cost_of_equity, + 'cost_of_debt': cost_of_debt, + 'debt_to_equity': debt_to_equity, + 'equity_weight': equity_weight, + 'debt_weight': debt_weight, + 'tax_rate': tax_rate, + 'wacc': wacc + } + + return wacc + + def project_cash_flows(self) -> Dict[str, List[float]]: + """ + Project future cash flows based on assumptions. + + Returns: + Dictionary with projected financials + """ + years = self.assumptions['projection_years'] + + # Start with last historical revenue if available + if self.historical_financials and 'revenue' in self.historical_financials: + base_revenue = self.historical_financials['revenue'][-1] + else: + base_revenue = 1000 # Default base + + projections = { + 'year': list(range(1, years + 1)), + 'revenue': [], + 'ebitda': [], + 'ebit': [], + 'tax': [], + 'nopat': [], + 'capex': [], + 'nwc_change': [], + 'fcf': [] + } + + prev_revenue = base_revenue + prev_nwc = base_revenue * 0.10 # Initial NWC assumption + + for i in range(years): + # Revenue + revenue = prev_revenue * (1 + self.assumptions['revenue_growth'][i]) + projections['revenue'].append(revenue) + + # EBITDA + ebitda = revenue * self.assumptions['ebitda_margin'][i] + projections['ebitda'].append(ebitda) + + # EBIT (assuming depreciation = capex for simplicity) + depreciation = revenue * self.assumptions['capex_percent'][i] + ebit = ebitda - depreciation + projections['ebit'].append(ebit) + + # Tax + tax = ebit * self.assumptions['tax_rate'] + projections['tax'].append(tax) + + # NOPAT + nopat = ebit - tax + projections['nopat'].append(nopat) + + # Capex + capex = revenue * self.assumptions['capex_percent'][i] + projections['capex'].append(capex) + + # NWC change + nwc = revenue * self.assumptions['nwc_percent'][i] + nwc_change = nwc - prev_nwc + projections['nwc_change'].append(nwc_change) + + # Free Cash Flow + fcf = nopat + depreciation - capex - nwc_change + projections['fcf'].append(fcf) + + prev_revenue = revenue + prev_nwc = nwc + + self.projections = projections + return projections + + def calculate_terminal_value( + self, + method: str = 'growth', + exit_multiple: Optional[float] = None + ) -> float: + """ + Calculate terminal value using perpetuity growth or exit multiple. + + Args: + method: 'growth' for perpetuity growth, 'multiple' for exit multiple + exit_multiple: EV/EBITDA exit multiple (if using multiple method) + + Returns: + Terminal value + """ + if not self.projections: + raise ValueError("Must project cash flows first") + + if method == 'growth': + # Gordon growth model + final_fcf = self.projections['fcf'][-1] + terminal_growth = self.assumptions['terminal_growth'] + wacc = self.wacc_components['wacc'] + + # FCF in terminal year + terminal_fcf = final_fcf * (1 + terminal_growth) + + # Terminal value + terminal_value = terminal_fcf / (wacc - terminal_growth) + + elif method == 'multiple': + if exit_multiple is None: + exit_multiple = 10 # Default EV/EBITDA multiple + + final_ebitda = self.projections['ebitda'][-1] + terminal_value = final_ebitda * exit_multiple + + else: + raise ValueError("Method must be 'growth' or 'multiple'") + + return terminal_value + + def calculate_enterprise_value( + self, + terminal_method: str = 'growth', + exit_multiple: Optional[float] = None + ) -> Dict[str, Any]: + """ + Calculate enterprise value by discounting cash flows. + + Args: + terminal_method: Method for terminal value calculation + exit_multiple: Exit multiple if using multiple method + + Returns: + Valuation results dictionary + """ + if not self.projections: + self.project_cash_flows() + + if 'wacc' not in self.wacc_components: + raise ValueError("Must calculate WACC first") + + wacc = self.wacc_components['wacc'] + years = self.assumptions['projection_years'] + + # Calculate PV of projected cash flows + pv_fcf = [] + for i, fcf in enumerate(self.projections['fcf']): + discount_factor = (1 + wacc) ** (i + 1) + pv = fcf / discount_factor + pv_fcf.append(pv) + + total_pv_fcf = sum(pv_fcf) + + # Calculate terminal value + terminal_value = self.calculate_terminal_value(terminal_method, exit_multiple) + + # Discount terminal value + terminal_discount = (1 + wacc) ** years + pv_terminal = terminal_value / terminal_discount + + # Enterprise value + enterprise_value = total_pv_fcf + pv_terminal + + self.valuation_results = { + 'enterprise_value': enterprise_value, + 'pv_fcf': total_pv_fcf, + 'pv_terminal': pv_terminal, + 'terminal_value': terminal_value, + 'terminal_method': terminal_method, + 'pv_fcf_detail': pv_fcf, + 'terminal_percent': pv_terminal / enterprise_value * 100 + } + + return self.valuation_results + + def calculate_equity_value( + self, + net_debt: float, + cash: float = 0, + shares_outstanding: float = 100 + ) -> Dict[str, Any]: + """ + Calculate equity value from enterprise value. + + Args: + net_debt: Total debt minus cash + cash: Cash and equivalents (if not netted) + shares_outstanding: Number of shares (millions) + + Returns: + Equity valuation metrics + """ + if 'enterprise_value' not in self.valuation_results: + raise ValueError("Must calculate enterprise value first") + + ev = self.valuation_results['enterprise_value'] + + # Equity value = EV - Net Debt + equity_value = ev - net_debt + cash + + # Per share value + value_per_share = equity_value / shares_outstanding if shares_outstanding > 0 else 0 + + equity_results = { + 'equity_value': equity_value, + 'shares_outstanding': shares_outstanding, + 'value_per_share': value_per_share, + 'net_debt': net_debt, + 'cash': cash + } + + self.valuation_results.update(equity_results) + return equity_results + + def sensitivity_analysis( + self, + variable1: str, + range1: List[float], + variable2: str, + range2: List[float] + ) -> np.ndarray: + """ + Perform two-way sensitivity analysis on valuation. + + Args: + variable1: First variable to test ('wacc', 'growth', 'margin') + range1: Range of values for variable1 + variable2: Second variable to test + range2: Range of values for variable2 + + Returns: + 2D array of valuations + """ + results = np.zeros((len(range1), len(range2))) + + # Store original values + orig_wacc = self.wacc_components.get('wacc', 0.10) + orig_growth = self.assumptions.get('terminal_growth', 0.03) + orig_margin = self.assumptions.get('ebitda_margin', [0.20] * 5) + + for i, val1 in enumerate(range1): + for j, val2 in enumerate(range2): + # Update first variable + if variable1 == 'wacc': + self.wacc_components['wacc'] = val1 + elif variable1 == 'growth': + self.assumptions['terminal_growth'] = val1 + elif variable1 == 'margin': + self.assumptions['ebitda_margin'] = [val1] * len(orig_margin) + + # Update second variable + if variable2 == 'wacc': + self.wacc_components['wacc'] = val2 + elif variable2 == 'growth': + self.assumptions['terminal_growth'] = val2 + elif variable2 == 'margin': + self.assumptions['ebitda_margin'] = [val2] * len(orig_margin) + + # Recalculate + self.project_cash_flows() + valuation = self.calculate_enterprise_value() + results[i, j] = valuation['enterprise_value'] + + # Restore original values + self.wacc_components['wacc'] = orig_wacc + self.assumptions['terminal_growth'] = orig_growth + self.assumptions['ebitda_margin'] = orig_margin + + return results + + def generate_summary(self) -> str: + """ + Generate text summary of valuation results. + + Returns: + Formatted summary string + """ + if not self.valuation_results: + return "No valuation results available. Run valuation first." + + summary = [ + f"DCF Valuation Summary - {self.company_name}", + "=" * 50, + "", + "Key Assumptions:", + f" Projection Period: {self.assumptions['projection_years']} years", + f" Revenue Growth: {np.mean(self.assumptions['revenue_growth'])*100:.1f}% avg", + f" EBITDA Margin: {np.mean(self.assumptions['ebitda_margin'])*100:.1f}% avg", + f" Terminal Growth: {self.assumptions['terminal_growth']*100:.1f}%", + f" WACC: {self.wacc_components['wacc']*100:.1f}%", + "", + "Valuation Results:", + f" Enterprise Value: ${self.valuation_results['enterprise_value']:,.0f}M", + f" PV of FCF: ${self.valuation_results['pv_fcf']:,.0f}M", + f" PV of Terminal: ${self.valuation_results['pv_terminal']:,.0f}M", + f" Terminal % of Value: {self.valuation_results['terminal_percent']:.1f}%", + "" + ] + + if 'equity_value' in self.valuation_results: + summary.extend([ + "Equity Valuation:", + f" Equity Value: ${self.valuation_results['equity_value']:,.0f}M", + f" Shares Outstanding: {self.valuation_results['shares_outstanding']:.0f}M", + f" Value per Share: ${self.valuation_results['value_per_share']:.2f}", + "" + ]) + + return "\n".join(summary) + + +# Helper functions for common calculations + +def calculate_beta( + stock_returns: List[float], + market_returns: List[float] +) -> float: + """ + Calculate beta from return series. + + Args: + stock_returns: Historical stock returns + market_returns: Historical market returns + + Returns: + Beta coefficient + """ + covariance = np.cov(stock_returns, market_returns)[0, 1] + market_variance = np.var(market_returns) + beta = covariance / market_variance if market_variance != 0 else 1.0 + return beta + + +def calculate_fcf_cagr(fcf_series: List[float]) -> float: + """ + Calculate compound annual growth rate of FCF. + + Args: + fcf_series: Free cash flow time series + + Returns: + CAGR as decimal + """ + if len(fcf_series) < 2: + return 0 + + years = len(fcf_series) - 1 + if fcf_series[0] <= 0 or fcf_series[-1] <= 0: + return 0 + + cagr = (fcf_series[-1] / fcf_series[0]) ** (1 / years) - 1 + return cagr + + +# Example usage +if __name__ == "__main__": + # Create model + model = DCFModel("TechCorp") + + # Set historical data + model.set_historical_financials( + revenue=[800, 900, 1000], + ebitda=[160, 189, 220], + capex=[40, 45, 50], + nwc=[80, 90, 100], + years=[2022, 2023, 2024] + ) + + # Set assumptions + model.set_assumptions( + projection_years=5, + revenue_growth=[0.15, 0.12, 0.10, 0.08, 0.06], + ebitda_margin=[0.23, 0.24, 0.25, 0.25, 0.25], + tax_rate=0.25, + terminal_growth=0.03 + ) + + # Calculate WACC + model.calculate_wacc( + risk_free_rate=0.04, + beta=1.2, + market_premium=0.07, + cost_of_debt=0.05, + debt_to_equity=0.5 + ) + + # Project cash flows + model.project_cash_flows() + + # Calculate valuation + model.calculate_enterprise_value() + + # Calculate equity value + model.calculate_equity_value(net_debt=200, shares_outstanding=50) + + # Print summary + print(model.generate_summary()) \ No newline at end of file diff --git a/skills/custom_skills/creating-financial-models/sensitivity_analysis.py b/skills/custom_skills/creating-financial-models/sensitivity_analysis.py new file mode 100644 index 00000000..a5a38966 --- /dev/null +++ b/skills/custom_skills/creating-financial-models/sensitivity_analysis.py @@ -0,0 +1,376 @@ +""" +Sensitivity analysis module for financial models. +Tests impact of variable changes on key outputs. +""" + +import numpy as np +import pandas as pd +from typing import Dict, List, Any, Tuple, Callable + + +class SensitivityAnalyzer: + """Perform sensitivity analysis on financial models.""" + + def __init__(self, base_model: Any): + """ + Initialize sensitivity analyzer. + + Args: + base_model: Base financial model to analyze + """ + self.base_model = base_model + self.base_output = None + self.sensitivity_results = {} + + def one_way_sensitivity( + self, + variable_name: str, + base_value: float, + range_pct: float, + steps: int, + output_func: Callable, + model_update_func: Callable + ) -> pd.DataFrame: + """ + Perform one-way sensitivity analysis. + + Args: + variable_name: Name of variable to test + base_value: Base case value + range_pct: +/- percentage range to test + steps: Number of steps in range + output_func: Function to calculate output metric + model_update_func: Function to update model with new value + + Returns: + DataFrame with sensitivity results + """ + # Calculate range + min_val = base_value * (1 - range_pct) + max_val = base_value * (1 + range_pct) + test_values = np.linspace(min_val, max_val, steps) + + results = [] + for value in test_values: + # Update model + model_update_func(value) + + # Calculate output + output = output_func() + + results.append({ + 'variable': variable_name, + 'value': value, + 'pct_change': (value - base_value) / base_value * 100, + 'output': output, + 'output_change': output - self.base_output if self.base_output else 0 + }) + + # Reset to base + model_update_func(base_value) + + return pd.DataFrame(results) + + def two_way_sensitivity( + self, + var1_name: str, + var1_base: float, + var1_range: List[float], + var2_name: str, + var2_base: float, + var2_range: List[float], + output_func: Callable, + model_update_func: Callable + ) -> pd.DataFrame: + """ + Perform two-way sensitivity analysis. + + Args: + var1_name: First variable name + var1_base: First variable base value + var1_range: Range of values for first variable + var2_name: Second variable name + var2_base: Second variable base value + var2_range: Range of values for second variable + output_func: Function to calculate output + model_update_func: Function to update model (takes var1, var2) + + Returns: + DataFrame with two-way sensitivity table + """ + results = np.zeros((len(var1_range), len(var2_range))) + + for i, val1 in enumerate(var1_range): + for j, val2 in enumerate(var2_range): + # Update both variables + model_update_func(val1, val2) + + # Calculate output + results[i, j] = output_func() + + # Reset to base + model_update_func(var1_base, var2_base) + + # Create DataFrame + df = pd.DataFrame( + results, + index=[f"{var1_name}={v:.2%}" if v < 1 else f"{var1_name}={v:.1f}" + for v in var1_range], + columns=[f"{var2_name}={v:.2%}" if v < 1 else f"{var2_name}={v:.1f}" + for v in var2_range] + ) + + return df + + def tornado_analysis( + self, + variables: Dict[str, Dict[str, Any]], + output_func: Callable + ) -> pd.DataFrame: + """ + Create tornado diagram data showing relative impact of variables. + + Args: + variables: Dictionary of variables with base, low, high values + output_func: Function to calculate output + + Returns: + DataFrame sorted by impact magnitude + """ + # Store base output + self.base_output = output_func() + + tornado_data = [] + + for var_name, var_info in variables.items(): + # Test low value + var_info['update_func'](var_info['low']) + low_output = output_func() + + # Test high value + var_info['update_func'](var_info['high']) + high_output = output_func() + + # Reset to base + var_info['update_func'](var_info['base']) + + # Calculate impact + impact = high_output - low_output + low_delta = low_output - self.base_output + high_delta = high_output - self.base_output + + tornado_data.append({ + 'variable': var_name, + 'base_value': var_info['base'], + 'low_value': var_info['low'], + 'high_value': var_info['high'], + 'low_output': low_output, + 'high_output': high_output, + 'low_delta': low_delta, + 'high_delta': high_delta, + 'impact': abs(impact), + 'impact_pct': abs(impact) / self.base_output * 100 + }) + + # Sort by impact + df = pd.DataFrame(tornado_data) + df = df.sort_values('impact', ascending=False) + + return df + + def scenario_analysis( + self, + scenarios: Dict[str, Dict[str, float]], + variable_updates: Dict[str, Callable], + output_func: Callable, + probability_weights: Optional[Dict[str, float]] = None + ) -> pd.DataFrame: + """ + Analyze multiple scenarios with different variable combinations. + + Args: + scenarios: Dictionary of scenarios with variable values + variable_updates: Functions to update each variable + output_func: Function to calculate output + probability_weights: Optional probability for each scenario + + Returns: + DataFrame with scenario results + """ + results = [] + + for scenario_name, variables in scenarios.items(): + # Update all variables for this scenario + for var_name, value in variables.items(): + if var_name in variable_updates: + variable_updates[var_name](value) + + # Calculate output + output = output_func() + + # Get probability if provided + prob = probability_weights.get(scenario_name, 1/len(scenarios)) \ + if probability_weights else 1/len(scenarios) + + results.append({ + 'scenario': scenario_name, + 'probability': prob, + 'output': output, + **variables # Include all variable values + }) + + # Reset model (simplified - should restore all base values) + + df = pd.DataFrame(results) + + # Calculate expected value + df['weighted_output'] = df['output'] * df['probability'] + expected_value = df['weighted_output'].sum() + + # Add summary row + summary = pd.DataFrame([{ + 'scenario': 'Expected Value', + 'probability': 1.0, + 'output': expected_value, + 'weighted_output': expected_value + }]) + + df = pd.concat([df, summary], ignore_index=True) + + return df + + def breakeven_analysis( + self, + variable_name: str, + variable_update: Callable, + output_func: Callable, + target_value: float, + min_search: float, + max_search: float, + tolerance: float = 0.01 + ) -> float: + """ + Find breakeven point where output equals target. + + Args: + variable_name: Variable to adjust + variable_update: Function to update variable + output_func: Function to calculate output + target_value: Target output value + min_search: Minimum search range + max_search: Maximum search range + tolerance: Convergence tolerance + + Returns: + Breakeven value of variable + """ + # Binary search for breakeven + low = min_search + high = max_search + + while (high - low) > tolerance: + mid = (low + high) / 2 + variable_update(mid) + output = output_func() + + if abs(output - target_value) < tolerance: + return mid + elif output < target_value: + low = mid + else: + high = mid + + return (low + high) / 2 + + +def create_data_table( + row_variable: Tuple[str, List[float], Callable], + col_variable: Tuple[str, List[float], Callable], + output_func: Callable +) -> pd.DataFrame: + """ + Create Excel-style data table for two variables. + + Args: + row_variable: (name, values, update_function) + col_variable: (name, values, update_function) + output_func: Function to calculate output + + Returns: + DataFrame formatted as data table + """ + row_name, row_values, row_update = row_variable + col_name, col_values, col_update = col_variable + + results = np.zeros((len(row_values), len(col_values))) + + for i, row_val in enumerate(row_values): + for j, col_val in enumerate(col_values): + row_update(row_val) + col_update(col_val) + results[i, j] = output_func() + + df = pd.DataFrame( + results, + index=pd.Index(row_values, name=row_name), + columns=pd.Index(col_values, name=col_name) + ) + + return df + + +# Example usage +if __name__ == "__main__": + # Mock model for demonstration + class SimpleModel: + def __init__(self): + self.revenue = 1000 + self.margin = 0.20 + self.multiple = 10 + + def calculate_value(self): + ebitda = self.revenue * self.margin + return ebitda * self.multiple + + # Create model and analyzer + model = SimpleModel() + analyzer = SensitivityAnalyzer(model) + + # One-way sensitivity + results = analyzer.one_way_sensitivity( + variable_name="Revenue", + base_value=model.revenue, + range_pct=0.20, + steps=5, + output_func=model.calculate_value, + model_update_func=lambda x: setattr(model, 'revenue', x) + ) + + print("One-Way Sensitivity Analysis:") + print(results) + + # Tornado analysis + variables = { + 'Revenue': { + 'base': 1000, + 'low': 800, + 'high': 1200, + 'update_func': lambda x: setattr(model, 'revenue', x) + }, + 'Margin': { + 'base': 0.20, + 'low': 0.15, + 'high': 0.25, + 'update_func': lambda x: setattr(model, 'margin', x) + }, + 'Multiple': { + 'base': 10, + 'low': 8, + 'high': 12, + 'update_func': lambda x: setattr(model, 'multiple', x) + } + } + + tornado = analyzer.tornado_analysis(variables, model.calculate_value) + print("\nTornado Analysis:") + print(tornado[['variable', 'impact', 'impact_pct']]) \ No newline at end of file diff --git a/skills/file_utils.py b/skills/file_utils.py new file mode 100644 index 00000000..9446770a --- /dev/null +++ b/skills/file_utils.py @@ -0,0 +1,289 @@ +""" +Utility functions for working with Claude Skills and Files API. + +This module provides helper functions for: +- Extracting file IDs from Claude API responses +- Downloading files via the Files API +- Saving files to disk +""" + +import json +import os +from pathlib import Path +from typing import Optional, List, Dict, Any +from anthropic import Anthropic + + +def extract_file_ids(response) -> List[str]: + """ + Extract all file IDs from a Claude API response. + + Skills create files during code execution and return file_id attributes + in the tool results. This function parses the response to find all file IDs. + + Args: + response: The response object from client.beta.messages.create() + + Returns: + List of file IDs found in the response + + Example: + >>> response = client.beta.messages.create(...) + >>> file_ids = extract_file_ids(response) + >>> print(f"Found {len(file_ids)} files") + """ + file_ids = [] + + for block in response.content: + # Check for bash_code_execution_tool_result (beta API format) + if block.type == "bash_code_execution_tool_result": + try: + if hasattr(block, 'content') and hasattr(block.content, 'content'): + # Iterate through content array + for item in block.content.content: + if hasattr(item, 'file_id'): + file_ids.append(item.file_id) + except Exception as e: + print(f"Warning: Error parsing bash_code_execution_tool_result: {e}") + continue + + # Check for legacy tool_result blocks (for backward compatibility) + elif block.type == "tool_result": + try: + if hasattr(block, 'output'): + output_str = str(block.output) + + # Look for file_id patterns in the output + if 'file_id' in output_str.lower(): + # Try to parse as JSON first + try: + output_json = json.loads(output_str) + if isinstance(output_json, dict) and 'file_id' in output_json: + file_ids.append(output_json['file_id']) + elif isinstance(output_json, list): + for item in output_json: + if isinstance(item, dict) and 'file_id' in item: + file_ids.append(item['file_id']) + except json.JSONDecodeError: + # If not JSON, use regex to find file_id patterns + import re + pattern = r"file_id['\"]?\s*[:=]\s*['\"]?([a-zA-Z0-9_-]+)" + matches = re.findall(pattern, output_str) + file_ids.extend(matches) + except Exception as e: + print(f"Warning: Error parsing tool_result block: {e}") + continue + + # Remove duplicates while preserving order + seen = set() + unique_file_ids = [] + for fid in file_ids: + if fid not in seen: + seen.add(fid) + unique_file_ids.append(fid) + + return unique_file_ids + + +def download_file( + client: Anthropic, + file_id: str, + output_path: str, + overwrite: bool = True +) -> Dict[str, Any]: + """ + Download a file from Claude's Files API and save it locally. + + Args: + client: Anthropic client instance + file_id: The file ID returned by Skills + output_path: Local path where the file should be saved + overwrite: Whether to overwrite existing files (default: True) + + Returns: + Dictionary with download metadata: + { + 'file_id': str, + 'output_path': str, + 'size': int, + 'success': bool, + 'error': Optional[str] + } + + Example: + >>> client = Anthropic(api_key="...") + >>> result = download_file(client, "file_abc123", "outputs/report.xlsx") + >>> if result['success']: + ... if result.get('overwritten'): + ... print(f"Overwrote existing file: {result['output_path']}") + ... print(f"Downloaded {result['size']} bytes to {result['output_path']}") + """ + result = { + 'file_id': file_id, + 'output_path': output_path, + 'size': 0, + 'success': False, + 'error': None + } + + try: + # Check if file exists + file_exists = os.path.exists(output_path) + if file_exists and not overwrite: + result['error'] = f"File already exists: {output_path} (set overwrite=True to replace)" + return result + + # Create output directory if it doesn't exist + output_dir = os.path.dirname(output_path) + if output_dir: + Path(output_dir).mkdir(parents=True, exist_ok=True) + + # Download file content from Files API (beta namespace) + file_content = client.beta.files.download(file_id=file_id) + + # Save to disk + with open(output_path, 'wb') as f: + f.write(file_content.read()) + + # Get file size + result['size'] = os.path.getsize(output_path) + result['success'] = True + result['overwritten'] = file_exists # Track if we overwrote an existing file + + except Exception as e: + result['error'] = str(e) + + return result + + +def download_all_files( + client: Anthropic, + response, + output_dir: str = "outputs", + prefix: str = "", + overwrite: bool = True +) -> List[Dict[str, Any]]: + """ + Extract and download all files from a Claude API response. + + This is a convenience function that combines extract_file_ids() + and download_file() to download all files in a single call. + + Args: + client: Anthropic client instance + response: The response object from client.messages.create() + output_dir: Directory where files should be saved + prefix: Optional prefix for filenames (e.g., "financial_report_") + overwrite: Whether to overwrite existing files (default: True) + + Returns: + List of download results (one per file) + + Example: + >>> response = client.messages.create(...) + >>> results = download_all_files(client, response, output_dir="outputs") + >>> for result in results: + ... if result['success']: + ... print(f"✓ Downloaded: {result['output_path']}") + ... else: + ... print(f"✗ Failed: {result['error']}") + """ + file_ids = extract_file_ids(response) + results = [] + + for i, file_id in enumerate(file_ids, 1): + # Try to get file metadata for proper filename + try: + file_info = client.beta.files.retrieve_metadata(file_id=file_id) + filename = file_info.filename + except Exception: + # If we can't get metadata, use a generic filename + filename = f"file_{i}.bin" + + # Add prefix if provided + if prefix: + filename = f"{prefix}{filename}" + + # Construct full output path + output_path = os.path.join(output_dir, filename) + + # Download the file + result = download_file(client, file_id, output_path, overwrite=overwrite) + results.append(result) + + return results + + +def get_file_info(client: Anthropic, file_id: str) -> Optional[Dict[str, Any]]: + """ + Retrieve metadata about a file from the Files API. + + Args: + client: Anthropic client instance + file_id: The file ID to query + + Returns: + Dictionary with file metadata, or None if not found + + Example: + >>> info = get_file_info(client, "file_abc123") + >>> if info: + ... print(f"Filename: {info['filename']}") + ... print(f"Size: {info['size']} bytes") + ... print(f"Created: {info['created_at']}") + """ + try: + file_info = client.beta.files.retrieve_metadata(file_id=file_id) + return { + 'file_id': file_info.id, + 'filename': file_info.filename, + 'size': file_info.size_bytes, + 'mime_type': file_info.mime_type, + 'created_at': file_info.created_at, + 'type': file_info.type, + 'downloadable': file_info.downloadable + } + except Exception as e: + print(f"Error retrieving file info: {e}") + return None + + +def print_download_summary(results: List[Dict[str, Any]]) -> None: + """ + Print a formatted summary of file download results. + + Args: + results: List of download results from download_all_files() + + Example: + >>> results = download_all_files(client, response) + >>> print_download_summary(results) + + File Download Summary + ===================== + ✓ outputs/budget.xlsx (45.2 KB) + ✓ outputs/presentation.pptx (127.8 KB) + ✗ outputs/report.pdf - Error: File not found + + Total: 2/3 files downloaded successfully + """ + print("\nFile Download Summary") + print("=" * 50) + + success_count = 0 + total_size = 0 + + for result in results: + if result['success']: + size_kb = result['size'] / 1024 + overwrite_notice = " [overwritten]" if result.get('overwritten', False) else "" + print(f"✓ {result['output_path']} ({size_kb:.1f} KB){overwrite_notice}") + success_count += 1 + total_size += result['size'] + else: + print(f"✗ {result['output_path']} - Error: {result['error']}") + + print(f"\nTotal: {success_count}/{len(results)} files downloaded successfully") + if success_count > 0: + total_mb = total_size / (1024 * 1024) + print(f"Total size: {total_mb:.2f} MB") diff --git a/skills/notebooks/01_skills_introduction.ipynb b/skills/notebooks/01_skills_introduction.ipynb new file mode 100644 index 00000000..f0a38c19 --- /dev/null +++ b/skills/notebooks/01_skills_introduction.ipynb @@ -0,0 +1,865 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": "# Introduction to Claude Skills\n\nLearn how to use Claude's Skills feature to create professional documents, analyze data, and automate business workflows with Excel, PowerPoint, and PDF generation.\n\n> **See it in action:** The Skills you'll learn about power Claude's file creation capabilities! Check out **[Claude Creates Files](https://www.anthropic.com/news/create-files)** to see how these Skills enable Claude to create and edit documents directly in Claude.ai." + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table of Contents\n", + "\n", + "1. [Setup & Installation](#setup)\n", + "2. [Understanding Skills](#understanding)\n", + "3. [Discovering Available Skills](#discovering)\n", + "4. [Quick Start: Excel](#excel-quickstart)\n", + "5. [Quick Start: PowerPoint](#powerpoint-quickstart)\n", + "6. [Quick Start: PDF](#pdf-quickstart)\n", + "7. [Troubleshooting](#troubleshooting)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "## 1. Setup & Installation {#setup}\n\n### Prerequisites\n\nBefore starting, make sure you have:\n- Python 3.8 or higher\n- An Anthropic API key from [console.anthropic.com](https://console.anthropic.com/)\n\n### Environment Setup (First Time Only)\n\n**If you haven't set up your environment yet**, follow these steps:\n\n#### Step 1: Create Virtual Environment\n\n```bash\n# Navigate to the skills directory\ncd /path/to/claude-cookbooks/skills\n\n# Create virtual environment\npython -m venv venv\n\n# Activate it\nsource venv/bin/activate # On macOS/Linux\n# OR\nvenv\\Scripts\\activate # On Windows\n```\n\n#### Step 2: Install Dependencies\n\n```bash\n# With venv activated, install requirements\npip install -r requirements.txt\n```\n\n#### Step 3: Select Kernel in VSCode/Jupyter\n\n**In VSCode:**\n1. Open this notebook\n2. Click the kernel picker in the top-right (e.g., \"Python 3.11.x\")\n3. Select \"Python Environments...\"\n4. Choose the `./venv/bin/python` interpreter\n\n**In Jupyter:**\n1. From the Kernel menu → Change Kernel\n2. Select the kernel matching your venv\n\n#### Step 4: Configure API Key\n\n```bash\n# Copy the example file\ncp .env.example .env\n\n# Edit .env and add your API key:\n# ANTHROPIC_API_KEY=sk-ant-api03-...\n```\n\n### Quick Installation Check\n\nRun the cell below to verify your environment is set up correctly:" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "**If you see any ❌ or ⚠️ warnings above**, please complete the setup steps before continuing.\n\n**If anthropic SDK version is too old (needs 0.71.0 or later):**\n```bash\npip install anthropic>=0.71.0\n```\nThen **restart the Jupyter kernel** to pick up the new version.\n\n---\n\n### API Configuration\n\nNow let's load the API key and configure the client:" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### API Configuration\n", + "\n", + "**⚠️ Important**: Create a `.env` file in the skills directory:\n", + "\n", + "```bash\n", + "# Copy the example file\n", + "cp ../.env.example ../.env\n", + "```\n", + "\n", + "Then edit `../.env` to add your Anthropic API key." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import sys\n", + "from pathlib import Path\n", + "\n", + "# Add parent directory to path for imports\n", + "sys.path.insert(0, str(Path.cwd().parent))\n", + "\n", + "import json\n", + "\n", + "from anthropic import Anthropic\n", + "from dotenv import load_dotenv\n", + "\n", + "# Import our file utilities\n", + "from file_utils import (\n", + " download_all_files,\n", + " download_file,\n", + " extract_file_ids,\n", + " get_file_info,\n", + " print_download_summary,\n", + ")\n", + "\n", + "# Load environment variables from parent directory\n", + "load_dotenv(Path.cwd().parent / \".env\")\n", + "\n", + "API_KEY = os.getenv(\"ANTHROPIC_API_KEY\")\n", + "MODEL = os.getenv(\"ANTHROPIC_MODEL\", \"claude-sonnet-4-5-20250929\")\n", + "\n", + "if not API_KEY:\n", + " raise ValueError(\n", + " \"ANTHROPIC_API_KEY not found. \"\n", + " \"Copy ../.env.example to ../.env and add your API key.\"\n", + " )\n", + "\n", + "# Initialize client\n", + "# Note: We'll add beta headers per-request when using Skills\n", + "client = Anthropic(api_key=API_KEY)\n", + "\n", + "# Create outputs directory if it doesn't exist\n", + "OUTPUT_DIR = Path.cwd().parent / \"outputs\"\n", + "OUTPUT_DIR.mkdir(exist_ok=True)\n", + "\n", + "print(\"✓ API key loaded\")\n", + "print(f\"✓ Using model: {MODEL}\")\n", + "print(f\"✓ Output directory: {OUTPUT_DIR}\")\n", + "print(\"\\n📝 Note: Beta headers will be added per-request when using Skills\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test Connection\n", + "\n", + "Let's verify our API connection works:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Simple test to verify API connection\n", + "test_response = client.messages.create(\n", + " model=MODEL,\n", + " max_tokens=100,\n", + " messages=[\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"Say 'Connection successful!' if you can read this.\",\n", + " }\n", + " ],\n", + ")\n", + "\n", + "print(\"API Test Response:\")\n", + "print(test_response.content[0].text)\n", + "print(\n", + " f\"\\n✓ Token usage: {test_response.usage.input_tokens} in, {test_response.usage.output_tokens} out\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "## 2. Understanding Skills {#understanding}\n\n### What are Skills?\n\n**Skills** are organized packages of instructions, executable code, and resources that give Claude specialized capabilities for specific tasks. Think of them as \"expertise packages\" that Claude can discover and load dynamically.\n\n📖 Read our engineering blog post on [Equipping agents for the real world with Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills)\n\n### Why Skills Matter\n\nAfter learning about MCPs (Model Context Protocol) and tools, you might wonder why Skills are important:\n\n- **Skills are higher-level** than individual tools - they combine instructions, code, and resources\n- **Skills are composable** - multiple skills work together seamlessly\n- **Skills are efficient** - progressive disclosure means you only pay for what you use\n- **Skills include proven code** - helper scripts that work reliably, saving time and reducing errors\n\n### Key Benefits\n\n- **Expert-level Performance**: Deliver professional results without the learning curve\n- **Proven Helper Scripts**: Skills contain tested, working code that Claude can use immediately\n- **Organizational Knowledge**: Package company workflows and best practices\n- **Cost Efficiency**: Progressive disclosure minimizes token usage\n- **Reliability**: Pre-tested scripts mean fewer errors and consistent results\n- **Time Savings**: Claude uses existing solutions instead of generating code from scratch\n- **Composable**: Multiple skills work together for complex workflows\n\n### Progressive Disclosure Architecture\n\nSkills use a three-tier loading model:\n\n![Progressive Disclosure - How Skills Load](../assets/prog-disc-1.png)\n\n1. **Metadata** (name: 64 chars, description: 1024 chars): Claude sees skill name and description\n2. **Full Instructions** (<5k tokens): Loaded when skill is relevant\n3. **Linked Files**: Additional resources loaded only if needed\n\n![Progressive Disclosure Stages](../assets/prog-disc-2.png)\n\nThis keeps operations efficient while providing deep expertise on demand. Initially, Claude sees just the metadata from the YAML frontmatter of SKILL.md. Only when a skill is relevant does Claude load the full contents, including any helper scripts and resources.\n\n### Skill Types\n\n| Type | Description | Example |\n|------|-------------|----------|\n| **Anthropic-Managed** | Pre-built skills maintained by Anthropic | `xlsx`, `pptx`, `pdf`, `docx` |\n| **Custom** | User-defined skills for specific workflows | Brand guidelines, financial models |" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "### Skills Conceptual Overview\n\n![Skills Conceptual Diagram](../assets/skills-conceptual-diagram.png)\n\nThis diagram illustrates:\n- **Skill Directory Structure**: How Skills are organized with SKILL.md and supporting files\n- **YAML Frontmatter**: The metadata that Claude sees initially\n- **Progressive Loading**: How Skills are discovered and loaded on-demand\n- **Composability**: Multiple Skills working together in a single request" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### How Skills Work with Code Execution\n", + "\n", + "Skills require the **code execution** tool to be enabled. Here's the typical workflow:\n", + "\n", + "```python\n", + "# Use client.beta.messages.create() for Skills support\n", + "response = client.beta.messages.create(\n", + " model=\"claude-sonnet-4-5-20250929\",\n", + " max_tokens=4096,\n", + " container={\n", + " \"skills\": [\n", + " {\"type\": \"anthropic\", \"skill_id\": \"xlsx\", \"version\": \"latest\"}\n", + " ]\n", + " },\n", + " tools=[{\"type\": \"code_execution_20250825\", \"name\": \"code_execution\"}],\n", + " messages=[{\"role\": \"user\", \"content\": \"Create an Excel file...\"}],\n", + " # Use betas parameter instead of extra_headers\n", + " betas=[\"code-execution-2025-08-25\", \"files-api-2025-04-14\", \"skills-2025-10-02\"]\n", + ")\n", + "```\n", + "\n", + "**What happens:**\n", + "1. Claude receives your request with the xlsx skill loaded\n", + "2. Claude uses code execution to create the file\n", + "3. The response includes a `file_id` for the created file\n", + "4. You use the **Files API** to download the file\n", + "\n", + "**Important: Beta API**\n", + "- Use `client.beta.messages.create()` (not `client.messages.create()`)\n", + "- The `container` parameter is only available in the beta API\n", + "- Use the `betas` parameter to enable beta features:\n", + " - `code-execution-2025-08-25` - Enables code execution\n", + " - `files-api-2025-04-14` - Required for downloading files\n", + " - `skills-2025-10-02` - Enables Skills feature\n", + "\n", + "⚠️ **Note**: When using Skills, you MUST include the code_execution tool in your request." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Token Usage Optimization\n", + "\n", + "Skills dramatically reduce token usage compared to providing instructions in prompts:\n", + "\n", + "| Approach | Token Cost | Performance |\n", + "|----------|------------|-------------|\n", + "| Manual instructions | 5,000-10,000 tokens/request | Variable quality |\n", + "| Skills (metadata only) | Minimal (just name/description) | Expert-level |\n", + "| Skills (full load) | ~5,000 tokens when skill is used | Expert-level |\n", + "\n", + "**The Big Win:** You can pack multiple skills into your prompt without bloating it. Each skill only costs you the metadata (name + description) until you actually use it.\n", + "\n", + "**Example**: Creating an Excel file with formatting\n", + "- Without Skills: ~8,000 tokens to explain all Excel features upfront\n", + "- With Skills: Minimal metadata overhead initially, ~5,000 tokens only when Excel skill is invoked\n", + "- **Key Insight**: The 98% savings applies to the initial context. Once you use a skill, the full instructions are loaded.\n", + "\n", + "**Additional Benefits:**\n", + "- Skills contain helper scripts that are known to work, improving reliability\n", + "- Claude saves time by using proven code patterns instead of generating from scratch\n", + "- You get more consistent, professional results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ⏱️ Expected Generation Times\n", + "\n", + "**⚠️ IMPORTANT**: Document generation with Skills requires code execution and file creation, which takes time. Be patient and let cells complete.\n", + "\n", + "**Observed generation times:**\n", + "- **Excel files**: ~2 minutes (with charts and formatting)\n", + "- **PowerPoint presentations**: ~1-2 minutes (simple 2-slide presentations with charts)\n", + "- **PDF documents**: ~40-60 seconds (simple documents)\n", + "\n", + "**What to expect:**\n", + "- The cell will show `[*]` while running\n", + "- You may see \"Executing...\" status for 1-2 minutes\n", + "- **Do not interrupt the cell** - let it complete fully\n", + "\n", + "**💡 Recommendations:**\n", + "1. **Start simple**: Begin with minimal examples to verify your setup\n", + "2. **Gradually increase complexity**: Add features incrementally\n", + "3. **Be patient**: Operations typically take 40 seconds to 2 minutes\n", + "4. **Note**: Very complex documents may take longer - keep examples focused" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Discovering Available Skills {#discovering}\n", + "\n", + "### List All Built-in Skills\n", + "\n", + "Let's discover what Anthropic-managed skills are available:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# List all available Anthropic skills\n", + "# Note: Skills API requires the skills beta header\n", + "client_with_skills_beta = Anthropic(\n", + " api_key=API_KEY, default_headers={\"anthropic-beta\": \"skills-2025-10-02\"}\n", + ")\n", + "\n", + "skills_response = client_with_skills_beta.beta.skills.list(source=\"anthropic\")\n", + "\n", + "print(\"Available Anthropic-Managed Skills:\")\n", + "print(\"=\" * 80)\n", + "\n", + "for skill in skills_response.data:\n", + " print(f\"\\n📦 Skill ID: {skill.id}\")\n", + " print(f\" Title: {skill.display_title}\")\n", + " print(f\" Latest Version: {skill.latest_version}\")\n", + " print(f\" Created: {skill.created_at}\")\n", + "\n", + " # Get version details\n", + " try:\n", + " version_info = client_with_skills_beta.beta.skills.versions.retrieve(\n", + " skill_id=skill.id, version=skill.latest_version\n", + " )\n", + " print(f\" Name: {version_info.name}\")\n", + " print(f\" Description: {version_info.description}\")\n", + " except Exception as e:\n", + " print(f\" (Unable to fetch version details: {e})\")\n", + "\n", + "print(f\"\\n\\n✓ Found {len(skills_response.data)} Anthropic-managed skills\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Understanding Skill Metadata\n", + "\n", + "Each skill has:\n", + "- **skill_id**: Unique identifier (e.g., \"xlsx\", \"pptx\")\n", + "- **version**: Version number or \"latest\"\n", + "- **name**: Human-readable name\n", + "- **description**: What the skill does\n", + "- **directory**: Skill's folder structure\n", + "\n", + "### Versioning Strategy\n", + "\n", + "- Use `\"latest\"` for Anthropic skills (recommended)\n", + "- Anthropic updates skills automatically\n", + "- Pin specific versions for production stability\n", + "- Custom skills use epoch timestamps for versions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example: Monthly Budget Spreadsheet\n", + "\n", + "We'll start with two examples - a simple one-liner and a detailed request.\n", + "\n", + "#### Simple Example (1-2 lines)\n", + "First, let's see how Skills work with a minimal prompt:\n", + "\n", + "```python\n", + "# Simple prompt - Skills handle the complexity\n", + "prompt = \"Create a quarterly sales report Excel file with revenue data and a chart\"\n", + "```\n", + "\n", + "#### Detailed Example\n", + "For more control, you can provide specific requirements:\n", + "- Income and expense categories\n", + "- Formulas for totals\n", + "- Basic formatting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example: Monthly Budget Spreadsheet\n", + "\n", + "We'll create a simple budget spreadsheet with:\n", + "- Income and expense categories\n", + "- Formulas for totals\n", + "- Basic formatting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**⏱️ Note**: Excel generation typically takes **1-2 minutes** (with charts and formatting). The cell will show `[*]` while running - be patient!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create an Excel budget spreadsheet\n", + "excel_response = client.beta.messages.create( # Note: Using beta.messages for Skills support\n", + " model=MODEL,\n", + " max_tokens=4096,\n", + " container={\n", + " \"skills\": [{\"type\": \"anthropic\", \"skill_id\": \"xlsx\", \"version\": \"latest\"}]\n", + " },\n", + " tools=[{\"type\": \"code_execution_20250825\", \"name\": \"code_execution\"}],\n", + " messages=[\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"\"\"Create a monthly budget Excel spreadsheet with the following:\n", + "\n", + "Income:\n", + "- Salary: $5,000\n", + "- Freelance: $1,200\n", + "- Investments: $300\n", + "\n", + "Expenses:\n", + "- Rent: $1,500\n", + "- Utilities: $200\n", + "- Groceries: $600\n", + "- Transportation: $300\n", + "- Entertainment: $400\n", + "- Savings: $1,000\n", + "\n", + "Include:\n", + "1. Formulas to calculate total income and total expenses\n", + "2. A formula for net savings (income - expenses)\n", + "3. Format currency values properly\n", + "4. Add a simple column chart showing income vs expenses\n", + "5. Use professional formatting with headers\n", + "\"\"\",\n", + " }\n", + " ],\n", + " # Use betas parameter for beta features\n", + " betas=[\"code-execution-2025-08-25\", \"files-api-2025-04-14\", \"skills-2025-10-02\"],\n", + ")\n", + "\n", + "print(\"Excel Response:\")\n", + "print(\"=\" * 80)\n", + "for content in excel_response.content:\n", + " if content.type == \"text\":\n", + " print(content.text)\n", + " elif content.type == \"tool_use\":\n", + " print(f\"\\n🔧 Tool: {content.name}\")\n", + " if hasattr(content, \"input\"):\n", + " print(f\" Input preview: {str(content.input)[:200]}...\")\n", + "\n", + "print(\"\\n\\n📊 Token Usage:\")\n", + "print(f\" Input: {excel_response.usage.input_tokens}\")\n", + "print(f\" Output: {excel_response.usage.output_tokens}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Download the Excel File\n", + "\n", + "Now let's extract the file_id and download the generated Excel file:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Extract file IDs from the response\n", + "file_ids = extract_file_ids(excel_response)\n", + "\n", + "if file_ids:\n", + " print(f\"✓ Found {len(file_ids)} file(s)\\n\")\n", + "\n", + " # Download all files\n", + " results = download_all_files(\n", + " client, excel_response, output_dir=str(OUTPUT_DIR), prefix=\"budget_\"\n", + " )\n", + "\n", + " # Print summary\n", + " print_download_summary(results)\n", + "\n", + " # Show file details\n", + " for file_id in file_ids:\n", + " info = get_file_info(client, file_id)\n", + " if info:\n", + " print(\"\\n📄 File Details:\")\n", + " print(f\" Filename: {info['filename']}\")\n", + " print(f\" Size: {info['size'] / 1024:.1f} KB\")\n", + " print(f\" Created: {info['created_at']}\")\n", + "else:\n", + " print(\"❌ No files found in response\")\n", + " print(\"\\nDebug: Response content types:\")\n", + " for i, content in enumerate(excel_response.content):\n", + " print(f\" {i}. {content.type}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**✨ What just happened?**\n", + "\n", + "1. Claude used the `xlsx` skill to create a professional Excel file\n", + "2. The skill handled all Excel-specific formatting and formulas\n", + "3. The file was created in Claude's code execution environment\n", + "4. We extracted the `file_id` from the response\n", + "5. We downloaded the file using the Files API\n", + "6. The file is now saved in `outputs/budget_*.xlsx`\n", + "\n", + "Open the file in Excel to see the results!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Quick Start: PowerPoint {#powerpoint-quickstart}\n", + "\n", + "Now let's create a PowerPoint presentation using the `pptx` skill." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example: Revenue Presentation\n", + "\n", + "#### Simple Example (1 line)\n", + "```python\n", + "# Minimal prompt - let Skills handle the details\n", + "prompt = \"Create an executive summary presentation with 3 slides about Q3 results\"\n", + "```\n", + "\n", + "#### Detailed Example\n", + "**Note**: This is intentionally kept simple (2 slides, 1 chart) to minimize generation time and demonstrate the core functionality." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example: Simple Revenue Presentation\n", + "\n", + "**Note**: This is intentionally kept simple (2 slides, 1 chart) to minimize generation time and demonstrate the core functionality." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a PowerPoint presentation\n", + "pptx_response = client.beta.messages.create(\n", + " model=MODEL,\n", + " max_tokens=4096,\n", + " container={\n", + " \"skills\": [{\"type\": \"anthropic\", \"skill_id\": \"pptx\", \"version\": \"latest\"}]\n", + " },\n", + " tools=[{\"type\": \"code_execution_20250825\", \"name\": \"code_execution\"}],\n", + " messages=[\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"\"\"Create a simple 2-slide PowerPoint presentation:\n", + "\n", + "Slide 1: Title slide\n", + "- Title: \"Q3 2025 Results\"\n", + "- Subtitle: \"Acme Corporation\"\n", + "\n", + "Slide 2: Revenue Overview\n", + "- Title: \"Quarterly Revenue\"\n", + "- Add a simple column chart showing:\n", + " - Q1: $12M\n", + " - Q2: $13M\n", + " - Q3: $14M\n", + "\n", + "Use clean, professional formatting.\n", + "\"\"\",\n", + " }\n", + " ],\n", + " betas=[\"code-execution-2025-08-25\", \"files-api-2025-04-14\", \"skills-2025-10-02\"],\n", + ")\n", + "\n", + "print(\"PowerPoint Response:\")\n", + "print(\"=\" * 80)\n", + "for content in pptx_response.content:\n", + " if content.type == \"text\":\n", + " print(content.text)\n", + "\n", + "print(\"\\n\\n📊 Token Usage:\")\n", + "print(f\" Input: {pptx_response.usage.input_tokens}\")\n", + "print(f\" Output: {pptx_response.usage.output_tokens}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Download the PowerPoint File" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Download the PowerPoint file\n", + "file_ids = extract_file_ids(pptx_response)\n", + "\n", + "if file_ids:\n", + " results = download_all_files(\n", + " client, pptx_response, output_dir=str(OUTPUT_DIR), prefix=\"q3_review_\"\n", + " )\n", + "\n", + " print_download_summary(results)\n", + "\n", + " print(\"\\n✅ Open the presentation in PowerPoint or Google Slides to view!\")\n", + "else:\n", + " print(\"❌ No files found in response\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**⏱️ Note**: PDF generation typically takes **1-2 minutes** for simple documents. The cell will show `[*]` while running - be patient!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example: PDF Documents\n", + "\n", + "#### Simple Example (1 line)\n", + "```python\n", + "# Quick PDF generation\n", + "prompt = \"Create a professional invoice PDF for $500 consulting services\"\n", + "```\n", + "\n", + "#### Detailed Example: Receipt\n", + "**Note**: This is intentionally kept simple to ensure clean formatting." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example: Simple Receipt\n", + "\n", + "**Note**: This is intentionally kept simple to ensure clean formatting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a PDF receipt\n", + "pdf_response = client.beta.messages.create(\n", + " model=MODEL,\n", + " max_tokens=4096,\n", + " container={\n", + " \"skills\": [{\"type\": \"anthropic\", \"skill_id\": \"pdf\", \"version\": \"latest\"}]\n", + " },\n", + " tools=[{\"type\": \"code_execution_20250825\", \"name\": \"code_execution\"}],\n", + " messages=[\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"\"\"Create a simple receipt PDF:\n", + "\n", + "RECEIPT\n", + "\n", + "Acme Corporation\n", + "Date: January 15, 2025\n", + "Receipt #: RCT-2025-001\n", + "\n", + "Customer: Jane Smith\n", + "\n", + "Items:\n", + "- Product A: $50.00\n", + "- Product B: $75.00\n", + "- Product C: $25.00\n", + "\n", + "Subtotal: $150.00\n", + "Tax (8%): $12.00\n", + "Total: $162.00\n", + "\n", + "Thank you for your business!\n", + "\n", + "Use simple, clean formatting with clear sections.\n", + "\"\"\",\n", + " }\n", + " ],\n", + " betas=[\"code-execution-2025-08-25\", \"files-api-2025-04-14\", \"skills-2025-10-02\"],\n", + ")\n", + "\n", + "print(\"PDF Response:\")\n", + "print(\"=\" * 80)\n", + "for content in pdf_response.content:\n", + " if content.type == \"text\":\n", + " print(content.text)\n", + "\n", + "print(\"\\n\\n📊 Token Usage:\")\n", + "print(f\" Input: {pdf_response.usage.input_tokens}\")\n", + "print(f\" Output: {pdf_response.usage.output_tokens}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Download and Verify the PDF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Download the PDF file\n", + "file_ids = extract_file_ids(pdf_response)\n", + "\n", + "if file_ids:\n", + " results = download_all_files(\n", + " client, pdf_response, output_dir=str(OUTPUT_DIR), prefix=\"receipt_\"\n", + " )\n", + "\n", + " print_download_summary(results)\n", + "\n", + " # Verify PDF integrity\n", + " for result in results:\n", + " if result[\"success\"]:\n", + " file_path = result[\"output_path\"]\n", + " file_size = result[\"size\"]\n", + "\n", + " # Basic PDF validation\n", + " with open(file_path, \"rb\") as f:\n", + " header = f.read(5)\n", + " if header == b\"%PDF-\":\n", + " print(f\"\\n✅ PDF file is valid: {file_path}\")\n", + " print(f\" File size: {file_size / 1024:.1f} KB\")\n", + " else:\n", + " print(f\"\\n⚠️ File may not be a valid PDF: {file_path}\")\n", + "else:\n", + " print(\"❌ No files found in response\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Troubleshooting {#troubleshooting}\n", + "\n", + "### Common Issues and Solutions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Issue 1: API Key Not Found\n", + "\n", + "**Error:**\n", + "```\n", + "ValueError: ANTHROPIC_API_KEY not found\n", + "```\n", + "\n", + "**Solution:**\n", + "1. Ensure `.env` file exists in the parent directory\n", + "2. Check that `ANTHROPIC_API_KEY=sk-ant-api03-...` is set\n", + "3. Restart the Jupyter kernel after creating/editing `.env`\n", + "\n", + "### Issue 2: Container Parameter Not Recognized\n", + "\n", + "**Error:**\n", + "```\n", + "TypeError: Messages.create() got an unexpected keyword argument 'container'\n", + "```\n", + "\n", + "**Solution:**\n", + "Use `client.beta.messages.create()` instead of `client.messages.create()`:\n", + "```python\n", + "# ✅ Correct - use beta.messages\n", + "response = client.beta.messages.create(\n", + " model=MODEL,\n", + " container={\"skills\": [...]},\n", + " tools=[{\"type\": \"code_execution_20250825\", \"name\": \"code_execution\"}],\n", + " messages=[...],\n", + " betas=[\"code-execution-2025-08-25\", \"files-api-2025-04-14\", \"skills-2025-10-02\"]\n", + ")\n", + "\n", + "# ❌ Incorrect - regular messages doesn't support container\n", + "response = client.messages.create(\n", + " model=MODEL,\n", + " container={\"skills\": [...]}, # Error!\n", + " messages=[...]\n", + ")\n", + "```\n", + "\n", + "### Issue 3: Skills Beta Requires Code Execution Tool\n", + "\n", + "**Error:**\n", + "```\n", + "BadRequestError: Skills beta requires the code_execution tool to be included in the request.\n", + "```\n", + "\n", + "**Solution:**\n", + "When using Skills, you MUST include the code_execution tool:\n", + "```python\n", + "# ✅ Correct\n", + "response = client.beta.messages.create(\n", + " model=MODEL,\n", + " tools=[{\"type\": \"code_execution_20250825\", \"name\": \"code_execution\"}],\n", + " messages=[...],\n", + " betas=[\"...\", \"skills-2025-10-02\"]\n", + ")\n", + "\n", + "# ❌ Incorrect - missing code_execution tool\n", + "response = client.beta.messages.create(\n", + " model=MODEL,\n", + " messages=[...],\n", + " betas=[\"...\", \"skills-2025-10-02\"]\n", + ")\n", + "```\n", + "\n", + "### Issue 4: No Files Found in Response\n", + "\n", + "**Error:**\n", + "```\n", + "❌ No files found in response\n", + "```\n", + "\n", + "**Solution:**\n", + "1. Check that code execution tool is included in the request\n", + "2. Verify the skill was loaded (check response content)\n", + "3. Ensure the task actually requires file creation\n", + "4. Look for error messages in the response text\n", + "\n", + "### Issue 5: File Download Failed\n", + "\n", + "**Error:**\n", + "```\n", + "Error retrieving file: File not found\n", + "```\n", + "\n", + "**Solution:**\n", + "1. Files may have a limited lifetime on Anthropic's servers\n", + "2. Download files immediately after creation\n", + "3. Check file_id is correctly extracted from response\n", + "4. Verify Files API beta is included in betas list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Token Optimization Tips\n", + "\n", + "1. **Use \"latest\" version** for Anthropic skills - automatically optimized\n", + "2. **Batch operations** - Create multiple files in one conversation when possible\n", + "3. **Reuse containers** - Use `container.id` from previous responses to avoid reloading skills\n", + "4. **Be specific** - Clear instructions mean fewer iterations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### API Rate Limiting\n", + "\n", + "If you encounter rate limits:\n", + "- Implement exponential backoff for retries\n", + "- Use batch processing for multiple files\n", + "- Consider upgrading your API tier for higher limits" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "## Next Steps\n\n🎉 **Congratulations!** You've learned the basics of Claude Skills.\n\n### See Skills in Action\n\nCheck out the official announcement to see how these Skills power Claude's file creation capabilities:\n- **[Claude Creates Files](https://www.anthropic.com/news/create-files)** - See how Skills enable Claude to create and edit Excel, PowerPoint, and PDF files directly\n\n### Continue Learning:\n\n- **[Notebook 2: Financial Applications](02_skills_financial_applications.ipynb)** - Real-world business use cases with financial data\n- **[Notebook 3: Custom Skills Development](03_skills_custom_development.ipynb)** - Build your own specialized skills\n\n### Support Articles:\n\n- 📚 **[Teach Claude your way of working using Skills](https://support.claude.com/en/articles/12580051-teach-claude-your-way-of-working-using-skills)** - User guide for working with Skills\n- 🛠️ **[How to create a skill with Claude through conversation](https://support.claude.com/en/articles/12599426-how-to-create-a-skill-with-claude-through-conversation)** - Interactive skill creation guide\n\n### Resources:\n\n- [Claude API Documentation](https://docs.anthropic.com/en/api/messages)\n- [Skills Documentation](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview)\n- [Skills Best Practices](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices)\n- [Files API Documentation](https://docs.claude.com/en/api/files-content)\n- [Claude Support](https://support.claude.com)\n\n### Try These Experiments:\n\n1. Start with simple one-line prompts to see Skills in action\n2. Modify the budget example to include more categories\n3. Create a presentation with your own data\n4. Generate a PDF report combining text and tables\n5. Use multiple skills together in a single request" + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/skills/notebooks/02_skills_financial_applications.ipynb b/skills/notebooks/02_skills_financial_applications.ipynb new file mode 100644 index 00000000..d686dfc2 --- /dev/null +++ b/skills/notebooks/02_skills_financial_applications.ipynb @@ -0,0 +1,896 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": "# Claude Skills for Financial Applications\n\nBuild real-world financial dashboards, portfolio analytics, and automated reporting workflows using Claude's Excel, PowerPoint, and PDF skills.\n\n> **💡 Real-world Impact:** These are the same Skills that power **[Claude Creates Files](https://www.anthropic.com/news/create-files)**, enabling Claude to create professional financial documents directly in the interface.\n\n**What you'll learn:**\n- Create comprehensive financial models in Excel with formulas and charts\n- Generate executive presentations from financial data\n- Build portfolio analysis tools with risk metrics\n- Automate multi-format reporting pipelines" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table of Contents\n", + "\n", + "1. [Setup & Data Loading](#setup)\n", + "2. [Use Case 1: Financial Dashboard Creation](#financial-dashboard)\n", + " - [Excel Financial Model](#excel-model)\n", + " - [Executive PowerPoint](#executive-ppt)\n", + " - [PDF Financial Report](#pdf-report)\n", + "3. [Use Case 2: Portfolio Analysis Workflow](#portfolio-analysis)\n", + " - [Portfolio Analytics Excel](#portfolio-excel)\n", + " - [Investment Committee Deck](#investment-deck)\n", + "4. [Use Case 3: Automated Reporting Pipeline](#reporting-pipeline)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "This notebook assumes you've completed **Notebook 1: Introduction to Skills**.\n", + "\n", + "If you haven't:\n", + "1. Complete the setup in Notebook 1 first\n", + "2. Verify your environment with the test cells\n", + "3. Ensure you can create and download files\n", + "\n", + "**Required:**\n", + "- Anthropic API key configured\n", + "- SDK version 0.69.0 installed from whl\n", + "- Virtual environment activated" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Setup & Data Loading {#setup}\n", + "\n", + "Let's start by importing our dependencies and loading the financial data we'll work with throughout this notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Standard imports\n", + "import json\n", + "import os\n", + "import sys\n", + "from datetime import datetime, timedelta\n", + "from pathlib import Path\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "# Add parent directory for imports\n", + "sys.path.insert(0, str(Path.cwd().parent))\n", + "\n", + "# Anthropic SDK\n", + "from anthropic import Anthropic\n", + "from dotenv import load_dotenv\n", + "\n", + "# Our utilities\n", + "from file_utils import (\n", + " download_all_files,\n", + " extract_file_ids,\n", + " get_file_info,\n", + " print_download_summary,\n", + ")\n", + "\n", + "# Load environment\n", + "load_dotenv(Path.cwd().parent / \".env\")\n", + "\n", + "# Configuration\n", + "API_KEY = os.getenv(\"ANTHROPIC_API_KEY\")\n", + "MODEL = \"claude-sonnet-4-5-20250929\"\n", + "\n", + "if not API_KEY:\n", + " raise ValueError(\"ANTHROPIC_API_KEY not found. Please configure your .env file.\")\n", + "\n", + "# Initialize client\n", + "client = Anthropic(api_key=API_KEY)\n", + "\n", + "# Setup directories\n", + "OUTPUT_DIR = Path.cwd().parent / \"outputs\" / \"financial\"\n", + "OUTPUT_DIR.mkdir(parents=True, exist_ok=True)\n", + "\n", + "DATA_DIR = Path.cwd().parent / \"sample_data\"\n", + "\n", + "print(\"✓ Environment configured\")\n", + "print(f\"✓ Output directory: {OUTPUT_DIR}\")\n", + "print(f\"✓ Data directory: {DATA_DIR}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load Financial Data\n", + "\n", + "We have four datasets representing different aspects of a company's financial position:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load financial statements\n", + "financial_statements = pd.read_csv(DATA_DIR / \"financial_statements.csv\")\n", + "print(\"📊 Financial Statements Overview:\")\n", + "print(f\" Shape: {financial_statements.shape}\")\n", + "print(\n", + " f\" Categories: {len(financial_statements['Category'].unique())} financial metrics\"\n", + ")\n", + "print(f\" Quarters: {list(financial_statements.columns[1:5])}\")\n", + "print()\n", + "\n", + "# Show sample data\n", + "print(\"Sample data (first 5 rows):\")\n", + "financial_statements.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load portfolio holdings\n", + "with open(DATA_DIR / \"portfolio_holdings.json\") as f:\n", + " portfolio_data = json.load(f)\n", + "\n", + "print(\"💼 Portfolio Overview:\")\n", + "print(f\" Portfolio: {portfolio_data['portfolio_name']}\")\n", + "print(f\" Total Value: ${portfolio_data['total_value']:,.2f}\")\n", + "print(f\" Holdings: {len(portfolio_data['holdings'])} stocks\")\n", + "print(f\" Cash Position: ${portfolio_data['cash_position']['amount']:,.2f}\")\n", + "print(\n", + " f\" Total Return: {portfolio_data['performance_metrics']['total_return_percent']:.1f}%\"\n", + ")\n", + "print()\n", + "\n", + "# Convert holdings to DataFrame for easier manipulation\n", + "portfolio_df = pd.DataFrame(portfolio_data[\"holdings\"])\n", + "print(\"Top 5 holdings by value:\")\n", + "portfolio_df.nlargest(5, \"market_value\")[\n", + " [\"ticker\", \"name\", \"market_value\", \"unrealized_gain\"]\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load quarterly metrics\n", + "with open(DATA_DIR / \"quarterly_metrics.json\") as f:\n", + " quarterly_metrics = json.load(f)\n", + "\n", + "print(\"📈 Quarterly Metrics Overview:\")\n", + "print(f\" Quarters available: {len(quarterly_metrics['quarters'])}\")\n", + "print(f\" Metrics per quarter: {len(quarterly_metrics['quarters'][0])} KPIs\")\n", + "print()\n", + "\n", + "# Show latest quarter metrics\n", + "latest_quarter = quarterly_metrics[\"quarters\"][-1]\n", + "print(f\"Latest Quarter ({latest_quarter['quarter']}):\")\n", + "for key, value in latest_quarter.items():\n", + " if key != \"quarter\" and isinstance(value, int | float):\n", + " if \"revenue\" in key.lower() or \"cost\" in key.lower():\n", + " print(f\" {key.replace('_', ' ').title()}: ${value:,.0f}\")\n", + " elif (\n", + " \"percent\" in key.lower() or \"margin\" in key.lower() or \"rate\" in key.lower()\n", + " ):\n", + " print(f\" {key.replace('_', ' ').title()}: {value:.1f}%\")\n", + " else:\n", + " print(f\" {key.replace('_', ' ').title()}: {value:,.0f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Helper Functions\n", + "\n", + "Let's define some helper functions for this notebook:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def create_skills_message(client, prompt, skills, prefix=\"\", show_token_usage=True):\n", + " \"\"\"\n", + " Helper function to create messages with Skills.\n", + "\n", + " Args:\n", + " client: Anthropic client\n", + " prompt: User prompt\n", + " skills: List of skill dicts [{\"type\": \"anthropic\", \"skill_id\": \"xlsx\", \"version\": \"latest\"}]\n", + " prefix: Prefix for downloaded files\n", + " show_token_usage: Whether to print token usage\n", + "\n", + " Returns:\n", + " Tuple of (response, download_results)\n", + " \"\"\"\n", + " response = client.beta.messages.create(\n", + " model=MODEL,\n", + " max_tokens=4096,\n", + " container={\"skills\": skills},\n", + " tools=[{\"type\": \"code_execution_20250825\", \"name\": \"code_execution\"}],\n", + " messages=[{\"role\": \"user\", \"content\": prompt}],\n", + " betas=[\n", + " \"code-execution-2025-08-25\",\n", + " \"files-api-2025-04-14\",\n", + " \"skills-2025-10-02\",\n", + " ],\n", + " )\n", + "\n", + " if show_token_usage:\n", + " print(\n", + " f\"\\n📊 Token Usage: {response.usage.input_tokens} in, {response.usage.output_tokens} out\"\n", + " )\n", + "\n", + " # Download files\n", + " results = download_all_files(\n", + " client, response, output_dir=str(OUTPUT_DIR), prefix=prefix\n", + " )\n", + "\n", + " return response, results\n", + "\n", + "\n", + "def format_financial_value(value, is_currency=True, decimals=0):\n", + " \"\"\"Format financial values for display.\"\"\"\n", + " if is_currency:\n", + " return f\"${value:,.{decimals}f}\"\n", + " else:\n", + " return f\"{value:,.{decimals}f}\"\n", + "\n", + "\n", + "print(\"✓ Helper functions defined\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Use Case 1: Financial Dashboard Creation {#financial-dashboard}\n", + "\n", + "Now that we have our data loaded and helper functions defined, let's dive into our first practical use case: creating comprehensive financial dashboards. We'll start by generating multi-sheet Excel workbooks that automatically include formulas, formatting, and charts.\n", + "\n", + "### 2.1 Excel Financial Model {#excel-model}\n", + "\n", + "We'll create a financial dashboard that includes:\n", + "- Profit & Loss statements with year-over-year comparisons\n", + "- Balance sheet analysis\n", + "- Cash flow tracking\n", + "- KPI dashboards with visualizations\n", + "\n", + "This demonstrates how Claude's Skills can handle complex Excel generation tasks that would typically require hours of manual work." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create Financial Dashboard Excel\n", + "print(\"Creating financial dashboard Excel file...\")\n", + "print(\"This creates a 2-sheet dashboard optimized for the Skills API.\")\n", + "print(\"\\n⏱️ Generation time: 1-2 minutes\\n\")\n", + "\n", + "# Prepare the financial data\n", + "fs_data = financial_statements.to_dict(\"records\")\n", + "quarters_2024 = [\"Q1_2024\", \"Q2_2024\", \"Q3_2024\", \"Q4_2024\"]\n", + "\n", + "# Extract key financial metrics\n", + "revenue_by_quarter = {\n", + " \"Q1 2024\": financial_statements[financial_statements[\"Category\"] == \"Revenue\"][\n", + " \"Q1_2024\"\n", + " ].values[0],\n", + " \"Q2 2024\": financial_statements[financial_statements[\"Category\"] == \"Revenue\"][\n", + " \"Q2_2024\"\n", + " ].values[0],\n", + " \"Q3 2024\": financial_statements[financial_statements[\"Category\"] == \"Revenue\"][\n", + " \"Q3_2024\"\n", + " ].values[0],\n", + " \"Q4 2024\": financial_statements[financial_statements[\"Category\"] == \"Revenue\"][\n", + " \"Q4_2024\"\n", + " ].values[0],\n", + "}\n", + "\n", + "financial_dashboard_prompt = f\"\"\"\n", + "Create a financial dashboard Excel workbook with 2 sheets:\n", + "\n", + "Sheet 1 - \"P&L Summary\":\n", + "Create a Profit & Loss summary table for 2024 quarters with these rows:\n", + "- Revenue: {', '.join([f'Q{i+1}: ${v/1000000:.1f}M' for i, v in enumerate(revenue_by_quarter.values())])}\n", + "- Gross Profit: Use values from the data\n", + "- Operating Income: Use values from the data\n", + "- Net Income: Use values from the data\n", + "- Add a Total column with SUM formulas\n", + "- Add a row showing profit margins (Net Income / Revenue)\n", + "- Apply currency formatting and bold headers\n", + "- Add a simple bar chart showing quarterly revenue\n", + "\n", + "Sheet 2 - \"Key Metrics\":\n", + "Create a metrics dashboard with:\n", + "- Total Revenue 2024: SUM of all quarters\n", + "- Average Quarterly Revenue: AVERAGE formula\n", + "- Q4 vs Q1 Growth: Percentage increase\n", + "- Best Quarter: MAX formula to identify\n", + "- Operating Margin Q4: Calculate from data\n", + "- Year-over-year growth vs 2023\n", + "\n", + "Apply professional formatting with borders, bold headers, and currency formats.\n", + "\"\"\"\n", + "\n", + "# Create the Excel financial dashboard\n", + "excel_response, excel_results = create_skills_message(\n", + " client,\n", + " financial_dashboard_prompt,\n", + " [{\"type\": \"anthropic\", \"skill_id\": \"xlsx\", \"version\": \"latest\"}],\n", + " prefix=\"financial_dashboard_\",\n", + ")\n", + "\n", + "print(\"\\n\" + \"=\" * 60)\n", + "print_download_summary(excel_results)\n", + "\n", + "if len(excel_results) > 0 and excel_results[0][\"success\"]:\n", + " print(\"\\n✅ Financial dashboard Excel created successfully!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 💡 Best Practices for Excel Generation\n", + "\n", + "Based on our testing, here are the optimal approaches for creating Excel files with Skills:\n", + "\n", + "**Recommended Approach:**\n", + "- **2-3 sheets per workbook** works reliably and generates quickly\n", + "- **Focus each sheet** on a specific purpose (e.g., P&L, metrics, charts)\n", + "- **Add complexity incrementally** - start simple, then enhance\n", + "\n", + "**For Complex Dashboards:**\n", + "1. **Create multiple focused files** instead of one complex file\n", + " - Example: `financial_pnl.xlsx`, `balance_sheet.xlsx`, `kpi_dashboard.xlsx`\n", + "2. **Use the pipeline pattern** to create and enhance files sequentially\n", + "3. **Combine files programmatically** using pandas or openpyxl if needed\n", + "\n", + "**Performance Tips:**\n", + "- Simple 2-sheet dashboards: ~1-2 minutes\n", + "- PowerPoint and PDF generation: Very reliable for complex content\n", + "- Token usage: Structured data (JSON/CSV) is more efficient than prose" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2 Executive PowerPoint {#executive-ppt}\n", + "\n", + "With our financial data now organized in Excel, let's create an executive presentation that summarizes the key insights. This demonstrates how Skills can generate professional PowerPoint presentations with charts, formatted text, and multiple slides - perfect for board meetings or investor updates.\n", + "\n", + "The presentation will include:\n", + "- Q4 2024 performance highlights\n", + "- Financial metrics with year-over-year comparisons\n", + "- Profitability trends with visualizations\n", + "- Key takeaways and outlook" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Creating executive presentation from financial metrics...\")\n", + "print(\"\\n⏱️ Generation time: 1-2 minutes\\n\")\n", + "\n", + "# Calculate some key metrics for the presentation\n", + "q4_2024_revenue = 14500000\n", + "q4_2023_revenue = 12300000\n", + "yoy_growth = (q4_2024_revenue - q4_2023_revenue) / q4_2023_revenue * 100\n", + "\n", + "q4_2024_net_income = 1878750\n", + "q4_2023_net_income = 1209000\n", + "net_income_growth = (q4_2024_net_income - q4_2023_net_income) / q4_2023_net_income * 100\n", + "\n", + "executive_ppt_prompt = f\"\"\"\n", + "Create a 4-slide executive presentation for Q4 2024 financial results:\n", + "\n", + "Slide 1 - Title:\n", + "- Title: \"Q4 2024 Financial Results\"\n", + "- Subtitle: \"Executive Summary - Acme Corporation\"\n", + "- Date: January 2025\n", + "\n", + "Slide 2 - Financial Highlights:\n", + "- Title: \"Q4 2024 Performance Highlights\"\n", + "- Create a two-column layout:\n", + " Left side - Key Metrics:\n", + " • Revenue: $14.5M (+{yoy_growth:.1f}% YoY)\n", + " • Net Income: $1.88M (+{net_income_growth:.1f}% YoY)\n", + " • Operating Margin: 17.9% (up 2.9pp)\n", + " • Operating Cash Flow: $2.85M\n", + "\n", + " Right side - Column chart showing quarterly revenue:\n", + " Q1 2024: $12.5M\n", + " Q2 2024: $13.2M\n", + " Q3 2024: $13.8M\n", + " Q4 2024: $14.5M\n", + "\n", + "Slide 3 - Profitability Trends:\n", + "- Title: \"Margin Expansion & Profitability\"\n", + "- Add a line chart showing net margin % by quarter:\n", + " Q1 2024: 11.4%\n", + " Q2 2024: 11.8%\n", + " Q3 2024: 12.4%\n", + " Q4 2024: 13.0%\n", + "- Add bullet points below:\n", + " • Consistent margin expansion throughout 2024\n", + " • Operating leverage driving profitability\n", + " • Cost optimization initiatives delivering results\n", + "\n", + "Slide 4 - Key Takeaways:\n", + "- Title: \"Key Takeaways & Outlook\"\n", + "- Bullet points:\n", + " ✓ Record Q4 revenue of $14.5M\n", + " ✓ 17.9% YoY revenue growth\n", + " ✓ 55% increase in net income YoY\n", + " ✓ Strong cash generation: $2.85M operating cash flow\n", + " ✓ Well-positioned for continued growth in 2025\n", + "\n", + "Use professional corporate design:\n", + "- Dark blue (#003366) for headers\n", + "- Clean, modern layout\n", + "- Data-driven visualizations\n", + "\"\"\"\n", + "\n", + "# Create the executive presentation\n", + "ppt_response, ppt_results = create_skills_message(\n", + " client,\n", + " executive_ppt_prompt,\n", + " [{\"type\": \"anthropic\", \"skill_id\": \"pptx\", \"version\": \"latest\"}],\n", + " prefix=\"executive_summary_\",\n", + ")\n", + "\n", + "print(\"\\n\" + \"=\" * 60)\n", + "print_download_summary(ppt_results)\n", + "\n", + "if len(ppt_results) > 0 and ppt_results[0][\"success\"]:\n", + " print(\"\\n✅ Executive presentation created successfully!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Use Case 2: Portfolio Analysis Workflow {#portfolio-analysis}\n", + "\n", + "Now let's shift our focus from company financials to investment portfolio analysis. In this section, we'll demonstrate how to create comprehensive portfolio analytics and investment committee presentations using the portfolio data we loaded earlier.\n", + "\n", + "This workflow showcases:\n", + "- Detailed portfolio performance analysis in Excel\n", + "- Risk metrics and sector allocation visualization\n", + "- Professional investment committee presentations\n", + "- Data-driven rebalancing recommendations\n", + "\n", + "We'll start by creating an Excel workbook with portfolio analytics, then generate an investment committee presentation that summarizes our findings." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### First, let's create a comprehensive portfolio analysis Excel workbook\n", + "\n", + "Before we create the investment committee presentation, we need to analyze our portfolio data in detail. This Excel workbook will serve as the foundation for our investment recommendations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Creating portfolio analysis Excel workbook...\")\n", + "print(\"This creates a focused 2-sheet portfolio analysis optimized for the Skills API.\")\n", + "print(\"\\n⏱️ Generation time: 1-2 minutes\\n\")\n", + "\n", + "# Prepare portfolio data for the prompt\n", + "top_holdings = portfolio_df.nlargest(5, \"market_value\")\n", + "sector_allocation = portfolio_data[\"sector_allocation\"]\n", + "\n", + "portfolio_excel_prompt = f\"\"\"\n", + "Create a portfolio analysis Excel workbook with 2 sheets:\n", + "\n", + "Sheet 1 - \"Portfolio Overview\":\n", + "Create a comprehensive holdings and performance table:\n", + "\n", + "Section 1 - Holdings (top of sheet):\n", + "{portfolio_df[['ticker', 'name', 'shares', 'current_price', 'market_value', 'unrealized_gain', 'allocation_percent']].head(10).to_string()}\n", + "\n", + "Section 2 - Portfolio Summary:\n", + "- Total portfolio value: ${portfolio_data['total_value']:,.2f}\n", + "- Total unrealized gain: ${portfolio_df['unrealized_gain'].sum():,.2f}\n", + "- Total Return: {portfolio_data['performance_metrics']['total_return_percent']:.1f}%\n", + "- YTD Return: {portfolio_data['performance_metrics']['year_to_date_return']:.1f}%\n", + "- Sharpe Ratio: {portfolio_data['performance_metrics']['sharpe_ratio']:.2f}\n", + "- Portfolio Beta: {portfolio_data['performance_metrics']['beta']:.2f}\n", + "\n", + "Apply conditional formatting: green for gains, red for losses.\n", + "Add a bar chart showing top 5 holdings by value.\n", + "\n", + "Sheet 2 - \"Sector Analysis & Risk\":\n", + "Create sector allocation and risk metrics:\n", + "\n", + "Section 1 - Sector Allocation:\n", + "{json.dumps(sector_allocation, indent=2)}\n", + "Include a pie chart of sector allocation.\n", + "\n", + "Section 2 - Key Risk Metrics:\n", + "- Portfolio Beta: {portfolio_data['performance_metrics']['beta']:.2f}\n", + "- Standard Deviation: {portfolio_data['performance_metrics']['standard_deviation']:.1f}%\n", + "- Value at Risk (95%): $62,500\n", + "- Maximum Drawdown: -12.3%\n", + "- Sharpe Ratio: {portfolio_data['performance_metrics']['sharpe_ratio']:.2f}\n", + "\n", + "Section 3 - Rebalancing Recommendations:\n", + "- Reduce Technology from 20% to 18%\n", + "- Increase Healthcare from 8.7% to 10%\n", + "- Maintain current diversification\n", + "\n", + "Apply professional formatting with clear sections and headers.\n", + "\"\"\"\n", + "\n", + "# Create portfolio analysis Excel\n", + "portfolio_response, portfolio_results = create_skills_message(\n", + " client,\n", + " portfolio_excel_prompt,\n", + " [{\"type\": \"anthropic\", \"skill_id\": \"xlsx\", \"version\": \"latest\"}],\n", + " prefix=\"portfolio_analysis_\",\n", + ")\n", + "\n", + "print(\"\\n\" + \"=\" * 60)\n", + "print_download_summary(portfolio_results)\n", + "\n", + "if len(portfolio_results) > 0 and portfolio_results[0][\"success\"]:\n", + " print(\"\\n✅ Portfolio analysis Excel created successfully!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.2 Investment Committee Presentation {#investment-deck}\n", + "\n", + "With our detailed portfolio analysis complete, let's now create a professional presentation for the investment committee. This presentation will distill the key insights from our Excel analysis into a concise, visual format suitable for decision-makers.\n", + "\n", + "The presentation will cover:\n", + "- Portfolio performance summary with key metrics\n", + "- Asset allocation and diversification analysis\n", + "- Risk metrics and risk-adjusted returns\n", + "- Strategic recommendations for rebalancing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Creating investment committee presentation...\")\n", + "print(\"\\n⏱️ Generation time: 1-2 minutes\\n\")\n", + "\n", + "investment_deck_prompt = f\"\"\"\n", + "Create a 5-slide investment committee presentation:\n", + "\n", + "Slide 1 - Title:\n", + "- Title: \"Portfolio Review - Q4 2024\"\n", + "- Subtitle: \"{portfolio_data['portfolio_name']}\"\n", + "- Date: January 2025\n", + "- Portfolio Value: ${portfolio_data['total_value']:,.0f}\n", + "\n", + "Slide 2 - Portfolio Overview:\n", + "- Title: \"Portfolio Performance Summary\"\n", + "- Two-column layout:\n", + "\n", + " Left - Key Metrics:\n", + " • Total Value: ${portfolio_data['total_value']:,.0f}\n", + " • YTD Return: +{portfolio_data['performance_metrics']['year_to_date_return']:.1f}%\n", + " • Total Return: ${portfolio_data['performance_metrics']['total_return']:,.0f}\n", + " • Sharpe Ratio: {portfolio_data['performance_metrics']['sharpe_ratio']:.2f}\n", + "\n", + " Right - Bar chart of top 5 holdings by value:\n", + " {', '.join([f\"{h['ticker']}: ${h['market_value']:,.0f}\" for h in top_holdings.to_dict('records')])}\n", + "\n", + "Slide 3 - Sector Allocation:\n", + "- Title: \"Asset Allocation & Diversification\"\n", + "- Pie chart showing:\n", + " Technology: {sector_allocation['Technology']:.1f}%\n", + " Financials: {sector_allocation['Financials']:.1f}%\n", + " Healthcare: {sector_allocation['Healthcare']:.1f}%\n", + " Consumer: {sector_allocation['Consumer Discretionary'] + sector_allocation['Consumer Staples']:.1f}%\n", + " Fixed Income: {sector_allocation['Bonds']:.1f}%\n", + " Cash: {sector_allocation['Cash']:.1f}%\n", + "\n", + "Slide 4 - Risk Analysis:\n", + "- Title: \"Risk Metrics & Analysis\"\n", + "- Content:\n", + " Risk Indicators:\n", + " • Portfolio Beta: {portfolio_data['performance_metrics']['beta']:.2f} (lower market risk)\n", + " • Standard Deviation: {portfolio_data['performance_metrics']['standard_deviation']:.1f}%\n", + " • Maximum Drawdown: -12.3%\n", + " • Value at Risk (95%): $62,500\n", + "\n", + " Risk-Adjusted Performance:\n", + " • Sharpe Ratio: {portfolio_data['performance_metrics']['sharpe_ratio']:.2f} (excellent)\n", + " • Alpha Generation: +2.3% vs benchmark\n", + "\n", + "Slide 5 - Recommendations:\n", + "- Title: \"Strategic Recommendations\"\n", + "- Bullet points:\n", + " ✓ Maintain current allocation - well diversified\n", + " ✓ Consider profit-taking in Technology (20% → 18%)\n", + " ✓ Increase Healthcare allocation (8.7% → 10%)\n", + " ✓ Monitor bond duration given rate environment\n", + " ✓ Rebalance quarterly to maintain targets\n", + "\n", + "Use professional investment presentation design.\n", + "\"\"\"\n", + "\n", + "# Create investment committee deck\n", + "investment_response, investment_results = create_skills_message(\n", + " client,\n", + " investment_deck_prompt,\n", + " [{\"type\": \"anthropic\", \"skill_id\": \"pptx\", \"version\": \"latest\"}],\n", + " prefix=\"investment_committee_\",\n", + ")\n", + "\n", + "print(\"\\n\" + \"=\" * 60)\n", + "print_download_summary(investment_results)\n", + "print(\"\\n✅ Investment committee presentation created successfully!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Use Case 3: Automated Reporting Pipeline {#reporting-pipeline}\n", + "\n", + "So far, we've created individual documents for specific purposes. Now let's demonstrate the power of chaining multiple Skills together in an automated workflow. This pipeline pattern is essential for production systems where you need to generate multiple related documents from the same data source.\n", + "\n", + "In this example, we'll create a complete reporting suite that:\n", + "1. **Analyzes data** in Excel with calculations and charts\n", + "2. **Summarizes insights** in a PowerPoint presentation\n", + "3. **Documents the process** in a formal PDF report\n", + "\n", + "This showcases how Skills can work together to create a comprehensive reporting solution that would traditionally require multiple tools and manual coordination.\n", + "\n", + "**Key benefits of the pipeline approach:**\n", + "- Consistent data across all documents\n", + "- Reduced total generation time\n", + "- Token usage optimization\n", + "- Scalable to multiple report types\n", + "\n", + "**⏱️ Total expected time:** 2-3 minutes for the complete pipeline" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"🔄 Starting Automated Reporting Pipeline\")\n", + "print(\"=\" * 60)\n", + "print(\"This will create a complete reporting suite:\")\n", + "print(\"1. Excel analysis → 2. PowerPoint summary → 3. PDF documentation\")\n", + "print(\"\\n⏱️ Total pipeline time: 2-3 minutes\\n\")\n", + "\n", + "# Track token usage across the pipeline\n", + "pipeline_tokens = {\"input\": 0, \"output\": 0}\n", + "\n", + "# Step 1: Create Excel Analysis\n", + "print(\"Step 1/3: Creating Excel analysis with quarterly metrics...\")\n", + "\n", + "excel_pipeline_prompt = f\"\"\"\n", + "Create a quarterly business metrics Excel file:\n", + "\n", + "Sheet 1 - \"Quarterly KPIs\":\n", + "Create a table with these quarterly metrics for 2024:\n", + "{json.dumps([{k: v for k, v in q.items() if k in ['quarter', 'revenue', 'gross_margin', 'customer_count', 'churn_rate']}\n", + " for q in quarterly_metrics['quarters']], indent=2)}\n", + "\n", + "Add:\n", + "- Quarter-over-quarter growth calculations\n", + "- Average and total rows\n", + "- Conditional formatting for trends\n", + "- Line chart showing revenue trend\n", + "- Column chart showing customer count\n", + "\n", + "Sheet 2 - \"YoY Comparison\":\n", + "Compare Q4 2024 vs Q4 2023 for all metrics.\n", + "Calculate percentage changes and highlight improvements.\n", + "\n", + "Professional formatting with headers and borders.\n", + "\"\"\"\n", + "\n", + "excel_response, excel_results = create_skills_message(\n", + " client,\n", + " excel_pipeline_prompt,\n", + " [{\"type\": \"anthropic\", \"skill_id\": \"xlsx\", \"version\": \"latest\"}],\n", + " prefix=\"pipeline_1_metrics_\",\n", + " show_token_usage=False,\n", + ")\n", + "\n", + "pipeline_tokens[\"input\"] += excel_response.usage.input_tokens\n", + "pipeline_tokens[\"output\"] += excel_response.usage.output_tokens\n", + "print(\n", + " f\"✓ Excel created - Tokens: {excel_response.usage.input_tokens} in, {excel_response.usage.output_tokens} out\"\n", + ")\n", + "\n", + "# Step 2: Create PowerPoint Summary\n", + "print(\"\\nStep 2/3: Creating PowerPoint summary from metrics...\")\n", + "\n", + "ppt_pipeline_prompt = \"\"\"\n", + "Create a 3-slide quarterly metrics summary presentation:\n", + "\n", + "Slide 1:\n", + "- Title: \"Q4 2024 Metrics Summary\"\n", + "- Subtitle: \"Automated Reporting Pipeline Demo\"\n", + "\n", + "Slide 2:\n", + "- Title: \"Key Performance Indicators\"\n", + "- Show Q4 2024 metrics:\n", + " • Revenue: $3.2M (+15% QoQ)\n", + " • Customers: 850 (+8.9% QoQ)\n", + " • Gross Margin: 72%\n", + " • Churn Rate: 2.8% (improved from 3.5%)\n", + "- Add a simple bar chart comparing Q3 vs Q4 revenue\n", + "\n", + "Slide 3:\n", + "- Title: \"Quarterly Trend Analysis\"\n", + "- Line chart showing revenue growth Q1-Q4\n", + "- Key insight bullets:\n", + " • Consistent QoQ growth\n", + " • Customer acquisition accelerating\n", + " • Churn reduction successful\n", + "\n", + "Clean, data-focused design.\n", + "\"\"\"\n", + "\n", + "ppt_response, ppt_results = create_skills_message(\n", + " client,\n", + " ppt_pipeline_prompt,\n", + " [{\"type\": \"anthropic\", \"skill_id\": \"pptx\", \"version\": \"latest\"}],\n", + " prefix=\"pipeline_2_summary_\",\n", + " show_token_usage=False,\n", + ")\n", + "\n", + "pipeline_tokens[\"input\"] += ppt_response.usage.input_tokens\n", + "pipeline_tokens[\"output\"] += ppt_response.usage.output_tokens\n", + "print(\n", + " f\"✓ PowerPoint created - Tokens: {ppt_response.usage.input_tokens} in, {ppt_response.usage.output_tokens} out\"\n", + ")\n", + "\n", + "# Step 3: Create PDF Documentation\n", + "print(\"\\nStep 3/3: Creating PDF documentation...\")\n", + "\n", + "pdf_pipeline_prompt = \"\"\"\n", + "Create a PDF document summarizing the quarterly reporting pipeline:\n", + "\n", + "AUTOMATED REPORTING PIPELINE\n", + "Q4 2024 Results Documentation\n", + "\n", + "EXECUTIVE SUMMARY\n", + "This document summarizes the Q4 2024 business metrics generated through\n", + "our automated reporting pipeline.\n", + "\n", + "KEY METRICS\n", + "- Revenue: $3.2M (15% QoQ growth)\n", + "- Customer Base: 850 active customers\n", + "- Gross Margin: 72%\n", + "- Churn Rate: 2.8% (improved from 3.5%)\n", + "\n", + "PIPELINE COMPONENTS\n", + "1. Data Processing: Quarterly metrics analyzed in Excel\n", + "2. Visualization: Key insights presented in PowerPoint\n", + "3. Documentation: Formal report generated in PDF\n", + "\n", + "AUTOMATION BENEFITS\n", + "• Reduced reporting time by 90%\n", + "• Consistent format and quality\n", + "• Eliminated manual errors\n", + "• Scalable to multiple reports\n", + "\n", + "NEXT STEPS\n", + "- Expand pipeline to include predictive analytics\n", + "- Add automated email distribution\n", + "- Implement real-time data feeds\n", + "\n", + "Generated: January 2025\n", + "Pipeline Version: 1.0\n", + "\n", + "Format as a professional technical document.\n", + "\"\"\"\n", + "\n", + "pdf_response, pdf_results = create_skills_message(\n", + " client,\n", + " pdf_pipeline_prompt,\n", + " [{\"type\": \"anthropic\", \"skill_id\": \"pdf\", \"version\": \"latest\"}],\n", + " prefix=\"pipeline_3_documentation_\",\n", + " show_token_usage=False,\n", + ")\n", + "\n", + "pipeline_tokens[\"input\"] += pdf_response.usage.input_tokens\n", + "pipeline_tokens[\"output\"] += pdf_response.usage.output_tokens\n", + "print(\n", + " f\"✓ PDF created - Tokens: {pdf_response.usage.input_tokens} in, {pdf_response.usage.output_tokens} out\"\n", + ")\n", + "\n", + "# Pipeline Summary\n", + "print(\"\\n\" + \"=\" * 60)\n", + "print(\"🎯 PIPELINE COMPLETE!\")\n", + "print(\"=\" * 60)\n", + "\n", + "print(\"\\n📊 Pipeline Token Usage Summary:\")\n", + "print(f\" Total Input Tokens: {pipeline_tokens['input']:,}\")\n", + "print(f\" Total Output Tokens: {pipeline_tokens['output']:,}\")\n", + "print(f\" Total Tokens: {pipeline_tokens['input'] + pipeline_tokens['output']:,}\")\n", + "print(\n", + " f\" Average per document: {(pipeline_tokens['input'] + pipeline_tokens['output']) // 3:,}\"\n", + ")\n", + "\n", + "print(\"\\n📁 Generated Files:\")\n", + "all_results = excel_results + ppt_results + pdf_results\n", + "for i, result in enumerate(all_results, 1):\n", + " if result[\"success\"]:\n", + " print(\n", + " f\" {i}. {os.path.basename(result['output_path'])} ({result['size'] / 1024:.1f} KB)\"\n", + " )\n", + "\n", + "print(\"\\n✅ Automated reporting pipeline executed successfully!\")\n", + "print(\" All three documents created and linked in workflow.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "## Summary & Next Steps\n\n### What We've Accomplished\n\nIn this notebook, you've learned how to:\n\n✅ **Financial Dashboard Creation**\n- Built multi-sheet Excel models with formulas and charts\n- Generated executive PowerPoint presentations\n- Created professional PDF reports\n\n✅ **Portfolio Analysis**\n- Developed portfolio analytics workbooks\n- Created investment committee presentations\n- Implemented risk metrics and rebalancing tools\n\n✅ **Automated Pipelines**\n- Chained multiple document formats\n- Optimized token usage\n- Built production-ready patterns\n\n### Key Takeaways\n\n1. **Skills dramatically simplify financial document creation** - What would take hours manually takes minutes\n2. **Token efficiency is excellent** - Skills use ~90% fewer tokens than manual instructions\n3. **Quality is professional-grade** - Documents are immediately usable in business contexts\n4. **Automation is straightforward** - Pipeline patterns enable complex workflows\n\n### Continue Your Learning\n\n📚 **Next: [Notebook 3 - Custom Skills Development](03_skills_custom_development.ipynb)**\n- Build your own specialized financial skills\n- Create company-specific templates\n- Implement advanced automation\n\n### Try These Experiments\n\n1. **Modify the financial dashboard** to include your own metrics\n2. **Create a custom portfolio** with different asset classes\n3. **Build a pipeline** for your specific reporting needs\n4. **Experiment with complexity** to understand generation times\n5. **Track token usage** across different document types\n\n### Resources\n\n- [Claude API Documentation](https://docs.anthropic.com/en/api/messages)\n- [Skills Documentation](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview)\n- [Best Practices](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices)\n- [Files API Reference](https://docs.claude.com/en/api/files-content)" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/skills/notebooks/03_skills_custom_development.ipynb b/skills/notebooks/03_skills_custom_development.ipynb new file mode 100644 index 00000000..99a5c99a --- /dev/null +++ b/skills/notebooks/03_skills_custom_development.ipynb @@ -0,0 +1,761 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Building Custom Skills for Claude\n", + "\n", + "Learn how to create, deploy, and manage custom skills to extend Claude's capabilities with your organization's specialized knowledge and workflows." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table of Contents\n", + "\n", + "1. [Introduction & Setup](#introduction)\n", + "2. [Understanding Custom Skills Architecture](#architecture)\n", + "3. [Example 1: Financial Ratio Calculator](#financial-ratio)\n", + "4. [Example 2: Company Brand Guidelines](#brand-guidelines)\n", + "5. [Example 3: Financial Modeling Suite](#financial-modeling)\n", + "6. [Skill Management & Versioning](#management)\n", + "7. [Best Practices & Production Tips](#best-practices)\n", + "8. [Troubleshooting](#troubleshooting)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Introduction & Setup {#introduction}\n", + "\n", + "### What are Custom Skills?\n", + "\n", + "**Custom skills** are specialized expertise packages you create to teach Claude your organization's unique workflows, domain knowledge, and best practices. Unlike Anthropic's pre-built skills (Excel, PowerPoint, PDF), custom skills allow you to:\n", + "\n", + "- **Codify organizational knowledge** - Capture your team's specific methodologies\n", + "- **Ensure consistency** - Apply the same standards across all interactions\n", + "- **Automate complex workflows** - Chain together multi-step processes\n", + "- **Maintain intellectual property** - Keep proprietary methods secure\n", + "\n", + "### Key Benefits\n", + "\n", + "| Benefit | Description |\n", + "|---------|-------------|\n", + "| **Expertise at Scale** | Deploy specialized knowledge to every Claude interaction |\n", + "| **Version Control** | Track changes and roll back if needed |\n", + "| **Composability** | Combine multiple skills for complex tasks |\n", + "| **Privacy** | Your skills remain private to your organization |\n", + "\n", + "### Prerequisites\n", + "\n", + "Before starting, ensure you have:\n", + "- Completed [Notebook 1: Introduction to Skills](01_skills_introduction.ipynb)\n", + "- An Anthropic API key with Skills beta access\n", + "- Python environment with the local SDK installed" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Environment Setup\n", + "\n", + "Let's set up our environment and import necessary libraries:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "import json\nimport os\nimport shutil\nimport sys\nfrom datetime import datetime\nfrom pathlib import Path\nfrom typing import Any, Optional\n\n# Add parent directory for imports\nsys.path.insert(0, str(Path.cwd().parent))\n\nfrom anthropic import Anthropic\nfrom anthropic.lib import files_from_dir\nfrom dotenv import load_dotenv\n\n# Import our utilities\nfrom file_utils import (\n download_all_files,\n extract_file_ids,\n get_file_info,\n print_download_summary,\n)\n\n# We'll create skill_utils later in this notebook\n# from skill_utils import (\n# create_skill,\n# list_skills,\n# delete_skill,\n# test_skill\n# )\n\n# Load environment variables\nload_dotenv(Path.cwd().parent / \".env\")\n\nAPI_KEY = os.getenv(\"ANTHROPIC_API_KEY\")\nMODEL = os.getenv(\"ANTHROPIC_MODEL\", \"claude-sonnet-4-5-20250929\")\n\nif not API_KEY:\n raise ValueError(\n \"ANTHROPIC_API_KEY not found. \"\n \"Copy ../.env.example to ../.env and add your API key.\"\n )\n\n# Initialize client with Skills beta\nclient = Anthropic(\n api_key=API_KEY, default_headers={\"anthropic-beta\": \"skills-2025-10-02\"}\n)\n\n# Setup directories\nSKILLS_DIR = Path.cwd().parent / \"custom_skills\"\nOUTPUT_DIR = Path.cwd().parent / \"outputs\"\nOUTPUT_DIR.mkdir(exist_ok=True)\n\nprint(\"✓ API key loaded\")\nprint(f\"✓ Using model: {MODEL}\")\nprint(f\"✓ Custom skills directory: {SKILLS_DIR}\")\nprint(f\"✓ Output directory: {OUTPUT_DIR}\")\nprint(\"\\n📝 Skills beta header configured for skill management\")" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "## 2. Understanding Custom Skills Architecture {#architecture}\n\n### Skill Structure\n\nEvery custom skill follows this directory structure:\n\n```\nskill_name/\n├── SKILL.md # REQUIRED: Instructions with YAML frontmatter\n├── *.md # Optional: Any additional .md files (documentation, guides)\n├── scripts/ # Optional: Executable code\n│ ├── process.py\n│ └── utils.js\n└── resources/ # Optional: Templates, data files\n └── template.xlsx\n```\n\n**Important:** \n- **SKILL.md is the ONLY required file** - everything else is optional\n- **Multiple .md files allowed** - You can have any number of markdown files in the top-level folder\n- **All .md files are loaded** - Not just SKILL.md and REFERENCE.md, but any .md file you include\n- **Organize as needed** - Use multiple .md files to structure complex documentation\n\n📖 Read our engineering blog post on [Equipping agents for the real world with Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills)\n\n### Skills Are Not Just Markdown\n\n![Skills Can Include Scripts and Files](../assets/not-just-markdown.png)\n\nSkills can bundle various file types:\n- **Markdown files**: Instructions and documentation (SKILL.md, REFERENCE.md, etc.)\n- **Scripts**: Python, JavaScript, or other executable code for complex operations\n- **Templates**: Pre-built files that can be customized (Excel templates, document templates)\n- **Resources**: Supporting data files, configuration, or assets\n\n### SKILL.md Requirements\n\nThe `SKILL.md` file must include:\n\n1. **YAML Frontmatter** (name: 64 chars, description: 1024 chars)\n - `name`: Lowercase alphanumeric with hyphens (required)\n - `description`: Brief description of what the skill does (required)\n\n2. **Instructions** (markdown format)\n - Clear guidance for Claude\n - Examples of usage\n - Any constraints or rules\n - Recommended: Keep under 5,000 tokens\n\n### Additional Documentation Files\n\nYou can include multiple markdown files for better organization:\n\n```\nskill_name/\n├── SKILL.md # Main instructions (required)\n├── REFERENCE.md # API reference (optional)\n├── EXAMPLES.md # Usage examples (optional)\n├── TROUBLESHOOTING.md # Common issues (optional)\n└── CHANGELOG.md # Version history (optional)\n```\n\nAll `.md` files in the root directory will be available to Claude when the skill is loaded.\n\n### Bundled Files Example\n\n![Bundled Files in Skills](../assets/skills-bundled-files.png)\n\nThis example shows how Skills can bundle multiple files:\n- **SKILL.md**: Contains the main instructions with colors, typography, and sections\n- **slide-decks.md**: Additional documentation for specific use cases\n- **Scripts and resources**: Can be referenced and used during skill execution\n\n### Progressive Disclosure\n\nSkills load in three stages to optimize token usage:\n\n| Stage | Content | Token Cost | When Loaded |\n|-------|---------|------------|-------------|\n| **1. Metadata** | Name & description | name: 64 chars, description: 1024 chars | Always visible |\n| **2. Instructions** | All .md files | <5,000 tokens recommended | When relevant |\n| **3. Resources** | Scripts & files | As needed | During execution |\n\n### API Workflow\n\n```python\n# 1. Create skill\nskill = client.beta.skills.create(\n display_title=\"My Skill\",\n files=files_from_dir(\"path/to/skill\")\n)\n\n# 2. Use in messages\nresponse = client.beta.messages.create(\n container={\n \"skills\": [{\n \"type\": \"custom\",\n \"skill_id\": skill.id,\n \"version\": \"latest\"\n }]\n },\n # ... rest of message parameters\n)\n```\n\n### Best Practices\n\nFor detailed guidance on skill creation and best practices, see:\n- [Claude Skills Best Practices](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices)\n- [Skills Documentation](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview)" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create Skill Utility Functions\n", + "\n", + "Let's create helper functions for skill management:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "def create_skill(\n client: Anthropic, skill_path: str, display_title: str\n) -> dict[str, Any]:\n \"\"\"\n Create a new custom skill from a directory.\n\n Args:\n client: Anthropic client instance\n skill_path: Path to skill directory\n display_title: Human-readable skill name\n\n Returns:\n Dictionary with skill_id, version, and metadata\n \"\"\"\n try:\n # Create skill using files_from_dir\n skill = client.beta.skills.create(\n display_title=display_title, files=files_from_dir(skill_path)\n )\n\n return {\n \"success\": True,\n \"skill_id\": skill.id,\n \"display_title\": skill.display_title,\n \"latest_version\": skill.latest_version,\n \"created_at\": skill.created_at,\n \"source\": skill.source,\n }\n except Exception as e:\n return {\"success\": False, \"error\": str(e)}\n\n\ndef list_custom_skills(client: Anthropic) -> list[dict[str, Any]]:\n \"\"\"\n List all custom skills in the workspace.\n\n Returns:\n List of skill dictionaries\n \"\"\"\n try:\n skills_response = client.beta.skills.list(source=\"custom\")\n\n skills = []\n for skill in skills_response.data:\n skills.append(\n {\n \"skill_id\": skill.id,\n \"display_title\": skill.display_title,\n \"latest_version\": skill.latest_version,\n \"created_at\": skill.created_at,\n \"updated_at\": skill.updated_at,\n }\n )\n\n return skills\n except Exception as e:\n print(f\"Error listing skills: {e}\")\n return []\n\n\ndef delete_skill(client: Anthropic, skill_id: str) -> bool:\n \"\"\"\n Delete a custom skill and all its versions.\n\n Args:\n client: Anthropic client\n skill_id: ID of skill to delete\n\n Returns:\n True if successful, False otherwise\n \"\"\"\n try:\n # First delete all versions\n versions = client.beta.skills.versions.list(skill_id=skill_id)\n\n for version in versions.data:\n client.beta.skills.versions.delete(\n skill_id=skill_id, version=version.version\n )\n\n # Then delete the skill itself\n client.beta.skills.delete(skill_id)\n return True\n\n except Exception as e:\n print(f\"Error deleting skill: {e}\")\n return False\n\n\ndef test_skill(\n client: Anthropic,\n skill_id: str,\n test_prompt: str,\n model: str = \"claude-sonnet-4-5-20250929\",\n) -> Any:\n \"\"\"\n Test a custom skill with a prompt.\n\n Args:\n client: Anthropic client\n skill_id: ID of skill to test\n test_prompt: Prompt to test the skill\n model: Model to use for testing\n\n Returns:\n Response from Claude\n \"\"\"\n response = client.beta.messages.create(\n model=model,\n max_tokens=4096,\n container={\n \"skills\": [{\"type\": \"custom\", \"skill_id\": skill_id, \"version\": \"latest\"}]\n },\n tools=[{\"type\": \"code_execution_20250825\", \"name\": \"code_execution\"}],\n messages=[{\"role\": \"user\", \"content\": test_prompt}],\n betas=[\n \"code-execution-2025-08-25\",\n \"files-api-2025-04-14\",\n \"skills-2025-10-02\",\n ],\n )\n\n return response\n\n\nprint(\"✓ Skill utility functions defined\")\nprint(\" - create_skill()\")\nprint(\" - list_custom_skills()\")\nprint(\" - delete_skill()\")\nprint(\" - test_skill()\")" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Check Existing Custom Skills\n", + "\n", + "Let's see if any custom skills already exist in your workspace:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ⚠️ Important: Clean Up Existing Skills Before Starting\n", + "\n", + "If you're re-running this notebook, you may have skills from a previous session. Skills cannot have duplicate display titles, so you have three options:\n", + "\n", + "1. **Delete existing skills** (recommended for testing) - Clean slate approach\n", + "2. **Use different display titles** - Add timestamps or version numbers to names\n", + "3. **Update existing skills with new versions** - See [Skill Management & Versioning](#management) section\n", + "\n", + "Let's check for and optionally clean up existing skills:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check for existing skills that might conflict\n", + "existing_skills = list_custom_skills(client)\n", + "skill_titles_to_create = [\n", + " \"Financial Ratio Analyzer\",\n", + " \"Corporate Brand Guidelines\",\n", + " \"Financial Modeling Suite\",\n", + "]\n", + "conflicting_skills = []\n", + "\n", + "if existing_skills:\n", + " print(f\"Found {len(existing_skills)} existing custom skill(s):\")\n", + " for skill in existing_skills:\n", + " print(f\" - {skill['display_title']} (ID: {skill['skill_id']})\")\n", + " if skill[\"display_title\"] in skill_titles_to_create:\n", + " conflicting_skills.append(skill)\n", + "\n", + " if conflicting_skills:\n", + " print(\n", + " f\"\\n⚠️ Found {len(conflicting_skills)} skill(s) that will conflict with this notebook:\"\n", + " )\n", + " for skill in conflicting_skills:\n", + " print(f\" - {skill['display_title']} (ID: {skill['skill_id']})\")\n", + "\n", + " print(\"\\n\" + \"=\" * 70)\n", + " print(\"To clean up these skills and start fresh, uncomment and run:\")\n", + " print(\"=\" * 70)\n", + " print(\"\\n# UNCOMMENT THE LINES BELOW TO DELETE CONFLICTING SKILLS:\")\n", + " print(\"# for skill in conflicting_skills:\")\n", + " print(\"# if delete_skill(client, skill['skill_id']):\")\n", + " print(\"# print(f\\\"✅ Deleted: {skill['display_title']}\\\")\")\n", + " print(\"# else:\")\n", + " print(\"# print(f\\\"❌ Failed to delete: {skill['display_title']}\\\")\")\n", + "\n", + " # for skill in conflicting_skills:\n", + " # if delete_skill(client, skill['skill_id']):\n", + " # print(f\"✅ Deleted: {skill['display_title']}\")\n", + " # else:\n", + " # print(f\"❌ Failed to delete: {skill['display_title']}\")\n", + " else:\n", + " print(\"\\n✅ No conflicting skills found. Ready to proceed!\")\n", + "else:\n", + " print(\"✅ No existing custom skills found. Ready to create new ones!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Example 1: Financial Ratio Calculator {#financial-ratio}\n", + "\n", + "Let's create our first custom skill - a financial ratio calculator that can analyze company financial health.\n", + "\n", + "### Skill Overview\n", + "\n", + "The **Financial Ratio Calculator** skill will:\n", + "- Calculate key financial ratios (ROE, P/E, Current Ratio, etc.)\n", + "- Interpret ratios with industry context\n", + "- Generate formatted reports\n", + "- Work with various data formats (CSV, JSON, text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Upload the Financial Analyzer Skill\n", + "\n", + "Now let's upload our financial analyzer skill to Claude:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Upload the Financial Analyzer skill\nfinancial_skill_path = SKILLS_DIR / \"analyzing-financial-statements\"\n\nif financial_skill_path.exists():\n print(\"Uploading Financial Analyzer skill...\")\n result = create_skill(client, str(financial_skill_path), \"Financial Ratio Analyzer\")\n\n if result[\"success\"]:\n financial_skill_id = result[\"skill_id\"]\n print(\"✅ Skill uploaded successfully!\")\n print(f\" Skill ID: {financial_skill_id}\")\n print(f\" Version: {result['latest_version']}\")\n print(f\" Created: {result['created_at']}\")\n else:\n print(f\"❌ Upload failed: {result['error']}\")\n if \"cannot reuse an existing display_title\" in str(result[\"error\"]):\n print(\"\\n💡 Solution: A skill with this name already exists.\")\n print(\n \" Run the 'Clean Up Existing Skills' cell above to delete it first,\"\n )\n print(\" or change the display_title to something unique.\")\nelse:\n print(f\"⚠️ Skill directory not found: {financial_skill_path}\")\n print(\n \"Please ensure the custom_skills directory contains the analyzing-financial-statements folder.\"\n )" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test the Financial Analyzer Skill\n", + "\n", + "Let's test the skill with sample financial data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Test the Financial Analyzer skill\n", + "if \"financial_skill_id\" in locals():\n", + " test_prompt = \"\"\"\n", + " Calculate financial ratios for this company:\n", + "\n", + " Income Statement:\n", + " - Revenue: $1,000M\n", + " - EBITDA: $200M\n", + " - Net Income: $120M\n", + "\n", + " Balance Sheet:\n", + " - Total Assets: $2,000M\n", + " - Current Assets: $500M\n", + " - Current Liabilities: $300M\n", + " - Total Debt: $400M\n", + " - Shareholders Equity: $1,200M\n", + "\n", + " Market Data:\n", + " - Share Price: $50\n", + " - Shares Outstanding: 100M\n", + "\n", + " Please calculate key ratios and provide analysis.\n", + " \"\"\"\n", + "\n", + " print(\"Testing Financial Analyzer skill...\")\n", + " response = test_skill(client, financial_skill_id, test_prompt)\n", + "\n", + " # Print response\n", + " for content in response.content:\n", + " if content.type == \"text\":\n", + " print(content.text)\n", + "else:\n", + " print(\"⚠️ Please upload the Financial Analyzer skill first (run the previous cell)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Example 2: Company Brand Guidelines {#brand-guidelines}\n", + "\n", + "Now let's create a skill that ensures all documents follow corporate brand standards.\n", + "\n", + "### Skill Overview\n", + "\n", + "The **Brand Guidelines** skill will:\n", + "- Apply consistent colors, fonts, and layouts\n", + "- Ensure logo placement and usage\n", + "- Maintain professional tone and messaging\n", + "- Work across all document types (Excel, PowerPoint, PDF)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Upload the Brand Guidelines skill\nbrand_skill_path = SKILLS_DIR / \"applying-brand-guidelines\"\n\nif brand_skill_path.exists():\n print(\"Uploading Brand Guidelines skill...\")\n result = create_skill(client, str(brand_skill_path), \"Corporate Brand Guidelines\")\n\n if result[\"success\"]:\n brand_skill_id = result[\"skill_id\"]\n print(\"✅ Skill uploaded successfully!\")\n print(f\" Skill ID: {brand_skill_id}\")\n print(f\" Version: {result['latest_version']}\")\n else:\n print(f\"❌ Upload failed: {result['error']}\")\n if \"cannot reuse an existing display_title\" in str(result[\"error\"]):\n print(\"\\n💡 Solution: A skill with this name already exists.\")\n print(\n \" Run the 'Clean Up Existing Skills' cell above to delete it first,\"\n )\n print(\" or change the display_title to something unique.\")\nelse:\n print(f\"⚠️ Skill directory not found: {brand_skill_path}\")" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test Brand Guidelines with Document Creation\n", + "\n", + "Let's test the brand skill by creating a branded PowerPoint presentation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Test Brand Guidelines skill with PowerPoint creation\n", + "if \"brand_skill_id\" in locals():\n", + " # Combine brand skill with Anthropic's pptx skill\n", + " response = client.beta.messages.create(\n", + " model=MODEL,\n", + " max_tokens=4096,\n", + " container={\n", + " \"skills\": [\n", + " {\"type\": \"custom\", \"skill_id\": brand_skill_id, \"version\": \"latest\"},\n", + " {\"type\": \"anthropic\", \"skill_id\": \"pptx\", \"version\": \"latest\"},\n", + " ]\n", + " },\n", + " tools=[{\"type\": \"code_execution_20250825\", \"name\": \"code_execution\"}],\n", + " messages=[\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"\"\"Create a 3-slide PowerPoint presentation following Acme Corporation brand guidelines:\n", + "\n", + " Slide 1: Title slide for \"Q4 2025 Results\"\n", + " Slide 2: Revenue Overview with a chart showing Q1-Q4 growth\n", + " Slide 3: Key Achievements (3 bullet points)\n", + "\n", + " Apply all brand colors, fonts, and formatting standards.\n", + " \"\"\",\n", + " }\n", + " ],\n", + " betas=[\n", + " \"code-execution-2025-08-25\",\n", + " \"files-api-2025-04-14\",\n", + " \"skills-2025-10-02\",\n", + " ],\n", + " )\n", + "\n", + " print(\"Response from Claude:\")\n", + " for content in response.content:\n", + " if content.type == \"text\":\n", + " print(\n", + " content.text[:500] + \"...\" if len(content.text) > 500 else content.text\n", + " )\n", + "\n", + " # Download generated file\n", + " file_ids = extract_file_ids(response)\n", + " if file_ids:\n", + " results = download_all_files(\n", + " client, response, output_dir=str(OUTPUT_DIR), prefix=\"branded_\"\n", + " )\n", + " print_download_summary(results)\n", + "else:\n", + " print(\"⚠️ Please upload the Brand Guidelines skill first\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Example 3: Financial Modeling Suite {#financial-modeling}\n", + "\n", + "Let's create our most advanced skill - a comprehensive financial modeling suite for valuation and risk analysis.\n", + "\n", + "### Skill Overview\n", + "\n", + "The **Financial Modeling Suite** skill provides:\n", + "- **DCF Valuation**: Complete discounted cash flow models\n", + "- **Sensitivity Analysis**: Test impact of variables on valuation\n", + "- **Monte Carlo Simulation**: Risk modeling with probability distributions\n", + "- **Scenario Planning**: Best/base/worst case analysis\n", + "\n", + "This demonstrates a multi-file skill with complex calculations and professional-grade financial modeling." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Upload the Financial Modeling Suite \n", + "\n", + "First, upload the financial modeling skill:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Upload the Financial Modeling Suite skill\nmodeling_skill_path = SKILLS_DIR / \"creating-financial-models\"\n\nif modeling_skill_path.exists():\n print(\"Uploading Financial Modeling Suite skill...\")\n result = create_skill(client, str(modeling_skill_path), \"Financial Modeling Suite\")\n\n if result[\"success\"]:\n modeling_skill_id = result[\"skill_id\"]\n print(\"✅ Skill uploaded successfully!\")\n print(f\" Skill ID: {modeling_skill_id}\")\n print(f\" Version: {result['latest_version']}\")\n print(\"\\nThis skill includes:\")\n print(\" - DCF valuation model (dcf_model.py)\")\n print(\" - Sensitivity analysis framework (sensitivity_analysis.py)\")\n print(\" - Monte Carlo simulation capabilities\")\n print(\" - Scenario planning tools\")\n else:\n print(f\"❌ Upload failed: {result['error']}\")\nelse:\n print(f\"⚠️ Skill directory not found: {modeling_skill_path}\")\n print(\n \"Please ensure the custom_skills directory contains the creating-financial-models folder.\"\n )" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test the Financial Modeling Suite\n", + "\n", + "Let's test the advanced modeling capabilities with a DCF valuation request:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Test the Financial Modeling Suite with a DCF valuation\n", + "if \"modeling_skill_id\" in locals():\n", + " dcf_test_prompt = \"\"\"\n", + " Perform a DCF valuation for TechCorp with the following data:\n", + "\n", + " Historical Financials (Last 3 Years):\n", + " - Revenue: $500M, $600M, $750M\n", + " - EBITDA Margin: 25%, 27%, 30%\n", + " - CapEx: $50M, $55M, $60M\n", + " - Working Capital: 15% of revenue\n", + "\n", + " Projections:\n", + " - Revenue growth: 20% for years 1-3, then declining to 5% by year 5\n", + " - EBITDA margin expanding to 35% by year 5\n", + " - Terminal growth rate: 3%\n", + "\n", + " Market Assumptions:\n", + " - WACC: 10%\n", + " - Tax rate: 25%\n", + " - Current net debt: $200M\n", + " - Shares outstanding: 100M\n", + "\n", + " Please create a complete DCF model with sensitivity analysis on WACC and terminal growth.\n", + " Generate an Excel file with the full model including:\n", + " 1. Revenue projections\n", + " 2. Free cash flow calculations\n", + " 3. Terminal value\n", + " 4. Enterprise value to equity value bridge\n", + " 5. Sensitivity table\n", + " \"\"\"\n", + "\n", + " print(\"Testing Financial Modeling Suite with DCF valuation...\")\n", + " print(\"=\" * 70)\n", + " print(\"\\n⏱️ Note: Complex financial model generation may take 1-2 minutes.\\n\")\n", + "\n", + " response = client.beta.messages.create(\n", + " model=MODEL,\n", + " max_tokens=4096,\n", + " container={\n", + " \"skills\": [\n", + " {\"type\": \"custom\", \"skill_id\": modeling_skill_id, \"version\": \"latest\"},\n", + " {\"type\": \"anthropic\", \"skill_id\": \"xlsx\", \"version\": \"latest\"},\n", + " ]\n", + " },\n", + " tools=[{\"type\": \"code_execution_20250825\", \"name\": \"code_execution\"}],\n", + " messages=[{\"role\": \"user\", \"content\": dcf_test_prompt}],\n", + " betas=[\n", + " \"code-execution-2025-08-25\",\n", + " \"files-api-2025-04-14\",\n", + " \"skills-2025-10-02\",\n", + " ],\n", + " )\n", + "\n", + " # Print Claude's response\n", + " for content in response.content:\n", + " if content.type == \"text\":\n", + " # Print first 800 characters to keep output manageable\n", + " text = content.text\n", + " if len(text) > 800:\n", + " print(text[:800] + \"\\n\\n[... Output truncated for brevity ...]\")\n", + " else:\n", + " print(text)\n", + "\n", + " # Download the DCF model if generated\n", + " file_ids = extract_file_ids(response)\n", + " if file_ids:\n", + " print(\"\\n\" + \"=\" * 70)\n", + " print(\"Downloading generated DCF model...\")\n", + " results = download_all_files(\n", + " client, response, output_dir=str(OUTPUT_DIR), prefix=\"dcf_model_\"\n", + " )\n", + " print_download_summary(results)\n", + " print(\"\\n💡 Open the Excel file to explore the complete DCF valuation model!\")\n", + "else:\n", + " print(\n", + " \"⚠️ Please upload the Financial Modeling Suite skill first (run the previous cell)\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Skill Management & Versioning {#management}\n", + "\n", + "Managing skills over time requires understanding versioning, updates, and lifecycle management.\n", + "\n", + "### Listing Your Skills\n", + "\n", + "Get an overview of all custom skills in your workspace:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# List all your custom skills\n", + "my_skills = list_custom_skills(client)\n", + "\n", + "if my_skills:\n", + " print(f\"You have {len(my_skills)} custom skill(s):\\n\")\n", + " print(\"=\" * 70)\n", + " for i, skill in enumerate(my_skills, 1):\n", + " print(f\"\\n{i}. {skill['display_title']}\")\n", + " print(f\" Skill ID: {skill['skill_id']}\")\n", + " print(f\" Current Version: {skill['latest_version']}\")\n", + " print(f\" Created: {skill['created_at']}\")\n", + " if skill.get(\"updated_at\"):\n", + " print(f\" Last Updated: {skill['updated_at']}\")\n", + " print(\"\\n\" + \"=\" * 70)\n", + "else:\n", + " print(\"No custom skills found in your workspace.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Creating New Versions\n", + "\n", + "Skills support versioning to maintain history and enable rollback. Let's make an enhancement to our Financial Analyzer skill and create a new version.\n", + "\n", + "#### Step 1: Enhance the Financial Analyzer\n", + "\n", + "We'll add **healthcare industry** benchmarks to make our skill more versatile. This is a real-world scenario where you'd expand a skill's capabilities based on user needs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Add healthcare industry benchmarks to the Financial Analyzer\n# This demonstrates a realistic skill enhancement scenario\n\nif \"financial_skill_id\" in locals():\n # Read the current interpret_ratios.py file\n interpret_file_path = SKILLS_DIR / \"analyzing-financial-statements\" / \"interpret_ratios.py\"\n\n with open(interpret_file_path) as f:\n content = f.read()\n\n # Add healthcare benchmarks after the 'manufacturing' section\n healthcare_benchmarks = \"\"\" },\n 'healthcare': {\n 'current_ratio': {'excellent': 2.3, 'good': 1.8, 'acceptable': 1.4, 'poor': 1.0},\n 'debt_to_equity': {'excellent': 0.3, 'good': 0.6, 'acceptable': 1.0, 'poor': 1.8},\n 'roe': {'excellent': 0.22, 'good': 0.16, 'acceptable': 0.11, 'poor': 0.07},\n 'gross_margin': {'excellent': 0.65, 'good': 0.45, 'acceptable': 0.30, 'poor': 0.20},\n 'pe_ratio': {'undervalued': 18, 'fair': 28, 'growth': 40, 'expensive': 55}\n \"\"\"\n\n # Find the position after manufacturing section and before the closing brace\n insert_pos = content.find(\" }\\n }\") # Find the end of the BENCHMARKS dict\n\n if insert_pos != -1:\n # Insert the healthcare benchmarks\n new_content = (\n content[:insert_pos] + healthcare_benchmarks + content[insert_pos:]\n )\n\n # Save the enhanced file\n with open(interpret_file_path, \"w\") as f:\n f.write(new_content)\n\n print(\"✅ Enhanced Financial Analyzer with healthcare industry benchmarks\")\n print(\"\\nChanges made:\")\n print(\" - Added healthcare industry to BENCHMARKS\")\n print(\" - Includes specific thresholds for:\")\n print(\" • Current ratio (liquidity)\")\n print(\" • Debt-to-equity (leverage)\")\n print(\" • ROE (profitability)\")\n print(\" • Gross margin\")\n print(\" • P/E ratio (valuation)\")\n print(\n \"\\n📝 Now we can create a new version of the skill with this enhancement!\"\n )\n else:\n print(\"⚠️ Could not find the correct position to insert healthcare benchmarks\")\n print(\"The file structure may have changed.\")\nelse:\n print(\"⚠️ Please upload the Financial Analyzer skill first (run cells in Section 3)\")" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Step 2: Create a New Version\n", + "\n", + "Now that we've enhanced our skill, let's create a new version to track this change:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Create a new version of the enhanced Financial Analyzer skill\ndef create_skill_version(client: Anthropic, skill_id: str, skill_path: str):\n \"\"\"Create a new version of an existing skill.\"\"\"\n try:\n version = client.beta.skills.versions.create(\n skill_id=skill_id, files=files_from_dir(skill_path)\n )\n return {\n \"success\": True,\n \"version\": version.version,\n \"created_at\": version.created_at,\n }\n except Exception as e:\n return {\"success\": False, \"error\": str(e)}\n\n\n# Create the new version with our healthcare enhancement\nif \"financial_skill_id\" in locals():\n print(\"Creating new version of Financial Analyzer with healthcare benchmarks...\")\n\n result = create_skill_version(\n client, financial_skill_id, str(SKILLS_DIR / \"analyzing-financial-statements\")\n )\n\n if result[\"success\"]:\n print(\"✅ New version created successfully!\")\n print(f\" Version: {result['version']}\")\n print(f\" Created: {result['created_at']}\")\n print(\"\\n📊 Version History:\")\n print(\" v1: Original skill with tech, retail, financial, manufacturing\")\n print(f\" v{result['version']}: Enhanced with healthcare industry benchmarks\")\n else:\n print(f\"❌ Version creation failed: {result['error']}\")\nelse:\n print(\n \"⚠️ Please run the previous cells to upload the skill and make enhancements first\"\n )" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Step 3: Test the New Version\n", + "\n", + "Let's verify our enhancement works by analyzing a healthcare company:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Test the enhanced skill with healthcare industry data\n", + "if \"financial_skill_id\" in locals():\n", + " healthcare_test_prompt = \"\"\"\n", + " Analyze this healthcare company using the healthcare industry benchmarks:\n", + "\n", + " Company: MedTech Solutions (Healthcare Industry)\n", + "\n", + " Income Statement:\n", + " - Revenue: $800M\n", + " - EBITDA: $320M\n", + " - Net Income: $160M\n", + "\n", + " Balance Sheet:\n", + " - Total Assets: $1,200M\n", + " - Current Assets: $400M\n", + " - Current Liabilities: $200M\n", + " - Total Debt: $300M\n", + " - Shareholders Equity: $700M\n", + "\n", + " Market Data:\n", + " - Share Price: $75\n", + " - Shares Outstanding: 50M\n", + "\n", + " Please calculate key ratios and provide healthcare-specific analysis.\n", + " \"\"\"\n", + "\n", + " print(\"Testing enhanced Financial Analyzer with healthcare company...\")\n", + " print(\"=\" * 70)\n", + "\n", + " response = test_skill(client, financial_skill_id, healthcare_test_prompt, MODEL)\n", + "\n", + " # Print Claude's analysis\n", + " for content in response.content:\n", + " if content.type == \"text\":\n", + " # Print first 1000 characters to keep output manageable\n", + " text = content.text\n", + " if len(text) > 1000:\n", + " print(text[:1000] + \"\\n\\n[... Output truncated for brevity ...]\")\n", + " else:\n", + " print(text)\n", + "\n", + " print(\n", + " \"\\n✅ The skill now recognizes 'healthcare' as an industry and applies specific benchmarks!\"\n", + " )\n", + "else:\n", + " print(\"⚠️ Please run the previous cells to create the enhanced version first\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Cleanup: Managing Your Skills\n", + "\n", + "When you're done testing or need to clean up your workspace, you can selectively remove skills. Let's review what we've created and provide options for cleanup:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Comprehensive skill cleanup with detailed reporting\n", + "def review_and_cleanup_skills(client, dry_run=True):\n", + " \"\"\"\n", + " Review all skills and optionally clean up the ones created in this notebook.\n", + "\n", + " Args:\n", + " client: Anthropic client\n", + " dry_run: If True, only show what would be deleted without actually deleting\n", + " \"\"\"\n", + " # Get all current skills\n", + " all_skills = list_custom_skills(client)\n", + "\n", + " # Skills we created in this notebook\n", + " notebook_skill_names = [\n", + " \"Financial Ratio Analyzer\",\n", + " \"Corporate Brand Guidelines\",\n", + " \"Financial Modeling Suite\",\n", + " ]\n", + "\n", + " # Track skills created by this notebook\n", + " notebook_skills = []\n", + " other_skills = []\n", + "\n", + " for skill in all_skills:\n", + " if skill[\"display_title\"] in notebook_skill_names:\n", + " notebook_skills.append(skill)\n", + " else:\n", + " other_skills.append(skill)\n", + "\n", + " print(\"=\" * 70)\n", + " print(\"SKILL INVENTORY REPORT\")\n", + " print(\"=\" * 70)\n", + "\n", + " print(f\"\\nTotal custom skills in workspace: {len(all_skills)}\")\n", + "\n", + " if notebook_skills:\n", + " print(f\"\\n📚 Skills created by this notebook ({len(notebook_skills)}):\")\n", + " for skill in notebook_skills:\n", + " print(f\" • {skill['display_title']}\")\n", + " print(f\" ID: {skill['skill_id']}\")\n", + " print(f\" Version: {skill['latest_version']}\")\n", + " print(f\" Created: {skill['created_at']}\")\n", + " else:\n", + " print(\"\\n✅ No skills from this notebook found\")\n", + "\n", + " if other_skills:\n", + " print(f\"\\n🔧 Other skills in workspace ({len(other_skills)}):\")\n", + " for skill in other_skills:\n", + " print(f\" • {skill['display_title']} (v{skill['latest_version']})\")\n", + "\n", + " # Cleanup options\n", + " if notebook_skills:\n", + " print(\"\\n\" + \"=\" * 70)\n", + " print(\"CLEANUP OPTIONS\")\n", + " print(\"=\" * 70)\n", + "\n", + " if dry_run:\n", + " print(\"\\n🔍 DRY RUN MODE - No skills will be deleted\")\n", + " print(\"\\nTo delete the notebook skills, uncomment and run:\")\n", + " print(\"-\" * 40)\n", + " print(\"# review_and_cleanup_skills(client, dry_run=False)\")\n", + " print(\"-\" * 40)\n", + "\n", + " print(\"\\nThis would delete:\")\n", + " for skill in notebook_skills:\n", + " print(f\" • {skill['display_title']}\")\n", + " else:\n", + " print(\"\\n⚠️ DELETION MODE - Skills will be permanently removed\")\n", + " print(\"\\nDeleting notebook skills...\")\n", + "\n", + " success_count = 0\n", + " for skill in notebook_skills:\n", + " if delete_skill(client, skill[\"skill_id\"]):\n", + " print(f\" ✅ Deleted: {skill['display_title']}\")\n", + " success_count += 1\n", + " else:\n", + " print(f\" ❌ Failed to delete: {skill['display_title']}\")\n", + "\n", + " print(\n", + " f\"\\n📊 Cleanup complete: {success_count}/{len(notebook_skills)} skills deleted\"\n", + " )\n", + "\n", + " return {\n", + " \"total_skills\": len(all_skills),\n", + " \"notebook_skills\": len(notebook_skills),\n", + " \"other_skills\": len(other_skills),\n", + " \"notebook_skill_ids\": [s[\"skill_id\"] for s in notebook_skills],\n", + " }\n", + "\n", + "\n", + "# Run the review (in dry-run mode by default)\n", + "print(\"Reviewing your custom skills workspace...\")\n", + "cleanup_summary = review_and_cleanup_skills(client, dry_run=True)\n", + "\n", + "# Store skill IDs for potential cleanup\n", + "if cleanup_summary[\"notebook_skill_ids\"]:\n", + " skills_to_cleanup = cleanup_summary[\"notebook_skill_ids\"]\n", + " print(\n", + " f\"\\n💡 Tip: {len(skills_to_cleanup)} skill(s) can be cleaned up when you're done testing\"\n", + " )\n", + "\n", + "# UNCOMMENT THE LINE BELOW TO ACTUALLY DELETE THE NOTEBOOK SKILLS:\n", + "# review_and_cleanup_skills(client, dry_run=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "## 7. Best Practices & Production Tips {#best-practices}\n\n### Skill Design Principles\n\n1. **Single Responsibility**: Each skill should focus on one area of expertise\n2. **Clear Documentation**: SKILL.md should be comprehensive yet concise\n3. **Error Handling**: Scripts should handle edge cases gracefully\n4. **Version Control**: Use Git to track skill changes\n5. **Testing**: Always test skills before production deployment\n\n### Directory Structure Best Practices\n\n```\ncustom_skills/\n├── financial_analyzer/ # Single purpose, clear naming\n│ ├── SKILL.md # Under 5,000 tokens\n│ ├── scripts/ # Modular Python/JS files\n│ └── tests/ # Unit tests for scripts\n├── brand_guidelines/ # Organizational standards\n│ ├── SKILL.md\n│ ├── REFERENCE.md # Additional documentation\n│ └── assets/ # Logos, templates\n```\n\n### Performance Optimization\n\n| Strategy | Impact | Implementation |\n|----------|--------|----------------|\n| **Minimal Frontmatter** | Faster skill discovery | name: 64 chars, description: 1024 chars |\n| **Lazy Loading** | Reduced token usage | Reference files only when needed |\n| **Skill Composition** | Avoid duplication | Combine skills vs. mega-skill |\n| **Caching** | Faster responses | Reuse skill containers |\n\n### Security Considerations\n\n- **API Keys**: Never hardcode credentials in skills\n- **Data Privacy**: Don't include sensitive data in skill files\n- **Access Control**: Skills are workspace-specific\n- **Validation**: Sanitize inputs in scripts\n- **Audit Trail**: Log skill usage for compliance" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "## Next Steps\n\n🎉 **Congratulations!** You've learned how to create, deploy, and manage custom skills for Claude.\n\n### What You've Learned\n\n- ✅ Custom skill architecture and requirements\n- ✅ Creating skills with SKILL.md and Python scripts\n- ✅ Uploading skills via the API\n- ✅ Combining custom and Anthropic skills\n- ✅ Best practices for production deployment\n- ✅ Troubleshooting common issues\n\n### Continue Your Journey\n\n1. **Experiment**: Modify the example skills for your use cases\n2. **Build**: Create skills for your organization's workflows\n3. **Optimize**: Monitor token usage and performance\n4. **Share**: Document your skills for team collaboration\n\n### Resources\n\n- [Claude API Documentation](https://docs.anthropic.com/en/api/messages)\n- [Skills Documentation](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview)\n- [Best Practices](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices)\n- [Files API Documentation](https://docs.claude.com/en/api/files-content)\n- Example Skills Repository (coming soon)\n\n### Skill Ideas to Try\n\n- 📊 **Data Pipeline**: ETL workflows with validation\n- 📝 **Document Templates**: Contracts, proposals, reports\n- 🔍 **Code Review**: Style guides and best practices\n- 📈 **Analytics Dashboard**: KPI tracking and visualization\n- 🤖 **Automation Suite**: Repetitive task workflows\n\nHappy skill building! 🚀" + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/skills/requirements.txt b/skills/requirements.txt new file mode 100644 index 00000000..224fe8a8 --- /dev/null +++ b/skills/requirements.txt @@ -0,0 +1,26 @@ +# Claude Skills Cookbook Requirements +# Install with: pip install -r requirements.txt + +# Core dependencies +anthropic>=0.71.0 # Anthropic SDK with Skills support +python-dotenv>=1.0.0 # Environment variable management +ipykernel>=6.25.0 # Jupyter kernel for notebooks +jupyter>=1.0.0 # Jupyter notebook support + +# Data manipulation and analysis +pandas>=2.0.0 # Data analysis and manipulation +numpy>=1.24.0 # Numerical computing +openpyxl>=3.1.0 # Excel file reading/writing + +# Visualization (for financial charts in notebooks) +matplotlib>=3.7.0 # Basic plotting +plotly>=5.14.0 # Interactive visualizations + +# Utilities +requests>=2.31.0 # HTTP requests for examples +tqdm>=4.65.0 # Progress bars for long operations +tabulate>=0.9.0 # Pretty-print tables in notebooks + +# Development tools (optional but recommended) +ipywidgets>=8.0.0 # Interactive widgets for notebooks +nbformat>=5.9.0 # Notebook format validation \ No newline at end of file diff --git a/skills/sample_data/budget_template.csv b/skills/sample_data/budget_template.csv new file mode 100644 index 00000000..ba9f9094 --- /dev/null +++ b/skills/sample_data/budget_template.csv @@ -0,0 +1,31 @@ +Department,Category,Jan_Budget,Jan_Actual,Feb_Budget,Feb_Actual,Mar_Budget,Mar_Actual,Q1_Budget,Q1_Actual,Variance,Variance_Percent +Sales,Salaries,250000,245000,250000,248000,250000,252000,750000,745000,-5000,-0.67 +Sales,Commissions,75000,82000,75000,78500,75000,85000,225000,245500,20500,9.11 +Sales,Travel,15000,13500,15000,16200,15000,14800,45000,44500,-500,-1.11 +Sales,Marketing,50000,48000,50000,52000,50000,51000,150000,151000,1000,0.67 +Sales,Training,10000,8500,10000,9000,10000,10500,30000,28000,-2000,-6.67 +Operations,Salaries,180000,178000,180000,180000,180000,181000,540000,539000,-1000,-0.19 +Operations,Equipment,25000,23000,25000,26500,25000,24500,75000,74000,-1000,-1.33 +Operations,Supplies,12000,11500,12000,12300,12000,11800,36000,35600,-400,-1.11 +Operations,Maintenance,8000,7500,8000,8200,8000,7800,24000,23500,-500,-2.08 +Operations,Utilities,15000,14500,15000,15500,15000,14800,45000,44800,-200,-0.44 +IT,Salaries,200000,198000,200000,200000,200000,202000,600000,600000,0,0.00 +IT,Software,35000,32000,35000,36000,35000,34500,105000,102500,-2500,-2.38 +IT,Hardware,20000,18500,20000,21000,20000,19500,60000,59000,-1000,-1.67 +IT,Cloud Services,45000,44000,45000,45500,45000,46000,135000,135500,500,0.37 +IT,Security,15000,15000,15000,14500,15000,15500,45000,45000,0,0.00 +HR,Salaries,120000,118000,120000,120000,120000,121000,360000,359000,-1000,-0.28 +HR,Recruiting,25000,22000,25000,27000,25000,24000,75000,73000,-2000,-2.67 +HR,Benefits,85000,83000,85000,85000,85000,86000,255000,254000,-1000,-0.39 +HR,Training,20000,18500,20000,19000,20000,21000,60000,58500,-1500,-2.50 +HR,Employee Events,10000,9500,10000,10500,10000,9800,30000,29800,-200,-0.67 +Finance,Salaries,150000,148000,150000,150000,150000,151000,450000,449000,-1000,-0.22 +Finance,Audit Fees,30000,30000,30000,30000,30000,30000,90000,90000,0,0.00 +Finance,Software,12000,11500,12000,12000,12000,12500,36000,36000,0,0.00 +Finance,Consulting,20000,18000,20000,22000,20000,19000,60000,59000,-1000,-1.67 +Finance,Compliance,15000,14500,15000,15000,15000,15500,45000,45000,0,0.00 +Admin,Office Rent,50000,50000,50000,50000,50000,50000,150000,150000,0,0.00 +Admin,Insurance,25000,25000,25000,25000,25000,25000,75000,75000,0,0.00 +Admin,Legal Fees,15000,12000,15000,16000,15000,14000,45000,42000,-3000,-6.67 +Admin,Office Supplies,8000,7500,8000,8200,8000,7800,24000,23500,-500,-2.08 +Admin,Communications,5000,4800,5000,5000,5000,5200,15000,15000,0,0.00 \ No newline at end of file diff --git a/skills/sample_data/financial_statements.csv b/skills/sample_data/financial_statements.csv new file mode 100644 index 00000000..d93ae818 --- /dev/null +++ b/skills/sample_data/financial_statements.csv @@ -0,0 +1,22 @@ +Category,Q1_2024,Q2_2024,Q3_2024,Q4_2024,Q1_2023,Q2_2023,Q3_2023,Q4_2023 +Revenue,12500000,13200000,13800000,14500000,11000000,11500000,12000000,12300000 +Cost of Revenue,7500000,7920000,8280000,8700000,6820000,7130000,7440000,7626000 +Gross Profit,5000000,5280000,5520000,5800000,4180000,4370000,4560000,4674000 +Operating Expenses,3000000,3100000,3150000,3200000,2800000,2850000,2900000,2950000 +R&D Expenses,800000,850000,870000,900000,700000,720000,750000,780000 +Sales & Marketing,1200000,1250000,1280000,1300000,1100000,1130000,1150000,1170000 +General & Administrative,1000000,1000000,1000000,1000000,1000000,1000000,1000000,1000000 +Operating Income,2000000,2180000,2370000,2600000,1380000,1520000,1660000,1724000 +Interest Income,50000,52000,53000,55000,45000,46000,47000,48000 +Interest Expense,150000,150000,150000,150000,160000,160000,160000,160000 +Pre-tax Income,1900000,2082000,2273000,2505000,1265000,1406000,1547000,1612000 +Income Tax,475000,520500,568250,626250,316250,351500,386750,403000 +Net Income,1425000,1561500,1704750,1878750,948750,1054500,1160250,1209000 +Current Assets,25000000,26500000,27800000,29000000,22000000,23000000,24000000,24500000 +Current Liabilities,8000000,8200000,8400000,8600000,7500000,7600000,7700000,7800000 +Total Assets,45000000,46500000,48000000,50000000,40000000,41000000,42000000,43000000 +Total Liabilities,15000000,15200000,15400000,15600000,14500000,14600000,14700000,14800000 +Shareholders Equity,30000000,31300000,32600000,34400000,25500000,26400000,27300000,28200000 +Operating Cash Flow,2200000,2400000,2600000,2850000,1500000,1650000,1800000,1900000 +Capital Expenditures,500000,520000,540000,560000,450000,460000,470000,480000 +Free Cash Flow,1700000,1880000,2060000,2290000,1050000,1190000,1330000,1420000 \ No newline at end of file diff --git a/skills/sample_data/portfolio_holdings.json b/skills/sample_data/portfolio_holdings.json new file mode 100644 index 00000000..7c25724b --- /dev/null +++ b/skills/sample_data/portfolio_holdings.json @@ -0,0 +1,182 @@ +{ + "portfolio_name": "Growth & Income Portfolio", + "portfolio_id": "PF-2024-001", + "base_currency": "USD", + "last_updated": "2024-12-31", + "total_value": 1250000, + "holdings": [ + { + "ticker": "AAPL", + "name": "Apple Inc.", + "sector": "Technology", + "shares": 500, + "purchase_price": 145.50, + "current_price": 192.50, + "purchase_date": "2023-06-15", + "market_value": 96250, + "unrealized_gain": 23500, + "dividend_yield": 0.44, + "allocation_percent": 7.7 + }, + { + "ticker": "MSFT", + "name": "Microsoft Corporation", + "sector": "Technology", + "shares": 300, + "purchase_price": 310.25, + "current_price": 376.50, + "purchase_date": "2023-08-20", + "market_value": 112950, + "unrealized_gain": 19875, + "dividend_yield": 0.72, + "allocation_percent": 9.04 + }, + { + "ticker": "JPM", + "name": "JPMorgan Chase & Co.", + "sector": "Financials", + "shares": 400, + "purchase_price": 135.75, + "current_price": 170.25, + "purchase_date": "2023-09-10", + "market_value": 68100, + "unrealized_gain": 13800, + "dividend_yield": 2.33, + "allocation_percent": 5.45 + }, + { + "ticker": "JNJ", + "name": "Johnson & Johnson", + "sector": "Healthcare", + "shares": 350, + "purchase_price": 155.00, + "current_price": 160.75, + "purchase_date": "2023-07-05", + "market_value": 56262.50, + "unrealized_gain": 2012.50, + "dividend_yield": 2.95, + "allocation_percent": 4.5 + }, + { + "ticker": "V", + "name": "Visa Inc.", + "sector": "Financials", + "shares": 200, + "purchase_price": 220.50, + "current_price": 260.75, + "purchase_date": "2023-05-12", + "market_value": 52150, + "unrealized_gain": 8050, + "dividend_yield": 0.74, + "allocation_percent": 4.17 + }, + { + "ticker": "PG", + "name": "Procter & Gamble Co.", + "sector": "Consumer Staples", + "shares": 300, + "purchase_price": 145.25, + "current_price": 152.50, + "purchase_date": "2023-04-18", + "market_value": 45750, + "unrealized_gain": 2175, + "dividend_yield": 2.42, + "allocation_percent": 3.66 + }, + { + "ticker": "UNH", + "name": "UnitedHealth Group Inc.", + "sector": "Healthcare", + "shares": 100, + "purchase_price": 475.00, + "current_price": 525.50, + "purchase_date": "2023-10-02", + "market_value": 52550, + "unrealized_gain": 5050, + "dividend_yield": 1.45, + "allocation_percent": 4.2 + }, + { + "ticker": "HD", + "name": "The Home Depot Inc.", + "sector": "Consumer Discretionary", + "shares": 150, + "purchase_price": 295.75, + "current_price": 345.25, + "purchase_date": "2023-11-15", + "market_value": 51787.50, + "unrealized_gain": 7425, + "dividend_yield": 2.37, + "allocation_percent": 4.14 + }, + { + "ticker": "NVDA", + "name": "NVIDIA Corporation", + "sector": "Technology", + "shares": 80, + "purchase_price": 420.00, + "current_price": 495.50, + "purchase_date": "2023-12-01", + "market_value": 39640, + "unrealized_gain": 6040, + "dividend_yield": 0.03, + "allocation_percent": 3.17 + }, + { + "ticker": "AMZN", + "name": "Amazon.com Inc.", + "sector": "Consumer Discretionary", + "shares": 250, + "purchase_price": 130.25, + "current_price": 155.75, + "purchase_date": "2023-03-22", + "market_value": 38937.50, + "unrealized_gain": 6375, + "dividend_yield": 0, + "allocation_percent": 3.11 + } + ], + "cash_position": { + "amount": 50000, + "allocation_percent": 4.0 + }, + "bonds": { + "total_value": 585432.50, + "allocation_percent": 46.83, + "holdings": [ + { + "name": "US Treasury 10Y", + "value": 250000, + "yield": 4.25 + }, + { + "name": "Corporate Bond ETF", + "value": 200000, + "yield": 5.10 + }, + { + "name": "Municipal Bonds", + "value": 135432.50, + "yield": 3.75 + } + ] + }, + "performance_metrics": { + "total_return": 95302.50, + "total_return_percent": 7.62, + "year_to_date_return": 12.5, + "annualized_return": 9.8, + "sharpe_ratio": 1.45, + "beta": 0.85, + "standard_deviation": 12.3 + }, + "sector_allocation": { + "Technology": 19.98, + "Financials": 9.62, + "Healthcare": 8.7, + "Consumer Discretionary": 7.25, + "Consumer Staples": 3.66, + "Bonds": 46.83, + "Cash": 4.0 + } +} \ No newline at end of file diff --git a/skills/sample_data/quarterly_metrics.json b/skills/sample_data/quarterly_metrics.json new file mode 100644 index 00000000..98eaf1bb --- /dev/null +++ b/skills/sample_data/quarterly_metrics.json @@ -0,0 +1,84 @@ +{ + "company": "Acme Corporation", + "fiscal_year": 2024, + "quarters": [ + { + "quarter": "Q1 2024", + "revenue": 2500000, + "gross_margin": 68.0, + "operating_margin": 15.5, + "net_margin": 10.2, + "customer_count": 650, + "new_customers": 75, + "churn_rate": 3.5, + "arr": 30000000, + "mrr": 2500000, + "cac": 18000, + "ltv": 400000, + "employee_count": 125, + "product_releases": 2, + "support_tickets": 450 + }, + { + "quarter": "Q2 2024", + "revenue": 2750000, + "gross_margin": 69.5, + "operating_margin": 16.2, + "net_margin": 11.1, + "customer_count": 720, + "new_customers": 85, + "churn_rate": 3.2, + "arr": 33000000, + "mrr": 2750000, + "cac": 17000, + "ltv": 420000, + "employee_count": 135, + "product_releases": 3, + "support_tickets": 425 + }, + { + "quarter": "Q3 2024", + "revenue": 2950000, + "gross_margin": 70.5, + "operating_margin": 17.1, + "net_margin": 11.8, + "customer_count": 780, + "new_customers": 82, + "churn_rate": 3.0, + "arr": 35400000, + "mrr": 2950000, + "cac": 16500, + "ltv": 435000, + "employee_count": 142, + "product_releases": 2, + "support_tickets": 410 + }, + { + "quarter": "Q4 2024", + "revenue": 3200000, + "gross_margin": 72.0, + "operating_margin": 18.5, + "net_margin": 12.5, + "customer_count": 850, + "new_customers": 95, + "churn_rate": 2.8, + "arr": 38400000, + "mrr": 3200000, + "cac": 16000, + "ltv": 450000, + "employee_count": 148, + "product_releases": 4, + "support_tickets": 390 + } + ], + "year_summary": { + "total_revenue": 11400000, + "avg_gross_margin": 70.0, + "avg_operating_margin": 16.8, + "total_new_customers": 337, + "ending_customer_count": 850, + "ending_arr": 38400000, + "avg_cac": 16875, + "avg_ltv": 426250 + } +} \ No newline at end of file diff --git a/skills/skill_utils.py b/skills/skill_utils.py new file mode 100644 index 00000000..9d3ded9b --- /dev/null +++ b/skills/skill_utils.py @@ -0,0 +1,439 @@ +""" +Utility functions for managing custom skills with Claude's Skills API. + +This module provides helper functions for: +- Creating and uploading custom skills +- Listing and retrieving skill information +- Managing skill versions +- Testing skills with Claude +- Deleting skills +""" + +import json +import os +from pathlib import Path +from typing import Optional, List, Dict, Any +from anthropic import Anthropic +from anthropic.lib import files_from_dir + + +def create_skill( + client: Anthropic, + skill_path: str, + display_title: str +) -> Dict[str, Any]: + """ + Create a new custom skill from a directory. + + The directory must contain: + - SKILL.md file with YAML frontmatter (name, description) + - Optional: scripts, resources, REFERENCE.md + + Args: + client: Anthropic client instance with Skills beta + skill_path: Path to skill directory containing SKILL.md + display_title: Human-readable name for the skill + + Returns: + Dictionary with skill creation results: + { + 'success': bool, + 'skill_id': str (if successful), + 'display_title': str, + 'latest_version': str, + 'created_at': str, + 'source': str ('custom'), + 'error': str (if failed) + } + + Example: + >>> client = Anthropic(api_key="...", default_headers={"anthropic-beta": "skills-2025-10-02"}) + >>> result = create_skill(client, "custom_skills/financial_analyzer", "Financial Analyzer") + >>> if result['success']: + ... print(f"Created skill: {result['skill_id']}") + """ + try: + # Validate skill directory + skill_dir = Path(skill_path) + if not skill_dir.exists(): + return { + 'success': False, + 'error': f"Skill directory does not exist: {skill_path}" + } + + skill_md = skill_dir / "SKILL.md" + if not skill_md.exists(): + return { + 'success': False, + 'error': f"SKILL.md not found in {skill_path}" + } + + # Create skill using files_from_dir + skill = client.beta.skills.create( + display_title=display_title, + files=files_from_dir(skill_path) + ) + + return { + 'success': True, + 'skill_id': skill.id, + 'display_title': skill.display_title, + 'latest_version': skill.latest_version, + 'created_at': skill.created_at, + 'source': skill.source + } + + except Exception as e: + return { + 'success': False, + 'error': str(e) + } + + +def list_custom_skills(client: Anthropic) -> List[Dict[str, Any]]: + """ + List all custom skills in the workspace. + + Args: + client: Anthropic client instance with Skills beta + + Returns: + List of skill dictionaries with metadata + + Example: + >>> skills = list_custom_skills(client) + >>> for skill in skills: + ... print(f"{skill['display_title']}: {skill['skill_id']}") + """ + try: + skills_response = client.beta.skills.list(source="custom") + + skills = [] + for skill in skills_response.data: + skills.append({ + 'skill_id': skill.id, + 'display_title': skill.display_title, + 'latest_version': skill.latest_version, + 'created_at': skill.created_at, + 'updated_at': skill.updated_at + }) + + return skills + + except Exception as e: + print(f"Error listing skills: {e}") + return [] + + +def get_skill_version( + client: Anthropic, + skill_id: str, + version: str = "latest" +) -> Optional[Dict[str, Any]]: + """ + Get detailed information about a specific skill version. + + Args: + client: Anthropic client instance + skill_id: ID of the skill + version: Version to retrieve (default: "latest") + + Returns: + Dictionary with version details or None if not found + """ + try: + # Get latest version if not specified + if version == "latest": + skill = client.beta.skills.retrieve(skill_id) + version = skill.latest_version + + version_info = client.beta.skills.versions.retrieve( + skill_id=skill_id, + version=version + ) + + return { + 'version': version_info.version, + 'skill_id': version_info.skill_id, + 'name': version_info.name, + 'description': version_info.description, + 'directory': version_info.directory, + 'created_at': version_info.created_at + } + + except Exception as e: + print(f"Error getting skill version: {e}") + return None + + +def create_skill_version( + client: Anthropic, + skill_id: str, + skill_path: str +) -> Dict[str, Any]: + """ + Create a new version of an existing skill. + + Args: + client: Anthropic client instance + skill_id: ID of the existing skill + skill_path: Path to updated skill directory + + Returns: + Dictionary with version creation results + """ + try: + version = client.beta.skills.versions.create( + skill_id=skill_id, + files=files_from_dir(skill_path) + ) + + return { + 'success': True, + 'version': version.version, + 'skill_id': version.skill_id, + 'created_at': version.created_at + } + + except Exception as e: + return { + 'success': False, + 'error': str(e) + } + + +def delete_skill( + client: Anthropic, + skill_id: str, + delete_versions: bool = True +) -> bool: + """ + Delete a custom skill and optionally all its versions. + + Note: All versions must be deleted before the skill can be deleted. + + Args: + client: Anthropic client instance + skill_id: ID of skill to delete + delete_versions: Whether to delete all versions first + + Returns: + True if successful, False otherwise + """ + try: + if delete_versions: + # First delete all versions + versions = client.beta.skills.versions.list(skill_id=skill_id) + + for version in versions.data: + client.beta.skills.versions.delete( + skill_id=skill_id, + version=version.version + ) + print(f" Deleted version: {version.version}") + + # Then delete the skill itself + client.beta.skills.delete(skill_id) + print(f"✓ Deleted skill: {skill_id}") + return True + + except Exception as e: + print(f"Error deleting skill: {e}") + return False + + +def test_skill( + client: Anthropic, + skill_id: str, + test_prompt: str, + model: str = "claude-sonnet-4-5-20250929", + include_anthropic_skills: Optional[List[str]] = None +) -> Any: + """ + Test a custom skill with a prompt. + + Args: + client: Anthropic client instance + skill_id: ID of skill to test + test_prompt: Prompt to test the skill + model: Model to use for testing + include_anthropic_skills: Optional list of Anthropic skill IDs to include + + Returns: + Response from Claude + + Example: + >>> response = test_skill( + ... client, + ... "skill_abc123", + ... "Calculate P/E ratio for a company with price $50 and earnings $2.50", + ... include_anthropic_skills=["xlsx"] + ... ) + """ + # Build skills list + skills = [{ + "type": "custom", + "skill_id": skill_id, + "version": "latest" + }] + + # Add Anthropic skills if requested + if include_anthropic_skills: + for anthropic_skill in include_anthropic_skills: + skills.append({ + "type": "anthropic", + "skill_id": anthropic_skill, + "version": "latest" + }) + + response = client.beta.messages.create( + model=model, + max_tokens=4096, + container={"skills": skills}, + tools=[{"type": "code_execution_20250825", "name": "code_execution"}], + messages=[{"role": "user", "content": test_prompt}], + betas=["code-execution-2025-08-25", "files-api-2025-04-14", "skills-2025-10-02"] + ) + + return response + + +def list_skill_versions( + client: Anthropic, + skill_id: str +) -> List[Dict[str, Any]]: + """ + List all versions of a skill. + + Args: + client: Anthropic client instance + skill_id: ID of the skill + + Returns: + List of version dictionaries + """ + try: + versions_response = client.beta.skills.versions.list(skill_id=skill_id) + + versions = [] + for version in versions_response.data: + versions.append({ + 'version': version.version, + 'skill_id': version.skill_id, + 'created_at': version.created_at + }) + + return versions + + except Exception as e: + print(f"Error listing versions: {e}") + return [] + + +def validate_skill_directory(skill_path: str) -> Dict[str, Any]: + """ + Validate a skill directory structure before upload. + + Checks for: + - SKILL.md exists + - YAML frontmatter is valid + - Directory name matches skill name + - Total size is under 8MB + + Args: + skill_path: Path to skill directory + + Returns: + Dictionary with validation results + """ + result = { + 'valid': True, + 'errors': [], + 'warnings': [], + 'info': {} + } + + skill_dir = Path(skill_path) + + # Check directory exists + if not skill_dir.exists(): + result['valid'] = False + result['errors'].append(f"Directory does not exist: {skill_path}") + return result + + # Check for SKILL.md + skill_md = skill_dir / "SKILL.md" + if not skill_md.exists(): + result['valid'] = False + result['errors'].append("SKILL.md file is required") + else: + # Read and validate SKILL.md + content = skill_md.read_text() + + # Check for YAML frontmatter + if not content.startswith("---"): + result['valid'] = False + result['errors'].append("SKILL.md must start with YAML frontmatter (---)") + else: + # Extract frontmatter + try: + end_idx = content.index("---", 3) + frontmatter = content[3:end_idx].strip() + + # Check for required fields + if "name:" not in frontmatter: + result['valid'] = False + result['errors'].append("YAML frontmatter must include 'name' field") + + if "description:" not in frontmatter: + result['valid'] = False + result['errors'].append("YAML frontmatter must include 'description' field") + + # Check frontmatter size + if len(frontmatter) > 1024: + result['valid'] = False + result['errors'].append(f"YAML frontmatter exceeds 1024 chars (found: {len(frontmatter)})") + + except ValueError: + result['valid'] = False + result['errors'].append("Invalid YAML frontmatter format") + + # Check total size + total_size = sum(f.stat().st_size for f in skill_dir.rglob("*") if f.is_file()) + result['info']['total_size_mb'] = total_size / (1024 * 1024) + + if total_size > 8 * 1024 * 1024: + result['valid'] = False + result['errors'].append(f"Total size exceeds 8MB (found: {total_size / (1024 * 1024):.2f} MB)") + + # Count files + files = list(skill_dir.rglob("*")) + result['info']['file_count'] = len([f for f in files if f.is_file()]) + result['info']['directory_count'] = len([f for f in files if f.is_dir()]) + + # Check for common files + if (skill_dir / "REFERENCE.md").exists(): + result['info']['has_reference'] = True + + if (skill_dir / "scripts").exists(): + result['info']['has_scripts'] = True + result['info']['script_files'] = [f.name for f in (skill_dir / "scripts").iterdir() if f.is_file()] + + return result + + +def print_skill_summary(skill_info: Dict[str, Any]) -> None: + """ + Print a formatted summary of a skill. + + Args: + skill_info: Dictionary with skill information + """ + print(f"📦 Skill: {skill_info.get('display_title', 'Unknown')}") + print(f" ID: {skill_info.get('skill_id', 'N/A')}") + print(f" Version: {skill_info.get('latest_version', 'N/A')}") + print(f" Source: {skill_info.get('source', 'N/A')}") + print(f" Created: {skill_info.get('created_at', 'N/A')}") + + if 'error' in skill_info: + print(f" ❌ Error: {skill_info['error']}") \ No newline at end of file