Source code for usda_fdc.analysis.cli

"""
Command-line interface for nutrient analysis.
"""

import os
import sys
import json
import argparse
from typing import List, Optional, Dict, Any

from dotenv import load_dotenv

from .. import __version__
from ..client import FdcClient
from .analysis import analyze_food, compare_foods
from .dri import DriType, Gender
from .recipe import create_recipe, analyze_recipe
from .visualization import generate_html_report

def analyze_command(args: argparse.Namespace) -> None:
    """Handle analyze command."""
    client = FdcClient(args.api_key)
    
    try:
        # Get the food
        food = client.get_food(args.fdc_id)
        
        # Analyze the food
        analysis = analyze_food(
            food,
            serving_size=args.serving_size,
            dri_type=DriType(args.dri_type),
            gender=Gender(args.gender),
            age=args.age
        )
        
        # Output the analysis
        if args.format == "json":
            # Convert to JSON-serializable dict
            result = {
                "food": {
                    "fdc_id": food.fdc_id,
                    "description": food.description,
                    "data_type": food.data_type
                },
                "serving_size": analysis.serving_size,
                "calories": analysis.calories_per_serving,
                "macronutrients": {
                    "protein": analysis.protein_per_serving,
                    "carbs": analysis.carbs_per_serving,
                    "fat": analysis.fat_per_serving
                },
                "macronutrient_distribution": analysis.macronutrient_distribution,
                "nutrients": {
                    nutrient_id: {
                        "name": value.nutrient.name,
                        "amount": value.amount,
                        "unit": value.unit,
                        "dri": value.dri,
                        "dri_percent": value.dri_percent
                    }
                    for nutrient_id, value in analysis.nutrients.items()
                }
            }
            
            if args.output:
                with open(args.output, 'w') as f:
                    json.dump(result, f, indent=2)
            else:
                print(json.dumps(result, indent=2))
        
        elif args.format == "html":
            # Generate HTML report
            html = generate_html_report(analysis)
            
            if args.output:
                with open(args.output, 'w') as f:
                    f.write(html)
            else:
                print(html)
        
        else:  # text format
            print(f"Nutrient Analysis: {food.description}")
            print(f"Serving Size: {analysis.serving_size}g")
            print(f"Calories: {analysis.calories_per_serving:.1f} kcal")
            
            print("\nMacronutrient Distribution:")
            for macro, percent in analysis.macronutrient_distribution.items():
                print(f"- {macro.capitalize()}: {percent:.1f}%")
            
            print("\nKey Nutrients:")
            for nutrient_id in ["protein", "fiber", "vitamin_c", "calcium", "iron"]:
                nutrient_value = analysis.get_nutrient(nutrient_id)
                if nutrient_value:
                    dri_percent = f"{nutrient_value.dri_percent:.1f}%" if nutrient_value.dri_percent is not None else "N/A"
                    print(f"- {nutrient_value.nutrient.name}: {nutrient_value.amount:.1f} {nutrient_value.unit} ({dri_percent} of DRI)")
            
            if args.detailed:
                print("\nDetailed Nutrients:")
                for nutrient_id, value in sorted(
                    analysis.nutrients.items(),
                    key=lambda x: x[1].dri_percent or 0,
                    reverse=True
                ):
                    dri_percent = f"{value.dri_percent:.1f}%" if value.dri_percent is not None else "N/A"
                    print(f"- {value.nutrient.name}: {value.amount:.1f} {value.unit} ({dri_percent} of DRI)")
    
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

def compare_command(args: argparse.Namespace) -> None:
    """Handle compare command."""
    client = FdcClient(args.api_key)
    
    try:
        # Get the foods
        foods = [client.get_food(fdc_id) for fdc_id in args.fdc_ids]
        
        # Parse nutrient IDs
        nutrient_ids = args.nutrients.split(",") if args.nutrients else None
        
        # Compare the foods
        comparison = compare_foods(
            foods,
            nutrient_ids=nutrient_ids,
            serving_sizes=[args.serving_size] * len(foods),
            dri_type=DriType(args.dri_type),
            gender=Gender(args.gender),
            age=args.age
        )
        
        # Output the comparison
        if args.format == "json":
            # Convert to JSON-serializable dict
            result = {
                "foods": [
                    {
                        "fdc_id": food.fdc_id,
                        "description": food.description,
                        "data_type": food.data_type
                    }
                    for food in foods
                ],
                "serving_size": args.serving_size,
                "comparison": {
                    nutrient_id: [
                        {
                            "food": food_name,
                            "amount": amount,
                            "unit": unit
                        }
                        for food_name, amount, unit in values
                    ]
                    for nutrient_id, values in comparison.items()
                }
            }
            
            if args.output:
                with open(args.output, 'w') as f:
                    json.dump(result, f, indent=2)
            else:
                print(json.dumps(result, indent=2))
        
        else:  # text format
            print(f"Food Comparison (per {args.serving_size}g):")
            print(f"Foods: {', '.join(food.description for food in foods)}")
            
            print("\nNutrient Comparison:")
            for nutrient_id, values in comparison.items():
                if not values:
                    continue
                
                print(f"\n{nutrient_id.capitalize()}:")
                for food_name, amount, unit in values:
                    print(f"- {food_name}: {amount:.1f} {unit}")
    
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

