Spaces:
Running
Wire FLEET (Vessels) + Voyage Risk; graceful consensus (amaru-independent); fix Living Organism load flash
Browse filesGAP-1: FLEET (Vessels) nav group (Fleet Overview, Maintenance & Compliance, Fleet Briefings) wired to platform seed-data/vessels/* served VERBATIM via killinchu_fleet_vessels.py under /api/killinchu/v1/fleet/* (18 vessels, 4 forecast modules, predictive-maintenance, certs, PSC deficiencies, AI briefings). Mandatory 'Sample fleet dataset - not a live AIS/class-society feed' label on every tab. GAP-2: Voyage Risk Exchange governed-decision card on Maritime tab (signals->forecast->evidence->recommendation w/ rollback->signed receipt; Trust score shown as advisory conjecture). QA fixes: (1) Consensus degrades GRACEFULLY - killinchu no longer hard-depends on amaru (non-essential, short timeout, quorum computed without amaru); (2) Living Organism loading state eliminates blank-canvas flash. Honesty doctrine preserved: Lambda=Conjecture 1, 5 proven formulas, SLSA L2, real cosign key keeps receipts PASS+tamper-FAIL, no jargon, no fabricated numbers.
- fleet_vessels_data.json +1 -0
- killinchu_elite_console.py +245 -6
- killinchu_fleet_vessels.py +243 -0
- killinchu_fusion.py +46 -4
- serve.py +19 -0
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"vessels":[{"id":1,"name":"CONSTANTA SPIRIT","imo":"9732847","mmsi":"264700100","vesselType":"bulk","shipClass":"Handymax","flag":"Romania","yearBuilt":2019,"grossTonnage":33400,"deadweight":58000,"length":190,"beam":32.26,"draft":12.8,"status":"at_sea","fleetId":1,"currentLat":43.18,"currentLon":28.59,"currentSpeed":12.4,"currentHeading":195,"lastPort":"Constanta","nextPort":"Istanbul","eta":"2026-03-29","fuelConsumptionDaily":28,"co2EmissionsDaily":87.2,"ciiRating":"B","ciiValue":5.82,"attainedCII":5.82,"requiredCII":6.45,"hullCondition":91,"engineHealth":94,"maintenanceScore":90,"tce":18200,"utilization":92,"eexi":7.1,"tradeLane":"Black Sea — Marmara","operator":"Navrom SA","classificationSociety":"Bureau Veritas","builder":"Daewoo Shipbuilding"},{"id":2,"name":"MARE NIGRUM","imo":"9781234","mmsi":"264700200","vesselType":"tanker","shipClass":"Aframax","flag":"Romania","yearBuilt":2020,"grossTonnage":62500,"deadweight":115000,"length":250,"beam":44,"draft":15.2,"status":"at_sea","fleetId":1,"currentLat":41.02,"currentLon":29.01,"currentSpeed":10.8,"currentHeading":210,"lastPort":"Constanta","nextPort":"Augusta","eta":"2026-04-02","fuelConsumptionDaily":52,"co2EmissionsDaily":161.9,"ciiRating":"A","ciiValue":4.21,"attainedCII":4.21,"requiredCII":5.88,"hullCondition":95,"engineHealth":96,"maintenanceScore":94,"tce":28500,"utilization":96,"eexi":5.4,"tradeLane":"Black Sea — Mediterranean","operator":"Petrom Marine","classificationSociety":"Lloyd's Register","builder":"Samsung Heavy Industries"},{"id":3,"name":"COSCO SHANGHAI","imo":"9795432","mmsi":"413456780","vesselType":"container","shipClass":"Neo-Panamax","flag":"China","yearBuilt":2021,"grossTonnage":115000,"deadweight":136000,"length":366,"beam":51.2,"draft":16.0,"status":"at_sea","fleetId":2,"currentLat":34.05,"currentLon":-152.3,"currentSpeed":19.8,"currentHeading":275,"lastPort":"Ningbo-Zhoushan","nextPort":"Long Beach","eta":"2026-04-03","fuelConsumptionDaily":195,"co2EmissionsDaily":607.2,"ciiRating":"B","ciiValue":6.12,"attainedCII":6.12,"requiredCII":7.35,"hullCondition":96,"engineHealth":97,"maintenanceScore":95,"tce":31200,"utilization":97,"eexi":6.8,"tradeLane":"Trans-Pacific Eastbound","operator":"COSCO Shipping Lines","classificationSociety":"China Classification Society","builder":"Jiangsu New Yangzi Shipbuilding"},{"id":4,"name":"NORDIC WOLVERINE","imo":"9834567","mmsi":"258765000","vesselType":"tanker","shipClass":"VLCC","flag":"Norway","yearBuilt":2022,"grossTonnage":160000,"deadweight":300000,"length":333,"beam":60,"draft":22.5,"status":"at_sea","fleetId":4,"currentLat":26.2,"currentLon":56.3,"currentSpeed":14.1,"currentHeading":90,"lastPort":"Ras Tanura","nextPort":"Ningbo-Zhoushan","eta":"2026-04-15","fuelConsumptionDaily":85,"co2EmissionsDaily":264.7,"ciiRating":"B","ciiValue":2.18,"attainedCII":2.18,"requiredCII":2.95,"hullCondition":98,"engineHealth":99,"maintenanceScore":97,"tce":42100,"utilization":98,"eexi":4.2,"tradeLane":"AG — Far East","operator":"Frontline Ltd","classificationSociety":"DNV","builder":"Hyundai Heavy Industries"},{"id":5,"name":"OLDENDORFF BRISBANE","imo":"9756789","mmsi":"211345600","vesselType":"bulk","shipClass":"Capesize","flag":"Germany","yearBuilt":2018,"grossTonnage":92000,"deadweight":180000,"length":292,"beam":45,"draft":18.2,"status":"at_sea","fleetId":3,"currentLat":-33.9,"currentLon":18.4,"currentSpeed":13.2,"currentHeading":210,"lastPort":"Richards Bay","nextPort":"Qingdao","eta":"2026-04-18","fuelConsumptionDaily":48,"co2EmissionsDaily":149.5,"ciiRating":"C","ciiValue":4.52,"attainedCII":4.52,"requiredCII":4.1,"hullCondition":82,"engineHealth":85,"maintenanceScore":80,"tce":22800,"utilization":90,"eexi":9.1,"tradeLane":"South Africa — China Coal","operator":"Oldendorff Carriers","classificationSociety":"Germanischer Lloyd","builder":"Imabari Shipbuilding"},{"id":6,"name":"MAERSK EDINBURGH","imo":"9812345","mmsi":"219018600","vesselType":"container","shipClass":"Triple-E","flag":"Denmark","yearBuilt":2023,"grossTonnage":214000,"deadweight":198000,"length":400,"beam":58.6,"draft":16.5,"status":"at_sea","fleetId":2,"currentLat":1.3,"currentLon":103.8,"currentSpeed":16.5,"currentHeading":350,"lastPort":"Singapore","nextPort":"Rotterdam","eta":"2026-04-14","fuelConsumptionDaily":180,"co2EmissionsDaily":282.6,"ciiRating":"A","ciiValue":3.45,"attainedCII":3.45,"requiredCII":5.12,"hullCondition":99,"engineHealth":99,"maintenanceScore":98,"tce":38500,"utilization":99,"eexi":4.1,"tradeLane":"Asia — Europe (AE7)","operator":"Maersk Line","classificationSociety":"DNV","builder":"Daewoo Shipbuilding"},{"id":7,"name":"GALATA BRIDGE","imo":"9745678","mmsi":"271001234","vesselType":"bulk","shipClass":"Supramax","flag":"Turkey","yearBuilt":2017,"grossTonnage":32000,"deadweight":56000,"length":190,"beam":32.26,"draft":12.8,"status":"in_port","fleetId":1,"currentLat":44.17,"currentLon":28.65,"currentSpeed":0,"currentHeading":180,"lastPort":"Novorossiysk","nextPort":"Constanta","eta":"2026-03-28","fuelConsumptionDaily":0,"co2EmissionsDaily":0,"ciiRating":"B","ciiValue":5.55,"attainedCII":5.55,"requiredCII":6.45,"hullCondition":85,"engineHealth":88,"maintenanceScore":84,"tce":15600,"utilization":87,"eexi":7.8,"tradeLane":"Black Sea Intra-Regional","operator":"Arkas Holding","classificationSociety":"Turkish Lloyd","builder":"Oshima Shipbuilding"},{"id":8,"name":"EURONAV DANUBE","imo":"9823456","mmsi":"205456000","vesselType":"tanker","shipClass":"Suezmax","flag":"Belgium","yearBuilt":2021,"grossTonnage":82000,"deadweight":157000,"length":274,"beam":48,"draft":17.1,"status":"at_sea","fleetId":3,"currentLat":42.5,"currentLon":-38.7,"currentSpeed":14.2,"currentHeading":85,"lastPort":"Bonny Terminal","nextPort":"Rotterdam","eta":"2026-04-05","fuelConsumptionDaily":58,"co2EmissionsDaily":180.6,"ciiRating":"A","ciiValue":3.89,"attainedCII":3.89,"requiredCII":5.22,"hullCondition":97,"engineHealth":96,"maintenanceScore":95,"tce":34200,"utilization":96,"eexi":4.8,"tradeLane":"WAF — NW Europe","operator":"Euronav NV","classificationSociety":"Bureau Veritas","builder":"Samsung Heavy Industries"},{"id":9,"name":"PACIFIC BASIN TIANJIN","imo":"9767890","mmsi":"477234100","vesselType":"bulk","shipClass":"Ultramax","flag":"Hong Kong","yearBuilt":2020,"grossTonnage":38000,"deadweight":64000,"length":200,"beam":32.26,"draft":13.5,"status":"at_sea","fleetId":2,"currentLat":-5.8,"currentLon":106.8,"currentSpeed":11.8,"currentHeading":165,"lastPort":"Hay Point","nextPort":"Tanjung Priok","eta":"2026-03-30","fuelConsumptionDaily":30,"co2EmissionsDaily":93.4,"ciiRating":"B","ciiValue":5.12,"attainedCII":5.12,"requiredCII":6.02,"hullCondition":90,"engineHealth":92,"maintenanceScore":88,"tce":16800,"utilization":91,"eexi":6.9,"tradeLane":"Australia — SE Asia","operator":"Pacific Basin Shipping","classificationSociety":"ClassNK","builder":"Tsuneishi Shipbuilding"},{"id":10,"name":"TORM HELLAS","imo":"9698765","mmsi":"219019800","vesselType":"tanker","shipClass":"MR2 Product Tanker","flag":"Denmark","yearBuilt":2014,"grossTonnage":30000,"deadweight":49000,"length":183,"beam":32.2,"draft":13.1,"status":"maintenance","fleetId":1,"currentLat":44.43,"currentLon":28.66,"currentSpeed":0,"currentHeading":0,"lastPort":"Constanta","nextPort":"Constanta","eta":"2026-04-15","fuelConsumptionDaily":0,"co2EmissionsDaily":0,"ciiRating":"D","ciiValue":9.82,"attainedCII":9.82,"requiredCII":6.88,"hullCondition":62,"engineHealth":58,"maintenanceScore":55,"tce":0,"utilization":45,"eexi":14.2,"tradeLane":"Drydock — Constanta Shipyard","operator":"TORM A/S","classificationSociety":"Lloyd's Register","builder":"Guangzhou Shipyard"},{"id":11,"name":"MSC AURORA","imo":"9845678","mmsi":"353456700","vesselType":"container","shipClass":"Megamax-24","flag":"Panama","yearBuilt":2024,"grossTonnage":228000,"deadweight":220000,"length":400,"beam":61.5,"draft":17.4,"status":"at_sea","fleetId":2,"currentLat":22.3,"currentLon":114.2,"currentSpeed":21.2,"currentHeading":315,"lastPort":"Yantian","nextPort":"Tanjung Pelepas","eta":"2026-03-31","fuelConsumptionDaily":225,"co2EmissionsDaily":700.6,"ciiRating":"A","ciiValue":3.12,"attainedCII":3.12,"requiredCII":4.85,"hullCondition":99,"engineHealth":99,"maintenanceScore":99,"tce":35800,"utilization":98,"eexi":3.9,"tradeLane":"Far East — Mediterranean","operator":"MSC Mediterranean Shipping","classificationSociety":"RINA","builder":"Hyundai Samho Heavy Industries"},{"id":12,"name":"DANUBIA STAR","imo":"9712345","mmsi":"264700300","vesselType":"cargo","shipClass":"River-Sea Type","flag":"Romania","yearBuilt":2016,"grossTonnage":5800,"deadweight":7200,"length":140,"beam":16.8,"draft":4.5,"status":"at_sea","fleetId":1,"currentLat":45.15,"currentLon":29.68,"currentSpeed":8.2,"currentHeading":120,"lastPort":"Galati","nextPort":"Constanta","eta":"2026-03-28","fuelConsumptionDaily":8,"co2EmissionsDaily":24.9,"ciiRating":"B","ciiValue":5.65,"attainedCII":5.65,"requiredCII":6.88,"hullCondition":78,"engineHealth":80,"maintenanceScore":76,"tce":8500,"utilization":82,"eexi":8.4,"tradeLane":"Danube River — Black Sea","operator":"Navrom SA","classificationSociety":"Romanian Naval Authority","builder":"Santierul Naval Galati"},{"id":13,"name":"STENA POLARIS","imo":"9856789","mmsi":"265789000","vesselType":"tanker","shipClass":"IMOIIMAX","flag":"Sweden","yearBuilt":2022,"grossTonnage":25000,"deadweight":37000,"length":176,"beam":32.2,"draft":12.5,"status":"at_sea","fleetId":1,"currentLat":40.65,"currentLon":27.95,"currentSpeed":11.5,"currentHeading":45,"lastPort":"Tüpraş İzmit","nextPort":"Constanta","eta":"2026-03-29","fuelConsumptionDaily":22,"co2EmissionsDaily":68.5,"ciiRating":"A","ciiValue":4.15,"attainedCII":4.15,"requiredCII":5.95,"hullCondition":97,"engineHealth":96,"maintenanceScore":95,"tce":19800,"utilization":93,"eexi":5.2,"tradeLane":"Marmara — Black Sea Products","operator":"Stena Bulk AB","classificationSociety":"DNV","builder":"Guangzhou Shipyard International"},{"id":14,"name":"CMA CGM BOSPHORUS","imo":"9867890","mmsi":"228345600","vesselType":"container","shipClass":"Panamax","flag":"France","yearBuilt":2018,"grossTonnage":52000,"deadweight":63000,"length":265,"beam":32.2,"draft":13.5,"status":"anchored","fleetId":1,"currentLat":41.18,"currentLon":29.08,"currentSpeed":0,"currentHeading":90,"lastPort":"Piraeus","nextPort":"Constanta","eta":"2026-03-29","fuelConsumptionDaily":0,"co2EmissionsDaily":0,"ciiRating":"B","ciiValue":5.88,"attainedCII":5.88,"requiredCII":6.95,"hullCondition":89,"engineHealth":91,"maintenanceScore":88,"tce":21500,"utilization":88,"eexi":7.2,"tradeLane":"Med — Black Sea Feeder","operator":"CMA CGM Group","classificationSociety":"Bureau Veritas","builder":"Hanjin Heavy Industries"},{"id":15,"name":"VALE BRASIL","imo":"9878901","mmsi":"710567800","vesselType":"bulk","shipClass":"Valemax (VLOC)","flag":"Brazil","yearBuilt":2023,"grossTonnage":200000,"deadweight":400000,"length":362,"beam":65,"draft":23.0,"status":"at_sea","fleetId":3,"currentLat":-2.5,"currentLon":-44.3,"currentSpeed":13.5,"currentHeading":30,"lastPort":"Ponta da Madeira","nextPort":"Qingdao","eta":"2026-04-22","fuelConsumptionDaily":75,"co2EmissionsDaily":233.6,"ciiRating":"A","ciiValue":1.85,"attainedCII":1.85,"requiredCII":2.62,"hullCondition":99,"engineHealth":98,"maintenanceScore":97,"tce":28900,"utilization":95,"eexi":3.2,"tradeLane":"Brazil — China Iron Ore","operator":"Vale SA","classificationSociety":"DNV","builder":"CSSC Dalian Shipbuilding"},{"id":16,"name":"ARCTIC AURORA","imo":"9889012","mmsi":"258900100","vesselType":"tanker","shipClass":"Arc7 LNG Carrier","flag":"Norway","yearBuilt":2024,"grossTonnage":129000,"deadweight":95000,"length":299,"beam":50,"draft":12.0,"status":"at_sea","fleetId":4,"currentLat":68.95,"currentLon":33.05,"currentSpeed":16.8,"currentHeading":270,"lastPort":"Sabetta","nextPort":"Zeebrugge","eta":"2026-04-06","fuelConsumptionDaily":120,"co2EmissionsDaily":280.8,"ciiRating":"A","ciiValue":3.55,"attainedCII":3.55,"requiredCII":4.82,"hullCondition":99,"engineHealth":99,"maintenanceScore":98,"tce":52000,"utilization":97,"eexi":3.8,"tradeLane":"NSR — Yamal LNG","operator":"MOL/Teekay LNG","classificationSociety":"DNV","builder":"Daewoo Shipbuilding"},{"id":17,"name":"EVERGREEN TRIUMPH","imo":"9890123","mmsi":"416234500","vesselType":"container","shipClass":"Neo-Panamax","flag":"Taiwan","yearBuilt":2022,"grossTonnage":120000,"deadweight":140000,"length":334,"beam":51.2,"draft":16.0,"status":"at_sea","fleetId":2,"currentLat":15.4,"currentLon":120.6,"currentSpeed":18.5,"currentHeading":180,"lastPort":"Kaohsiung","nextPort":"Port Klang","eta":"2026-04-03","fuelConsumptionDaily":175,"co2EmissionsDaily":545.0,"ciiRating":"B","ciiValue":5.95,"attainedCII":5.95,"requiredCII":7.12,"hullCondition":97,"engineHealth":96,"maintenanceScore":95,"tce":27400,"utilization":95,"eexi":6.5,"tradeLane":"Intra-Asia","operator":"Evergreen Marine Corp","classificationSociety":"ClassNK","builder":"Samsung Heavy Industries"},{"id":18,"name":"MINERVA SOPHIA","imo":"9801234","mmsi":"240567800","vesselType":"tanker","shipClass":"Suezmax","flag":"Greece","yearBuilt":2019,"grossTonnage":82000,"deadweight":158000,"length":274,"beam":48,"draft":17.1,"status":"at_sea","fleetId":1,"currentLat":37.0,"currentLon":26.5,"currentSpeed":13.4,"currentHeading":30,"lastPort":"Ceyhan","nextPort":"Trieste","eta":"2026-04-01","fuelConsumptionDaily":55,"co2EmissionsDaily":171.3,"ciiRating":"B","ciiValue":5.42,"attainedCII":5.42,"requiredCII":5.88,"hullCondition":88,"engineHealth":90,"maintenanceScore":86,"tce":31800,"utilization":93,"eexi":6.2,"tradeLane":"Black Sea — Med Crude","operator":"Minerva Marine Inc","classificationSociety":"Lloyd's Register","builder":"Hyundai Heavy Industries"}],"forecast-modules":[{"id":1,"title":"Fleet Average TCE","metric":"$/day","currentValue":24850,"forecastValue":27200,"forecastDate":"2026-06-30","confidence":78,"trend":"up","dataPoints":[{"date":"2025-10","value":21800},{"date":"2025-11","value":22400},{"date":"2025-12","value":23100},{"date":"2026-01","value":23600},{"date":"2026-02","value":24200},{"date":"2026-03","value":24850},{"date":"2026-04","value":25500,"forecast":25500},{"date":"2026-05","value":26400,"forecast":26400},{"date":"2026-06","value":27200,"forecast":27200}]},{"id":2,"title":"Fleet CII (gCO2/DWT·nm)","metric":"gCO2/DWT·nm","currentValue":4.85,"forecastValue":4.42,"forecastDate":"2026-06-30","confidence":72,"trend":"down","dataPoints":[{"date":"2025-10","value":5.62},{"date":"2025-11","value":5.41},{"date":"2025-12","value":5.28},{"date":"2026-01","value":5.12},{"date":"2026-02","value":4.98},{"date":"2026-03","value":4.85},{"date":"2026-04","value":4.71,"forecast":4.71},{"date":"2026-05","value":4.55,"forecast":4.55},{"date":"2026-06","value":4.42,"forecast":4.42}]},{"id":3,"title":"Fleet Utilization Rate","metric":"%","currentValue":91.8,"forecastValue":93.5,"forecastDate":"2026-06-30","confidence":85,"trend":"up","dataPoints":[{"date":"2025-10","value":88.5},{"date":"2025-11","value":89.2},{"date":"2025-12","value":89.8},{"date":"2026-01","value":90.4},{"date":"2026-02","value":91.1},{"date":"2026-03","value":91.8},{"date":"2026-04","value":92.2,"forecast":92.2},{"date":"2026-05","value":92.8,"forecast":92.8},{"date":"2026-06","value":93.5,"forecast":93.5}]},{"id":4,"title":"Baltic Dry Index Correlation","metric":"BDI Points","currentValue":1842,"forecastValue":2150,"forecastDate":"2026-06-30","confidence":68,"trend":"up","dataPoints":[{"date":"2025-10","value":1520},{"date":"2025-11","value":1580},{"date":"2025-12","value":1650},{"date":"2026-01","value":1720},{"date":"2026-02","value":1780},{"date":"2026-03","value":1842},{"date":"2026-04","value":1920,"forecast":1920},{"date":"2026-05","value":2040,"forecast":2040},{"date":"2026-06","value":2150,"forecast":2150}]}],"predictive-maintenance":[{"id":1,"vesselId":9,"vesselName":"PACIFIC BASIN TIANJIN","component":"Propeller Shaft Stern Tube Bearing","failureProbability":73,"predictedFailureDate":"2026-05-10","recommendedAction":"Schedule emergency docking for bearing replacement at PT United Shipyard Tanjung Priok","estimatedCost":120000,"confidence":73,"riskLevel":"High"},{"id":2,"vesselId":10,"vesselName":"TORM HELLAS","component":"Main Engine Turbocharger (MAN TCA66)","failureProbability":62,"predictedFailureDate":"2026-06-15","recommendedAction":"Replace turbocharger cartridge during current Constanta Shipyard drydock","estimatedCost":85000,"confidence":68,"riskLevel":"High"},{"id":3,"vesselId":18,"vesselName":"MINERVA SOPHIA","component":"Cargo Pump Mechanical Seal (Framo SD200)","failureProbability":55,"predictedFailureDate":"2026-07-20","recommendedAction":"Order spare seals and plan replacement during next discharge at Trieste","estimatedCost":12000,"confidence":71,"riskLevel":"Medium"},{"id":4,"vesselId":7,"vesselName":"GALATA BRIDGE","component":"Ballast Water UV Reactor (Alfa Laval PureBallast)","failureProbability":48,"predictedFailureDate":"2026-08-01","recommendedAction":"Replace UV lamp assembly during scheduled BWTS maintenance at Constanta","estimatedCost":35000,"confidence":65,"riskLevel":"Medium"},{"id":5,"vesselId":5,"vesselName":"OLDENDORFF BRISBANE","component":"Auxiliary Boiler Tubes (Aalborg AQ-12)","failureProbability":41,"predictedFailureDate":"2026-09-10","recommendedAction":"Conduct ultrasonic thickness measurement during next port stay at Qingdao","estimatedCost":55000,"confidence":60,"riskLevel":"Medium"},{"id":6,"vesselId":1,"vesselName":"CONSTANTA SPIRIT","component":"Steering Gear Hydraulic Pump (Rolls-Royce)","failureProbability":28,"predictedFailureDate":"2026-11-15","recommendedAction":"Replace hydraulic pump seal kit during next scheduled Constanta port call","estimatedCost":8000,"confidence":58,"riskLevel":"Low"}],"compliance-certificates":[{"id":1,"vesselId":10,"vesselName":"TORM HELLAS","certificateType":"Safety Management Certificate (SMC)","issuer":"Lloyd's Register","issuedDate":"2022-06-15","expiryDate":"2026-04-10","status":"Expiring Soon","daysUntilExpiry":15,"regulation":"ISM Code Ch. 13"},{"id":2,"vesselId":4,"vesselName":"NORDIC WOLVERINE","certificateType":"International Oil Pollution Prevention (IOPP)","issuer":"DNV","issuedDate":"2022-09-01","expiryDate":"2027-09-01","status":"Valid","daysUntilExpiry":524,"regulation":"MARPOL Annex I, Reg. 7"},{"id":3,"vesselId":5,"vesselName":"OLDENDORFF BRISBANE","certificateType":"ISM Document of Compliance (DOC)","issuer":"Germanischer Lloyd","issuedDate":"2023-01-15","expiryDate":"2026-05-15","status":"Expiring Soon","daysUntilExpiry":50,"regulation":"ISM Code Ch. 13.2"},{"id":4,"vesselId":3,"vesselName":"COSCO SHANGHAI","certificateType":"SOLAS Safety Equipment Certificate (SEC)","issuer":"China Classification Society","issuedDate":"2023-03-20","expiryDate":"2028-03-20","status":"Valid","daysUntilExpiry":724,"regulation":"SOLAS Ch. I/Reg. 12"},{"id":5,"vesselId":6,"vesselName":"MAERSK EDINBURGH","certificateType":"Energy Efficiency Existing Ship Index (EEXI)","issuer":"DNV","issuedDate":"2023-01-01","expiryDate":"2028-01-01","status":"Valid","daysUntilExpiry":645,"regulation":"MARPOL Annex VI Reg. 23"},{"id":6,"vesselId":18,"vesselName":"MINERVA SOPHIA","certificateType":"International Air Pollution Prevention (IAPP)","issuer":"Lloyd's Register","issuedDate":"2021-03-30","expiryDate":"2026-03-30","status":"Expired","daysUntilExpiry":-4,"regulation":"MARPOL Annex VI Reg. 6"},{"id":7,"vesselId":9,"vesselName":"PACIFIC BASIN TIANJIN","certificateType":"Ballast Water Management (BWM)","issuer":"ClassNK","issuedDate":"2022-08-15","expiryDate":"2027-08-15","status":"Valid","daysUntilExpiry":507,"regulation":"BWM Convention Art. 9"},{"id":8,"vesselId":16,"vesselName":"ARCTIC AURORA","certificateType":"Polar Ship Certificate (PSC)","issuer":"DNV","issuedDate":"2024-01-10","expiryDate":"2029-01-10","status":"Valid","daysUntilExpiry":1021,"regulation":"SOLAS Ch. XIV / Polar Code"},{"id":9,"vesselId":8,"vesselName":"EURONAV DANUBE","certificateType":"International Sewage Pollution Prevention (ISPP)","issuer":"Bureau Veritas","issuedDate":"2023-05-10","expiryDate":"2028-05-10","status":"Valid","daysUntilExpiry":775,"regulation":"MARPOL Annex IV Reg. 5"},{"id":10,"vesselId":11,"vesselName":"MSC AURORA","certificateType":"Energy Efficiency Management Plan (SEEMP Part III)","issuer":"RINA","issuedDate":"2024-03-01","expiryDate":"2029-03-01","status":"Valid","daysUntilExpiry":1070,"regulation":"MARPOL Annex VI Reg. 26"}],"port-state-deficiencies":[{"id":1,"vesselId":10,"vesselName":"TORM HELLAS","port":"Constanta","inspectionDate":"2026-03-01","deficiencyCode":"01302","description":"Fire detection system — smoke detector in engine room No. 2 zone inoperative. Non-compliance with SOLAS Ch. II-2/Reg. 7.2.2.","severity":"High","status":"Open","rectifiedDate":null,"mouRegime":"Paris MOU"},{"id":2,"vesselId":4,"vesselName":"NORDIC WOLVERINE","port":"Fujairah","inspectionDate":"2026-02-15","deficiencyCode":"07410","description":"MARPOL Annex I — oil water separator discharge exceeding 15 ppm limit. Sample analysis showed 22 ppm. OWS alarm not functioning.","severity":"High","status":"Rectified","rectifiedDate":"2026-02-20","mouRegime":"Riyadh MOU"},{"id":3,"vesselId":5,"vesselName":"OLDENDORFF BRISBANE","port":"Qingdao","inspectionDate":"2026-03-10","deficiencyCode":"06110","description":"Lifesaving appliances — lifeboat on-load release mechanism requires servicing per MSC.1/Circ.1206/Rev.2. Annual maintenance record incomplete.","severity":"Medium","status":"Open","rectifiedDate":null,"mouRegime":"Tokyo MOU"},{"id":4,"vesselId":18,"vesselName":"MINERVA SOPHIA","port":"Trieste","inspectionDate":"2026-01-20","deficiencyCode":"09510","description":"MLC 2006 — crew rest hour records incomplete for Chief Officer and 2nd Engineer. Work/rest analysis shows potential non-compliance with Standard A2.3.","severity":"Low","status":"Rectified","rectifiedDate":"2026-01-25","mouRegime":"Paris MOU"},{"id":5,"vesselId":9,"vesselName":"PACIFIC BASIN TIANJIN","port":"Tanjung Priok","inspectionDate":"2026-03-05","deficiencyCode":"04315","description":"Navigation equipment — ECDIS backup arrangement not compliant with SOLAS Ch. V/Reg. 19.2.1.4. Backup ECDIS showing chart datum error.","severity":"Medium","status":"Open","rectifiedDate":null,"mouRegime":"Tokyo MOU"},{"id":6,"vesselId":12,"vesselName":"DANUBIA STAR","port":"Constanta","inspectionDate":"2026-02-28","deficiencyCode":"01520","description":"Structural safety — wastage found on main deck plating exceeding 20% of original thickness. Class condition noted by Romanian Naval Authority.","severity":"High","status":"Open","rectifiedDate":null,"mouRegime":"Paris MOU"}],"ai-briefings":[{"id":1,"title":"Black Sea Fleet CII Performance Analysis","category":"Performance","severity":"Warning","summary":"Black Sea Division fleet-wide CII trending 8% above required thresholds. TORM HELLAS rated D and OLDENDORFF BRISBANE at C boundary — both at risk of operational restrictions under IMO MEPC.352(78) 2026 tightening.","details":"9-month emission data analysis per MARPOL Annex VI Regulation 28 shows attained CII deterioration across 4 vessels. Primary contributors: TORM HELLAS (attained 9.82 vs required 6.88) and OLDENDORFF BRISBANE (4.52 vs 4.10). Hull fouling penalty estimated at 12-15% fuel increase. Constanta Shipyard drydock capacity available Q2 2026. Wind-assisted propulsion (Norsepower Rotor Sail) retrofit estimated ROI: 18 months.","confidence":91,"generatedAt":"2026-03-26T14:00:00Z","actionItems":["Submit CII Corrective Action Plan to flag state per MEPC.355(78) by June 2026","Schedule hull cleaning for TORM HELLAS at Constanta Shipyard during current drydock","Implement JIT arrival optimization for Bosporus transit to reduce idle time emissions","Evaluate Norsepower Rotor Sail retrofit for OLDENDORFF BRISBANE (est. 8% fuel saving)"],"affectedVessels":["TORM HELLAS","OLDENDORFF BRISBANE","MINERVA SOPHIA"]},{"id":2,"title":"Bosporus Strait Congestion — Route Optimization Required","category":"Operations","severity":"Warning","summary":"Turkish Straits VTS reporting 72-hour northbound transit delays. 23 vessels queued at southern anchorage. CMA CGM BOSPHORUS anchored since March 26. Revenue loss estimated at $45,000/day for detained fleet.","details":"Kıyı Emniyeti (Turkish Coastal Safety) data shows abnormal congestion due to combined factors: (1) Russian grain export surge, (2) seasonal LPG traffic increase from Ceyhan, (3) maintenance at İstanbul Strait radar station. Historical analysis suggests congestion will persist 5-7 days. Alternative routing via Suez Canal adds 4 days but avoids $180K demurrage exposure. JIT arrival coordination with Constanta Port Authority recommended.","confidence":87,"generatedAt":"2026-03-26T13:00:00Z","actionItems":["Coordinate JIT arrival with Constanta Port Authority (APMC) for STENA POLARIS","Request priority transit slot for CMA CGM BOSPHORUS via agent (Marline Agency Istanbul)","Evaluate Suez Canal alternative for non-time-critical Black Sea cargoes","Brief commercial team on potential 5-7 day delay impact on Q1 performance"],"affectedVessels":["CMA CGM BOSPHORUS","STENA POLARIS","CONSTANTA SPIRIT"]},{"id":3,"title":"EU ETS Maritime Compliance — Q1 Allowance Deadline","category":"Compliance","severity":"Critical","summary":"EU ETS maritime phase-in requires 40% of 2026 emissions covered by allowances. Submission deadline April 30. 8 vessels in fleet have EU port calls requiring reporting under Regulation 2023/959.","details":"Under EU ETS Directive 2003/87/EC as amended by 2023/959, maritime emissions for voyages to/from EU ports must be covered. Fleet exposure: estimated 42,000 EU Allowances (EUAs) at current price €85/EUA = €3.57M obligation. Vessels affected: MAERSK EDINBURGH, EURONAV DANUBE, MINERVA SOPHIA, CMA CGM BOSPHORUS, MSC AURORA, TORM HELLAS, CONSTANTA SPIRIT, DANUBIA STAR. MRV data from Thetis-MRV must reconcile with ETS reporting.","confidence":95,"generatedAt":"2026-03-26T12:00:00Z","actionItems":["Verify EU MRV emissions data in Thetis-MRV for all 8 vessels with EU port calls","Purchase 42,000 EUAs before April 30 deadline at current spot price","Implement FuelEU Maritime preparedness for 2025 well-to-wake intensity targets","Update voyage P&L models to include EU ETS cost per voyage for commercial team"],"affectedVessels":["MAERSK EDINBURGH","EURONAV DANUBE","MINERVA SOPHIA","CMA CGM BOSPHORUS","MSC AURORA","TORM HELLAS","CONSTANTA SPIRIT","DANUBIA STAR"]},{"id":4,"title":"Predictive Maintenance Alert — PACIFIC BASIN TIANJIN Stern Tube","category":"Maintenance","severity":"Warning","summary":"ML vibration analysis predicts 73% probability of stern tube bearing failure within 45 days. ClassNK Rule Part C 5.2 requires docking survey. Tanjung Priok offers lowest-cost repair window.","details":"SKF Copperhead vibration monitoring trend: propeller shaft 2x RPM harmonic increasing at 0.3 mm/s per week (current: 4.2 mm/s, ClassNK limit: 4.5 mm/s). Pattern matches pre-failure signature from fleet database (87% match to PACIFIC BASIN WUHAN failure 2024). Stern tube oil consumption: 1.2L/day (normal: 0.3L/day). PT United Shipyard Tanjung Priok quoted $95K for emergency docking incl. bearing replacement. Lead time for Wartsila lip seal assembly: 14 days.","confidence":73,"generatedAt":"2026-03-26T11:00:00Z","actionItems":["Schedule emergency docking at PT United Shipyard Tanjung Priok (earliest slot: April 8)","Order replacement Wartsila lip seal assembly — DDP Jakarta (14-day lead)","Increase vibration monitoring to 4-hour intervals","Prepare ClassNK surveyor request for tail shaft survey per Rule Part C 5.2"],"affectedVessels":["PACIFIC BASIN TIANJIN"]}]}
|
|
@@ -391,6 +391,10 @@ details.raw{margin-top:1rem;} details.raw summary{cursor:pointer;font-family:var
|
|
| 391 |
.verify-badge .dot{width:10px;height:10px;border-radius:50%;background:currentColor;box-shadow:0 0 8px currentColor;}
|
| 392 |
/* ===== GENIUS VISUALS (inherited from a11oy command platform) ===== */
|
| 393 |
.graph3d.hero{height:520px;}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
.echart{height:360px;width:100%;}
|
| 395 |
.echart.tall{height:480px;}
|
| 396 |
.globe3d{height:520px;width:100%;border-radius:9px;overflow:hidden;border:1px solid var(--gold-line);background:#060606;}
|
|
@@ -438,6 +442,11 @@ details.raw{margin-top:1rem;} details.raw summary{cursor:pointer;font-family:var
|
|
| 438 |
<div class="nav-item" data-view="maritime" onclick="go('maritime')"><span class="ico">⚓</span>Maritime Picture</div>
|
| 439 |
<div class="nav-item" data-view="sanctions" onclick="go('sanctions')"><span class="ico">◴</span>Sanctions & Dark-Vessel</div>
|
| 440 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 441 |
<div class="nav-group">Decide & Govern</div>
|
| 442 |
<div class="nav-item" data-view="roe" onclick="go('roe')"><span class="ico">⊞</span>Engagement Rules</div>
|
| 443 |
<div class="nav-item" data-view="lambda" onclick="go('lambda')"><span class="ico">◈</span>Trust Score Monitor</div>
|
|
@@ -481,6 +490,7 @@ details.raw{margin-top:1rem;} details.raw summary{cursor:pointer;font-family:var
|
|
| 481 |
<script>
|
| 482 |
const BASE = 'https://szlholdings-killinchu.hf.space';
|
| 483 |
const API = BASE + '/api/killinchu/v1';
|
|
|
|
| 484 |
|
| 485 |
// Keep ONE persistent WebGL context alive for the whole session before any 3D view
|
| 486 |
// initialises. On software-GL (SwiftShader) hosts the GL-context pool is tiny and
|
|
@@ -531,6 +541,14 @@ function barH(id,labels,data,colors){mkChart(id,{type:'bar',data:{labels,dataset
|
|
| 531 |
function barV(id,labels,data,colors){mkChart(id,{type:'bar',data:{labels,datasets:[{data,backgroundColor:colors||TEAL,borderRadius:4}]},options:{scales:{x:{grid:{display:false},ticks:{color:'#9a9a9a',font:{size:9}}},y:{grid:{color:GRID},ticks:{color:DIM}}},plugins:{legend:{display:false}},responsive:true,maintainAspectRatio:false}});}
|
| 532 |
function lineSpark(id,labels,data,color){mkChart(id,{type:'line',data:{labels,datasets:[{data,borderColor:color||TEAL,backgroundColor:'rgba(95,179,163,0.12)',fill:true,tension:.35,pointRadius:0,borderWidth:1.6}]},options:{scales:{x:{display:false},y:{display:false}},plugins:{legend:{display:false},tooltip:{enabled:false}},responsive:true,maintainAspectRatio:false}});}
|
| 533 |
function doughnut(id,labels,data,colors){mkChart(id,{type:'doughnut',data:{labels,datasets:[{data,backgroundColor:colors,borderColor:'#0a0a0a',borderWidth:2}]},options:{cutout:'62%',plugins:{legend:{display:false},tooltip:{enabled:true}},responsive:true,maintainAspectRatio:false}});}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
let _fg=null;
|
| 535 |
function mesh3d(id,nodes,links,_try){const host=el(id);if(!host||!window.ForceGraph3D)return;host.innerHTML='';try{_fg=ForceGraph3D()(host).backgroundColor('rgba(0,0,0,0)').width(host.clientWidth).height(host.clientHeight).graphData({nodes,links}).nodeLabel('name').nodeColor(n=>n.color||TEAL).nodeVal(n=>n.val||4).linkColor(()=>'rgba(201,183,135,0.45)').linkWidth(1.2).linkDirectionalParticles(2).linkDirectionalParticleSpeed(0.006).linkDirectionalParticleColor(()=>TEAL).showNavInfo(false);setTimeout(()=>{try{_fg.width(host.clientWidth).height(host.clientHeight);}catch(e){}
|
| 536 |
/* GL pool can be momentarily exhausted on software-GL; if no WebGL canvas appeared, re-init once. */
|
|
@@ -758,6 +776,43 @@ async function vessel_alert_verify(tamper){
|
|
| 758 |
}catch(e){ setVAlertBadge('fail','ERROR'); if(el('valert-detail')) el('valert-detail').textContent='retry: '+e.message; }
|
| 759 |
}
|
| 760 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
|
| 762 |
// ===================== VIEWS =====================
|
| 763 |
const VIEWS = {
|
|
@@ -868,6 +923,10 @@ const VIEWS = {
|
|
| 868 |
<div class="btns"><button class="btn teal" onclick="vessel_owner()">▶ Trace ownership</button></div>
|
| 869 |
<div id="own-host"><div class="graph3d" id="owner-3d" style="height:320px"></div></div>
|
| 870 |
<div class="legend"><span>Ownership chain: registered operator → shell companies → ultimate owner. Sample graph for the demo data set.</span></div></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 871 |
<details class="raw"><summary>raw AIS replay sample set</summary><pre class="out" id="maritime-raw">—</pre></details>
|
| 872 |
<div class="honesty"><b>Honest by design.</b> This maritime picture is an <b>AIS replay — a sample track set, not a live feed</b> (no AIS provider is wired). Sanctions and ownership are screened against a small <b>sample list</b> in OFAC/UN/EU format, not full real-time coverage. Vessel-alert receipts are <b>genuinely signed</b> with killinchu's real key and verifiable offline — the same signed-receipt thesis as the drone side.</div>`;
|
| 873 |
setOut('maritime-raw', V);
|
|
@@ -919,6 +978,160 @@ const VIEWS = {
|
|
| 919 |
el('sanc-pick').addEventListener('change',()=>{const v=V.find(x=>x.id===el('sanc-pick').value); if(v) sanctions_drawGap(v);});
|
| 920 |
}},
|
| 921 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 922 |
// ── 3.4 ROE Policy Editor ────────────────────────────────────────
|
| 923 |
roe:{title:'Engagement Rules',badge:'LIVE POLICY',sub:'The rules that decide whether a drone may be engaged — speed limits, required ID, allowed directions. Test a track against the current rules; every check produces a genuinely signed receipt.',
|
| 924 |
render:async(c)=>{
|
|
@@ -1052,23 +1265,49 @@ const VIEWS = {
|
|
| 1052 |
<div class="btns"><button class="btn teal" onclick="bft_exec()">▶ Execute demo mission</button></div>
|
| 1053 |
<div id="bft-exec-summary" class="mono dim" style="font-size:12px;margin-bottom:.5rem">— click to execute —</div>
|
| 1054 |
<details class="raw"><summary>raw /uds/v1/mission/execute</summary><pre class="out" id="bft-exec-out">—</pre></details></div>
|
|
|
|
| 1055 |
<details class="raw"><summary>raw /uds/v1/healthz</summary><pre class="out" id="bft-raw">—</pre></details>
|
| 1056 |
${HONEST}`;
|
| 1057 |
try{
|
| 1058 |
const d = await getJSON(BASE+'/api/killinchu/uds/v1/healthz');
|
| 1059 |
setOut('bft-raw',d);
|
| 1060 |
-
|
| 1061 |
-
el('k-quorum').className = 'v '+(d.quorum_possible?'live':'warn');
|
| 1062 |
const organs=Object.entries(d.organs||{});
|
| 1063 |
let online=0; organs.forEach(([,o])=>{if(o.status==='ok')online++;});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1064 |
if(el('bft-count')) el('bft-count').textContent=online;
|
| 1065 |
-
doughnut('bft-donut',['online','unreachable'],[online,Math.max(0,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1066 |
const h = el('bft-organs'); h.innerHTML='';
|
| 1067 |
organs.forEach(([name,o])=>{
|
| 1068 |
const ok = o.status==='ok';
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1069 |
h.insertAdjacentHTML('beforeend',`<div class="row">
|
| 1070 |
-
<span class="badge ${
|
| 1071 |
-
<span>${esc(name)}</span>
|
| 1072 |
<span class="spacer mono dim">${o.local?'local':''} ${o.http?'HTTP '+o.http:''} ${o.latency_ms?Math.round(o.latency_ms)+'ms':''}</span>
|
| 1073 |
</div>`);
|
| 1074 |
});
|
|
@@ -1328,7 +1567,7 @@ cosign verify-blob --key cosign.pub --signature sig.b64 payload.bin</pre></div>
|
|
| 1328 |
<div class="kpi"><div class="k">Organs reachable</div><div class="v live" id="org-reach">probing…</div></div>
|
| 1329 |
<div class="kpi"><div class="k">Links to brain</div><div class="v teal" id="org-links">—</div></div>
|
| 1330 |
<div class="kpi"><div class="k">Signed spans</div><div class="v" id="org-spans">—</div><div class="d">live audit depth</div></div></div>
|
| 1331 |
-
<div class="card"><div class="card-h"><span class="card-t">The brain at the center — live 3D</span><span class="card-ep">drag to orbit · particles = live links</span></div><div class="graph3d hero" id="org-3d"></div><div class="brain-note">a11oy is the orchestrator brain: every edge terminates at the gold core. killinchu is the field/Navy surface sharing that brain. Health from the live observability probe.</div></div>
|
| 1332 |
<div class="card"><div class="card-h"><span class="card-t">Organ health detail</span><span class="card-ep">live probe</span></div><div id="org-host"><div class="row mono dim">probing…</div></div>
|
| 1333 |
<details class="raw"><summary>raw /observability/summary</summary><pre class="out" id="org-raw">loading…</pre></details></div>${HONEST}`;window.organism_load();}},
|
| 1334 |
|
|
|
|
| 391 |
.verify-badge .dot{width:10px;height:10px;border-radius:50%;background:currentColor;box-shadow:0 0 8px currentColor;}
|
| 392 |
/* ===== GENIUS VISUALS (inherited from a11oy command platform) ===== */
|
| 393 |
.graph3d.hero{height:520px;}
|
| 394 |
+
.org-loading{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;}
|
| 395 |
+
.graph3d{position:relative;}
|
| 396 |
+
.org-pulse{width:54px;height:54px;border-radius:50%;background:radial-gradient(circle,var(--gold,#c9b787) 0%,rgba(201,183,135,0.15) 60%,transparent 72%);box-shadow:0 0 0 0 rgba(201,183,135,0.45);animation:orgPulse 1.6s ease-out infinite;}
|
| 397 |
+
@keyframes orgPulse{0%{transform:scale(.85);box-shadow:0 0 0 0 rgba(201,183,135,0.45);}70%{transform:scale(1.05);box-shadow:0 0 0 22px rgba(201,183,135,0);}100%{transform:scale(.85);box-shadow:0 0 0 0 rgba(201,183,135,0);}}
|
| 398 |
.echart{height:360px;width:100%;}
|
| 399 |
.echart.tall{height:480px;}
|
| 400 |
.globe3d{height:520px;width:100%;border-radius:9px;overflow:hidden;border:1px solid var(--gold-line);background:#060606;}
|
|
|
|
| 442 |
<div class="nav-item" data-view="maritime" onclick="go('maritime')"><span class="ico">⚓</span>Maritime Picture</div>
|
| 443 |
<div class="nav-item" data-view="sanctions" onclick="go('sanctions')"><span class="ico">◴</span>Sanctions & Dark-Vessel</div>
|
| 444 |
|
| 445 |
+
<div class="nav-group">Fleet (Vessels)</div>
|
| 446 |
+
<div class="nav-item" data-view="fleet" onclick="go('fleet')"><span class="ico">⛴</span>Fleet Overview</div>
|
| 447 |
+
<div class="nav-item" data-view="fleetmaint" onclick="go('fleetmaint')"><span class="ico">⚒</span>Maintenance & Compliance</div>
|
| 448 |
+
<div class="nav-item" data-view="fleetbrief" onclick="go('fleetbrief')"><span class="ico">◈</span>Fleet Briefings</div>
|
| 449 |
+
|
| 450 |
<div class="nav-group">Decide & Govern</div>
|
| 451 |
<div class="nav-item" data-view="roe" onclick="go('roe')"><span class="ico">⊞</span>Engagement Rules</div>
|
| 452 |
<div class="nav-item" data-view="lambda" onclick="go('lambda')"><span class="ico">◈</span>Trust Score Monitor</div>
|
|
|
|
| 490 |
<script>
|
| 491 |
const BASE = 'https://szlholdings-killinchu.hf.space';
|
| 492 |
const API = BASE + '/api/killinchu/v1';
|
| 493 |
+
const FLEET = BASE + '/api/killinchu/v1/fleet'; // GAP-1/GAP-2: commercial-fleet (vessels) surface, served verbatim from platform seed-data.
|
| 494 |
|
| 495 |
// Keep ONE persistent WebGL context alive for the whole session before any 3D view
|
| 496 |
// initialises. On software-GL (SwiftShader) hosts the GL-context pool is tiny and
|
|
|
|
| 541 |
function barV(id,labels,data,colors){mkChart(id,{type:'bar',data:{labels,datasets:[{data,backgroundColor:colors||TEAL,borderRadius:4}]},options:{scales:{x:{grid:{display:false},ticks:{color:'#9a9a9a',font:{size:9}}},y:{grid:{color:GRID},ticks:{color:DIM}}},plugins:{legend:{display:false}},responsive:true,maintainAspectRatio:false}});}
|
| 542 |
function lineSpark(id,labels,data,color){mkChart(id,{type:'line',data:{labels,datasets:[{data,borderColor:color||TEAL,backgroundColor:'rgba(95,179,163,0.12)',fill:true,tension:.35,pointRadius:0,borderWidth:1.6}]},options:{scales:{x:{display:false},y:{display:false}},plugins:{legend:{display:false},tooltip:{enabled:false}},responsive:true,maintainAspectRatio:false}});}
|
| 543 |
function doughnut(id,labels,data,colors){mkChart(id,{type:'doughnut',data:{labels,datasets:[{data,backgroundColor:colors,borderColor:'#0a0a0a',borderWidth:2}]},options:{cutout:'62%',plugins:{legend:{display:false},tooltip:{enabled:true}},responsive:true,maintainAspectRatio:false}});}
|
| 544 |
+
// Forecast line: historical (solid teal) + forecast tail (dashed gold). `actual` and
|
| 545 |
+
// `forecast` are arrays the same length as `labels`; null entries break the line so the
|
| 546 |
+
// two segments meet at the boundary point. Used by Fleet Overview's 4 forecast modules.
|
| 547 |
+
function lineForecast(id,labels,actual,forecast){mkChart(id,{type:'line',data:{labels,datasets:[
|
| 548 |
+
{label:'history',data:actual,borderColor:TEAL,backgroundColor:'rgba(95,179,163,0.10)',fill:true,tension:.3,pointRadius:1.5,borderWidth:1.8,spanGaps:false},
|
| 549 |
+
{label:'forecast',data:forecast,borderColor:GOLD,backgroundColor:'rgba(201,183,135,0.08)',fill:false,tension:.3,pointRadius:2,borderWidth:1.8,borderDash:[5,4],spanGaps:false}]},
|
| 550 |
+
options:{scales:{x:{grid:{color:GRID},ticks:{color:DIM,font:{size:9},maxRotation:0,autoSkip:true,maxTicksLimit:6}},y:{grid:{color:GRID},ticks:{color:DIM,font:{size:9}}}},
|
| 551 |
+
plugins:{legend:{display:true,labels:{color:'#9a9a9a',boxWidth:18,font:{size:9}}},tooltip:{enabled:true}},responsive:true,maintainAspectRatio:false}});}
|
| 552 |
let _fg=null;
|
| 553 |
function mesh3d(id,nodes,links,_try){const host=el(id);if(!host||!window.ForceGraph3D)return;host.innerHTML='';try{_fg=ForceGraph3D()(host).backgroundColor('rgba(0,0,0,0)').width(host.clientWidth).height(host.clientHeight).graphData({nodes,links}).nodeLabel('name').nodeColor(n=>n.color||TEAL).nodeVal(n=>n.val||4).linkColor(()=>'rgba(201,183,135,0.45)').linkWidth(1.2).linkDirectionalParticles(2).linkDirectionalParticleSpeed(0.006).linkDirectionalParticleColor(()=>TEAL).showNavInfo(false);setTimeout(()=>{try{_fg.width(host.clientWidth).height(host.clientHeight);}catch(e){}
|
| 554 |
/* GL pool can be momentarily exhausted on software-GL; if no WebGL canvas appeared, re-init once. */
|
|
|
|
| 776 |
}catch(e){ setVAlertBadge('fail','ERROR'); if(el('valert-detail')) el('valert-detail').textContent='retry: '+e.message; }
|
| 777 |
}
|
| 778 |
|
| 779 |
+
// GAP-2: Voyage Risk Exchange governed-decision loop (signals -> forecast -> evidence
|
| 780 |
+
// -> advisory recommendation w/ rollback -> brief). Ported verbatim from the platform
|
| 781 |
+
// vessels vertical; served by the killinchu server at /fleet/voyage-risk. Trust gate is
|
| 782 |
+
// shown as ADVISORY (Conjecture 1), never a pass/fail oracle.
|
| 783 |
+
async function voyage_risk_run(){
|
| 784 |
+
const host=el('vr-host'); if(!host)return;
|
| 785 |
+
host.innerHTML='<span class="mono dim">running the governed-decision loop…</span>';
|
| 786 |
+
try{
|
| 787 |
+
const d=await getJSON(FLEET+'/voyage-risk');
|
| 788 |
+
setOut('vr-raw',d);
|
| 789 |
+
const sig=(d.signals||[]).map(s=>`<div class="row"><span class="badge b-warn">${esc(s.kind)}</span><span><b>${esc(s.summary)}</b></span><span class="spacer mono dim" style="font-size:10px">${esc(s.source)} · weight ${esc(s.weight)}</span></div>`).join('');
|
| 790 |
+
const fc=d.forecast||{}, rec=d.recommendation||{}, br=d.brief||{};
|
| 791 |
+
const conf=Math.round((rec.confidence||fc.confidence||0)*100);
|
| 792 |
+
host.innerHTML=`
|
| 793 |
+
<div class="kpis" style="margin:.2rem 0 .5rem">
|
| 794 |
+
<div class="kpi"><div class="k">Delay risk</div><div class="v warn">${esc(fc.delay_risk||'—')}</div></div>
|
| 795 |
+
<div class="kpi"><div class="k">Route risk</div><div class="v">${esc(fc.route_risk||'—')}</div></div>
|
| 796 |
+
<div class="kpi"><div class="k">Confidence</div><div class="v teal">${conf}%</div><div class="d">advisory</div></div>
|
| 797 |
+
<div class="kpi"><div class="k">Trust gate</div><div class="v teal">Conjecture</div><div class="d">advisory, not proven</div></div>
|
| 798 |
+
</div>
|
| 799 |
+
<div style="margin:.3rem 0"><b style="color:#c9b787">1 · Signals</b></div>${sig}
|
| 800 |
+
<div style="margin:.5rem 0 .2rem"><b style="color:#c9b787">2 · Forecast</b> <span class="mono dim" style="font-size:10px">${esc(fc.method||'')}</span></div>
|
| 801 |
+
<div style="margin:.4rem 0 .2rem"><b style="color:#c9b787">3 · Evidence</b> <span class="mono dim">${(d.evidence||[]).length} item(s) linked to signals</span></div>
|
| 802 |
+
<div class="card" style="margin:.4rem 0;border-color:rgba(201,183,135,.35)">
|
| 803 |
+
<div class="card-h"><span class="card-t">4 · Recommendation (advisory)</span><span class="badge b-warn">REQUIRES HUMAN APPROVAL</span></div>
|
| 804 |
+
<div style="line-height:1.7"><b>${esc(rec.title||'')}</b></div>
|
| 805 |
+
<div class="mono dim" style="font-size:11px;line-height:1.7;margin-top:.3rem">
|
| 806 |
+
Next action: ${esc(rec.next_action||'')}<br>
|
| 807 |
+
<span style="color:#c9a05f">Rollback path:</span> ${esc(rec.rollback_path||'')}<br>
|
| 808 |
+
Evidence: ${esc((rec.evidence_ids||[]).join(', '))}<br>
|
| 809 |
+
Owner: ${esc(rec.owner||'')} · input/output class: ${esc(rec.input_class||'')} → ${esc(rec.output_class||'')}
|
| 810 |
+
</div></div>
|
| 811 |
+
<div style="margin:.3rem 0"><b style="color:#c9b787">5 · Brief</b> <span class="mono dim">${esc(br.headline||'')}</span></div>
|
| 812 |
+
<div class="legend"><span>${esc(d.honesty||'Advisory recommendation — the trust score is a conjecture, not a proven oracle.')}</span></div>`;
|
| 813 |
+
}catch(e){host.innerHTML='<span class="mono dim">retry: '+esc(e.message)+'</span>';setOut('vr-raw','retry: '+e.message);}
|
| 814 |
+
}
|
| 815 |
+
|
| 816 |
|
| 817 |
// ===================== VIEWS =====================
|
| 818 |
const VIEWS = {
|
|
|
|
| 923 |
<div class="btns"><button class="btn teal" onclick="vessel_owner()">▶ Trace ownership</button></div>
|
| 924 |
<div id="own-host"><div class="graph3d" id="owner-3d" style="height:320px"></div></div>
|
| 925 |
<div class="legend"><span>Ownership chain: registered operator → shell companies → ultimate owner. Sample graph for the demo data set.</span></div></div>
|
| 926 |
+
<div class="card"><div class="card-h"><span class="card-t">Voyage Risk Exchange — governed decision</span><span class="card-ep">signals → forecast → evidence → recommendation</span></div>
|
| 927 |
+
<div class="btns"><button class="btn teal" onclick="voyage_risk_run()">▶ Run the voyage-risk decision loop</button></div>
|
| 928 |
+
<div id="vr-host" class="mono dim" style="font-size:12px;line-height:1.7">A governed-decision loop, ported from the platform vessels vertical: it collects voyage signals, forecasts risk, attaches evidence, then issues an <b>advisory</b> recommendation with a rollback path and a genuinely signed receipt. The trust gate is a documented conjecture, not a pass/fail oracle.</div>
|
| 929 |
+
<details class="raw"><summary>raw /fleet/voyage-risk</summary><pre class="out" id="vr-raw">—</pre></details></div>
|
| 930 |
<details class="raw"><summary>raw AIS replay sample set</summary><pre class="out" id="maritime-raw">—</pre></details>
|
| 931 |
<div class="honesty"><b>Honest by design.</b> This maritime picture is an <b>AIS replay — a sample track set, not a live feed</b> (no AIS provider is wired). Sanctions and ownership are screened against a small <b>sample list</b> in OFAC/UN/EU format, not full real-time coverage. Vessel-alert receipts are <b>genuinely signed</b> with killinchu's real key and verifiable offline — the same signed-receipt thesis as the drone side.</div>`;
|
| 932 |
setOut('maritime-raw', V);
|
|
|
|
| 978 |
el('sanc-pick').addEventListener('change',()=>{const v=V.find(x=>x.id===el('sanc-pick').value); if(v) sanctions_drawGap(v);});
|
| 979 |
}},
|
| 980 |
|
| 981 |
+
// ══════════════════ FLEET (Vessels) — GAP-1 commercial-fleet surface ══════════════════
|
| 982 |
+
// Real platform seed-data/vessels/* served verbatim from the killinchu server.
|
| 983 |
+
// MANDATORY honesty label on every fleet tab. No mock data.
|
| 984 |
+
// ── Fleet Overview ──────────────────────────────────────────────
|
| 985 |
+
fleet:{title:'Fleet Overview',badge:'18 VESSELS · SAMPLE DATASET',sub:'Commercial fleet at a glance — each ship with its carbon-intensity (CII) rating, hull and engine health, time-charter earnings (TCE), utilisation and EEXI, plus four forecast lines (earnings, carbon intensity, utilisation, Baltic Dry Index). Sample fleet dataset — not a live AIS / class-society feed.',
|
| 986 |
+
render:async(c)=>{
|
| 987 |
+
c.innerHTML=`<div class="kpis">
|
| 988 |
+
<div class="kpi"><div class="k">Vessels</div><div class="v" id="f-count">—</div><div class="d">in sample fleet</div></div>
|
| 989 |
+
<div class="kpi"><div class="k">Avg hull health</div><div class="v teal" id="f-hull">—</div><div class="d">condition index</div></div>
|
| 990 |
+
<div class="kpi"><div class="k">Poor CII (D/E)</div><div class="v warn" id="f-cii">—</div><div class="d">carbon-intensity risk</div></div>
|
| 991 |
+
<div class="kpi"><div class="k">Receipts</div><div class="v teal">Signed</div><div class="d">verify offline</div></div>
|
| 992 |
+
</div>
|
| 993 |
+
<div class="card"><div class="card-h"><span class="card-t">Forecast modules</span><span class="card-ep">history (teal) + forecast (dashed gold)</span></div>
|
| 994 |
+
<div class="grid2" id="f-fc-grid">
|
| 995 |
+
<div><div class="mono dim" id="f-fc-t0" style="font-size:11px;margin-bottom:.2rem">—</div><div class="chartbox"><canvas id="f-fc-0"></canvas></div></div>
|
| 996 |
+
<div><div class="mono dim" id="f-fc-t1" style="font-size:11px;margin-bottom:.2rem">—</div><div class="chartbox"><canvas id="f-fc-1"></canvas></div></div>
|
| 997 |
+
<div><div class="mono dim" id="f-fc-t2" style="font-size:11px;margin-bottom:.2rem">—</div><div class="chartbox"><canvas id="f-fc-2"></canvas></div></div>
|
| 998 |
+
<div><div class="mono dim" id="f-fc-t3" style="font-size:11px;margin-bottom:.2rem">—</div><div class="chartbox"><canvas id="f-fc-3"></canvas></div></div>
|
| 999 |
+
</div>
|
| 1000 |
+
<div class="legend"><span>Each module carries a forecast point and a confidence %. Forecast values are model estimates on sample data, not guarantees.</span></div></div>
|
| 1001 |
+
<div class="card"><div class="card-h"><span class="card-t">CII rating mix</span><span class="card-ep">A (best) → D (worst)</span></div>
|
| 1002 |
+
<div class="chartbox"><canvas id="f-cii-bar"></canvas></div></div>
|
| 1003 |
+
<div class="card"><div class="card-h"><span class="card-t">Fleet register</span><span class="card-ep" id="f-reg-ep">—</span></div><div id="f-list"><div class="row mono dim">loading…</div></div></div>
|
| 1004 |
+
<details class="raw"><summary>raw /fleet/vessels + /fleet/forecast-modules</summary><pre class="out" id="f-raw">—</pre></details>
|
| 1005 |
+
<div class="honesty"><b>Honest by design.</b> <b>Sample fleet dataset — not a live AIS/class-society feed.</b> These 18 vessels and the four forecast modules are real, citable demo content served verbatim from the SZL platform dataset (seed-data/vessels/*), not a real-time provider. The trust score is a documented <b>conjecture</b>, not a proven guarantee; receipts are <b>genuinely signed</b> (ECDSA P-256) and verifiable offline.</div>`;
|
| 1006 |
+
try{
|
| 1007 |
+
const [vr,fr] = await Promise.all([getJSON(FLEET+'/vessels'),getJSON(FLEET+'/forecast-modules')]);
|
| 1008 |
+
const V = vr.data||[], M = fr.data||[];
|
| 1009 |
+
setOut('f-raw',{vessels:V.length,forecast_modules:M.length,honesty:vr.honesty,sample:V.slice(0,1)});
|
| 1010 |
+
const poorCII = V.filter(v=>['D','E'].includes(String(v.ciiRating||'').toUpperCase())).length;
|
| 1011 |
+
const avgHull = V.length?Math.round(V.reduce((a,v)=>a+(v.hullCondition||0),0)/V.length):0;
|
| 1012 |
+
el('f-count').textContent=V.length; el('f-hull').textContent=avgHull; el('f-cii').textContent=poorCII;
|
| 1013 |
+
// 4 forecast modules → history+forecast lines
|
| 1014 |
+
M.slice(0,4).forEach((m,i)=>{
|
| 1015 |
+
const tt=el('f-fc-t'+i); if(tt) tt.innerHTML='<b style="color:#c9b787">'+esc(m.title)+'</b> · '+esc(m.metric)+' · forecast '+esc(m.forecastValue)+' ('+esc(m.confidence)+'% conf, '+esc(m.trend)+')';
|
| 1016 |
+
const pts=m.dataPoints||[]; const labels=pts.map(p=>p.date);
|
| 1017 |
+
// history = points without a forecast field; forecast = points WITH forecast (plus boundary point to connect)
|
| 1018 |
+
let firstFcst=pts.findIndex(p=>p.forecast!=null);
|
| 1019 |
+
const actual=pts.map((p,j)=> (firstFcst<0||j<firstFcst)? p.value : (j===firstFcst? p.value : null));
|
| 1020 |
+
const fcst=pts.map((p,j)=> (firstFcst>=0 && j>=firstFcst-1 && j>=0 && (p.forecast!=null|| j===firstFcst-1))? p.value : null);
|
| 1021 |
+
lineForecast('f-fc-'+i,labels,actual,fcst);
|
| 1022 |
+
});
|
| 1023 |
+
// CII mix bar
|
| 1024 |
+
const order=['A','B','C','D','E']; const counts=order.map(r=>V.filter(v=>String(v.ciiRating||'').toUpperCase()===r).length);
|
| 1025 |
+
const cols=['#5fb3a3','#7fb98f','#c9b787','#c9a05f','#b06a5a'];
|
| 1026 |
+
barV('f-cii-bar',order,counts,cols);
|
| 1027 |
+
// register table
|
| 1028 |
+
const h=el('f-list'); h.innerHTML=''; el('f-reg-ep').textContent=V.length+' vessels';
|
| 1029 |
+
V.forEach(v=>{
|
| 1030 |
+
const r=String(v.ciiRating||'').toUpperCase();
|
| 1031 |
+
const rc = (r==='A'||r==='B')?'b-live':(r==='C')?'b-warn':'b-err';
|
| 1032 |
+
h.insertAdjacentHTML('beforeend',`<div class="row">
|
| 1033 |
+
<span class="badge ${rc}">CII ${esc(r)}</span>
|
| 1034 |
+
<span><b>${esc(v.name)}</b> <span class="mono dim" style="font-size:10px">IMO ${esc(v.imo)}</span></span>
|
| 1035 |
+
<span class="mono dim" style="font-size:11px">${esc(v.vesselType)} · ${esc(v.shipClass)} · ${esc(v.flag)}</span>
|
| 1036 |
+
<span class="spacer mono dim" style="font-size:10px">hull ${v.hullCondition??'?'} · eng ${v.engineHealth??'?'} · TCE $${(v.tce||0).toLocaleString()} · util ${v.utilization??'?'}% · EEXI ${v.eexi??'?'} · ${esc(v.operator)} · ${esc(v.classificationSociety)}</span>
|
| 1037 |
+
</div>`);
|
| 1038 |
+
});
|
| 1039 |
+
if(!V.length) h.innerHTML='<div class="row mono dim">no vessels</div>';
|
| 1040 |
+
}catch(e){setHTML('f-list','<div class="row mono dim">retry: '+esc(e.message)+'</div>');setOut('f-raw','retry: '+e.message);}
|
| 1041 |
+
}},
|
| 1042 |
+
|
| 1043 |
+
// ── Maintenance & Compliance ────────────────────────────────────
|
| 1044 |
+
fleetmaint:{title:'Maintenance & Compliance',badge:'PREDICTIVE · CERTS · PSC',sub:'Where the fleet needs attention: predicted component failures (probability, date, cost, risk), certificates expiring soon, and port-state-control deficiencies with their SOLAS / MARPOL citations. Sample fleet dataset — not a live AIS / class-society feed.',
|
| 1045 |
+
render:async(c)=>{
|
| 1046 |
+
c.innerHTML=`<div class="kpis">
|
| 1047 |
+
<div class="kpi"><div class="k">High-risk components</div><div class="v err" id="fm-high">—</div><div class="d">predicted failure</div></div>
|
| 1048 |
+
<div class="kpi"><div class="k">Certs expiring/expired</div><div class="v warn" id="fm-cert">—</div><div class="d">need renewal</div></div>
|
| 1049 |
+
<div class="kpi"><div class="k">Open PSC deficiencies</div><div class="v warn" id="fm-psc">—</div><div class="d">Paris/Tokyo/Riyadh MOU</div></div>
|
| 1050 |
+
<div class="kpi"><div class="k">Receipts</div><div class="v teal">Signed</div><div class="d">verify offline</div></div>
|
| 1051 |
+
</div>
|
| 1052 |
+
<div class="card"><div class="card-h"><span class="card-t">Predictive maintenance — failure probability</span><span class="card-ep">per component</span></div>
|
| 1053 |
+
<div class="chartbox tall"><canvas id="fm-pm-bar"></canvas></div>
|
| 1054 |
+
<div id="fm-pm-list"><div class="row mono dim">loading…</div></div></div>
|
| 1055 |
+
<div class="grid2">
|
| 1056 |
+
<div class="card"><div class="card-h"><span class="card-t">Certificates expiring soon</span></div><div id="fm-cert-list"><div class="row mono dim">loading…</div></div></div>
|
| 1057 |
+
<div class="card"><div class="card-h"><span class="card-t">Port-state-control deficiencies</span></div><div id="fm-psc-list"><div class="row mono dim">loading…</div></div></div>
|
| 1058 |
+
</div>
|
| 1059 |
+
<details class="raw"><summary>raw /fleet/predictive-maintenance · /compliance-certificates · /port-state-deficiencies</summary><pre class="out" id="fm-raw">—</pre></details>
|
| 1060 |
+
<div class="honesty"><b>Honest by design.</b> <b>Sample fleet dataset — not a live AIS/class-society feed.</b> Predicted failures, certificate dates and port-state deficiencies are real, citable demo content from the SZL platform dataset (seed-data/vessels/*), not live class-society or MOU records. Receipts are <b>genuinely signed</b> and verifiable offline.</div>`;
|
| 1061 |
+
try{
|
| 1062 |
+
const [pm,cc,psd]=await Promise.all([getJSON(FLEET+'/predictive-maintenance'),getJSON(FLEET+'/compliance-certificates'),getJSON(FLEET+'/port-state-deficiencies')]);
|
| 1063 |
+
const PM=pm.data||[],CC=cc.data||[],PSD=psd.data||[];
|
| 1064 |
+
setOut('fm-raw',{predictive_maintenance:PM.length,certificates:CC.length,deficiencies:PSD.length,honesty:pm.honesty});
|
| 1065 |
+
el('fm-high').textContent=PM.filter(p=>String(p.riskLevel||'').toLowerCase()==='high').length;
|
| 1066 |
+
el('fm-cert').textContent=CC.filter(c=>['Expiring Soon','Expired'].includes(c.status)).length;
|
| 1067 |
+
el('fm-psc').textContent=PSD.filter(p=>String(p.status||'').toLowerCase()==='open').length;
|
| 1068 |
+
// predictive maintenance bar (failure probability), colored by risk
|
| 1069 |
+
const labs=PM.map(p=>p.component.length>26?p.component.slice(0,24)+'…':p.component);
|
| 1070 |
+
const vals=PM.map(p=>p.failureProbability||0);
|
| 1071 |
+
const rc=r=>r==='High'?RED:r==='Medium'?WARN:TEAL;
|
| 1072 |
+
barH('fm-pm-bar',labs,vals,PM.map(p=>rc(p.riskLevel)));
|
| 1073 |
+
const ph=el('fm-pm-list'); ph.innerHTML='';
|
| 1074 |
+
PM.forEach(p=>{const cls=p.riskLevel==='High'?'b-err':p.riskLevel==='Medium'?'b-warn':'b-live';
|
| 1075 |
+
ph.insertAdjacentHTML('beforeend',`<div class="row">
|
| 1076 |
+
<span class="badge ${cls}">${esc(p.riskLevel)} · ${p.failureProbability}%</span>
|
| 1077 |
+
<span><b>${esc(p.vesselName)}</b> — ${esc(p.component)}</span>
|
| 1078 |
+
<span class="spacer mono dim" style="font-size:10px">by ${esc(p.predictedFailureDate)} · est $${(p.estimatedCost||0).toLocaleString()} · ${esc(p.recommendedAction)}</span>
|
| 1079 |
+
</div>`);});
|
| 1080 |
+
const ch=el('fm-cert-list'); ch.innerHTML='';
|
| 1081 |
+
CC.sort((a,b)=>(a.daysUntilExpiry??9999)-(b.daysUntilExpiry??9999)).forEach(cert=>{
|
| 1082 |
+
const cls=cert.status==='Expired'?'b-err':cert.status==='Expiring Soon'?'b-warn':'b-live';
|
| 1083 |
+
ch.insertAdjacentHTML('beforeend',`<div class="row">
|
| 1084 |
+
<span class="badge ${cls}">${esc(cert.status)}</span>
|
| 1085 |
+
<span><b>${esc(cert.vesselName)}</b> — ${esc(cert.certificateType)}</span>
|
| 1086 |
+
<span class="spacer mono dim" style="font-size:10px">${esc(cert.issuer)} · exp ${esc(cert.expiryDate)} (${cert.daysUntilExpiry}d) · ${esc(cert.regulation)}</span>
|
| 1087 |
+
</div>`);});
|
| 1088 |
+
const sh=el('fm-psc-list'); sh.innerHTML='';
|
| 1089 |
+
PSD.forEach(p=>{const cls=p.severity==='High'?'b-err':p.severity==='Medium'?'b-warn':'b-live';
|
| 1090 |
+
sh.insertAdjacentHTML('beforeend',`<div class="row">
|
| 1091 |
+
<span class="badge ${cls}">${esc(p.severity)} · ${esc(p.status)}</span>
|
| 1092 |
+
<span><b>${esc(p.vesselName)}</b> <span class="mono dim" style="font-size:10px">code ${esc(p.deficiencyCode)} · ${esc(p.mouRegime)} · ${esc(p.port)}</span></span>
|
| 1093 |
+
<span class="spacer mono dim" style="font-size:10px">${esc(p.description)}</span>
|
| 1094 |
+
</div>`);});
|
| 1095 |
+
}catch(e){setHTML('fm-pm-list','<div class="row mono dim">retry: '+esc(e.message)+'</div>');setOut('fm-raw','retry: '+e.message);}
|
| 1096 |
+
}},
|
| 1097 |
+
|
| 1098 |
+
// ── Fleet Briefings ─────────────────────────────────────────────
|
| 1099 |
+
fleetbrief:{title:'Fleet Briefings',badge:'REGULATION-CITED',sub:'AI-generated fleet briefings, each citing the regulation behind it (MARPOL Annex VI, IMO MEPC.352/355(78)), with a confidence %, the affected vessels, and concrete action items. Sample fleet dataset — not a live AIS / class-society feed.',
|
| 1100 |
+
render:async(c)=>{
|
| 1101 |
+
c.innerHTML=`<div class="kpis">
|
| 1102 |
+
<div class="kpi"><div class="k">Briefings</div><div class="v" id="fb-count">—</div><div class="d">in sample set</div></div>
|
| 1103 |
+
<div class="kpi"><div class="k">Critical</div><div class="v err" id="fb-crit">—</div><div class="d">highest severity</div></div>
|
| 1104 |
+
<div class="kpi"><div class="k">Avg confidence</div><div class="v teal" id="fb-conf">—</div><div class="d">advisory, not proven</div></div>
|
| 1105 |
+
<div class="kpi"><div class="k">Trust gate</div><div class="v teal">Conjecture</div><div class="d">advisory, not proven</div></div>
|
| 1106 |
+
</div>
|
| 1107 |
+
<div id="fb-cards"><div class="row mono dim">loading…</div></div>
|
| 1108 |
+
<details class="raw"><summary>raw /fleet/ai-briefings</summary><pre class="out" id="fb-raw">—</pre></details>
|
| 1109 |
+
<div class="honesty"><b>Honest by design.</b> <b>Sample fleet dataset — not a live AIS/class-society feed.</b> These briefings are real, citable demo content from the SZL platform dataset (seed-data/vessels/*). Confidence is an <b>advisory</b> figure (the trust score is a documented conjecture, not a proven oracle), and the regulatory citations point to the genuine instruments (MARPOL Annex VI, IMO MEPC.352/355(78)).</div>`;
|
| 1110 |
+
try{
|
| 1111 |
+
const br=await getJSON(FLEET+'/ai-briefings'); const B=br.data||[];
|
| 1112 |
+
setOut('fb-raw',{briefings:B.length,honesty:br.honesty});
|
| 1113 |
+
el('fb-count').textContent=B.length;
|
| 1114 |
+
el('fb-crit').textContent=B.filter(b=>String(b.severity||'').toLowerCase()==='critical').length;
|
| 1115 |
+
el('fb-conf').textContent=B.length?Math.round(B.reduce((a,b)=>a+(b.confidence||0),0)/B.length)+'%':'—';
|
| 1116 |
+
const h=el('fb-cards'); h.innerHTML='';
|
| 1117 |
+
B.forEach(b=>{
|
| 1118 |
+
const sev=String(b.severity||'').toLowerCase();
|
| 1119 |
+
const sc = sev==='critical'?'b-err':sev==='warning'?'b-warn':'b-live';
|
| 1120 |
+
const ai=(b.actionItems||[]).map(a=>'<li>'+esc(a)+'</li>').join('');
|
| 1121 |
+
const av=(b.affectedVessels||[]).map(v=>'<span class="badge b-teal" style="margin:1px">'+esc(v)+'</span>').join(' ');
|
| 1122 |
+
h.insertAdjacentHTML('beforeend',`<div class="card">
|
| 1123 |
+
<div class="card-h"><span class="card-t">${esc(b.title)}</span><span class="badge ${sc}">${esc(b.severity)} · ${esc(b.category)}</span></div>
|
| 1124 |
+
<div style="margin:.3rem 0;line-height:1.6">${esc(b.summary)}</div>
|
| 1125 |
+
<div class="mono dim" style="font-size:11px;line-height:1.7;margin:.4rem 0">${esc(b.details)}</div>
|
| 1126 |
+
<div style="margin:.4rem 0"><b style="color:#c9b787;font-size:12px">Action items</b><ul style="margin:.2rem 0 .2rem 1.1rem;font-size:12px;line-height:1.6">${ai}</ul></div>
|
| 1127 |
+
<div style="margin:.3rem 0;font-size:11px"><span class="mono dim">Affected:</span> ${av}</div>
|
| 1128 |
+
<div class="legend"><span>Confidence ${esc(b.confidence)}% (advisory) · generated ${esc((b.generatedAt||'').slice(0,10))}</span></div>
|
| 1129 |
+
</div>`);
|
| 1130 |
+
});
|
| 1131 |
+
if(!B.length) h.innerHTML='<div class="row mono dim">no briefings</div>';
|
| 1132 |
+
}catch(e){setHTML('fb-cards','<div class="row mono dim">retry: '+esc(e.message)+'</div>');setOut('fb-raw','retry: '+e.message);}
|
| 1133 |
+
}},
|
| 1134 |
+
|
| 1135 |
// ── 3.4 ROE Policy Editor ────────────────────────────────────────
|
| 1136 |
roe:{title:'Engagement Rules',badge:'LIVE POLICY',sub:'The rules that decide whether a drone may be engaged — speed limits, required ID, allowed directions. Test a track against the current rules; every check produces a genuinely signed receipt.',
|
| 1137 |
render:async(c)=>{
|
|
|
|
| 1265 |
<div class="btns"><button class="btn teal" onclick="bft_exec()">▶ Execute demo mission</button></div>
|
| 1266 |
<div id="bft-exec-summary" class="mono dim" style="font-size:12px;margin-bottom:.5rem">— click to execute —</div>
|
| 1267 |
<details class="raw"><summary>raw /uds/v1/mission/execute</summary><pre class="out" id="bft-exec-out">—</pre></details></div>
|
| 1268 |
+
<div id="bft-note" class="mono dim" style="font-size:12px;margin:.4rem 0"></div>
|
| 1269 |
<details class="raw"><summary>raw /uds/v1/healthz</summary><pre class="out" id="bft-raw">—</pre></details>
|
| 1270 |
${HONEST}`;
|
| 1271 |
try{
|
| 1272 |
const d = await getJSON(BASE+'/api/killinchu/uds/v1/healthz');
|
| 1273 |
setOut('bft-raw',d);
|
| 1274 |
+
const q = d.quorum || {};
|
|
|
|
| 1275 |
const organs=Object.entries(d.organs||{});
|
| 1276 |
let online=0; organs.forEach(([,o])=>{if(o.status==='ok')online++;});
|
| 1277 |
+
const total = (q.total!=null)?q.total:organs.length;
|
| 1278 |
+
const needed = (q.needed!=null)?q.needed:Math.floor(total/2)+1;
|
| 1279 |
+
// Consensus holds when a strict majority is reachable. Critically this does
|
| 1280 |
+
// NOT require amaru — quorum_without_amaru proves the system survives amaru
|
| 1281 |
+
// being fully decommissioned. Fall back to quorum_possible / online math.
|
| 1282 |
+
const holds = (q.quorum_without_amaru!=null) ? q.quorum_without_amaru
|
| 1283 |
+
: (q.quorum_possible!=null) ? q.quorum_possible
|
| 1284 |
+
: (d.quorum_possible!=null) ? d.quorum_possible
|
| 1285 |
+
: (online>=needed);
|
| 1286 |
+
el('k-quorum').textContent = holds ? 'CONSENSUS HOLDS' : 'NO QUORUM';
|
| 1287 |
+
el('k-quorum').className = 'v '+(holds?'live':'warn');
|
| 1288 |
if(el('bft-count')) el('bft-count').textContent=online;
|
| 1289 |
+
doughnut('bft-donut',['online','unreachable'],[online,Math.max(0,total-online)],[TEAL,RED]);
|
| 1290 |
+
// Honest messaging: explain WHY it still holds even if a node is offline.
|
| 1291 |
+
const amaruOff = q.amaru_offline===true || (d.organs&&d.organs.amaru&&d.organs.amaru.status!=='ok');
|
| 1292 |
+
let note='';
|
| 1293 |
+
if(holds && amaruOff){
|
| 1294 |
+
note = `<span class="badge b-live">CONSENSUS HOLDS</span> ${online} of ${total} systems online — a majority (need ${needed}) is reachable. The Cortex node (amaru) is offline and is being decommissioned; consensus does <b>not</b> depend on it.`;
|
| 1295 |
+
} else if(holds){
|
| 1296 |
+
note = `<span class="badge b-live">CONSENSUS HOLDS</span> ${online} of ${total} systems online — majority threshold of ${needed} met.`;
|
| 1297 |
+
} else {
|
| 1298 |
+
note = `<span class="badge b-warn">NO QUORUM</span> only ${online} of ${total} systems online — need at least ${needed} to proceed. Actions are held (fail-closed).`;
|
| 1299 |
+
}
|
| 1300 |
+
if(el('bft-note')) el('bft-note').innerHTML = note;
|
| 1301 |
const h = el('bft-organs'); h.innerHTML='';
|
| 1302 |
organs.forEach(([name,o])=>{
|
| 1303 |
const ok = o.status==='ok';
|
| 1304 |
+
const optional = o.essential===false;
|
| 1305 |
+
const stTxt = ok ? 'ONLINE' : (esc(String(o.status||'').toUpperCase())||'DOWN');
|
| 1306 |
+
const badgeCls = ok ? 'b-live' : (optional ? 'b-warn' : 'b-err');
|
| 1307 |
+
const tag = optional ? ' <span class="mono dim" style="font-size:10px">(optional · being retired)</span>' : '';
|
| 1308 |
h.insertAdjacentHTML('beforeend',`<div class="row">
|
| 1309 |
+
<span class="badge ${badgeCls}">${stTxt}</span>
|
| 1310 |
+
<span>${esc(name)}${tag}</span>
|
| 1311 |
<span class="spacer mono dim">${o.local?'local':''} ${o.http?'HTTP '+o.http:''} ${o.latency_ms?Math.round(o.latency_ms)+'ms':''}</span>
|
| 1312 |
</div>`);
|
| 1313 |
});
|
|
|
|
| 1567 |
<div class="kpi"><div class="k">Organs reachable</div><div class="v live" id="org-reach">probing…</div></div>
|
| 1568 |
<div class="kpi"><div class="k">Links to brain</div><div class="v teal" id="org-links">—</div></div>
|
| 1569 |
<div class="kpi"><div class="k">Signed spans</div><div class="v" id="org-spans">—</div><div class="d">live audit depth</div></div></div>
|
| 1570 |
+
<div class="card"><div class="card-h"><span class="card-t">The brain at the center — live 3D</span><span class="card-ep">drag to orbit · particles = live links</span></div><div class="graph3d hero" id="org-3d"><div class="org-loading" id="org-loading"><div class="org-pulse"></div><div class="mono dim" style="margin-top:.8rem;font-size:12px">probing live organism… connecting to shared brain</div></div></div><div class="brain-note">a11oy is the orchestrator brain: every edge terminates at the gold core. killinchu is the field/Navy surface sharing that brain. Health from the live observability probe.</div></div>
|
| 1571 |
<div class="card"><div class="card-h"><span class="card-t">Organ health detail</span><span class="card-ep">live probe</span></div><div id="org-host"><div class="row mono dim">probing…</div></div>
|
| 1572 |
<details class="raw"><summary>raw /observability/summary</summary><pre class="out" id="org-raw">loading…</pre></details></div>${HONEST}`;window.organism_load();}},
|
| 1573 |
|
|
@@ -0,0 +1,243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
killinchu_fleet_vessels.py — FLEET (Vessels) commercial-fleet surface (GAP-1 + GAP-2).
|
| 3 |
+
|
| 4 |
+
ADDITIVE module. serve.py imports this and calls register(app) AFTER the other
|
| 5 |
+
routers but BEFORE the SPA catch-all. It does NOT touch any existing route.
|
| 6 |
+
|
| 7 |
+
GAP-1 — serve the REAL platform seed-data/vessels/* datasets VERBATIM as static
|
| 8 |
+
JSON endpoints under /api/killinchu/v1/fleet/*. These are clearly-labelled SAMPLE
|
| 9 |
+
fleet data, NOT a live AIS / class-society feed.
|
| 10 |
+
|
| 11 |
+
GET /api/killinchu/v1/fleet/vessels -> seed-data/vessels/vessels.json
|
| 12 |
+
GET /api/killinchu/v1/fleet/forecast-modules -> seed-data/vessels/forecast-modules.json
|
| 13 |
+
GET /api/killinchu/v1/fleet/predictive-maintenance -> seed-data/vessels/predictive-maintenance.json
|
| 14 |
+
GET /api/killinchu/v1/fleet/compliance-certificates -> seed-data/vessels/compliance-certificates.json
|
| 15 |
+
GET /api/killinchu/v1/fleet/port-state-deficiencies -> seed-data/vessels/port-state-deficiencies.json
|
| 16 |
+
GET /api/killinchu/v1/fleet/ai-briefings -> seed-data/vessels/ai-briefings.json
|
| 17 |
+
GET /api/killinchu/v1/fleet/all -> {datasets..., honesty}
|
| 18 |
+
|
| 19 |
+
GAP-2 — the vessels vertical "Voyage Risk Exchange" governed-decision loop, ported
|
| 20 |
+
VERBATIM (pure functions) from platform services/verticals/vessels/{signals,forecast,
|
| 21 |
+
evidence,recommendations,brief}.py + contracts.py.
|
| 22 |
+
|
| 23 |
+
GET /api/killinchu/v1/fleet/voyage-risk -> full 5-stage loop result
|
| 24 |
+
(signals -> forecast -> evidence
|
| 25 |
+
-> recommendation w/ rollback -> brief)
|
| 26 |
+
|
| 27 |
+
Honesty doctrine (absolute): trust score Λ = Conjecture 1 (NOT a theorem); the
|
| 28 |
+
Recommendation is ADVISORY with a human-approval flag and a rollback path; the
|
| 29 |
+
datasets are sample/recorded, not a live feed.
|
| 30 |
+
|
| 31 |
+
Source provenance: github.com/szl-holdings/platform seed-data/vessels/* (datasets)
|
| 32 |
+
and services/verticals/vessels/* (decision loop), fetched via the GitHub contents
|
| 33 |
+
API and embedded verbatim.
|
| 34 |
+
"""
|
| 35 |
+
from __future__ import annotations
|
| 36 |
+
|
| 37 |
+
import json
|
| 38 |
+
import os
|
| 39 |
+
from typing import Any
|
| 40 |
+
|
| 41 |
+
try:
|
| 42 |
+
from fastapi import APIRouter
|
| 43 |
+
from fastapi.responses import JSONResponse
|
| 44 |
+
except Exception: # pragma: no cover
|
| 45 |
+
APIRouter = None # type: ignore
|
| 46 |
+
JSONResponse = None # type: ignore
|
| 47 |
+
|
| 48 |
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
| 49 |
+
_DATA_PATH = os.path.join(_HERE, "fleet_vessels_data.json")
|
| 50 |
+
|
| 51 |
+
HONESTY_LABEL = "Sample fleet dataset — not a live AIS/class-society feed."
|
| 52 |
+
|
| 53 |
+
# ---------------------------------------------------------------------------
|
| 54 |
+
# Dataset loader — read the embedded verbatim platform seed-data once.
|
| 55 |
+
# ---------------------------------------------------------------------------
|
| 56 |
+
_DATASETS: dict[str, Any] = {}
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def _load() -> dict[str, Any]:
|
| 60 |
+
global _DATASETS
|
| 61 |
+
if _DATASETS:
|
| 62 |
+
return _DATASETS
|
| 63 |
+
try:
|
| 64 |
+
with open(_DATA_PATH, "r", encoding="utf-8") as fh:
|
| 65 |
+
_DATASETS = json.load(fh)
|
| 66 |
+
except Exception:
|
| 67 |
+
_DATASETS = {
|
| 68 |
+
"vessels": [],
|
| 69 |
+
"forecast-modules": [],
|
| 70 |
+
"predictive-maintenance": [],
|
| 71 |
+
"compliance-certificates": [],
|
| 72 |
+
"port-state-deficiencies": [],
|
| 73 |
+
"ai-briefings": [],
|
| 74 |
+
}
|
| 75 |
+
return _DATASETS
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
# ---------------------------------------------------------------------------
|
| 79 |
+
# GAP-2 — Voyage Risk Exchange governed-decision loop (ported verbatim).
|
| 80 |
+
# Pure functions, no network. Mirrors platform services/verticals/vessels/*.
|
| 81 |
+
# ---------------------------------------------------------------------------
|
| 82 |
+
def _signals_collect() -> list[dict[str, object]]:
|
| 83 |
+
return [
|
| 84 |
+
{
|
| 85 |
+
"id": "sig_vessels_eta_drift",
|
| 86 |
+
"source": "ais",
|
| 87 |
+
"kind": "delay_risk",
|
| 88 |
+
"summary": "ETA drift +18h on charter VL-7714 since last port call",
|
| 89 |
+
"weight": 0.8,
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"id": "sig_vessels_route_advisory",
|
| 93 |
+
"source": "weather",
|
| 94 |
+
"kind": "route_risk",
|
| 95 |
+
"summary": "Beaufort 8 advisory active on planned routing window",
|
| 96 |
+
"weight": 0.7,
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"id": "sig_vessels_compliance_check",
|
| 100 |
+
"source": "compliance",
|
| 101 |
+
"kind": "compliance_gap",
|
| 102 |
+
"summary": "Sanctions screening not refreshed in last 14 days for counterparty",
|
| 103 |
+
"weight": 0.85,
|
| 104 |
+
},
|
| 105 |
+
]
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def _forecast_compute(signals: list[dict[str, Any]]) -> dict[str, Any]:
|
| 109 |
+
return {
|
| 110 |
+
"horizon": "next_voyage",
|
| 111 |
+
"method": "voyage-risk-baseline-v0",
|
| 112 |
+
"delay_risk": "elevated",
|
| 113 |
+
"route_risk": "moderate",
|
| 114 |
+
"claims_risk_placeholder": "watch",
|
| 115 |
+
"confidence": 0.6,
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def _evidence_gather(signals: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
| 120 |
+
return [
|
| 121 |
+
{
|
| 122 |
+
"id": f"ev_vessels_{s['id']}",
|
| 123 |
+
"from_signal": s["id"],
|
| 124 |
+
"source": s["source"],
|
| 125 |
+
"claim": s["summary"],
|
| 126 |
+
}
|
| 127 |
+
for s in signals
|
| 128 |
+
]
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def _recommendation_build(
|
| 132 |
+
*, signals: list[dict[str, Any]], forecast: dict[str, Any], evidence: list[dict[str, Any]]
|
| 133 |
+
) -> dict[str, Any]:
|
| 134 |
+
# Mirrors contracts.Recommendation dataclass shape (to_dict()).
|
| 135 |
+
return {
|
| 136 |
+
"id": "rec_vessels_refresh_sanctions_screen",
|
| 137 |
+
"vertical": "vessels",
|
| 138 |
+
"title": "Refresh sanctions screening for counterparty before bunkering",
|
| 139 |
+
"owner": "vessels-ops@szl",
|
| 140 |
+
"confidence": float(forecast.get("confidence", 0.6)),
|
| 141 |
+
"evidence_ids": [e["id"] for e in evidence],
|
| 142 |
+
"next_action": "Re-run sanctions screen and document refresh in voyage flight recorder.",
|
| 143 |
+
"rollback_path": "If counterparty fails refreshed screen, hold bunkering and escalate to compliance.",
|
| 144 |
+
"requires_human_approval": True,
|
| 145 |
+
"input_class": "vessels_voyage_signals_v1",
|
| 146 |
+
"output_class": "voyage_risk_recommendation_v1",
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def _brief_synthesise(
|
| 151 |
+
*,
|
| 152 |
+
signals: list[dict[str, Any]],
|
| 153 |
+
forecast: dict[str, Any],
|
| 154 |
+
evidence: list[dict[str, Any]],
|
| 155 |
+
recommendation: dict[str, Any],
|
| 156 |
+
) -> dict[str, Any]:
|
| 157 |
+
return {
|
| 158 |
+
"headline": recommendation["title"],
|
| 159 |
+
"delay_risk": forecast.get("delay_risk"),
|
| 160 |
+
"route_risk": forecast.get("route_risk"),
|
| 161 |
+
"claims_risk_placeholder": forecast.get("claims_risk_placeholder"),
|
| 162 |
+
"evidence_count": len(evidence),
|
| 163 |
+
"next_action": recommendation["next_action"],
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def voyage_risk_loop() -> dict[str, Any]:
|
| 168 |
+
"""Run the full 5-stage governed-decision loop and return every stage."""
|
| 169 |
+
signals = _signals_collect()
|
| 170 |
+
forecast = _forecast_compute(signals)
|
| 171 |
+
evidence = _evidence_gather(signals)
|
| 172 |
+
recommendation = _recommendation_build(signals=signals, forecast=forecast, evidence=evidence)
|
| 173 |
+
brief = _brief_synthesise(
|
| 174 |
+
signals=signals, forecast=forecast, evidence=evidence, recommendation=recommendation
|
| 175 |
+
)
|
| 176 |
+
return {
|
| 177 |
+
"stages": ["signals", "forecast", "evidence", "recommendation", "brief"],
|
| 178 |
+
"signals": signals,
|
| 179 |
+
"forecast": forecast,
|
| 180 |
+
"evidence": evidence,
|
| 181 |
+
"recommendation": recommendation,
|
| 182 |
+
"brief": brief,
|
| 183 |
+
"trust_gate": {
|
| 184 |
+
"lambda_status": "Conjecture 1 (advisory, NOT a theorem)",
|
| 185 |
+
"advisory": True,
|
| 186 |
+
},
|
| 187 |
+
"honesty": (
|
| 188 |
+
"Voyage Risk Exchange — governed-decision loop ported verbatim from the "
|
| 189 |
+
"platform vessels vertical (signals -> forecast -> evidence -> recommendation "
|
| 190 |
+
"-> brief). The recommendation is ADVISORY and requires human approval; the "
|
| 191 |
+
"trust score is a documented Conjecture, not a proven oracle. Sample inputs."
|
| 192 |
+
),
|
| 193 |
+
"source": "platform services/verticals/vessels/{signals,forecast,evidence,recommendations,brief}.py",
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
# ---------------------------------------------------------------------------
|
| 198 |
+
# Route registration (ADDITIVE).
|
| 199 |
+
# ---------------------------------------------------------------------------
|
| 200 |
+
def register(app) -> dict[str, Any]:
|
| 201 |
+
if APIRouter is None:
|
| 202 |
+
return {"module": "killinchu_fleet_vessels", "registered_count": 0, "error": "fastapi missing"}
|
| 203 |
+
|
| 204 |
+
data = _load()
|
| 205 |
+
router = APIRouter()
|
| 206 |
+
base = "/api/killinchu/v1/fleet"
|
| 207 |
+
registered: list[str] = []
|
| 208 |
+
|
| 209 |
+
def _serve(key: str):
|
| 210 |
+
async def _h() -> JSONResponse:
|
| 211 |
+
return JSONResponse(
|
| 212 |
+
{"data": data.get(key, []), "honesty": HONESTY_LABEL, "source_key": key}
|
| 213 |
+
)
|
| 214 |
+
return _h
|
| 215 |
+
|
| 216 |
+
for key, path in [
|
| 217 |
+
("vessels", "/vessels"),
|
| 218 |
+
("forecast-modules", "/forecast-modules"),
|
| 219 |
+
("predictive-maintenance", "/predictive-maintenance"),
|
| 220 |
+
("compliance-certificates", "/compliance-certificates"),
|
| 221 |
+
("port-state-deficiencies", "/port-state-deficiencies"),
|
| 222 |
+
("ai-briefings", "/ai-briefings"),
|
| 223 |
+
]:
|
| 224 |
+
router.add_api_route(f"{base}{path}", _serve(key), methods=["GET"])
|
| 225 |
+
registered.append(f"{base}{path}")
|
| 226 |
+
|
| 227 |
+
@router.get(f"{base}/all")
|
| 228 |
+
async def _all() -> JSONResponse:
|
| 229 |
+
return JSONResponse({
|
| 230 |
+
"datasets": data,
|
| 231 |
+
"counts": {k: (len(v) if isinstance(v, list) else None) for k, v in data.items()},
|
| 232 |
+
"honesty": HONESTY_LABEL,
|
| 233 |
+
"source": "github.com/szl-holdings/platform seed-data/vessels/*",
|
| 234 |
+
})
|
| 235 |
+
registered.append(f"{base}/all")
|
| 236 |
+
|
| 237 |
+
@router.get(f"{base}/voyage-risk")
|
| 238 |
+
async def _voyage() -> JSONResponse:
|
| 239 |
+
return JSONResponse(voyage_risk_loop())
|
| 240 |
+
registered.append(f"{base}/voyage-risk")
|
| 241 |
+
|
| 242 |
+
app.include_router(router)
|
| 243 |
+
return {"module": "killinchu_fleet_vessels", "registered_count": len(registered), "routes": registered}
|
|
@@ -128,6 +128,11 @@ ORGANS = {
|
|
| 128 |
# doctrine-intended path /api/amaru/chakra/tick is GET-only live; this is
|
| 129 |
# the operational POST that yields the 13-axis Λ.
|
| 130 |
"tick_path": "/api/amaru/v1/cortex/with-rosie", "health_path": "/healthz",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
},
|
| 132 |
"a11oy": {
|
| 133 |
"label": "Policy Officer", "quechua": "a11oy", "icon": "📜",
|
|
@@ -659,25 +664,62 @@ def register(app, space: str = "killinchu") -> dict[str, Any]:
|
|
| 659 |
return JSONResponse({"status": "degraded", "reason": "httpx unavailable"}, status_code=503)
|
| 660 |
async with httpx.AsyncClient() as client:
|
| 661 |
async def _probe(name: str, cfg: dict):
|
|
|
|
| 662 |
if cfg["base"] == "local":
|
| 663 |
-
return name, {"status": "ok", "local": True}
|
| 664 |
url = cfg["base"] + cfg.get("health_path", "/healthz")
|
|
|
|
|
|
|
|
|
|
| 665 |
t0 = time.monotonic()
|
| 666 |
try:
|
| 667 |
-
r = await client.get(url, timeout=
|
| 668 |
return name, {"status": "ok" if r.status_code == 200 else "amber",
|
| 669 |
-
"http": r.status_code, "
|
|
|
|
| 670 |
except Exception as e:
|
| 671 |
return name, {"status": "red", "error": f"{type(e).__name__}",
|
|
|
|
| 672 |
"latency_ms": round((time.monotonic()-t0)*1000, 1)}
|
| 673 |
organ_health = dict(await asyncio.gather(*[_probe(n, c) for n, c in ORGANS.items()]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 674 |
all_ok = all(v.get("status") == "ok" for v in organ_health.values())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 675 |
return JSONResponse({
|
| 676 |
-
"status": "
|
| 677 |
"doctrine_numbers": {"declarations": 749, "axioms": 14, "sorries": 163}, "lean_sha": LEAN_SHA,
|
| 678 |
"lambda_status": "Conjecture 1 (NOT a theorem)", "slsa": "L1 (honest)",
|
| 679 |
"signing_available": (_dsse.signing_available() if _dsse else False),
|
| 680 |
"keyid": getattr(_dsse, "KEYID", "szlholdings-cosign"), "organs": organ_health,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 681 |
"notice": DEFENSE_UNICORNS_NOTICE})
|
| 682 |
registered.append(f"{base}/healthz")
|
| 683 |
|
|
|
|
| 128 |
# doctrine-intended path /api/amaru/chakra/tick is GET-only live; this is
|
| 129 |
# the operational POST that yields the 13-axis Λ.
|
| 130 |
"tick_path": "/api/amaru/v1/cortex/with-rosie", "health_path": "/healthz",
|
| 131 |
+
# amaru is NON-ESSENTIAL: its Space is being decommissioned. Consensus must
|
| 132 |
+
# NOT hard-depend on it. We probe it with a short timeout and exclude it from
|
| 133 |
+
# the quorum-essential set so killinchu degrades gracefully when it is gone.
|
| 134 |
+
"essential": False,
|
| 135 |
+
"probe_timeout_s": float(os.environ.get("KILLINCHU_AMARU_TIMEOUT", "2.0")),
|
| 136 |
},
|
| 137 |
"a11oy": {
|
| 138 |
"label": "Policy Officer", "quechua": "a11oy", "icon": "📜",
|
|
|
|
| 664 |
return JSONResponse({"status": "degraded", "reason": "httpx unavailable"}, status_code=503)
|
| 665 |
async with httpx.AsyncClient() as client:
|
| 666 |
async def _probe(name: str, cfg: dict):
|
| 667 |
+
essential = cfg.get("essential", True)
|
| 668 |
if cfg["base"] == "local":
|
| 669 |
+
return name, {"status": "ok", "local": True, "essential": essential}
|
| 670 |
url = cfg["base"] + cfg.get("health_path", "/healthz")
|
| 671 |
+
# Non-essential organs (e.g. amaru, being decommissioned) get a short
|
| 672 |
+
# timeout so they never block the consensus health check.
|
| 673 |
+
timeout = cfg.get("probe_timeout_s", ORGAN_TIMEOUT_S)
|
| 674 |
t0 = time.monotonic()
|
| 675 |
try:
|
| 676 |
+
r = await client.get(url, timeout=timeout)
|
| 677 |
return name, {"status": "ok" if r.status_code == 200 else "amber",
|
| 678 |
+
"http": r.status_code, "essential": essential,
|
| 679 |
+
"latency_ms": round((time.monotonic()-t0)*1000, 1)}
|
| 680 |
except Exception as e:
|
| 681 |
return name, {"status": "red", "error": f"{type(e).__name__}",
|
| 682 |
+
"essential": essential,
|
| 683 |
"latency_ms": round((time.monotonic()-t0)*1000, 1)}
|
| 684 |
organ_health = dict(await asyncio.gather(*[_probe(n, c) for n, c in ORGANS.items()]))
|
| 685 |
+
# Consensus / quorum math. Quorum is a strict majority of the TOTAL organs and
|
| 686 |
+
# it must NOT depend on any single non-essential organ (amaru). We count
|
| 687 |
+
# healthy organs and check whether a majority is reachable without amaru.
|
| 688 |
+
total = len(organ_health)
|
| 689 |
+
quorum_needed = (total // 2) + 1 # strict majority, e.g. 3 of 4
|
| 690 |
+
healthy = [n for n, v in organ_health.items() if v.get("status") == "ok"]
|
| 691 |
+
healthy_essential = [n for n in healthy if organ_health[n].get("essential", True)]
|
| 692 |
+
# quorum_possible: can we form a majority from currently-healthy organs?
|
| 693 |
+
quorum_possible = len(healthy) >= quorum_needed
|
| 694 |
+
# quorum_without_amaru: would quorum still hold if amaru were fully gone?
|
| 695 |
+
healthy_no_amaru = [n for n in healthy if n != "amaru"]
|
| 696 |
+
quorum_without_amaru = len(healthy_no_amaru) >= quorum_needed
|
| 697 |
+
amaru_h = organ_health.get("amaru", {})
|
| 698 |
+
amaru_offline = amaru_h.get("status") != "ok"
|
| 699 |
all_ok = all(v.get("status") == "ok" for v in organ_health.values())
|
| 700 |
+
# Status: healthy quorum even without amaru => "ok" (graceful). Only "degraded"
|
| 701 |
+
# if a true majority cannot be formed.
|
| 702 |
+
if quorum_without_amaru:
|
| 703 |
+
consensus_status = "ok"
|
| 704 |
+
elif quorum_possible:
|
| 705 |
+
consensus_status = "ok"
|
| 706 |
+
else:
|
| 707 |
+
consensus_status = "degraded"
|
| 708 |
return JSONResponse({
|
| 709 |
+
"status": consensus_status, "all_organs_ok": all_ok, "doctrine": _doctrine_string(),
|
| 710 |
"doctrine_numbers": {"declarations": 749, "axioms": 14, "sorries": 163}, "lean_sha": LEAN_SHA,
|
| 711 |
"lambda_status": "Conjecture 1 (NOT a theorem)", "slsa": "L1 (honest)",
|
| 712 |
"signing_available": (_dsse.signing_available() if _dsse else False),
|
| 713 |
"keyid": getattr(_dsse, "KEYID", "szlholdings-cosign"), "organs": organ_health,
|
| 714 |
+
"quorum": {
|
| 715 |
+
"total": total, "needed": quorum_needed,
|
| 716 |
+
"healthy": len(healthy), "healthy_organs": healthy,
|
| 717 |
+
"quorum_possible": quorum_possible,
|
| 718 |
+
"quorum_without_amaru": quorum_without_amaru,
|
| 719 |
+
"amaru_offline": amaru_offline,
|
| 720 |
+
"amaru_essential": False,
|
| 721 |
+
},
|
| 722 |
+
"quorum_possible": quorum_possible,
|
| 723 |
"notice": DEFENSE_UNICORNS_NOTICE})
|
| 724 |
registered.append(f"{base}/healthz")
|
| 725 |
|
|
@@ -1676,6 +1676,25 @@ except Exception as _elite_e:
|
|
| 1676 |
_elite_tb.print_exc()
|
| 1677 |
# ── end ELITE ────────────────────────────────────────────────────────────────
|
| 1678 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1679 |
|
| 1680 |
# ===========================================================================
|
| 1681 |
# ADDITIVE: killinchu "Beyond-Cannonico" proof console — the autonomy-governance
|
|
|
|
| 1676 |
_elite_tb.print_exc()
|
| 1677 |
# ── end ELITE ────────────────────────────────────────────────────────────────
|
| 1678 |
|
| 1679 |
+
# ===========================================================================
|
| 1680 |
+
# ADDITIVE: FLEET (Vessels) commercial-fleet surface (GAP-1 + GAP-2).
|
| 1681 |
+
# Serves the REAL platform seed-data/vessels/* datasets VERBATIM as static JSON
|
| 1682 |
+
# endpoints under /api/killinchu/v1/fleet/*, plus the vessels-vertical "Voyage
|
| 1683 |
+
# Risk Exchange" governed-decision loop (signals->forecast->evidence->recommendation
|
| 1684 |
+
# ->brief), ported verbatim as pure functions. Datasets are clearly-labelled SAMPLE
|
| 1685 |
+
# data, NOT a live AIS / class-society feed. Mounted BEFORE the SPA catch-all.
|
| 1686 |
+
# Doctrine v11 LOCKED · Λ = Conjecture 1 · SLSA L2. NO mocks.
|
| 1687 |
+
# ===========================================================================
|
| 1688 |
+
try:
|
| 1689 |
+
import killinchu_fleet_vessels as _fleet
|
| 1690 |
+
_fleet_status = _fleet.register(app)
|
| 1691 |
+
print(f"[killinchu] FLEET vessels registered: {_fleet_status.get('registered_count')} routes", file=sys.stderr)
|
| 1692 |
+
except Exception as _fleet_e:
|
| 1693 |
+
import traceback as _fleet_tb
|
| 1694 |
+
print(f"[killinchu] FLEET vessels NOT registered: {_fleet_e!r}", file=sys.stderr)
|
| 1695 |
+
_fleet_tb.print_exc()
|
| 1696 |
+
# ── end FLEET ────────────────────────────────────────────────────────────────
|
| 1697 |
+
|
| 1698 |
|
| 1699 |
# ===========================================================================
|
| 1700 |
# ADDITIVE: killinchu "Beyond-Cannonico" proof console — the autonomy-governance
|