Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
CREATE TABLE CardAssignment (
customerId BIGINT NOT NULL,
cardNo STRING NOT NULL,
cardType STRING NOT NULL,
`timestamp` TIMESTAMP_LTZ(3) NOT NULL METADATA FROM 'timestamp',
WATERMARK FOR `timestamp` AS `timestamp` - INTERVAL '1' SECOND
) WITH (
'connector' = 'kafka',
'properties.bootstrap.servers' = '${KAFKA_BOOTSTRAP_SERVERS}',
'properties.group.id' = 'mygroupid',
'scan.startup.mode' = 'group-offsets',
'properties.auto.offset.reset' = 'earliest',
'value.format' = 'flexible-json',
'topic' = 'cardassignment'
);

CREATE TABLE Merchant (
merchantId BIGINT NOT NULL,
name STRING NOT NULL,
category STRING NOT NULL,
updatedTime TIMESTAMP_LTZ(3) NOT NULL METADATA FROM 'timestamp',
WATERMARK FOR `updatedTime` AS `updatedTime` - INTERVAL '1' SECOND
) WITH (
'connector' = 'kafka',
'properties.bootstrap.servers' = '${KAFKA_BOOTSTRAP_SERVERS}',
'properties.group.id' = 'mygroupid',
'scan.startup.mode' = 'group-offsets',
'properties.auto.offset.reset' = 'earliest',
'value.format' = 'flexible-json',
'topic' = 'merchant'
);


CREATE TABLE MerchantReward (
merchantId BIGINT NOT NULL,
rewardsByCard ARRAY<ROW<
cardType STRING,
rewardPercentage BIGINT,
startTimestamp BIGINT,
expirationTimestamp BIGINT
>> NOT NULL,
updatedTime TIMESTAMP_LTZ(3) NOT NULL METADATA FROM 'timestamp',
WATERMARK FOR `updatedTime` AS `updatedTime` - INTERVAL '1' SECOND
) WITH (
'connector' = 'kafka',
'properties.bootstrap.servers' = '${KAFKA_BOOTSTRAP_SERVERS}',
'properties.group.id' = 'mygroupid',
'scan.startup.mode' = 'group-offsets',
'properties.auto.offset.reset' = 'earliest',
'value.format' = 'flexible-json',
'topic' = 'merchantreward'
);

