Spaces:
Sleeping
Sleeping
Integrate TxAgent into CPS API: add TxAgent files, update requirements, fix imports and database collections
Browse files- api/routes/txagent.py +263 -0
- requirements.txt +2 -1
api/routes/txagent.py
CHANGED
|
@@ -415,4 +415,267 @@ async def get_chats(current_user: dict = Depends(get_current_user)):
|
|
| 415 |
logger.error(f"Error getting chats: {e}")
|
| 416 |
raise HTTPException(status_code=500, detail="Failed to get chats")
|
| 417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
|
|
|
|
| 415 |
logger.error(f"Error getting chats: {e}")
|
| 416 |
raise HTTPException(status_code=500, detail="Failed to get chats")
|
| 417 |
|
| 418 |
+
@router.get("/patients/{patient_id}/analysis-reports/pdf")
|
| 419 |
+
async def get_patient_analysis_reports_pdf(
|
| 420 |
+
patient_id: str = Path(...),
|
| 421 |
+
current_user: dict = Depends(get_current_user)
|
| 422 |
+
):
|
| 423 |
+
"""Generate PDF analysis reports for a specific patient"""
|
| 424 |
+
try:
|
| 425 |
+
if not any(role in current_user.get('roles', []) for role in ['doctor', 'admin']):
|
| 426 |
+
raise HTTPException(status_code=403, detail="Only doctors and admins can generate PDF reports")
|
| 427 |
+
|
| 428 |
+
logger.info(f"Generating PDF analysis reports for patient {patient_id} by {current_user['email']}")
|
| 429 |
+
|
| 430 |
+
# Import database collections
|
| 431 |
+
from db.mongo import db
|
| 432 |
+
analysis_collection = db.analysis_results
|
| 433 |
+
|
| 434 |
+
# Find analysis results for the patient
|
| 435 |
+
analysis_results = await analysis_collection.find({"patient_id": patient_id}).to_list(length=None)
|
| 436 |
+
|
| 437 |
+
if not analysis_results:
|
| 438 |
+
raise HTTPException(status_code=404, detail="No analysis results found for this patient")
|
| 439 |
+
|
| 440 |
+
# Create a simple PDF report
|
| 441 |
+
from reportlab.lib.pagesizes import letter
|
| 442 |
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
|
| 443 |
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
| 444 |
+
from reportlab.lib.units import inch
|
| 445 |
+
from reportlab.lib import colors
|
| 446 |
+
import io
|
| 447 |
+
|
| 448 |
+
# Create PDF buffer
|
| 449 |
+
buffer = io.BytesIO()
|
| 450 |
+
doc = SimpleDocTemplate(buffer, pagesize=letter)
|
| 451 |
+
styles = getSampleStyleSheet()
|
| 452 |
+
story = []
|
| 453 |
+
|
| 454 |
+
# Title
|
| 455 |
+
title_style = ParagraphStyle(
|
| 456 |
+
'CustomTitle',
|
| 457 |
+
parent=styles['Heading1'],
|
| 458 |
+
fontSize=16,
|
| 459 |
+
spaceAfter=30,
|
| 460 |
+
alignment=1 # Center alignment
|
| 461 |
+
)
|
| 462 |
+
story.append(Paragraph("Patient Analysis Report", title_style))
|
| 463 |
+
story.append(Spacer(1, 12))
|
| 464 |
+
|
| 465 |
+
# Patient Information
|
| 466 |
+
story.append(Paragraph("Patient Information", styles['Heading2']))
|
| 467 |
+
story.append(Spacer(1, 12))
|
| 468 |
+
|
| 469 |
+
# Get patient info from first analysis result
|
| 470 |
+
first_result = analysis_results[0]
|
| 471 |
+
patient_info = [
|
| 472 |
+
["Patient ID:", patient_id],
|
| 473 |
+
["Analysis Date:", first_result.get('timestamp', 'N/A')],
|
| 474 |
+
]
|
| 475 |
+
|
| 476 |
+
patient_table = Table(patient_info, colWidths=[2*inch, 4*inch])
|
| 477 |
+
patient_table.setStyle(TableStyle([
|
| 478 |
+
('BACKGROUND', (0, 0), (0, -1), colors.grey),
|
| 479 |
+
('TEXTCOLOR', (0, 0), (0, -1), colors.whitesmoke),
|
| 480 |
+
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
| 481 |
+
('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'),
|
| 482 |
+
('FONTSIZE', (0, 0), (-1, -1), 10),
|
| 483 |
+
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
| 484 |
+
('BACKGROUND', (1, 0), (1, -1), colors.beige),
|
| 485 |
+
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
| 486 |
+
]))
|
| 487 |
+
story.append(patient_table)
|
| 488 |
+
story.append(Spacer(1, 20))
|
| 489 |
+
|
| 490 |
+
# Analysis Results
|
| 491 |
+
story.append(Paragraph("Analysis Results", styles['Heading2']))
|
| 492 |
+
story.append(Spacer(1, 12))
|
| 493 |
+
|
| 494 |
+
for i, result in enumerate(analysis_results):
|
| 495 |
+
# Risk Assessment
|
| 496 |
+
suicide_risk = result.get('suicide_risk', {})
|
| 497 |
+
risk_level = suicide_risk.get('level', 'none') if isinstance(suicide_risk, dict) else 'none'
|
| 498 |
+
risk_score = suicide_risk.get('score', 0.0) if isinstance(suicide_risk, dict) else 0.0
|
| 499 |
+
risk_factors = suicide_risk.get('factors', []) if isinstance(suicide_risk, dict) else []
|
| 500 |
+
|
| 501 |
+
story.append(Paragraph(f"Analysis #{i+1}", styles['Heading3']))
|
| 502 |
+
story.append(Spacer(1, 6))
|
| 503 |
+
|
| 504 |
+
analysis_data = [
|
| 505 |
+
["Risk Level:", risk_level.upper()],
|
| 506 |
+
["Risk Score:", f"{risk_score:.2f}"],
|
| 507 |
+
["Risk Factors:", ", ".join(risk_factors) if risk_factors else "None identified"],
|
| 508 |
+
["Analysis Date:", result.get('timestamp', 'N/A')],
|
| 509 |
+
]
|
| 510 |
+
|
| 511 |
+
analysis_table = Table(analysis_data, colWidths=[2*inch, 4*inch])
|
| 512 |
+
analysis_table.setStyle(TableStyle([
|
| 513 |
+
('BACKGROUND', (0, 0), (0, -1), colors.lightblue),
|
| 514 |
+
('TEXTCOLOR', (0, 0), (0, -1), colors.black),
|
| 515 |
+
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
| 516 |
+
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
|
| 517 |
+
('FONTSIZE', (0, 0), (-1, -1), 9),
|
| 518 |
+
('BOTTOMPADDING', (0, 0), (-1, 0), 6),
|
| 519 |
+
('BACKGROUND', (1, 0), (1, -1), colors.white),
|
| 520 |
+
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
| 521 |
+
]))
|
| 522 |
+
story.append(analysis_table)
|
| 523 |
+
story.append(Spacer(1, 12))
|
| 524 |
+
|
| 525 |
+
# Summary if available
|
| 526 |
+
if result.get('summary'):
|
| 527 |
+
story.append(Paragraph("Summary:", styles['Heading4']))
|
| 528 |
+
story.append(Paragraph(result['summary'], styles['Normal']))
|
| 529 |
+
story.append(Spacer(1, 12))
|
| 530 |
+
|
| 531 |
+
# Build PDF
|
| 532 |
+
doc.build(story)
|
| 533 |
+
buffer.seek(0)
|
| 534 |
+
|
| 535 |
+
return StreamingResponse(
|
| 536 |
+
buffer,
|
| 537 |
+
media_type="application/pdf",
|
| 538 |
+
headers={"Content-Disposition": f"attachment; filename=patient_{patient_id}_analysis_reports.pdf"}
|
| 539 |
+
)
|
| 540 |
+
|
| 541 |
+
except Exception as e:
|
| 542 |
+
logger.error(f"Error generating PDF report for patient {patient_id}: {str(e)}")
|
| 543 |
+
raise HTTPException(status_code=500, detail=f"Failed to generate PDF report: {str(e)}")
|
| 544 |
+
|
| 545 |
+
@router.get("/patients/analysis-reports/all/pdf")
|
| 546 |
+
async def get_all_patients_analysis_reports_pdf(
|
| 547 |
+
current_user: dict = Depends(get_current_user)
|
| 548 |
+
):
|
| 549 |
+
"""Generate PDF analysis reports for all patients"""
|
| 550 |
+
try:
|
| 551 |
+
if not any(role in current_user.get('roles', []) for role in ['doctor', 'admin']):
|
| 552 |
+
raise HTTPException(status_code=403, detail="Only doctors and admins can generate PDF reports")
|
| 553 |
+
|
| 554 |
+
logger.info(f"Generating PDF analysis reports for all patients by {current_user['email']}")
|
| 555 |
+
|
| 556 |
+
# Import database collections
|
| 557 |
+
from db.mongo import db
|
| 558 |
+
analysis_collection = db.analysis_results
|
| 559 |
+
|
| 560 |
+
# Find all analysis results
|
| 561 |
+
analysis_results = await analysis_collection.find({}).to_list(length=None)
|
| 562 |
+
|
| 563 |
+
if not analysis_results:
|
| 564 |
+
raise HTTPException(status_code=404, detail="No analysis results found")
|
| 565 |
+
|
| 566 |
+
# Create a simple PDF report
|
| 567 |
+
from reportlab.lib.pagesizes import letter
|
| 568 |
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
|
| 569 |
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
| 570 |
+
from reportlab.lib.units import inch
|
| 571 |
+
from reportlab.lib import colors
|
| 572 |
+
import io
|
| 573 |
+
|
| 574 |
+
# Create PDF buffer
|
| 575 |
+
buffer = io.BytesIO()
|
| 576 |
+
doc = SimpleDocTemplate(buffer, pagesize=letter)
|
| 577 |
+
styles = getSampleStyleSheet()
|
| 578 |
+
story = []
|
| 579 |
+
|
| 580 |
+
# Title
|
| 581 |
+
title_style = ParagraphStyle(
|
| 582 |
+
'CustomTitle',
|
| 583 |
+
parent=styles['Heading1'],
|
| 584 |
+
fontSize=16,
|
| 585 |
+
spaceAfter=30,
|
| 586 |
+
alignment=1 # Center alignment
|
| 587 |
+
)
|
| 588 |
+
story.append(Paragraph("All Patients Analysis Reports", title_style))
|
| 589 |
+
story.append(Spacer(1, 12))
|
| 590 |
+
|
| 591 |
+
# Summary
|
| 592 |
+
story.append(Paragraph("Summary", styles['Heading2']))
|
| 593 |
+
story.append(Spacer(1, 12))
|
| 594 |
+
|
| 595 |
+
summary_data = [
|
| 596 |
+
["Total Analysis Reports:", str(len(analysis_results))],
|
| 597 |
+
["Generated Date:", datetime.now().strftime("%Y-%m-%d %H:%M:%S")],
|
| 598 |
+
["Generated By:", current_user['email']],
|
| 599 |
+
]
|
| 600 |
+
|
| 601 |
+
summary_table = Table(summary_data, colWidths=[2*inch, 4*inch])
|
| 602 |
+
summary_table.setStyle(TableStyle([
|
| 603 |
+
('BACKGROUND', (0, 0), (0, -1), colors.grey),
|
| 604 |
+
('TEXTCOLOR', (0, 0), (0, -1), colors.whitesmoke),
|
| 605 |
+
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
| 606 |
+
('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'),
|
| 607 |
+
('FONTSIZE', (0, 0), (-1, -1), 10),
|
| 608 |
+
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
| 609 |
+
('BACKGROUND', (1, 0), (1, -1), colors.beige),
|
| 610 |
+
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
| 611 |
+
]))
|
| 612 |
+
story.append(summary_table)
|
| 613 |
+
story.append(Spacer(1, 20))
|
| 614 |
+
|
| 615 |
+
# Group results by patient
|
| 616 |
+
patient_results = {}
|
| 617 |
+
for result in analysis_results:
|
| 618 |
+
patient_id = result.get('patient_id', 'unknown')
|
| 619 |
+
if patient_id not in patient_results:
|
| 620 |
+
patient_results[patient_id] = []
|
| 621 |
+
patient_results[patient_id].append(result)
|
| 622 |
+
|
| 623 |
+
# Patient Reports
|
| 624 |
+
for patient_id, results in patient_results.items():
|
| 625 |
+
story.append(Paragraph(f"Patient: {patient_id}", styles['Heading2']))
|
| 626 |
+
story.append(Spacer(1, 12))
|
| 627 |
+
|
| 628 |
+
for i, result in enumerate(results):
|
| 629 |
+
# Risk Assessment
|
| 630 |
+
suicide_risk = result.get('suicide_risk', {})
|
| 631 |
+
risk_level = suicide_risk.get('level', 'none') if isinstance(suicide_risk, dict) else 'none'
|
| 632 |
+
risk_score = suicide_risk.get('score', 0.0) if isinstance(suicide_risk, dict) else 0.0
|
| 633 |
+
risk_factors = suicide_risk.get('factors', []) if isinstance(suicide_risk, dict) else []
|
| 634 |
+
|
| 635 |
+
story.append(Paragraph(f"Analysis #{i+1}", styles['Heading3']))
|
| 636 |
+
story.append(Spacer(1, 6))
|
| 637 |
+
|
| 638 |
+
analysis_data = [
|
| 639 |
+
["Risk Level:", risk_level.upper()],
|
| 640 |
+
["Risk Score:", f"{risk_score:.2f}"],
|
| 641 |
+
["Risk Factors:", ", ".join(risk_factors) if risk_factors else "None identified"],
|
| 642 |
+
["Analysis Date:", result.get('timestamp', 'N/A')],
|
| 643 |
+
]
|
| 644 |
+
|
| 645 |
+
analysis_table = Table(analysis_data, colWidths=[2*inch, 4*inch])
|
| 646 |
+
analysis_table.setStyle(TableStyle([
|
| 647 |
+
('BACKGROUND', (0, 0), (0, -1), colors.lightblue),
|
| 648 |
+
('TEXTCOLOR', (0, 0), (0, -1), colors.black),
|
| 649 |
+
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
| 650 |
+
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
|
| 651 |
+
('FONTSIZE', (0, 0), (-1, -1), 9),
|
| 652 |
+
('BOTTOMPADDING', (0, 0), (-1, 0), 6),
|
| 653 |
+
('BACKGROUND', (1, 0), (1, -1), colors.white),
|
| 654 |
+
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
| 655 |
+
]))
|
| 656 |
+
story.append(analysis_table)
|
| 657 |
+
story.append(Spacer(1, 12))
|
| 658 |
+
|
| 659 |
+
# Summary if available
|
| 660 |
+
if result.get('summary'):
|
| 661 |
+
story.append(Paragraph("Summary:", styles['Heading4']))
|
| 662 |
+
story.append(Paragraph(result['summary'], styles['Normal']))
|
| 663 |
+
story.append(Spacer(1, 12))
|
| 664 |
+
|
| 665 |
+
story.append(Spacer(1, 20))
|
| 666 |
+
|
| 667 |
+
# Build PDF
|
| 668 |
+
doc.build(story)
|
| 669 |
+
buffer.seek(0)
|
| 670 |
+
|
| 671 |
+
return StreamingResponse(
|
| 672 |
+
buffer,
|
| 673 |
+
media_type="application/pdf",
|
| 674 |
+
headers={"Content-Disposition": f"attachment; filename=all_patients_analysis_reports_{datetime.now().strftime('%Y%m%d')}.pdf"}
|
| 675 |
+
)
|
| 676 |
+
|
| 677 |
+
except Exception as e:
|
| 678 |
+
logger.error(f"Error generating PDF report for all patients: {str(e)}")
|
| 679 |
+
raise HTTPException(status_code=500, detail=f"Failed to generate PDF report: {str(e)}")
|
| 680 |
+
|
| 681 |
|
requirements.txt
CHANGED
|
@@ -30,4 +30,5 @@ fitz
|
|
| 30 |
python-docx
|
| 31 |
pyfcm
|
| 32 |
httpx
|
| 33 |
-
jwt
|
|
|
|
|
|
| 30 |
python-docx
|
| 31 |
pyfcm
|
| 32 |
httpx
|
| 33 |
+
jwt
|
| 34 |
+
reportlab>=3.6.0
|