Remy

An AI coding agent that searches your codebase, edits real files, runs checks, and prepares work for review on desktop or CLI.

Available on macOS and Windows

Customer Analytics Dashboard···
ExplorerChangesTerminalBrowserLLM trace
Build a customer analytics dashboard with charts, filters, and date ranges.
I’ll build a reviewable analytics dashboard with data models, metrics APIs, interactive charts, and date-range filtering.
0 / 5 tasks done
Set up project structure
Add event, user, and metric models
Create metrics, filter, and export APIs
Build dashboard and chart detail views
Add setup docs and tests
Build summary
18 files changed
5 pages created
4 API routes added
12 tests passed
All tasks are complete. The analytics dashboard is ready for review.
Ask for follow-up changes
+ Dynamic
Localcustomer analytics0% context
Loading diff…
No files changed
customer-analytics-dashboard
backend
app
__init__.py
database.py
models.py
main.py
schemas.py
risk_engine.py
eligibility_engine.py
routes
applications.py
documents.py
requirements.txt
tests
test_applications.py
test_risk_engine.py
frontend
package.json
vite.config.ts
index.html
src
App.tsx
pages
ApplicationForm.tsx
Dashboard.tsx
LoanDetails.tsx
AmortizationView.tsx
README.md
.env.example
index.htmlmain.py
1”””
2Loan Approval System – FastAPI Application
3===========================================
4Private-lender personal loan management with:
5  • Borrower application intake
6  • Credit / risk assessment engine
7  • Auto-approval eligibility rules
8  • Document upload & management
9  • Amortization schedule generation
10  • Dashboard analytics
11”””
12
13from __future__ import annotations
14
15import os
16import uuid
17from typing import List
18
19from fastapi import FastAPI, Depends, HTTPException, UploadFile, File, Query
20from fastapi.middleware.cors import CORSMiddleware
21from fastapi.staticfiles import StaticFiles
22from sqlalchemy.orm import Session
23from sqlalchemy import func
24
25from .database import Base, engine, get_db
26from .models import Borrower, Loan, Document, AmortizationEntry, LoanStatus
27from .schemas import (
28    BorrowerOut,
29    LoanApplicationCreate,
30    LoanOut,
31    LoanStatusUpdate,
32    RiskAssessmentOut,
33    AmortizationEntryOut,
34    DocumentOut,
35    DashboardStats,
36)
37from .risk_engine import assess_risk
38from .amortization import generate_schedule
39
40# ---------------------------------------------------------------------------
41# Bootstrap
42# ---------------------------------------------------------------------------
43
44Base.metadata.create_all(bind=engine)
45
46UPLOAD_DIR = os.path.join(os.path.dirname(__file__), ”..”, ”uploads”)
47os.makedirs(UPLOAD_DIR, exist_ok=True)
48
49app = FastAPI(
50    title=”Loan Approval System”,
51    version=”1.0.0”,
52    description=”Private-lender personal loan management API”,
53)
54
55app.add_middleware(
56    CORSMiddleware,
57    allow_origins=[”*”],
58    allow_credentials=True,
59    allow_methods=[”*”],
60    allow_headers=[”*”],
61)
62
63
64# ---------------------------------------------------------------------------
65# HEALTH
66# ---------------------------------------------------------------------------
67
68@app.get(”/api/health”)
69def health_check():
70    return {”status”: ”ok”}
71
72
73# ---------------------------------------------------------------------------
74# LOAN APPLICATION (combined borrower + loan creation)
75# ---------------------------------------------------------------------------
76
77@app.post(”/api/applications”, response_model=LoanOut, status_code=201)
78def create_application(payload: LoanApplicationCreate, db: Session = Depends(get_db)):
79    ”””Submit a new loan application (creates borrower if needed).”””
80
81    # Upsert borrower by email
82    borrower = db.query(Borrower).filter(Borrower.email == payload.email).first()
83    if borrower:
84        # Update mutable fields
85        for field in (
86            ”first_name”, ”last_name”, ”phone”, ”date_of_birth”, ”address”,
87            ”employment_type”, ”employer_name”, ”monthly_income”,
88            ”existing_debt”, ”credit_score”,
89        ):
90            setattr(borrower, field, getattr(payload, field))
91    else:
92        borrower = Borrower(
93            first_name=payload.first_name,
94            last_name=payload.last_name,
95            email=payload.email,
96            phone=payload.phone,
97            date_of_birth=payload.date_of_birth,
98            address=payload.address,
99            employment_type=payload.employment_type,
100            employer_name=payload.employer_name,
101            monthly_income=payload.monthly_income,
102            existing_debt=payload.existing_debt,
103            credit_score=payload.credit_score,
104        )
105        db.add(borrower)
106        db.flush()
107
108    # Run risk assessment
109    assessment = assess_risk(
110        credit_score=payload.credit_score,
111        monthly_income=payload.monthly_income,
112        existing_debt=payload.existing_debt,
113        employment_type=payload.employment_type,
114        dob_str=payload.date_of_birth,
115        amount_requested=payload.amount_requested,
116        term_months=payload.term_months,
117    )
118
119    # Decide auto-approval
120    if assessment.eligible and assessment.risk_score <= 30:
121        status = LoanStatus.APPROVED
122        amount_approved = assessment.max_loan_amount
123        interest_rate = assessment.suggested_interest_rate
124        rejection_reason = None
125    elif not assessment.eligible:
126        status = LoanStatus.REJECTED
127        amount_approved = None
128        interest_rate = None
129        rejection_reason = ”Auto-rejected: ” + ”; ”.join(
130            f for f in assessment.factors if ”excessive” in f.lower()
131            or ”poor” in f.lower() or ”unemployed” in f.lower()
132            or ”too high” in f.lower()
133        ) or ”Does not meet eligibility criteria”
134    else:
135        status = LoanStatus.UNDER_REVIEW
136        amount_approved = None
137        interest_rate = assessment.suggested_interest_rate
138        rejection_reason = None
139
140    loan = Loan(
141        borrower_id=borrower.id,
142        amount_requested=payload.amount_requested,
143        amount_approved=amount_approved,
144        interest_rate=interest_rate,
145        term_months=payload.term_months,
146        purpose=payload.purpose,
147        status=status,
148        risk_level=assessment.risk_level,
149        risk_score=assessment.risk_score,
150        rejection_reason=rejection_reason,
151    )
152    db.add(loan)
153    db.flush() # ensure loan.id is available
154
155    # Generate amortization for approved loans
156    if status == LoanStatus.APPROVED and amount_approved and interest_rate:
157        entries = generate_schedule(amount_approved, interest_rate, payload.term_months)
158        for entry in entries:
159            db.add(AmortizationEntry(
160                loan_id=loan.id,
161                month_number=entry.month_number,
162                payment_date=entry.payment_date,
163                beginning_balance=entry.beginning_balance,
164                monthly_payment=entry.monthly_payment,
165                principal=entry.principal,
166                interest=entry.interest,
167                ending_balance=entry.ending_balance,
168            ))
169
170    db.commit()
171    db.refresh(loan)
172    return loan
173
174
175# ---------------------------------------------------------------------------
176# LOANS – list / detail / update
177# ---------------------------------------------------------------------------
178
179@app.get(”/api/loans”, response_model=List[LoanOut])
180def list_loans(
181    status: LoanStatus | None = None,
182    skip: int = Query(0, ge=0),
183    limit: int = Query(50, ge=1, le=200),
184    db: Session = Depends(get_db),
185):
186    q = db.query(Loan)
187    if status:
188        q = q.filter(Loan.status == status)
189    return q.order_by(Loan.created_at.desc()).offset(skip).limit(limit).all()
190
191
192@app.get(”/api/loans/{loan_id}”, response_model=LoanOut)
193def get_loan(loan_id: str, db: Session = Depends(get_db)):
194    loan = db.query(Loan).filter(Loan.id == loan_id).first()
195    if not loan:
196        raise HTTPException(404, ”Loan not found”)
197    return loan
198
199
200@app.patch(”/api/loans/{loan_id}”, response_model=LoanOut)
201def update_loan_status(loan_id: str, payload: LoanStatusUpdate, db: Session = Depends(get_db)):
202    ”””Lender manually approves / rejects / disburses a loan.”””
203    loan = db.query(Loan).filter(Loan.id == loan_id).first()
204    if not loan:
205        raise HTTPException(404, ”Loan not found”)
206
207    loan.status = payload.status
208    if payload.amount_approved is not None:
209        loan.amount_approved = payload.amount_approved
210    if payload.interest_rate is not None:
211        loan.interest_rate = payload.interest_rate
212    if payload.rejection_reason is not None:
213        loan.rejection_reason = payload.rejection_reason
214
215    # Generate amortization on approval if not yet present
216    if payload.status == LoanStatus.APPROVED and loan.amount_approved and loan.interest_rate:
217        existing = db.query(AmortizationEntry).filter(AmortizationEntry.loan_id == loan_id).count()
218        if existing == 0:
219            entries = generate_schedule(loan.amount_approved, loan.interest_rate, loan.term_months)
220            for entry in entries:
221                db.add(AmortizationEntry(
222                    loan_id=loan.id,
223                    month_number=entry.month_number,
224                    payment_date=entry.payment_date,
225                    beginning_balance=entry.beginning_balance,
226                    monthly_payment=entry.monthly_payment,
227                    principal=entry.principal,
228                    interest=entry.interest,
229                    ending_balance=entry.ending_balance,
230                ))
231
232    db.commit()
233    db.refresh(loan)
234    return loan
235
236
237# ---------------------------------------------------------------------------
238# RISK ASSESSMENT (standalone – ”what-if” check)
239# ---------------------------------------------------------------------------
240
241@app.post(”/api/risk-assessment”, response_model=RiskAssessmentOut)
242def run_risk_assessment(payload: LoanApplicationCreate):
243    return assess_risk(
244        credit_score=payload.credit_score,
245        monthly_income=payload.monthly_income,
246        existing_debt=payload.existing_debt,
247        employment_type=payload.employment_type,
248        dob_str=payload.date_of_birth,
249        amount_requested=payload.amount_requested,
250        term_months=payload.term_months,
251    )
252
253
254# ---------------------------------------------------------------------------
255# AMORTIZATION SCHEDULE
256# ---------------------------------------------------------------------------
257
258@app.get(”/api/loans/{loan_id}/amortization”, response_model=List[AmortizationEntryOut])
259def get_amortization(loan_id: str, db: Session = Depends(get_db)):
260    loan = db.query(Loan).filter(Loan.id == loan_id).first()
261    if not loan:
262        raise HTTPException(404, ”Loan not found”)
263    return (
264        db.query(AmortizationEntry)
265        .filter(AmortizationEntry.loan_id == loan_id)
266        .order_by(AmortizationEntry.month_number)
267        .all()
268    )
269
270
271# ---------------------------------------------------------------------------
272# DOCUMENT UPLOAD
273# ---------------------------------------------------------------------------
274
275@app.post(”/api/loans/{loan_id}/documents”, response_model=DocumentOut, status_code=201)
276async def upload_document(loan_id: str, file: UploadFile = File(...), db: Session = Depends(get_db)):
277    loan = db.query(Loan).filter(Loan.id == loan_id).first()
278    if not loan:
279        raise HTTPException(404, ”Loan not found”)
280
281    ext = os.path.splitext(file.filename or ”file”)[1]
282    saved_name = f”{uuid.uuid4().hex}{ext}”
283    path = os.path.join(UPLOAD_DIR, saved_name)
284
285    contents = await file.read()
286    with open(path, ”wb”) as f:
287        f.write(contents)
288
289    doc = Document(
290        loan_id=loan_id,
291        filename=saved_name,
292        original_name=file.filename or ”unknown”,
293        content_type=file.content_type,
294    )
295    db.add(doc)
296    db.commit()
297    db.refresh(doc)
298    return doc
299
300
301@app.get(”/api/loans/{loan_id}/documents”, response_model=List[DocumentOut])
302def list_documents(loan_id: str, db: Session = Depends(get_db)):
303    return db.query(Document).filter(Document.loan_id == loan_id).all()
304
305
306# ---------------------------------------------------------------------------
307# BORROWERS
308# ---------------------------------------------------------------------------
309
310@app.get(”/api/borrowers”, response_model=List[BorrowerOut])
311def list_borrowers(db: Session = Depends(get_db)):
312    return db.query(Borrower).order_by(Borrower.created_at.desc()).all()
313
314
315@app.get(”/api/borrowers/{borrower_id}”, response_model=BorrowerOut)
316def get_borrower(borrower_id: str, db: Session = Depends(get_db)):
317    b = db.query(Borrower).filter(Borrower.id == borrower_id).first()
318    if not b:
319        raise HTTPException(404, ”Borrower not found”)
320    return b
321
322
323# ---------------------------------------------------------------------------
324# DASHBOARD / ANALYTICS
325# ---------------------------------------------------------------------------
326
327@app.get(”/api/dashboard”, response_model=DashboardStats)
328def dashboard(db: Session = Depends(get_db)):
329    total = db.query(func.count(Loan.id)).scalar() or 0
330    pending = db.query(func.count(Loan.id)).filter(Loan.status == LoanStatus.PENDING).scalar() or 0
331    under_review = db.query(func.count(Loan.id)).filter(Loan.status == LoanStatus.UNDER_REVIEW).scalar() or 0
332    approved = db.query(func.count(Loan.id)).filter(
333        Loan.status.in_([LoanStatus.APPROVED, LoanStatus.DISBURSED])
334    ).scalar() or 0
335    rejected = db.query(func.count(Loan.id)).filter(Loan.status == LoanStatus.REJECTED).scalar() or 0
336
337    total_disbursed = (
338        db.query(func.coalesce(func.sum(Loan.amount_approved), 0))
339        .filter(Loan.status == LoanStatus.DISBURSED)
340        .scalar()
341    )
342    avg_amount = db.query(func.coalesce(func.avg(Loan.amount_requested), 0)).scalar()
343    avg_risk = db.query(func.coalesce(func.avg(Loan.risk_score), 0)).scalar()
344
345    return DashboardStats(
346        total_applications=total,
347        pending=pending + under_review,
348        approved=approved,
349        rejected=rejected,
350        total_disbursed_amount=round(float(total_disbursed), 2),
351        average_loan_amount=round(float(avg_amount), 2),
352        average_risk_score=round(float(avg_risk), 2),
353    )

