Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1681,6 +1681,312 @@ async def health_check():
|
|
| 1681 |
return {"status": "healthy", "service": "Real Estate Financial Model API"}
|
| 1682 |
|
| 1683 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1684 |
if __name__ == "__main__":
|
| 1685 |
|
| 1686 |
# Hardcoded API Key
|
|
|
|
| 1681 |
return {"status": "healthy", "service": "Real Estate Financial Model API"}
|
| 1682 |
|
| 1683 |
|
| 1684 |
+
@app.post("/api/generate-model-with-summary")
|
| 1685 |
+
async def generate_model_with_summary(files: List[UploadFile] = File(...)):
|
| 1686 |
+
"""
|
| 1687 |
+
API endpoint that returns JSON summary with key metrics
|
| 1688 |
+
AND includes base64 encoded Excel file
|
| 1689 |
+
|
| 1690 |
+
Returns JSON with:
|
| 1691 |
+
- Key financial metrics
|
| 1692 |
+
- Deal summary
|
| 1693 |
+
- Market analysis
|
| 1694 |
+
- Excel file as base64
|
| 1695 |
+
"""
|
| 1696 |
+
if not files:
|
| 1697 |
+
raise HTTPException(status_code=400, detail="No files uploaded")
|
| 1698 |
+
|
| 1699 |
+
temp_dir = None
|
| 1700 |
+
try:
|
| 1701 |
+
# Create temporary directory
|
| 1702 |
+
temp_dir = tempfile.mkdtemp()
|
| 1703 |
+
print(f"📁 Created temp directory: {temp_dir}")
|
| 1704 |
+
|
| 1705 |
+
# Save uploaded files
|
| 1706 |
+
for upload_file in files:
|
| 1707 |
+
file_path = Path(temp_dir) / upload_file.filename
|
| 1708 |
+
print(f"💾 Saving file: {upload_file.filename}")
|
| 1709 |
+
|
| 1710 |
+
with open(file_path, "wb") as f:
|
| 1711 |
+
content = await upload_file.read()
|
| 1712 |
+
f.write(content)
|
| 1713 |
+
|
| 1714 |
+
print(f"✅ Saved {len(files)} files")
|
| 1715 |
+
|
| 1716 |
+
# Initialize pipeline
|
| 1717 |
+
print("🔧 Initializing pipeline...")
|
| 1718 |
+
pipeline = RealEstateModelPipeline(GEMINI_API_KEY)
|
| 1719 |
+
|
| 1720 |
+
# Create output file
|
| 1721 |
+
output_file = Path(temp_dir) / "Real_Estate_Financial_Model.xlsx"
|
| 1722 |
+
|
| 1723 |
+
# Run pipeline
|
| 1724 |
+
print("🚀 Running pipeline...")
|
| 1725 |
+
pipeline.run_full_pipeline(temp_dir, str(output_file))
|
| 1726 |
+
|
| 1727 |
+
# Check if file was created
|
| 1728 |
+
if not output_file.exists():
|
| 1729 |
+
raise Exception("Excel file was not generated")
|
| 1730 |
+
|
| 1731 |
+
print(f"✅ Excel file created: {output_file}")
|
| 1732 |
+
|
| 1733 |
+
# Get formula results
|
| 1734 |
+
r = pipeline.formula_results
|
| 1735 |
+
d = pipeline.structured_data
|
| 1736 |
+
|
| 1737 |
+
# Build comprehensive JSON response
|
| 1738 |
+
response_data = {
|
| 1739 |
+
"status": "success",
|
| 1740 |
+
"message": "Financial model generated successfully",
|
| 1741 |
+
|
| 1742 |
+
# KEY METRICS (Dashboard Top Cards)
|
| 1743 |
+
"key_metrics": {
|
| 1744 |
+
"purchase_price": {
|
| 1745 |
+
"value": r.get('PRICE', 0),
|
| 1746 |
+
"formatted": f"${r.get('PRICE', 0):,.0f}",
|
| 1747 |
+
"label": "Purchase Price"
|
| 1748 |
+
},
|
| 1749 |
+
"noi": {
|
| 1750 |
+
"value": r.get('NET_OPERATING_INCOME', 0),
|
| 1751 |
+
"formatted": f"${r.get('NET_OPERATING_INCOME', 0):,.0f}",
|
| 1752 |
+
"label": "NOI"
|
| 1753 |
+
},
|
| 1754 |
+
"cap_rate": {
|
| 1755 |
+
"value": r.get('CAP_RATE', 0),
|
| 1756 |
+
"formatted": f"{r.get('CAP_RATE', 0):.1f}%",
|
| 1757 |
+
"label": "Cap Rate"
|
| 1758 |
+
},
|
| 1759 |
+
"irr_10yr": {
|
| 1760 |
+
"value": float(r.get('IRR_TO_LP', 0).real if isinstance(r.get('IRR_TO_LP', 0), complex) else r.get('IRR_TO_LP', 0)),
|
| 1761 |
+
"formatted": f"{float(r.get('IRR_TO_LP', 0).real if isinstance(r.get('IRR_TO_LP', 0), complex) else r.get('IRR_TO_LP', 0)):.1f}%",
|
| 1762 |
+
"label": "IRR (10 yr)"
|
| 1763 |
+
},
|
| 1764 |
+
"dscr": {
|
| 1765 |
+
"value": r.get('DEBT_SERVICE_COVERAGE_RATIO', 0),
|
| 1766 |
+
"formatted": f"{r.get('DEBT_SERVICE_COVERAGE_RATIO', 0):.2f}x",
|
| 1767 |
+
"label": "DSCR"
|
| 1768 |
+
}
|
| 1769 |
+
},
|
| 1770 |
+
|
| 1771 |
+
# T12 SUMMARY (Extracted Summary)
|
| 1772 |
+
"t12_summary": {
|
| 1773 |
+
"gross_income": {
|
| 1774 |
+
"annual": r.get('EFFECTIVE_GROSS_INCOME', 0),
|
| 1775 |
+
"monthly": r.get('EFFECTIVE_GROSS_INCOME', 0) / 12,
|
| 1776 |
+
"source": "Calculated from rent roll"
|
| 1777 |
+
},
|
| 1778 |
+
"operating_expenses": {
|
| 1779 |
+
"annual": r.get('TOTAL_OPERATING_EXPENSES', 0),
|
| 1780 |
+
"monthly": r.get('TOTAL_OPERATING_EXPENSES', 0) / 12,
|
| 1781 |
+
"expense_ratio": f"{(r.get('TOTAL_OPERATING_EXPENSES', 0) / r.get('EFFECTIVE_GROSS_INCOME', 1) if r.get('EFFECTIVE_GROSS_INCOME', 0) > 0 else 0):.1%}"
|
| 1782 |
+
},
|
| 1783 |
+
"noi": {
|
| 1784 |
+
"annual": r.get('NET_OPERATING_INCOME', 0),
|
| 1785 |
+
"monthly": r.get('NET_OPERATING_INCOME', 0) / 12,
|
| 1786 |
+
"verified": True
|
| 1787 |
+
},
|
| 1788 |
+
"occupancy": {
|
| 1789 |
+
"rate": f"{(1 - r.get('VACANCY_RATE', 0)):.0%}",
|
| 1790 |
+
"value": 1 - r.get('VACANCY_RATE', 0)
|
| 1791 |
+
}
|
| 1792 |
+
},
|
| 1793 |
+
|
| 1794 |
+
# PROPERTY INFO
|
| 1795 |
+
"property_info": {
|
| 1796 |
+
"address": d.get('property_info', {}).get('address', 'N/A'),
|
| 1797 |
+
"property_type": d.get('property_info', {}).get('property_type', 'N/A'),
|
| 1798 |
+
"units": r.get('UNITS', 0),
|
| 1799 |
+
"gross_sf": r.get('GROSS_SF', 0),
|
| 1800 |
+
"rentable_sf": r.get('RENTABLE_SF', 0),
|
| 1801 |
+
"building_efficiency": f"{r.get('BUILDING_EFFICIENCY', 0):.1%}"
|
| 1802 |
+
},
|
| 1803 |
+
|
| 1804 |
+
# DEAL VS MARKET AVERAGES
|
| 1805 |
+
"market_comparison": {
|
| 1806 |
+
"your_deal": {
|
| 1807 |
+
"cap_rate": r.get('CAP_RATE', 0) / 100,
|
| 1808 |
+
"irr": float(r.get('IRR_TO_LP', 0).real if isinstance(r.get('IRR_TO_LP', 0), complex) else r.get('IRR_TO_LP', 0)) / 100,
|
| 1809 |
+
"dscr": r.get('DEBT_SERVICE_COVERAGE_RATIO', 0)
|
| 1810 |
+
},
|
| 1811 |
+
"market_average": {
|
| 1812 |
+
"cap_rate": 0.055, # Example market average
|
| 1813 |
+
"irr": 0.12,
|
| 1814 |
+
"dscr": 1.25
|
| 1815 |
+
}
|
| 1816 |
+
},
|
| 1817 |
+
|
| 1818 |
+
# AI OBSERVATIONS
|
| 1819 |
+
"ai_observations": [
|
| 1820 |
+
{
|
| 1821 |
+
"observation": f"Cap Rate {'above' if r.get('CAP_RATE', 0) > 5.5 else 'below'} area average",
|
| 1822 |
+
"recommendation": "undervalued potential" if r.get('CAP_RATE', 0) > 5.5 else "premium pricing"
|
| 1823 |
+
},
|
| 1824 |
+
{
|
| 1825 |
+
"observation": f"Expense ratio at {(r.get('TOTAL_OPERATING_EXPENSES', 0) / r.get('EFFECTIVE_GROSS_INCOME', 1) if r.get('EFFECTIVE_GROSS_INCOME', 0) > 0 else 0):.1%}",
|
| 1826 |
+
"recommendation": "within normal range" if 0.3 <= (r.get('TOTAL_OPERATING_EXPENSES', 0) / r.get('EFFECTIVE_GROSS_INCOME', 1)) <= 0.45 else "review expenses"
|
| 1827 |
+
},
|
| 1828 |
+
{
|
| 1829 |
+
"observation": "DSCR meets financing standards" if r.get('DEBT_SERVICE_COVERAGE_RATIO', 0) >= 1.2 else "DSCR below standards",
|
| 1830 |
+
"recommendation": "approved" if r.get('DEBT_SERVICE_COVERAGE_RATIO', 0) >= 1.2 else "increase equity"
|
| 1831 |
+
},
|
| 1832 |
+
{
|
| 1833 |
+
"observation": "NOI verification",
|
| 1834 |
+
"recommendation": "Full underwriting review recommended"
|
| 1835 |
+
}
|
| 1836 |
+
],
|
| 1837 |
+
|
| 1838 |
+
# CAPITAL STACK
|
| 1839 |
+
"capital_stack": {
|
| 1840 |
+
"total_project_cost": r.get('TOTAL_FINANCING_CONTINGENCY_AND_RESERVES', 0),
|
| 1841 |
+
"total_debt": r.get('TOTAL_DEBT', 0),
|
| 1842 |
+
"total_equity": r.get('TOTAL_EQUITY', 0),
|
| 1843 |
+
"debt_percentage": r.get('DEBT_PERCENTAGE', 0),
|
| 1844 |
+
"equity_percentage": r.get('EQUITY_PERCENTAGE', 0),
|
| 1845 |
+
"ltc_ratio": r.get('LTC_RATIO', 0)
|
| 1846 |
+
},
|
| 1847 |
+
|
| 1848 |
+
# RETURNS ANALYSIS
|
| 1849 |
+
"returns": {
|
| 1850 |
+
"yield_on_cost": r.get('YIELD_ON_COST_PERCENTAGE', 0),
|
| 1851 |
+
"stabilized_yield_on_cost": r.get('STABILIZED_YIELD_ON_COST_PERCENTAGE', 0),
|
| 1852 |
+
"cash_on_cash_return": r.get('CASH_ON_CASH_RETURN', 0),
|
| 1853 |
+
"lp_multiple": r.get('LP_MULTIPLE', 0),
|
| 1854 |
+
"lp_irr": float(r.get('IRR_TO_LP', 0).real if isinstance(r.get('IRR_TO_LP', 0), complex) else r.get('IRR_TO_LP', 0)),
|
| 1855 |
+
"hold_period_years": r.get('HOLD_PERIOD_MONTHS', 0) / 12
|
| 1856 |
+
},
|
| 1857 |
+
|
| 1858 |
+
# OPERATING METRICS
|
| 1859 |
+
"operating_metrics": {
|
| 1860 |
+
"gross_potential_rent": r.get('GROSS_POTENTIAL_FREE_MARKET_RENT', 0) + r.get('GROSS_POTENTIAL_AFFORDABLE_RENT', 0),
|
| 1861 |
+
"vacancy_loss": r.get('VACANCY_LOSS', 0),
|
| 1862 |
+
"effective_gross_income": r.get('EFFECTIVE_GROSS_INCOME', 0),
|
| 1863 |
+
"operating_expenses": r.get('TOTAL_OPERATING_EXPENSES', 0),
|
| 1864 |
+
"noi": r.get('NET_OPERATING_INCOME', 0),
|
| 1865 |
+
"noi_per_unit": r.get('NOI_PER_UNIT', 0),
|
| 1866 |
+
"noi_per_sf": r.get('NOI_PER_GSF', 0)
|
| 1867 |
+
},
|
| 1868 |
+
|
| 1869 |
+
# COST BREAKDOWN
|
| 1870 |
+
"cost_breakdown": {
|
| 1871 |
+
"acquisition": {
|
| 1872 |
+
"total": r.get('TOTAL_ACQUISITION_COST', 0),
|
| 1873 |
+
"per_unit": r.get('TOTAL_ACQUISITION_COST_PER_UNIT', 0),
|
| 1874 |
+
"per_sf": r.get('TOTAL_ACQUISITION_COST_PER_GSF', 0)
|
| 1875 |
+
},
|
| 1876 |
+
"construction": {
|
| 1877 |
+
"total": r.get('TOTAL_CONSTRUCTION_GMP', 0),
|
| 1878 |
+
"per_unit": r.get('CONSTRUCTION_GMP_PER_UNIT', 0),
|
| 1879 |
+
"per_sf": r.get('CONSTRUCTION_GMP_PER_GSF', 0)
|
| 1880 |
+
},
|
| 1881 |
+
"soft_costs": {
|
| 1882 |
+
"total": r.get('TOTAL_SOFT_COST', 0),
|
| 1883 |
+
"per_sf": r.get('TOTAL_SOFT_COST_PER_GSF', 0)
|
| 1884 |
+
},
|
| 1885 |
+
"contingency": r.get('CONTINGENCY_COST', 0),
|
| 1886 |
+
"reserves": r.get('OPERATING_RESERVE', 0)
|
| 1887 |
+
},
|
| 1888 |
+
|
| 1889 |
+
# SOURCE FILES
|
| 1890 |
+
"source_files": [
|
| 1891 |
+
{
|
| 1892 |
+
"filename": file.filename,
|
| 1893 |
+
"size": len(await file.read()) if hasattr(file, 'read') else 0,
|
| 1894 |
+
"type": file.filename.split('.')[-1].upper()
|
| 1895 |
+
}
|
| 1896 |
+
for file in files
|
| 1897 |
+
],
|
| 1898 |
+
|
| 1899 |
+
# EXCEL FILE (BASE64 ENCODED)
|
| 1900 |
+
"excel_file": {
|
| 1901 |
+
"filename": "Real_Estate_Financial_Model.xlsx",
|
| 1902 |
+
"size_bytes": output_file.stat().st_size,
|
| 1903 |
+
"base64_data": base64.b64encode(output_file.read_bytes()).decode('utf-8')
|
| 1904 |
+
}
|
| 1905 |
+
}
|
| 1906 |
+
|
| 1907 |
+
print("✅ Response JSON created")
|
| 1908 |
+
|
| 1909 |
+
return JSONResponse(content=response_data)
|
| 1910 |
+
|
| 1911 |
+
except Exception as e:
|
| 1912 |
+
import traceback
|
| 1913 |
+
error_details = traceback.format_exc()
|
| 1914 |
+
print("❌ ERROR OCCURRED:")
|
| 1915 |
+
print(error_details)
|
| 1916 |
+
|
| 1917 |
+
raise HTTPException(
|
| 1918 |
+
status_code=500,
|
| 1919 |
+
detail={
|
| 1920 |
+
"error": str(e),
|
| 1921 |
+
"type": type(e).__name__,
|
| 1922 |
+
"traceback": error_details
|
| 1923 |
+
}
|
| 1924 |
+
)
|
| 1925 |
+
|
| 1926 |
+
|
| 1927 |
+
|
| 1928 |
+
# NEW: Endpoint that returns ONLY JSON (no Excel file)
|
| 1929 |
+
@app.post("/api/analyze-only")
|
| 1930 |
+
async def analyze_only(files: List[UploadFile] = File(...)):
|
| 1931 |
+
"""
|
| 1932 |
+
Returns only JSON summary without Excel file
|
| 1933 |
+
Faster response, good for dashboards
|
| 1934 |
+
"""
|
| 1935 |
+
if not files:
|
| 1936 |
+
raise HTTPException(status_code=400, detail="No files uploaded")
|
| 1937 |
+
|
| 1938 |
+
temp_dir = None
|
| 1939 |
+
try:
|
| 1940 |
+
temp_dir = tempfile.mkdtemp()
|
| 1941 |
+
|
| 1942 |
+
for upload_file in files:
|
| 1943 |
+
file_path = Path(temp_dir) / upload_file.filename
|
| 1944 |
+
with open(file_path, "wb") as f:
|
| 1945 |
+
content = await upload_file.read()
|
| 1946 |
+
f.write(content)
|
| 1947 |
+
|
| 1948 |
+
pipeline = RealEstateModelPipeline(GEMINI_API_KEY)
|
| 1949 |
+
|
| 1950 |
+
# Extract and process data (no Excel generation)
|
| 1951 |
+
pipeline.extract_all_pdfs(temp_dir)
|
| 1952 |
+
structured_data = pipeline.extract_structured_data()
|
| 1953 |
+
structured_data = pipeline.post_process_extracted_data(structured_data)
|
| 1954 |
+
pipeline.calculate_all_formulas(structured_data)
|
| 1955 |
+
|
| 1956 |
+
r = pipeline.formula_results
|
| 1957 |
+
d = pipeline.structured_data
|
| 1958 |
+
|
| 1959 |
+
# Return lightweight JSON
|
| 1960 |
+
return JSONResponse(content={
|
| 1961 |
+
"status": "success",
|
| 1962 |
+
"key_metrics": {
|
| 1963 |
+
"purchase_price": r.get('PRICE', 0),
|
| 1964 |
+
"noi": r.get('NET_OPERATING_INCOME', 0),
|
| 1965 |
+
"cap_rate": r.get('CAP_RATE', 0),
|
| 1966 |
+
"irr": float(r.get('IRR_TO_LP', 0).real if isinstance(r.get('IRR_TO_LP', 0), complex) else r.get('IRR_TO_LP', 0)),
|
| 1967 |
+
"dscr": r.get('DEBT_SERVICE_COVERAGE_RATIO', 0)
|
| 1968 |
+
},
|
| 1969 |
+
"property_info": {
|
| 1970 |
+
"address": d.get('property_info', {}).get('address', 'N/A'),
|
| 1971 |
+
"units": r.get('UNITS', 0),
|
| 1972 |
+
"gross_sf": r.get('GROSS_SF', 0)
|
| 1973 |
+
},
|
| 1974 |
+
"capital_stack": {
|
| 1975 |
+
"total_project_cost": r.get('TOTAL_FINANCING_CONTINGENCY_AND_RESERVES', 0),
|
| 1976 |
+
"total_debt": r.get('TOTAL_DEBT', 0),
|
| 1977 |
+
"total_equity": r.get('TOTAL_EQUITY', 0)
|
| 1978 |
+
},
|
| 1979 |
+
"returns": {
|
| 1980 |
+
"yield_on_cost": r.get('YIELD_ON_COST_PERCENTAGE', 0),
|
| 1981 |
+
"cash_on_cash": r.get('CASH_ON_CASH_RETURN', 0),
|
| 1982 |
+
"lp_multiple": r.get('LP_MULTIPLE', 0)
|
| 1983 |
+
}
|
| 1984 |
+
})
|
| 1985 |
+
|
| 1986 |
+
except Exception as e:
|
| 1987 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 1988 |
+
|
| 1989 |
+
|
| 1990 |
if __name__ == "__main__":
|
| 1991 |
|
| 1992 |
# Hardcoded API Key
|