CREATE TABLE Transaction (
transactionId BIGINT NOT NULL,
cardNo STRING NOT NULL,
`time` TIMESTAMP_LTZ(3) NOT NULL METADATA FROM 'timestamp',
amount DOUBLE NOT NULL,
merchantId BIGINT NOT NULL,
WATERMARK FOR `time` AS `time` - INTERVAL '1' SECOND
) WITH (
'connector' = 'kafka',
'properties.bootstrap.servers' = '${KAFKA_BOOTSTRAP_SERVERS}',
'properties.group.id' = 'mygroupid',
'scan.startup.mode' = 'group-offsets',
'properties.auto.offset.reset' = 'earliest',
'value.format' = 'flexible-json',
'topic' = 'transaction'
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
CREATE TABLE CardAssignment (
customerId BIGINT NOT NULL,
cardNo STRING NOT NULL,
cardType STRING NOT NULL,
`timestamp` TIMESTAMP_LTZ(3) NOT NULL,
PRIMARY KEY (`customerId`, `cardNo`, `timestamp`) NOT ENFORCED,
WATERMARK FOR `timestamp` AS `timestamp` - INTERVAL '1' SECOND
) WITH (
'format' = 'flexible-json',
'path' = '${DATA_PATH}/card_assignment.jsonl',
'source.monitor-interval' = '10 min',
'connector' = 'filesystem'
);

CREATE TABLE Merchant (
merchantId BIGINT NOT NULL,
name STRING NOT NULL,
category STRING NOT NULL,
updatedTime TIMESTAMP_LTZ(3) NOT NULL,
PRIMARY KEY (`merchantId`, `updatedTime`) NOT ENFORCED,
WATERMARK FOR `updatedTime` AS `updatedTime` - INTERVAL '1' SECOND
) WITH (
'format' = 'flexible-json',
'path' = '${DATA_PATH}/merchant.jsonl',
'source.monitor-interval' = '10 min',
'connector' = 'filesystem'
);

CREATE TABLE MerchantReward (
merchantId BIGINT NOT NULL,
rewardsByCard ARRAY<ROW<cardType STRING, rewardPercentage BIGINT, startTimestamp BIGINT, expirationTimestamp BIGINT>> NOT NULL,
updatedTime TIMESTAMP_LTZ(3) NOT NULL,
PRIMARY KEY (`merchantId`, `updatedTime`) NOT ENFORCED,
WATERMARK FOR `updatedTime` AS `updatedTime` - INTERVAL '1' SECOND
) WITH (
'format' = 'flexible-json',
'path' = '${DATA_PATH}/merchant_reward.jsonl',
'source.monitor-interval' = '10 min',
'connector' = 'filesystem'
);

CREATE TABLE Transaction (
transactionId BIGINT NOT NULL,
cardNo STRING NOT NULL,
`time` TIMESTAMP_LTZ(3) NOT NULL,
amount DOUBLE NOT NULL,
merchantId BIGINT NOT NULL,
PRIMARY KEY (`transactionId`, `time`) NOT ENFORCED,
WATERMARK FOR `time` AS `time` - INTERVAL '1' SECOND
) WITH (
'format' = 'flexible-json',
'path' = '${DATA_PATH}/transaction.jsonl',
'source.monitor-interval' = '10 min',
'connector' = 'filesystem'
);
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
/* Import Data */
IMPORT creditcard-{{variant}}.merchant AS _MerchantStream;
IMPORT creditcard-{{variant}}.card_assignment AS _CardAssignmentStream;
IMPORT creditcard-{{variant}}.transaction AS _Transaction;
IMPORT connectors.sources-{{variant}} AS sources;

/* Deduplicate CDC Streams */
_Merchant := DISTINCT _MerchantStream ON merchantId ORDER BY updatedTime DESC;
_CardAssignment := DISTINCT _CardAssignmentStream ON cardNo ORDER BY `timestamp` DESC;
_Merchant := DISTINCT sources.Merchant ON merchantId ORDER BY updatedTime DESC;
_CardAssignment := DISTINCT sources.CardAssignment ON cardNo ORDER BY `timestamp` DESC;

/** Enrich credit card transactions with customer and merchant information */
/*+no_query */
CustomerTransaction := SELECT t.transactionId, t.cardNo, t.`time`, t.amount, m.name AS merchantName,
m.category, c.customerId
FROM _Transaction t
FROM sources.Transaction t
JOIN _CardAssignment FOR SYSTEM_TIME AS OF t.`time` c ON t.cardNo = c.cardNo
JOIN _Merchant FOR SYSTEM_TIME AS OF t.`time` m ON t.merchantId = m.merchantId;

Expand All @@ -29,12 +28,14 @@ _SpendingByDay := SELECT customerId, window_time as timeDay, SUM(amount) as spen

/* ==== QUERY ENDPOINTS ==== */

/** Returns all credit card transactions within a specified time period ordered by time (most recent first) */
/** Returns all credit card transactions since fromTime (inclusive) and until toTime (exclusive) for the given customer showing most recent transactions first.
fromTime and toTime must be RFC-3339 compliant date time scalar. Both must be the start of a day, e.g. 2024-01-19T00:00:00-00:00. */
Transactions(customerId BIGINT NOT NULL, fromTime TIMESTAMP NOT NULL, toTime TIMESTAMP NOT NULL) :=
SELECT * FROM CustomerTransaction WHERE customerId = :customerId AND :fromTime <= `time` AND :toTime > `time`
ORDER BY `time` DESC LIMIT 10000;

/* Returns the total customer spending by day for the specified time period ordered by time (most recent first) */
/* Returns the total customer spending by day since fromTime (inclusive) and until toTime (exclusive) ordered by time (most recent first)
fromTime and toTime must be RFC-3339 compliant date time scalar. Both must be the start of a day, e.g. 2024-01-19T00:00:00-00:00. */
SpendingByDay(customerId BIGINT NOT NULL, fromTime TIMESTAMP NOT NULL, toTime TIMESTAMP NOT NULL) :=
SELECT timeDay, spending
FROM _SpendingByDay WHERE customerId = :customerId AND :fromTime <= timeDay AND :toTime > timeDay
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# GraphQL API endpoint for production
# Update this URL to your production GraphQL endpoint
VITE_GRAPHQL_URL=https://api.example.com/v1/graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Environment variables
.env
.env.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
118 changes: 118 additions & 0 deletions finance-credit-card-chatbot/credit-card-analytics/frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Credit Card Analytics Dashboard

A React-based single-page application (SPA) for visualizing and analyzing credit card transaction data. This internal operations portal provides comprehensive analytics for customer spending patterns and transaction history.

## Features

- **Customer Analytics Search**: Enter customer ID and date range to view detailed analytics
- **Spending by Category**: Interactive line chart showing weekly spending trends across different categories
- **Daily Spending Overview**: Line chart visualizing day-by-day spending patterns
- **Transaction History**: Detailed table view of all transactions with filtering capabilities

## Tech Stack

- **React 18** - UI framework
- **Vite** - Build tool and dev server
- **Urql** - GraphQL client for API communication
- **Recharts** - Data visualization library
- **date-fns** - Date formatting and manipulation

## Prerequisites

- Node.js (v16 or higher)
- npm or yarn
- GraphQL API running at `http://localhost:8888/v1/graphql`

## Installation

```bash
npm install
```

## Configuration

The application uses environment variables for configuration. Create a `.env` file in the root directory:

```bash
# .env
VITE_GRAPHQL_URL=http://localhost:8888/v1/graphql
```

For production, update `.env.production`:

```bash
# .env.production
VITE_GRAPHQL_URL=https://your-production-api.com/v1/graphql
```

**Environment Variables:**

- `VITE_GRAPHQL_URL` - GraphQL API endpoint (required)
- Development default: `http://localhost:8888/v1/graphql`
- Production: Configure in `.env.production`

## Development

Start the development server:

```bash
npm run dev
```

The application will be available at `http://localhost:5173/`

## Build

Create a production build:

```bash
npm run build
```

Preview the production build:

```bash
npm run preview
```

## GraphQL API

The application connects to a GraphQL API with the following queries:

### SpendingByCategory
Returns weekly spending aggregated by category for a customer.

### SpendingByDay
Returns daily spending totals for a customer within a specified date range.

### Transactions
Returns detailed transaction records for a customer within a specified date range.

## Default Configuration

- **GraphQL Endpoint**: `http://localhost:8888/v1/graphql`
- **Default Date Range**: 2024-05-05 to 2024-06-05
- **Default Customer ID**: 1

## Project Structure

```
frontend/
├── src/
│ ├── App.jsx # Main application component
│ ├── App.css # Application styles
│ ├── main.jsx # Application entry point with GraphQL provider
│ └── index.css # Global styles
├── index.html # HTML template
├── vite.config.js # Vite configuration
└── package.json # Dependencies and scripts
```

## Claude Code Prompt

In the folder @finance-credit-card-chatbot/credit-card-analytics/frontend implement a SPA (Vite + React + Apollo/Urql) that is a frontend to the graphql api defined in
@finance-credit-card-chatbot/credit-card-analytics/schema.v1.graphqls which is running on http://localhost:8888/v1/graphql. Make it look like an internal banking app where
the user enters customerid and (optionally) a time range with date selectors for fromTime and toTime. By default, toTime is 2024-06-05 and fromTime is 2024-05-05. It shows
visualizations for all three queries: spending by category as a line chart (with a different line for each category and week on the x axis, spending on the y), spending by
day as a line chart (day x-axis, spending y) and transactions as a table. Use suitable visualization libraries to keep the code concise. Test the SPA to make sure it works
against the running api.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>frontend</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Loading
Loading