Autonomous agents

Hand off the task. The agent runs the whole loop.

  • Plans the approach and shows you the path before it touches anything
  • Works through the task end to end without step-by-step prompting
  • Catches failures and revises before handing back
  • Returns a reviewable summary of what changed and why
agent · sessionIn progress
Reading filesWaiting
Plan readyWaiting
Files editedWaiting
Checks runningWaiting
Preparing summaryWaiting
Ready for reviewWaiting
corepass-code.html+3 -11
        <a href="#" class="btn btn-nav"><span class="btn-text">Try Remy</span></a>
        <a href="#" class="btn btn-nav" data-download><span class="btn-text">Download for macOS</span></a>
— edit 2
            <a href="#" class="btn btn-lg btn-white"><span>Download for macOS</span></a>
            <a href="#" class="btn btn-lg btn-ghost btn-arrow-after">

Real implementation

Real code in your repo, not generated screens.

  • Produces working features, not clickable mockups
  • Edits your actual codebase, and the result runs
  • Refactors across files with behavior intact
  • Lands with test coverage, so the change is verified, not assumed

Work across desktop, CLI, and cloud.

Start in the desktop app, run tasks from the terminal, or let longer work continue in the cloud.

CLI

Start tasks, inspect progress, and run agents from your terminal.

Corepass cloud