def recipe_command(args: argparse.Namespace) -> None:
    """Handle recipe command."""
    client = FdcClient(args.api_key)
    
    try:
        # Get ingredients
        ingredients = []
        if args.ingredients:
            ingredients = args.ingredients
        elif args.ingredients_file:
            with open(args.ingredients_file, 'r') as f:
                ingredients = [line.strip() for line in f if line.strip()]
        
        if not ingredients:
            print("Error: No ingredients provided.", file=sys.stderr)
            sys.exit(1)
        
        # Create the recipe
        recipe = create_recipe(
            name=args.name,
            ingredient_texts=ingredients,
            client=client,
            servings=args.servings
        )
        
        # Analyze the recipe
        analysis = analyze_recipe(
            recipe,
            dri_type=DriType(args.dri_type),
            gender=Gender(args.gender),
            age=args.age
        )
        
        # Output the analysis
        if args.format == "json":
            # Convert to JSON-serializable dict
            result = {
                "recipe": {
                    "name": recipe.name,
                    "servings": recipe.servings,
                    "total_weight_g": recipe.total_weight_g,
                    "weight_per_serving": recipe.get_weight_per_serving(),
                    "ingredients": [
                        {
                            "description": ingredient.description,
                            "food": {
                                "fdc_id": ingredient.food.fdc_id,
                                "description": ingredient.food.description
                            },
                            "weight_g": ingredient.weight_g
                        }
                        for ingredient in recipe.ingredients
                    ]
                },
                "per_serving": {
                    "calories": analysis.per_serving_analysis.calories_per_serving,
                    "macronutrients": {
                        "protein": analysis.per_serving_analysis.protein_per_serving,
                        "carbs": analysis.per_serving_analysis.carbs_per_serving,
                        "fat": analysis.per_serving_analysis.fat_per_serving
                    },
                    "macronutrient_distribution": analysis.per_serving_analysis.macronutrient_distribution,
                    "nutrients": {
                        nutrient_id: {
                            "name": value.nutrient.name,
                            "amount": value.amount,
                            "unit": value.unit,
                            "dri": value.dri,
                            "dri_percent": value.dri_percent
                        }
                        for nutrient_id, value in analysis.per_serving_analysis.nutrients.items()
                    }
                }
            }
            
            if args.output:
                with open(args.output, 'w') as f:
                    json.dump(result, f, indent=2)
            else:
                print(json.dumps(result, indent=2))
        
        elif args.format == "html":
            # Generate HTML report
            html = generate_html_report(analysis.per_serving_analysis)
            
            if args.output:
                with open(args.output, 'w') as f:
                    f.write(html)
            else:
                print(html)
        
        else:  # text format
            print(f"Recipe Analysis: {recipe.name}")
            print(f"Servings: {recipe.servings}")
            print(f"Total Weight: {recipe.total_weight_g:.1f}g")
            print(f"Weight per Serving: {recipe.get_weight_per_serving():.1f}g")
            
            print("\nIngredients:")
            for ingredient in recipe.ingredients:
                print(f"- {ingredient}")
            
            print("\nNutrition per Serving:")
            print(f"Calories: {analysis.per_serving_analysis.calories_per_serving:.1f} kcal")
            
            print("\nMacronutrient Distribution:")
            for macro, percent in analysis.per_serving_analysis.macronutrient_distribution.items():
                print(f"- {macro.capitalize()}: {percent:.1f}%")
            
            print("\nKey Nutrients per Serving:")
            for nutrient_id in ["protein", "fiber", "vitamin_c", "calcium", "iron"]:
                nutrient_value = analysis.per_serving_analysis.get_nutrient(nutrient_id)
                if nutrient_value:
                    dri_percent = f"{nutrient_value.dri_percent:.1f}%" if nutrient_value.dri_percent is not None else "N/A"
                    print(f"- {nutrient_value.nutrient.name}: {nutrient_value.amount:.1f} {nutrient_value.unit} ({dri_percent} of DRI)")
            
            if args.detailed:
                print("\nDetailed Nutrients per Serving:")
                for nutrient_id, value in sorted(
                    analysis.per_serving_analysis.nutrients.items(),
                    key=lambda x: x[1].dri_percent or 0,
                    reverse=True
                ):
                    dri_percent = f"{value.dri_percent:.1f}%" if value.dri_percent is not None else "N/A"
                    print(f"- {value.nutrient.name}: {value.amount:.1f} {value.unit} ({dri_percent} of DRI)")
    
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

