theaniketgiri commited on
Commit
3f61e65
·
0 Parent(s):
.gitignore ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual Environment
24
+ venv/
25
+ env/
26
+ ENV/
27
+
28
+ # IDE
29
+ .idea/
30
+ .vscode/
31
+ *.swp
32
+ *.swo
33
+
34
+ # Database
35
+ *.db
36
+ *.sqlite3
37
+
38
+ # Environment variables
39
+ .env
40
+ .env.local
41
+
42
+ # Logs
43
+ *.log
44
+
45
+ # Hugging Face specific
46
+ .cache/
47
+ transformers/
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ # Set up a new user named "user" with user ID 1000
4
+ RUN useradd -m -u 1000 user
5
+
6
+ # Switch to the "user" user
7
+ USER user
8
+
9
+ # Set home to the user's home directory
10
+ ENV HOME=/home/user \
11
+ PATH=/home/user/.local/bin:$PATH
12
+
13
+ # Set the working directory to the user's home directory
14
+ WORKDIR $HOME/app
15
+
16
+ # Try and run pip command after setting the user with `USER user` to avoid permission issues with Python
17
+ RUN pip install --no-cache-dir --upgrade pip
18
+
19
+ # Copy the current directory contents into the container at $HOME/app setting the owner to the user
20
+ COPY --chown=user . $HOME/app
21
+
22
+ # Install dependencies
23
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
24
+
25
+ # Create a directory for model cache
26
+ RUN mkdir -p $HOME/.cache/huggingface
27
+ RUN chmod 777 $HOME/.cache/huggingface
28
+
29
+ # Expose the port the app runs on
30
+ EXPOSE 7860
31
+
32
+ # Command to run the application
33
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
LICENSE ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Synthex AI - Commercial License
2
+
3
+ Copyright (c) 2024 Synthex AI
4
+
5
+ This software and associated documentation files (the "Software") are proprietary and confidential.
6
+ The Software is protected by copyright laws and international copyright treaties, as well as other
7
+ intellectual property laws and treaties.
8
+
9
+ TERMS AND CONDITIONS
10
+
11
+ 1. License Grant
12
+ This license grants you a limited, non-exclusive, non-transferable license to use the Software
13
+ solely for your internal business purposes, subject to the terms and conditions of this Agreement.
14
+
15
+ 2. Restrictions
16
+ You may not:
17
+ - Copy, modify, or create derivative works of the Software
18
+ - Reverse engineer, decompile, or disassemble the Software
19
+ - Remove or alter any proprietary notices or labels on the Software
20
+ - Use the Software for any illegal purpose
21
+ - Transfer, sublicense, or resell the Software
22
+
23
+ 3. Proprietary Rights
24
+ The Software and all copies, modifications, and derivative works are owned by Synthex AI and
25
+ are protected by copyright, trade secret, and other intellectual property laws.
26
+
27
+ 4. Confidentiality
28
+ You agree to maintain the confidentiality of the Software and not disclose it to any third party
29
+ without Synthex AI's prior written consent.
30
+
31
+ 5. Warranty Disclaimer
32
+ THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. SYNTHEX AI DISCLAIMS ALL
33
+ WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34
+ FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
35
+
36
+ 6. Limitation of Liability
37
+ IN NO EVENT SHALL SYNTHEX AI BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM,
38
+ OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39
+
40
+ 7. Termination
41
+ This license is effective until terminated. Your rights under this license will terminate
42
+ automatically without notice if you fail to comply with any of its terms.
43
+
44
+ 8. Governing Law
45
+ This Agreement shall be governed by and construed in accordance with the laws of the State of
46
+ Delaware, without regard to its conflict of law provisions.
47
+
48
+ 9. Contact Information
49
+ For licensing inquiries, please contact:
50
+ Synthex AI
51
52
+ Website: https://synthex.ai
53
+
54
+ By using the Software, you acknowledge that you have read this Agreement, understand it, and agree
55
+ to be bound by its terms and conditions.
README.md ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Synthex Medical Text Generator
3
+ emoji: 🏥
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: docker
7
+ app_port: 7860
8
+ ---
9
+
10
+ # Synthex Medical Text Generator
11
+
12
+ A synthetic medical text generator built with FastAPI and Hugging Face Transformers.
13
+
14
+ ## Features
15
+
16
+ - Generate synthetic medical text data
17
+ - Multiple record types:
18
+ - Clinical Notes
19
+ - Discharge Summaries
20
+ - Lab Reports
21
+ - Prescriptions
22
+ - HIPAA-compliant fictional data
23
+ - RESTful API endpoints
24
+
25
+ ## API Endpoints
26
+
27
+ - `GET /`: Get API information
28
+ - `POST /generate`: Generate medical records
29
+ - `GET /health`: Health check endpoint
30
+
31
+ ## Example Usage
32
+
33
+ ```bash
34
+ # Generate a clinical note
35
+ curl -X POST "https://theaniketgiri-synthex.hf.space/generate" \
36
+ -H "Content-Type: application/json" \
37
+ -d '{"record_type": "clinical_note", "count": 1}'
38
+ ```
39
+
40
+ ## Technical Details
41
+
42
+ - Built with FastAPI
43
+ - Uses Bio_ClinicalBERT model from Hugging Face
44
+ - Docker container with Python 3.9
45
+ - Exposed on port 7860
46
+
47
+ ## License
48
+
49
+ MIT License
app.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from pydantic import BaseModel
4
+ from typing import List, Optional
5
+ from datetime import datetime
6
+ from transformers import AutoTokenizer, AutoModelForCausalLM
7
+ import torch
8
+ import json
9
+
10
+ app = FastAPI(
11
+ title="Synthex Medical Text Generator",
12
+ description="Generate synthetic medical text data for research and development purposes.",
13
+ version="1.0.0"
14
+ )
15
+
16
+ # Add CORS middleware
17
+ app.add_middleware(
18
+ CORSMiddleware,
19
+ allow_origins=["*"], # Allows all origins
20
+ allow_credentials=True,
21
+ allow_methods=["*"], # Allows all methods
22
+ allow_headers=["*"], # Allows all headers
23
+ )
24
+
25
+ # Initialize model and tokenizer
26
+ model = None
27
+ tokenizer = None
28
+
29
+ def load_model():
30
+ global model, tokenizer
31
+ if model is None or tokenizer is None:
32
+ model_name = "emilyalsentzer/Bio_ClinicalBERT"
33
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
34
+ model = AutoModelForCausalLM.from_pretrained(model_name)
35
+ return model, tokenizer
36
+
37
+ class GenerateRequest(BaseModel):
38
+ record_type: str
39
+ count: int = 1
40
+
41
+ class MedicalRecord(BaseModel):
42
+ type: str
43
+ content: str
44
+ generated_at: str
45
+
46
+ @app.get("/")
47
+ def read_root():
48
+ return {
49
+ "name": "Synthex Medical Text Generator",
50
+ "version": "1.0.0",
51
+ "description": "Generate synthetic medical text data for research and development purposes."
52
+ }
53
+
54
+ @app.post("/generate", response_model=List[MedicalRecord])
55
+ async def generate_records(request: GenerateRequest):
56
+ try:
57
+ model, tokenizer = load_model()
58
+
59
+ records = []
60
+ for i in range(request.count):
61
+ # Generate text using the model
62
+ input_text = f"Generate a {request.record_type}:"
63
+ inputs = tokenizer(input_text, return_tensors="pt", max_length=512, truncation=True)
64
+ outputs = model.generate(
65
+ inputs["input_ids"],
66
+ max_length=200,
67
+ num_return_sequences=1,
68
+ temperature=0.7,
69
+ top_p=0.9,
70
+ do_sample=True
71
+ )
72
+ generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
73
+
74
+ # Create record
75
+ record = MedicalRecord(
76
+ type=request.record_type,
77
+ content=generated_text,
78
+ generated_at=datetime.now().isoformat()
79
+ )
80
+ records.append(record)
81
+
82
+ return records
83
+
84
+ except Exception as e:
85
+ raise HTTPException(status_code=500, detail=str(e))
86
+
87
+ @app.get("/health")
88
+ def health_check():
89
+ return {"status": "healthy"}
app/api/admin.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, status
2
+ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
3
+ from sqlalchemy.orm import Session
4
+ from app.core.database import get_db
5
+ from app.models.models import BetaApplication, ApplicationStatus
6
+ from app.core.config import settings
7
+ from datetime import datetime, timedelta
8
+ from jose import JWTError, jwt
9
+ from pydantic import BaseModel
10
+ from typing import List, Optional
11
+
12
+ router = APIRouter()
13
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
14
+
15
+ class Token(BaseModel):
16
+ access_token: str
17
+ token_type: str
18
+
19
+ class TokenData(BaseModel):
20
+ email: Optional[str] = None
21
+
22
+ class ApplicationUpdate(BaseModel):
23
+ status: ApplicationStatus
24
+
25
+ class BetaApplicationResponse(BaseModel):
26
+ id: str
27
+ email: str
28
+ company: str
29
+ use_case: str
30
+ status: str
31
+ created_at: datetime
32
+ updated_at: Optional[datetime] = None
33
+
34
+ class Config:
35
+ from_attributes = True
36
+
37
+ class BetaApplicationList(BaseModel):
38
+ applications: List[BetaApplicationResponse]
39
+
40
+ def create_access_token(data: dict):
41
+ to_encode = data.copy()
42
+ expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
43
+ to_encode.update({"exp": expire})
44
+ encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm="HS256")
45
+ return encoded_jwt
46
+
47
+ async def get_current_admin(token: str = Depends(oauth2_scheme)):
48
+ credentials_exception = HTTPException(
49
+ status_code=status.HTTP_401_UNAUTHORIZED,
50
+ detail="Could not validate credentials",
51
+ headers={"WWW-Authenticate": "Bearer"},
52
+ )
53
+ try:
54
+ payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
55
+ email: str = payload.get("sub")
56
+ if email is None or email != settings.ADMIN_EMAIL:
57
+ raise credentials_exception
58
+ token_data = TokenData(email=email)
59
+ except JWTError:
60
+ raise credentials_exception
61
+ return token_data
62
+
63
+ @router.post("/login", response_model=Token)
64
+ async def login(form_data: OAuth2PasswordRequestForm = Depends()):
65
+ if form_data.username != settings.ADMIN_EMAIL or form_data.password != settings.ADMIN_PASSWORD:
66
+ raise HTTPException(
67
+ status_code=status.HTTP_401_UNAUTHORIZED,
68
+ detail="Incorrect email or password",
69
+ headers={"WWW-Authenticate": "Bearer"},
70
+ )
71
+ access_token = create_access_token(data={"sub": form_data.username})
72
+ return {"access_token": access_token, "token_type": "bearer"}
73
+
74
+ @router.get("/applications")
75
+ async def get_applications(
76
+ db: Session = Depends(get_db),
77
+ current_admin: TokenData = Depends(get_current_admin)
78
+ ):
79
+ applications = db.query(BetaApplication).all()
80
+ return {
81
+ "applications": [
82
+ {
83
+ "id": app.id,
84
+ "email": app.email,
85
+ "company": app.company,
86
+ "use_case": app.use_case,
87
+ "status": app.status.value,
88
+ "created_at": app.created_at.isoformat(),
89
+ "updated_at": app.updated_at.isoformat() if app.updated_at else None
90
+ }
91
+ for app in applications
92
+ ]
93
+ }
94
+
95
+ @router.patch("/applications/{application_id}")
96
+ async def update_application(
97
+ application_id: str,
98
+ update: ApplicationUpdate,
99
+ db: Session = Depends(get_db),
100
+ current_admin: TokenData = Depends(get_current_admin)
101
+ ):
102
+ application = db.query(BetaApplication).filter(
103
+ BetaApplication.id == application_id
104
+ ).first()
105
+
106
+ if not application:
107
+ raise HTTPException(
108
+ status_code=404,
109
+ detail="Application not found"
110
+ )
111
+
112
+ application.status = update.status
113
+ db.commit()
114
+ db.refresh(application)
115
+
116
+ return {"message": "Application updated successfully"}
app/api/auth.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, status
2
+ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
3
+ from jose import JWTError, jwt
4
+ from datetime import datetime, timedelta
5
+ from pydantic import BaseModel
6
+ from typing import Optional
7
+ from app.core.config import settings
8
+
9
+ router = APIRouter()
10
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
11
+
12
+ class Token(BaseModel):
13
+ access_token: str
14
+ token_type: str
15
+
16
+ class TokenData(BaseModel):
17
+ username: Optional[str] = None
18
+
19
+ def create_access_token(data: dict):
20
+ to_encode = data.copy()
21
+ expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
22
+ to_encode.update({"exp": expire})
23
+ encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm="HS256")
24
+ return encoded_jwt
25
+
26
+ async def get_current_user(token: str = Depends(oauth2_scheme)):
27
+ credentials_exception = HTTPException(
28
+ status_code=status.HTTP_401_UNAUTHORIZED,
29
+ detail="Could not validate credentials",
30
+ headers={"WWW-Authenticate": "Bearer"},
31
+ )
32
+ try:
33
+ payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
34
+ username: str = payload.get("sub")
35
+ if username is None:
36
+ raise credentials_exception
37
+ token_data = TokenData(username=username)
38
+ except JWTError:
39
+ raise credentials_exception
40
+ return token_data
41
+
42
+ @router.post("/token", response_model=Token)
43
+ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
44
+ if form_data.username != settings.ADMIN_EMAIL or form_data.password != settings.ADMIN_PASSWORD:
45
+ raise HTTPException(
46
+ status_code=status.HTTP_401_UNAUTHORIZED,
47
+ detail="Incorrect username or password",
48
+ headers={"WWW-Authenticate": "Bearer"},
49
+ )
50
+ access_token = create_access_token(data={"sub": form_data.username})
51
+ return {"access_token": access_token, "token_type": "bearer"}
app/api/beta.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, Body
2
+ from sqlalchemy.orm import Session
3
+ from app.core.database import get_db
4
+ from app.models.models import BetaApplication, ApplicationStatus
5
+ from pydantic import BaseModel, Field
6
+ import uuid
7
+ from datetime import datetime
8
+
9
+ router = APIRouter()
10
+
11
+ class BetaApplicationCreate(BaseModel):
12
+ email: str
13
+ company: str
14
+ useCase: str = Field(..., alias="use_case")
15
+
16
+ class Config:
17
+ populate_by_name = True
18
+
19
+ class BetaApplicationResponse(BaseModel):
20
+ id: str
21
+ email: str
22
+ company: str
23
+ useCase: str = Field(..., alias="use_case")
24
+ status: str
25
+ createdAt: datetime = Field(..., alias="created_at")
26
+ updatedAt: datetime | None = Field(None, alias="updated_at")
27
+
28
+ class Config:
29
+ from_attributes = True
30
+ populate_by_name = True
31
+
32
+ class VerifyApplicationRequest(BaseModel):
33
+ application_id: str
34
+
35
+ @router.post("/apply", response_model=BetaApplicationResponse)
36
+ def create_application(
37
+ application: BetaApplicationCreate,
38
+ db: Session = Depends(get_db)
39
+ ):
40
+ # Check if email already exists
41
+ existing = db.query(BetaApplication).filter(
42
+ BetaApplication.email == application.email
43
+ ).first()
44
+
45
+ if existing:
46
+ raise HTTPException(
47
+ status_code=400,
48
+ detail="Email already registered"
49
+ )
50
+
51
+ # Create new application
52
+ db_application = BetaApplication(
53
+ id=str(uuid.uuid4()),
54
+ email=application.email,
55
+ company=application.company,
56
+ use_case=application.useCase,
57
+ status=ApplicationStatus.PENDING
58
+ )
59
+
60
+ db.add(db_application)
61
+ db.commit()
62
+ db.refresh(db_application)
63
+
64
+ return db_application
65
+
66
+ @router.post("/verify")
67
+ def verify_application(
68
+ request: VerifyApplicationRequest,
69
+ db: Session = Depends(get_db)
70
+ ):
71
+ application = db.query(BetaApplication).filter(
72
+ BetaApplication.id == request.application_id
73
+ ).first()
74
+
75
+ if not application:
76
+ raise HTTPException(
77
+ status_code=404,
78
+ detail="Application not found"
79
+ )
80
+
81
+ if application.status != ApplicationStatus.APPROVED:
82
+ raise HTTPException(
83
+ status_code=403,
84
+ detail="Application not approved"
85
+ )
86
+
87
+ return {"message": "Access granted"}
app/api/data.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException
2
+ from sqlalchemy.orm import Session
3
+ from app.core.database import get_db
4
+ from app.models.models import BetaApplication, ApplicationStatus
5
+ from app.services.data_generator import DataGenerator
6
+ from pydantic import BaseModel
7
+ from typing import List, Dict, Any
8
+
9
+ router = APIRouter()
10
+ data_generator = DataGenerator()
11
+
12
+ class GenerateDataRequest(BaseModel):
13
+ count: int = 10
14
+ record_types: List[str] = ["lab_report", "clinical_note", "discharge_summary"]
15
+
16
+ class GenerateDataResponse(BaseModel):
17
+ records: List[Dict[str, Any]]
18
+
19
+ @router.post("/generate", response_model=GenerateDataResponse)
20
+ async def generate_data(
21
+ request: GenerateDataRequest,
22
+ db: Session = Depends(get_db)
23
+ ):
24
+ # Verify application status
25
+ application = db.query(BetaApplication).filter(
26
+ BetaApplication.status == ApplicationStatus.APPROVED
27
+ ).first()
28
+
29
+ if not application:
30
+ raise HTTPException(
31
+ status_code=403,
32
+ detail="No approved application found"
33
+ )
34
+
35
+ # Generate records
36
+ records = data_generator.generate_records(request.count, request.record_types)
37
+
38
+ return {"records": records}
app/api/generator.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import List, Optional
4
+ import random
5
+
6
+ router = APIRouter()
7
+
8
+ class GenerationRequest(BaseModel):
9
+ recordType: str
10
+ quantity: int
11
+
12
+ class GeneratedRecord(BaseModel):
13
+ id: str
14
+ content: str
15
+ recordType: str
16
+
17
+ # Templates for different record types
18
+ TEMPLATES = {
19
+ "Clinical Note": [
20
+ "Patient presents with {symptom}. Vital signs: BP {bp}, HR {hr}, Temp {temp}. Assessment: {assessment}. Plan: {plan}.",
21
+ "Follow-up visit for {condition}. Patient reports {symptom}. Current medications: {meds}. Plan: {plan}."
22
+ ],
23
+ "Discharge Summary": [
24
+ "Patient admitted for {condition}. Treatment included {treatment}. Discharged on {date} with instructions to {instructions}.",
25
+ "Hospital course: {course}. Discharge medications: {meds}. Follow-up with {specialist} in {timeframe}."
26
+ ],
27
+ "Lab Report": [
28
+ "Lab results for {test}: {result}. Reference range: {range}. Interpretation: {interpretation}.",
29
+ "Blood work shows {finding}. Additional tests recommended: {tests}."
30
+ ],
31
+ "Prescription": [
32
+ "Prescribed {medication} {dose} {frequency} for {condition}. Duration: {duration}. Instructions: {instructions}.",
33
+ "Medication: {medication}. Dosage: {dose}. Take {frequency} with {food}. Refills: {refills}."
34
+ ],
35
+ "Patient Intake": [
36
+ "New patient intake. Chief complaint: {complaint}. History: {history}. Allergies: {allergies}. Current medications: {meds}.",
37
+ "Patient demographics: {demographics}. Insurance: {insurance}. Emergency contact: {contact}."
38
+ ]
39
+ }
40
+
41
+ # Placeholder data for template variables
42
+ PLACEHOLDERS = {
43
+ "symptom": ["headache", "fever", "cough", "fatigue", "nausea"],
44
+ "bp": ["120/80", "130/85", "118/75", "125/82"],
45
+ "hr": ["72", "80", "68", "75"],
46
+ "temp": ["98.6", "99.1", "98.2", "99.5"],
47
+ "assessment": ["stable", "improving", "requires monitoring", "critical"],
48
+ "plan": ["continue current treatment", "schedule follow-up", "refer to specialist", "discharge"],
49
+ "condition": ["hypertension", "diabetes", "asthma", "arthritis"],
50
+ "meds": ["aspirin", "metformin", "albuterol", "ibuprofen"],
51
+ "treatment": ["antibiotics", "physical therapy", "surgery", "bed rest"],
52
+ "date": ["2023-10-01", "2023-10-15", "2023-11-01"],
53
+ "instructions": ["rest", "take medications as prescribed", "follow up in 2 weeks", "avoid strenuous activity"],
54
+ "course": ["stable", "complicated", "uneventful", "critical"],
55
+ "specialist": ["cardiologist", "neurologist", "orthopedist", "dermatologist"],
56
+ "timeframe": ["1 week", "2 weeks", "1 month", "3 months"],
57
+ "test": ["CBC", "lipid panel", "glucose", "thyroid function"],
58
+ "result": ["normal", "elevated", "low", "critical"],
59
+ "range": ["10-20", "70-100", "3.5-5.0", "0.5-1.5"],
60
+ "interpretation": ["within normal limits", "requires follow-up", "abnormal", "critical"],
61
+ "finding": ["anemia", "hyperlipidemia", "hypoglycemia", "hyperthyroidism"],
62
+ "tests": ["MRI", "CT scan", "ultrasound", "biopsy"],
63
+ "medication": ["amoxicillin", "lisinopril", "atorvastatin", "levothyroxine"],
64
+ "dose": ["500mg", "10mg", "20mg", "50mcg"],
65
+ "frequency": ["once daily", "twice daily", "three times daily", "as needed"],
66
+ "duration": ["7 days", "30 days", "90 days", "indefinite"],
67
+ "refills": ["0", "1", "2", "3"],
68
+ "food": ["with food", "on an empty stomach", "with water", "with milk"],
69
+ "complaint": ["chest pain", "shortness of breath", "joint pain", "dizziness"],
70
+ "history": ["hypertension", "diabetes", "asthma", "arthritis"],
71
+ "allergies": ["penicillin", "sulfa", "latex", "none"],
72
+ "demographics": ["45-year-old male", "60-year-old female", "30-year-old male", "50-year-old female"],
73
+ "insurance": ["Blue Cross", "Aetna", "Medicare", "Medicaid"],
74
+ "contact": ["John Doe (555-123-4567)", "Jane Smith (555-987-6543)"]
75
+ }
76
+
77
+ def generate_record(record_type: str) -> GeneratedRecord:
78
+ if record_type not in TEMPLATES:
79
+ raise ValueError(f"Unsupported record type: {record_type}")
80
+
81
+ template = random.choice(TEMPLATES[record_type])
82
+ content = template.format(**{k: random.choice(v) for k, v in PLACEHOLDERS.items()})
83
+
84
+ return GeneratedRecord(
85
+ id=f"record-{random.randint(1000, 9999)}",
86
+ content=content,
87
+ recordType=record_type
88
+ )
89
+
90
+ @router.post("/generate", response_model=List[GeneratedRecord])
91
+ async def generate_records(request: GenerationRequest):
92
+ if request.quantity <= 0:
93
+ raise HTTPException(status_code=400, detail="Quantity must be positive")
94
+ if request.quantity > 100:
95
+ raise HTTPException(status_code=400, detail="Quantity cannot exceed 100")
96
+
97
+ records = [generate_record(request.recordType) for _ in range(request.quantity)]
98
+ return records
app/api/medical.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import List, Dict, Any
4
+ from app.services.medical_generator import MedicalTextGenerator
5
+
6
+ router = APIRouter()
7
+ generator = MedicalTextGenerator()
8
+
9
+ class GenerateRequest(BaseModel):
10
+ count: int = 1
11
+ record_type: str = "clinical_note"
12
+
13
+ class GenerateResponse(BaseModel):
14
+ records: List[Dict[str, Any]]
15
+
16
+ @router.post("/generate", response_model=GenerateResponse)
17
+ async def generate_medical_text(request: GenerateRequest):
18
+ """
19
+ Generate synthetic medical text records.
20
+
21
+ Args:
22
+ request: GenerateRequest containing count and record_type
23
+
24
+ Returns:
25
+ GenerateResponse containing the generated records
26
+ """
27
+ try:
28
+ records = generator.batch_generate(request.count, request.record_type)
29
+ return GenerateResponse(records=records)
30
+ except ValueError as e:
31
+ raise HTTPException(status_code=400, detail=str(e))
32
+ except Exception as e:
33
+ raise HTTPException(status_code=500, detail=f"Error generating records: {str(e)}")
app/core/config.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic_settings import BaseSettings
2
+ from typing import Optional
3
+
4
+ class Settings(BaseSettings):
5
+ # API Settings
6
+ API_V1_STR: str = "/api/v1"
7
+ PROJECT_NAME: str = "Synthex"
8
+
9
+ # Security
10
+ SECRET_KEY: str = "your-secret-key-here" # Change this in production
11
+ ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 # 8 days
12
+
13
+ # Database
14
+ DATABASE_URL: str = "postgresql://neondb_owner:npg_tRvA0lD3GrZk@ep-lucky-rain-a8rbsfj4-pooler.eastus2.azure.neon.tech/neondb?sslmode=require"
15
+
16
+ # Admin
17
+ ADMIN_EMAIL: str = "[email protected]"
18
+ ADMIN_PASSWORD: str = "admin123" # Change this in production
19
+
20
+ class Config:
21
+ case_sensitive = True
22
+
23
+ settings = Settings()
app/core/database.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import create_engine
2
+ from sqlalchemy.ext.declarative import declarative_base
3
+ from sqlalchemy.orm import sessionmaker
4
+ from app.core.config import settings
5
+
6
+ engine = create_engine(settings.DATABASE_URL)
7
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
8
+
9
+ Base = declarative_base()
10
+
11
+ # Dependency
12
+ def get_db():
13
+ db = SessionLocal()
14
+ try:
15
+ yield db
16
+ finally:
17
+ db.close()
18
+
19
+ # Create tables
20
+ def init_db():
21
+ Base.metadata.create_all(bind=engine)
app/main.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from app.api import beta, admin, generator, auth, data, medical
4
+ from app.core.config import settings
5
+ from app.core.database import init_db, engine, Base
6
+
7
+ app = FastAPI(
8
+ title="Synthex API",
9
+ description="API for generating synthetic medical records",
10
+ version="1.0.0",
11
+ )
12
+
13
+ # Initialize database on startup
14
+ @app.on_event("startup")
15
+ async def startup_event():
16
+ init_db()
17
+
18
+ # Configure CORS
19
+ app.add_middleware(
20
+ CORSMiddleware,
21
+ allow_origins=["http://localhost:3000"], # Frontend URL
22
+ allow_credentials=True,
23
+ allow_methods=["*"],
24
+ allow_headers=["*"],
25
+ )
26
+
27
+ # Include routers
28
+ app.include_router(beta.router, prefix="/api/beta", tags=["beta"])
29
+ app.include_router(admin.router, prefix="/api/admin", tags=["admin"])
30
+ app.include_router(generator.router, prefix="/api/generator", tags=["generator"])
31
+ app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
32
+ app.include_router(data.router, prefix="/api/data", tags=["data"])
33
+ app.include_router(medical.router, prefix="/api/medical", tags=["medical"])
34
+
35
+ @app.get("/")
36
+ async def root():
37
+ return {"message": "Welcome to Synthex API"}
38
+
39
+ # Create database tables
40
+ Base.metadata.create_all(bind=engine)
app/models/models.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, String, DateTime, Enum
2
+ from sqlalchemy.sql import func
3
+ from app.core.database import Base
4
+ import enum
5
+
6
+ class ApplicationStatus(str, enum.Enum):
7
+ PENDING = "pending"
8
+ APPROVED = "approved"
9
+ REJECTED = "rejected"
10
+
11
+ class BetaApplication(Base):
12
+ __tablename__ = "beta_applications"
13
+
14
+ id = Column(String, primary_key=True, index=True)
15
+ email = Column(String, unique=True, index=True)
16
+ company = Column(String)
17
+ use_case = Column(String)
18
+ status = Column(Enum(ApplicationStatus), default=ApplicationStatus.PENDING)
19
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
20
+ updated_at = Column(DateTime(timezone=True), onupdate=func.now())
app/routers/data.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import List, Optional
4
+ from ..services.data_generator import DataGenerator
5
+ import logging
6
+
7
+ # Configure logging
8
+ logging.basicConfig(level=logging.DEBUG)
9
+ logger = logging.getLogger(__name__)
10
+
11
+ router = APIRouter()
12
+ generator = DataGenerator()
13
+
14
+ class GenerateRequest(BaseModel):
15
+ count: int
16
+ types: List[str]
17
+ export_format: Optional[str] = "JSON"
18
+
19
+ @router.post("/generate")
20
+ async def generate_records(request: GenerateRequest):
21
+ """Generate synthetic medical records."""
22
+ try:
23
+ logger.debug(f"Received request: count={request.count}, types={request.types}, export_format={request.export_format}")
24
+
25
+ # Validate count
26
+ if request.count < 1 or request.count > 100:
27
+ raise HTTPException(status_code=400, detail="Count must be between 1 and 100")
28
+
29
+ # Validate types
30
+ valid_types = ["clinical_note", "discharge_summary", "lab_report", "prescription", "patient_intake"]
31
+ if not all(t in valid_types for t in request.types):
32
+ raise HTTPException(status_code=400, detail=f"Invalid record type. Must be one of: {', '.join(valid_types)}")
33
+
34
+ # Generate records
35
+ logger.debug("Generating records...")
36
+ records = generator.generate_records(count=request.count, types=request.types)
37
+ logger.debug(f"Generated {len(records)} records")
38
+
39
+ # Export if format specified
40
+ if request.export_format:
41
+ try:
42
+ logger.debug(f"Exporting records in {request.export_format} format")
43
+ exported_data = generator.export_records(records, request.export_format)
44
+ return {
45
+ "records": records,
46
+ "exported_data": exported_data,
47
+ "export_format": request.export_format
48
+ }
49
+ except ValueError as e:
50
+ logger.error(f"Export error: {str(e)}")
51
+ raise HTTPException(status_code=400, detail=str(e))
52
+
53
+ return {"records": records}
54
+
55
+ except Exception as e:
56
+ logger.error(f"Error generating records: {str(e)}", exc_info=True)
57
+ raise HTTPException(status_code=500, detail=str(e))
app/services/data_generator.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Dict, Any
2
+ from datetime import datetime, timedelta
3
+ import random
4
+ import json
5
+ import csv
6
+ import io
7
+
8
+ class DataGenerator:
9
+ def __init__(self):
10
+ self.first_names = [
11
+ "John", "Jane", "Michael", "Emily", "David", "Sarah", "James", "Emma",
12
+ "Robert", "Olivia", "William", "Sophia", "Daniel", "Ava", "Matthew", "Isabella"
13
+ ]
14
+ self.last_names = [
15
+ "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis",
16
+ "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", "Thomas"
17
+ ]
18
+ self.doctors = [
19
+ "Dr. Sarah Chen", "Dr. Michael Patel", "Dr. Emily Rodriguez", "Dr. James Wilson",
20
+ "Dr. Lisa Thompson", "Dr. Robert Kim", "Dr. Maria Garcia", "Dr. David Lee"
21
+ ]
22
+ self.conditions = [
23
+ "Hypertension", "Type 2 Diabetes", "Asthma", "Arthritis", "Migraine",
24
+ "Anxiety", "Depression", "GERD", "Hypothyroidism", "Osteoporosis"
25
+ ]
26
+ self.medications = [
27
+ "Lisinopril", "Metformin", "Albuterol", "Ibuprofen", "Sumatriptan",
28
+ "Sertraline", "Omeprazole", "Levothyroxine", "Alendronate", "Atorvastatin"
29
+ ]
30
+ self.lab_tests = [
31
+ "Complete Blood Count", "Basic Metabolic Panel", "Lipid Panel", "Hemoglobin A1C",
32
+ "Thyroid Function Test", "Vitamin D Level", "Liver Function Test", "Urinalysis"
33
+ ]
34
+
35
+ def generate_patient_info(self) -> Dict[str, Any]:
36
+ first_name = random.choice(self.first_names)
37
+ last_name = random.choice(self.last_names)
38
+ age = random.randint(18, 85)
39
+ gender = random.choice(["Male", "Female"])
40
+ return {
41
+ "patient_name": f"{first_name} {last_name}",
42
+ "age": age,
43
+ "gender": gender,
44
+ "doctor_name": random.choice(self.doctors),
45
+ "date": datetime.now().strftime("%Y-%m-%d")
46
+ }
47
+
48
+ def generate_clinical_note(self, metadata: Dict[str, Any]) -> str:
49
+ condition = random.choice(self.conditions)
50
+ medications = random.sample(self.medications, random.randint(1, 3))
51
+ return f"""CLINICAL NOTE
52
+
53
+ Patient: {metadata['patient_name']}
54
+ Date: {metadata['date']}
55
+ Provider: {metadata['doctor_name']}
56
+
57
+ Chief Complaint:
58
+ Patient presents with symptoms related to {condition.lower()}.
59
+
60
+ History of Present Illness:
61
+ Patient reports ongoing symptoms for the past {random.randint(1, 12)} months.
62
+ Symptoms include {', '.join(random.sample(['fatigue', 'pain', 'discomfort', 'anxiety', 'depression'], 2))}.
63
+
64
+ Assessment:
65
+ Primary diagnosis: {condition}
66
+
67
+ Plan:
68
+ 1. Continue current medications: {', '.join(medications)}
69
+ 2. Follow up in {random.randint(1, 3)} months
70
+ 3. Recommended lifestyle modifications
71
+ 4. Schedule routine lab work
72
+
73
+ Signed: {metadata['doctor_name']}"""
74
+
75
+ def generate_discharge_summary(self, metadata: Dict[str, Any]) -> str:
76
+ condition = random.choice(self.conditions)
77
+ medications = random.sample(self.medications, random.randint(1, 3))
78
+ admission_date = (datetime.now().replace(day=1) - timedelta(days=random.randint(1, 30))).strftime("%Y-%m-%d")
79
+ discharge_date = datetime.now().strftime("%Y-%m-%d")
80
+ return f"""DISCHARGE SUMMARY
81
+
82
+ Patient: {metadata['patient_name']}
83
+ Admission Date: {admission_date}
84
+ Discharge Date: {discharge_date}
85
+ Attending Physician: {metadata['doctor_name']}
86
+
87
+ Admission Diagnosis:
88
+ {condition}
89
+
90
+ Hospital Course:
91
+ Patient was admitted for management of {condition.lower()}.
92
+ Treatment included {', '.join(medications)}.
93
+ Patient showed significant improvement during hospitalization.
94
+
95
+ Discharge Medications:
96
+ {', '.join(medications)}
97
+
98
+ Discharge Instructions:
99
+ 1. Follow up with primary care physician in 1 week
100
+ 2. Continue medications as prescribed
101
+ 3. Maintain healthy lifestyle
102
+ 4. Call if symptoms worsen
103
+
104
+ Signed: {metadata['doctor_name']}"""
105
+
106
+ def generate_lab_report(self, metadata: Dict[str, Any]) -> str:
107
+ test = random.choice(self.lab_tests)
108
+ result = random.uniform(1.0, 10.0)
109
+ reference_range = "1.0 - 10.0"
110
+ status = "Normal" if 1.0 <= result <= 10.0 else "Abnormal"
111
+ return f"""LABORATORY REPORT
112
+
113
+ Patient: {metadata['patient_name']}
114
+ Date: {metadata['date']}
115
+ Ordering Physician: {metadata['doctor_name']}
116
+
117
+ Test: {test}
118
+ Result: {result:.1f}
119
+ Reference Range: {reference_range}
120
+ Status: {status}
121
+
122
+ Comments:
123
+ Results have been reviewed and interpreted by the laboratory director.
124
+ Please contact the laboratory if you have any questions.
125
+
126
+ Signed: Laboratory Director"""
127
+
128
+ def generate_prescription(self, metadata: Dict[str, Any]) -> str:
129
+ medication = random.choice(self.medications)
130
+ dosage = f"{random.randint(1, 10)}mg"
131
+ frequency = random.choice(["once daily", "twice daily", "three times daily", "as needed"])
132
+ quantity = random.randint(30, 90)
133
+ refills = random.randint(0, 3)
134
+ return f"""PRESCRIPTION
135
+
136
+ Patient: {metadata['patient_name']}
137
+ Date: {metadata['date']}
138
+ Prescribing Physician: {metadata['doctor_name']}
139
+
140
+ Medication: {medication}
141
+ Dosage: {dosage}
142
+ Frequency: {frequency}
143
+ Quantity: {quantity}
144
+ Refills: {refills}
145
+
146
+ Instructions:
147
+ Take {dosage} {frequency} with food.
148
+ Store at room temperature.
149
+ Call if side effects occur.
150
+
151
+ Signed: {metadata['doctor_name']}"""
152
+
153
+ def generate_patient_intake(self, metadata: Dict[str, Any]) -> str:
154
+ conditions = random.sample(self.conditions, random.randint(1, 3))
155
+ medications = random.sample(self.medications, random.randint(1, 3))
156
+ allergies = random.sample(["Penicillin", "Sulfa", "Latex", "Peanuts"], random.randint(0, 2))
157
+ return f"""PATIENT INTAKE FORM
158
+
159
+ Patient Information:
160
+ Name: {metadata['patient_name']}
161
+ Age: {metadata['age']}
162
+ Gender: {metadata['gender']}
163
+ Date: {metadata['date']}
164
+
165
+ Medical History:
166
+ Current Conditions: {', '.join(conditions)}
167
+ Current Medications: {', '.join(medications)}
168
+ Allergies: {', '.join(allergies) if allergies else 'None reported'}
169
+
170
+ Family History:
171
+ - Mother: {random.choice(self.conditions)}
172
+ - Father: {random.choice(self.conditions)}
173
+
174
+ Social History:
175
+ - Smoking: {random.choice(['Never', 'Former', 'Current'])}
176
+ - Alcohol: {random.choice(['None', 'Occasional', 'Regular'])}
177
+ - Exercise: {random.choice(['None', 'Occasional', 'Regular'])}
178
+
179
+ Review of Systems:
180
+ - General: No significant findings
181
+ - Cardiovascular: No significant findings
182
+ - Respiratory: No significant findings
183
+ - Gastrointestinal: No significant findings
184
+ - Neurological: No significant findings
185
+
186
+ Signed: {metadata['patient_name']}"""
187
+
188
+ def generate_records(self, count: int, types: List[str]) -> List[Dict[str, Any]]:
189
+ if not types:
190
+ raise ValueError("At least one record type must be specified")
191
+ records = []
192
+ type_generators = {
193
+ "clinical_note": self.generate_clinical_note,
194
+ "discharge_summary": self.generate_discharge_summary,
195
+ "lab_report": self.generate_lab_report,
196
+ "prescription": self.generate_prescription,
197
+ "patient_intake": self.generate_patient_intake
198
+ }
199
+ invalid_types = [t for t in types if t not in type_generators]
200
+ if invalid_types:
201
+ raise ValueError(f"Invalid record types: {', '.join(invalid_types)}")
202
+ for _ in range(count):
203
+ record_type = random.choice(types)
204
+ metadata = self.generate_patient_info()
205
+ content = type_generators[record_type](metadata)
206
+ records.append({
207
+ "id": f"REC-{random.randint(10000, 99999)}",
208
+ "type": record_type,
209
+ "content": content,
210
+ "generated_at": datetime.now().isoformat(),
211
+ "metadata": metadata
212
+ })
213
+ return records
214
+
215
+ def export_records(self, records: List[Dict[str, Any]], format: str) -> str:
216
+ if format == "JSON":
217
+ return json.dumps(records, indent=2)
218
+ elif format == "CSV":
219
+ output = io.StringIO()
220
+ writer = csv.writer(output)
221
+ writer.writerow(["ID", "Type", "Content", "Generated At", "Patient Name", "Age", "Gender", "Doctor Name", "Date"])
222
+ for record in records:
223
+ writer.writerow([
224
+ record["id"],
225
+ record["type"],
226
+ record["content"],
227
+ record["generated_at"],
228
+ record["metadata"]["patient_name"],
229
+ record["metadata"]["age"],
230
+ record["metadata"]["gender"],
231
+ record["metadata"]["doctor_name"],
232
+ record["metadata"]["date"]
233
+ ])
234
+ return output.getvalue()
235
+ elif format == "TXT":
236
+ output = []
237
+ for record in records:
238
+ output.append(f"Record ID: {record['id']}")
239
+ output.append(f"Type: {record['type']}")
240
+ output.append(f"Generated At: {record['generated_at']}")
241
+ output.append(f"Patient: {record['metadata']['patient_name']}")
242
+ output.append(f"Age: {record['metadata']['age']}")
243
+ output.append(f"Gender: {record['metadata']['gender']}")
244
+ output.append(f"Doctor: {record['metadata']['doctor_name']}")
245
+ output.append(f"Date: {record['metadata']['date']}")
246
+ output.append("\nContent:")
247
+ output.append(record['content'])
248
+ output.append("\n" + "="*80 + "\n")
249
+ return "\n".join(output)
250
+ else:
251
+ raise ValueError(f"Unsupported export format: {format}")
app/services/medical_generator.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ from typing import List, Dict, Any
4
+ from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer
5
+ from datetime import datetime
6
+
7
+ class MedicalTextGenerator:
8
+ def __init__(self):
9
+ # Initialize with a medical-specific model
10
+ self.model_name = "emilyalsentzer/Bio_ClinicalBERT"
11
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
12
+ self.model = AutoModelForCausalLM.from_pretrained(self.model_name)
13
+
14
+ # Create text generation pipeline
15
+ self.generator = pipeline(
16
+ "text-generation",
17
+ model=self.model,
18
+ tokenizer=self.tokenizer,
19
+ max_length=512,
20
+ num_return_sequences=1
21
+ )
22
+
23
+ def _generate_text(self, prompt: str) -> str:
24
+ """Generate text using the model."""
25
+ try:
26
+ # Generate text
27
+ outputs = self.generator(
28
+ prompt,
29
+ max_length=512,
30
+ num_return_sequences=1,
31
+ temperature=0.7,
32
+ top_p=0.9,
33
+ do_sample=True
34
+ )
35
+
36
+ # Extract generated text
37
+ generated_text = outputs[0]['generated_text']
38
+
39
+ # Clean up the text
40
+ generated_text = generated_text.replace(prompt, "").strip()
41
+ return generated_text
42
+ except Exception as e:
43
+ print(f"Error generating text: {e}")
44
+ return ""
45
+
46
+ def generate_clinical_note(self, template_type: str = "general") -> str:
47
+ """Generate a clinical note based on the template type."""
48
+ prompt = f"""
49
+ Generate a realistic but completely fictional clinical note for {template_type}.
50
+ Include:
51
+ - Chief complaint
52
+ - History of present illness
53
+ - Physical exam
54
+ - Assessment
55
+ - Plan
56
+
57
+ Make it medically accurate but use fictional patient details.
58
+ Follow HIPAA guidelines and ensure no real PII is included.
59
+ """
60
+ return self._generate_text(prompt)
61
+
62
+ def generate_discharge_summary(self) -> str:
63
+ """Generate a discharge summary."""
64
+ prompt = """
65
+ Generate a realistic but completely fictional hospital discharge summary.
66
+ Include:
67
+ - Admission date
68
+ - Discharge date
69
+ - Reason for admission
70
+ - Hospital course
71
+ - Discharge medications
72
+ - Discharge instructions
73
+ - Follow-up plan
74
+
75
+ Make it medically accurate but use fictional patient details.
76
+ Follow HIPAA guidelines and ensure no real PII is included.
77
+ """
78
+ return self._generate_text(prompt)
79
+
80
+ def generate_lab_report(self) -> str:
81
+ """Generate a laboratory report."""
82
+ prompt = """
83
+ Generate a realistic but completely fictional laboratory report.
84
+ Include:
85
+ - Test name
86
+ - Results
87
+ - Reference ranges
88
+ - Interpretation
89
+ - Comments
90
+
91
+ Make it medically accurate but use fictional patient details.
92
+ Follow HIPAA guidelines and ensure no real PII is included.
93
+ """
94
+ return self._generate_text(prompt)
95
+
96
+ def generate_prescription(self) -> str:
97
+ """Generate a prescription."""
98
+ prompt = """
99
+ Generate a realistic but completely fictional prescription.
100
+ Include:
101
+ - Medication name
102
+ - Dosage
103
+ - Frequency
104
+ - Duration
105
+ - Special instructions
106
+
107
+ Make it medically accurate but use fictional patient details.
108
+ Follow HIPAA guidelines and ensure no real PII is included.
109
+ """
110
+ return self._generate_text(prompt)
111
+
112
+ def batch_generate(self, count: int, record_type: str) -> List[Dict[str, Any]]:
113
+ """Generate multiple records of the specified type."""
114
+ records = []
115
+ type_generators = {
116
+ "clinical_note": self.generate_clinical_note,
117
+ "discharge_summary": self.generate_discharge_summary,
118
+ "lab_report": self.generate_lab_report,
119
+ "prescription": self.generate_prescription
120
+ }
121
+
122
+ if record_type not in type_generators:
123
+ raise ValueError(f"Unsupported record type: {record_type}")
124
+
125
+ generator = type_generators[record_type]
126
+
127
+ for i in range(count):
128
+ content = generator()
129
+ records.append({
130
+ "id": f"REC-{datetime.now().strftime('%Y%m%d%H%M%S')}-{i+1}",
131
+ "type": record_type,
132
+ "content": content,
133
+ "generated_at": datetime.now().isoformat()
134
+ })
135
+ time.sleep(1) # Small delay to prevent overload
136
+
137
+ return records
create_tables.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.core.database import Base, engine
2
+ from app.models.models import BetaApplication
3
+ from pydantic import BaseModel, Field
4
+ from datetime import datetime
5
+
6
+ # Create the tables
7
+ Base.metadata.create_all(engine)
8
+
9
+ print("Tables created successfully.")
10
+
11
+ class BetaApplicationSchema(BaseModel):
12
+ id: str
13
+ email: str
14
+ company: str
15
+ useCase: str = Field(..., alias="use_case")
16
+ status: str
17
+ createdAt: datetime = Field(..., alias="created_at")
18
+ updatedAt: datetime | None = Field(None, alias="updated_at")
19
+
20
+ class Config:
21
+ orm_mode = True
22
+ allow_population_by_field_name = True
init_db.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.core.database import Base, engine
2
+ from app.models.models import BetaApplication
3
+
4
+ def init_db():
5
+ Base.metadata.create_all(bind=engine)
6
+
7
+ if __name__ == "__main__":
8
+ print("Creating database tables...")
9
+ init_db()
10
+ print("Database tables created successfully!")
main.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from dotenv import load_dotenv
4
+ import os
5
+
6
+ # Load environment variables
7
+ load_dotenv()
8
+
9
+ # Create FastAPI app
10
+ app = FastAPI(
11
+ title="Synthex API",
12
+ description="Backend API for Synthex Medical Text Generator",
13
+ version="1.0.0"
14
+ )
15
+
16
+ # Configure CORS
17
+ app.add_middleware(
18
+ CORSMiddleware,
19
+ allow_origins=["http://localhost:3000"], # Frontend URL
20
+ allow_credentials=True,
21
+ allow_methods=["*"],
22
+ allow_headers=["*"],
23
+ )
24
+
25
+ # Health check endpoint
26
+ @app.get("/")
27
+ async def root():
28
+ return {"status": "healthy", "message": "Synthex API is running"}
29
+
30
+ # Import and include routers
31
+ from app.api import beta, admin, generator
32
+
33
+ app.include_router(beta.router, prefix="/api/beta", tags=["beta"])
34
+ app.include_router(admin.router, prefix="/api/admin", tags=["admin"])
35
+ app.include_router(generator.router, prefix="/api/generator", tags=["generator"])
36
+
37
+ if __name__ == "__main__":
38
+ import uvicorn
39
+ uvicorn.run(app, host="0.0.0.0", port=8000)
pyproject.toml ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [tool.black]
2
+ line-length = 88
3
+ target-version = ['py38']
4
+ include = '\.pyi?$'
5
+ exclude = '''
6
+ /(
7
+ \.git
8
+ | \.hg
9
+ | \.mypy_cache
10
+ | \.tox
11
+ | \.venv
12
+ | _build
13
+ | buck-out
14
+ | build
15
+ | dist
16
+ )/
17
+ '''
recreate_tables.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import text
2
+ from app.core.database import Base, engine
3
+ from app.models.models import BetaApplication
4
+
5
+ # Drop all tables with CASCADE
6
+ with engine.connect() as conn:
7
+ conn.execute(text("DROP SCHEMA public CASCADE;"))
8
+ conn.execute(text("CREATE SCHEMA public;"))
9
+ conn.commit()
10
+
11
+ # Create all tables
12
+ Base.metadata.create_all(engine)
13
+
14
+ print("Tables recreated successfully.")
requirements-dev.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ -r requirements.txt
2
+ pytest==7.4.3
3
+ pytest-cov==4.1.0
4
+ black==23.11.0
5
+ flake8==6.1.0
6
+ mypy==1.7.1
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ streamlit>=1.24.0
2
+ transformers>=4.30.0
3
+ torch>=2.0.0
4
+ pydantic>=1.8.0
5
+ python-dotenv>=0.19.0
6
+ fastapi>=0.68.0
7
+ uvicorn[standard]>=0.15.0
8
+ sqlalchemy>=1.4.0
9
+ python-multipart>=0.0.5
run_linters.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import sys
3
+ import os
4
+ import platform
5
+
6
+ def run_black():
7
+ print("\nRunning Black...")
8
+ try:
9
+ subprocess.run(
10
+ ["black", "app/", "tests/"],
11
+ check=True
12
+ )
13
+ print("Black completed successfully!")
14
+ except subprocess.CalledProcessError:
15
+ print("Black found issues!")
16
+ sys.exit(1)
17
+
18
+ def run_flake8():
19
+ print("\nRunning Flake8...")
20
+ try:
21
+ subprocess.run(
22
+ ["flake8", "app/", "tests/"],
23
+ check=True
24
+ )
25
+ print("Flake8 completed successfully!")
26
+ except subprocess.CalledProcessError:
27
+ print("Flake8 found issues!")
28
+ sys.exit(1)
29
+
30
+ def run_mypy():
31
+ print("\nRunning MyPy...")
32
+ try:
33
+ subprocess.run(
34
+ ["mypy", "app/", "tests/"],
35
+ check=True
36
+ )
37
+ print("MyPy completed successfully!")
38
+ except subprocess.CalledProcessError:
39
+ print("MyPy found issues!")
40
+ sys.exit(1)
41
+
42
+ def main():
43
+ print("Running linters...")
44
+
45
+ run_black()
46
+ run_flake8()
47
+ run_mypy()
48
+
49
+ print("\nAll linters passed successfully!")
50
+
51
+ if __name__ == "__main__":
52
+ main()
run_tests.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import sys
3
+ import os
4
+ import platform
5
+
6
+ def run_tests():
7
+ print("Running tests...")
8
+
9
+ if platform.system() == "Windows":
10
+ python = "venv/Scripts/python"
11
+ else:
12
+ python = "venv/bin/python"
13
+
14
+ try:
15
+ subprocess.run(
16
+ [python, "-m", "pytest", "tests/", "-v"],
17
+ check=True
18
+ )
19
+ print("\nAll tests passed successfully!")
20
+ except subprocess.CalledProcessError:
21
+ print("\nSome tests failed!")
22
+ sys.exit(1)
23
+
24
+ if __name__ == "__main__":
25
+ run_tests()
setup.cfg ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [flake8]
2
+ max-line-length = 88
3
+ extend-ignore = E203
4
+ exclude = .git,__pycache__,build,dist
5
+
6
+ [mypy]
7
+ python_version = 3.8
8
+ warn_return_any = True
9
+ warn_unused_configs = True
10
+ disallow_untyped_defs = True
11
+ disallow_incomplete_defs = True
12
+ check_untyped_defs = True
13
+ disallow_untyped_decorators = True
14
+ no_implicit_optional = True
15
+ warn_redundant_casts = True
16
+ warn_unused_ignores = True
17
+ warn_no_return = True
18
+ warn_unreachable = True
19
+
20
+ [coverage:run]
21
+ source = app
22
+ omit = tests/*
23
+
24
+ [coverage:report]
25
+ exclude_lines =
26
+ pragma: no cover
27
+ def __repr__
28
+ raise NotImplementedError
29
+ if __name__ == .__main__.:
30
+ pass
31
+ raise ImportError
tests/test_api.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi.testclient import TestClient
2
+ from app.main import app
3
+ from app.core.database import Base, engine
4
+ from app.models.models import BetaApplication
5
+ import pytest
6
+
7
+ client = TestClient(app)
8
+
9
+ @pytest.fixture(autouse=True)
10
+ def setup_database():
11
+ Base.metadata.create_all(bind=engine)
12
+ yield
13
+ Base.metadata.drop_all(bind=engine)
14
+
15
+ def test_create_application():
16
+ response = client.post(
17
+ "/api/beta/apply",
18
+ json={
19
+ "email": "[email protected]",
20
+ "company": "Test Company",
21
+ "useCase": "Testing"
22
+ }
23
+ )
24
+ assert response.status_code == 200
25
+ data = response.json()
26
+ assert data["email"] == "[email protected]"
27
+ assert data["company"] == "Test Company"
28
+ assert data["useCase"] == "Testing"
29
+ assert data["status"] == "pending"
30
+
31
+ def test_create_duplicate_application():
32
+ # Create first application
33
+ client.post(
34
+ "/api/beta/apply",
35
+ json={
36
+ "email": "[email protected]",
37
+ "company": "Test Company",
38
+ "useCase": "Testing"
39
+ }
40
+ )
41
+
42
+ # Try to create duplicate
43
+ response = client.post(
44
+ "/api/beta/apply",
45
+ json={
46
+ "email": "[email protected]",
47
+ "company": "Another Company",
48
+ "useCase": "Another Use Case"
49
+ }
50
+ )
51
+ assert response.status_code == 400
52
+ assert response.json()["detail"] == "Email already registered"
53
+
54
+ def test_verify_application():
55
+ # Create application
56
+ response = client.post(
57
+ "/api/beta/apply",
58
+ json={
59
+ "email": "[email protected]",
60
+ "company": "Test Company",
61
+ "useCase": "Testing"
62
+ }
63
+ )
64
+ application_id = response.json()["id"]
65
+
66
+ # Try to verify unapproved application
67
+ response = client.post(
68
+ "/api/beta/verify",
69
+ json={"application_id": application_id}
70
+ )
71
+ assert response.status_code == 403
72
+ assert response.json()["detail"] == "Application not approved"
73
+
74
+ def test_admin_login():
75
+ response = client.post(
76
+ "/api/admin/login",
77
+ data={
78
+ "username": "[email protected]",
79
+ "password": "admin123"
80
+ }
81
+ )
82
+ assert response.status_code == 200
83
+ data = response.json()
84
+ assert "access_token" in data
85
+ assert data["token_type"] == "bearer"
86
+
87
+ def test_admin_login_invalid_credentials():
88
+ response = client.post(
89
+ "/api/admin/login",
90
+ data={
91
+ "username": "[email protected]",
92
+ "password": "wrong_password"
93
+ }
94
+ )
95
+ assert response.status_code == 401
96
+ assert response.json()["detail"] == "Incorrect email or password"
97
+
98
+ def test_get_applications_unauthorized():
99
+ response = client.get("/api/admin/applications")
100
+ assert response.status_code == 401
101
+ assert response.json()["detail"] == "Not authenticated"
102
+
103
+ def test_update_application():
104
+ # Create application
105
+ response = client.post(
106
+ "/api/beta/apply",
107
+ json={
108
+ "email": "[email protected]",
109
+ "company": "Test Company",
110
+ "useCase": "Testing"
111
+ }
112
+ )
113
+ application_id = response.json()["id"]
114
+
115
+ # Login as admin
116
+ login_response = client.post(
117
+ "/api/admin/login",
118
+ data={
119
+ "username": "[email protected]",
120
+ "password": "admin123"
121
+ }
122
+ )
123
+ token = login_response.json()["access_token"]
124
+
125
+ # Update application
126
+ response = client.patch(
127
+ f"/api/admin/applications/{application_id}",
128
+ json={"status": "approved"},
129
+ headers={"Authorization": f"Bearer {token}"}
130
+ )
131
+ assert response.status_code == 200
132
+ assert response.json()["message"] == "Application updated successfully"
133
+
134
+ # Verify application
135
+ response = client.post(
136
+ "/api/beta/verify",
137
+ json={"application_id": application_id}
138
+ )
139
+ assert response.status_code == 200
140
+ assert response.json()["message"] == "Access granted"
tests/test_integration.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ from fastapi.testclient import TestClient
3
+ from app.main import app
4
+
5
+ client = TestClient(app)
6
+
7
+ def test_beta_verification():
8
+ response = client.post("/api/beta/verify", json={"application_id": "test123"})
9
+ assert response.status_code == 200
10
+ assert response.json()["message"] == "Access granted"
11
+
12
+ def test_jwt_authentication():
13
+ response = client.post("/api/auth/token", data={"username": "[email protected]", "password": "admin123"})
14
+ assert response.status_code == 200
15
+ assert "access_token" in response.json()
16
+
17
+ def test_record_generation():
18
+ # First, get a token
19
+ token_response = client.post("/api/auth/token", data={"username": "[email protected]", "password": "admin123"})
20
+ token = token_response.json()["access_token"]
21
+
22
+ # Then, generate records
23
+ response = client.post(
24
+ "/api/generator/generate",
25
+ json={"recordType": "Clinical Note", "quantity": 5},
26
+ headers={"Authorization": f"Bearer {token}"}
27
+ )
28
+ assert response.status_code == 200
29
+ assert len(response.json()) == 5