Let longer tasks keep running without using your local machine.

Run in cloud
Context per task
Focused search loads only the files each task needs.
Without focused searchRemy
Context loaded
100%
32%
68% less context
Startup time
100%
45%
2.2× faster start
Task cost
100%
38%
62% lower cost
Quick Search finds the right files before the agent starts.

Lower cost per task

  • Pay for the context the task needs, not the whole repo
  • Skip the broad scan and start editing sooner
  • Get more done per dollar, not per prompt
  • Predictable cost even as the codebase grows

Instant revert

  • Let the agent try larger changes, undo is always one click
  • See every change before it becomes permanent
  • Roll back a full run to the exact state you started from
  • Run more experiments without touching your working branch
agent sessioncompleted
Build a pricing page from an existing design system
6 files changed+148 -32
Checkspassed
pricing.html+148 -32
  <section class="pricing-hero">
    <h1>Plans that scale with you</h1>
  <section class="placeholder">
Working branch restored
main · clean · no files changed
Describe a task or ask a question

The right model for each task

Match the model's power to the work, not to the price tag.

  • A lightweight model for small edits and quick fixes
  • A stronger model for refactors and long runs
  • Dynamic picks for you when you would rather not choose
  • Search every model and switch in one click

Start building with Remy.

Turn ideas into working software with an AI coding agent that searches, builds, tests, and keeps you in control.

bash
corepass code "Build the usage dashboard, run checks, and prepare the changes"