You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Team Size: 4 (2 backend developers, 1 frontend developer for docs portal, 1 QA engineer)
Team Experience: Strong Python backend experience, moderate AWS experience, no prior math library development. Team has used FastAPI and Flask professionally. Familiar with pytest. Limited CDK experience (will need examples).
Programming Languages
Required Languages
Language
Version
Purpose
Rationale
Python
3.12+
API service, math engine, Lambda handlers, CDK infrastructure
Minimal frontend for API docs. No framework needed; static generation with Jinja2 templates.
Permitted Languages
Language
Conditions for Use
Rust
Approved for performance-critical math functions (e.g., expression parser) if Python performance is insufficient. Requires profiling evidence before adoption. Exposed to Python via PyO3/maturin.
TypeScript
Approved for CDK infrastructure if the team prefers CDK in TypeScript over Python CDK. Decision must be made before construction begins, not mid-project.
Prohibited Languages
Language
Reason
Use Instead
Java
No team expertise. Adds operational complexity (JVM cold starts in Lambda).
Python
Go
No team expertise. Python covers all current requirements.
Python
C/C++
Maintenance burden for native extensions.
Rust via PyO3 if native performance is needed
Package and Environment Management
uv as the Standard Tool
uv is the sole package and environment management tool for this project. Do not use pip, pip-tools, poetry, pipenv, or conda.
uv Usage Standards
# Project initialization (already done; do not re-run)
uv init calcengine
cd calcengine
# Adding dependencies
uv add fastapi # Add a runtime dependency
uv add uvicorn[standard] # Add with extras
uv add --dev pytest pytest-cov # Add a development dependency
uv add --dev mypy ruff # Add dev tooling# Removing dependencies
uv remove requests # Remove a dependency# Running commands in the project environment
uv run python -m calcengine.main # Run application
uv run pytest # Run tests
uv run mypy src/ # Run type checker
uv run ruff check src/ # Run linter# Syncing environment from lockfile
uv sync # Install all dependencies from uv.lock
uv sync --dev # Include dev dependencies# Lockfile management# uv.lock is auto-generated. NEVER edit it manually.# uv.lock MUST be committed to version control.
Type-safe data models, JSON serialization, integral to FastAPI.
uvicorn
0.30+
ASGI server
Standard production server for FastAPI. Used locally and in Lambda via Mangum.
Mangum
1.x
Lambda adapter
Wraps FastAPI ASGI app for AWS Lambda handler. Zero-config adapter.
pytest
8.x
Testing framework
Team standard. Rich plugin ecosystem.
mypy
1.x
Static type checking
Catch type errors before runtime. Strict mode enforced.
ruff
0.8+
Linting and formatting
Replaces flake8, isort, and black in a single fast tool.
structlog
24.x+
Structured JSON logging
All Lambda handlers and API endpoints must emit structured JSON logs. Configured once in a shared module.
aws-cdk-lib
2.x
Infrastructure as Code
AWS deployment. Python CDK constructs for all infrastructure.
Preferred Libraries
Use these when their capability is needed. Do not add them preemptively.
Library
Purpose
Use When
mpmath
Arbitrary-precision arithmetic
Phase 2: when arbitrary-precision mode is implemented. Not needed for MVP (IEEE 754 double is sufficient).
numpy
Array operations, linear algebra
Phase 2: when matrix/vector operations are implemented. Do not use for basic arithmetic.
scipy
Statistical distributions, numerical integration
Phase 2+: when advanced statistics and calculus are implemented.
httpx
Async HTTP client
Outbound HTTP calls (e.g., currency rate fetching in Phase 3). Preferred over requests for async compatibility.
boto3
AWS SDK
Any direct AWS service interaction not handled by CDK at deploy time (e.g., DynamoDB queries, Secrets Manager reads at runtime).
pytest-cov
Test coverage reporting
Always. Included in dev dependencies from project start.
pytest-asyncio
Async test support
When testing async FastAPI endpoints or async functions.
hypothesis
Property-based testing
Mathematical function testing. Generates random inputs to find edge cases. Strongly recommended for all math modules.
freezegun
Time mocking
When testing time-dependent logic (rate limiting, token expiry, audit timestamps).
Prohibited Libraries
Library
Reason
Alternative
Flask
Project uses FastAPI. Do not mix web frameworks.
FastAPI
Django
Excessive for an API service. ORM not needed.
FastAPI + direct DynamoDB access
requests
Synchronous-only. Blocks the async event loop in FastAPI.
httpx
sympy
Too heavy for MVP scope. Pulls in large dependency tree.
Implement expression parser directly. Re-evaluate for Phase 3 symbolic computation.
pandas
Not needed. CalcEngine processes individual calculations, not dataframes.
Standard Python or numpy for array operations when needed.
SQLAlchemy
No relational database in MVP. DynamoDB is the data store.
boto3 DynamoDB resource/client
celery
Unnecessary complexity for MVP. All calculations are synchronous and fast (<50ms).
Re-evaluate in Phase 3 for batch processing. Use SQS + Lambda if async is needed earlier.
poetry / pipenv / pip-tools
Project uses uv exclusively. Do not introduce alternative package managers.
uv
black / isort / flake8
Replaced by ruff, which combines all three.
ruff
Library Approval Process
To add a library not on the required or preferred lists:
Open a GitHub issue titled "Dependency Request: [library-name]"
Include: purpose, alternatives considered, license (must be MIT, Apache 2.0, or BSD), maintenance status (last release date, open issues count), and size impact
Tech lead reviews and approves or rejects
If approved, add via uv add and update this document
Cloud Environment
Cloud Provider
Primary Provider: AWS
Account Structure: Single AWS account for MVP. Separate dev/staging/prod accounts in Phase 2.
Regions: us-east-1 (primary). No disaster recovery region for MVP. Multi-region planned for Phase 2.
Service Allow List
Service
Approved Use Cases
Constraints
AWS Lambda
API request handlers, math computation
Python 3.12 runtime. Max 256MB memory for MVP (increase if profiling shows need). 30-second timeout.
Amazon API Gateway (HTTP API)
Public REST API endpoint
HTTP API type (not REST API type). Custom domain with TLS. Usage plans for rate limiting.
Amazon DynamoDB
API key storage, usage metering, rate limit counters
On-demand capacity mode. Single-table design. TTL for rate limit windows.
Bucket encryption enabled. Public access blocked except for docs site bucket (CloudFront distribution).
Amazon CloudFront
CDN for documentation portal and API spec
HTTPS only. Cache static assets aggressively.
Amazon CloudWatch
Logging, metrics, alarms, dashboards
Structured JSON logs from all Lambdas. Custom metrics for calculation counts, latency percentiles, error rates.
AWS Secrets Manager
Stripe API keys, signing keys
Automatic rotation where supported. Lambda reads at cold start, caches in memory.
AWS Certificate Manager
TLS certificates for custom domain
Used with API Gateway and CloudFront.
Amazon Cognito
Developer account authentication for docs portal and API key management
User pool for developer signup/login. Not used for API call authentication (API keys for that).
Amazon SQS
Dead-letter queue for failed async operations
Standard queue. Used for failed billing events and error capture. Not used for calculation requests in MVP.
AWS CDK
Infrastructure as Code deployment
Python CDK. All infrastructure defined in CDK. No manual console changes.
AWS CloudTrail
API audit logging
Enabled for all management events. Data events for S3 and Lambda in production.
AWS IAM
Service permissions
Least-privilege policies per Lambda function. No wildcard resource permissions.
Service Disallow List
Service
Reason
Alternative
Amazon EC2
Operational overhead. Serverless model preferred.
Lambda for compute.
Amazon ECS / Fargate
Over-engineering for MVP request/response workload.
Lambda. Re-evaluate if cold starts become a problem.
Amazon RDS / Aurora
Relational database not needed. API key and usage data fits DynamoDB.
DynamoDB.
Amazon ElastiCache / Redis
No caching layer needed for MVP. Calculations are stateless and fast.
In-memory caching within Lambda execution context if needed.
AWS Elastic Beanstalk
Does not fit IaC model.
CDK + Lambda.
Amazon Kinesis
Streaming not needed. All calculations are synchronous request/response.
SQS if async processing is needed.
AWS Step Functions
No multi-step orchestration in MVP.
Direct Lambda invocation. Re-evaluate for Phase 3 batch processing.
Amazon SNS
No pub/sub needed in MVP.
SQS for dead-letter queues.
Service Approval Process
To use a service not on the allow list:
Open a GitHub issue titled "AWS Service Request: [service-name]"
Include: use case, cost estimate (monthly), security implications, operational burden, and why an allowed service cannot meet the need
Tech lead reviews. Services with PII access or network exposure require additional security review.
If approved, add CDK construct and update this document
Preferred Technologies and Patterns
Architecture Pattern
Modular monolith deployed as serverless functions.
CalcEngine is a single Python package with internal modules (arithmetic, trigonometry, statistics, etc.), exposed through a single FastAPI application, deployed to AWS Lambda behind API Gateway. This is not a microservice architecture.
Decision
Choice
Rationale
Architecture style
Modular monolith
Small team (4 people), single domain, no independent scaling requirements per module in MVP.
Deployment model
Single Lambda function serving all API routes via Mangum
Simplicity. One deployment artifact. Cold start amortized across all endpoints.
Module boundaries
Python packages within src/calcengine/
Clean internal boundaries without the operational cost of separate services. Can extract to separate Lambdas later if specific endpoints need different memory/timeout.
API Design Standards
Style: REST over HTTPS. JSON request and response bodies.
Base URL: https://api.calcengine.io/v1/
Versioning: URL path prefix (/v1/, /v2/). Major version only. Non-breaking changes do not increment version.
Documentation: OpenAPI 3.1 specification auto-generated by FastAPI. Hosted at https://docs.calcengine.io.
Naming Convention: snake_case for JSON field names (Python convention). kebab-case for URL paths.
Content Type: application/json for all requests and responses. No XML support.
{
"error": {
"code": "DOMAIN_ERROR",
"message": "Cannot compute logarithm of a negative number",
"detail": "log(-5) is undefined for real numbers",
"parameter": "expression",
"documentation_url": "https://docs.calcengine.io/errors/DOMAIN_ERROR"
}
}
Error Codes (MVP):
Code
HTTP Status
Meaning
PARSE_ERROR
400
Expression could not be parsed. Malformed syntax.
DOMAIN_ERROR
422
Mathematically undefined (log(-1), sqrt(-1), division by zero).
OVERFLOW_ERROR
422
Result exceeds representable range.
INVALID_PARAMETER
400
Request parameter has invalid type or value.
EXPRESSION_TOO_LONG
400
Expression exceeds maximum allowed length.
RATE_LIMIT_EXCEEDED
429
API key has exceeded its rate limit.
UNAUTHORIZED
401
Missing or invalid API key.
INTERNAL_ERROR
500
Unexpected server error.
Data Patterns
Primary Data Store: DynamoDB (single-table design)
Entities in DynamoDB: API keys, usage counters (per key per month), rate limit windows (per key per minute)
Access Pattern: All reads and writes are by primary key (API key ID). No scans. No complex queries.
Caching: No external cache. Lambda reuses DynamoDB connections across warm invocations. API key validation results cached in Lambda memory for 60 seconds.
No relational database: If relational queries become necessary (reporting, analytics), evaluate DynamoDB export to S3 + Athena before adding RDS.
Logging Pattern
All log output must be structured JSON via structlog. Human-readable console output for local development only.
Unique ID per request (from API Gateway or generated)
api_key_id
Hashed API key identifier (never log the raw key)
endpoint
API path called
http_method
GET, POST, etc.
http_status
Response status code
duration_ms
Total request processing time
timestamp
ISO 8601 timestamp
Security Requirements
Authentication and Authorization
API Call Authentication: API key passed in Authorization: Bearer {key} header. API keys are 32-character random strings, stored as bcrypt hashes in DynamoDB.
Developer Portal Authentication: Amazon Cognito user pool. Email + password signup with email verification.
Authorization Model: Flat. All API keys have access to all endpoints. Tier-based rate limits (free, starter, professional) enforced by usage metering, not endpoint-level permissions.
API Key Management: Developers create, rotate, and revoke keys through the developer portal. Maximum 3 active keys per account.
Data Protection
Encryption at Rest: DynamoDB encrypted with AWS-managed KMS key. S3 buckets encrypted with SSE-S3.
Encryption in Transit: TLS 1.2+ enforced on API Gateway custom domain and CloudFront distribution. No HTTP (plaintext) endpoints.
PII Handling: Developer accounts store email and hashed password. No other PII collected. Mathematical expressions are not PII. Expressions are logged for debugging but not stored permanently (CloudWatch log retention: 30 days).
Data Classification: API keys = Confidential. Developer emails = Internal. Mathematical expressions and results = Public.
Expression character allowlist: Alphanumeric, arithmetic operators (+ - * / ^ %), parentheses, decimal point, comma, whitespace, and recognized function names. Reject unrecognized characters.
No code execution: The expression parser must never call eval(), exec(), compile(), or any dynamic code execution. Expressions are parsed into an AST and evaluated by the math engine.
Recursion depth limit: Expression parser limits nesting depth to 100 levels. Prevents stack overflow on deeply nested expressions like (((((...)))).
Numeric range validation: Results that exceed IEEE 754 double-precision range return OVERFLOW_ERROR instead of Infinity or NaN.
Secrets Management
Stripe API Keys: Stored in AWS Secrets Manager. Read by Lambda at cold start, cached in memory.
Cognito Client Secret: Stored in AWS Secrets Manager.
Prohibited Practices:
No secrets in pyproject.toml, source code, or .env files committed to Git
No secrets in Lambda environment variables (use Secrets Manager at runtime)
No AWS access keys in code (Lambda uses IAM execution roles)
.env files for local development only, listed in .gitignore
Dependency Security
Scanning: GitHub Dependabot enabled for Python dependencies. Alerts on known vulnerabilities.
License Policy: Allowed: MIT, Apache 2.0, BSD (2-clause and 3-clause), PSF, ISC. Prohibited: GPL, LGPL, AGPL, SSPL, proprietary. Check with uv tree before adding new dependencies.
Update Policy: Critical/High CVEs patched within 7 days. Medium within 30 days. Low evaluated quarterly.
OWASP Top 10 Compliance (2021)
A01:2021 - Broken Access Control
Control
CalcEngine Implementation
Authorization enforcement
API key validated in FastAPI middleware (api/middleware/auth.py) on every request before the route handler executes. No endpoint is accessible without a valid key.
Default deny
API Gateway rejects requests without an Authorization header at the gateway level (401). Lambda handler rejects requests with invalid or revoked keys (401).
Resource ownership
Each API key is tied to a Cognito account. Developers can only list, rotate, and revoke their own keys. DynamoDB queries are scoped to the authenticated user's partition key.
API Gateway CORS configured to allow only the documentation portal origin (https://docs.calcengine.io). No wildcard origins. GET and POST methods only.
Directory traversal / path manipulation
Not applicable. CalcEngine does not serve files or accept file paths as input.
A02:2021 - Cryptographic Failures
Control
CalcEngine Implementation
Data in transit
TLS 1.2+ enforced on API Gateway custom domain and CloudFront. HTTP endpoints do not exist. API Gateway configured with SecurityPolicy: TLS_1_2.
Data at rest
DynamoDB encrypted with AWS-managed KMS key. S3 buckets encrypted with SSE-S3. CloudWatch logs encrypted with service-managed keys.
Password/credential storage
Developer portal passwords hashed with bcrypt (Cognito-managed). API keys stored as bcrypt hashes in DynamoDB. Raw API keys are returned exactly once at creation time and never stored or logged.
Sensitive data in responses
API responses never contain API keys, account credentials, or internal identifiers. Error messages do not leak table names, ARNs, or stack traces.
Sensitive data in logs
API key IDs (hashed identifier, not the key itself) are logged. Raw API keys are never logged. Developer emails are not included in calculation logs.
A03:2021 - Injection
Control
CalcEngine Implementation
Expression injection
The expression parser builds an AST from a strict grammar. It does not use eval(), exec(), compile(), or any Python code execution mechanism. Only recognized tokens (numbers, operators, parentheses, whitelisted function names) are accepted. Unrecognized tokens cause a PARSE_ERROR (400).
Character allowlist
Expression input restricted to: digits, decimal point, arithmetic operators (+ - * / ^ %), parentheses, comma, whitespace, and a fixed set of function names (sin, cos, tan, log, sqrt, etc.). All other characters are rejected before parsing.
NoSQL injection
DynamoDB queries use the boto3 SDK with parameterized key conditions. No string concatenation of user input into query expressions. Partition keys and sort keys are set programmatically, never interpolated from request bodies.
HTTP header injection
FastAPI and Pydantic validate and type-check all request input. Response headers are set programmatically by the framework, not from user input.
Log injection
structlog escapes special characters in log values. User-supplied expressions are logged as string values within structured JSON fields, not interpolated into log format strings.
A04:2021 - Insecure Design
Control
CalcEngine Implementation
Threat modeling
Threat model created during AIDLC NFR Requirements stage. Reviewed when new endpoints or integration points are added. Primary threats: expression injection, resource exhaustion, API key abuse.
Defense in depth
Validation at three layers: (1) API Gateway request validation, (2) Pydantic model validation in FastAPI, (3) domain validation in engine functions. Each layer rejects independently.
Business logic limits
Expression length capped at 4,096 characters. Parser recursion depth capped at 100 levels. Maximum array size for statistics endpoints: 10,000 elements. These limits prevent resource exhaustion without affecting legitimate use.
Abuse case testing
Test suite includes negative/abuse tests: oversized expressions, deeply nested parentheses, expressions designed to cause slow evaluation, rapid-fire requests exceeding rate limits, invalid/expired/revoked API keys.
A05:2021 - Security Misconfiguration
Control
CalcEngine Implementation
Infrastructure as Code
All infrastructure defined in AWS CDK (Python). No manual console changes. CDK diff reviewed in pull requests before deploy.
Default credentials
No default API keys, admin accounts, or hardcoded passwords in any environment. Cognito user pool requires email verification.
Error messages
Production error responses return the CalcEngine error code, a user-friendly message, and a documentation URL. They never expose Python tracebacks, Lambda ARNs, DynamoDB table names, or internal file paths. FastAPI debug=False in production.
Unnecessary features
No /docs or /redoc interactive endpoints exposed in production Lambda. OpenAPI spec served only from the static documentation site. No health-check endpoints that reveal version details beyond engine_version.
Security headers
API Gateway responses include: Strict-Transport-Security: max-age=31536000; includeSubDomains, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Cache-Control: no-store on API responses. CloudFront adds security headers to documentation site.
Lambda configuration
Lambda functions use the minimum required memory (256MB). Timeout set to 30 seconds. Reserved concurrency configured to prevent runaway scaling. No environment variables containing secrets (Secrets Manager at runtime).
A06:2021 - Vulnerable and Outdated Components
Control
CalcEngine Implementation
Dependency scanning
GitHub Dependabot enabled. Scans pyproject.toml and uv.lock for known vulnerabilities. Alerts create GitHub issues automatically.
Allowed: MIT, Apache 2.0, BSD, PSF, ISC. Prohibited: GPL, LGPL, AGPL, SSPL, proprietary. Checked with uv tree before adding dependencies.
Lockfile integrity
uv.lock committed to Git and enforced in CI. uv sync --locked in CI pipeline fails if lockfile is out of date. No ad-hoc uv add in CI.
Minimal dependencies
Prohibited libraries list prevents bloated dependency trees (no pandas, Django, SQLAlchemy, sympy in MVP). Each new dependency requires a GitHub issue with justification.
A07:2021 - Identification and Authentication Failures
Control
CalcEngine Implementation
API key hashing
API keys are 32-character cryptographically random strings (via secrets.token_urlsafe). Stored as bcrypt hashes. Lookup uses a key prefix (first 8 chars, stored in plaintext) to find the record, then bcrypt verify confirms the full key.
Brute force protection
API Gateway throttling: 100 requests/second per IP across all endpoints. Failed authentication attempts (invalid key) logged with api_key_prefix and source IP. After 50 failed auth attempts from a single IP in 5 minutes, temporary IP block via WAF rule.
Developers can create a new key before revoking the old one (overlap period for zero-downtime rotation). Maximum 3 active keys per account prevents key hoarding.
Credential exposure
API key returned exactly once at creation (in the HTTP response body). Not stored in plaintext anywhere. Not included in emails. Not visible in the developer portal after creation.
Multi-factor authentication
Not required for MVP. Cognito MFA support is available and will be enabled as an option in Phase 2 when team/enterprise accounts are introduced.
A08:2021 - Software and Data Integrity Failures
Control
CalcEngine Implementation
CI/CD pipeline security
GitHub Actions. main branch protected: requires PR, at least 1 review, all CI checks passing. No direct pushes to main. Deploy workflow triggered only on merge to main.
Dependency integrity
uv.lock contains hashes for all dependencies. uv sync --locked verifies hashes on install. Lockfile changes in PRs are reviewed explicitly.
Deployment artifact integrity
Lambda deployment package built in CI from a clean uv sync --locked install. No local builds deployed to production. CDK deploy runs only from the CI pipeline, not from developer machines.
Deserialization safety
Pydantic v2 models parse and validate all incoming JSON. No use of pickle, yaml.load() (unsafe loader), or marshal. Only json.loads() via Pydantic's JSON parsing. Pydantic model_config has extra = "forbid" to reject unexpected fields.
A09:2021 - Security Logging and Monitoring Failures
Control
CalcEngine Implementation
Security events logged
All events below are logged as structured JSON to CloudWatch: authentication failures (invalid/expired/revoked key), rate limit exceeded (429), input validation failures (400), authorization anomalies, and all 5xx errors.
Log protection
CloudWatch logs are retained for 30 days. Log group resource policy prevents deletion by Lambda execution role. CloudTrail logs management events to a separate S3 bucket with object lock.
Alerting
CloudWatch Alarms configured for: 5xx error rate > 1% over 5 minutes, authentication failure rate > 100/minute, single API key generating > 10x its rate limit in attempts, Lambda concurrent execution > 80% of reserved concurrency. Alarms notify via SNS to on-call email/SMS.
Low risk for MVP. CalcEngine does not make outbound HTTP requests based on user input. The expression parser evaluates mathematical expressions; it does not fetch URLs, resolve hostnames, or make network calls.
Outbound requests
The only outbound network calls from Lambda are: (1) DynamoDB queries via AWS SDK (endpoint determined by AWS region, not user input), (2) Secrets Manager reads at cold start (secret name hardcoded in config, not user input).
Phase 3 consideration
When currency conversion is added (Phase 3), the service will fetch exchange rates from a financial data provider. At that point: the provider URL will be an environment variable (not user input), requests will use an allowlisted hostname, and responses will be validated against an expected schema before use. This section must be updated before Phase 3 launches.
Network segmentation
Lambda functions run in the AWS-managed VPC (no customer VPC for MVP). They can only reach AWS services via public endpoints. No internal services, databases, or metadata endpoints are reachable from Lambda in this configuration.
Testing Requirements
Test Strategy Overview
Test Type
Required
Coverage Target
Tooling
Unit Tests
Yes
90% line, 80% branch
pytest + pytest-cov
Mathematical Accuracy Tests
Yes
100% of implemented functions
pytest + hypothesis
Integration Tests
Yes
All API endpoints, DynamoDB interactions
pytest + moto (AWS mocking)
Load Tests
Yes (pre-launch)
1,000 concurrent requests, p50 < 50ms
Locust
Security Tests
Yes
Input validation, injection prevention
pytest (custom) + manual OWASP review
End-to-End Tests
Conditional
Critical user journeys against deployed staging
pytest + httpx against live API
Unit Testing Standards
Coverage Minimum: 90% line coverage, 80% branch coverage. Enforced by pytest-cov with fail_under = 90 in pyproject.toml.
Mocking Policy: Mock AWS services (DynamoDB, Secrets Manager) with moto. Mock time with freezegun. Do not mock internal math functions. Math functions must be tested with real computation.
Naming Convention: Test files mirror source files. src/calcengine/trig.py tested in tests/unit/test_trig.py. Test functions named test_<function>_<scenario> (e.g., test_sin_zero_returns_zero, test_sin_negative_pi_returns_zero).
Test Location: Separate tests/ directory tree. Not co-located with source.
This is a CalcEngine-specific testing category that does not exist in most projects.
Reference implementation: Every math function must be tested against Python's math module, mpmath library (at high precision), or published mathematical tables.
Property-based testing with hypothesis: Use hypothesis to generate random valid inputs and verify properties hold (e.g., sin(x)^2 + cos(x)^2 == 1, log(a*b) == log(a) + log(b)).
Edge cases: Every function must have explicit tests for: zero, negative zero, very small numbers (near epsilon), very large numbers, domain boundaries (e.g., asin(1), asin(1.0000001)), and special values (pi, e, multiples of pi/2 for trig).
Tolerance: Results must match reference values within 1 ULP (unit in the last place) for basic functions. Document any functions where wider tolerance is accepted, with justification.
Scope: Test full API request/response cycle through FastAPI test client. Test DynamoDB interactions with moto.
Environment: Local. No deployed services needed. moto mocks all AWS services.
Data Management: Each test creates its own DynamoDB table via moto fixture and tears down after. No shared test state.
CI/CD Testing Gates
Pipeline Stage
Required Tests
Tooling
Failure Action
Pre-commit
ruff check, ruff format --check, mypy
ruff, mypy via pre-commit hooks
Block commit
Pull Request
Unit tests, accuracy tests, integration tests, coverage check
pytest, GitHub Actions
Block merge
Pre-deploy (staging)
All PR tests + load test (100 concurrent, 60 seconds)
pytest + Locust, GitHub Actions
Block deploy
Post-deploy (production)
Smoke tests (10 representative calculations against live API)
pytest + httpx
Alert on-call. Auto-rollback if >50% failure.
Running Tests Locally
# Run all tests
uv run pytest
# Run only unit tests
uv run pytest tests/unit/ -m unit
# Run only accuracy tests
uv run pytest tests/accuracy/ -m accuracy
# Run with coverage report
uv run pytest --cov --cov-report=term-missing
# Run type checking
uv run mypy src/
# Run linter
uv run ruff check src/ tests/
# Run formatter check (no changes)
uv run ruff format --check src/ tests/
# Run formatter (apply changes)
uv run ruff format src/ tests/
No AWS imports. No HTTP imports. No side effects. Pure functions only. Must be testable without any mocking.
src/calcengine/api/
FastAPI routes, middleware, models
HTTP-layer only. Calls engine functions. Does not contain math logic.
src/calcengine/storage/
DynamoDB access layer
All AWS data access isolated here. No business logic.
infrastructure/
CDK stacks
Python CDK only. No application code.
tests/
All tests
Mirrors src/ structure. Separate unit/, integration/, accuracy/ directories.
examples/
Template code for patterns
Working code with tests and README. Updated when standards change.
Example and Template Code
Example 1: API Endpoint Pattern
examples/api-endpoint/README.md:
# API Endpoint Pattern## What This Demonstrates
Standard pattern for adding a new calculation endpoint to CalcEngine.
Shows: route definition, Pydantic models, engine call, error handling, logging.
## When to Use- Adding any new calculation endpoint
- Adding any new HTTP route to the API
## When Not to Use- Internal engine functions (see math-function example)
- Infrastructure changes (see cdk-construct example)
## Customization Guide| Element | Customize? | Notes ||---------|-----------|-------|| Route path and method | Yes | Follow /v1/{category}/{function} convention || Request/response models | Yes | Define Pydantic models specific to the endpoint || Engine function call | Yes | Call the appropriate engine module function || Error handling structure | No | Always use CalcEngineError hierarchy and error_response() || Logging calls | No | Always log with request_id, api_key_id, duration_ms || Response envelope | No | Always return {"result": ..., "expression": ..., "computation_time_ms": ..., "engine_version": ...} |
examples/api-endpoint/example_endpoint.py:
"""Example: Standard API endpoint pattern for CalcEngine."""importtimeimportstructlogfromfastapiimportAPIRouter, DependsfrompydanticimportBaseModel, Fieldfromcalcengine.api.middleware.authimportget_api_key_idfromcalcengine.api.models.errorsimporterror_responsefromcalcengine.api.models.responsesimportCalculationResponsefromcalcengine.engine.errorsimportCalcEngineErrorfromcalcengine.engine.trigonometryimportsinlogger=structlog.get_logger()
router=APIRouter()
classSinRequest(BaseModel):
"""Request model for sine calculation."""value: float=Field(..., description="Input angle")
angle_mode: str=Field(
default="radians",
pattern="^(radians|degrees)$",
description="Angle unit: 'radians' or 'degrees'",
)
@router.post("/v1/trigonometry/sin", response_model=CalculationResponse)asyncdefcalculate_sin(
request: SinRequest,
api_key_id: str=Depends(get_api_key_id),
) ->CalculationResponse|dict:
"""Calculate the sine of the given value."""start=time.perf_counter()
try:
result=sin(request.value, angle_mode=request.angle_mode)
elapsed= (time.perf_counter() -start) *1000logger.info(
"calculation_completed",
endpoint="/v1/trigonometry/sin",
input_value=request.value,
angle_mode=request.angle_mode,
result=result,
computation_time_ms=round(elapsed, 3),
api_key_id=api_key_id,
)
returnCalculationResponse(
result=result,
expression=f"sin({request.value})",
computation_time_ms=round(elapsed, 3),
)
exceptCalcEngineErrorase:
elapsed= (time.perf_counter() -start) *1000logger.warning(
"calculation_failed",
endpoint="/v1/trigonometry/sin",
input_value=request.value,
error_code=e.code,
error_detail=str(e),
computation_time_ms=round(elapsed, 3),
api_key_id=api_key_id,
)
returnerror_response(e)
examples/api-endpoint/test_example_endpoint.py:
"""Example: Standard test pattern for a CalcEngine API endpoint."""importmathimportpytestfromfastapi.testclientimportTestClientfromcalcengine.mainimportapp@pytest.fixturedefclient() ->TestClient:
"""Create a test client with a mocked API key."""returnTestClient(app)
classTestSinEndpoint:
"""Tests for POST /v1/trigonometry/sin."""@pytest.mark.unitdeftest_sin_zero_radians(self, client: TestClient) ->None:
response=client.post(
"/v1/trigonometry/sin",
json={"value": 0, "angle_mode": "radians"},
headers={"Authorization": "Bearer test-api-key"},
)
assertresponse.status_code==200data=response.json()
assertdata["result"] ==pytest.approx(0.0)
assert"computation_time_ms"indata@pytest.mark.unitdeftest_sin_pi_over_2_radians(self, client: TestClient) ->None:
response=client.post(
"/v1/trigonometry/sin",
json={"value": math.pi/2, "angle_mode": "radians"},
headers={"Authorization": "Bearer test-api-key"},
)
assertresponse.status_code==200assertresponse.json()["result"] ==pytest.approx(1.0)
@pytest.mark.unitdeftest_sin_90_degrees(self, client: TestClient) ->None:
response=client.post(
"/v1/trigonometry/sin",
json={"value": 90, "angle_mode": "degrees"},
headers={"Authorization": "Bearer test-api-key"},
)
assertresponse.status_code==200assertresponse.json()["result"] ==pytest.approx(1.0)
@pytest.mark.unitdeftest_sin_invalid_angle_mode(self, client: TestClient) ->None:
response=client.post(
"/v1/trigonometry/sin",
json={"value": 1.0, "angle_mode": "gradians"},
headers={"Authorization": "Bearer test-api-key"},
)
assertresponse.status_code==422# Pydantic validation error@pytest.mark.unitdeftest_sin_missing_auth(self, client: TestClient) ->None:
response=client.post(
"/v1/trigonometry/sin",
json={"value": 0},
)
assertresponse.status_code==401
Example 2: Pure Math Function Pattern
examples/math-function/README.md:
# Math Function Pattern## What This Demonstrates
Standard pattern for implementing a pure math function in the engine layer.
Shows: function signature, type hints, domain validation, error raising, docstring format.
## When to Use- Adding any new mathematical function to src/calcengine/engine/
## When Not to Use- API endpoints (see api-endpoint example)
- Functions that require AWS or HTTP access (those belong in api/ or storage/)
## Key Rules- No imports from api/, storage/, or any external service
- Pure functions only: same input always produces same output
- Raise CalcEngineError subclasses for domain errors, never return None or NaN
- Type hints on all parameters and return values
examples/math-function/example_function.py:
"""Example: Standard pattern for a pure math function in CalcEngine engine layer."""importmathfromcalcengine.engine.errorsimportDomainErrordeflog_base(value: float, base: float=10.0) ->float:
"""Compute the logarithm of a value with the given base. Args: value: The number to compute the logarithm of. Must be positive. base: The logarithm base. Must be positive and not equal to 1. Defaults to 10 (common logarithm). Returns: The logarithm of value in the given base. Raises: DomainError: If value <= 0, base <= 0, or base == 1. """ifvalue<=0:
raiseDomainError(
code="DOMAIN_ERROR",
message=f"Cannot compute logarithm of {value}",
detail="Logarithm is only defined for positive numbers",
parameter="value",
)
ifbase<=0:
raiseDomainError(
code="DOMAIN_ERROR",
message=f"Cannot use {base} as logarithm base",
detail="Logarithm base must be positive",
parameter="base",
)
ifbase==1.0:
raiseDomainError(
code="DOMAIN_ERROR",
message="Cannot use 1 as logarithm base",
detail="Logarithm base 1 is undefined (division by zero in change-of-base)",
parameter="base",
)
returnmath.log(value) /math.log(base)
# CDK Construct Pattern## What This Demonstrates
Standard pattern for defining a CDK stack for CalcEngine infrastructure.
Shows: Lambda function, API Gateway integration, DynamoDB table, IAM permissions.
## When to Use- Adding new infrastructure resources to the project
## Key Rules- All infrastructure in infrastructure/stacks/ directory
- One stack per logical group (api, data, monitoring, auth, docs)
- Use environment variables from CDK context, never hardcode
- Least-privilege IAM: each Lambda gets only the permissions it needs