Spaces:
Sleeping
Sleeping
| import os | |
| from fastapi import FastAPI, Query | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import HTMLResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from app.services.data_service import data_service | |
| from app.services.count_converter import ( | |
| convert_count, | |
| get_available_standard_counts, | |
| get_available_ply_counts, | |
| nearest_standard_counts, | |
| parse_count_string, | |
| ) | |
| from app.services.reed_calculator import get_reed_range | |
| app = FastAPI(title="PDC Intelligence API", version="2.0.0") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| async def startup_event() -> None: | |
| print("Loading data...") | |
| data_service.load_data() | |
| print("Warming up API caches...") | |
| data_service.get_dashboard_summary() | |
| data_service.get_filters() | |
| data_service.get_relativity_analytics() | |
| data_service.get_validation_report(sample_size=100, seed=42) | |
| print("Cache warming complete.") | |
| def health() -> dict: | |
| return data_service.get_health() | |
| def dashboard() -> dict: | |
| return data_service.get_dashboard_summary() | |
| def filters() -> dict: | |
| return data_service.get_filters() | |
| def articles( | |
| page: int = Query(1, ge=1), | |
| limit: int = Query(25, ge=1, le=200), | |
| search: str = "", | |
| weave: str = "", | |
| blend: str = "", | |
| loom_type: str = "", | |
| dataset: str = "", | |
| count_band: str = "", | |
| ) -> dict: | |
| return data_service.get_articles( | |
| page, limit, search, weave, blend, loom_type, dataset, count_band, | |
| ) | |
| def article(master_article: str) -> dict: | |
| return data_service.get_article_detail(master_article) | |
| def relativity() -> dict: | |
| return data_service.get_relativity_analytics() | |
| def process_flow() -> dict: | |
| return data_service.get_process_flow() | |
| def documentation() -> dict: | |
| return data_service.get_documentation() | |
| def data_source_audit() -> dict: | |
| return data_service.get_data_source_audit() | |
| def validation_report( | |
| sample_size: int = Query(250, ge=30, le=2000), seed: int = 42, | |
| ) -> dict: | |
| return data_service.get_validation_report(sample_size=sample_size, seed=seed) | |
| def predict(payload: dict) -> dict: | |
| return data_service.predict_construction(payload) | |
| # ============== New endpoints ============== | |
| def available_counts() -> dict: | |
| return { | |
| "standard_ne": get_available_standard_counts(), | |
| "ply_yarns": get_available_ply_counts(), | |
| } | |
| def count_convert( | |
| value: float = Query(...), | |
| from_unit: str = Query("ne"), | |
| to_unit: str = Query("denier"), | |
| ) -> dict: | |
| result = convert_count(value, from_unit, to_unit) | |
| return { | |
| "input": {"value": value, "unit": from_unit}, | |
| "output": {"value": result, "unit": to_unit}, | |
| } | |
| def nearest_counts( | |
| value: float = Query(...), | |
| n: int = Query(3, ge=1, le=10), | |
| ) -> dict: | |
| nearest = nearest_standard_counts(value, n) | |
| return { | |
| "input_value": value, | |
| "nearest_standard_ne": nearest, | |
| } | |
| def parse_count(count_string: str = Query(...)) -> dict: | |
| result = parse_count_string(count_string) | |
| if result is None: | |
| return {"error": f"Could not parse: {count_string}"} | |
| return {"input": count_string, "parsed": result} | |
| def reed_ranges(loom_type: str = Query("airjet")) -> dict: | |
| return { | |
| "loom_type": loom_type, | |
| "reed_counts": get_reed_range(loom_type), | |
| "all_airjet": get_reed_range("airjet"), | |
| "all_rapier": get_reed_range("rapier"), | |
| } | |
| def export_xml(payload: dict): | |
| import io | |
| import xml.etree.ElementTree as ET | |
| from fastapi.responses import Response | |
| rec = payload.get("recommendation", {}) | |
| inp = payload.get("input", {}) | |
| def gv(key: str, fallback=0): | |
| val = rec.get(key) | |
| if val: | |
| try: | |
| return round(float(val), 2) | |
| except (ValueError, TypeError): | |
| return fallback | |
| return fallback | |
| w_cnt = gv("warp_count", inp.get("normalized_warp_count") or inp.get("raw_warp_count") or 0) | |
| wt_cnt = gv("weft_count", inp.get("normalized_weft_count") or inp.get("raw_weft_count") or 0) | |
| reed = gv("reed_count", 0) | |
| e_dent = gv("ends_per_dent", 0) | |
| r_space = gv("reed_space", 0) | |
| g_epi = gv("greige_epi", 0) | |
| g_ppi = gv("greige_ppi", 0) | |
| f_epi = gv("finish_epi", 0) | |
| f_ppi = gv("finish_ppi", 0) | |
| f_width = gv("finish_width", 0) | |
| blend = str(inp.get("blend", "")) | |
| weave = str(inp.get("weave", "")) | |
| root = ET.Element("PenelopeDesigns", Version="v1.12") | |
| fabric = ET.SubElement(root, "Fabric", Name="construction") | |
| fabric.set("Folder1", "BA") | |
| fabric.set("Folder2", "DS") | |
| fabric.set("Folder3", "2026") | |
| yarns = ET.SubElement(fabric, "Yarns") | |
| for yname, count, ylabel in [ | |
| ("WARP_YARN", w_cnt, "WARP"), | |
| ("WEFT_YARN", wt_cnt, "WEFT"), | |
| ]: | |
| yarn = ET.SubElement(yarns, "Yarn", DataBaseId="0", Name=yname, Set="construction") | |
| ET.SubElement(yarn, "SecondName").text = ylabel | |
| ET.SubElement(yarn, "Count").text = f"{count} ne" if count else "" | |
| ET.SubElement(yarn, "Material").text = blend | |
| ends_total = int(g_epi * r_space * 0.0254 * 100) if g_epi and r_space else 0 | |
| warp = ET.SubElement(fabric, "Warp", NEndsRepeat=str(ends_total), NColorWays="1", NColors="1", NBeams="1") | |
| cw_warp = ET.SubElement(warp, "ColorWay", Name="A") | |
| ET.SubElement(cw_warp, "Yarn", Id="0", DataBaseId="0", Set="construction", Name="WARP_YARN", NEnds=str(ends_total)) | |
| weft = ET.SubElement(fabric, "Weft", NEndsRepeat="0", NColorWays="1", NColors="1") | |
| cw_weft = ET.SubElement(weft, "ColorWay", Name="A") | |
| ET.SubElement(cw_weft, "Yarn", Id="0", DataBaseId="0", Set="construction", Name="WEFT_YARN") | |
| ET.SubElement(fabric, "Weave", Name=f"{weave}.", Folder1="WEAVE", NEndsRepeat="0", NPicksRepeat="0") | |
| denting = ET.SubElement(fabric, "Denting", NDents=str(int(e_dent)), NEnds="4") | |
| ET.SubElement(denting, "Dents").text = f"{int(e_dent)}x{int(e_dent)}" if e_dent else "0" | |
| tech = ET.SubElement(fabric, "TechnicalData") | |
| reed_dens_cm = round(reed / 39.37, 1) if reed else 0 | |
| ET.SubElement(tech, "ReedDensity", Value=str(reed), Unit="inch", ValueCm=str(reed_dens_cm)) | |
| reed_width_cm = round(r_space * 2.54, 2) if r_space else 0 | |
| ET.SubElement(tech, "ReedWidth", Value=str(r_space), Unit="inch", ValueCM=str(reed_width_cm)) | |
| fin_wid_cm = round(f_width * 2.54, 2) if f_width else 0 | |
| ET.SubElement(tech, "FinishedWidth", Value=str(f_width), Unit="inch", ValueCM=str(fin_wid_cm)) | |
| ET.SubElement(tech, "FinishedWarpDensity", Value=str(f_epi), Unit="inch", ValueCm=str(round(f_epi / 39.37, 1))) | |
| ET.SubElement(tech, "WovenWarpDensity", Value=str(g_epi), Unit="inch", ValueCm=str(round(g_epi / 39.37, 1))) | |
| ET.SubElement(tech, "WeftDensity", Value=str(g_ppi), Unit="inch", ValueCm=str(round(g_ppi / 39.37, 1))) | |
| ET.SubElement(tech, "Composition", Warp=blend, Weft=blend, Total=blend) | |
| ET.SubElement(tech, "Comment", Weave=weave) | |
| tree = ET.ElementTree(root) | |
| ET.indent(tree, space=" ", level=0) | |
| out = io.BytesIO() | |
| tree.write(out, encoding="utf-8", xml_declaration=True) | |
| return Response( | |
| content=out.getvalue(), | |
| media_type="application/xml", | |
| headers={"Content-Disposition": "attachment; filename=construction.xml"}, | |
| ) | |
| # Serve Next.js static frontend | |
| FRONTEND_BUILD_DIR = os.path.join( | |
| os.path.dirname(os.path.abspath(__file__)), "..", "frontend", "out" | |
| ) | |
| FRONTEND_BUILD_DIR = os.path.abspath(FRONTEND_BUILD_DIR) | |
| if os.path.isdir(FRONTEND_BUILD_DIR): | |
| app.mount( | |
| "/", StaticFiles(directory=FRONTEND_BUILD_DIR, html=True), name="frontend" | |
| ) | |
| else: | |
| def serve_frontend(): | |
| return HTMLResponse( | |
| content=""" | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>PDC Intelligence</title> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| </head> | |
| <body class="bg-slate-50"> | |
| <div class="p-8"> | |
| <h1 class="text-2xl font-bold text-slate-800">PDC Intelligence</h1> | |
| <p class="text-slate-600 mt-2">Backend is running. API available at /api/*</p> | |
| <div class="mt-4 p-4 bg-white rounded-lg border border-slate-200"> | |
| <h2 class="font-semibold">Available Endpoints:</h2> | |
| <ul class="mt-2 space-y-1 text-sm"> | |
| <li><code class="bg-slate-100 px-2 py-1">GET /api/health</code> - Health check</li> | |
| <li><code class="bg-slate-100 px-2 py-1">GET /api/dashboard</code> - Dashboard summary</li> | |
| <li><code class="bg-slate-100 px-2 py-1">GET /api/filters</code> - Available filters</li> | |
| <li><code class="bg-slate-100 px-2 py-1">GET /api/articles</code> - Article search</li> | |
| <li><code class="bg-slate-100 px-2 py-1">POST /api/predictions/construction</code> - Construction prediction</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |
| """, | |
| status_code=200, | |
| ) | |