[docs]def main() -> None: """Main entry point for the CLI.""" # Load environment variables from .env file load_dotenv() # Create the top-level parser parser = argparse.ArgumentParser( prog="fdc-nat", description="USDA Food Data Central (FDC) Nutrient Analysis Tool" ) parser.add_argument( "--version", action="version", version=f"%(prog)s {__version__}" ) parser.add_argument( "--api-key", default=os.environ.get("FDC_API_KEY"), help="FDC API key (can also be set via FDC_API_KEY environment variable)" ) # Create subparsers for commands subparsers = parser.add_subparsers(dest="command", help="Command to execute") # Analyze command analyze_parser = subparsers.add_parser("analyze", help="Analyze a food") analyze_parser.add_argument("fdc_id", help="FDC ID of the food") analyze_parser.add_argument("--serving-size", type=float, default=100.0, help="Serving size in grams") analyze_parser.add_argument("--dri-type", choices=[t.value for t in DriType], default=DriType.RDA.value, help="DRI type") analyze_parser.add_argument("--gender", choices=[g.value for g in Gender], default=Gender.MALE.value, help="Gender for DRI") analyze_parser.add_argument("--age", type=int, default=30, help="Age for DRI") analyze_parser.add_argument("--detailed", action="store_true", help="Show detailed nutrient information") analyze_parser.add_argument("--format", choices=["text", "json", "html"], default="text", help="Output format") analyze_parser.add_argument("--output", help="Output file (default: stdout)") analyze_parser.set_defaults(func=analyze_command) # Compare command compare_parser = subparsers.add_parser("compare", help="Compare foods") compare_parser.add_argument("fdc_ids", nargs="+", help="FDC IDs of the foods to compare") compare_parser.add_argument("--serving-size", type=float, default=100.0, help="Serving size in grams") compare_parser.add_argument("--nutrients", help="Comma-separated list of nutrient IDs to compare") compare_parser.add_argument("--dri-type", choices=[t.value for t in DriType], default=DriType.RDA.value, help="DRI type") compare_parser.add_argument("--gender", choices=[g.value for g in Gender], default=Gender.MALE.value, help="Gender for DRI") compare_parser.add_argument("--age", type=int, default=30, help="Age for DRI") compare_parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format") compare_parser.add_argument("--output", help="Output file (default: stdout)") compare_parser.set_defaults(func=compare_command) # Recipe command recipe_parser = subparsers.add_parser("recipe", help="Analyze a recipe") recipe_parser.add_argument("--name", default="Recipe", help="Recipe name") recipe_parser.add_argument("--ingredients", nargs="*", help="List of ingredients") recipe_parser.add_argument("--ingredients-file", help="File with ingredients (one per line)") recipe_parser.add_argument("--servings", type=int, default=1, help="Number of servings") recipe_parser.add_argument("--dri-type", choices=[t.value for t in DriType], default=DriType.RDA.value, help="DRI type") recipe_parser.add_argument("--gender", choices=[g.value for g in Gender], default=Gender.MALE.value, help="Gender for DRI") recipe_parser.add_argument("--age", type=int, default=30, help="Age for DRI") recipe_parser.add_argument("--detailed", action="store_true", help="Show detailed nutrient information") recipe_parser.add_argument("--format", choices=["text", "json", "html"], default="text", help="Output format") recipe_parser.add_argument("--output", help="Output file (default: stdout)") recipe_parser.set_defaults(func=recipe_command) # Parse arguments args = parser.parse_args() # Check if API key is provided if not args.api_key: print("Error: No API key provided. Use --api-key or set FDC_API_KEY environment variable.", file=sys.stderr) sys.exit(1) # Execute command if provided if hasattr(args, "func"): args.func(args) else: parser.print_help()
if __name__ == "__main__": main()